Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!doctype html>
<meta charset=utf-8>
<title>KeyframeEffect interface: style change events</title>
<link rel="help"
<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>