<!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>