diff options
Diffstat (limited to 'testing/web-platform/tests/web-animations')
61 files changed, 11093 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/OWNERS b/testing/web-platform/tests/web-animations/OWNERS new file mode 100644 index 000000000..fd38f5e5b --- /dev/null +++ b/testing/web-platform/tests/web-animations/OWNERS @@ -0,0 +1 @@ +@birtles diff --git a/testing/web-platform/tests/web-animations/README.md b/testing/web-platform/tests/web-animations/README.md new file mode 100644 index 000000000..c6d7c72c2 --- /dev/null +++ b/testing/web-platform/tests/web-animations/README.md @@ -0,0 +1,107 @@ +Web Animations Test Suite +========================= + +Specification: https://w3c.github.io/web-animations/ + + +Guidelines for writing tests +---------------------------- + +* Try to follow the spec outline where possible. + + For example, if you want to test setting the start time, you might be + tempted to put all the tests in: + + > `/web-animations/interfaces/Animation/startTime.html` + + However, in the spec most of the logic is in the “Set the animation + start time“ procedure in the “Timing model” section. + + Instead, try something like: + + > * `/web-animations/timing-model/animations/set-the-animation-start-time.html`<br> + > Tests all the branches and inputs to the procedure as defined in the + > spec (using the `Animation.startTime` API). + > * `/web-animations/interfaces/Animation/startTime.html`<br> + > Tests API-layer specific issues like mapping unresolved values to + > null, etc. + + On that note, two levels of subdirectories is enough even if the spec has + deeper nesting. + + Note that most of the existing tests in the suite _don't_ do this well yet. + That's the direction we're heading, however. + +* Test the spec. + + * If the spec defines a timing calculation that is directly + reflected in the iteration progress + (i.e. `anim.effect.getComputedTiming().progress`), test that instead + of calling `getComputedStyle(elem).marginLeft`. + + * Likewise, don't add needless tests for `anim.playbackState`. + The playback state is a calculated value based on other values. + It's rarely necessary to test directly unless you need, for example, + to check that a pending task is scheduled (which isn't observable + elsewhere other than waiting for the corresponding promise to + complete). + +* Try to keep tests as simple and focused as possible. + + e.g. + + ```javascript + test(function(t) { + var anim = createDiv(t).animate(null); + assert_class_string(anim, 'Animation', 'Returned object is an Animation'); + }, 'Element.animate() creates an Animation object'); + ``` + + ```javascript + test(function(t) { + assert_throws({ name: 'TypeError' }, function() { + createDiv(t).animate(null, -1); + }); + }, 'Setting a negative duration throws a TypeError'); + ``` + + ```javascript + promise_test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + return animation.ready.then(function() { + assert_greater_than(animation.startTime, 0, 'startTime when running'); + }); + }, 'startTime is resolved when running'); + ``` + + If you're generating complex test loops and factoring out utility functions + that affect the logic of the test (other than, say, simple assertion utility + functions), you're probably doing it wrong. + + It should be possible to understand exactly what the test is doing at a + glance without having to scroll up and down the test file and refer to + other files. + + See Justin Searls' presentation, [“How to stop hating your + tests”](http://blog.testdouble.com/posts/2015-11-16-how-to-stop-hating-your-tests.html) + for some tips on making your tests simpler. + +* Assume tests will run on under-performing hardware where the time between + animation frames might run into 10s of seconds. + As a result, animations that are expected to still be running during + the test should be at least 100s in length. + +* Avoid using `GLOBAL_CONSTS` that make the test harder to read. + It's fine to repeat the the same parameter values like `100 * MS_PER_SEC` + over and over again since it makes it easy to read and debug a test in + isolation. + Remember, even if we do need to make all tests take, say 200s each, text + editors are very good at search and replace. + +* Use the `assert_times_equal` assertion for comparing calculated times. + It tests times are equal using the precision recommended in the spec whilst + allowing implementations to override the function to meet their own + precision requirements. + +* There are quite a few bad tests in the repository. We're learning as + we go. Don't just copy them blindly—please fix them! diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete-animation.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete-animation.html new file mode 100644 index 000000000..e43f26d16 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete-animation.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests for discrete animation</title> +<link rel="help" href="http://w3c.github.io/web-animations/#animatable-as-string-section"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + + var anim = div.animate({ fontStyle: [ 'normal', 'italic' ] }, + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = anim.effect.getComputedTiming().duration / 2 - 1; + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value just before the middle of' + + ' the interval'); + anim.currentTime++; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value at exact middle of' + + ' the interval'); + anim.finish(); + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value during forwards fill'); +}, 'Test animating discrete values'); + +test(function(t) { + var div = createDiv(t); + var originalHeight = getComputedStyle(div).height; + + var anim = div.animate({ height: [ 'auto', '200px' ] }, + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = anim.effect.getComputedTiming().duration / 2 - 1; + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value just before the middle of' + + ' the interval'); + anim.currentTime++; + assert_equals(getComputedStyle(div).height, '200px', + 'Animation produces \'to\' value at exact middle of' + + ' the interval'); + anim.finish(); + assert_equals(getComputedStyle(div).height, '200px', + 'Animation produces \'to\' value during forwards fill'); +}, 'Test discrete animation is used when interpolation fails'); + +test(function(t) { + var div = createDiv(t); + var originalHeight = getComputedStyle(div).height; + + var anim = div.animate({ height: [ 'auto', + '200px', + '300px', + 'auto', + '400px' ] }, + { duration: 1000, fill: 'forwards' }); + + // There are five values, so there are four pairs to try to interpolate. + // We test at the middle of each pair. + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 125; + assert_equals(getComputedStyle(div).height, '200px', + 'First non-interpolable pair uses discrete interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, '250px', + 'Second interpolable pair uses linear interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, originalHeight, + 'Third non-interpolable pair uses discrete interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, '400px', + 'Fourth non-interpolable pair uses discrete interpolation'); +}, 'Test discrete animation is used only for pairs of values that cannot' + + ' be interpolated'); + +test(function(t) { + var div = createDiv(t); + var originalHeight = getComputedStyle(div).height; + + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + var anim = div.animate({ fontStyle: [ 'italic', 'oblique' ] }, + { duration: 1000, fill: 'forwards', + easing: 'cubic-bezier(0.68,0,1,0.01)' }); + + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 940; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'from\' value at 94% of the iteration' + + ' time'); + anim.currentTime = 960; + assert_equals(getComputedStyle(div).fontStyle, 'oblique', + 'Animation produces \'to\' value at 96% of the iteration' + + ' time'); +}, 'Test the 50% switch point for discrete animation is based on the' + + ' effect easing'); + +test(function(t) { + var div = createDiv(t); + var originalHeight = getComputedStyle(div).height; + + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + var anim = div.animate([ { fontStyle: 'italic', + easing: 'cubic-bezier(0.68,0,1,0.01)' }, + { fontStyle: 'oblique' } ], + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 940; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'from\' value at 94% of the iteration' + + ' time'); + anim.currentTime = 960; + assert_equals(getComputedStyle(div).fontStyle, 'oblique', + 'Animation produces \'to\' value at 96% of the iteration' + + ' time'); +}, 'Test the 50% switch point for discrete animation is based on the' + + ' keyframe easing'); + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html new file mode 100644 index 000000000..9f7cfaea1 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html @@ -0,0 +1,152 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Keyframe spacing tests on shapes</title> +<link rel="help" href="https://w3c.github.io/web-animations/#spacing-keyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +// Help function for testing the computed offsets by the distance array. +function assert_animation_offsets(anim, dist) { + const frames = anim.effect.getKeyframes(); + const cumDist = dist.reduce( (prev, curr) => { + prev.push(prev.length == 0 ? curr : curr + prev[prev.length - 1]); + return prev; + }, []); + + const total = cumDist[cumDist.length - 1]; + for (var i = 0; i < frames.length; ++i) { + assert_equals(frames[i].computedOffset, cumDist[i] / total, + 'computedOffset of frame ' + i); + } +} + +test(function(t) { + var anim = + createDiv(t).animate([ { clipPath: 'circle(20px)' }, + { clipPath: 'ellipse(10px 20px)' }, + { clipPath: 'polygon(50px 0px, 100px 50px, ' + + ' 50px 100px, 0px 50px)' }, + { clipPath: 'inset(20px round 10px)' } ], + { spacing: 'paced(clip-path)' }); + + const frames = anim.effect.getKeyframes(); + const slots = frames.length - 1; + for (var i = 0; i < frames.length; ++i) { + assert_equals(frames[i].computedOffset, i / slots, + 'computedOffset of frame ' + i); + } +}, 'Test falling back to distribute spacing when using different basic shapes'); + +test(function(t) { + var anim = + createDiv(t).animate([ { clipPath: 'circle(10px)' }, + { clipPath: 'circle(20px) content-box' }, + { clipPath: 'circle(70px)' }, + { clipPath: 'circle(10px) padding-box' } ], + { spacing: 'paced(clip-path)' }); + + const frames = anim.effect.getKeyframes(); + const slots = frames.length - 1; + for (var i = 0; i < frames.length; ++i) { + assert_equals(frames[i].computedOffset, i / slots, + 'computedOffset of frame ' + i); + } +}, 'Test falling back to distribute spacing when using different ' + + 'reference boxes'); + +test(function(t) { + // 1st: circle(calc(20px) at calc(20px + 0%) calc(10px + 0%)) + // 2nd: circle(calc(50px) at calc(10px + 0%) calc(10px + 0%)) + // 3rd: circle(70px at calc(10px + 0%) calc(50px + 0%)) + // 4th: circle(10px at calc(50px + 0%) calc(30px + 0%)) + var anim = + createDiv(t).animate([ { clipPath: 'circle(calc(10px + 10px) ' + + ' at 20px 10px)' }, + { clipPath: 'circle(calc(20px + 30px) ' + + ' at 10px 10px)' }, + { clipPath: 'circle(70px at 10px 50px)' }, + { clipPath: 'circle(10px at 50px 30px)' } ], + { spacing: 'paced(clip-path)' }); + + var dist = [ 0, + Math.sqrt(30 * 30 + 10 * 10), + Math.sqrt(20 * 20 + 40 * 40), + Math.sqrt(60 * 60 + 40 * 40 + 20 * 20) ]; + assert_animation_offsets(anim, dist); +}, 'Test spacing on circle' ); + +test(function(t) { + // 1st: ellipse(20px calc(20px) at calc(0px + 50%) calc(0px + 50%)) + // 2nd: ellipse(20px calc(50px) at calc(0px + 50%) calc(0px + 50%)) + // 3rd: ellipse(30px 70px at calc(0px + 50%) calc(0px + 50%)) + // 4th: ellipse(30px 10px at calc(0px + 50%) calc(0px + 50%)) + var anim = + createDiv(t).animate([ { clipPath: 'ellipse(20px calc(10px + 10px))' }, + { clipPath: 'ellipse(20px calc(20px + 30px))' }, + { clipPath: 'ellipse(30px 70px)' }, + { clipPath: 'ellipse(30px 10px)' } ], + { spacing: 'paced(clip-path)' }); + + var dist = [ 0, + Math.sqrt(30 * 30), + Math.sqrt(10 * 10 + 20 * 20), + Math.sqrt(60 * 60) ]; + assert_animation_offsets(anim, dist); +}, 'Test spacing on ellipse' ); + +test(function(t) { + // 1st: polygon(nonzero, 50px 0px, 100px 50px, 50px 100px, 0px 50px) + // 2nd: polygon(nonzero, 40px 0px, 100px 70px, 10px 100px, 0px 70px) + // 3rd: polygon(nonzero, 100px 0px, 100px 100px, 10px 80px, 0px 50px) + // 4th: polygon(nonzero, 100px 100px, -10px 100px, 20px 80px, 20px 50px) + var anim = + createDiv(t).animate([ { clipPath: 'polygon(50px 0px, 100px 50px, ' + + ' 50px 100px, 0px 50px)' }, + { clipPath: 'polygon(40px 0px, 100px 70px, ' + + ' 10px 100px, 0px 70px)' }, + { clipPath: 'polygon(100px 0px, 100px 100px, ' + + ' 10px 80px, 0px 50px)' }, + { clipPath: 'polygon(100px 100px, -10px 100px, ' + + ' 20px 80px, 20px 50px)' } ], + { spacing: 'paced(clip-path)' }); + + var dist = [ 0, + Math.sqrt(10 * 10 + 20 * 20 + 40 * 40 + 20 * 20), + Math.sqrt(60 * 60 + 30 * 30 + 20 * 20 + 20 * 20), + Math.sqrt(100 * 100 + 110 * 110 + 10 * 10 + 20 * 20) ]; + assert_animation_offsets(anim, dist); +}, 'Test spacing on polygon' ); + +test(function(t) { + // Note: Rounding corners are 4 CSS pair values and + // each pair has x & y components. + // 1st: inset(5px 5px 5px 5px round 40px 30px 20px 5px / 40px 30px 20px 5px) + // 2nd: inset(10px 5px 10px 5px round 50px 60px / 50px 60px) + // 3rd: inset(40px 40px 40px 40px round 10px / 10px) + // 4th: inset(30px 40px 30px 40px round 20px / 20px) + var anim = + createDiv(t).animate([ { clipPath: 'inset(5px 5px 5px 5px ' + + ' round 40px 30px 20px 5px)' }, + { clipPath: 'inset(10px 5px round 50px 60px)' }, + { clipPath: 'inset(40px 40px round 10px)' }, + { clipPath: 'inset(30px 40px round 20px)' } ], + { spacing: 'paced(clip-path)' }); + + var dist = [ 0, + Math.sqrt(5 * 5 * 2 + (50 - 40) * (50 - 40) * 2 + + (60 - 30) * (60 - 30) * 2 + + (50 - 20) * (50 - 20) * 2 + + (60 - 5) * (60 - 5) * 2), + Math.sqrt(30 * 30 * 2 + 35 * 35 * 2 + (50 - 10) * (50 - 10) * 4 + + (60 - 10) * (60 - 10) * 4), + Math.sqrt(10 * 10 * 2 + (20 - 10) * (20 - 10) * 8) ]; + assert_animation_offsets(anim, dist); +}, 'Test spacing on inset' ); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html new file mode 100644 index 000000000..9e41b753e --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html @@ -0,0 +1,1198 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests for animation types</title> +<link rel="help" href="https://w3c.github.io/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +"use strict"; + +var gCSSProperties = { + "align-content": { + // https://drafts.csswg.org/css-align/#propdef-align-content + tests: [ + discrete("flex-start", "flex-end") + ] + }, + "align-items": { + // https://drafts.csswg.org/css-align/#propdef-align-items + tests: [ + discrete("flex-start", "flex-end") + ] + }, + "align-self": { + // https://drafts.csswg.org/css-align/#propdef-align-self + tests: [ + discrete("flex-start", "flex-end") + ] + }, + "backface-visibility": { + // https://drafts.csswg.org/css-transforms/#propdef-backface-visibility + "tests": [ + discrete("visible", "hidden") + ] + }, + "background-attachment": { + // https://drafts.csswg.org/css-backgrounds-3/#background-attachment + "tests": [ + discrete("fixed", "local") + ] + }, + "background-blend-mode": { + // https://drafts.fxtf.org/compositing-1/#propdef-background-blend-mode + "tests": [ + discrete("multiply", "screen") + ] + }, + "background-clip": { + // https://drafts.csswg.org/css-backgrounds-3/#background-clip + "tests": [ + discrete("padding-box", "content-box") + ] + }, + "background-image": { + // https://drafts.csswg.org/css-backgrounds-3/#background-image + "tests": [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "background-origin": { + // https://drafts.csswg.org/css-backgrounds-3/#background-origin + "tests": [ + discrete("padding-box", "content-box") + ] + }, + "background-repeat": { + // https://drafts.csswg.org/css-backgrounds-3/#background-repeat + "tests": [ + discrete("space", "round") + ] + }, + "border-bottom-style": { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-style + "tests": [ + discrete("dotted", "solid") + ] + }, + "border-collapse": { + // https://drafts.csswg.org/css-tables/#propdef-border-collapse + "tests": [ + discrete("collapse", "separate") + ] + }, + "border-image-outset": { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-outset + "tests": [ + discrete("1 1 1 1", "5 5 5 5") + ] + }, + "border-image-repeat": { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-repeat + "tests": [ + discrete("stretch stretch", "repeat repeat") + ] + }, + "border-image-slice": { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-slice + "tests": [ + discrete("1 1 1 1", "5 5 5 5") + ] + }, + "border-image-source": { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-source + "tests": [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "border-image-width": { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-width + "tests": [ + discrete("1 1 1 1", "5 5 5 5") + ] + }, + "border-left-style": { + // https://drafts.csswg.org/css-backgrounds-3/#border-left-style + "tests": [ + discrete("dotted", "solid") + ] + }, + "border-right-style": { + // https://drafts.csswg.org/css-backgrounds-3/#border-right-style + "tests": [ + discrete("dotted", "solid") + ] + }, + "border-top-style": { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-style + "tests": [ + discrete("dotted", "solid") + ] + }, + "box-decoration-break": { + // https://drafts.csswg.org/css-break/#propdef-box-decoration-break + "tests": [ + discrete("slice", "clone") + ] + }, + "box-sizing": { + // https://drafts.csswg.org/css-ui-4/#box-sizing + "tests": [ + discrete("content-box", "border-box") + ] + }, + "caption-side": { + // https://drafts.csswg.org/css-tables/#propdef-caption-side + "tests": [ + discrete("top", "bottom") + ] + }, + "clear": { + // https://drafts.csswg.org/css-page-floats/#propdef-clear + "tests": [ + discrete("left", "right") + ] + }, + "clip-rule": { + // https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule + tests: [ + discrete("evenodd", "nonzero") + ] + }, + "color-adjust": { + // https://drafts.csswg.org/css-color-4/#color-adjust + tests: [ + discrete("economy", "exact") + ] + }, + "color-interpolation": { + // https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty + tests: [ + discrete("linearRGB", "auto") + ] + }, + "color-interpolation-filters": { + // https://drafts.fxtf.org/filters-1/#propdef-color-interpolation-filters + tests: [ + discrete("sRGB", "linearRGB") + ] + }, + "column-fill": { + // https://drafts.csswg.org/css-multicol/#propdef-column-fill + tests: [ + discrete("auto", "balance") + ] + }, + "column-rule-style": { + // https://drafts.csswg.org/css-multicol/#propdef-column-rule-style + tests: [ + discrete("none", "dotted") + ] + }, + "contain": { + // https://drafts.csswg.org/css-containment/#propdef-contain + tests: [ + discrete("strict", "none") + ] + }, + "content": { + // https://drafts.csswg.org/css-content-3/#propdef-content + tests: [ + discrete("\"a\"", "\"b\"") + ], + tagName: "::before" + }, + "counter-increment": { + // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment + tests: [ + discrete("ident-1 1", "ident-2 2") + ] + }, + "counter-reset": { + // https://drafts.csswg.org/css-lists-3/#propdef-counter-reset + tests: [ + discrete("ident-1 1", "ident-2 2") + ] + }, + "cursor": { + // https://drafts.csswg.org/css2/ui.html#propdef-cursor + tests: [ + discrete("pointer", "wait") + ] + }, + "direction": { + // https://drafts.csswg.org/css-writing-modes-3/#propdef-direction + tests: [ + discrete("ltr", "rtl") + ] + }, + "dominant-baseline": { + // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline + tests: [ + discrete("ideographic", "alphabetic") + ] + }, + "empty-cells": { + // https://drafts.csswg.org/css-tables/#propdef-empty-cells + tests: [ + discrete("show", "hide") + ] + }, + "fill-rule": { + // https://svgwg.org/svg2-draft/painting.html#FillRuleProperty + tests: [ + discrete("evenodd", "nonzero") + ] + }, + "flex-basis": { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-basis + tests: [ + lengthPercentageOrCalc(), + discrete("auto", "10px") + ] + }, + "flex-direction": { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-direction + tests: [ + discrete("row", "row-reverse") + ] + }, + "flex-grow": { + // https://drafts.csswg.org/css-flexbox/#flex-grow-property + tests: [ + positiveNumber() + ] + }, + "flex-shrink": { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-shrink + tests: [ + positiveNumber() + ] + }, + "flex-wrap": { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-wrap + tests: [ + discrete("nowrap", "wrap") + ] + }, + "font-style": { + // https://drafts.csswg.org/css-fonts/#propdef-font-style + tests: [ + discrete("italic", "oblique") + ] + }, + "float": { + // https://drafts.csswg.org/css-page-floats/#propdef-float + tests: [ + discrete("left", "right") + ] + }, + "font-family": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-family + tests: [ + discrete("helvetica", "verdana") + ] + }, + "font-feature-settings": { + // https://drafts.csswg.org/css-fonts/#descdef-font-feature-settings + tests: [ + discrete("\"liga\" 5", "normal") + ] + }, + "font-kerning": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-kerning + tests: [ + discrete("auto", "normal") + ] + }, + "font-language-override": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override + tests: [ + discrete("\"eng\"", "normal") + ] + }, + "font-style": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-style + tests: [ + discrete("italic", "oblique") + ] + }, + "font-synthesis": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-synthesis + tests: [ + discrete("none", "weight style") + ] + }, + "font-variant-alternates": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-alternates + tests: [ + discrete("swash(unknown)", "stylistic(unknown)") + ] + }, + "font-variant-caps": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-caps + tests: [ + discrete("small-caps", "unicase") + ] + }, + "font-variant-east-asian": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-east-asian + tests: [ + discrete("full-width", "proportional-width") + ] + }, + "font-variant-ligatures": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-ligatures + tests: [ + discrete("common-ligatures", "no-common-ligatures") + ] + }, + "font-variant-numeric": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-numeric + tests: [ + discrete("lining-nums", "oldstyle-nums") + ] + }, + "font-variant-position": { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-position + tests: [ + discrete("sub", "super") + ] + }, + "grid-auto-columns": { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-columns + tests: [ + discrete("1px", "5px") + ] + }, + "grid-auto-flow": { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow + tests: [ + discrete("row", "column") + ] + }, + "grid-auto-rows": { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-rows + tests: [ + discrete("1px", "5px") + ] + }, + "grid-column-end": { + // https://drafts.csswg.org/css-grid/#propdef-grid-column-end + tests: [ + discrete("1", "5") + ] + }, + "grid-column-start": { + // https://drafts.csswg.org/css-grid/#propdef-grid-column-start + tests: [ + discrete("1", "5") + ] + }, + "grid-row-end": { + // https://drafts.csswg.org/css-grid/#propdef-grid-row-end + tests: [ + discrete("1", "5") + ] + }, + "grid-row-start": { + // https://drafts.csswg.org/css-grid/#propdef-grid-row-start + tests: [ + discrete("1", "5") + ] + }, + "grid-template-areas": { + // https://drafts.csswg.org/css-template/#grid-template-areas + tests: [ + discrete("\". . a b\" \". .a b\"", "none") + ] + }, + "grid-template-columns": { + // https://drafts.csswg.org/css-template/#grid-template-columns + tests: [ + discrete("1px", "5px") + ] + }, + "grid-template-rows": { + // https://drafts.csswg.org/css-template/#grid-template-rows + tests: [ + discrete("1px", "5px") + ] + }, + "hyphens": { + // https://drafts.csswg.org/css-text-3/#propdef-hyphens + tests: [ + discrete("manual", "auto") + ] + }, + "image-orientation": { + // https://drafts.csswg.org/css-images-3/#propdef-image-orientation + tests: [ + discrete("0deg", "90deg") + ] + }, + "ime-mode": { + // https://drafts.csswg.org/css-ui/#input-method-editor + tests: [ + discrete("disabled", "auto") + ] + }, + "initial-letter": { + // https://drafts.csswg.org/css-inline/#propdef-initial-letter + tests: [ + discrete("1 2", "3 4") + ] + }, + "isolation": { + // https://drafts.fxtf.org/compositing-1/#propdef-isolation + tests: [ + discrete("auto", "isolate") + ] + }, + "justify-content": { + // https://drafts.csswg.org/css-align/#propdef-justify-content + tests: [ + discrete("baseline", "last baseline") + ] + }, + "justify-items": { + // https://drafts.csswg.org/css-align/#propdef-justify-items + tests: [ + discrete("baseline", "last baseline") + ] + }, + "justify-self": { + // https://drafts.csswg.org/css-align/#propdef-justify-self + tests: [ + discrete("baseline", "last baseline") + ] + }, + "list-style-image": { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-image + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "list-style-position": { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-position + tests: [ + discrete("inside", "outside") + ] + }, + "list-style-type": { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-type + tests: [ + discrete("circle", "square") + ] + }, + "marker-end": { + // https://svgwg.org/specs/markers/#MarkerEndProperty + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "marker-mid": { + // https://svgwg.org/specs/markers/#MarkerMidProperty + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "marker-start": { + // https://svgwg.org/specs/markers/#MarkerStartProperty + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "mask": { + // https://drafts.fxtf.org/css-masking-1/#the-mask + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "mask-clip": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip + tests: [ + discrete("content-box", "border-box") + ] + }, + "mask-composite": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite + tests: [ + discrete("add", "subtract") + ] + }, + "mask-image": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-image + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "mask-mode": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode + tests: [ + discrete("alpha", "luminance") + ] + }, + "mask-origin": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin + tests: [ + discrete("content-box", "border-box") + ] + }, + "mask-repeat": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat + tests: [ + discrete("space", "round") + ] + }, + "mask-type": { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-type + tests: [ + discrete("alpha", "luminance") + ] + }, + "mix-blend-mode": { + // https://drafts.fxtf.org/compositing-1/#propdef-mix-blend-mode + tests: [ + discrete("multiply", "screen") + ] + }, + "object-fit": { + // https://drafts.csswg.org/css-images-3/#propdef-object-fit + tests: [ + discrete("fill", "contain") + ] + }, + "order": { + // https://drafts.csswg.org/css-flexbox/#propdef-order + tests: [ + integer() + ] + }, + "outline-style": { + // https://drafts.csswg.org/css-ui/#propdef-outline-style + tests: [ + discrete("none", "dotted") + ] + }, + "overflow-clip-box": { + // https://developer.mozilla.org/en/docs/Web/CSS/overflow-clip-box + tests: [ + discrete("padding-box", "content-box") + ] + }, + "overflow-wrap": { + // https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap + tests: [ + discrete("normal", "break-word") + ] + }, + "overflow-x": { + // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-x + tests: [ + discrete("visible", "hidden") + ] + }, + "overflow-y": { + // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-y + tests: [ + discrete("visible", "hidden") + ] + }, + "page-break-after": { + // https://drafts.csswg.org/css-break-3/#propdef-break-after + tests: [ + discrete("always", "auto") + ] + }, + "page-break-before": { + // https://drafts.csswg.org/css-break-3/#propdef-break-before + tests: [ + discrete("always", "auto") + ] + }, + "page-break-inside": { + // https://drafts.csswg.org/css-break-3/#propdef-break-inside + tests: [ + discrete("auto", "avoid") + ] + }, + "paint-order": { + // https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty + tests: [ + discrete("fill", "stroke") + ] + }, + "pointer-events": { + // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty + tests: [ + discrete("fill", "none") + ] + }, + "position": { + // https://drafts.csswg.org/css-position/#propdef-position + tests: [ + discrete("absolute", "fixed") + ] + }, + "quotes": { + // https://drafts.csswg.org/css-content-3/#propdef-quotes + tests: [ + discrete("\"“\" \"”\" \"‘\" \"’\"", "\"‘\" \"’\" \"“\" \"”\"") + ] + }, + "resize": { + // https://drafts.csswg.org/css-ui/#propdef-resize + tests: [ + discrete("both", "horizontal") + ] + }, + "ruby-align": { + // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align + tests: [ + discrete("start", "center") + ] + }, + "ruby-position": { + // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-position + tests: [ + discrete("under", "over") + ], + tagName: "ruby" + }, + "scroll-behavior": { + // https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior + tests: [ + discrete("auto", "smooth") + ] + }, + "scroll-snap-type-x": { + // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-x + tests: [ + discrete("mandatory", "proximity") + ] + }, + "scroll-snap-type-y": { + // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-y + tests: [ + discrete("mandatory", "proximity") + ] + }, + "shape-outside": { + // http://dev.w3.org/csswg/css-shapes/#propdef-shape-outside + tests: [ + discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")") + ] + }, + "shape-rendering": { + // https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty + tests: [ + discrete("optimizeSpeed", "crispEdges") + ] + }, + "stroke-linecap": { + // https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty + tests: [ + discrete("round", "square") + ] + }, + "stroke-linejoin": { + // https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty + tests: [ + discrete("round", "miter") + ], + tagName: "rect" + }, + "table-layout": { + // https://drafts.csswg.org/css-tables/#propdef-table-layout + tests: [ + discrete("auto", "fixed") + ] + }, + "text-align": { + // https://drafts.csswg.org/css-text-3/#propdef-text-align + tests: [ + discrete("start", "end") + ] + }, + "text-align-last": { + // https://drafts.csswg.org/css-text-3/#propdef-text-align-last + tests: [ + discrete("start", "end") + ] + }, + "text-anchor": { + // https://svgwg.org/svg2-draft/text.html#TextAnchorProperty + tests: [ + discrete("middle", "end") + ] + }, + "text-combine-upright": { + // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright + tests: [ + discrete("all", "none") + ] + }, + "text-decoration-line": { + // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-line + tests: [ + discrete("underline", "overline") + ] + }, + "text-decoration-style": { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-decoration-style + tests: [ + discrete("solid", "dotted") + ] + }, + "text-emphasis-position": { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-position + tests: [ + discrete("over right", "under left") + ] + }, + "text-emphasis-style": { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-style + tests: [ + discrete("filled circle", "open dot") + ] + }, + "text-orientation": { + // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation + tests: [ + discrete("upright", "sideways") + ] + }, + "text-overflow": { + // https://drafts.csswg.org/css-ui/#propdef-text-overflow + tests: [ + discrete("clip", "ellipsis") + ] + }, + "text-rendering": { + // https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty + tests: [ + discrete("optimizeSpeed", "optimizeLegibility") + ] + }, + "text-transform": { + // https://drafts.csswg.org/css-text-3/#propdef-text-transform + tests: [ + discrete("capitalize", "uppercase") + ] + }, + "touch-action": { + // https://w3c.github.io/pointerevents/#the-touch-action-css-property + tests: [ + discrete("auto", "none") + ] + }, + "transform-box": { + // https://drafts.csswg.org/css-transforms/#propdef-transform-box + tests: [ + discrete("fill-box", "border-box") + ] + }, + "transform-style": { + // https://drafts.csswg.org/css-transforms/#propdef-transform-style + tests: [ + discrete("flat", "preserve-3d") + ] + }, + "unicode-bidi": { + // https://drafts.csswg.org/css-writing-modes-3/#propdef-unicode-bidi + tests: [ + discrete("embed", "bidi-override") + ] + }, + "vector-effect": { + // https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty + tests: [ + discrete("none", "non-scaling-stroke") + ] + }, + "visibility": { + // https://drafts.csswg.org/css2/visufx.html#propdef-visibility + tests: [ + visibility() + ] + }, + "white-space": { + // https://drafts.csswg.org/css-text-4/#propdef-white-space + tests: [ + discrete("pre", "nowrap") + ] + }, + "word-break": { + // https://drafts.csswg.org/css-text-3/#propdef-word-break + tests: [ + discrete("keep-all", "break-all") + ] + }, + "will-change": { + // http://dev.w3.org/csswg/css-will-change/#propdef-will-change + tests: [ + discrete("scroll-position", "contents") + ] + }, + "writing-mode": { + // https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode + tests: [ + discrete("vertical-rl", "sideways-rl") + ] + }, +} + +for (var property in gCSSProperties) { + if (!isSupported(property)) { + continue; + } + var testData = gCSSProperties[property]; + testData.tests.forEach(function(testFunction) { + testFunction(property, testData); + }); +} + +function discrete(from, to) { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = [from, to]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 499, expected: from.toLowerCase() }, + { time: 500, expected: to.toLowerCase() }, + { time: 1000, expected: to.toLowerCase() }]); + }, property + " uses discrete animation when animating between '" + + from + "' and '" + to + "' with linear easing"); + + test(function(t) { + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = [from, to]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both", + easing: "cubic-bezier(0.68,0,1,0.01)" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 940, expected: from.toLowerCase() }, + { time: 960, expected: to.toLowerCase() }]); + }, property + " uses discrete animation when animating between '" + + from + "' and '" + to + "' with effect easing"); + + test(function(t) { + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = [from, to]; + keyframes.easing = "cubic-bezier(0.68,0,1,0.01)"; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 940, expected: from.toLowerCase() }, + { time: 960, expected: to.toLowerCase() }]); + }, property + " uses discrete animation when animating between '" + + from + "' and '" + to + "' with keyframe easing"); + } +} + +function length() { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["10px", "50px"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10px" }, + { time: 500, expected: "30px" }, + { time: 1000, expected: "50px" }]); + }, property + " supports animating as a length"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["1rem", "5rem"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10px" }, + { time: 500, expected: "30px" }, + { time: 1000, expected: "50px" }]); + }, property + " supports animating as a length of rem"); + } +} + +function percentage() { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["10%", "50%"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10%" }, + { time: 500, expected: "30%" }, + { time: 1000, expected: "50%" }]); + }, property + " supports animating as a percentage"); + } +} + +function integer() { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = [-2, 2]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "-2" }, + { time: 500, expected: "0" }, + { time: 1000, expected: "2" }]); + }, property + " supports animating as an integer"); + } +} + +function positiveNumber() { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = [1.1, 1.5]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "1.1" }, + { time: 500, expected: "1.3" }, + { time: 1000, expected: "1.5" }]); + }, property + " supports animating as a positive number"); + } +} + +function lengthPercentageOrCalc() { + return function(property, options) { + length()(property, options); + percentage()(property, options); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["10px", "20%"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10px" }, + { time: 500, expected: "calc(5px + 10%)" }, + { time: 1000, expected: "20%" }]); + }, property + " supports animating as combination units 'px' and '%'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["10%", "2em"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10%" }, + { time: 500, expected: "calc(10px + 5%)" }, + { time: 1000, expected: "20px" }]); + }, property + " supports animating as combination units '%' and 'em'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["1em", "2rem"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10px" }, + { time: 500, expected: "15px" }, + { time: 1000, expected: "20px" }]); + }, property + " supports animating as combination units 'em' and 'rem'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["10px", "calc(1em + 20%)"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "10px" }, + { time: 500, expected: "calc(10px + 10%)" }, + { time: 1000, expected: "calc(10px + 20%)" }]); + }, property + " supports animating as combination units 'px' and 'calc'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["calc(10px + 10%)", "calc(1em + 1rem + 20%)"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, + expected: "calc(10px + 10%)" }, + { time: 500, + expected: "calc(15px + 15%)" }, + { time: 1000, + expected: "calc(20px + 20%)" }]); + }, property + " supports animating as a calc"); + } +} + +function visibility() { + return function(property, options) { + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["visible", "hidden"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "visible" }, + { time: 999, expected: "visible" }, + { time: 1000, expected: "hidden" }]); + }, property + " uses visibility animation when animating " + + "from 'visible' to 'hidden'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["hidden", "visible"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "hidden" }, + { time: 1, expected: "visible" }, + { time: 1000, expected: "visible" }]); + }, property + " uses visibility animation when animating " + + "from 'hidden' to 'visible'"); + + test(function(t) { + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["hidden", "collapse"]; + var target = createTestElement(t, options.tagName); + var animation = target.animate(keyframes, + { duration: 1000, fill: "both" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "hidden" }, + { time: 499, expected: "hidden" }, + { time: 500, expected: "collapse" }, + { time: 1000, expected: "collapse" }]); + }, property + " uses visibility animation when animating " + + "from 'hidden' to 'collapse'"); + + test(function(t) { + // Easing: http://cubic-bezier.com/#.68,-.55,.26,1.55 + // With this curve, the value is less than 0 till about 34% + // also more than 1 since about 63% + var idlName = propertyToIDL(property); + var keyframes = {}; + keyframes[idlName] = ["visible", "hidden"]; + var target = createTestElement(t, options.tagName); + var animation = + target.animate(keyframes, + { duration: 1000, fill: "both", + easing: "cubic-bezier(0.68, -0.55, 0.26, 1.55)" }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: "visible" }, + { time: 1, expected: "visible" }, + { time: 330, expected: "visible" }, + { time: 340, expected: "visible" }, + { time: 620, expected: "visible" }, + { time: 630, expected: "hidden" }, + { time: 1000, expected: "hidden" }]); + }, property + " uses visibility animation when animating " + + "from 'visible' to 'hidden' with easeInOutBack easing"); + } +} + +function testAnimationSamples(animation, idlName, testSamples) { + var type = animation.effect.target.type; + var target = type + ? animation.effect.target.parentElement + : animation.effect.target; + testSamples.forEach(function(testSample) { + animation.currentTime = testSample.time; + assert_equals(getComputedStyle(target, type)[idlName], + testSample.expected, + "The value should be " + testSample.expected + + " at " + testSample.time + "ms"); + }); +} + +function createTestElement(t, tagName) { + return tagName && tagName.startsWith("::") + ? createPseudo(t, tagName.substring(2)) + : createElement(t, tagName); +} + +function isSupported(property) { + var testKeyframe = new TestKeyframe(propertyToIDL(property)); + try { + // Since TestKeyframe returns 'undefined' for |property|, + // the KeyframeEffect constructor will throw + // if the string "undefined" is not a valid value for the property. + new KeyframeEffect(null, testKeyframe); + } catch(e) {} + return testKeyframe.propAccessCount !== 0; +} + +function TestKeyframe(testProp) { + var _propAccessCount = 0; + + Object.defineProperty(this, testProp, { + get: function() { _propAccessCount++; }, + enumerable: true + }); + + Object.defineProperty(this, 'propAccessCount', { + get: function() { return _propAccessCount; } + }); +} + +function propertyToIDL(property) { + // https://w3c.github.io/web-animations/#animation-property-name-to-idl-attribute-name + if (property === "float") { + return "cssFloat"; + } + return property.replace(/-[a-z]/gi, + function (str) { + return str.substr(1).toUpperCase(); }); +} + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html new file mode 100644 index 000000000..07fb6097c --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests that property values respond to changes to their context</title> +<link rel="help" href="https://w3c.github.io/web-animations/#keyframes-section"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> + +test(function(t) { + var div = createDiv(t); + div.style.fontSize = '10px'; + var animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value before updating font-size'); + div.style.fontSize = '20px'; + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size'); +}, 'Effect values reflect changes to font-size on element'); + +test(function(t) { + var parentDiv = createDiv(t); + var div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + + var animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value before updating font-size on parent element'); + parentDiv.style.fontSize = '20px'; + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size on parent element'); +}, 'Effect values reflect changes to font-size on parent element'); + +promise_test(function(t) { + var parentDiv = createDiv(t); + var div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + var animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + + animation.pause(); + animation.currentTime = 500; + parentDiv.style.fontSize = '20px'; + + return animation.ready.then(function() { + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size on parent element'); + }); +}, 'Effect values reflect changes to font-size when computed style is not' + + ' immediately flushed'); + +promise_test(function(t) { + var divWith10pxFontSize = createDiv(t); + divWith10pxFontSize.style.fontSize = '10px'; + var divWith20pxFontSize = createDiv(t); + divWith20pxFontSize.style.fontSize = '20px'; + + var div = createDiv(t); + div.remove(); // Detach + var animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.pause(); + + return animation.ready.then(function() { + animation.currentTime = 500; + + divWith10pxFontSize.appendChild(div); + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value after attaching to font-size:10px parent'); + divWith20pxFontSize.appendChild(div); + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after attaching to font-size:20px parent'); + }); +}, 'Effect values reflect changes to font-size from reparenting'); + +test(function(t) { + var divA = createDiv(t); + divA.style.fontSize = '10px'; + + var divB = createDiv(t); + divB.style.fontSize = '20px'; + + var animation = divA.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(divA).marginLeft, '150px', + 'Effect value before updating target element'); + + animation.effect.target = divB; + assert_equals(getComputedStyle(divB).marginLeft, '300px', + 'Effect value after updating target element'); +}, 'Effect values reflect changes to target element'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/spacing-keyframes.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/spacing-keyframes.html new file mode 100644 index 000000000..90e26d276 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/spacing-keyframes.html @@ -0,0 +1,391 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Keyframe spacing tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#spacing-keyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC }); + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[0].computedOffset, 0.0, '1st frame offset'); + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, 'last frame offset'); +}, 'Test distribute spacing'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px', offset: 0.5 }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'distribute' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames[0].computedOffset, 0.0, '1st frame offset'); + assert_equals(frames[1].computedOffset, 0.5 * 1 / 2, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 0.5, '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, 'last frame offset'); +}, 'Test distribute spacing with specific offsets'); + +test(function(t) { + var anim = createDiv(t).animate(null, + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames.length, 0, "empty keyframe list"); +}, 'Test paced spacing without any keyframe'); + + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 20, 140, 190]; + assert_equals(frames[0].computedOffset, 0.0, + '1st frame offset'); + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, + 'last frame offset'); +}, 'Test paced spacing'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px', offset: 0.5 }, + { marginLeft: '120px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist1 = [ 0, 20, 140 ]; + var cumDist2 = [ 0, 20, 90 ]; + assert_equals(frames[1].computedOffset, 0.5 * cumDist1[1] / cumDist1[2], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, 0.5, + '3rd frame offset'); + assert_equals(frames[3].computedOffset, 0.5 + 0.5 * cumDist2[1] / cumDist2[2], + '4th frame offset'); +}, 'Test paced spacing with specific offsets'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '0px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 0, 100, 150]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); +}, 'Test paced spacing if some paced property values are equal'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '0px' }, + { marginLeft: '0px' }, + { marginLeft: '0px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); +}, 'Test falling back to distribute spacing if all paced property value ' + + 'are equal'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginTop: '-20px' }, + { marginLeft: '100px' }, + { margin: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames[1].computedOffset, frames[2].computedOffset * 1 / 2, + '2nd frame offset using distribute spacing'); + assert_equals(frames[2].computedOffset, 100 / 150, + '3rd frame offset using paced spacing'); +}, 'Test paced spacing if there a keyframe without the paced property'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginTop: '40px' }, + { marginTop: '-20px' }, + { marginLeft: '40px' }, + { marginTop: '60px' }, + { margin: '10px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 0, 0, 40, 40, 70]; + assert_equals(frames[1].computedOffset, frames[3].computedOffset * 1 / 3, + '2nd frame offset using distribute spacing'); + assert_equals(frames[2].computedOffset, frames[3].computedOffset * 2 / 3, + '3rd frame offset using distribute spacing'); + assert_equals(frames[3].computedOffset, cumDist[3] / cumDist[5], + '4th frame offset using paced spacing'); + assert_equals(frames[4].computedOffset, + frames[3].computedOffset + + (1 - frames[3].computedOffset) * 1 / 2, + '5th frame offset using distribute spacing'); +}, 'Test paced spacing if a paced property that appears on only some ' + + 'keyframes'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginTop: '-20px', offset: 0.5 }, + { marginLeft: '40px' }, + { marginLeft: '100px' }, + { margin: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames[2].computedOffset, 0.5 + 0.5 * 1 / 3, + '3rd frame offset using distribute spacing because it is the ' + + 'first paceable keyframe'); + assert_equals(frames[3].computedOffset, + frames[2].computedOffset + + (1.0 - frames[2].computedOffset) * 60 / 110, + '4th frame offset using paced spacing'); +}, 'Test paced spacing if a paced property that appears on only some ' + + 'keyframes and there is a specific offset'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginTop: '20px', offset: 0.2 }, + { marginTop: '40px' }, + { marginTop: '-20px' }, + { marginLeft: '-20px' }, + { marginLeft: '40px' }, + { marginTop: '60px' }, + { marginLeft: '100px' }, + { marginTop: '50px' }, + { marginTop: '100px', offset: 0.8 }, + { margin: '0px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + var frames = anim.effect.getKeyframes(); + // Test distribute spacing in (A, Paced A] and [Paced B, frame B). + var slots = frames.length - 3; + var start = 0.2; + var diff = 0.8 - start; + assert_equals(frames[2].computedOffset, start + diff * 1.0 / slots, + '3nd frame offset using distribute spacing'); + assert_equals(frames[3].computedOffset, start + diff * 2.0 / slots, + '4rd frame offset using distribute spacing'); + assert_equals(frames[4].computedOffset, start + diff * 3.0 / slots, + '5th frame offset using distribute spacing because it is ' + + 'the first paceable keyframe'); + assert_equals(frames[7].computedOffset, start + diff * 6.0 / slots, + '8th frame offset using distribute spacing because it is ' + + 'the last paceable keyframe'); + assert_equals(frames[8].computedOffset, start + diff * 7.0 / slots, + '9th frame offset using distribute spacing'); + // Test paced spacing and other null computed offsets in (Paced A, Paced B). + var cumDist = [0, 60, 60, 120]; + assert_equals(frames[5].computedOffset, + frames[4].computedOffset + cumDist[2] / cumDist[3] * + (frames[7].computedOffset - frames[4].computedOffset), + '6th frame offset using paced spacing'); + assert_equals(frames[6].computedOffset, + frames[5].computedOffset + 1.0 / 2.0 * + (frames[7].computedOffset - frames[5].computedOffset), + '7th frame offset using distribute spacing'); +}, 'Test paced spacing where there are some keyframes without offsets and ' + + 'without the paced property before the first paceable keyframe and ' + + 'after the last paceable keyframe'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { margin: '-20px' }, + { margin: '100px' }, + { margin: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 20, 140, 190]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); +}, 'Test paced spacing for using shorthand property'); + +test(function(t) { + var anim = + createDiv(t).animate([ { marginLeft: '0px', marginRight: '0px', + marginTop: '10px', marginBottom: '10px' }, + { marginLeft: '-20px', marginRight: '-20px', + marginTop: '0px', marginBottom: '0px' }, + { marginLeft: '100px', marginRight: '100px', + marginTop: '-50px', marginBottom: '-50px' }, + { marginLeft: '50px', marginRight: '50px', + marginTop: '80px', marginBottom: '80px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin)' }); + + var frames = anim.effect.getKeyframes(); + var dist = [ 0, + Math.sqrt(20 * 20 * 2 + 10 * 10 * 2), + Math.sqrt(120 * 120 * 2 + 50 * 50 * 2), + Math.sqrt(50 * 50 * 2 + 130 * 130 * 2) ]; + var cumDist = []; + dist.reduce(function(prev, curr, i) { return cumDist[i] = prev + curr; }, 0); + assert_approx_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + 0.0001, '2nd frame offset'); + assert_approx_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + 0.0001, '3rd frame offset'); +}, 'Test paced spacing using shorthand property where only the longhand ' + + 'components are specified'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px', marginTop: '0px' }, + { marginLeft: '-20px', marginTop: '-20px' }, + { marginLeft: '100px', marginTop: '100px' }, + { marginLeft: '50px', marginTop: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin)' }); + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[1].computedOffset, 1 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2 / slots, '3rd frame offset'); +}, 'Test falling back to distribute spacing if all keyframe miss some ' + + 'components'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginLeft: '-20px' }, + { marginTop: '40px' }, + { margin: '100px' }, + { margin: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin)' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames[1].computedOffset, frames[3].computedOffset * 1 / 3, + '2nd frame offset using distribute spacing'); + assert_equals(frames[2].computedOffset, frames[3].computedOffset * 2 / 3, + '3rd frame offset using distribute spacing'); + assert_equals(frames[3].computedOffset, 100 / 150, + '4th frame offset using paced spacing'); +}, 'Test paced spacing only for keyframes specifying all longhand ' + + 'components, and falling back to distribute spacing for the reset'); + +test(function(t) { + var anim = createDiv(t).animate([ { margin: '0px' }, + { marginLeft: '-20px' }, + { marginTop: '40px', offset: 0.5 }, + { margin: '100px' }, + { margin: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin)' }); + + var frames = anim.effect.getKeyframes(); + assert_equals(frames[1].computedOffset, 0.5 * 1 / 2, + '2nd frame offset using distribute spacing'); + assert_equals(frames[3].computedOffset, 0.5 + 0.5 * 1 / 2, + '4th frame offset using distribute spacing because it is the ' + + 'first paceable keyframe from a non-null offset keyframe'); +}, 'Test paced spacing only for keyframes specifying all some components, ' + + 'and falling back to distribute spacing for the reset with some specific ' + + 'offsets'); + +// Tests for setting spacing by KeyframeEffect.spacing. + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC }); + + anim.effect.spacing = 'paced(margin-left)'; + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 20, 140, 190]; + assert_equals(frames[0].computedOffset, 0.0, + '1st frame offset'); + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, + 'last frame offset'); +}, 'Test paced spacing by setter'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + anim.effect.spacing = 'distribute'; + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[0].computedOffset, 0.0, '1st frame offset'); + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, 'last frame offset'); +}, 'Test distribute spacing by setter'); + +test(function(t) { + var anim = + createDiv(t).animate([ { marginLeft: '0px', borderRadius: '0%' }, + { marginLeft: '-20px', borderRadius: '50%' }, + { marginLeft: '100px', borderRadius: '25%' }, + { marginLeft: '50px', borderRadius: '100%' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + anim.effect.spacing = 'paced(border-radius)'; + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 50, 50 + 25, 50 + 25 + 75]; + + assert_equals(frames[0].computedOffset, 0.0, + '1st frame offset'); + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, + 'last frame offset'); +}, 'Test paced spacing by changing the paced property'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html new file mode 100644 index 000000000..eb67f669a --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/the-effect-value-of-a-keyframe-effect.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Keyframe handling tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate([ { offset: 0, opacity: 0 }, + { offset: 0, opacity: 0.1 }, + { offset: 0, opacity: 0.2 }, + { offset: 1, opacity: 0.8 }, + { offset: 1, opacity: 0.9 }, + { offset: 1, opacity: 1 } ], + { duration: 1000, + easing: 'cubic-bezier(0.5, -0.5, 0.5, 1.5)' }); + assert_equals(getComputedStyle(div).opacity, '0.2', + 'When progress is zero the last keyframe with offset 0 should' + + ' be used'); + // http://cubic-bezier.com/#.5,-0.5,.5,1.5 + // At t=0.15, the progress should be negative + anim.currentTime = 150; + assert_equals(getComputedStyle(div).opacity, '0', + 'When progress is negative, the first keyframe with a 0 offset' + + ' should be used'); + // At t=0.71, the progress should be just less than 1 + anim.currentTime = 710; + assert_approx_equals(parseFloat(getComputedStyle(div).opacity), 0.8, 0.01, + 'When progress is just less than 1, the first keyframe with an' + + ' offset of 1 should be used as the interval endpoint'); + // At t=0.85, the progress should be > 1 + anim.currentTime = 850; + assert_equals(getComputedStyle(div).opacity, '1', + 'When progress is greater than 1.0, the last keyframe with a 1' + + ' offset should be used'); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', + 'When progress is equal to 1.0, the last keyframe with a 1' + + ' offset should be used'); +}, 'Overlapping keyframes at 0 and 1 use the appropriate value when the' + + ' progress is outside the range [0, 1]'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate([ { offset: 0, opacity: 0 }, + { offset: 0.5, opacity: 0.3 }, + { offset: 0.5, opacity: 0.5 }, + { offset: 0.5, opacity: 0.7 }, + { offset: 1, opacity: 1 } ], 1000); + anim.currentTime = 250; + assert_equals(getComputedStyle(div).opacity, '0.15', + 'Before the overlap point, the first keyframe from the' + + ' overlap point should be used as interval endpoint'); + anim.currentTime = 500; + assert_equals(getComputedStyle(div).opacity, '0.7', + 'At the overlap point, the last keyframe from the' + + ' overlap point should be used as interval startpoint'); + anim.currentTime = 750; + assert_equals(getComputedStyle(div).opacity, '0.85', + 'After the overlap point, the last keyframe from the' + + ' overlap point should be used as interval startpoint'); +}, 'Overlapping keyframes between 0 and 1 use the appropriate value on each' + + ' side of the overlap point'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ visibility: ['hidden','visible'] }, + { duration: 100 * MS_PER_SEC, fill: 'both' }); + + anim.currentTime = 0; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress = 0.'); + + anim.currentTime = 10 * MS_PER_SEC + 1; + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress > 0 due to linear easing.'); + + anim.finish(); + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress = 1.'); + +}, "Test visibility clamping behavior."); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ visibility: ['hidden', 'visible'] }, + { duration: 100 * MS_PER_SEC, fill: 'both', + easing: 'cubic-bezier(0.25, -0.6, 0, 0.5)' }); + + anim.currentTime = 0; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress = 0.'); + + // Timing function is below zero. So we expected visibility is hidden. + anim.currentTime = 10 * MS_PER_SEC + 1; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress < 0 due to cubic-bezier easing.'); + + anim.currentTime = 60 * MS_PER_SEC; + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress > 0 due to cubic-bezier easing.'); + +}, "Test visibility clamping behavior with an easing that has a negative component"); + +done(); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html new file mode 100644 index 000000000..a54298aa4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animatable.animate tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-animate"> +<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> +<iframe width="10" height="10" id="iframe"></iframe> +<script> +'use strict'; + +// Tests on Element + +test(function(t) { + var div = createDiv(t); + var anim = div.animate(null); + assert_class_string(anim, 'Animation', 'Returned object is an Animation'); +}, 'Element.animate() creates an Animation object'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype, + 'The prototype of the created Animation is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype, + 'The prototype of the created Animation is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object in the relevant realm of' + + ' the target element'); + +test(function(t) { + var div = createDiv(t); + var anim = Element.prototype.animate.call(div, null); + assert_class_string(anim.effect, 'KeyframeEffect', + 'Returned Animation has a KeyframeEffect'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim.effect), + iframe.KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim.effect), + KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect' + + ' that is created in the relevant realm of the target element'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = div.animate(null); + assert_equals(Object.getPrototypeOf(anim.effect.timing), + iframe.AnimationEffectTiming.prototype, + 'The prototype of the created AnimationEffectTiming is that' + + ' defined on the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim.effect.timing), + AnimationEffectTiming.prototype, + 'The prototype of the created AnimationEffectTiming is NOT' + + ' that of the current global'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect' + + ' whose AnimationEffectTiming object is created in the relevant realm' + + ' of the target element'); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate(subtest.input, 2000); + assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); + }, 'Element.animate() accepts ' + subtest.desc); +}); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate(subtest.input, 2000); + assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); + }, 'Element.animate() accepts ' + subtest.desc); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + assert_throws(subtest.expected, function() { + div.animate(subtest.input, 2000); + }); + }, 'Element.animate() does not accept ' + subtest.desc); +}); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.effect.timing.duration, 2000); + // Also check that unspecified parameters receive their default values + assert_equals(anim.effect.timing.fill, 'auto'); +}, 'Element.animate() accepts a double as an options argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: Infinity, fill: 'forwards' }); + assert_equals(anim.effect.timing.duration, Infinity); + assert_equals(anim.effect.timing.fill, 'forwards'); + // Also check that unspecified parameters receive their default values + assert_equals(anim.effect.timing.direction, 'normal'); +}, 'Element.animate() accepts a KeyframeAnimationOptions argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }); + assert_equals(anim.effect.timing.duration, 'auto'); +}, 'Element.animate() accepts an absent options argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.id, ''); +}, 'Element.animate() correctly sets the id attribute when no id is specified'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, { id: 'test' }); + assert_equals(anim.id, 'test'); +}, 'Element.animate() correctly sets the id attribute'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.timeline, document.timeline); +}, 'Element.animate() correctly sets the Animation\'s timeline'); + +async_test(function(t) { + var iframe = document.createElement('iframe'); + iframe.width = 10; + iframe.height = 10; + + iframe.addEventListener('load', t.step_func(function() { + var div = createDiv(t, iframe.contentDocument); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.timeline, iframe.contentDocument.timeline); + iframe.remove(); + t.done(); + })); + + document.body.appendChild(iframe); +}, 'Element.animate() correctly sets the Animation\'s timeline when ' + + 'triggered on an element in a different document'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.playState, 'pending'); +}, 'Element.animate() calls play on the Animation'); + +// Tests on CSSPseudoElement + +test(function(t) { + var pseudoTarget = createPseudo(t, 'before'); + var anim = pseudoTarget.animate(null); + assert_class_string(anim, 'Animation', 'The returned object is an Animation'); +}, 'CSSPseudoElement.animate() creates an Animation object'); + +test(function(t) { + var pseudoTarget = createPseudo(t, 'before'); + var anim = pseudoTarget.animate(null); + assert_equals(anim.effect.target, pseudoTarget, + 'The returned Animation targets to the correct object'); +}, 'CSSPseudoElement.animate() creates an Animation object targeting ' + + 'to the correct CSSPseudoElement object'); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html b/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html new file mode 100644 index 000000000..f8f174abd --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.cancel()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-cancel"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({transform: ['translate(100px)', 'translate(100px)']}, + 100 * MS_PER_SEC); + return animation.ready.then(function() { + assert_not_equals(getComputedStyle(div).transform, 'none', + 'transform style is animated before cancelling'); + animation.cancel(); + assert_equals(getComputedStyle(div).transform, 'none', + 'transform style is no longer animated after cancelling'); + }); +}, 'Animated style is cleared after calling Animation.cancel()'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({marginLeft: ['100px', '200px']}, + 100 * MS_PER_SEC); + animation.effect.timing.easing = 'linear'; + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'margin-left style is updated when cancelled animation is' + + ' seeked'); +}, 'After cancelling an animation, it can still be seeked'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({marginLeft:['100px', '200px']}, + 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + animation.play(); + assert_equals(getComputedStyle(div).marginLeft, '100px', + 'margin-left style is animated after re-starting animation'); + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation succeeds in running after being re-started'); + }); +}, 'After cancelling an animation, it can still be re-used'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html b/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html new file mode 100644 index 000000000..4f76194b6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation constructor tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animation-animation"> +<link rel="author" title="Hiroyuki Ikezoe" href="mailto:hiikezoe@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +var gTarget = document.getElementById("target"); + +function createEffect() { + return new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] }); +} + +function createNull() { + return null; +} + +var gTestArguments = [ + { + createEffect: createNull, + timeline: null, + expectedTimeline: null, + expectedTimelineDescription: "null", + description: "with null effect and null timeline" + }, + { + createEffect: createNull, + timeline: document.timeline, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with null effect and non-null timeline" + }, + { + createEffect: createNull, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with null effect and no timeline parameter" + }, + { + createEffect: createEffect, + timeline: null, + expectedTimeline: null, + expectedTimelineDescription: "null", + description: "with non-null effect and null timeline" + }, + { + createEffect: createEffect, + timeline: document.timeline, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with non-null effect and non-null timeline" + }, + { + createEffect: createEffect, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with non-null effect and no timeline parameter" + }, +]; + +gTestArguments.forEach(function(args) { + test(function(t) { + var effect = args.createEffect(); + var animation = new Animation(effect, args.timeline); + + assert_not_equals(animation, null, + "An animation sohuld be created"); + assert_equals(animation.effect, effect, + "Animation returns the same effect passed to " + + "the Constructor"); + assert_equals(animation.timeline, args.expectedTimeline, + "Animation timeline should be " + args.expectedTimelineDescription); + assert_equals(animation.playState, "idle", + "Animation.playState should be initially 'idle'"); + }, "Animation can be constructed " + args.description); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, + { left: ["10px", "20px"] }, + { duration: 10000, + fill: "forwards" }); + var anim = new Animation(effect, document.timeline); + anim.pause(); + assert_equals(effect.getComputedTiming().progress, 0.0); + anim.currentTime += 5000; + assert_equals(effect.getComputedTiming().progress, 0.5); + anim.finish(); + assert_equals(effect.getComputedTiming().progress, 1.0); +}, "Animation constructed by an effect with null target runs normally"); + +async_test(function(t) { + var iframe = document.createElement('iframe'); + + iframe.addEventListener('load', t.step_func(function() { + var div = createDiv(t, iframe.contentDocument); + var effect = new KeyframeEffectReadOnly(div, null, 10000); + var anim = new Animation(effect); + assert_equals(anim.timeline, document.timeline); + iframe.remove(); + t.done(); + })); + + document.body.appendChild(iframe); +}, "Animation constructed with a keyframe that target element is in iframe"); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html b/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html new file mode 100644 index 000000000..d881a96a7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.effect tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var anim = new Animation(); + assert_equals(anim.effect, null, "initial effect is null"); + + var newEffect = new KeyframeEffectReadOnly(createDiv(t), null); + anim.effect = newEffect; + assert_equals(anim.effect, newEffect, "new effect is set"); +}, "effect is set correctly."); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html b/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html new file mode 100644 index 000000000..9b9c4c710 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html @@ -0,0 +1,246 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.finish()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finish"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +var gKeyFrames = { 'marginLeft': ['100px', '200px'] }; + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0; + + assert_throws({name: 'InvalidStateError'}, function() { + animation.finish(); + }); +}, 'Test exceptions when finishing non-running animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, + {duration : 100 * MS_PER_SEC, + iterations : Infinity}); + + assert_throws({name: 'InvalidStateError'}, function() { + animation.finish(); + }); +}, 'Test exceptions when finishing infinite animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set to the end ' + + 'of the active duration'); +}, 'Test finishing of animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + // 1s past effect end + animation.currentTime = + animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC; + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set back to the ' + + 'end of the active duration'); +}, 'Test finishing of animation with a current time past the effect end'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(function() { + animation.playbackRate = -1; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set to zero'); + }); +}, 'Test finishing of reversed animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(function() { + animation.playbackRate = -1; + animation.currentTime = -1000; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set back to zero'); + }); +}, 'Test finishing of reversed animation with a current time less than zero'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + return animation.ready.then(function() { + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a paused animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC, + 'The start time of a paused animation should be set ' + + 'after calling finish()'); + }); +}, 'Test finish() while paused'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + // Update playbackRate so we can test that the calculated startTime + // respects it + animation.playbackRate = 2; + // While animation is still pause-pending call finish() + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 2, + 'The start time of a pause-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while pause-pending with positive playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + animation.playbackRate = -2; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_equals(animation.startTime, animation.timeline.currentTime, + 'The start time of a pause-pending animation should be ' + + 'set after calling finish()'); +}, 'Test finish() while pause-pending with negative playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0.5; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a play-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5, + 'The start time of a play-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while play-pending'); + +// FIXME: Add a test for when we are play-pending without an active timeline. +// - In that case even after calling finish() we should still be pending but +// the current time should be updated + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.pause(); + animation.play(); + // We are now in the unusual situation of being play-pending whilst having + // a resolved start time. Check that finish() still triggers a transition + // to the finished state immediately. + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'After aborting a pause then calling finish() the play ' + + 'state of an animation should become "finished" immediately'); + }); +}, 'Test finish() during aborted pause'); + +promise_test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.finish(); + var marginLeft = parseFloat(getComputedStyle(div).marginLeft); + + assert_equals(marginLeft, 10, + 'The computed style should be reset when finish() is ' + + 'called'); + }); +}, 'Test resetting of computed style'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously'); + +promise_test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var animation = new Animation(effect, document.timeline); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously with an animation ' + + 'without a target'); + +promise_test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var animation = new Animation(effect, document.timeline); + animation.play(); + + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.currentTime = animation.effect.getComputedTiming().endTime - 1; + return waitForAnimationFrames(2); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation finishes normally'); + }); +}, 'Test normally finished animation resolves finished promise synchronously ' + + 'with an animation without a target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html b/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html new file mode 100644 index 000000000..d56de1b03 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html @@ -0,0 +1,370 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.finished</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finished"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + return animation.ready.then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing starts'); + animation.pause(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise does not change when pausing'); + animation.play(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise does not change when play() unpauses'); + + animation.currentTime = 100 * MS_PER_SEC; + + return animation.finished; + }).then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing completes'); + }); +}, 'Test pausing then playing does not change the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing completes'); + animation.play(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise changes when replaying animation'); + + previousFinishedPromise = animation.finished; + animation.play(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same after redundant play() call'); + + }); +}, 'Test restarting a finished animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise; + animation.finish(); + return animation.finished.then(function() { + previousFinishedPromise = animation.finished; + animation.playbackRate = -1; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should be replaced when reversing a ' + + 'finished promise'); + animation.currentTime = 0; + return animation.finished; + }).then(function() { + previousFinishedPromise = animation.finished; + animation.play(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise is replaced after play() call on ' + + 'finished, reversed animation'); + }); +}, 'Test restarting a reversed finished animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.currentTime = 100 * MS_PER_SEC + 1000; + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is unchanged jumping past end of ' + + 'finished animation'); + }); +}, 'Test redundant finishing of animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + // Setup callback to run if finished promise is resolved + var finishPromiseResolved = false; + animation.finished.then(function() { + finishPromiseResolved = true; + }); + return animation.ready.then(function() { + // Jump to mid-way in interval and pause + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + return animation.ready; + }).then(function() { + // Jump to the end + // (But don't use finish() since that should unpause as well) + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }).then(function() { + assert_false(finishPromiseResolved, + 'Finished promise should not resolve when paused'); + }); +}, 'Finished promise does not resolve when paused'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + // Setup callback to run if finished promise is resolved + var finishPromiseResolved = false; + animation.finished.then(function() { + finishPromiseResolved = true; + }); + return animation.ready.then(function() { + // Jump to mid-way in interval and pause + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + // Jump to the end + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }).then(function() { + assert_false(finishPromiseResolved, + 'Finished promise should not resolve when pause-pending'); + }); +}, 'Finished promise does not resolve when pause-pending'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.finish(); + return animation.finished.then(function(resolvedAnimation) { + assert_equals(resolvedAnimation, animation, + 'Object identity of animation passed to Promise callback' + + ' matches the animation object owning the Promise'); + }); +}, 'The finished promise is fulfilled with its Animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + + // Set up listeners on finished promise + var retPromise = animation.finished.then(function() { + assert_unreached('finished promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after the original is ' + + 'rejected'); + }); + + animation.cancel(); + + return retPromise; +}, 'finished promise is rejected when an animation is cancelled by calling ' + + 'cancel()'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.cancel(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'A new finished promise should be created when' + + ' cancelling a finished animation'); + }); +}, 'cancelling an already-finished animation replaces the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + // The spec says we still create a new finished promise and reject the old + // one even if we're already idle. That behavior might change, but for now + // test that we do that. + var retPromise = animation.finished.catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + }); + + // Redundant call to cancel(); + var previousFinishedPromise = animation.finished; + animation.cancel(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'A redundant call to cancel() should still generate a new' + + ' finished promise'); + return retPromise; +}, 'cancelling an idle animation still replaces the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + const HALF_DUR = 100 * MS_PER_SEC / 2; + const QUARTER_DUR = 100 * MS_PER_SEC / 4; + var gotNextFrame = false; + var currentTimeBeforeShortening; + animation.currentTime = HALF_DUR; + return animation.ready.then(function() { + currentTimeBeforeShortening = animation.currentTime; + animation.effect.timing.duration = QUARTER_DUR; + // Below we use gotNextFrame to check that shortening of the animation + // duration causes the finished promise to resolve, rather than it just + // getting resolved on the next animation frame. This relies on the fact + // that the promises are resolved as a micro-task before the next frame + // happens. + waitForAnimationFrames(1).then(function() { + gotNextFrame = true; + }); + + return animation.finished; + }).then(function() { + assert_false(gotNextFrame, 'shortening of the animation duration should ' + + 'resolve the finished promise'); + assert_equals(animation.currentTime, currentTimeBeforeShortening, + 'currentTime should be unchanged when duration shortened'); + var previousFinishedPromise = animation.finished; + animation.effect.timing.duration = 100 * MS_PER_SEC; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after lengthening the ' + + 'duration causes the animation to become active'); + }); +}, 'Test finished promise changes for animation duration changes'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var retPromise = animation.ready.then(function() { + animation.playbackRate = 0; + animation.currentTime = 100 * MS_PER_SEC + 1000; + return waitForAnimationFrames(2); + }); + + animation.finished.then(t.step_func(function() { + assert_unreached('finished promise should not resolve when playbackRate ' + + 'is zero'); + })); + + return retPromise; +}, 'Test finished promise changes when playbackRate == 0'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.playbackRate = -1; + return animation.finished; + }); +}, 'Test finished promise resolves when reaching to the natural boundary.'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.currentTime = 0; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change once a prior ' + + 'finished promise resolved and the animation ' + + 'falls out finished state'); + }); +}, 'Test finished promise changes when a prior finished promise resolved ' + + 'and the animation falls out finished state'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + assert_equals(animation.finished, previousFinishedPromise, + 'No new finished promise generated when finished state ' + + 'is checked asynchronously'); +}, 'Test no new finished promise generated when finished state ' + + 'is checked asynchronously'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + animation.currentTime = 100 * MS_PER_SEC / 2; + assert_not_equals(animation.finished, previousFinishedPromise, + 'New finished promise generated when finished state ' + + 'is checked synchronously'); +}, 'Test new finished promise generated when finished state ' + + 'is checked synchronously'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + return animation.ready.then(function() { + animation.finish(); + animation.currentTime = 100 * MS_PER_SEC / 2; + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved even if ' + + 'the finished state is changed soon'); + }); + +}, 'Test synchronous finished promise resolved even if finished state ' + + 'is changed soon'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after finish() is ' + + 'called even if there are other asynchronous promises just before it'); + }); +}, 'Test synchronous finished promise resolved even if asynchronous ' + + 'finished promise happens just before synchronous promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + }); +}, 'Test finished promise is not resolved when the animation ' + + 'falls out finished state immediately'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + animation.currentTime = 0; + }); + +}, 'Test finished promise is not resolved once the animation ' + + 'falls out finished state even though the current finished ' + + 'promise is generated soon after animation state became finished'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/id.html b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html new file mode 100644 index 000000000..2fadd5623 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.id</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-id"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + assert_equals(animation.id, '', 'id for Animation is initially empty'); +}, 'Animation.id initial value'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.id = 'anim'; + + assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); +}, 'Animation.id setter'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html b/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html new file mode 100644 index 000000000..b8d9ced61 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.oncancel</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-oncancel"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var finishedTimelineTime; + animation.finished.then().catch(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.oncancel = t.step_func_done(function(event) { + assert_equals(event.currentTime, null, + 'event.currentTime should be null'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is rejected'); + }); + + animation.cancel(); +}, 'oncancel event is fired when animation.cancel() is called.'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html b/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html new file mode 100644 index 000000000..50e5bed6b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.onfinish</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-onfinish"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 0, + 'event.currentTime should be zero'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.playbackRate = -1; +}, 'onfinish event is fired when the currentTime < 0 and ' + + 'the playbackRate < 0'); + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 100 * MS_PER_SEC, + 'event.currentTime should be the effect end'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.currentTime = 100 * MS_PER_SEC; +}, 'onfinish event is fired when the currentTime > 0 and ' + + 'the playbackRate > 0'); + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 100 * MS_PER_SEC, + 'event.currentTime should be the effect end'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.finish(); +}, 'onfinish event is fired when animation.finish() is called'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when paused'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + return animation.ready.then(function() { + animation.playbackRate = 0; + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when the playbackRate is zero'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when the animation falls out ' + + 'finished state immediately'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html b/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html new file mode 100644 index 000000000..bcfaf4a9b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html @@ -0,0 +1,98 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.pause()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-pause"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + var previousCurrentTime = animation.currentTime; + + return animation.ready.then(waitForAnimationFrames(1)).then(function() { + assert_true(animation.currentTime >= previousCurrentTime, + 'currentTime is initially increasing'); + animation.pause(); + return animation.ready; + }).then(function() { + previousCurrentTime = animation.currentTime; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(animation.currentTime, previousCurrentTime, + 'currentTime does not increase after calling pause()'); + }); +}, 'pause() a running animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + + // Go to idle state then pause + animation.cancel(); + animation.pause(); + + assert_equals(animation.currentTime, 0, 'currentTime is set to 0'); + assert_equals(animation.startTime, null, 'startTime is not set'); + assert_equals(animation.playState, 'pending', 'initially pause-pending'); + + // Check it still resolves as expected + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused', + 'resolves to paused state asynchronously'); + assert_equals(animation.currentTime, 0, + 'keeps the initially set currentTime'); + }); +}, 'pause() from idle'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + animation.cancel(); + animation.playbackRate = -1; + animation.pause(); + + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'currentTime is set to the effect end'); + + return animation.ready.then(function() { + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'keeps the initially set currentTime'); + }); +}, 'pause() from idle with a negative playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 1000 * MS_PER_SEC, + iterations: Infinity}); + animation.cancel(); + animation.playbackRate = -1; + + assert_throws('InvalidStateError', + function () { animation.pause(); }, + 'Expect InvalidStateError exception on calling pause() ' + + 'from idle with a negative playbackRate and ' + + 'infinite-duration animation'); +}, 'pause() from idle with a negative playbackRate and endless effect'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + return animation.ready + .then(function(animation) { + animation.finish(); + animation.pause(); + return animation.ready; + }).then(function(animation) { + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'currentTime after pausing finished animation'); + }); +}, 'pause() on a finished animation'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/play.html b/testing/web-platform/tests/web-animations/interfaces/Animation/play.html new file mode 100644 index 000000000..767d8df17 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/play.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.play()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-play"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({ transform: ['none', 'translate(10px)']}, + { duration : 100 * MS_PER_SEC, + iterations : Infinity}); + return animation.ready.then(function() { + // Seek to a time outside the active range so that play() will have to + // snap back to the start + animation.currentTime = -5 * MS_PER_SEC; + animation.playbackRate = -1; + + assert_throws('InvalidStateError', + function () { animation.play(); }, + 'Expected InvalidStateError exception on calling play() ' + + 'with a negative playbackRate and infinite-duration ' + + 'animation'); + }); +}, 'play() throws when seeking an infinite-duration animation played in ' + + 'reverse'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html b/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html new file mode 100644 index 000000000..15af526cd --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.playState</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playstate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + }); +}, 'Animation.playState reports \'pending\'->\'running\' when initially ' + + 'played'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.pause(); + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'Animation.playState reports \'pending\'->\'paused\' when pausing'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + assert_equals(animation.playState, 'idle'); +}, 'Animation.playState is \'idle\' when canceled.'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(animation.playState, 'paused', + 'After seeking an idle animation, it is effectively paused'); +}, 'Animation.playState is \'paused\' after cancelling an animation, ' + + 'seeking it makes it paused'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html b/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html new file mode 100644 index 000000000..c923df6b4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.playbackRate</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playbackrate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +function assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + description) { + var accuracy = 0.001; /* accuracy of DOMHighResTimeStamp */ + var animationCurrentTimeDifference = + animation.currentTime - previousAnimationCurrentTime; + var timelineCurrentTimeDifference = + animation.timeline.currentTime - previousTimelineCurrentTime; + + assert_approx_equals(animationCurrentTimeDifference, + timelineCurrentTimeDifference * animation.playbackRate, + accuracy, + description); +} + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.currentTime = 7 * MS_PER_SEC; // ms + animation.playbackRate = 0.5; + + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Reducing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + animation.playbackRate = 2; + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Increasing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + }); +}, 'Test the initial effect of setting playbackRate on currentTime'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + animation.playbackRate = 2; + var previousTimelineCurrentTime; + var previousAnimationCurrentTime; + return animation.ready.then(function() { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + return waitForAnimationFrames(1); + }).then(function() { + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be 2 times faster than timeline.'); + }); +}, 'Test the effect of setting playbackRate on currentTime'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + animation.playbackRate = 2; + var previousTimelineCurrentTime; + var previousAnimationCurrentTime; + return animation.ready.then(function() { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + animation.playbackRate = 1; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(animation.playbackRate, 1, + 'sanity check: animation.playbackRate is still 1.'); + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be the same speed as timeline now.'); + }); +}, 'Test the effect of setting playbackRate while playing animation'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html b/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html new file mode 100644 index 000000000..815fe3da7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.ready</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-ready"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var originalReadyPromise = animation.ready; + var pauseReadyPromise; + + return animation.ready.then(function() { + assert_equals(animation.ready, originalReadyPromise, + 'Ready promise is the same object when playing completes'); + animation.pause(); + assert_not_equals(animation.ready, originalReadyPromise, + 'A new ready promise is created when pausing'); + pauseReadyPromise = animation.ready; + // Wait for the promise to fulfill since if we abort the pause the ready + // promise object is reused. + return animation.ready; + }).then(function() { + animation.play(); + assert_not_equals(animation.ready, pauseReadyPromise, + 'A new ready promise is created when playing'); + }); +}, 'A new ready promise is created when play()/pause() is called'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + return animation.ready.then(function() { + var promiseBeforeCallingPlay = animation.ready; + animation.play(); + assert_equals(animation.ready, promiseBeforeCallingPlay, + 'Ready promise has same object identity after redundant call' + + ' to play()'); + }); +}, 'Redundant calls to play() do not generate new ready promise objects'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + return animation.ready.then(function(resolvedAnimation) { + assert_equals(resolvedAnimation, animation, + 'Object identity of Animation passed to Promise callback' + + ' matches the Animation object owning the Promise'); + }); +}, 'The ready promise is fulfilled with its Animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + + animation.cancel(); + + return retPromise; +}, 'ready promise is rejected when a pause-pending animation is cancelled by' + + ' calling cancel()'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.pause(); + // Set up listeners on pause-pending ready promise + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + animation.cancel(); + return retPromise; + }); +}, 'ready promise is rejected when a pause-pending animation is cancelled by' + + ' calling cancel()'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html b/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html new file mode 100644 index 000000000..555226111 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.reverse()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-reverse"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + + // Wait a frame because if currentTime is still 0 when we call + // reverse(), it will throw (per spec). + return animation.ready.then(waitForAnimationFrames(1)).then(function() { + assert_greater_than_equal(animation.currentTime, 0, + 'currentTime expected to be greater than 0, one frame after starting'); + animation.currentTime = 50 * MS_PER_SEC; + var previousPlaybackRate = animation.playbackRate; + animation.reverse(); + assert_equals(animation.playbackRate, -previousPlaybackRate, + 'playbackRate should be inverted'); + }); +}, 'reverse() inverts playbackRate'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.currentTime = 50 * MS_PER_SEC; + animation.pause(); + + return animation.ready.then(function() { + animation.reverse(); + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation.playState should be "running" after reverse()'); + }); +}, 'reverse() starts to play when pausing animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.currentTime = 50 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 50 * MS_PER_SEC, + 'reverse() should not change the currentTime ' + + 'if the currentTime is in the middle of animation duration'); +}, 'reverse() maintains the same currentTime'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.currentTime = 200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'reverse() should start playing from the animation effect end ' + + 'if the playbackRate > 0 and the currentTime > effect end'); +}, 'reverse() when playbackRate > 0 and currentTime > effect end'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'reverse() should start playing from the animation effect end ' + + 'if the playbackRate > 0 and the currentTime < 0'); +}, 'reverse() when playbackRate > 0 and currentTime < 0'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = -1; + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime < 0'); +}, 'reverse() when playbackRate < 0 and currentTime < 0'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = -1; + animation.currentTime = 200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime > effect end'); +}, 'reverse() when playbackRate < 0 and currentTime > effect end'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.currentTime = -200 * MS_PER_SEC; + + assert_throws('InvalidStateError', + function () { animation.reverse(); }, + 'reverse() should throw InvalidStateError ' + + 'if the playbackRate > 0 and the currentTime < 0 ' + + 'and the target effect is positive infinity'); +}, 'reverse() when playbackRate > 0 and currentTime < 0 ' + + 'and the target effect end is positive infinity'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.playbackRate = -1; + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime < 0 ' + + 'and the target effect is positive infinity'); +}, 'reverse() when playbackRate < 0 and currentTime < 0 ' + + 'and the target effect end is positive infinity'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = 0; + animation.currentTime = 50 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.playbackRate, 0, + 'reverse() should preserve playbackRate if the playbackRate == 0'); + assert_equals(animation.currentTime, 50 * MS_PER_SEC, + 'reverse() should not affect the currentTime if the playbackRate == 0'); + t.done(); +}, 'reverse() when playbackRate == 0'); + +test(function(t) { + var div = createDiv(t); + var animation = + new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC), null); + + assert_throws('InvalidStateError', function() { animation.reverse(); }); +}, 'Reversing an animation without an active timeline throws an ' + + 'InvalidStateError'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html b/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html new file mode 100644 index 000000000..6dfd7cf29 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.startTime tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-animation-starttime"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a newly created (idle) animation is unresolved'); + +test(function(t) { + var animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + animation.play(); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a play-pending animation is unresolved'); + +test(function(t) { + var animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + animation.pause(); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a pause-pending animation is unresolved'); + +test(function(t) { + var animation = createDiv(t).animate(null); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a play-pending animation created using Element.animate' + + ' shortcut is unresolved'); + +promise_test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + return animation.ready.then(function() { + assert_greater_than(animation.startTime, 0, 'startTime when running'); + }); +}, 'startTime is resolved when running'); + +test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + animation.cancel(); + assert_equals(animation.startTime, null); + assert_equals(animation.currentTime, null); +}, 'startTime and currentTime are unresolved when animation is cancelled'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html new file mode 100644 index 000000000..d6e1cd904 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>delay tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-delay"> +<link rel="author" title="Daisuke Akatsuka" href="mailto:daisuke@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = 100; + assert_equals(anim.effect.timing.delay, 100, 'set delay 100'); + assert_equals(anim.effect.getComputedTiming().delay, 100, + 'getComputedTiming() after set delay 100'); +}, 'set delay 100'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = -100; + assert_equals(anim.effect.timing.delay, -100, 'set delay -100'); + assert_equals(anim.effect.getComputedTiming().delay, -100, + 'getComputedTiming() after set delay -100'); +}, 'set delay -100'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = 100; + assert_equals(anim.effect.getComputedTiming().progress, null); + assert_equals(anim.effect.getComputedTiming().currentIteration, null); +}, 'Test adding a positive delay to an animation without a backwards fill ' + + 'makes it no longer active'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'both', + duration: 100 }); + anim.effect.timing.delay = -50; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); +}, 'Test seeking an animation by setting a negative delay'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'both', + duration: 100 }); + anim.effect.timing.delay = -100; + assert_equals(anim.effect.getComputedTiming().progress, 1); + assert_equals(anim.effect.getComputedTiming().currentIteration, 0); +}, 'Test finishing an animation using a large negative delay'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html new file mode 100644 index 000000000..c3657f3a0 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>direction tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-direction"> +<link rel="author" title="Ryo Kato" href="mailto:foobar094@gmail.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + + var directions = ['normal', 'reverse', 'alternate', 'alternate-reverse']; + directions.forEach(function(direction) { + anim.effect.timing.direction = direction; + assert_equals(anim.effect.timing.direction, direction, + 'set direction to ' + direction); + }); +}, 'set direction to a valid keyword'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html new file mode 100644 index 000000000..8e2a1a1b9 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>duration tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animationeffecttiming-duration"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = 123.45; + assert_times_equal(anim.effect.timing.duration, 123.45, + 'set duration 123.45'); + assert_times_equal(anim.effect.getComputedTiming().duration, 123.45, + 'getComputedTiming() after set duration 123.45'); +}, 'set duration 123.45'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = 'auto'; + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); + assert_equals(anim.effect.getComputedTiming().duration, 0, + 'getComputedTiming() after set duration \'auto\''); +}, 'set duration auto'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 'auto' }); + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); + assert_equals(anim.effect.getComputedTiming().duration, 0, + 'getComputedTiming() after set duration \'auto\''); +}, 'set auto duration in animate as object'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = Infinity; + assert_equals(anim.effect.timing.duration, Infinity, 'set duration Infinity'); + assert_equals(anim.effect.getComputedTiming().duration, Infinity, + 'getComputedTiming() after set duration Infinity'); +}, 'set duration Infinity'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -1); + }); +}, 'set negative duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -Infinity); + }); +}, 'set negative Infinity duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, NaN); + }); +}, 'set NaN duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -1 }); + }); +}, 'set negative duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -Infinity }); + }); +}, 'set negative Infinity duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: NaN }); + }); +}, 'set NaN duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: 'abc' }); + }); +}, 'set abc string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: '100' }); + }); +}, 'set 100 string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -1; + }); +}, 'set negative duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -Infinity; + }); +}, 'set negative Infinity duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = NaN; + }); +}, 'set NaN duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = 'abc'; + }); +}, 'set duration abc'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = '100'; + }); +}, 'set duration string 100'); + + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html new file mode 100644 index 000000000..cedd7e68b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>easing tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-easing"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/effect-easing-tests.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +function assert_progress(animation, currentTime, easingFunction) { + animation.currentTime = currentTime; + var portion = currentTime / animation.effect.timing.duration; + assert_approx_equals(animation.effect.getComputedTiming().progress, + easingFunction(portion), + 0.01, + 'The progress of the animation should be approximately ' + + easingFunction(portion) + ' at ' + currentTime + 'ms'); +} + +gEffectEasingTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ], + { duration: 1000 * MS_PER_SEC, + fill: 'forwards' }); + anim.effect.timing.easing = options.easing; + assert_equals(anim.effect.timing.easing, + options.serialization || options.easing); + + var easing = options.easingFunction; + assert_progress(anim, 0, easing); + assert_progress(anim, 250 * MS_PER_SEC, easing); + assert_progress(anim, 500 * MS_PER_SEC, easing); + assert_progress(anim, 750 * MS_PER_SEC, easing); + assert_progress(anim, 1000 * MS_PER_SEC, easing); + }, options.desc); +}); + +gInvalidEasingTests.forEach(function(options) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); + assert_throws({ name: 'TypeError' }, + function() { + anim.effect.timing.easing = options.easing; + }); + }, 'Invalid effect easing value test: \'' + options.easing + '\''); +}); + +test(function(t) { + var delay = 1000 * MS_PER_SEC; + + var target = createDiv(t); + var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ], + { duration: 1000 * MS_PER_SEC, + fill: 'both', + delay: delay, + easing: 'steps(2, start)' }); + + anim.effect.timing.easing = 'steps(2, end)'; + assert_equals(anim.effect.getComputedTiming().progress, 0, + 'easing replace to steps(2, end) at before phase'); + + anim.currentTime = delay + 750 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.5, + 'change currentTime to active phase'); + + anim.effect.timing.easing = 'steps(2, start)'; + assert_equals(anim.effect.getComputedTiming().progress, 1, + 'easing replace to steps(2, start) at active phase'); + + anim.currentTime = delay + 1500 * MS_PER_SEC; + anim.effect.timing.easing = 'steps(2, end)'; + assert_equals(anim.effect.getComputedTiming().progress, 1, + 'easing replace to steps(2, end) again at after phase'); +}, 'Change the easing while the animation is running'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html new file mode 100644 index 000000000..40136b45c --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>endDelay tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animationeffecttiming-enddelay"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.endDelay = 123.45; + assert_times_equal(anim.effect.timing.endDelay, 123.45, + 'set endDelay 123.45'); + assert_times_equal(anim.effect.getComputedTiming().endDelay, 123.45, + 'getComputedTiming() after set endDelay 123.45'); +}, 'set endDelay 123.45'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.endDelay = -1000; + assert_equals(anim.effect.timing.endDelay, -1000, 'set endDelay -1000'); + assert_equals(anim.effect.getComputedTiming().endDelay, -1000, + 'getComputedTiming() after set endDelay -1000'); +}, 'set endDelay -1000'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({name: "TypeError"}, function() { + anim.effect.timing.endDelay = Infinity; + }, 'we can not assign Infinity to timing.endDelay'); +}, 'set endDelay Infinity'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({name: "TypeError"}, function() { + anim.effect.timing.endDelay = -Infinity; + }, 'we can not assign negative Infinity to timing.endDelay'); +}, 'set endDelay negative Infinity'); + +async_test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 100000, endDelay: 50000 }); + anim.onfinish = t.step_func(function(event) { + assert_unreached('onfinish event should not be fired'); + }); + + anim.ready.then(function() { + anim.currentTime = 100000; + return waitForAnimationFrames(2); + }).then(t.step_func(function() { + t.done(); + })); +}, 'onfinish event is not fired duration endDelay'); + +async_test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 100000, endDelay: 30000 }); + anim.ready.then(function() { + anim.currentTime = 110000; // during endDelay + anim.onfinish = t.step_func(function(event) { + assert_unreached('onfinish event should not be fired during endDelay'); + }); + return waitForAnimationFrames(2); + }).then(t.step_func(function() { + anim.onfinish = t.step_func(function(event) { + t.done(); + }); + anim.currentTime = 130000; // after endTime + })); +}, 'onfinish event is fired currentTime is after endTime'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html new file mode 100644 index 000000000..21e523b73 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>fill tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-fill"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +["none", "forwards", "backwards", "both", ].forEach(function(fill){ + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.fill = fill; + assert_equals(anim.effect.timing.fill, fill, 'set fill ' + fill); + assert_equals(anim.effect.getComputedTiming().fill, fill, 'getComputedTiming() after set fill ' + fill); + }, 'set fill ' + fill); +}); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html new file mode 100644 index 000000000..d96192c9d --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Element.getAnimations tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#animationeffecttiming"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.finish(); + assert_equals(div.getAnimations().length, 0, 'animation finished'); + anim.effect.timing.duration += 100000; + assert_equals(div.getAnimations()[0], anim, 'set duration 102000'); + anim.effect.timing.duration = 0; + assert_equals(div.getAnimations().length, 0, 'set duration 0'); + anim.effect.timing.duration = 'auto'; + assert_equals(div.getAnimations().length, 0, 'set duration \'auto\''); +}, 'when duration is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + + anim.effect.timing.endDelay = -3000; + assert_equals(div.getAnimations().length, 0, + 'set negative endDelay so as endTime is less than currentTime'); + anim.effect.timing.endDelay = 1000; + assert_equals(div.getAnimations()[0], anim, + 'set positive endDelay so as endTime is more than currentTime'); + + anim.effect.timing.duration = 1000; + anim.currentTime = 1500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime less than endTime'); + anim.effect.timing.endDelay = -500; + anim.currentTime = 400; + assert_equals(div.getAnimations()[0], anim, + 'set currentTime less than endTime when endDelay is negative value'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as endTime when endDelay is negative value'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as duration when endDelay is negative value'); +}, 'when endDelay is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.finish(); + assert_equals(div.getAnimations().length, 0, 'animation finished'); + anim.effect.timing.iterations = 10; + assert_equals(div.getAnimations()[0], anim, 'set iterations 10'); + anim.effect.timing.iterations = 0; + assert_equals(div.getAnimations().length, 0, 'set iterations 0'); + anim.effect.timing.iterations = Infinity; + assert_equals(div.getAnimations().length, 1, 'set iterations Infinity'); +}, 'when iterations is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: 500, endDelay: -500 }); + assert_equals(div.getAnimations()[0], anim, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations()[0], anim, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: 500, endDelay: -500'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: -500, endDelay: -500 }); + assert_equals(div.getAnimations().length, 0, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: -500, endDelay: -500'); + + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html new file mode 100644 index 000000000..d6503a431 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html @@ -0,0 +1,172 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>getComputedStyle tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#animationeffecttiming"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100000); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', 'animation finished'); + anim.effect.timing.duration *= 2; + assert_equals(getComputedStyle(div).opacity, '0.5', 'set double duration'); + anim.effect.timing.duration = 0; + assert_equals(getComputedStyle(div).opacity, '1', 'set duration 0'); + anim.effect.timing.duration = 'auto'; + assert_equals(getComputedStyle(div).opacity, '1', 'set duration \'auto\''); +}, 'changed duration immediately updates its computed styles'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100000); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', 'animation finished'); + anim.effect.timing.iterations = 2; + assert_equals(getComputedStyle(div).opacity, '0', 'set 2 iterations'); + anim.effect.timing.iterations = 0; + assert_equals(getComputedStyle(div).opacity, '1', 'set iterations 0'); + anim.effect.timing.iterations = Infinity; + assert_equals(getComputedStyle(div).opacity, '0', 'set iterations Infinity'); +}, 'changed iterations immediately updates its computed styles'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: 1000, fill: 'none' }); + + anim.currentTime = 9000; + assert_equals(getComputedStyle(div).opacity, '0.1', + 'set currentTime during duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endDelay'); +}, 'change currentTime when fill is none and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: 1000, + fill: 'forwards' }); + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0.5', + 'set currentTime during duration'); + + anim.currentTime = 9999; + assert_equals(getComputedStyle(div).opacity, '0.0001', + 'set currentTime just a little before duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endDelay'); +}, 'change currentTime when fill forwards and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: -5000, fill: 'none' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endTime'); +}, 'change currentTime when fill none and endDelay is negative'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: -5000, + fill: 'forwards' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0.5', + 'set currentTime same as endTime'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endTime'); +}, 'change currentTime when fill forwards and endDelay is negative'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 10000, + direction: 'normal' }); + + anim.currentTime = 7000; + anim.effect.timing.direction = 'reverse'; + + assert_equals(getComputedStyle(div).opacity, '0.3', + 'change direction from "normal" to "reverse"'); +}, 'change direction from "normal" to "reverse"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterations: 2, + duration: 10000, + direction: 'normal' }); + + anim.currentTime = 17000; + anim.effect.timing.direction = 'alternate'; + + assert_equals(getComputedStyle(div).opacity, '0.3', + 'change direction from "normal" to "alternate"'); + }, 'change direction from "normal" to "alternate"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterations: 2, + duration: 10000, + direction: 'normal' }); + + anim.currentTime = 17000; + anim.effect.timing.direction = 'alternate-reverse'; + + assert_equals(getComputedStyle(div).opacity, '0.7', + 'change direction from "normal" to "alternate-reverse"'); +}, 'change direction from "normal" to "alternate-reverse"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'backwards', + duration: 10000, + direction: 'normal' }); + + // test for a flip of value at the currentTime = 0 + anim.effect.timing.direction = 'reverse'; + + assert_equals(getComputedStyle(div).opacity, '1', + 'change direction from "normal" to "reverse" ' + + 'at the starting point'); +}, 'change direction from "normal" to "reverse"'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html new file mode 100644 index 000000000..0273fd12b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>iterationStart tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterationstart"> +<link rel="author" title="Daisuke Akatsuka" href="mailto:daisuke@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 1 }); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); +}, 'Test that changing the iterationStart affects computed timing ' + + 'when backwards-filling'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 0 }); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); +}, 'Test that changing the iterationStart affects computed timing ' + + 'during the active phase'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 0 }); + anim.finish(); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 3); +}, 'Test that changing the iterationStart affects computed timing ' + + 'when forwards-filling'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + assert_throws({ name: 'TypeError' }, + function() { + anim.effect.timing.iterationStart = -1; + }); + assert_throws({ name: 'TypeError' }, + function() { + div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: -1 }); + }); +}, 'Test invalid iterationStart value'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html new file mode 100644 index 000000000..1ba41028b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>iterations tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterations"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.iterations = 2; + assert_equals(anim.effect.timing.iterations, 2, 'set duration 2'); + assert_equals(anim.effect.getComputedTiming().iterations, 2, + 'getComputedTiming() after set iterations 2'); +}, 'set iterations 2'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.iterations = Infinity; + assert_equals(anim.effect.timing.iterations, Infinity, 'set duration Infinity'); + assert_equals(anim.effect.getComputedTiming().iterations, Infinity, + 'getComputedTiming() after set iterations Infinity'); +}, 'set iterations Infinity'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = -1; + }); +}, 'set negative iterations'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = -Infinity; + }); +}, 'set negative infinity iterations '); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = NaN; + }); +}, 'set NaN iterations'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html new file mode 100644 index 000000000..0f782f50a --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html @@ -0,0 +1,59 @@ +<!doctype html> +<meta charset=utf-8> +<title>Default document timeline tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-documents-default-timeline"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe width="10" height="10" id="iframe"></iframe> +<script> +'use strict'; + +test(function() { + assert_equals(document.timeline, document.timeline, + 'document.timeline returns the same object every time'); + var iframe = document.getElementById('iframe'); + assert_not_equals(document.timeline, iframe.contentDocument.timeline, + 'document.timeline returns a different object for each document'); + assert_not_equals(iframe.contentDocument.timeline, null, + 'document.timeline on an iframe is not null'); +}, 'document.timeline identity tests'); + +promise_test(function(t) { + assert_true(document.timeline.currentTime > 0, + 'document.timeline.currentTime is positive'); + // document.timeline.currentTime should be set even before document + // load fires. We expect this code to be run before document load and hence + // the above assertion is sufficient. + // If the following assertion fails, this test needs to be redesigned. + assert_true(document.readyState !== 'complete', + 'Test is running prior to document load'); + + // Test that the document timeline's current time is measured from + // navigationStart. + // + // We can't just compare document.timeline.currentTime to + // window.performance.now() because currentTime is only updated on a sample + // so we use requestAnimationFrame instead. + return window.requestAnimationFrame(t.step_func(function(rafTime) { + assert_equals(document.timeline.currentTime, rafTime, + 'document.timeline.currentTime matches' + + ' requestAnimationFrame time'); + })); +}, 'document.timeline.currentTime value tests'); + +promise_test(function(t) { + var valueAtStart = document.timeline.currentTime; + var timeAtStart = window.performance.now(); + while (window.performance.now() - timeAtStart < 100) { + // Wait 100ms + } + assert_equals(document.timeline.currentTime, valueAtStart, + 'document.timeline.currentTime does not change within a script block'); + return window.requestAnimationFrame(t.step_func(function() { + assert_true(document.timeline.currentTime > valueAtStart, + 'document.timeline.currentTime increases between script blocks'); + })); +}, 'document.timeline.currentTime liveness tests'); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html new file mode 100644 index 000000000..29c891407 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title>Web Animations API: DocumentTimeline tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="/resources/idlharness.js"></script> +<div id="log"></div> +<script type="text/plain" id="AnimationTimeline-IDL"> +interface AnimationTimeline { + readonly attribute double? currentTime; +}; +</script> +<script type="text/plain" id="DocumentTimeline-IDL"> +dictionary DocumentTimelineOptions { + DOMHighResTimeStamp originTime = 0; +}; +[Constructor (optional DocumentTimelineOptions options)] +interface DocumentTimeline : AnimationTimeline { +}; +</script> +<script> +'use strict'; + +var idlArray; +test(function() { + idlArray = new IdlArray(); + idlArray.add_untested_idls( + document.getElementById('AnimationTimeline-IDL').textContent); + idlArray.add_idls( + document.getElementById('DocumentTimeline-IDL').textContent); + idlArray.add_objects( { DocumentTimeline: ['document.timeline'] } ); +}); +idlArray.test(); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html new file mode 100644 index 000000000..e87751c04 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>document.getAnimations tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-document-getanimations"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +var gKeyFrames = { 'marginLeft': ['100px', '200px'] }; + +test(function(t) { + assert_equals(document.getAnimations().length, 0, + 'getAnimations returns an empty sequence for a document ' + + 'with no animations'); +}, 'Test document.getAnimations for non-animated content'); + +test(function(t) { + var div = createDiv(t); + var anim1 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var anim2 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + assert_equals(document.getAnimations().length, 2, + 'getAnimation returns running animations'); + + anim1.finish(); + anim2.finish(); + assert_equals(document.getAnimations().length, 0, + 'getAnimation only returns running animations'); +}, 'Test document.getAnimations for script-generated animations') + +test(function(t) { + var div = createDiv(t); + var anim1 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var anim2 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + assert_array_equals(document.getAnimations(), + [ anim1, anim2 ], + 'getAnimations() returns running animations'); +}, 'Test the order of document.getAnimations with script generated animations') + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var anim = new Animation(effect, document.timeline); + anim.play(); + + assert_equals(document.getAnimations().length, 0, + 'document.getAnimations() only returns animations targeting ' + + 'elements in this document'); +}, 'Test document.getAnimations with null target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html b/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html new file mode 100644 index 000000000..8968ff4ce --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>DocumentTimeline constructor tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-documenttimeline-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var timeline = new DocumentTimeline(); + + assert_times_equal(timeline.currentTime, document.timeline.currentTime); +}, 'An origin time of zero is used when none is supplied'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: 0 }); + assert_times_equal(timeline.currentTime, document.timeline.currentTime); +}, 'A zero origin time produces a document timeline with a current time ' + + 'identical to the default document timeline'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: 10 * MS_PER_SEC }); + + assert_times_equal(timeline.currentTime, + (document.timeline.currentTime - 10 * MS_PER_SEC)); +}, 'A positive origin time makes the document timeline\'s current time lag ' + + 'behind the default document timeline'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: -10 * MS_PER_SEC }); + + assert_times_equal(timeline.currentTime, + (document.timeline.currentTime + 10 * MS_PER_SEC)); +}, 'A negative origin time makes the document timeline\'s current time run ' + + 'ahead of the default document timeline'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html new file mode 100644 index 000000000..97c7613f8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html @@ -0,0 +1,278 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly constructor tests</title> +<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"); + +test(function(t) { + gEmptyKeyframeListTests.forEach(function(frames) { + assert_equals(new KeyframeEffectReadOnly(target, frames) + .getKeyframes().length, + 0, "number of frames for " + JSON.stringify(frames)); + }); +}, "a KeyframeEffectReadOnly can be constructed with no frames"); + +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 + }); + assert_equals(effect.getKeyframes()[0].easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in a property-indexed keyframe"); + +test(function(t) { + gEasingValueTests.forEach(function(subtest) { + var easing = subtest[0]; + var expected = subtest[1]; + var effect = new KeyframeEffectReadOnly(target, [ + { offset: 0, left: "10px", easing: easing }, + { offset: 1, left: "20px" } + ]); + assert_equals(effect.getKeyframes()[0].easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in regular keyframes"); + +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 }); + assert_equals(effect.timing.easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in KeyframeTimingOptions"); + +test(function(t) { + var getKeyframe = function(composite) { + return { left: [ "10px", "20px" ], composite: composite }; + }; + gGoodKeyframeCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, getKeyframe(composite)); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, getKeyframe(composite)); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in property-indexed keyframes"); + +test(function(t) { + var getKeyframes = function(composite) { + return [ + { offset: 0, left: "10px", composite: composite }, + { offset: 1, left: "20px" } + ]; + }; + gGoodKeyframeCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, getKeyframes(composite)); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, getKeyframes(composite)); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in regular keyframes"); + +test(function(t) { + gGoodOptionsCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"] + }, { composite: composite }); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"] + }, { composite: composite }); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in KeyframeTimingOptions"); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), 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.getKeyframes()); + assert_frame_lists_equal(secondEffect.getKeyframes(), + effect.getKeyframes()); + }, "a KeyframeEffectReadOnly constructed with " + subtest.desc + + " roundtrips"); +}); + +test(function(t) { + var expectedOrder = ["composite", "easing", "offset", "left", "marginLeft"]; + var actualOrder = []; + var kf1 = {}; + var kf2 = { marginLeft: "10px", left: "20px", offset: 1 }; + [{ p: "marginLeft", v: "10px" }, + { p: "left", v: "20px" }, + { p: "offset", v: "0" }, + { p: "easing", v: "linear" }, + { p: "composite", v: "replace" }].forEach(function(e) { + Object.defineProperty(kf1, e.p, { + enumerable: true, + 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"); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), 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.getKeyframes()); + assert_frame_lists_equal(secondEffect.getKeyframes(), + effect.getKeyframes()); + }, "a KeyframeEffectReadOnly constructed with " + subtest.desc + + " roundtrips"); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + assert_throws(subtest.expected, function() { + new KeyframeEffectReadOnly(target, subtest.input); + }); + }, "KeyframeEffectReadOnly constructor throws with " + subtest.desc); +}); + +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"); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }); + + var timing = effect.timing; + assert_equals(timing.delay, 0, "default delay"); + assert_equals(timing.endDelay, 0, "default endDelay"); + assert_equals(timing.fill, "auto", "default fill"); + assert_equals(timing.iterations, 1.0, "default iterations"); + assert_equals(timing.iterationStart, 0.0, "default iterationStart"); + assert_equals(timing.duration, "auto", "default duration"); + assert_equals(timing.direction, "normal", "default direction"); + assert_equals(timing.easing, "linear", "default easing"); + + 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"); + +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. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + + var timing = effect.timing; + assert_equals(timing.delay, expected("delay", 0), + "timing delay"); + assert_equals(timing.fill, expected("fill", "auto"), + "timing fill"); + assert_equals(timing.iterations, expected("iterations", 1), + "timing iterations"); + assert_equals(timing.duration, expected("duration", "auto"), + "timing duration"); + assert_equals(timing.direction, expected("direction", "normal"), + "timing direction"); + + }, "a KeyframeEffectReadOnly constructed by " + stest.desc); +}); + +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); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, + { left: ["10px", "20px"] }, + { duration: 100 * MS_PER_SEC, + fill: "forwards" }); + assert_equals(effect.target, null, + "Effect created with null target has correct target"); +}, "a KeyframeEffectReadOnly constructed with null target"); + +test(function(t) { + var effect = new KeyframeEffect(target, null); + assert_class_string(effect, "KeyframeEffect"); + assert_class_string(effect.timing, "AnimationEffectTiming"); +}, "KeyframeEffect constructor creates an AnimationEffectTiming timing object"); + +test(function(t) { + var test_error = { name: "test" }; + + assert_throws(test_error, function() { + new KeyframeEffect(target, { get left() { throw test_error }}) + }); +}, "KeyframeEffect constructor propagates exceptions generated by accessing" + + " the options object"); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html new file mode 100644 index 000000000..bc278389c --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect copy constructor tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffect-keyframeeffect-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var effect = new KeyframeEffectReadOnly(createDiv(t), null); + assert_equals(effect.constructor.name, 'KeyframeEffectReadOnly'); + assert_equals(effect.timing.constructor.name, + 'AnimationEffectTimingReadOnly'); + + // Make a mutable copy + var copiedEffect = new KeyframeEffect(effect); + assert_equals(copiedEffect.constructor.name, 'KeyframeEffect'); + assert_equals(copiedEffect.timing.constructor.name, 'AnimationEffectTiming'); +}, 'Test mutable copy from a KeyframeEffectReadOnly source'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html new file mode 100644 index 000000000..05019cdf3 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html @@ -0,0 +1,683 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Effect-level easing tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#calculating-the-transformed-time"> +<link rel="author" title="Hiroyuki Ikezoe" href="mailto:hiikezoe@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/effect-easing-tests.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +function assert_style_left_at(animation, time, easingFunction) { + animation.currentTime = time; + var portion = time / animation.effect.timing.duration; + assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left), + easingFunction(portion) * 100, + 0.01, + 'The left of the animation should be approximately ' + + easingFunction(portion) * 100 + ' at ' + time + 'ms'); +} + +gEffectEasingTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px' }, { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: options.easing }); + var easing = options.easingFunction; + + anim.pause(); + + assert_style_left_at(anim, 0, easing); + assert_style_left_at(anim, 250, easing); + assert_style_left_at(anim, 500, easing); + assert_style_left_at(anim, 750, easing); + assert_style_left_at(anim, 1000, easing); + }, options.desc); +}); + +var gEffectEasingTestsWithKeyframeEasing = [ + { + desc: 'effect easing produces values greater than 1 with keyframe ' + + 'easing cubic-bezier(0, 0, 0, 0)', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)', + keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear + easingFunction: cubicBezier(0, 1.5, 1, 1.5) + }, + { + desc: 'effect easing produces values greater than 1 with keyframe ' + + 'easing cubic-bezier(1, 1, 1, 1)', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)', + keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear + easingFunction: cubicBezier(0, 1.5, 1, 1.5) + }, + { + desc: 'effect easing produces negative values 1 with keyframe ' + + 'easing cubic-bezier(0, 0, 0, 0)', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)', + keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear + easingFunction: cubicBezier(0, -0.5, 1, -0.5) + }, + { + desc: 'effect easing produces negative values 1 with keyframe ' + + 'easing cubic-bezier(1, 1, 1, 1)', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)', + keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear + easingFunction: cubicBezier(0, -0.5, 1, -0.5) + }, +]; + +gEffectEasingTestsWithKeyframeEasing.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + [ { left: '0px', easing: options.keyframeEasing }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: options.easing }); + var easing = options.easingFunction; + + anim.pause(); + + assert_style_left_at(anim, 0, easing); + assert_style_left_at(anim, 250, easing); + assert_style_left_at(anim, 500, easing); + assert_style_left_at(anim, 750, easing); + assert_style_left_at(anim, 1000, easing); + }, options.desc); +}); + +// Other test cases that effect easing produces values outside of [0,1]. +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-start' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + anim.pause(); + + // The bezier function produces values greater than 1 in (0.23368794, 1) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 230; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 250; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces values greater than 1 with step-start keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-end' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + anim.pause(); + + // The bezier function produces values greater than 1 in (0.23368794, 1) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 230; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 250; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces values greater than 1 with step-end keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-start' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + anim.pause(); + + // The bezier function produces negative values in (0, 0.766312060) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 750; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 800; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces negative values with step-start keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-end' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + anim.pause(); + + // The bezier function produces negative values in (0, 0.766312060) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 750; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 800; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces negative values with step-end keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#.5,1,.5,0 + [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + var keyframeEasing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0.5, 1, 0.5, 0)(x); + } + var keyframeEasingExtrapolated = function(x) { + assert_greater_than(x, 1.0, + 'This function should be called in (1.0, infinity) range'); + // p3x + (p2y - p3y) / (p2x - p3x) * (x - p3x) + return 1.0 + (0 - 1) / (0.5 - 1) * (x - 1.0); + } + var effectEasing = function(x) { + return cubicBezier(0, 1.5, 1, 1.5)(x); + } + + anim.pause(); + + // The effect-easing produces values greater than 1 in (0.23368794, 1) + assert_style_left_at(anim, 0, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 230, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 240, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 700, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 990, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return keyframeEasing(effectEasing(x)); + }); +}, 'effect easing produces values greater than 1 with keyframe easing ' + + 'producing values greater than 1'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#0,1.5,1,1.5 + [ { left: '0px', easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + var easing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0, 1.5, 1, 1.5)(x); + } + var easingExtrapolated = function(x) { + assert_greater_than(x, 1.0, + 'This function should be called in negative range'); + // For cubic-bezier(0, 1.5, 1, 1.5), the tangent at the + // endpoint (x = 1.0) is infinity so we should just return 1.0. + return 1.0; + } + + anim.pause(); + + // The effect-easing produces values greater than 1 in (0.23368794, 1) + assert_style_left_at(anim, 0, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 230, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 240, function(x) { + return easingExtrapolated(easing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 700, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 990, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return easing(easing(x)) + }); +}, 'effect easing which produces values greater than 1 and the tangent on ' + + 'the upper boundary is infinity with keyframe easing producing values ' + + 'greater than 1'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#.5,1,.5,0 + [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + var keyframeEasing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0.5, 1, 0.5, 0)(x); + } + var keyframeEasingExtrapolated = function(x) { + assert_less_than(x, 0.0, + 'This function should be called in negative range'); + // p0x + (p1y - p0y) / (p1x - p0x) * (x - p0x) + return (1 / 0.5) * x; + } + var effectEasing = function(x) { + return cubicBezier(0, -0.5, 1, -0.5)(x); + } + + anim.pause(); + + // The effect-easing produces negative values in (0, 0.766312060) + assert_style_left_at(anim, 0, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 10, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 300, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 750, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 770, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return keyframeEasing(effectEasing(x)); + }); +}, 'effect easing produces negative values with keyframe easing ' + + 'producing negative values'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#0,-0.5,1,-0.5 + [ { left: '0px', easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + var easing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0, -0.5, 1, -0.5)(x); + } + var easingExtrapolated = function(x) { + assert_less_than(x, 0.0, + 'This function should be called in negative range'); + // For cubic-bezier(0, -0.5, 1, -0.5), the tangent at the + // endpoint (x = 0.0) is infinity so we should just return 0.0. + return 0.0; + } + + anim.pause(); + + // The effect-easing produces negative values in (0, 0.766312060) + assert_style_left_at(anim, 0, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 10, function(x) { + return easingExtrapolated(easing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 300, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 750, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 770, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 1000, function(x) { + return easing(easing(x)) + }); +}, 'effect easing which produces negative values and the tangent on ' + + 'the lower boundary is infinity with keyframe easing producing ' + + 'negative values'); + +var gStepTimingFunctionTests = [ + { + description: 'Test bounds point of step-start easing', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing with compositor', + keyframe: [ { opacity: 0 }, + { opacity: 1 } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing with reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + direction: 'reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1001, progress: 1 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 1501, progress: 0.5 }, + { currentTime: 2000, progress: 0 }, + { currentTime: 2500, progress: 0 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart not at a transition point', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.25, + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1249, progress: 0.5 }, + { currentTime: 1250, progress: 1 }, + { currentTime: 1749, progress: 1 }, + { currentTime: 1750, progress: 0.5 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 }, + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.5, + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1499, progress: 1 }, + { currentTime: 1500, progress: 0.5 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart and reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.5, + direction: 'reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 1999, progress: 1 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step(4, start) easing ' + + 'with iterationStart 0.75 and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterationStart: 0.75, + easing: 'steps(4, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.75 }, + { currentTime: 999, progress: 0.75 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 2000, progress: 1 }, + { currentTime: 2500, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with alternate direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterations: 2, + iterationStart: 1.5, + direction: 'alternate', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 2999, progress: 1 }, + { currentTime: 3000, progress: 0.5 }, + { currentTime: 3500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with alternate-reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterations: 2, + iterationStart: 0.5, + direction: 'alternate-reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 2999, progress: 1 }, + { currentTime: 3000, progress: 0.5 }, + { currentTime: 3500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-start easing in keyframe', + keyframe: [ { width: '0px', easing: 'steps(2, start)' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + }, + conditions: [ + { currentTime: 0, progress: 0, width: '0px' }, + { currentTime: 999, progress: 0, width: '0px' }, + { currentTime: 1000, progress: 0, width: '50px' }, + { currentTime: 1499, progress: 0.499, width: '50px' }, + { currentTime: 1500, progress: 0.5, width: '100px' }, + { currentTime: 2000, progress: 1, width: '100px' }, + { currentTime: 2500, progress: 1, width: '100px' } + ] + }, + { + description: 'Test bounds point of step-end easing ' + + 'with iterationStart and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterationStart: 0.5, + easing: 'steps(2, end)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 0 }, + { currentTime: 1999, progress: 0 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-end easing ' + + 'with iterationStart not at a transition point', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.75, + easing: 'steps(2, end)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1249, progress: 0.5 }, + { currentTime: 1250, progress: 0 }, + { currentTime: 1749, progress: 0 }, + { currentTime: 1750, progress: 0.5 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 }, + ] + } +]; + +gStepTimingFunctionTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + var animation = target.animate(options.keyframe, options.effect); + options.conditions.forEach(function(condition) { + animation.currentTime = condition.currentTime; + if (typeof condition.progress !== 'undefined') { + assert_equals(animation.effect.getComputedTiming().progress, + condition.progress, + 'Progress at ' + animation.currentTime + 'ms'); + } + if (typeof condition.width !== 'undefined') { + assert_equals(getComputedStyle(target).width, + condition.width, + 'Progress at ' + animation.currentTime + 'ms'); + } + }); + }, options.description); +}); + +gInvalidEasingTests.forEach(function(options) { + test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, + function() { + div.animate({ easing: options.easing }, 100 * MS_PER_SEC); + }); + }, 'Invalid keyframe easing value: \'' + options.easing + '\''); +}); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html new file mode 100644 index 000000000..4d799272b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html @@ -0,0 +1,212 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly getComputedTiming() tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffectreadonly-getcomputedtiming"> +<link rel="author" title="Boris Chiou" href="mailto:boris.chiou@gmail.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +var target = document.getElementById("target"); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }); + + var ct = effect.getComputedTiming(); + assert_equals(ct.delay, 0, "computed delay"); + assert_equals(ct.fill, "none", "computed fill"); + assert_equals(ct.iterations, 1.0, "computed iterations"); + assert_equals(ct.duration, 0, "computed duration"); + assert_equals(ct.direction, "normal", "computed direction"); +}, "values of getComputedTiming() when a KeyframeEffectReadOnly is " + + "constructed without any KeyframeEffectOptions object"); + +var gGetComputedTimingTests = [ + { 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: "none", + iterations: 5.5, + duration: 0, + direction: "alternate" } }, + { desc: "a double value", + input: 3000, + timing: { duration: 3000 }, + expected: { delay: 0, + fill: "none", + iterations: 1, + duration: 3000, + direction: "normal" } }, + { 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: 0 } }, + { desc: "an Infinity iterations", + input: { iterations: Infinity }, + expected: { iterations: Infinity } }, + { desc: "an auto fill", + input: { fill: "auto" }, + expected: { fill: "none" } }, + { desc: "a forwards fill", + input: { fill: "forwards" }, + expected: { fill: "forwards" } } +]; + +gGetComputedTimingTests.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. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + + var ct = effect.getComputedTiming(); + assert_equals(ct.delay, expected("delay", 0), + "computed delay"); + assert_equals(ct.fill, expected("fill", "none"), + "computed fill"); + assert_equals(ct.iterations, expected("iterations", 1), + "computed iterations"); + assert_equals(ct.duration, expected("duration", 0), + "computed duration"); + assert_equals(ct.direction, expected("direction", "normal"), + "computed direction"); + + }, "values of getComputedTiming() when a KeyframeEffectReadOnly is " + + "constructed by " + stest.desc); +}); + +var gActiveDurationTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and integral iteration count", + input: { duration: 1000, iterations: 7 }, + expected: 7000 }, + { desc: "a non-zero duration and fractional iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "an non-zero duration and infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an non-zero duration and zero iteration count", + input: { duration: 1000, iterations: 0 }, + expected: 0 }, + { desc: "a zero duration and default iteration count", + input: { duration: 0 }, + expected: 0 }, + { desc: "a zero duration and fractional iteration count", + input: { duration: 0, iterations: 2.5 }, + expected: 0 }, + { desc: "a zero duration and infinite iteration count", + input: { duration: 0, iterations: Infinity }, + expected: 0 }, + { desc: "a zero duration and zero iteration count", + input: { duration: 0, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and default iteration count", + input: { duration: Infinity }, + expected: Infinity }, + { desc: "an infinite duration and zero iteration count", + input: { duration: Infinity, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and fractional iteration count", + input: { duration: Infinity, iterations: 2.5 }, + expected: Infinity }, + { desc: "an infinite duration and infinite iteration count", + input: { duration: Infinity, iterations: Infinity }, + expected: Infinity }, +]; + +gActiveDurationTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().activeDuration, + stest.expected); + + }, "getComputedTiming().activeDuration for " + stest.desc); +}); + +var gEndTimeTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and non-default iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "a non-zero duration and non-zero delay", + input: { duration: 1000, delay: 1500 }, + expected: 2500 }, + { desc: "a non-zero duration, non-zero delay and non-default iteration", + input: { duration: 1000, delay: 1500, iterations: 2 }, + expected: 3500 }, + { desc: "an infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an infinite duration", + input: { duration: Infinity, iterations: 10 }, + expected: Infinity }, + { desc: "an infinite duration and delay", + input: { duration: Infinity, iterations: 10, delay: 1000 }, + expected: Infinity }, + { desc: "an infinite duration and negative delay", + input: { duration: Infinity, iterations: 10, delay: -1000 }, + expected: Infinity }, + { desc: "an non-zero duration and negative delay", + input: { duration: 1000, iterations: 2, delay: -1000 }, + expected: 1000 }, + { desc: "an non-zero duration and negative delay greater than active " + + "duration", + input: { duration: 1000, iterations: 2, delay: -3000 }, + expected: 0 }, + { desc: "a zero duration and negative delay", + input: { duration: 0, iterations: 2, delay: -1000 }, + expected: 0 } +]; + +gEndTimeTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().endTime, + stest.expected); + + }, "getComputedTiming().endTime for " + stest.desc); +}); + +done(); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html new file mode 100644 index 000000000..743d10887 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html @@ -0,0 +1,693 @@ +<!doctype html> +<meta charset=utf-8> +<title>KeyframeEffect.iterationComposite tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#effect-accumulation-section"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '5px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite of <length> type animation'); + +test(function(t) { + var parent = createDiv(t); + parent.style.width = '100px'; + var div = createDiv(t); + parent.appendChild(div); + + var anim = + div.animate({ width: ['0%', '50%'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, '100px', + 'Animated width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '125px', + 'Animated width style at 50s of the third iteration'); +}, 'iterationComposite of <percentage> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ color: ['rgb(0, 0, 0)', 'rgb(120, 120, 120)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)', + 'Animated color style at 50s of the third iteration'); +}, 'iterationComposite of <color> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ color: ['rgb(0, 120, 0)', 'rgb(60, 60, 60)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + // The green color is (240 + 180) / 2 = 210 + assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)', + 'Animated color style at 50s of the third iteration'); +}, 'iterationComposite of <color> type animation that green component is ' + + 'decreasing'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ flexGrow: [0, 10] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '5', + 'Animated flex-grow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).flexGrow, '20', + 'Animated flex-grow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '25', + 'Animated flex-grow style at 50s of the third iteration'); +}, 'iterationComposite of <number> type animation'); + +test(function(t) { + var div = createDiv(t); + div.style.position = 'absolute'; + var anim = + div.animate({ clip: ['rect(0px, 0px, 0px, 0px)', + 'rect(10px, 10px, 10px, 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)', + 'Animated clip style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)', + 'Animated clip style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)', + 'Animated clip style at 50s of the third iteration'); +}, 'iterationComposite of <shape> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ width: ['calc(0vw + 0px)', 'calc(0vw + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '5px', + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, '20px', + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated calc width style at 50s of the third iteration'); +}, 'iterationComposite of <calc()> value animation'); + +test(function(t) { + var parent = createDiv(t); + parent.style.width = '100px'; + var div = createDiv(t); + parent.appendChild(div); + + var anim = + div.animate({ width: ['calc(0% + 0px)', 'calc(10% + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '10px', + // 100px * 5% + 5px + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, + '40px', // 100px * (10% + 10%) + (10px + 10px) + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, + '50px', // (40px + 60px) / 2 + 'Animated calc width style at 50s of the third iteration'); +}, 'iterationComposite of <calc()> value animation that the values can\'t' + + 'be reduced'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ opacity: [0, 0.4] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).opacity, '0.2', + 'Animated opacity style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).opacity, '0.8', + 'Animated opacity style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5 + 'Animated opacity style at 50s of the third iteration'); +}, 'iterationComposite of opacity animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ boxShadow: ['rgb(0, 0, 0) 0px 0px 0px 0px', + 'rgb(120, 120, 120) 10px 10px 10px 0px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(60, 60, 60) 5px 5px 5px 0px', + 'Animated box-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(240, 240, 240) 20px 20px 20px 0px', + 'Animated box-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(255, 255, 255) 25px 25px 25px 0px', + 'Animated box-shadow style at 50s of the third iteration'); +}, 'iterationComposite of box-shadow animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['blur(0px)', 'blur(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(5px)', + 'Animated filter blur style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, 'blur(20px)', + 'Animated filter blur style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(25px)', + 'Animated filter blur style at 50s of the third iteration'); +}, 'iterationComposite of filter blur animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1)', + 'brightness(180%)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.4)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8) + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5 + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iterationComposite of filter brightness for different unit animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(0)', + 'brightness(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iterationComposite of filter brightness animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['drop-shadow(rgb(0, 0, 0) 0px 0px 0px)', + 'drop-shadow(rgb(120, 120, 120) 10px 10px 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)', + 'Animated filter drop-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)', + 'Animated filter drop-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)', + 'Animated filter drop-shadow style at 50s of the third iteration'); +}, 'iterationComposite of filter drop-shadow animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'brightness(2) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3.5) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of same filter list animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'contrast(2) brightness(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'contrast(2) brightness(2)', // discrete + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + // We can't accumulate 'contrast(2) brightness(2)' onto + // the first list 'brightness(1) contrast(1)' because of + // mismatch of the order. + 'brightness(1) contrast(1)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + // We *can* accumulate 'contrast(2) brightness(2)' onto + // the same list 'contrast(2) brightness(2)' here. + 'contrast(4) brightness(4)', // discrete + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of discrete filter list because of mismatch ' + + 'of the order'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['sepia(0)', + 'sepia(1) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(0.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(2) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(2.5) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of different length filter list animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['rotate(0deg)', 'rotate(180deg)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg) + 'Animated transform(rotate) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg) + 'Animated transform(rotate) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg) + 'Animated transform(rotate) style at 50s of the third iteration'); +}, 'iterationComposite of transform(rotate) animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(0)', 'scale(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element, + // not accumulated. + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity + // element, not accumulated. + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: [ scale(0), scale(1) ] animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(1)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1)) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: [ scale(1), scale(2) ] animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(0)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // scale(1) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: scale(2) animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['rotate(0deg) translateX(0px)', + 'rotate(180deg) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px) + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation'); + +test(function(t) { + var div = createDiv(t); + // The transform list whose order is mismatched is compounded, + // so below animation is the same as; + // from matrix(2, 0, 0, 2, 0, 0) to matrix(3, 0, 0, 3, 30, 0) + var anim = + div.animate({ transform: ['translateX(0px) scale(2)', + 'scale(3) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(2.5, 0, 0, 2.5, 15, 0)', // scale(2.5) (0px + 30px*2) / 2 + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(4, 0, 0, 4, 60, 0)', // scale(2+(3-2)*2) (0px + 30px*2) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(5.5, 0, 0, 5.5, 135, 0)', // scale(4+7)/2 (60px + 210px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation whose order is mismatched'); + +test(function(t) { + var div = createDiv(t); + // Even if each transform list does not have functions which exist in + // other pair of the list, we don't fill any missing functions at all, + // it's just computed as compounded matrices + // Below animation is the same as; + // from matrix(1, 0, 0, 1, 0, 0) to matrix(2, 0, 0, 2, 20, 0) + var anim = + div.animate({ transform: ['translateX(0px)', + 'scale(2) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1.5, 0, 0, 1.5, 10, 0)', // scale(1.5) (0px + 10px*2) / 2 + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 40, 0)', // scale(1+(2-1)*2) (0px + 20px*2) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3.5, 0, 0, 3.5, 80, 0)', // scale(3+4)/2 (40px + 20px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation whose order is mismatched'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['10px', '20px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '15px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite starts with non-zero value animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['10px', '-10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '0px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, + '-10px', // 10px + -10px + -10px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '-20px', // (-10px + -30px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite with negative final value animation'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = + anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); + + anim.effect.iterationComposite = 'replace'; + assert_equals(getComputedStyle(div).marginLeft, '5px', + 'Animated style at 50s of the third iteration'); + + anim.effect.iterationComposite = 'accumulate'; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); +}, 'interationComposite changes'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = + anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); + + // double its duration. + anim.effect.timing.duration = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '12.5px', + 'Animated style at 25s of the first iteration'); + + // half of original. + anim.effect.timing.duration = anim.effect.timing.duration / 4; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Animated style at 50s of the fourth iteration'); +}, 'duration changes with iterationComposite(accumulate)'); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html new file mode 100644 index 000000000..d16831281 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html @@ -0,0 +1,329 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly constructor tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#processing-a-keyframes-argument"> +<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> +<script> +'use strict'; + +// Test the "process a keyframe-like object" procedure. +// +// This file only tests the KeyframeEffectReadOnly constructor since it is +// assumed that the implementation of the KeyframeEffect constructor, +// Animatable.animate() method, and KeyframeEffect.setKeyframes() method will +// all share common machinery and it is not necessary to test each method. + +// Test that only animatable properties are accessed + +var gNonAnimatableProps = [ + 'animation', // Shorthands where all the longhand sub-properties are not + // animatable, are also not animatable. + 'animationDelay', + 'animationDirection', + 'animationDuration', + 'animationFillMode', + 'animationIterationCount', + 'animationName', + 'animationPlayState', + 'animationTimingFunction', + 'transition', + 'transitionDelay', + 'transitionDuration', + 'transitionProperty', + 'transitionTimingFunction', + 'display', + 'unsupportedProperty', +]; + +function TestKeyframe(testProp) { + var _propAccessCount = 0; + + Object.defineProperty(this, testProp, { + get: function() { _propAccessCount++; }, + enumerable: true + }); + + Object.defineProperty(this, 'propAccessCount', { + get: function() { return _propAccessCount; } + }); +} + +function GetTestKeyframeSequence(testProp) { + return [ new TestKeyframe(testProp) ] +} + +gNonAnimatableProps.forEach(function(prop) { + test(function(t) { + var testKeyframe = new TestKeyframe(prop); + + new KeyframeEffectReadOnly(null, testKeyframe); + + assert_equals(testKeyframe.propAccessCount, 0, 'Accessor not called'); + }, 'non-animatable property \'' + prop + '\' is not accessed when using' + + ' a property-indexed keyframe object'); +}); + +gNonAnimatableProps.forEach(function(prop) { + test(function(t) { + var testKeyframes = GetTestKeyframeSequence(prop); + + new KeyframeEffectReadOnly(null, testKeyframes); + + assert_equals(testKeyframes[0].propAccessCount, 0, 'Accessor not called'); + }, 'non-animatable property \'' + prop + '\' is not accessed when using' + + ' a keyframe sequence'); +}); + +// Test equivalent forms of property indexed and sequenced keyframe syntax + +function assertEquivalentKeyframeSyntax(keyframesA, keyframesB) { + var processedKeyframesA = new KeyframeEffectReadOnly(null, keyframesA).getKeyframes(); + var processedKeyframesB = new KeyframeEffectReadOnly(null, keyframesB).getKeyframes(); + assert_frame_lists_equal(processedKeyframesA, processedKeyframesB); +} + +var gEquivalentSyntaxTests = [ + { + description: 'two properties with one value', + indexedKeyframes: { + left: '100px', + opacity: ['1'], + }, + sequencedKeyframes: [ + {left: '100px', opacity: '1'}, + ], + }, + { + description: 'two properties with three values', + indexedKeyframes: { + left: ['10px', '100px', '150px'], + opacity: ['1', '0', '1'], + }, + sequencedKeyframes: [ + {left: '10px', opacity: '1'}, + {left: '100px', opacity: '0'}, + {left: '150px', opacity: '1'}, + ], + }, + { + description: 'two properties with different numbers of values', + indexedKeyframes: { + left: ['0px', '100px', '200px'], + opacity: ['0', '1'] + }, + sequencedKeyframes: [ + {left: '0px', opacity: '0'}, + {left: '100px'}, + {left: '200px', opacity: '1'}, + ], + }, + { + description: 'same offset applied to all keyframes', + indexedKeyframes: { + left: ['0px', '100px'], + offset: 0.5, + }, + sequencedKeyframes: [ + {left: '0px', offset: 0.5}, + {left: '100px', offset: 0.5}, + ], + }, + { + description: 'same easing applied to all keyframes', + indexedKeyframes: { + left: ['10px', '100px', '150px'], + opacity: ['1', '0', '1'], + easing: 'ease', + }, + sequencedKeyframes: [ + {left: '10px', opacity: '1', easing: 'ease'}, + {left: '100px', opacity: '0', easing: 'ease'}, + {left: '150px', opacity: '1', easing: 'ease'}, + ], + }, + { + description: 'same composite applied to all keyframes', + indexedKeyframes: { + left: ['0px', '100px'], + composite: 'add', + }, + sequencedKeyframes: [ + {left: '0px', composite: 'add'}, + {left: '100px', composite: 'add'}, + ], + }, +]; + +gEquivalentSyntaxTests.forEach(function({description, indexedKeyframes, sequencedKeyframes}) { + test(function(t) { + assertEquivalentKeyframeSyntax(indexedKeyframes, sequencedKeyframes); + }, 'Equivalent property indexed and sequenced keyframes: ' + description); +}); + +// Test handling of custom iterable objects. + +function createIterable(iterations) { + return { + [Symbol.iterator]() { + var i = 0; + return { + next() { + return iterations[i++]; + }, + }; + }, + }; +} + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'Custom iterator with basic keyframes.'); + +test(() => { + var keyframes = createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ]); + keyframes.easing = 'ease-in-out'; + keyframes.offset = '0.1'; + var effect = new KeyframeEffect(null, keyframes); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'easing and offset are ignored on iterable objects.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px', top: '200px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px', top: '100px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px', top: '200px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px', top: '100px'}, + ]); +}, 'Custom iterator with multiple properties specified.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '250px', offset: 0.75}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: 0.75, computedOffset: 0.75, easing: 'linear', left: '250px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'Custom iterator with offset specified.'); + +test(() => { + assert_throws({name: 'TypeError'}, function() { + new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: 1234}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + }); +}, 'Custom iterator with non object keyframe should throw.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: ['100px', '200px']}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 1, easing: 'linear', left: '100px,200px'} + ]); +}, 'Custom iterator with value list in keyframe should give bizarre string representation of list.'); + +test(function(t) { + var keyframe = {}; + Object.defineProperty(keyframe, 'width', {value: '200px'}); + Object.defineProperty(keyframe, 'height', { + value: '100px', + enumerable: true}); + assert_equals(keyframe.width, '200px', 'width of keyframe is readable'); + assert_equals(keyframe.height, '100px', 'height of keyframe is readable'); + var anim = createDiv(t).animate([keyframe, {height: '200px'}], 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', height: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', height: '200px'}, + ]); +}, 'Only enumerable properties on keyframes are considered'); + +test(function(t) { + var KeyframeParent = function() { this.width = '100px'; }; + KeyframeParent.prototype = { height: '100px' }; + var Keyframe = function() { this.top = '100px'; }; + Keyframe.prototype = Object.create(KeyframeParent.prototype); + Object.defineProperty(Keyframe.prototype, 'left', { + value: '100px', + enumerable: 'true'}); + var keyframe = new Keyframe(); + var anim = createDiv(t).animate([keyframe, {top: '200px'}], 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', top: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', top: '200px'}, + ]); +}, 'Only properties defined directly on keyframes are considered'); + +test(function(t) { + var keyframes = {}; + Object.defineProperty(keyframes, 'width', ['100px', '200px']); + Object.defineProperty(keyframes, 'height', { + value: ['100px', '200px'], + enumerable: true}); + var anim = createDiv(t).animate(keyframes, 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', height: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', height: '200px'}, + ]); +}, 'Only enumerable properties on property indexed keyframes are considered'); + +test(function(t) { + var KeyframesParent = function() { this.width = '100px'; }; + KeyframesParent.prototype = { height: '100px' }; + var Keyframes = function() { this.top = ['100px', '200px']; }; + Keyframes.prototype = Object.create(KeyframesParent.prototype); + Object.defineProperty(Keyframes.prototype, 'left', { + value: ['100px', '200px'], + enumerable: 'true'}); + var keyframes = new Keyframes(); + var anim = createDiv(t).animate(keyframes, 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', top: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', top: '200px'}, + ]); +}, 'Only properties defined directly on property indexed keyframes are considered'); + +// FIXME: Test that properties are accessed in ascending order by Unicode +// codepoint +// (There is an existing test for this in +// keyframe-effect/constructor.html that should be moved here.) + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html new file mode 100644 index 000000000..c951411de --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect setKeyframes() tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-keyframeeffect-setkeyframes"> +<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> +<script> +'use strict'; + +var target = document.getElementById('target'); + +test(function(t) { + gEmptyKeyframeListTests.forEach(function(frame) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(frame); + assert_frame_lists_equal(effect.getKeyframes(), []); + }); +}, 'Keyframes can be replaced with an empty keyframe'); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, 'Keyframes can be replaced with ' + subtest.desc); +}); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, 'Keyframes can be replaced with ' + subtest.desc); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + assert_throws(subtest.expected, function() { + effect.setKeyframes(subtest.input); + }); + }, 'KeyframeEffect constructor throws with ' + subtest.desc); +}); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html new file mode 100644 index 000000000..2b07d3de6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Writable effect.target tests</title> +<link rel="help" + href="https://w3c.github.io/web-animations/#dom-keyframeeffect-target"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +var gKeyFrames = { 'marginLeft': ['0px', '100px'] }; + +test(function(t) { + var div = createDiv(t); + var effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC); + effect.target = div; + + var anim = new Animation(effect, document.timeline); + anim.play(); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress'); +}, 'Test setting target before constructing the associated animation'); + +test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC); + var anim = new Animation(effect, document.timeline); + anim.play(); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '10px', + 'Value at 50% progress before setting new target'); + effect.target = div; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress after setting new target'); +}, 'Test setting target from null to a valid target'); + +test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var anim = div.animate(gKeyFrames, 100 * MS_PER_SEC); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress before clearing the target') + + anim.effect.target = null; + assert_equals(getComputedStyle(div).marginLeft, '10px', + 'Value after clearing the target') +}, 'Test setting target from a valid target to null'); + +test(function(t) { + var a = createDiv(t); + var b = createDiv(t); + a.style.marginLeft = '10px'; + b.style.marginLeft = '20px'; + var anim = a.animate(gKeyFrames, 100 * MS_PER_SEC); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(a).marginLeft, '50px', + 'Value of 1st element (currently targeted) before ' + + 'changing the effect target'); + assert_equals(getComputedStyle(b).marginLeft, '20px', + 'Value of 2nd element (currently not targeted) before ' + + 'changing the effect target'); + anim.effect.target = b; + assert_equals(getComputedStyle(a).marginLeft, '10px', + 'Value of 1st element (currently not targeted) after ' + + 'changing the effect target'); + assert_equals(getComputedStyle(b).marginLeft, '50px', + 'Value of 2nd element (currently targeted) after ' + + 'changing the effect target'); + + // This makes sure the animation property is changed correctly on new + // targeted element. + anim.currentTime = 75 * MS_PER_SEC; + assert_equals(getComputedStyle(b).marginLeft, '75px', + 'Value of 2nd target (currently targeted) after ' + + 'changing the animation current time.'); +}, 'Test setting target from a valid target to another target'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + anim.effect.target = null; + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[0].computedOffset, 0.0, '1st frame offset'); + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, 'last frame offset'); +}, 'Test falling back to distribute spacing mode after setting null target'); + +test(function(t) { + var effect = new KeyframeEffect(null, + [ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + var frames = effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); +}, 'Test falling back to distribute spacing mode if there is no context ' + + 'element'); + +test(function(t) { + var div1 = createDiv(t); + var div2 = createDiv(t); + div1.style.marginLeft = '-20px'; + div2.style.marginLeft = '-50px'; + var child1 = document.createElement('div'); + var child2 = document.createElement('div'); + div1.appendChild(child1); + div2.appendChild(child2); + // body + // / \ + // div1 div2 + // (-20px) (-50px) + // | | + // child1 child2 + var anim = child1.animate([ { marginLeft: '0px' }, + { marginLeft: 'inherit' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 20, 140, 190]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); + + anim.effect.target = child2; + frames = anim.effect.getKeyframes(); + cumDist = [0, 50, 200, 250]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset after setting a new target'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset after setting a new target'); +}, 'Test paced spacing mode after setting a new target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html new file mode 100644 index 000000000..612a3af8d --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect spacing attribute tests</title> +<link rel="help" + href="https://w3c.github.io/web-animations/#dom-keyframeeffect-spacing"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_throws(new TypeError, function() { + anim.effect.spacing = ''; + }); +}, 'Test throwing TypeError if using empty string'); + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_throws(new TypeError, function() { + anim.effect.spacing = 'dist'; + }); +}, 'Test throwing TypeError if not using the correct keyword'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(A)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(--bg-color)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using CSS variables'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(animation-duration)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a non-animatable ' + + 'property'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'distribute'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test spacing value if setting distribute'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(transform)'; + assert_equals(anim.effect.spacing, 'paced(transform)', 'spacing mode'); +}, 'Test spacing value if setting paced'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html new file mode 100644 index 000000000..287ffe114 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly copy constructor tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var effect = new KeyframeEffectReadOnly(createDiv(t), null); + var copiedEffect = new KeyframeEffectReadOnly(effect); + assert_equals(copiedEffect.target, effect.target, 'same target'); +}, 'Test copied keyframeEffectReadOnly has the same target'); + +test(function(t) { + var effect = + new KeyframeEffectReadOnly(null, + [ { marginLeft: '0px' }, + { marginLeft: '-20px', easing: 'ease-in', + offset: 0.1 }, + { marginLeft: '100px', easing: 'ease-out' }, + { marginLeft: '50px' } ], + { spacing: 'paced(margin-left)' }); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + var KeyframesA = effect.getKeyframes(); + var KeyframesB = copiedEffect.getKeyframes(); + assert_equals(KeyframesA.length, KeyframesB.length, 'same keyframes length'); + + for (var i = 0; i < KeyframesA.length; ++i) { + assert_equals(KeyframesA[i].offset, KeyframesB[i].offset, + 'Keyframe ' + i + ' has the same offset'); + assert_equals(KeyframesA[i].computedOffset, KeyframesB[i].computedOffset, + 'keyframe ' + i + ' has the same computedOffset'); + assert_equals(KeyframesA[i].easing, KeyframesB[i].easing, + 'keyframe ' + i + ' has the same easing'); + assert_equals(KeyframesA[i].composite, KeyframesB[i].composite, + 'keyframe ' + i + ' has the same composite'); + + assert_true(!!KeyframesA[i].marginLeft, + 'original keyframe ' + i + ' has the valid property value'); + assert_true(!!KeyframesB[i].marginLeft, + 'new keyframe ' + i + ' has the valid property value'); + assert_equals(KeyframesA[i].marginLeft, KeyframesB[i].marginLeft, + 'keyframe ' + i + ' has the same property value pair'); + } +}, 'Test copied keyframeEffectReadOnly has the same keyframes'); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, null, + { spacing: 'paced(margin-left)', + iterationComposite: 'accumulate' }); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + assert_equals(copiedEffect.spacing, effect.spacing, 'same spacing'); + assert_equals(copiedEffect.iterationComposite, effect.iterationComposite, + 'same iterationCompositeOperation'); + assert_equals(copiedEffect.composite, effect.composite, + 'same compositeOperation'); +}, 'Test copied keyframeEffectReadOnly has the same keyframeEffectOptions'); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, null, + { duration: 100 * MS_PER_SEC, + delay: -1 * MS_PER_SEC, + endDelay: 2 * MS_PER_SEC, + fill: 'forwards', + iterationStart: 2, + iterations: 20, + easing: 'ease-out', + direction: 'alternate' } ); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + var timingA = effect.timing; + var timingB = copiedEffect.timing; + assert_not_equals(timingA, timingB, 'different timing objects'); + assert_equals(timingA.delay, timingB.delay, 'same delay'); + assert_equals(timingA.endDelay, timingB.endDelay, 'same endDelay'); + assert_equals(timingA.fill, timingB.fill, 'same fill'); + assert_equals(timingA.iterationStart, timingB.iterationStart, + 'same iterationStart'); + assert_equals(timingA.iterations, timingB.iterations, 'same iterations'); + assert_equals(timingA.duration, timingB.duration, 'same duration'); + assert_equals(timingA.direction, timingB.direction, 'same direction'); + assert_equals(timingA.easing, timingB.easing, 'same easing'); +}, 'Test copied keyframeEffectReadOnly has the same timing content'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html new file mode 100644 index 000000000..c83d1ebcb --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html @@ -0,0 +1,237 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly spacing attribute tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: '' }); + }); +}, 'Test throwing TypeError if using empty string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'dist' }); + }); +}, 'Test throwing TypeError if not using the correct keyword'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: ' paced(margin-left)' }); + }); +}, 'Test throwing TypeError if adding leading spaces'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin-left) ' }); + }); +}, 'Test throwing TypeError if adding trailing spaces'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced( margin-left)' }); + }); +}, 'Test throwing TypeError if adding leading spaces before the ' + + 'paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin-left )' }); + }); +}, 'Test throwing TypeError if adding trailing spaces after the ' + + 'paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced()' }); + }); +}, 'Test throwing TypeError if these is no paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(.margin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(1margin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(- )' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid name-start code point'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin.left)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid name ' + + 'code point'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(A)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(\\.margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (Full stop)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(\\ margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (white space)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(_margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (low line)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(-_margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a minus and a low line'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(-\\.margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a minus and a valid escape'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(--bg-color)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using CSS variables'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(animation)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a non-animatable ' + + 'shorthand property'); + +test(function(t) { + var anim = createDiv(t).animate(null, + { spacing: 'paced(animation-duration)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a non-animatable ' + + 'property'); + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test default value of spacing'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'distribute' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test spacing value if setting distribute'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(margin-left)' }); + assert_equals(anim.effect.spacing, 'paced(margin-left)', 'spacing mode'); +}, 'Test spacing value if setting paced'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/resources/effect-easing-tests.js b/testing/web-platform/tests/web-animations/resources/effect-easing-tests.js new file mode 100644 index 000000000..49c4ff5b8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/resources/effect-easing-tests.js @@ -0,0 +1,98 @@ +var gEffectEasingTests = [ + { + desc: 'step-start function', + easing: 'step-start', + easingFunction: stepStart(1), + serialization: 'steps(1, start)' + }, + { + desc: 'steps(1, start) function', + easing: 'steps(1, start)', + easingFunction: stepStart(1) + }, + { + desc: 'steps(2, start) function', + easing: 'steps(2, start)', + easingFunction: stepStart(2) + }, + { + desc: 'step-end function', + easing: 'step-end', + easingFunction: stepEnd(1), + serialization: 'steps(1)' + }, + { + desc: 'steps(1) function', + easing: 'steps(1)', + easingFunction: stepEnd(1) + }, + { + desc: 'steps(1, end) function', + easing: 'steps(1, end)', + easingFunction: stepEnd(1), + serialization: 'steps(1)' + }, + { + desc: 'steps(2, end) function', + easing: 'steps(2, end)', + easingFunction: stepEnd(2), + serialization: 'steps(2)' + }, + { + desc: 'linear function', + easing: 'linear', // cubic-bezier(0, 0, 1.0, 1.0) + easingFunction: cubicBezier(0, 0, 1.0, 1.0) + }, + { + desc: 'ease function', + easing: 'ease', // cubic-bezier(0.25, 0.1, 0.25, 1.0) + easingFunction: cubicBezier(0.25, 0.1, 0.25, 1.0) + }, + { + desc: 'ease-in function', + easing: 'ease-in', // cubic-bezier(0.42, 0, 1.0, 1.0) + easingFunction: cubicBezier(0.42, 0, 1.0, 1.0) + }, + { + desc: 'ease-in-out function', + easing: 'ease-in-out', // cubic-bezier(0.42, 0, 0.58, 1.0) + easingFunction: cubicBezier(0.42, 0, 0.58, 1.0) + }, + { + desc: 'ease-out function', + easing: 'ease-out', // cubic-bezier(0, 0, 0.58, 1.0) + easingFunction: cubicBezier(0, 0, 0.58, 1.0) + }, + { + desc: 'easing function which produces values greater than 1', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)', + easingFunction: cubicBezier(0, 1.5, 1, 1.5) + } +]; + +var gInvalidEasingTests = [ + { + easing: '' + }, + { + easing: 'test' + }, + { + easing: 'cubic-bezier(1.1, 0, 1, 1)' + }, + { + easing: 'cubic-bezier(0, 0, 1.1, 1)' + }, + { + easing: 'cubic-bezier(-0.1, 0, 1, 1)' + }, + { + easing: 'cubic-bezier(0, 0, -0.1, 1)' + }, + { + easing: 'steps(-1, start)' + }, + { + easing: 'steps(0.1, start)' + }, +]; diff --git a/testing/web-platform/tests/web-animations/resources/keyframe-utils.js b/testing/web-platform/tests/web-animations/resources/keyframe-utils.js new file mode 100644 index 000000000..626f0bffb --- /dev/null +++ b/testing/web-platform/tests/web-animations/resources/keyframe-utils.js @@ -0,0 +1,563 @@ +"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 property-indexed keyframes specification with a CSS variable" + + " reference", + input: { left: [ "var(--dist)", "calc(var(--dist) + 100px)" ] }, + output: [{ offset: null, computedOffset: 0.0, easing: "linear", + left: "var(--dist)" }, + { offset: null, computedOffset: 1.0, easing: "linear", + left: "calc(var(--dist) + 100px)" }] }, + { desc: "a property-indexed keyframes specification with a CSS variable" + + " reference in a shorthand property", + input: { margin: [ "var(--dist)", "calc(var(--dist) + 100px)" ] }, + output: [{ offset: null, computedOffset: 0.0, easing: "linear", + margin: "var(--dist)" }, + { offset: null, computedOffset: 1.0, easing: "linear", + margin: "calc(var(--dist) + 100px)" }] }, + { 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" }] }, +]; + +var gKeyframeSequenceTests = [ + { desc: "a one property one keyframe sequence", + input: [{ offset: 1, left: "10px" }], + output: [{ offset: null, computedOffset: 1, easing: "linear", + left: "10px" }] }, + { 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 single keyframe sequence with omitted offsets", + input: [{ left: "10px" }], + output: [{ offset: null, computedOffset: 1, easing: "linear", + left: "10px" }] }, + { desc: "a single keyframe sequence with string offset", + input: [{ offset: '0.5', left: "10px" }], + output: [{ offset: 0.5, computedOffset: 1, easing: "linear", + left: "10px" }] }, + { 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: "steps(1)", + left: "50px" }, + { offset: 1.0, computedOffset: 1.0, easing: "steps(1)", + 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 with a CSS variable reference", + input: [{ left: "var(--dist)" }, + { left: "calc(var(--dist) + 100px)" }], + output: [{ offset: null, computedOffset: 0.0, easing: "linear", + left: "var(--dist)" }, + { offset: null, computedOffset: 1.0, easing: "linear", + left: "calc(var(--dist) + 100px)" }] }, + { desc: "a keyframe sequence with a CSS variable reference in a shorthand" + + " property", + input: [{ margin: "var(--dist)" }, + { margin: "calc(var(--dist) + 100px)" }], + output: [{ offset: null, computedOffset: 0.0, easing: "linear", + margin: "var(--dist)" }, + { offset: null, computedOffset: 1.0, easing: "linear", + margin: "calc(var(--dist) + 100px)" }] }, + { 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)" }] }, + { desc: "a two property keyframe sequence 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 keyframe sequence 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 keyframe sequence with repeated values at offset 1 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: "steps(1)", + left: "500px" }] }, +]; + +var gInvalidKeyframesTests = [ + { desc: "keyframes with an out-of-bounded positive offset", + input: [ { opacity: 0 }, + { opacity: 0.5, offset: 2 }, + { opacity: 1 } ], + expected: { name: "TypeError" } }, + { desc: "keyframes with an out-of-bounded negative offset", + input: [ { opacity: 0 }, + { opacity: 0.5, offset: -1 }, + { opacity: 1 } ], + expected: { name: "TypeError" } }, + { desc: "keyframes not loosely sorted by offset", + input: [ { opacity: 0, offset: 1 }, + { opacity: 1, offset: 0 } ], + expected: { name: "TypeError" } }, + { desc: "property-indexed keyframes with an invalid easing value", + input: { opacity: [ 0, 0.5, 1 ], + easing: "inherit" }, + expected: { name: "TypeError" } }, + { desc: "a keyframe sequence with an invalid easing value", + input: [ { opacity: 0, easing: "jumpy" }, + { opacity: 1 } ], + expected: { name: "TypeError" } }, + { desc: "keyframes with an invalid composite value", + input: [ { opacity: 0, composite: "alternate" }, + { opacity: 1 } ], + expected: { name: "TypeError" } } +]; + +// ------------------------------ +// 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" } } +]; diff --git a/testing/web-platform/tests/web-animations/testcommon.js b/testing/web-platform/tests/web-animations/testcommon.js new file mode 100644 index 000000000..31ebdfaf2 --- /dev/null +++ b/testing/web-platform/tests/web-animations/testcommon.js @@ -0,0 +1,176 @@ +/* +Distributed under both the W3C Test Suite License [1] and the W3C +3-clause BSD License [2]. To contribute to a W3C Test Suite, see the +policies and contribution forms [3]. + +[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license +[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license +[3] http://www.w3.org/2004/10/27-testcases + */ + +'use strict'; + +var MS_PER_SEC = 1000; + +// The recommended minimum precision to use for time values[1]. +// +// [1] https://w3c.github.io/web-animations/#precision-of-time-values +var TIME_PRECISION = 0.0005; // ms + +// Allow implementations to substitute an alternative method for comparing +// times based on their precision requirements. +if (!window.assert_times_equal) { + window.assert_times_equal = function(actual, expected, description) { + assert_approx_equals(actual, expected, TIME_PRECISION, description); + } +} + +// creates div element, appends it to the document body and +// removes the created element during test cleanup +function createDiv(test, doc) { + return createElement(test, 'div', doc); +} + +// creates element of given tagName, appends it to the document body and +// removes the created element during test cleanup +// if tagName is null or undefined, returns div element +function createElement(test, tagName, doc) { + if (!doc) { + doc = document; + } + var element = doc.createElement(tagName || 'div'); + doc.body.appendChild(element); + test.add_cleanup(function() { + element.remove(); + }); + return element; +} + +// Creates a style element with the specified rules, appends it to the document +// head and removes the created element during test cleanup. +// |rules| is an object. For example: +// { '@keyframes anim': '' , +// '.className': 'animation: anim 100s;' }; +// or +// { '.className1::before': 'content: ""; width: 0px; transition: all 10s;', +// '.className2::before': 'width: 100px;' }; +// The object property name could be a keyframes name, or a selector. +// The object property value is declarations which are property:value pairs +// split by a space. +function createStyle(test, rules, doc) { + if (!doc) { + doc = document; + } + var extraStyle = doc.createElement('style'); + doc.head.appendChild(extraStyle); + if (rules) { + var sheet = extraStyle.sheet; + for (var selector in rules) { + sheet.insertRule(selector + '{' + rules[selector] + '}', + sheet.cssRules.length); + } + } + test.add_cleanup(function() { + extraStyle.remove(); + }); +} + +// Create a pseudo element +function createPseudo(test, type) { + createStyle(test, { '@keyframes anim': '', + ['.pseudo::' + type]: 'animation: anim 10s;' }); + var div = createDiv(test); + div.classList.add('pseudo'); + var anims = document.getAnimations(); + assert_true(anims.length >= 1); + var anim = anims[anims.length - 1]; + assert_equals(anim.effect.target.parentElement, div); + assert_equals(anim.effect.target.type, '::' + type); + anim.cancel(); + return anim.effect.target; +} + +// Convert px unit value to a Number +function pxToNum(str) { + return Number(String(str).match(/^(-?[\d.]+)px$/)[1]); +} + +// Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1). +function cubicBezier(x1, y1, x2, y2) { + function xForT(t) { + var omt = 1-t; + return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t; + } + + function yForT(t) { + var omt = 1-t; + return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t; + } + + function tForX(x) { + // Binary subdivision. + var mint = 0, maxt = 1; + for (var i = 0; i < 30; ++i) { + var guesst = (mint + maxt) / 2; + var guessx = xForT(guesst); + if (x < guessx) { + maxt = guesst; + } else { + mint = guesst; + } + } + return (mint + maxt) / 2; + } + + return function bezierClosure(x) { + if (x == 0) { + return 0; + } + if (x == 1) { + return 1; + } + return yForT(tForX(x)); + } +} + +function stepEnd(nsteps) { + return function stepEndClosure(x) { + return Math.floor(x * nsteps) / nsteps; + } +} + +function stepStart(nsteps) { + return function stepStartClosure(x) { + var result = Math.floor(x * nsteps + 1.0) / nsteps; + return (result > 1.0) ? 1.0 : result; + } +} + +function waitForAnimationFrames(frameCount) { + return new Promise(function(resolve, reject) { + function handleFrame() { + if (--frameCount <= 0) { + resolve(); + } else { + window.requestAnimationFrame(handleFrame); // wait another frame + } + } + window.requestAnimationFrame(handleFrame); + }); +} + +// Continually calls requestAnimationFrame until |minDelay| has elapsed +// as recorded using document.timeline.currentTime (i.e. frame time not +// wall-clock time). +function waitForAnimationFramesWithDelay(minDelay) { + var startTime = document.timeline.currentTime; + return new Promise(function(resolve) { + (function handleFrame() { + if (document.timeline.currentTime - startTime >= minDelay) { + resolve(); + } else { + window.requestAnimationFrame(handleFrame); + } + }()); + }); +} diff --git a/testing/web-platform/tests/web-animations/timing-model/animation-effects/active-time.html b/testing/web-platform/tests/web-animations/timing-model/animation-effects/active-time.html new file mode 100644 index 000000000..bdaad08ed --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/active-time.html @@ -0,0 +1,142 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Active time tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#calculating-the-active-time"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var tests = [ { fill: 'none', progress: null }, + { fill: 'backwards', progress: 0 }, + { fill: 'forwards', progress: null }, + { fill: 'both', progress: 0 } ]; + tests.forEach(function(test) { + var anim = createDiv(t).animate(null, { delay: 1, fill: test.fill }); + assert_equals(anim.effect.getComputedTiming().progress, test.progress, + 'Progress in before phase when using \'' + test.fill + + '\' fill'); + }); +}, 'Active time in before phase'); + +test(function(t) { + var anim = createDiv(t).animate(null, 1000); + anim.currentTime = 500; + assert_times_equal(anim.effect.getComputedTiming().progress, 0.5); +}, 'Active time in active phase and no start delay is the local time'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, delay: 500 }); + anim.currentTime = 1000; + assert_times_equal(anim.effect.getComputedTiming().progress, 0.5); +}, 'Active time in active phase and positive start delay is the local time' + + ' minus the start delay'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, delay: -500 }); + assert_times_equal(anim.effect.getComputedTiming().progress, 0.5); +}, 'Active time in active phase and negative start delay is the local time' + + ' minus the start delay'); + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_equals(anim.effect.getComputedTiming().progress, null); +}, 'Active time in after phase with no fill is unresolved'); + +test(function(t) { + var anim = createDiv(t).animate(null, { fill: 'backwards' }); + assert_equals(anim.effect.getComputedTiming().progress, null); +}, 'Active time in after phase with backwards-only fill is unresolved'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, // Should have no effect + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); + assert_times_equal(anim.effect.getComputedTiming().progress, 0.3); +}, 'Active time in after phase with forwards fill is the active duration'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 0, + iterations: Infinity, + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, Infinity); + assert_equals(anim.effect.getComputedTiming().progress, 1); +}, 'Active time in after phase with forwards fill, zero-duration, and ' + + ' infinite iteration count is the active duration'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, + endDelay: 4000, + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); + assert_times_equal(anim.effect.getComputedTiming().progress, 0.3); +}, 'Active time in after phase with forwards fill and positive end delay' + + ' is the active duration'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, + endDelay: -800, + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 1); + assert_times_equal(anim.effect.getComputedTiming().progress, 0.5); +}, 'Active time in after phase with forwards fill and negative end delay' + + ' is the active duration + end delay'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, + endDelay: -2500, + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 0); + assert_equals(anim.effect.getComputedTiming().progress, 0); +}, 'Active time in after phase with forwards fill and negative end delay' + + ' greater in magnitude than the active duration is zero'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, + endDelay: -4000, + fill: 'forwards' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 0); + assert_equals(anim.effect.getComputedTiming().progress, 0); +}, 'Active time in after phase with forwards fill and negative end delay' + + ' greater in magnitude than the sum of the active duration and start delay' + + ' is zero'); + +test(function(t) { + var anim = createDiv(t).animate(null, { duration: 1000, + iterations: 2.3, + delay: 500, + fill: 'both' }); + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); + assert_times_equal(anim.effect.getComputedTiming().progress, 0.3); +}, 'Active time in after phase with \'both\' fill is the active duration'); + +test(function(t) { + // Create an effect with a non-zero duration so we ensure we're not just + // testing the after-phase behavior. + var effect = new KeyframeEffect(null, null, 1); + assert_equals(effect.getComputedTiming().progress, null); +}, 'Active time when the local time is unresolved, is unresolved'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animation-effects/current-iteration.html b/testing/web-platform/tests/web-animations/timing-model/animation-effects/current-iteration.html new file mode 100644 index 000000000..d24908628 --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/current-iteration.html @@ -0,0 +1,584 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Current iteration tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#current-iteration"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +function runTests(tests, description) { + tests.forEach(function(currentTest) { + var testParams = ''; + for (var attr in currentTest.input) { + testParams += ' ' + attr + ':' + currentTest.input[attr]; + } + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, currentTest.input); + assert_equals(anim.effect.getComputedTiming().currentIteration, + currentTest.before); + anim.currentTime = currentTest.input.delay || 0; + assert_equals(anim.effect.getComputedTiming().currentIteration, + currentTest.active); + if (typeof currentTest.after !== 'undefined') { + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, + currentTest.after); + } + }, description + ':' + testParams); + }); +} + +async_test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, { delay: 1 }); + assert_equals(anim.effect.getComputedTiming().currentIteration, null); + anim.finished.then(t.step_func(function() { + assert_equals(anim.effect.getComputedTiming().currentIteration, null); + t.done(); + })); +}, 'Test currentIteration during before and after phase when fill is none'); + + +// -------------------------------------------------------------------- +// +// Zero iteration duration tests +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 0, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 2, + active: 2, + after: 2 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 2, + active: 2, + after: 2 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 2, + active: 2, + after: 2 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 3, + active: 3, + after: 3 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 3, + active: 3, + after: 3 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 3, + active: 3, + after: 3 + } +], 'Test zero iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is an integer +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 3, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 2, + after: 2 + }, + + { + input: { iterations: 3, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 2 + }, + + { + input: { iterations: 3, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 2, + active: 5, + after: 5 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 2, + active: 2, + after: 5 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 2, + active: 2 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 3, + active: 5, + after: 5 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 3, + active: 3, + after: 5 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 3, + active: 3 + } +], 'Test integer iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is a fraction +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 3.5, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 3, + after: 3 + }, + + { + input: { iterations: 3.5, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 3 + }, + + { + input: { iterations: 3.5, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 2, + active: 5, + after: 5 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 2, + active: 2, + after: 5 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 2, + active: 2 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 3, + active: 6, + after: 6 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 3, + active: 3, + after: 6 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 3, + active: 3 + } +], 'Test fractional iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is Infinity +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: Infinity, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: Infinity, + after: Infinity + }, + + { + input: { iterations: Infinity, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: Infinity, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 2, + active: Infinity, + after: Infinity + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 2, + active: 2 + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 2, + active: 2 + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 3, + active: Infinity, + after: Infinity + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 3, + active: 3 + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 3, + active: 3 + } +], 'Test infinity iterations'); + + +// -------------------------------------------------------------------- +// +// End delay tests +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: 50 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -200 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: 50 }, + before: 0, + active: 0, + after: 1 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 1, + iterationStart: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 2, + active: 2, + after: 2 + }, + + { + input: { iterations: 1, + iterationStart: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 2, + active: 2, + after: 2 + }, +], 'Test end delay'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html b/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html new file mode 100644 index 000000000..5dc32066f --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/phases-and-states.html @@ -0,0 +1,191 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for phases and states</title> +<link rel="help" href="https://w3c.github.io/web-animations/#animation-effect-phases-and-states"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +// -------------------------------------------------------------------- +// +// Phases +// +// -------------------------------------------------------------------- + +function assert_phase_at_time(animation, phase, currentTime) { + animation.currentTime = currentTime; + + if (phase === 'active') { + // If the fill mode is 'none', then progress will only be non-null if we + // are in the active phase. + animation.effect.timing.fill = 'none'; + assert_not_equals(animation.effect.getComputedTiming().progress, null, + 'Animation effect is in active phase when current time' + + ' is ' + currentTime + 'ms'); + } else { + // The easiest way to distinguish between the 'before' phase and the 'after' + // phase is to toggle the fill mode. For example, if the progress is null + // will the fill node is 'none' but non-null when the fill mode is + // 'backwards' then we are in the before phase. + animation.effect.timing.fill = 'none'; + assert_equals(animation.effect.getComputedTiming().progress, null, + 'Animation effect is in ' + phase + ' phase when current time' + + ' is ' + currentTime + 'ms' + + ' (progress is null with \'none\' fill mode)'); + + animation.effect.timing.fill = phase === 'before' + ? 'backwards' + : 'forwards'; + assert_not_equals(animation.effect.getComputedTiming().progress, null, + 'Animation effect is in ' + phase + ' phase when current' + + ' time is ' + currentTime + 'ms' + + ' (progress is non-null with appropriate fill mode)'); + } +} + +test(function(t) { + var animation = createDiv(t).animate(null, 1); + + [ { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'active' }, + { currentTime: 1, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for a simple animation effect'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, delay: 1 }); + + [ { currentTime: 0, phase: 'before' }, + { currentTime: 1, phase: 'active' }, + { currentTime: 2, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a positive start delay'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, delay: -1 }); + + [ { currentTime: -2, phase: 'before' }, + { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative start delay'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, endDelay: 1 }); + + [ { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'active' }, + { currentTime: 1, phase: 'after' }, + { currentTime: 2, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a positive end delay'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 2, endDelay: -1 }); + + [ { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'active' }, + { currentTime: 0.9, phase: 'active' }, + { currentTime: 1, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative end delay lesser' + + ' in magnitude than the active duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, endDelay: -1 }); + + [ { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'after' }, + { currentTime: 1, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative end delay equal' + + ' in magnitude to the active duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, endDelay: -2 }); + + [ { currentTime: -2, phase: 'before' }, + { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative end delay' + + ' greater in magnitude than the active duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 2, + delay: 1, + endDelay: -1 }); + + [ { currentTime: 0, phase: 'before' }, + { currentTime: 1, phase: 'active' }, + { currentTime: 2, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a positive start delay' + + ' and a negative end delay lesser in magnitude than the active duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, + delay: -1, + endDelay: -1 }); + + [ { currentTime: -2, phase: 'before' }, + { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative start delay' + + ' and a negative end delay equal in magnitude to the active duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, { duration: 1, + delay: -1, + endDelay: -2 }); + + [ { currentTime: -3, phase: 'before' }, + { currentTime: -2, phase: 'before' }, + { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for an animation effect with a negative start delay' + + ' and a negative end delay equal greater in magnitude than the active' + + ' duration'); + +test(function(t) { + var animation = createDiv(t).animate(null, 1); + animation.playbackRate = -1; + + [ { currentTime: -1, phase: 'before' }, + { currentTime: 0, phase: 'before' }, + { currentTime: 1, phase: 'active' }, + { currentTime: 2, phase: 'after' } ] + .forEach(function(test) { + assert_phase_at_time(animation, test.phase, test.currentTime); + }); +}, 'Phase calculation for a simple animation effect with negative playback' + + ' rate'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animation-effects/simple-iteration-progress.html b/testing/web-platform/tests/web-animations/timing-model/animation-effects/simple-iteration-progress.html new file mode 100644 index 000000000..f6a3a51bd --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animation-effects/simple-iteration-progress.html @@ -0,0 +1,575 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Simple iteration progress tests</title> +<link rel="help" + href="https://w3c.github.io/web-animations/#simple-iteration-progress"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +function runTests(tests, description) { + tests.forEach(function(currentTest) { + var testParams = ''; + for (var attr in currentTest.input) { + testParams += ' ' + attr + ':' + currentTest.input[attr]; + } + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, currentTest.input); + assert_equals(anim.effect.getComputedTiming().progress, + currentTest.before); + anim.currentTime = currentTest.input.delay || 0; + assert_equals(anim.effect.getComputedTiming().progress, + currentTest.active); + if (typeof currentTest.after !== 'undefined') { + anim.finish(); + assert_equals(anim.effect.getComputedTiming().progress, + currentTest.after); + } + }, description + ':' + testParams); + }); +} + + +// -------------------------------------------------------------------- +// +// Zero iteration duration tests +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 0, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 0, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterations: 0, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0 + } +], 'Test zero iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is an integer +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 3, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 1, + after: 1 + }, + + { + input: { iterations: 3, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 1 + }, + + { + input: { iterations: 3, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 3, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 1, + after: 1 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 1 + }, + + { + input: { iterations: 3, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + } +], 'Test integer iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is a fraction +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: 3.5, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 3.5, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0.5 + }, + + { + input: { iterations: 3.5, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 1, + after: 1 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 1 + }, + + { + input: { iterations: 3.5, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0, + after: 0.5 + }, + + { + input: { iterations: 3.5, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + } +], 'Test fractional iterations'); + + +// -------------------------------------------------------------------- +// +// Tests where the iteration count is Infinity +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { iterations: Infinity, + iterationStart: 0, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 1, + after: 1 + }, + + { + input: { iterations: Infinity, + iterationStart: 0, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: Infinity, + iterationStart: 0, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5 + }, + + { + input: { iterations: Infinity, + iterationStart: 2.5, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0.5, + active: 0.5 + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: 0, + delay: 1, + fill: 'both' }, + before: 0, + active: 1, + after: 1 + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: 100, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + }, + + { + input: { iterations: Infinity, + iterationStart: 3, + duration: Infinity, + delay: 1, + fill: 'both' }, + before: 0, + active: 0 + } +], 'Test infinity iterations'); + + +// -------------------------------------------------------------------- +// +// End delay tests +// +// -------------------------------------------------------------------- + +runTests([ + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: 50 }, + before: 0, + active: 0, + after: 1 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 0, + active: 0, + after: 0.5 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { duration: 100, + delay: 1, + fill: 'both', + endDelay: -200 }, + before: 0, + active: 0, + after: 0 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: 50 }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 0.5, + active: 0.5, + after: 1 + }, + + { + input: { iterationStart: 0.5, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0.5, + active: 0.5, + after: 0.5 + }, + + { + input: { iterations: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 1 + }, + + { + input: { iterations: 1, + iterationStart: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -50 }, + before: 0, + active: 0, + after: 0.5 + }, + + { + input: { iterations: 1, + iterationStart: 2, + duration: 100, + delay: 1, + fill: 'both', + endDelay: -100 }, + before: 0, + active: 0, + after: 0 + }, +], 'Test end delay'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animations/current-time.html b/testing/web-platform/tests/web-animations/timing-model/animations/current-time.html new file mode 100644 index 000000000..efc7ba78b --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animations/current-time.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests for current time</title> +<link rel="help" href="https://w3c.github.io/web-animations/#current-time"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + animation.play(); + assert_equals(animation.currentTime, 0, + 'Current time returns the hold time set when entering the play-pending ' + + 'state'); +}, 'The current time returns the hold time when set'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + + return animation.ready.then(function() { + assert_equals(animation.currentTime, null); + }); +}, 'The current time is unresolved when there is no associated timeline ' + + '(and no hold time is set)'); + +// FIXME: Test that the current time is unresolved when we have an inactive +// timeline if we find a way of creating an inactive timeline! + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + animation.startTime = null; + assert_equals(animation.currentTime, null); +}, 'The current time is unresolved when the start time is unresolved ' + + '(and no hold time is set)'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + animation.playbackRate = 2; + animation.startTime = document.timeline.currentTime - 25 * MS_PER_SEC; + + var timelineTime = document.timeline.currentTime; + var startTime = animation.startTime; + var playbackRate = animation.playbackRate; + assert_times_equal(animation.currentTime, + (timelineTime - startTime) * playbackRate, + 'Animation has a unresolved start time'); +}, 'The current time is calculated from the timeline time, start time and ' + + 'playback rate'); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html new file mode 100644 index 000000000..8b74c92a4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html @@ -0,0 +1,207 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Setting the start time tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#set-the-animation-start-time"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) +{ + // It should only be possible to set *either* the start time or the current + // time for an animation that does not have an active timeline. + + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + + assert_equals(animation.currentTime, null, 'Intial current time'); + assert_equals(animation.startTime, null, 'Intial start time'); + + animation.currentTime = 1000; + assert_equals(animation.currentTime, 1000, + 'Setting the current time succeeds'); + assert_equals(animation.startTime, null, + 'Start time remains null after setting current time'); + + animation.startTime = 1000; + assert_equals(animation.startTime, 1000, + 'Setting the start time succeeds'); + assert_equals(animation.currentTime, null, + 'Setting the start time clears the current time'); + + animation.startTime = null; + assert_equals(animation.startTime, null, + 'Setting the start time to an unresolved time succeeds'); + assert_equals(animation.currentTime, null, 'The current time is unaffected'); + +}, 'Setting the start time of an animation without an active timeline'); + +test(function(t) +{ + // Setting an unresolved start time on an animation without an active + // timeline should not clear the current time. + + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + + assert_equals(animation.currentTime, null, 'Intial current time'); + assert_equals(animation.startTime, null, 'Intial start time'); + + animation.currentTime = 1000; + assert_equals(animation.currentTime, 1000, + 'Setting the current time succeeds'); + assert_equals(animation.startTime, null, + 'Start time remains null after setting current time'); + + animation.startTime = null; + assert_equals(animation.startTime, null, 'Start time remains unresolved'); + assert_equals(animation.currentTime, 1000, 'Current time is unaffected'); + +}, 'Setting an unresolved start time an animation without an active timeline' + + ' does not clear the current time'); + +test(function(t) +{ + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + // So long as a hold time is set, querying the current time will return + // the hold time. + + // Since the start time is unresolved at this point, setting the current time + // will set the hold time + animation.currentTime = 1000; + assert_equals(animation.currentTime, 1000, + 'The current time is calculated from the hold time'); + + // If we set the start time, however, we should clear the hold time. + animation.startTime = document.timeline.currentTime - 2000; + assert_times_equal(animation.currentTime, 2000, + 'The current time is calculated from the start time,' + + ' not the hold time'); + + // Sanity check + assert_equals(animation.playState, 'running', + 'Animation reports it is running after setting a resolved' + + ' start time'); +}, 'Setting the start time clears the hold time'); + +test(function(t) +{ + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + // Set up a running animation (i.e. both start time and current time + // are resolved). + animation.startTime = document.timeline.currentTime - 1000; + assert_equals(animation.playState, 'running'); + assert_times_equal(animation.currentTime, 1000, + 'Current time is resolved for a running animation') + + // Clear start time + animation.startTime = null; + assert_times_equal(animation.currentTime, 1000, + 'Hold time is set after start time is made unresolved'); + assert_equals(animation.playState, 'paused', + 'Animation reports it is paused after setting an unresolved' + + ' start time'); +}, 'Setting an unresolved start time sets the hold time'); + +promise_test(function(t) +{ + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + var readyPromiseCallbackCalled = false; + animation.ready.then(function() { readyPromiseCallbackCalled = true; } ); + + // Put the animation in the play-pending state + animation.play(); + + // Sanity check + assert_equals(animation.playState, 'pending', + 'Animation is in play-pending state'); + + // Setting the start time should resolve the 'ready' promise, i.e. + // it should schedule a microtask to run the promise callbacks. + animation.startTime = document.timeline.currentTime; + assert_false(readyPromiseCallbackCalled, + 'Ready promise callback is not called synchronously'); + + // If we schedule another microtask then it should run immediately after + // the ready promise resolution microtask. + return Promise.resolve().then(function() { + assert_true(readyPromiseCallbackCalled, + 'Ready promise callback called after setting startTime'); + }); +}, 'Setting the start time resolves a pending ready promise'); + +promise_test(function(t) +{ + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + var readyPromiseCallbackCalled = false; + animation.ready.then(function() { readyPromiseCallbackCalled = true; } ); + + // Put the animation in the pause-pending state + animation.startTime = document.timeline.currentTime; + animation.pause(); + + // Sanity check + assert_equals(animation.playState, 'pending', + 'Animation is in pause-pending state'); + + // Setting the start time should resolve the 'ready' promise although + // the resolution callbacks when be run in a separate microtask. + animation.startTime = null; + assert_false(readyPromiseCallbackCalled, + 'Ready promise callback is not called synchronously'); + + return Promise.resolve().then(function() { + assert_true(readyPromiseCallbackCalled, + 'Ready promise callback called after setting startTime'); + }); +}, 'Setting the start time resolves a pending pause task'); + +promise_test(function(t) +{ + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + + // Set start time such that the current time is past the end time + animation.startTime = document.timeline.currentTime + - 110 * MS_PER_SEC; + assert_equals(animation.playState, 'finished', + 'Seeked to finished state using the startTime'); + + // If the 'did seek' flag is true, the current time should be greater than + // the effect end. + assert_greater_than(animation.currentTime, + animation.effect.getComputedTiming().endTime, + 'Setting the start time updated the finished state with' + + ' the \'did seek\' flag set to true'); + + // Furthermore, that time should persist if we have correctly updated + // the hold time + var finishedCurrentTime = animation.currentTime; + return waitForAnimationFrames(1).then(function() { + assert_equals(animation.currentTime, finishedCurrentTime, + 'Current time does not change after seeking past the effect' + + ' end time by setting the current time'); + }); +}, 'Setting the start time updates the finished state'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html new file mode 100644 index 000000000..4c51f0141 --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Setting the target effect tests</title> +<link rel='help' href='https://w3c.github.io/web-animations/#setting-the-target-effect'> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src='../../testcommon.js'></script> +<body> +<div id='log'></div> +<script> +'use strict'; + +promise_test(function(t) { + var anim = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] }, + 100 * MS_PER_SEC); + assert_equals(anim.playState, 'pending'); + + var retPromise = anim.ready.then(function() { + assert_unreached('ready promise is fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + + anim.effect = null; + assert_equals(anim.playState, 'paused'); + + return retPromise; +}, 'If new effect is null and old effect is not null, we reset the pending ' + + 'tasks and ready promise is rejected'); + +promise_test(function(t) { + var anim = new Animation(); + anim.pause(); + assert_equals(anim.playState, 'pending'); + + anim.effect = new KeyframeEffectReadOnly(createDiv(t), + { marginLeft: [ '0px', '100px' ] }, + 100 * MS_PER_SEC); + assert_equals(anim.playState, 'pending'); + + return anim.ready.then(function() { + assert_equals(anim.playState, 'paused'); + }); +}, 'If animation has a pending pause task, reschedule that task to run ' + + 'as soon as animation is ready.'); + +promise_test(function(t) { + var anim = new Animation(); + anim.play(); + assert_equals(anim.playState, 'pending'); + + anim.effect = new KeyframeEffectReadOnly(createDiv(t), + { marginLeft: [ '0px', '100px' ] }, + 100 * MS_PER_SEC); + assert_equals(anim.playState, 'pending'); + + return anim.ready.then(function() { + assert_equals(anim.playState, 'running'); + }); +}, 'If animation has a pending play task, reschedule that task to run ' + + 'as soon as animation is ready to play new effect.'); + +promise_test(function(t) { + var animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] }, + 100 * MS_PER_SEC); + var animB = new Animation(); + + return animA.ready.then(function() { + animB.effect = animA.effect; + assert_equals(animA.effect, null); + assert_equals(animA.playState, 'finished'); + }); +}, 'When setting the effect of an animation to the effect of an existing ' + + 'animation, the existing animation\'s target effect should be set to null.'); + +test(function(t) { + var animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] }, + 100 * MS_PER_SEC); + var animB = new Animation(); + var effect = animA.effect; + animA.currentTime = 50 * MS_PER_SEC; + animB.currentTime = 20 * MS_PER_SEC; + assert_equals(effect.getComputedTiming().progress, 0.5, + 'Original timing comes from first animation'); + animB.effect = effect; + assert_equals(effect.getComputedTiming().progress, 0.2, + 'After setting the effect on a different animation, ' + + 'it uses the new animation\'s timing'); +}, 'After setting the target effect of animation to the target effect of an ' + + 'existing animation, the target effect\'s timing is updated to reflect ' + + 'the current time of the new animation.'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html new file mode 100644 index 000000000..c540fe2ca --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html @@ -0,0 +1,276 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Setting the timeline tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#setting-the-timeline"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +// --------------------------------------------------------------------- +// +// Tests from no timeline to timeline +// +// --------------------------------------------------------------------- + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(animation.playState, 'paused'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'paused'); + assert_times_equal(animation.currentTime, 50 * MS_PER_SEC); +}, 'After setting timeline on paused animation it is still paused'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.currentTime = 200 * MS_PER_SEC; + assert_equals(animation.playState, 'paused'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'paused'); + assert_times_equal(animation.currentTime, 200 * MS_PER_SEC); +}, 'After setting timeline on animation paused outside active interval' + + ' it is still paused'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + assert_equals(animation.playState, 'idle'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'idle'); +}, 'After setting timeline on an idle animation without a start time' + + ' it is still idle'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.startTime = document.timeline.currentTime; + assert_equals(animation.playState, 'idle'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'running'); +}, 'After setting timeline on an idle animation with a start time' + + ' it is running'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC; + assert_equals(animation.playState, 'idle'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'finished'); +}, 'After setting timeline on an idle animation with a sufficiently ancient' + + ' start time it is finished'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.play(); + assert_equals(animation.playState, 'pending'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'pending'); +}, 'After setting timeline on a play-pending animation it is still pending'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.play(); + assert_equals(animation.playState, 'pending'); + + animation.timeline = document.timeline; + + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + }); +}, 'After setting timeline on a play-pending animation it begins playing' + + ' after pending'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.startTime = document.timeline.currentTime; + animation.pause(); + animation.timeline = null; + assert_equals(animation.playState, 'pending'); + + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'pending'); +}, 'After setting timeline on a pause-pending animation it is still pending'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + null); + animation.startTime = document.timeline.currentTime; + animation.pause(); + animation.timeline = null; + assert_equals(animation.playState, 'pending'); + + animation.timeline = document.timeline; + + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'After setting timeline on a pause-pending animation it becomes paused' + + ' after pending'); + +// --------------------------------------------------------------------- +// +// Tests from timeline to no timeline +// +// --------------------------------------------------------------------- + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(animation.playState, 'paused'); + + animation.timeline = null; + + assert_equals(animation.playState, 'paused'); + assert_times_equal(animation.currentTime, 50 * MS_PER_SEC); +}, 'After clearing timeline on paused animation it is still paused'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + var initialStartTime = document.timeline.currentTime - 200 * MS_PER_SEC; + animation.startTime = initialStartTime; + assert_equals(animation.playState, 'finished'); + + animation.timeline = null; + + assert_equals(animation.playState, 'idle'); + assert_times_equal(animation.startTime, initialStartTime); +}, 'After clearing timeline on finished animation it is idle'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + var initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC; + animation.startTime = initialStartTime; + assert_equals(animation.playState, 'running'); + + animation.timeline = null; + + assert_equals(animation.playState, 'idle'); + assert_times_equal(animation.startTime, initialStartTime); +}, 'After clearing timeline on running animation it is idle'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + assert_equals(animation.playState, 'idle'); + + animation.timeline = null; + + assert_equals(animation.playState, 'idle'); + assert_equals(animation.startTime, null); +}, 'After clearing timeline on idle animation it is still idle'); + +test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + assert_equals(animation.playState, 'pending'); + + animation.timeline = null; + + assert_equals(animation.playState, 'pending'); +}, 'After clearing timeline on play-pending animation it is still pending'); + +promise_test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + assert_equals(animation.playState, 'pending'); + + animation.timeline = null; + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + }); +}, 'After clearing and re-setting timeline on play-pending animation it' + + ' begins to play'); + +test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + animation.startTime = document.timeline.currentTime; + animation.pause(); + assert_equals(animation.playState, 'pending'); + + animation.timeline = null; + + assert_equals(animation.playState, 'pending'); +}, 'After clearing timeline on a pause-pending animation it is still pending'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + animation.startTime = document.timeline.currentTime; + animation.pause(); + assert_equals(animation.playState, 'pending'); + + animation.timeline = null; + animation.timeline = document.timeline; + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'After clearing and re-setting timeline on a pause-pending animation it' + + ' becomes paused'); + +promise_test(function(t) { + var animation = + new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), + document.timeline); + var initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC; + animation.startTime = initialStartTime; + animation.pause(); + animation.play(); + + animation.timeline = null; + animation.timeline = document.timeline; + assert_equals(animation.playState, 'pending'); + + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + assert_times_equal(animation.startTime, initialStartTime); + }); +}, 'After clearing and re-setting timeline on an animation in the middle of' + + ' an aborted pause, it continues playing using the same start time'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html b/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html new file mode 100644 index 000000000..0b77443f2 --- /dev/null +++ b/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html @@ -0,0 +1,331 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests for updating the finished state of an animation</title> +<link rel="help" href="https://w3c.github.io/web-animations/#updating-the-finished-state"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +// +// NOTE TO THE POOR PERSON WHO HAS TO MERGE THIS WITH THE TEST OF THE SAME +// NAME FROM BLINK +// +// There is a pull request from Blink at: +// +// https://github.com/w3c/web-platform-tests/pull/3328 +// +// which this file will surely conflict with. +// +// However, those tests cover a different part of the same algorithm. They +// are mostly concerned with testing events and promises rather than the +// timing part of the algorithm. +// +// The tests below cover the first part of the algorithm. So, please keep both +// sets of tests and delete this comment. Preferably put the tests in this +// file first. +// +// Thank you! +// + + +// CASE 1: playback rate > 0 and current time >= target effect end +// (Also the start time is resolved and there is pending task) + +// Did seek = false +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + + // Here and in the following tests we wait until ready resolves as + // otherwise we don't have a resolved start time. We test the case + // where the start time is unresolved in a subsequent test. + return anim.ready.then(function() { + // Seek to 1ms before the target end and then wait 1ms + anim.currentTime = 100 * MS_PER_SEC - 1; + return waitForAnimationFramesWithDelay(1); + }).then(function() { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is set to target end clamping current time'); + }); +}, 'Updating the finished state when playing past end'); + +// Did seek = true +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + return anim.ready.then(function() { + anim.currentTime = 200 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, 200 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); +}, 'Updating the finished state when seeking past end'); + +// Test current time == target end +// +// We can't really write a test for current time == target end with +// did seek = false since that would imply setting up an animation where +// the next animation frame time happens to exactly align with the target end. +// +// Fortunately, we don't need to test that case since even if the implementation +// fails to set the hold time on such a tick, it should be mostly unobservable +// (on the subsequent tick the hold time will be set to the same value anyway). + +// Did seek = true +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + return anim.ready.then(function() { + anim.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); +}, 'Updating the finished state when seeking exactly to end'); + + +// CASE 2: playback rate < 0 and current time <= 0 +// (Also the start time is resolved and there is pending task) + +// Did seek = false +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = -1; + anim.play(); // Make sure animation is not initially finished + return anim.ready.then(function() { + // Seek to 1ms before 0 and then wait 1ms + anim.currentTime = 1; + return waitForAnimationFramesWithDelay(1); + }).then(function() { + assert_equals(anim.currentTime, 0 * MS_PER_SEC, + 'Hold time is set to zero clamping current time'); + }); +}, 'Updating the finished state when playing in reverse past zero'); + +// Did seek = true +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = -1; + anim.play(); + return anim.ready.then(function() { + anim.currentTime = -100 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, -100 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); +}, 'Updating the finished state when seeking a reversed animation past zero'); + +// As before, it's difficult to test current time == 0 for did seek = false but +// it doesn't really matter. + +// Did seek = true +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = -1; + anim.play(); + return anim.ready.then(function() { + anim.currentTime = 0; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, 0 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); +}, 'Updating the finished state when seeking a reversed animation exactly' + + ' to zero'); + +// CASE 3: playback rate > 0 and current time < target end OR +// playback rate < 0 and current time > 0 +// (Also the start time is resolved and there is pending task) + +// Did seek = false; playback rate > 0 +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + + // We want to test that the hold time is cleared so first we need to + // put the animation in a state where the hold time is set. + anim.finish(); + return anim.ready.then(function() { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is initially set'); + // Then extend the duration so that the hold time is cleared and on + // the next tick the current time will increase. + anim.effect.timing.duration *= 2; + return waitForAnimationFrames(1); + }).then(function() { + assert_greater_than(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is not set so current time should increase'); + }); +}, 'Updating the finished state when playing before end'); + +// Did seek = true; playback rate > 0 +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.finish(); + return anim.ready.then(function() { + anim.currentTime = 50 * MS_PER_SEC; + // When did seek = true, updating the finished state: (i) updates + // the animation's start time and (ii) clears the hold time. + // We can test both by checking that the currentTime is initially + // updated and then increases. + assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); + return waitForAnimationFrames(1); + }).then(function() { + assert_greater_than(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time is not set so current time should increase'); + }); +}, 'Updating the finished state when seeking before end'); + +// Did seek = false; playback rate < 0 +// +// Unfortunately it is not possible to test this case. We need to have +// a hold time set, a resolved start time, and then perform some +// operation that updates the finished state with did seek set to true. +// +// However, the only situation where this could arrive is when we +// replace the timeline and that procedure is likely to change. For all +// other cases we either have an unresolved start time (e.g. when +// paused), we don't have a set hold time (e.g. regular playback), or +// the current time is zero (and anything that gets us out of that state +// will set did seek = true). + +// Did seek = true; playback rate < 0 +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = -1; + return anim.ready.then(function() { + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); + return waitForAnimationFrames(1); + }).then(function() { + assert_less_than(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time is not set so current time should decrease'); + }); +}, 'Updating the finished state when seeking a reversed animation before end'); + +// CASE 4: playback rate == 0 + +// current time < 0 +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = 0; + return anim.ready.then(function() { + anim.currentTime = -100 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, -100 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); +}, 'Updating the finished state when playback rate is zero and the' + + ' current time is less than zero'); + +// current time < target end +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = 0; + return anim.ready.then(function() { + anim.currentTime = 50 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); +}, 'Updating the finished state when playback rate is zero and the' + + ' current time is less than end'); + +// current time > target end +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.playbackRate = 0; + return anim.ready.then(function() { + anim.currentTime = 200 * MS_PER_SEC; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(anim.currentTime, 200 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); +}, 'Updating the finished state when playback rate is zero and the' + + ' current time is greater than end'); + +// CASE 5: current time unresolved + +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.cancel(); + // Trigger a change that will cause the "update the finished state" + // procedure to run. + anim.effect.timing.duration = 200 * MS_PER_SEC; + assert_equals(anim.currentTime, null, + 'The animation hold time / start time should not be updated'); + // The "update the finished state" procedure is supposed to run after any + // change to timing, but just in case an implementation defers that, let's + // wait a frame and check that the hold time / start time has still not been + // updated. + return waitForAnimationFrames(1).then(function() { + assert_equals(anim.currentTime, null, + 'The animation hold time / start time should not be updated'); + }); +}, 'Updating the finished state when current time is unresolved'); + +// CASE 6: has a pending task + +test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.cancel(); + anim.currentTime = 75 * MS_PER_SEC; + anim.play(); + // We now have a pending task and a resolved current time. + // + // In the next step we will adjust the timing so that the current time + // is greater than the target end. At this point the "update the finished + // state" procedure should run and if we fail to check for a pending task + // we will set the hold time to the target end, i.e. 50ms. + anim.effect.timing.duration = 50 * MS_PER_SEC; + assert_equals(anim.currentTime, 75 * MS_PER_SEC, + 'Hold time should not be updated'); +}, 'Updating the finished state when there is a pending task'); + +// CASE 7: start time unresolved + +// Did seek = false +promise_test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.cancel(); + // Make it so that only the start time is unresolved (to avoid overlapping + // with the test case where current time is unresolved) + anim.currentTime = 150 * MS_PER_SEC; + // Trigger a change that will cause the "update the finished state" + // procedure to run (did seek = false). + anim.effect.timing.duration = 200 * MS_PER_SEC; + return waitForAnimationFrames(1).then(function() { + assert_equals(anim.currentTime, 150 * MS_PER_SEC, + 'The animation hold time should not be updated'); + assert_equals(anim.startTime, null, + 'The animation start time should not be updated'); + }); +}, 'Updating the finished state when start time is unresolved and' + + ' did seek = false'); + +// Did seek = true +test(function(t) { + var anim = createDiv(t).animate(null, 100 * MS_PER_SEC); + anim.cancel(); + anim.currentTime = 150 * MS_PER_SEC; + // Trigger a change that will cause the "update the finished state" + // procedure to run. + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(anim.currentTime, 50 * MS_PER_SEC, + 'The animation hold time should not be updated'); + assert_equals(anim.startTime, null, + 'The animation start time should not be updated'); +}, 'Updating the finished state when start time is unresolved and' + + ' did seek = true'); + +</script> +</body> |