| <!doctype html> |
| <meta charset=utf-8> |
| <title>KeyframeEffect 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 KeyframeEffect interface does not |
| // produce style change events. |
| // |
| // There are two types of tests: |
| // |
| // MakeInEffectTest |
| // |
| // For properties that are able to cause the KeyframeEffect to start |
| // affecting the CSS 'opacity' property. |
| // |
| // This function takes either: |
| // |
| // (a) A function that makes the passed-in KeyframeEffect affect the |
| // 'opacity' property. |
| // |
| // (b) An object with the following format: |
| // |
| // { |
| // setup: elem => { /* return Animation */ } |
| // test: effect => { /* make |effect| affect 'opacity' */ } |
| // } |
| // |
| // If the latter form is used, the setup function should return an Animation |
| // whose KeyframeEffect does NOT (yet) affect the 'opacity' property (or is |
| // NOT yet in-effect). 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). |
| // |
| // UsePropertyTest |
| // |
| // For properties that cannot cause the KeyframeEffect to start affecting the |
| // CSS 'opacity' property. |
| // |
| // The shape of the parameter to the UsePropertyTest is identical to the |
| // MakeInEffectTest. The only difference is that the function (or 'test' |
| // function of the object format is used) does not need to make the |
| // KeyframeEffect affect the CSS 'opacity' property, but simply needs to |
| // get/set the property under test. |
| |
| const MakeInEffectTest = testFuncOrObj => { |
| let test, setup; |
| |
| if (typeof testFuncOrObj === 'function') { |
| test = testFuncOrObj; |
| } else { |
| test = testFuncOrObj.test; |
| if (typeof testFuncOrObj.setup === 'function') { |
| setup = testFuncOrObj.setup; |
| } |
| } |
| |
| if (!setup) { |
| setup = elem => |
| elem.animate({ color: ['blue', 'green'] }, 100 * MS_PER_SEC); |
| } |
| |
| return { test, setup }; |
| }; |
| |
| const UsePropertyTest = testFuncOrObj => { |
| const { test, setup } = MakeInEffectTest(testFuncOrObj); |
| |
| let coveringAnimation; |
| return { |
| setup: elem => { |
| coveringAnimation = new Animation( |
| new KeyframeEffect(elem, { opacity: [0, 1] }, 100 * MS_PER_SEC) |
| ); |
| |
| return setup(elem); |
| }, |
| test: effect => { |
| test(effect); |
| coveringAnimation.play(); |
| }, |
| }; |
| }; |
| |
| const tests = { |
| getTiming: UsePropertyTest(effect => effect.getTiming()), |
| getComputedTiming: UsePropertyTest(effect => effect.getComputedTiming()), |
| updateTiming: MakeInEffectTest({ |
| // Initially put the effect in its before phase (with no fill mode)... |
| setup: elem => |
| elem.animate( |
| { opacity: [0.5, 1] }, |
| { |
| duration: 100 * MS_PER_SEC, |
| delay: 100 * MS_PER_SEC, |
| } |
| ), |
| // ... so that when the delay is removed, it begins to affect the opacity. |
| test: effect => { |
| effect.updateTiming({ delay: 0 }); |
| }, |
| }), |
| get target() { |
| let targetElem; |
| return MakeInEffectTest({ |
| setup: (elem, t) => { |
| targetElem = elem; |
| const targetB = createDiv(t); |
| return targetB.animate({ opacity: [0.5, 1] }, 100 * MS_PER_SEC); |
| }, |
| test: effect => { |
| effect.target = targetElem; |
| }, |
| }); |
| }, |
| pseudoElement: MakeInEffectTest({ |
| setup: elem => elem.animate( |
| {opacity: [0.5, 1]}, |
| {duration: 100 * MS_PER_SEC, pseudoElement: '::before'} |
| ), |
| test: effect => { |
| effect.pseudoElement = null; |
| }, |
| }), |
| iterationComposite: UsePropertyTest(effect => { |
| // Get iterationComposite |
| effect.iterationComposite; |
| |
| // Set iterationComposite |
| effect.iterationComposite = 'accumulate'; |
| }), |
| composite: UsePropertyTest(effect => { |
| // Get composite |
| effect.composite; |
| |
| // Set composite |
| effect.composite = 'add'; |
| }), |
| getKeyframes: UsePropertyTest(effect => effect.getKeyframes()), |
| setKeyframes: MakeInEffectTest(effect => |
| effect.setKeyframes({ opacity: [0.5, 1] }) |
| ), |
| get ['KeyframeEffect constructor']() { |
| let originalElem; |
| let animation; |
| return UsePropertyTest({ |
| setup: elem => { |
| originalElem = elem; |
| // Return a dummy animation so the caller has something to wait on |
| return elem.animate(null); |
| }, |
| test: () => |
| new KeyframeEffect( |
| originalElem, |
| { opacity: [0.5, 1] }, |
| 100 * MS_PER_SEC |
| ), |
| }); |
| }, |
| get ['KeyframeEffect copy constructor']() { |
| let effectToClone; |
| return UsePropertyTest({ |
| setup: elem => { |
| effectToClone = new KeyframeEffect( |
| elem, |
| { opacity: [0.5, 1] }, |
| 100 * MS_PER_SEC |
| ); |
| // Return a dummy animation so the caller has something to wait on |
| return elem.animate(null); |
| }, |
| test: () => new KeyframeEffect(effectToClone), |
| }); |
| }, |
| }; |
| |
| // Check that each enumerable property and the constructors follow the |
| // expected behavior with regards to triggering style change events. |
| const properties = [ |
| ...Object.keys(AnimationEffect.prototype), |
| ...Object.keys(KeyframeEffect.prototype), |
| 'KeyframeEffect constructor', |
| 'KeyframeEffect copy constructor', |
| ]; |
| |
| test(() => { |
| for (const property of Object.keys(tests)) { |
| assert_in_array( |
| property, |
| properties, |
| `Test property '${property}' should be one of the properties on ` + |
| ' KeyframeEffect' |
| ); |
| } |
| }, '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 } = tests[key]; |
| |
| // Setup target element |
| const div = createDiv(t); |
| let gotTransition = false; |
| div.addEventListener('transitionrun', () => { |
| gotTransition = true; |
| }); |
| |
| // Setup animation |
| const animation = setup(div, t); |
| |
| // 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.effect); |
| |
| // 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 one animation |
| // frame to give the transitionrun event a chance to be dispatched. |
| await animation.ready; |
| await waitForAnimationFrames(1); |
| |
| assert_false(gotTransition, 'A transition should NOT have been triggered'); |
| }, `KeyframeEffect.${key} does NOT trigger a style change event`); |
| } |
| </script> |
| </body> |