diff options
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html')
-rw-r--r-- | testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html new file mode 100644 index 0000000000..271a47b301 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html @@ -0,0 +1,602 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Processing a keyframes argument (property access)</title> +<link rel="help" href="https://drafts.csswg.org/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'; + +// This file only tests the KeyframeEffect 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 + +const 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', + 'contain', + 'direction', + 'display', + 'textCombineUpright', + 'textOrientation', + 'unicodeBidi', + 'willChange', + 'writingMode', + + 'unsupportedProperty', + + 'float', // We use the string "cssFloat" to represent "float" property, and + // so reject "float" in the keyframe-like object. + 'font-size', // Supported property that uses dashes +]; + +function TestKeyframe(testProp) { + let _propAccessCount = 0; + + Object.defineProperty(this, testProp, { + get: () => { _propAccessCount++; }, + enumerable: true, + }); + + Object.defineProperty(this, 'propAccessCount', { + get: () => _propAccessCount + }); +} + +function GetTestKeyframeSequence(testProp) { + return [ new TestKeyframe(testProp) ] +} + +for (const prop of gNonAnimatableProps) { + test(() => { + const testKeyframe = new TestKeyframe(prop); + + new KeyframeEffect(null, testKeyframe); + + assert_equals(testKeyframe.propAccessCount, 0, 'Accessor not called'); + }, `non-animatable property '${prop}' is not accessed when using` + + ' a property-indexed keyframe object'); +} + +for (const prop of gNonAnimatableProps) { + test(() => { + const testKeyframes = GetTestKeyframeSequence(prop); + + new KeyframeEffect(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) { + const processedKeyframesA = + new KeyframeEffect(null, keyframesA).getKeyframes(); + const processedKeyframesB = + new KeyframeEffect(null, keyframesB).getKeyframes(); + assert_frame_lists_equal(processedKeyframesA, processedKeyframesB); +} + +const 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 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' }, + ], + }, +]; + +for (const {description, indexedKeyframes, sequencedKeyframes} of + gEquivalentSyntaxTests) { + test(() => { + assertEquivalentKeyframeSyntax(indexedKeyframes, sequencedKeyframes); + }, `Equivalent property-indexed and sequenced keyframes: ${description}`); +} + +// Test handling of custom iterable objects. + +function createIterable(iterations) { + return { + [Symbol.iterator]() { + let i = 0; + return { + next() { + return iterations[i++]; + }, + }; + }, + }; +} + +test(() => { + const 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', + composite: 'auto', + }, + { + offset: null, + computedOffset: 0.5, + easing: 'linear', + left: '300px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + left: '200px', + composite: 'auto', + }, + ]); +}, 'Keyframes are read from a custom iterator'); + +test(() => { + const 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'; + const effect = new KeyframeEffect(null, keyframes); + assert_frame_lists_equal(effect.getKeyframes(), [ + { + offset: null, + computedOffset: 0, + easing: 'linear', + left: '100px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 0.5, + easing: 'linear', + left: '300px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + left: '200px', + composite: 'auto', + }, + ]); +}, '\'easing\' and \'offset\' are ignored on iterable objects'); + +test(() => { + const 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', + composite: 'auto', + }, + { + offset: null, + computedOffset: 0.5, + easing: 'linear', + left: '300px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + left: '200px', + top: '100px', + composite: 'auto', + }, + ]); +}, 'Keyframes are read from a custom iterator with multiple properties' + + ' specified'); + +test(() => { + const 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', + composite: 'auto', + }, + { + offset: 0.75, + computedOffset: 0.75, + easing: 'linear', + left: '250px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + left: '200px', + composite: 'auto', + }, + ]); +}, 'Keyframes are read from a custom iterator with where an offset is' + + ' specified'); + +test(() => { + const test_error = { name: 'test' }; + const bad_keyframe = { get left() { throw test_error; } }; + assert_throws_exactly(test_error, () => { + new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px' } }, + { done: false, value: bad_keyframe }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + }); +}, 'If a keyframe throws for an animatable property, that exception should be' + + ' propagated'); + +test(() => { + assert_throws_js(TypeError, () => { + new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px' } }, + { done: false, value: 1234 }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + }); +}, 'Reading from a custom iterator that returns a non-object keyframe' + + ' should throw'); + +test(() => { + assert_throws_js(TypeError, () => { + new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px', easing: '' } }, + { done: false, value: 1234 }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + }); +}, 'Reading from a custom iterator that returns a non-object keyframe' + + ' and an invalid easing should throw'); + +test(() => { + assert_throws_js(TypeError, () => { + new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px' } }, + { done: false, value: { left: '150px', offset: 'o' } }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + }); +}, 'Reading from a custom iterator that returns a keyframe with a non finite' + + ' floating-point offset value should throw'); + +test(() => { + assert_throws_js(TypeError, () => { + new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px', easing: '' } }, + { done: false, value: { left: '150px', offset: 'o' } }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + }); +}, 'Reading from a custom iterator that returns a keyframe with a non finite' + + ' floating-point offset value and an invalid easing should throw'); + +test(() => { + const effect = new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px' } }, + { done: false }, // No value member; keyframe is undefined. + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: 'auto' }, + { offset: null, computedOffset: 0.5, easing: 'linear', composite: 'auto' }, + { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: 'auto' }, + ]); +}, 'An undefined keyframe returned from a custom iterator should be treated as a' + + ' default keyframe'); + +test(() => { + const effect = new KeyframeEffect(null, createIterable([ + { done: false, value: { left: '100px' } }, + { done: false, value: null }, + { done: false, value: { left: '200px' } }, + { done: true }, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: 'auto' }, + { offset: null, computedOffset: 0.5, easing: 'linear', composite: 'auto' }, + { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: 'auto' }, + ]); +}, 'A null keyframe returned from a custom iterator should be treated as a' + + ' default keyframe'); + +test(() => { + const 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', composite: 'auto' } + ]); +}, 'A list of values returned from a custom iterator should be ignored'); + +test(() => { + const test_error = { name: 'test' }; + const keyframe_obj = { + [Symbol.iterator]() { + return { next() { throw test_error; } }; + }, + }; + assert_throws_exactly(test_error, () => { + new KeyframeEffect(null, keyframe_obj); + }); +}, 'If a custom iterator throws from next(), the exception should be rethrown'); + +// Test handling of invalid Symbol.iterator + +test(() => { + const test_error = { name: 'test' }; + const keyframe_obj = { + [Symbol.iterator]() { + throw test_error; + }, + }; + assert_throws_exactly(test_error, () => { + new KeyframeEffect(null, keyframe_obj); + }); +}, 'Accessing a Symbol.iterator property that throws should rethrow'); + +test(() => { + const keyframe_obj = { + [Symbol.iterator]() { + return 42; // Not an object. + }, + }; + assert_throws_js(TypeError, () => { + new KeyframeEffect(null, keyframe_obj); + }); +}, 'A non-object returned from the Symbol.iterator property should cause a' + + ' TypeError to be thrown'); + +test(() => { + const 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'); + + const effect = new KeyframeEffect(null, [keyframe, { height: '200px' }]); + + assert_frame_lists_equal(effect.getKeyframes(), [ + { + offset: null, + computedOffset: 0, + easing: 'linear', + height: '100px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + height: '200px', + composite: 'auto', + }, + ]); +}, 'Only enumerable properties on keyframes are read'); + +test(() => { + const KeyframeParent = function() { this.width = '100px'; }; + KeyframeParent.prototype = { height: '100px' }; + const Keyframe = function() { this.top = '100px'; }; + Keyframe.prototype = Object.create(KeyframeParent.prototype); + Object.defineProperty(Keyframe.prototype, 'left', { + value: '100px', + enumerable: true, + }); + const keyframe = new Keyframe(); + + const effect = new KeyframeEffect(null, [keyframe, { top: '200px' }]); + + assert_frame_lists_equal(effect.getKeyframes(), [ + { + offset: null, + computedOffset: 0, + easing: 'linear', + top: '100px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + top: '200px', + composite: 'auto', + }, + ]); +}, 'Only properties defined directly on keyframes are read'); + +test(() => { + const keyframes = {}; + Object.defineProperty(keyframes, 'width', ['100px', '200px']); + Object.defineProperty(keyframes, 'height', { + value: ['100px', '200px'], + enumerable: true, + }); + + const effect = new KeyframeEffect(null, keyframes); + + assert_frame_lists_equal(effect.getKeyframes(), [ + { + offset: null, + computedOffset: 0, + easing: 'linear', + height: '100px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + height: '200px', + composite: 'auto', + }, + ]); +}, 'Only enumerable properties on property-indexed keyframes are read'); + +test(() => { + const KeyframesParent = function() { this.width = '100px'; }; + KeyframesParent.prototype = { height: '100px' }; + const Keyframes = function() { this.top = ['100px', '200px']; }; + Keyframes.prototype = Object.create(KeyframesParent.prototype); + Object.defineProperty(Keyframes.prototype, 'left', { + value: ['100px', '200px'], + enumerable: true, + }); + const keyframes = new Keyframes(); + + const effect = new KeyframeEffect(null, keyframes); + + assert_frame_lists_equal(effect.getKeyframes(), [ + { + offset: null, + computedOffset: 0, + easing: 'linear', + top: '100px', + composite: 'auto', + }, + { + offset: null, + computedOffset: 1, + easing: 'linear', + top: '200px', + composite: 'auto', + }, + ]); +}, 'Only properties defined directly on property-indexed keyframes are read'); + +test(() => { + const expectedOrder = ['composite', 'easing', 'offset', 'left', 'marginLeft']; + const actualOrder = []; + const kf1 = {}; + for (const {prop, value} of [{ prop: 'marginLeft', value: '10px' }, + { prop: 'left', value: '20px' }, + { prop: 'offset', value: '0' }, + { prop: 'easing', value: 'linear' }, + { prop: 'composite', value: 'replace' }]) { + Object.defineProperty(kf1, prop, { + enumerable: true, + get: () => { actualOrder.push(prop); return value; } + }); + } + const kf2 = { marginLeft: '10px', left: '20px', offset: 1 }; + + new KeyframeEffect(target, [kf1, kf2]); + + assert_array_equals(actualOrder, expectedOrder, 'property access order'); +}, 'Properties are read in ascending order by Unicode codepoint'); + +</script> |