diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html')
-rw-r--r-- | testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html new file mode 100644 index 0000000000..b41f748720 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html @@ -0,0 +1,371 @@ +<!doctype html> +<meta charset=utf-8> +<title>Animation interface: style change events</title> +<link rel="help" + href="https://drafts.csswg.org/web-animations-1/#model-liveness"> +<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 that each property defined in the Animation interface behaves as +// expected with regards to whether or not it produces style change events. +// +// There are two types of tests: +// +// PlayAnimationTest +// +// For properties that are able to cause the Animation to start affecting +// the target CSS property. +// +// This function takes either: +// +// (a) A function that simply "plays" that passed-in Animation (i.e. makes +// it start affecting the target CSS property. +// +// (b) An object with the following format: +// +// { +// setup: elem => { /* return Animation */ }, +// test: animation => { /* play |animation| */ }, +// shouldFlush: boolean /* optional, defaults to false */ +// } +// +// If the latter form is used, the setup function should return an Animation +// that does NOT (yet) have an in-effect AnimationEffect that affects the +// 'opacity' property. Otherwise, the transition we use to detect if a style +// change event has occurred will never have a chance to be triggered (since +// the animated style will clobber both before-change and after-change +// style). +// +// Examples of valid animations: +// +// - An animation that is idle, or finished but without a fill mode. +// - An animation with an effect that that does not affect opacity. +// +// UsePropertyTest +// +// For properties that cannot cause the Animation to start affecting the +// target CSS property. +// +// The shape of the parameter to the UsePropertyTest is identical to the +// PlayAnimationTest. The only difference is that the function (or 'test' +// function of the object format is used) does not need to play the +// animation, but simply needs to get/set the property under test. + +const PlayAnimationTest = testFuncOrObj => { + let test, setup, shouldFlush; + + if (typeof testFuncOrObj === 'function') { + test = testFuncOrObj; + shouldFlush = false; + } else { + test = testFuncOrObj.test; + if (typeof testFuncOrObj.setup === 'function') { + setup = testFuncOrObj.setup; + } + shouldFlush = !!testFuncOrObj.shouldFlush; + } + + if (!setup) { + setup = elem => + new Animation( + new KeyframeEffect(elem, { opacity: [0, 1] }, 100 * MS_PER_SEC) + ); + } + + return { test, setup, shouldFlush }; +}; + +const UsePropertyTest = testFuncOrObj => { + const { setup, test, shouldFlush } = PlayAnimationTest(testFuncOrObj); + + let coveringAnimation; + return { + setup: elem => { + coveringAnimation = new Animation( + new KeyframeEffect(elem, { opacity: [0, 1] }, 100 * MS_PER_SEC) + ); + + return setup(elem); + }, + test: animation => { + test(animation); + coveringAnimation.play(); + }, + shouldFlush, + }; +}; + +const tests = { + id: UsePropertyTest(animation => (animation.id = 'yer')), + get effect() { + let effect; + return PlayAnimationTest({ + setup: elem => { + // Create a new effect and animation but don't associate them yet + effect = new KeyframeEffect( + elem, + { opacity: [0.5, 1] }, + 100 * MS_PER_SEC + ); + return elem.animate(null, 100 * MS_PER_SEC); + }, + test: animation => { + // Read the effect + animation.effect; + + // Assign the effect + animation.effect = effect; + }, + }); + }, + timeline: PlayAnimationTest({ + setup: elem => { + // Create a new animation with no timeline + const animation = new Animation( + new KeyframeEffect(elem, { opacity: [0.5, 1] }, 100 * MS_PER_SEC), + null + ); + // Set the hold time so that once we assign a timeline it will begin to + // play. + animation.currentTime = 0; + + return animation; + }, + test: animation => { + // Get the timeline + animation.timeline; + + // Play the animation by setting the timeline + animation.timeline = document.timeline; + }, + }), + startTime: PlayAnimationTest(animation => { + // Get the startTime + animation.startTime; + + // Play the animation by setting the startTime + animation.startTime = document.timeline.currentTime; + }), + currentTime: PlayAnimationTest(animation => { + // Get the currentTime + animation.currentTime; + + // Play the animation by setting the currentTime + animation.currentTime = 0; + }), + playbackRate: UsePropertyTest(animation => { + // Get and set the playbackRate + animation.playbackRate = animation.playbackRate * 1.1; + }), + playState: UsePropertyTest(animation => animation.playState), + pending: UsePropertyTest(animation => animation.pending), + replaceState: UsePropertyTest(animation => animation.replaceState), + ready: UsePropertyTest(animation => animation.ready), + finished: UsePropertyTest(animation => { + // Get the finished Promise + animation.finished; + }), + onfinish: UsePropertyTest(animation => { + // Get the onfinish member + animation.onfinish; + + // Set the onfinish menber + animation.onfinish = () => {}; + }), + onremove: UsePropertyTest(animation => { + // Get the onremove member + animation.onremove; + + // Set the onremove menber + animation.onremove = () => {}; + }), + oncancel: UsePropertyTest(animation => { + // Get the oncancel member + animation.oncancel; + + // Set the oncancel menber + animation.oncancel = () => {}; + }), + cancel: UsePropertyTest({ + // Animate _something_ just to make the test more interesting + setup: elem => elem.animate({ color: ['green', 'blue'] }, 100 * MS_PER_SEC), + test: animation => { + animation.cancel(); + }, + }), + finish: PlayAnimationTest({ + setup: elem => + new Animation( + new KeyframeEffect( + elem, + { opacity: [0.5, 1] }, + { + duration: 100 * MS_PER_SEC, + fill: 'both', + } + ) + ), + test: animation => { + animation.finish(); + }, + }), + play: PlayAnimationTest(animation => animation.play()), + pause: PlayAnimationTest(animation => { + // Pause animation -- this will cause the animation to transition from the + // 'idle' state to the 'paused' (but pending) state with hold time zero. + animation.pause(); + }), + updatePlaybackRate: UsePropertyTest(animation => { + animation.updatePlaybackRate(1.1); + }), + // We would like to use a PlayAnimationTest here but reverse() is async and + // doesn't start applying its result until the animation is ready. + reverse: UsePropertyTest({ + setup: elem => { + // Create a new animation and seek it to the end so that it no longer + // affects style (since it has no fill mode). + const animation = elem.animate({ opacity: [0.5, 1] }, 100 * MS_PER_SEC); + animation.finish(); + return animation; + }, + test: animation => { + animation.reverse(); + }, + }), + persist: PlayAnimationTest({ + setup: async elem => { + // Create an animation whose replaceState is 'removed'. + const animA = elem.animate( + { opacity: 1 }, + { duration: 1, fill: 'forwards' } + ); + const animB = elem.animate( + { opacity: 1 }, + { duration: 1, fill: 'forwards' } + ); + await animA.finished; + animB.cancel(); + + return animA; + }, + test: animation => { + animation.persist(); + }, + }), + commitStyles: PlayAnimationTest({ + setup: async elem => { + // Create an animation whose replaceState is 'removed'. + const animA = elem.animate( + // It's important to use opacity of '1' here otherwise we'll create a + // transition due to updating the specified style whereas the transition + // we want to detect is the one from flushing due to calling + // commitStyles. + { opacity: 1 }, + { duration: 1, fill: 'forwards' } + ); + const animB = elem.animate( + { opacity: 1 }, + { duration: 1, fill: 'forwards' } + ); + await animA.finished; + animB.cancel(); + + return animA; + }, + test: animation => { + animation.commitStyles(); + }, + shouldFlush: true, + }), + get ['Animation constructor']() { + let originalElem; + return UsePropertyTest({ + setup: elem => { + originalElem = elem; + // Return a dummy animation so the caller has something to wait on + return elem.animate(null); + }, + test: () => + new Animation( + new KeyframeEffect( + originalElem, + { opacity: [0.5, 1] }, + 100 * MS_PER_SEC + ) + ), + }); + }, +}; + +// Check that each enumerable property and the constructor follow the +// expected behavior with regards to triggering style change events. +const properties = [ + ...Object.keys(Animation.prototype), + 'Animation constructor', +]; + +test(() => { + for (const property of Object.keys(tests)) { + assert_in_array( + property, + properties, + `Test property '${property}' should be one of the properties on ` + + ' Animation' + ); + } +}, 'All property keys are recognized'); + +for (const key of properties) { + promise_test(async t => { + assert_own_property(tests, key, `Should have a test for '${key}' property`); + const { setup, test, shouldFlush } = tests[key]; + + // Setup target element + const div = createDiv(t); + let gotTransition = false; + div.addEventListener('transitionrun', () => { + gotTransition = true; + }); + + // Setup animation + const animation = await setup(div); + + // Setup transition start point + div.style.transition = 'opacity 100s'; + getComputedStyle(div).opacity; + + // Update specified style but don't flush + div.style.opacity = '0.5'; + + // Trigger the property + test(animation); + + // If the test function produced a style change event it will have triggered + // a transition. + + // Wait for the animation to start and then for at least two animation + // frames to give the transitionrun event a chance to be dispatched. + assert_true( + typeof animation.ready !== 'undefined', + 'Should have a valid animation to wait on' + ); + await animation.ready; + await waitForAnimationFrames(2); + + if (shouldFlush) { + assert_true(gotTransition, 'A transition should have been triggered'); + } else { + assert_false( + gotTransition, + 'A transition should NOT have been triggered' + ); + } + }, `Animation.${key} produces expected style change events`); +} +</script> +</body> |