Bug 1244591 - Part 2: Extract useful keyframes tests to a new file r=birtles
authorRyo Kato <ryo_kato@hashedhyphen.com>
Sat, 16 Apr 2016 23:38:02 +0900
changeset 332329 4f2cae28821142346e72b97f09aae288f75571c2
parent 332328 e956fb34b9d8f9686d8fde7c86bd749e108c4971
child 332330 d2dc79c8d77cd1a0fe01ad01b07a0d026397a440
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1244591
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1244591 - Part 2: Extract useful keyframes tests to a new file r=birtles Extract a lot of tests from keyframe-effect/constructor.html and add some documentation so that we can reuse them in other tests. This helper will be used when we want to test these API: * KeyframeEffect(ReadOnly) Constructor * KeyframeEffect.setFrames() * SharedKeyframeList Constructor * Animatable.animate() and so on. MozReview-Commit-ID: 5jZbXyPFHih
testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
testing/web-platform/tests/web-animations/resources/keyframe-utils.js
--- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
+++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
@@ -1,66 +1,36 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>KeyframeEffectReadOnly constructor tests</title>
-<link rel="help" href="http://w3c.github.io/web-animations/#the-keyframeeffect-interfaces">
-<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://w3c.github.io/web-animations/#the-keyframeeffect-interfaces">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
+<script src="../resources/keyframe-utils.js"></script>
 <body>
 <div id="log"></div>
 <div id="target"></div>
 <style>
 #target {
   border-style: solid;  /* so border-*-width values don't compute to 0 */
 }
 </style>
 <script>
 "use strict";
 
 var target = document.getElementById("target");
 
-function assert_frames_equal(a, b, name) {
-  assert_equals(Object.keys(a).sort().toString(),
-                Object.keys(b).sort().toString(),
-                "properties on " + name);
-  for (var p in a) {
-    assert_equals(a[p], b[p], "value for '" + p + "' on " + name);
-  }
-}
-
-function assert_frame_lists_equal(a, b) {
-  assert_equals(a.length, b.length, "number of frames");
-  for (var i = 0; i < Math.min(a.length, b.length); i++) {
-    assert_frames_equal(a[i], b[i], "ComputedKeyframe #" + i);
-  }
-}
-
-var gEmptyKeyframeListTests = [
-  [],
-  null,
-  undefined,
-];
-
 test(function(t) {
   gEmptyKeyframeListTests.forEach(function(frames) {
     assert_equals(new KeyframeEffectReadOnly(target, frames).getFrames().length,
                   0, "number of frames for " + JSON.stringify(frames));
   });
 }, "a KeyframeEffectReadOnly can be constructed with no frames");
 
-// [specified easing value, expected easing value]
-var gEasingValueTests = [
-  ["linear", "linear"],
-  ["ease-in-out", "ease-in-out"],
-  ["Ease\\2d in-out", "ease-in-out"],
-  ["ease /**/", "ease"],
-];
-
 test(function(t) {
   gEasingValueTests.forEach(function(subtest) {
     var easing = subtest[0];
     var expected = subtest[1];
     var effect = new KeyframeEffectReadOnly(target, {
       left: ["10px", "20px"],
       easing: easing
     });
@@ -92,28 +62,16 @@ test(function(t) {
       left: ["10px", "20px"]
     }, { easing: easing });
     assert_equals(effect.timing.easing, expected,
                   "resulting easing for '" + easing + "'");
   });
 }, "easing values are parsed correctly when passed to the " +
    "KeyframeEffectReadOnly constructor in KeyframeTimingOptions");
 
-var gGoodKeyframeCompositeValueTests = [
-  "replace", "add", "accumulate", undefined
-];
-
-var gGoodOptionsCompositeValueTests = [
-  "replace", "add", "accumulate"
-];
-
-var gBadCompositeValueTests = [
-  "unrecognised", "replace ", "Replace", null
-];
-
 test(function(t) {
   var getFrame = function(composite) {
     return { left: [ "10px", "20px" ], composite: composite };
   };
   gGoodKeyframeCompositeValueTests.forEach(function(composite) {
     var effect = new KeyframeEffectReadOnly(target, getFrame(composite));
     assert_equals(effect.getFrames()[0].composite, composite,
                   "resulting composite for '" + composite + "'");
@@ -159,132 +117,16 @@ test(function(t) {
                                    new KeyframeEffectReadOnly(target, {
                                      left: ["10px", "20px"]
                                    }, { composite: composite });
                                  });
   });
 }, "composite values are parsed correctly when passed to the " +
    "KeyframeEffectReadOnly constructor in KeyframeTimingOptions");
 
-var gPropertyIndexedKeyframesTests = [
-  { desc:   "a one property two value property-indexed keyframes specification",
-    input:  { left: ["10px", "20px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               left: "10px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               left: "20px" }] },
-  { desc:   "a one shorthand property two value property-indexed keyframes"
-            + " specification",
-    input:  { margin: ["10px", "10px 20px 30px 40px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               margin: "10px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               margin: "10px 20px 30px 40px" }] },
-  { desc:   "a two property (one shorthand and one of its longhand components)"
-            + " two value property-indexed keyframes specification",
-    input:  { marginTop: ["50px", "60px"],
-              margin: ["10px", "10px 20px 30px 40px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               marginTop: "50px", margin: "10px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               marginTop: "60px", margin: "10px 20px 30px 40px" }] },
-  { desc:   "a two property two value property-indexed keyframes specification",
-    input:  { left: ["10px", "20px"],
-              top: ["30px", "40px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               left: "10px", top: "30px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               left: "20px", top: "40px" }] },
-  { desc:   "a two property property-indexed keyframes specification with"
-            + " different numbers of values",
-    input:  { left: ["10px", "20px", "30px"],
-              top: ["40px", "50px"] },
-    output: [{ offset: null, computedOffset: 0.0, easing: "linear",
-               left: "10px", top: "40px" },
-             { offset: null, computedOffset: 0.5, easing: "linear",
-               left: "20px" },
-             { offset: null, computedOffset: 1.0, easing: "linear",
-               left: "30px", top: "50px" }] },
-  { desc:   "a property-indexed keyframes specification with an invalid value",
-    input:  { left: ["10px", "20px", "30px", "40px", "50px"],
-              top:  ["15px", "25px", "invalid", "45px", "55px"] },
-    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
-               left: "10px", top: "15px" },
-             { offset: null, computedOffset: 0.25, easing: "linear",
-               left: "20px", top: "25px" },
-             { offset: null, computedOffset: 0.50, easing: "linear",
-               left: "30px", top: "invalid" },
-             { offset: null, computedOffset: 0.75, easing: "linear",
-               left: "40px", top: "45px" },
-             { offset: null, computedOffset: 1.00, easing: "linear",
-               left: "50px", top: "55px" }] },
-  { desc:   "a one property two value property-indexed keyframes specification"
-            + " that needs to stringify its values",
-    input:  { opacity: [0, 1] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               opacity: "0" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               opacity: "1" }] },
-  { desc:   "a one property one value property-indexed keyframes specification",
-    input:  { left: ["10px"] },
-    output: [{ offset: null, computedOffset: 1, easing: "linear",
-               left: "10px" }] },
-  { desc:   "a one property one non-array value property-indexed keyframes"
-            + " specification",
-    input:  { left: "10px" },
-    output: [{ offset: null, computedOffset: 1, easing: "linear",
-               left: "10px" }] },
-  { desc:   "a one property two value property-indexed keyframes specification"
-            + " where the first value is invalid",
-    input:  { left: ["invalid", "10px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               left: "invalid" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               left: "10px" }] },
-  { desc:   "a one property two value property-indexed keyframes specification"
-            + " where the second value is invalid",
-    input:  { left: ["10px", "invalid"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               left: "10px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               left: "invalid" }] },
-  { desc:   "a two property property-indexed keyframes specification where one"
-            + " property is missing from the first keyframe",
-    input:  [{ offset: 0, left: "10px" },
-             { offset: 1, left: "20px", top: "30px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               left: "20px", top: "30px" }] },
-  { desc:   "a two property property-indexed keyframes specification where one"
-            + " property is missing from the last keyframe",
-    input:  [{ offset: 0, left: "10px", top: "20px" },
-             { offset: 1, left: "30px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               left: "10px" , top: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               left: "30px" }] },
-  { desc:   "a property-indexed keyframes specification with repeated values"
-            + " at offset 0 with different easings",
-    input:  [{ offset: 0.0, left: "100px", easing: "ease" },
-             { offset: 0.0, left: "200px", easing: "ease" },
-             { offset: 0.5, left: "300px", easing: "linear" },
-             { offset: 1.0, left: "400px", easing: "ease-out" },
-             { offset: 1.0, left: "500px", easing: "step-end" }],
-    output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
-               left: "100px" },
-             { offset: 0.0, computedOffset: 0.0, easing: "ease",
-               left: "200px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "300px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "ease-out",
-               left: "400px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
-               left: "500px" }] },
-];
-
 gPropertyIndexedKeyframesTests.forEach(function(subtest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     assert_frame_lists_equal(effect.getFrames(), subtest.output);
   }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc);
 
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
@@ -309,247 +151,30 @@ test(function(t) {
       get: function() { actualOrder.push(e.p); return e.v; }
     });
   });
   new KeyframeEffectReadOnly(target, [kf1, kf2]);
   assert_array_equals(actualOrder, expectedOrder, "property access order");
 }, "the KeyframeEffectReadOnly constructor reads keyframe properties in the " +
    "expected order");
 
-var gKeyframeSequenceTests = [
-  { desc:   "a one property two keyframe sequence",
-    input:  [{ offset: 0, left: "10px" },
-             { offset: 1, left: "20px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "20px" }]
-  },
-  { desc:   "a two property two keyframe sequence",
-    input:  [{ offset: 0, left: "10px", top: "30px" },
-             { offset: 1, left: "20px", top: "40px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               left: "10px", top: "30px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               left: "20px", top: "40px" }] },
-  { desc:   "a one shorthand property two keyframe sequence",
-    input:  [{ offset: 0, margin: "10px" },
-             { offset: 1, margin: "20px 30px 40px 50px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               margin: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               margin: "20px 30px 40px 50px" }] },
-  { desc:   "a two property (a shorthand and one of its component longhands)"
-            + " two keyframe sequence",
-    input:  [{ offset: 0, margin: "10px", marginTop: "20px" },
-             { offset: 1, marginTop: "70px", margin: "30px 40px 50px 60px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               margin: "10px", marginTop: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               marginTop: "70px", margin: "30px 40px 50px 60px" }] },
-  { desc:   "a keyframe sequence with duplicate values for a given interior"
-            + " offset",
-    input:  [{ offset: 0.0, left: "10px" },
-             { offset: 0.5, left: "20px" },
-             { offset: 0.5, left: "30px" },
-             { offset: 0.5, left: "40px" },
-             { offset: 1.0, left: "50px" }],
-    output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
-               left: "10px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "20px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "30px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "40px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
-               left: "50px" }] },
-  { desc:   "a keyframe sequence with duplicate values for offsets 0 and 1",
-    input:  [{ offset: 0, left: "10px" },
-             { offset: 0, left: "20px" },
-             { offset: 0, left: "30px" },
-             { offset: 1, left: "40px" },
-             { offset: 1, left: "50px" },
-             { offset: 1, left: "60px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 0, computedOffset: 0, easing: "linear", left: "20px" },
-             { offset: 0, computedOffset: 0, easing: "linear", left: "30px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "40px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "50px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "60px" }]
-  },
-  { desc:   "a two property four keyframe sequence",
-    input:  [{ offset: 0, left: "10px" },
-             { offset: 0, top: "20px" },
-             { offset: 1, top: "30px" },
-             { offset: 1, left: "40px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
-             { offset: 0, computedOffset: 0, easing: "linear", top: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear", top: "30px" },
-             { offset: 1, computedOffset: 1, easing: "linear", left: "40px" }]
-  },
-  { desc:   "a one property keyframe sequence with some omitted offsets",
-    input:  [{ offset: 0.00, left: "10px" },
-             { offset: 0.25, left: "20px" },
-             { left: "30px" },
-             { left: "40px" },
-             { offset: 1.00, left: "50px" }],
-    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
-               left: "10px" },
-             { offset: 0.25, computedOffset: 0.25, easing: "linear",
-               left: "20px" },
-             { offset: null, computedOffset: 0.50, easing: "linear",
-               left: "30px" },
-             { offset: null, computedOffset: 0.75, easing: "linear",
-               left: "40px" },
-             { offset: 1.00, computedOffset: 1.00, easing: "linear",
-               left: "50px" }] },
-  { desc:   "a two property keyframe sequence with some omitted offsets",
-    input:  [{ offset: 0.00, left: "10px", top: "20px" },
-             { offset: 0.25, left: "30px" },
-             { left: "40px" },
-             { left: "50px", top: "60px" },
-             { offset: 1.00, left: "70px", top: "80px" }],
-    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
-               left: "10px", top: "20px" },
-             { offset: 0.25, computedOffset: 0.25, easing: "linear",
-               left: "30px" },
-             { offset: null, computedOffset: 0.50, easing: "linear",
-               left: "40px" },
-             { offset: null, computedOffset: 0.75, easing: "linear",
-               left: "50px", top: "60px" },
-             { offset: 1.00, computedOffset: 1.00, easing: "linear",
-               left: "70px", top: "80px" }] },
-  { desc:   "a one property keyframe sequence with all omitted offsets",
-    input:  [{ left: "10px" },
-             { left: "20px" },
-             { left: "30px" },
-             { left: "40px" },
-             { left: "50px" }],
-    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
-               left: "10px" },
-             { offset: null, computedOffset: 0.25, easing: "linear",
-               left: "20px" },
-             { offset: null, computedOffset: 0.50, easing: "linear",
-               left: "30px" },
-             { offset: null, computedOffset: 0.75, easing: "linear",
-               left: "40px" },
-             { offset: null, computedOffset: 1.00, easing: "linear",
-               left: "50px" }] },
-  { desc:   "a keyframe sequence with different easing values, but the same"
-            + " easing value for a given offset",
-    input:  [{ offset: 0.0, easing: "ease",     left: "10px"},
-             { offset: 0.0, easing: "ease",     top: "20px"},
-             { offset: 0.5, easing: "linear",   left: "30px" },
-             { offset: 0.5, easing: "linear",   top: "40px" },
-             { offset: 1.0, easing: "step-end", left: "50px" },
-             { offset: 1.0, easing: "step-end", top: "60px" }],
-    output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
-               left: "10px" },
-             { offset: 0.0, computedOffset: 0.0, easing: "ease",
-               top: "20px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               left: "30px" },
-             { offset: 0.5, computedOffset: 0.5, easing: "linear",
-               top: "40px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
-               left: "50px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
-               top: "60px" }] },
-  { desc:   "a keyframe sequence with different composite values, but the"
-            + " same composite value for a given offset",
-    input:  [{ offset: 0.0, composite: "replace", left: "10px" },
-             { offset: 0.0, composite: "replace", top: "20px" },
-             { offset: 0.5, composite: "add",     left: "30px" },
-             { offset: 0.5, composite: "add",     top: "40px" },
-             { offset: 1.0, composite: "replace", left: "50px" },
-             { offset: 1.0, composite: "replace", top: "60px" }],
-    output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
-               composite: "replace", left: "10px" },
-             { offset: 0.0, computedOffset: 0.0, easing: "linear",
-               composite: "replace", top: "20px" },
-             { offset: 0.5, computedOffset: 0.0, easing: "linear",
-               composite: "add", left: "30px" },
-             { offset: 0.5, computedOffset: 0.0, easing: "linear",
-               composite: "add", top: "40px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
-               composite: "replace", left: "50px" },
-             { offset: 1.0, computedOffset: 1.0, easing: "linear",
-               composite: "replace", top: "60px" }] },
-  { desc:   "a one property two keyframe sequence that needs to stringify"
-            + " its values",
-    input:  [{ offset: 0, opacity: 0 },
-             { offset: 1, opacity: 1 }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear", opacity: "0" },
-             { offset: 1, computedOffset: 1, easing: "linear", opacity: "1" }]
-  },
-  { desc:   "a keyframe sequence where shorthand precedes longhand",
-    input:  [{ offset: 0, margin: "10px", marginRight: "20px" },
-             { offset: 1, margin: "30px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               margin: "10px", marginRight: "20px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               margin: "30px" }] },
-  { desc:   "a keyframe sequence where longhand precedes shorthand",
-    input:  [{ offset: 0, marginRight: "20px", margin: "10px" },
-             { offset: 1, margin: "30px" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               marginRight: "20px", margin: "10px" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               margin: "30px" }] },
-  { desc:   "a keyframe sequence where lesser shorthand precedes greater"
-            + " shorthand",
-    input:  [{ offset: 0,
-               borderLeft: "1px solid rgb(1, 2, 3)",
-               border: "2px dotted rgb(4, 5, 6)" },
-             { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               borderLeft: "1px solid rgb(1, 2, 3)",
-               border: "2px dotted rgb(4, 5, 6)" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               border: "3px dashed rgb(7, 8, 9)" }] },
-  { desc:   "a keyframe sequence where greater shorthand precedes lesser"
-            + " shorthand",
-    input:  [{ offset: 0, border: "2px dotted rgb(4, 5, 6)",
-               borderLeft: "1px solid rgb(1, 2, 3)" },
-             { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
-    output: [{ offset: 0, computedOffset: 0, easing: "linear",
-               border: "2px dotted rgb(4, 5, 6)",
-               borderLeft: "1px solid rgb(1, 2, 3)" },
-             { offset: 1, computedOffset: 1, easing: "linear",
-               border: "3px dashed rgb(7, 8, 9)" }] },
-];
-
 gKeyframeSequenceTests.forEach(function(subtest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     assert_frame_lists_equal(effect.getFrames(), subtest.output);
   }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc);
 
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     var secondEffect = new KeyframeEffectReadOnly(target, effect.getFrames());
     assert_frame_lists_equal(secondEffect.getFrames(), effect.getFrames());
   }, "a KeyframeEffectReadOnly constructed with " + subtest.desc +
      " roundtrips");
 });
 
-var gInvalidEasingInKeyframeSequenceTests = [
-  { desc:   "a blank easing",
-    input:  [{ easing: "" }] },
-  { desc:   "an unrecognized easing",
-    input:  [{ easing: "unrecognized" }] },
-  { desc:   "an 'initial' easing",
-    input:  [{ easing: "initial" }] },
-  { desc:   "an 'inherit' easing",
-    input:  [{ easing: "inherit" }] },
-  { desc:   "a variable easing",
-    input:  [{ easing: "var(--x)" }] },
-  { desc:   "a multi-value easing",
-    input:  [{ easing: "ease-in-out, ease-out" }] }
-];
-
 gInvalidEasingInKeyframeSequenceTests.forEach(function(subtest) {
   test(function(t) {
     assert_throws(new TypeError, function() {
       new KeyframeEffectReadOnly(target, subtest.input);
     });
   }, "Invalid easing [" + subtest.desc + "] in keyframe sequence " +
      "should be thrown");
 });
@@ -571,54 +196,16 @@ test(function(t) {
   assert_equals(effect.composite, "replace", "default composite");
   assert_equals(effect.iterationComposite, "replace",
                 "default iterationComposite");
   assert_equals(effect.spacing, "distribute",
                 "default spacing");
 }, "a KeyframeEffectReadOnly constructed without any " +
    "KeyframeEffectOptions object");
 
-var gKeyframeEffectOptionTests = [
-  { desc:     "an empty KeyframeEffectOptions object",
-    input:    { },
-    expected: { } },
-  { desc:     "a normal KeyframeEffectOptions object",
-    input:    { delay: 1000,
-                fill: "auto",
-                iterations: 5.5,
-                duration: "auto",
-                direction: "alternate" },
-    expected: { delay: 1000,
-                fill: "auto",
-                iterations: 5.5,
-                duration: "auto",
-                direction: "alternate" } },
-  { desc:     "a double value",
-    input:    3000,
-    expected: { duration: 3000 } },
-  { desc:     "+Infinity",
-    input:    Infinity,
-    expected: { duration: Infinity } },
-  { desc:     "an Infinity duration",
-    input:    { duration: Infinity },
-    expected: { duration: Infinity } },
-  { desc:     "an auto duration",
-    input:    { duration: "auto" },
-    expected: { duration: "auto" } },
-  { desc:     "an Infinity iterations",
-    input:    { iterations: Infinity },
-    expected: { iterations: Infinity } },
-  { desc:     "an auto fill",
-    input:    { fill: "auto" },
-    expected: { fill: "auto" } },
-  { desc:     "a forwards fill",
-    input:    { fill: "forwards" },
-    expected: { fill: "forwards" } }
-];
-
 gKeyframeEffectOptionTests.forEach(function(stest) {
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target,
                                             {left: ["10px", "20px"]},
                                             stest.input);
 
     // Helper function to provide default expected values when the test does
     // not supply them.
@@ -636,67 +223,16 @@ gKeyframeEffectOptionTests.forEach(funct
     assert_equals(timing.duration, expected("duration", "auto"),
                   "timing duration");
     assert_equals(timing.direction, expected("direction", "normal"),
                   "timing direction");
 
   }, "a KeyframeEffectReadOnly constructed by " + stest.desc);
 });
 
-var gInvalidKeyframeEffectOptionTests = [
-  { desc:     "-Infinity",
-    input:    -Infinity,
-    expected: { name: "TypeError" } },
-  { desc:     "NaN",
-    input:    NaN,
-    expected: { name: "TypeError" } },
-  { desc:     "a negative value",
-    input:    -1,
-    expected: { name: "TypeError" } },
-  { desc:     "a negative Infinity duration",
-    input:    { duration: -Infinity },
-    expected: { name: "TypeError" } },
-  { desc:     "a NaN duration",
-    input:    { duration: NaN },
-    expected: { name: "TypeError" } },
-  { desc:     "a negative duration",
-    input:    { duration: -1 },
-    expected: { name: "TypeError" } },
-  { desc:     "a string duration",
-    input:    { duration: "merrychristmas" },
-    expected: { name: "TypeError" } },
-  { desc:     "a negative Infinity iterations",
-    input:    { iterations: -Infinity},
-    expected: { name: "TypeError" } },
-  { desc:     "a NaN iterations",
-    input:    { iterations: NaN },
-    expected: { name: "TypeError" } },
-  { desc:     "a negative iterations",
-    input:    { iterations: -1 },
-    expected: { name: "TypeError" } },
-  { desc:     "a blank easing",
-    input:    { easing: "" },
-    expected: { name: "TypeError" } },
-  { desc:     "an unrecognized easing",
-    input:    { easing: "unrecognised" },
-    expected: { name: "TypeError" } },
-  { desc:     "an 'initial' easing",
-    input:    { easing: "initial" },
-    expected: { name: "TypeError" } },
-  { desc:     "an 'inherit' easing",
-    input:    { easing: "inherit" },
-    expected: { name: "TypeError" } },
-  { desc:     "a variable easing",
-    input:    { easing: "var(--x)" },
-    expected: { name: "TypeError" } },
-  { desc:     "a multi-value easing",
-    input:    { easing: "ease-in-out, ease-out" },
-    expected: { name: "TypeError" } }
-];
-
 gInvalidKeyframeEffectOptionTests.forEach(function(stest) {
   test(function(t) {
     assert_throws(stest.expected, function() {
       new KeyframeEffectReadOnly(target,
                                  { left: ["10px", "20px"] },
                                  stest.input);
     });
   }, "Invalid KeyframeEffectReadOnly option by " + stest.desc);
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/resources/keyframe-utils.js
@@ -0,0 +1,493 @@
+"use strict";
+
+// Utility functions and common keyframe test data.
+
+// ------------------------------
+//  Helper functions
+// ------------------------------
+
+/**
+ * Test equality between two lists of computed keyframes
+ * @param {Array.<ComputedKeyframe>} a - actual computed keyframes
+ * @param {Array.<ComputedKeyframe>} b - expected computed keyframes
+ */
+function assert_frame_lists_equal(a, b) {
+  assert_equals(a.length, b.length, "number of frames");
+  for (var i = 0; i < Math.min(a.length, b.length); i++) {
+    assert_frames_equal(a[i], b[i], "ComputedKeyframe #" + i);
+  }
+}
+
+/** Helper */
+function assert_frames_equal(a, b, name) {
+  assert_equals(Object.keys(a).sort().toString(),
+                Object.keys(b).sort().toString(),
+                "properties on " + name);
+  for (var p in a) {
+    assert_equals(a[p], b[p], "value for '" + p + "' on " + name);
+  }
+}
+
+// ------------------------------
+//  Easing values
+// ------------------------------
+
+// [specified easing value, expected easing value]
+var gEasingValueTests = [
+  ["linear", "linear"],
+  ["ease-in-out", "ease-in-out"],
+  ["Ease\\2d in-out", "ease-in-out"],
+  ["ease /**/", "ease"],
+];
+
+var gInvalidEasingInKeyframeSequenceTests = [
+  { desc:   "a blank easing",
+    input:  [{ easing: "" }] },
+  { desc:   "an unrecognized easing",
+    input:  [{ easing: "unrecognized" }] },
+  { desc:   "an 'initial' easing",
+    input:  [{ easing: "initial" }] },
+  { desc:   "an 'inherit' easing",
+    input:  [{ easing: "inherit" }] },
+  { desc:   "a variable easing",
+    input:  [{ easing: "var(--x)" }] },
+  { desc:   "a multi-value easing",
+    input:  [{ easing: "ease-in-out, ease-out" }] }
+];
+
+// ------------------------------
+//  Composite values
+// ------------------------------
+
+var gGoodKeyframeCompositeValueTests = [
+  "replace", "add", "accumulate", undefined
+];
+
+var gGoodOptionsCompositeValueTests = [
+  "replace", "add", "accumulate"
+];
+
+var gBadCompositeValueTests = [
+  "unrecognised", "replace ", "Replace", null
+];
+
+// ------------------------------
+//  Keyframes
+// ------------------------------
+
+var gEmptyKeyframeListTests = [
+  [],
+  null,
+  undefined,
+];
+
+var gPropertyIndexedKeyframesTests = [
+  { desc:   "a one property two value property-indexed keyframes specification",
+    input:  { left: ["10px", "20px"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "20px" }] },
+  { desc:   "a one shorthand property two value property-indexed keyframes"
+            + " specification",
+    input:  { margin: ["10px", "10px 20px 30px 40px"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               margin: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               margin: "10px 20px 30px 40px" }] },
+  { desc:   "a two property (one shorthand and one of its longhand components)"
+            + " two value property-indexed keyframes specification",
+    input:  { marginTop: ["50px", "60px"],
+              margin: ["10px", "10px 20px 30px 40px"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               marginTop: "50px", margin: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               marginTop: "60px", margin: "10px 20px 30px 40px" }] },
+  { desc:   "a two property two value property-indexed keyframes specification",
+    input:  { left: ["10px", "20px"],
+              top: ["30px", "40px"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "10px", top: "30px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "20px", top: "40px" }] },
+  { desc:   "a two property property-indexed keyframes specification with"
+            + " different numbers of values",
+    input:  { left: ["10px", "20px", "30px"],
+              top: ["40px", "50px"] },
+    output: [{ offset: null, computedOffset: 0.0, easing: "linear",
+               left: "10px", top: "40px" },
+             { offset: null, computedOffset: 0.5, easing: "linear",
+               left: "20px" },
+             { offset: null, computedOffset: 1.0, easing: "linear",
+               left: "30px", top: "50px" }] },
+  { desc:   "a property-indexed keyframes specification with an invalid value",
+    input:  { left: ["10px", "20px", "30px", "40px", "50px"],
+              top:  ["15px", "25px", "invalid", "45px", "55px"] },
+    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
+               left: "10px", top: "15px" },
+             { offset: null, computedOffset: 0.25, easing: "linear",
+               left: "20px", top: "25px" },
+             { offset: null, computedOffset: 0.50, easing: "linear",
+               left: "30px", top: "invalid" },
+             { offset: null, computedOffset: 0.75, easing: "linear",
+               left: "40px", top: "45px" },
+             { offset: null, computedOffset: 1.00, easing: "linear",
+               left: "50px", top: "55px" }] },
+  { desc:   "a one property two value property-indexed keyframes specification"
+            + " that needs to stringify its values",
+    input:  { opacity: [0, 1] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               opacity: "0" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               opacity: "1" }] },
+  { desc:   "a one property one value property-indexed keyframes specification",
+    input:  { left: ["10px"] },
+    output: [{ offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
+  { desc:   "a one property one non-array value property-indexed keyframes"
+            + " specification",
+    input:  { left: "10px" },
+    output: [{ offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
+  { desc:   "a one property two value property-indexed keyframes specification"
+            + " where the first value is invalid",
+    input:  { left: ["invalid", "10px"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "invalid" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "10px" }] },
+  { desc:   "a one property two value property-indexed keyframes specification"
+            + " where the second value is invalid",
+    input:  { left: ["10px", "invalid"] },
+    output: [{ offset: null, computedOffset: 0, easing: "linear",
+               left: "10px" },
+             { offset: null, computedOffset: 1, easing: "linear",
+               left: "invalid" }] },
+  { desc:   "a two property property-indexed keyframes specification where one"
+            + " property is missing from the first keyframe",
+    input:  [{ offset: 0, left: "10px" },
+             { offset: 1, left: "20px", top: "30px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               left: "20px", top: "30px" }] },
+  { desc:   "a two property property-indexed keyframes specification where one"
+            + " property is missing from the last keyframe",
+    input:  [{ offset: 0, left: "10px", top: "20px" },
+             { offset: 1, left: "30px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               left: "10px" , top: "20px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               left: "30px" }] },
+  { desc:   "a property-indexed keyframes specification with repeated values"
+            + " at offset 0 with different easings",
+    input:  [{ offset: 0.0, left: "100px", easing: "ease" },
+             { offset: 0.0, left: "200px", easing: "ease" },
+             { offset: 0.5, left: "300px", easing: "linear" },
+             { offset: 1.0, left: "400px", easing: "ease-out" },
+             { offset: 1.0, left: "500px", easing: "step-end" }],
+    output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
+               left: "100px" },
+             { offset: 0.0, computedOffset: 0.0, easing: "ease",
+               left: "200px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "300px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "ease-out",
+               left: "400px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
+               left: "500px" }] },
+];
+
+var gKeyframeSequenceTests = [
+  { desc:   "a one property two keyframe sequence",
+    input:  [{ offset: 0, left: "10px" },
+             { offset: 1, left: "20px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "20px" }]
+  },
+  { desc:   "a two property two keyframe sequence",
+    input:  [{ offset: 0, left: "10px", top: "30px" },
+             { offset: 1, left: "20px", top: "40px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               left: "10px", top: "30px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               left: "20px", top: "40px" }] },
+  { desc:   "a one shorthand property two keyframe sequence",
+    input:  [{ offset: 0, margin: "10px" },
+             { offset: 1, margin: "20px 30px 40px 50px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               margin: "10px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               margin: "20px 30px 40px 50px" }] },
+  { desc:   "a two property (a shorthand and one of its component longhands)"
+            + " two keyframe sequence",
+    input:  [{ offset: 0, margin: "10px", marginTop: "20px" },
+             { offset: 1, marginTop: "70px", margin: "30px 40px 50px 60px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               margin: "10px", marginTop: "20px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               marginTop: "70px", margin: "30px 40px 50px 60px" }] },
+  { desc:   "a keyframe sequence with duplicate values for a given interior"
+            + " offset",
+    input:  [{ offset: 0.0, left: "10px" },
+             { offset: 0.5, left: "20px" },
+             { offset: 0.5, left: "30px" },
+             { offset: 0.5, left: "40px" },
+             { offset: 1.0, left: "50px" }],
+    output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
+               left: "10px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "20px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "30px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "40px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+               left: "50px" }] },
+  { desc:   "a keyframe sequence with duplicate values for offsets 0 and 1",
+    input:  [{ offset: 0, left: "10px" },
+             { offset: 0, left: "20px" },
+             { offset: 0, left: "30px" },
+             { offset: 1, left: "40px" },
+             { offset: 1, left: "50px" },
+             { offset: 1, left: "60px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 0, computedOffset: 0, easing: "linear", left: "20px" },
+             { offset: 0, computedOffset: 0, easing: "linear", left: "30px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "40px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "50px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "60px" }]
+  },
+  { desc:   "a two property four keyframe sequence",
+    input:  [{ offset: 0, left: "10px" },
+             { offset: 0, top: "20px" },
+             { offset: 1, top: "30px" },
+             { offset: 1, left: "40px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", left: "10px" },
+             { offset: 0, computedOffset: 0, easing: "linear", top: "20px" },
+             { offset: 1, computedOffset: 1, easing: "linear", top: "30px" },
+             { offset: 1, computedOffset: 1, easing: "linear", left: "40px" }]
+  },
+  { desc:   "a one property keyframe sequence with some omitted offsets",
+    input:  [{ offset: 0.00, left: "10px" },
+             { offset: 0.25, left: "20px" },
+             { left: "30px" },
+             { left: "40px" },
+             { offset: 1.00, left: "50px" }],
+    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
+               left: "10px" },
+             { offset: 0.25, computedOffset: 0.25, easing: "linear",
+               left: "20px" },
+             { offset: null, computedOffset: 0.50, easing: "linear",
+               left: "30px" },
+             { offset: null, computedOffset: 0.75, easing: "linear",
+               left: "40px" },
+             { offset: 1.00, computedOffset: 1.00, easing: "linear",
+               left: "50px" }] },
+  { desc:   "a two property keyframe sequence with some omitted offsets",
+    input:  [{ offset: 0.00, left: "10px", top: "20px" },
+             { offset: 0.25, left: "30px" },
+             { left: "40px" },
+             { left: "50px", top: "60px" },
+             { offset: 1.00, left: "70px", top: "80px" }],
+    output: [{ offset: 0.00, computedOffset: 0.00, easing: "linear",
+               left: "10px", top: "20px" },
+             { offset: 0.25, computedOffset: 0.25, easing: "linear",
+               left: "30px" },
+             { offset: null, computedOffset: 0.50, easing: "linear",
+               left: "40px" },
+             { offset: null, computedOffset: 0.75, easing: "linear",
+               left: "50px", top: "60px" },
+             { offset: 1.00, computedOffset: 1.00, easing: "linear",
+               left: "70px", top: "80px" }] },
+  { desc:   "a one property keyframe sequence with all omitted offsets",
+    input:  [{ left: "10px" },
+             { left: "20px" },
+             { left: "30px" },
+             { left: "40px" },
+             { left: "50px" }],
+    output: [{ offset: null, computedOffset: 0.00, easing: "linear",
+               left: "10px" },
+             { offset: null, computedOffset: 0.25, easing: "linear",
+               left: "20px" },
+             { offset: null, computedOffset: 0.50, easing: "linear",
+               left: "30px" },
+             { offset: null, computedOffset: 0.75, easing: "linear",
+               left: "40px" },
+             { offset: null, computedOffset: 1.00, easing: "linear",
+               left: "50px" }] },
+  { desc:   "a keyframe sequence with different easing values, but the same"
+            + " easing value for a given offset",
+    input:  [{ offset: 0.0, easing: "ease",     left: "10px"},
+             { offset: 0.0, easing: "ease",     top: "20px"},
+             { offset: 0.5, easing: "linear",   left: "30px" },
+             { offset: 0.5, easing: "linear",   top: "40px" },
+             { offset: 1.0, easing: "step-end", left: "50px" },
+             { offset: 1.0, easing: "step-end", top: "60px" }],
+    output: [{ offset: 0.0, computedOffset: 0.0, easing: "ease",
+               left: "10px" },
+             { offset: 0.0, computedOffset: 0.0, easing: "ease",
+               top: "20px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               left: "30px" },
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
+               top: "40px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
+               left: "50px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "step-end",
+               top: "60px" }] },
+  { desc:   "a keyframe sequence with different composite values, but the"
+            + " same composite value for a given offset",
+    input:  [{ offset: 0.0, composite: "replace", left: "10px" },
+             { offset: 0.0, composite: "replace", top: "20px" },
+             { offset: 0.5, composite: "add",     left: "30px" },
+             { offset: 0.5, composite: "add",     top: "40px" },
+             { offset: 1.0, composite: "replace", left: "50px" },
+             { offset: 1.0, composite: "replace", top: "60px" }],
+    output: [{ offset: 0.0, computedOffset: 0.0, easing: "linear",
+               composite: "replace", left: "10px" },
+             { offset: 0.0, computedOffset: 0.0, easing: "linear",
+               composite: "replace", top: "20px" },
+             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+               composite: "add", left: "30px" },
+             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+               composite: "add", top: "40px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+               composite: "replace", left: "50px" },
+             { offset: 1.0, computedOffset: 1.0, easing: "linear",
+               composite: "replace", top: "60px" }] },
+  { desc:   "a one property two keyframe sequence that needs to stringify"
+            + " its values",
+    input:  [{ offset: 0, opacity: 0 },
+             { offset: 1, opacity: 1 }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear", opacity: "0" },
+             { offset: 1, computedOffset: 1, easing: "linear", opacity: "1" }]
+  },
+  { desc:   "a keyframe sequence where shorthand precedes longhand",
+    input:  [{ offset: 0, margin: "10px", marginRight: "20px" },
+             { offset: 1, margin: "30px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               margin: "10px", marginRight: "20px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               margin: "30px" }] },
+  { desc:   "a keyframe sequence where longhand precedes shorthand",
+    input:  [{ offset: 0, marginRight: "20px", margin: "10px" },
+             { offset: 1, margin: "30px" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               marginRight: "20px", margin: "10px" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               margin: "30px" }] },
+  { desc:   "a keyframe sequence where lesser shorthand precedes greater"
+            + " shorthand",
+    input:  [{ offset: 0,
+               borderLeft: "1px solid rgb(1, 2, 3)",
+               border: "2px dotted rgb(4, 5, 6)" },
+             { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               borderLeft: "1px solid rgb(1, 2, 3)",
+               border: "2px dotted rgb(4, 5, 6)" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               border: "3px dashed rgb(7, 8, 9)" }] },
+  { desc:   "a keyframe sequence where greater shorthand precedes lesser"
+            + " shorthand",
+    input:  [{ offset: 0, border: "2px dotted rgb(4, 5, 6)",
+               borderLeft: "1px solid rgb(1, 2, 3)" },
+             { offset: 1, border: "3px dashed rgb(7, 8, 9)" }],
+    output: [{ offset: 0, computedOffset: 0, easing: "linear",
+               border: "2px dotted rgb(4, 5, 6)",
+               borderLeft: "1px solid rgb(1, 2, 3)" },
+             { offset: 1, computedOffset: 1, easing: "linear",
+               border: "3px dashed rgb(7, 8, 9)" }] },
+];
+
+// ------------------------------
+//  KeyframeEffectOptions
+// ------------------------------
+
+var gKeyframeEffectOptionTests = [
+  { desc:     "an empty KeyframeEffectOptions object",
+    input:    { },
+    expected: { } },
+  { desc:     "a normal KeyframeEffectOptions object",
+    input:    { delay: 1000,
+                fill: "auto",
+                iterations: 5.5,
+                duration: "auto",
+                direction: "alternate" },
+    expected: { delay: 1000,
+                fill: "auto",
+                iterations: 5.5,
+                duration: "auto",
+                direction: "alternate" } },
+  { desc:     "a double value",
+    input:    3000,
+    expected: { duration: 3000 } },
+  { desc:     "+Infinity",
+    input:    Infinity,
+    expected: { duration: Infinity } },
+  { desc:     "an Infinity duration",
+    input:    { duration: Infinity },
+    expected: { duration: Infinity } },
+  { desc:     "an auto duration",
+    input:    { duration: "auto" },
+    expected: { duration: "auto" } },
+  { desc:     "an Infinity iterations",
+    input:    { iterations: Infinity },
+    expected: { iterations: Infinity } },
+  { desc:     "an auto fill",
+    input:    { fill: "auto" },
+    expected: { fill: "auto" } },
+  { desc:     "a forwards fill",
+    input:    { fill: "forwards" },
+    expected: { fill: "forwards" } }
+];
+
+var gInvalidKeyframeEffectOptionTests = [
+  { desc:     "-Infinity",
+    input:    -Infinity,
+    expected: { name: "TypeError" } },
+  { desc:     "NaN",
+    input:    NaN,
+    expected: { name: "TypeError" } },
+  { desc:     "a negative value",
+    input:    -1,
+    expected: { name: "TypeError" } },
+  { desc:     "a negative Infinity duration",
+    input:    { duration: -Infinity },
+    expected: { name: "TypeError" } },
+  { desc:     "a NaN duration",
+    input:    { duration: NaN },
+    expected: { name: "TypeError" } },
+  { desc:     "a negative duration",
+    input:    { duration: -1 },
+    expected: { name: "TypeError" } },
+  { desc:     "a string duration",
+    input:    { duration: "merrychristmas" },
+    expected: { name: "TypeError" } },
+  { desc:     "a negative Infinity iterations",
+    input:    { iterations: -Infinity},
+    expected: { name: "TypeError" } },
+  { desc:     "a NaN iterations",
+    input:    { iterations: NaN },
+    expected: { name: "TypeError" } },
+  { desc:     "a negative iterations",
+    input:    { iterations: -1 },
+    expected: { name: "TypeError" } },
+  { desc:     "a blank easing",
+    input:    { easing: "" },
+    expected: { name: "TypeError" } },
+  { desc:     "an unrecognized easing",
+    input:    { easing: "unrecognised" },
+    expected: { name: "TypeError" } },
+  { desc:     "an 'initial' easing",
+    input:    { easing: "initial" },
+    expected: { name: "TypeError" } },
+  { desc:     "an 'inherit' easing",
+    input:    { easing: "inherit" },
+    expected: { name: "TypeError" } },
+  { desc:     "a variable easing",
+    input:    { easing: "var(--x)" },
+    expected: { name: "TypeError" } },
+  { desc:     "a multi-value easing",
+    input:    { easing: "ease-in-out, ease-out" },
+    expected: { name: "TypeError" } }
+];