mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-31 21:29:06 +00:00
LibWeb: Account for non-shorthand sub-properties when serializing border
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
When parsing values in `process_a_keyframes_argument` we don't expand properties using `StyleComputer::for_each_property_expanding_shorthands` unlike most other places - this means that if we parse a `border` we end up with the `border`'s sub-properties (`border-width`, `border-style`, `border-color`) still in their unexpanded forms (`CSSKeywordValue`, `LengthStyleValue`, `StyleValueList`, etc) rather than `ShorthandStyleValue`s which causes a crash when serializing the `border` value in `KeyframeEffect::get_keyframes`. The proper fix here is to parse `border`'s sub-properties directly to `ShorthandStyleValue`s instead of relying on `StyleComputer::for_each_property_expanding_shorthand` to do this conversion for us but this may be a while off. This commit also imports the previously crashing tests.
This commit is contained in:
parent
52e9dcd911
commit
a1c9b86ad3
Notes:
github-actions[bot]
2025-07-16 05:50:48 +00:00
Author: https://github.com/Calme1709
Commit: a1c9b86ad3
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5467
Reviewed-by: https://github.com/AtkinsSJ ✅
13 changed files with 2497 additions and 0 deletions
|
@ -241,6 +241,11 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
|
||||||
case PropertyID::Border: {
|
case PropertyID::Border: {
|
||||||
auto all_longhands_same_value = [](ValueComparingRefPtr<CSSStyleValue const> const& shorthand) -> bool {
|
auto all_longhands_same_value = [](ValueComparingRefPtr<CSSStyleValue const> const& shorthand) -> bool {
|
||||||
VERIFY(shorthand);
|
VERIFY(shorthand);
|
||||||
|
|
||||||
|
// FIXME: This can be removed once we parse border-width, border-style and border-color directly to ShorthandStyleValue
|
||||||
|
if (!shorthand->is_shorthand())
|
||||||
|
return !shorthand->is_value_list();
|
||||||
|
|
||||||
VERIFY(shorthand->is_shorthand());
|
VERIFY(shorthand->is_shorthand());
|
||||||
|
|
||||||
auto longhands = shorthand->as_shorthand().values();
|
auto longhands = shorthand->as_shorthand().values();
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 153 tests
|
||||||
|
|
||||||
|
139 Pass
|
||||||
|
14 Fail
|
||||||
|
Pass Element.animate() creates an Animation object
|
||||||
|
Pass Element.animate() creates an Animation object in the relevant realm of the target element
|
||||||
|
Pass Element.animate() creates an Animation object with a KeyframeEffect
|
||||||
|
Pass Element.animate() creates an Animation object with a KeyframeEffect that is created in the relevant realm of the target element
|
||||||
|
Pass Element.animate() accepts empty keyframe lists (input: [])
|
||||||
|
Pass Element.animate() accepts empty keyframe lists (input: null)
|
||||||
|
Pass Element.animate() accepts empty keyframe lists (input: undefined)
|
||||||
|
Pass Element.animate() accepts a one property two value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a one shorthand property two value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a two property two value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a two property property-indexed keyframes specification with different numbers of values
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframes specification with an invalid value
|
||||||
|
Pass Element.animate() accepts a one property two value property-indexed keyframes specification that needs to stringify its values
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframes specification with a CSS variable reference
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframes specification with a CSS variable reference in a shorthand property
|
||||||
|
Pass Element.animate() accepts a one property one value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a one property one non-array value property-indexed keyframes specification
|
||||||
|
Pass Element.animate() accepts a one property two value property-indexed keyframes specification where the first value is invalid
|
||||||
|
Pass Element.animate() accepts a one property two value property-indexed keyframes specification where the second value is invalid
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframes specification with a CSS variable as the property
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with a single offset
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of offsets
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of offsets that is too short
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of offsets that is too long
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an empty array of offsets
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with an array of offsets with an embedded null value
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with an array of offsets with a trailing null value
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with an array of offsets with leading and trailing null values
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with an array of offsets with adjacent null values
|
||||||
|
Fail Element.animate() accepts a property-indexed keyframe with an array of offsets with all null values (and too many at that)
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a single null offset
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of offsets that is not strictly ascending in the unused part of the array
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe without any specified easing
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a single easing
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of easings
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of easings that is too short
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a single-element array of easings
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an empty array of easings
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with an array of easings that is too long
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a single composite operation
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a composite array
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a composite array that is too short
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a composite array that is too long
|
||||||
|
Pass Element.animate() accepts a property-indexed keyframe with a single-element composite array
|
||||||
|
Pass Element.animate() accepts a one property one keyframe sequence
|
||||||
|
Pass Element.animate() accepts a one property two keyframe sequence
|
||||||
|
Pass Element.animate() accepts a two property two keyframe sequence
|
||||||
|
Pass Element.animate() accepts a one shorthand property two keyframe sequence
|
||||||
|
Pass Element.animate() accepts a two property (a shorthand and one of its component longhands) two keyframe sequence
|
||||||
|
Pass Element.animate() accepts a two property keyframe sequence where one property is missing from the first keyframe
|
||||||
|
Pass Element.animate() accepts a two property keyframe sequence where one property is missing from the last keyframe
|
||||||
|
Pass Element.animate() accepts a one property two keyframe sequence that needs to stringify its values
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with a CSS variable reference
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with a CSS variable reference in a shorthand property
|
||||||
|
Fail Element.animate() accepts a keyframe sequence with a CSS variable as its property
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with duplicate values for a given interior offset
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with duplicate values for offsets 0 and 1
|
||||||
|
Pass Element.animate() accepts a two property four keyframe sequence
|
||||||
|
Pass Element.animate() accepts a single keyframe sequence with omitted offset
|
||||||
|
Fail Element.animate() accepts a single keyframe sequence with null offset
|
||||||
|
Pass Element.animate() accepts a single keyframe sequence with string offset
|
||||||
|
Fail Element.animate() accepts a single keyframe sequence with a single calc() offset
|
||||||
|
Fail Element.animate() accepts a one property keyframe sequence with some omitted offsets
|
||||||
|
Fail Element.animate() accepts a one property keyframe sequence with some null offsets
|
||||||
|
Fail Element.animate() accepts a two property keyframe sequence with some omitted offsets
|
||||||
|
Pass Element.animate() accepts a one property keyframe sequence with all omitted offsets
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with different easing values, but the same easing value for a given offset
|
||||||
|
Pass Element.animate() accepts a keyframe sequence with different composite values, but the same composite value for a given offset
|
||||||
|
Pass Element.animate() does not accept keyframes with an out-of-bounded positive offset
|
||||||
|
Pass Element.animate() does not accept keyframes with an out-of-bounded negative offset
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes not loosely sorted by offset
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes not loosely sorted by offset even though not all offsets are specified
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes with offsets out of range
|
||||||
|
Pass Element.animate() does not accept keyframes not loosely sorted by offset
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes with an invalid easing value
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes with an invalid easing value as one of the array values
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframe with an invalid easing in the unused part of the array of easings
|
||||||
|
Pass Element.animate() does not accept empty property-indexed keyframe with an invalid easing
|
||||||
|
Pass Element.animate() does not accept empty property-indexed keyframe with an invalid easings array
|
||||||
|
Pass Element.animate() does not accept a keyframe sequence with an invalid easing value
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes with an invalid composite value
|
||||||
|
Pass Element.animate() does not accept property-indexed keyframes with an invalid composite value as one of the array values
|
||||||
|
Pass Element.animate() does not accept keyframes with an invalid composite value
|
||||||
|
Pass Element.animate() accepts a double as an options argument
|
||||||
|
Pass Element.animate() accepts a KeyframeAnimationOptions argument
|
||||||
|
Pass Element.animate() accepts an absent options argument
|
||||||
|
Pass Element.animate() does not accept invalid delay value: NaN
|
||||||
|
Pass Element.animate() does not accept invalid delay value: Infinity
|
||||||
|
Pass Element.animate() does not accept invalid delay value: -Infinity
|
||||||
|
Pass Element.animate() accepts a duration of 'auto' using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid duration value: -1
|
||||||
|
Pass Element.animate() does not accept invalid duration value: NaN
|
||||||
|
Pass Element.animate() does not accept invalid duration value: -Infinity
|
||||||
|
Pass Element.animate() does not accept invalid duration value: "abc"
|
||||||
|
Pass Element.animate() does not accept invalid duration value: -1 using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid duration value: NaN using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid duration value: -Infinity using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid duration value: "abc" using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid duration value: "100" using a dictionary object
|
||||||
|
Pass Element.animate() does not accept invalid easing: ''
|
||||||
|
Pass Element.animate() does not accept invalid easing: '7'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'test'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'initial'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'inherit'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'unset'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'unrecognized'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'var(--x)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'ease-in-out, ease-out'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'cubic-bezier(1.1, 0, 1, 1)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'cubic-bezier(0, 0, 1.1, 1)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'cubic-bezier(-0.1, 0, 1, 1)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'cubic-bezier(0, 0, -0.1, 1)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'cubic-bezier(0.1, 0, 4, 0.4)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'steps(-1, start)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'steps(0.1, start)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'steps(3, nowhere)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'steps(-3, end)'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'function (a){return a}'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'function (x){return x}'
|
||||||
|
Pass Element.animate() does not accept invalid easing: 'function(x, y){return 0.3}'
|
||||||
|
Pass Element.animate() does not accept invalid iterationStart value: -1
|
||||||
|
Pass Element.animate() does not accept invalid iterationStart value: NaN
|
||||||
|
Pass Element.animate() does not accept invalid iterationStart value: Infinity
|
||||||
|
Pass Element.animate() does not accept invalid iterationStart value: -Infinity
|
||||||
|
Pass Element.animate() does not accept invalid iterations value: -1
|
||||||
|
Pass Element.animate() does not accept invalid iterations value: -Infinity
|
||||||
|
Pass Element.animate() does not accept invalid iterations value: NaN
|
||||||
|
Pass Element.animate() correctly sets the id attribute when no id is specified
|
||||||
|
Pass Element.animate() correctly sets the id attribute
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline when triggered on an element in a different document
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline with no timeline parameter in KeyframeAnimationOptions.
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline with undefined timeline in KeyframeAnimationOptions.
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline with null timeline in KeyframeAnimationOptions.
|
||||||
|
Pass Element.animate() correctly sets the Animation's timeline with DocumentTimeline in KeyframeAnimationOptions.
|
||||||
|
Pass Element.animate() calls play on the Animation
|
||||||
|
Pass Element.animate() does NOT trigger a style change event
|
||||||
|
Pass animate() with pseudoElement parameter creates an Animation object
|
||||||
|
Pass animate() with pseudoElement parameter without content creates an Animation object
|
||||||
|
Pass animate() with pseudoElement parameter creates an Animation object for ::marker
|
||||||
|
Pass animate() with pseudoElement parameter creates an Animation object for ::first-line
|
||||||
|
Pass animate() with pseudoElement an Animation object targeting the correct pseudo-element
|
||||||
|
Pass animate() with pseudoElement without content creates an Animation object targeting the correct pseudo-element
|
||||||
|
Pass animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::marker
|
||||||
|
Pass animate() with pseudoElement an Animation object targeting the correct pseudo-element for ::first-line
|
||||||
|
Pass animate() with a non-null invalid pseudoElement '' throws a SyntaxError
|
||||||
|
Pass animate() with a non-null invalid pseudoElement 'before' throws a SyntaxError
|
||||||
|
Pass animate() with a non-null invalid pseudoElement ':abc' throws a SyntaxError
|
||||||
|
Pass animate() with a non-null invalid pseudoElement '::abc' throws a SyntaxError
|
||||||
|
Pass animate() with pseudoElement ::placeholder does not throw
|
||||||
|
Fail Finished fill animation doesn't replace animation on a different pseudoElement
|
|
@ -0,0 +1,181 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 175 tests
|
||||||
|
|
||||||
|
120 Pass
|
||||||
|
55 Fail
|
||||||
|
Pass A KeyframeEffect can be constructed with no frames
|
||||||
|
Pass easing values are parsed correctly when passed to the KeyframeEffect constructor in KeyframeEffectOptions
|
||||||
|
Pass Invalid easing values are correctly rejected when passed to the KeyframeEffect constructor in KeyframeEffectOptions
|
||||||
|
Pass composite values are parsed correctly when passed to the KeyframeEffect constructor in property-indexed keyframes
|
||||||
|
Pass composite values are parsed correctly when passed to the KeyframeEffect constructor in regular keyframes
|
||||||
|
Pass composite value is auto if the composite operation specified on the keyframe effect is being used
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a one property two value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one shorthand property two value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a one shorthand property two value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property two value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a two property two value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property property-indexed keyframes specification with different numbers of values
|
||||||
|
Fail A KeyframeEffect constructed with a two property property-indexed keyframes specification with different numbers of values roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframes specification with an invalid value
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframes specification with an invalid value roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two value property-indexed keyframes specification that needs to stringify its values
|
||||||
|
Fail A KeyframeEffect constructed with a one property two value property-indexed keyframes specification that needs to stringify its values roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframes specification with a CSS variable reference
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframes specification with a CSS variable reference roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframes specification with a CSS variable reference in a shorthand property
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframes specification with a CSS variable reference in a shorthand property roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property one value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a one property one value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property one non-array value property-indexed keyframes specification
|
||||||
|
Fail A KeyframeEffect constructed with a one property one non-array value property-indexed keyframes specification roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two value property-indexed keyframes specification where the first value is invalid
|
||||||
|
Fail A KeyframeEffect constructed with a one property two value property-indexed keyframes specification where the first value is invalid roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two value property-indexed keyframes specification where the second value is invalid
|
||||||
|
Fail A KeyframeEffect constructed with a one property two value property-indexed keyframes specification where the second value is invalid roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframes specification with a CSS variable as the property
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframes specification with a CSS variable as the property roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with a single offset
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single offset roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets that is too short
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets that is too short roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets that is too long
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets that is too long roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an empty array of offsets
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an empty array of offsets roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets with an embedded null value
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets with an embedded null value roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets with a trailing null value
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets with a trailing null value roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets with leading and trailing null values
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets with leading and trailing null values roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets with adjacent null values
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets with adjacent null values roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets with all null values (and too many at that)
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets with all null values (and too many at that) roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a single null offset
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single null offset roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of offsets that is not strictly ascending in the unused part of the array
|
||||||
|
Pass A KeyframeEffect constructed with a property-indexed keyframe with an array of offsets that is not strictly ascending in the unused part of the array roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe without any specified easing
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe without any specified easing roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a single easing
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single easing roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of easings
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of easings roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of easings that is too short
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of easings that is too short roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a single-element array of easings
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single-element array of easings roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an empty array of easings
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an empty array of easings roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with an array of easings that is too long
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with an array of easings that is too long roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a single composite operation
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single composite operation roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a composite array
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a composite array roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a composite array that is too short
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a composite array that is too short roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a composite array that is too long
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a composite array that is too long roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a property-indexed keyframe with a single-element composite array
|
||||||
|
Fail A KeyframeEffect constructed with a property-indexed keyframe with a single-element composite array roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property one keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a one property one keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a one property two keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property two keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a two property two keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one shorthand property two keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a one shorthand property two keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property (a shorthand and one of its component longhands) two keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a two property (a shorthand and one of its component longhands) two keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property keyframe sequence where one property is missing from the first keyframe
|
||||||
|
Pass A KeyframeEffect constructed with a two property keyframe sequence where one property is missing from the first keyframe roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property keyframe sequence where one property is missing from the last keyframe
|
||||||
|
Pass A KeyframeEffect constructed with a two property keyframe sequence where one property is missing from the last keyframe roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property two keyframe sequence that needs to stringify its values
|
||||||
|
Pass A KeyframeEffect constructed with a one property two keyframe sequence that needs to stringify its values roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with a CSS variable reference
|
||||||
|
Fail A KeyframeEffect constructed with a keyframe sequence with a CSS variable reference roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with a CSS variable reference in a shorthand property
|
||||||
|
Fail A KeyframeEffect constructed with a keyframe sequence with a CSS variable reference in a shorthand property roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a keyframe sequence with a CSS variable as its property
|
||||||
|
Fail A KeyframeEffect constructed with a keyframe sequence with a CSS variable as its property roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with duplicate values for a given interior offset
|
||||||
|
Pass A KeyframeEffect constructed with a keyframe sequence with duplicate values for a given interior offset roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with duplicate values for offsets 0 and 1
|
||||||
|
Pass A KeyframeEffect constructed with a keyframe sequence with duplicate values for offsets 0 and 1 roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a two property four keyframe sequence
|
||||||
|
Pass A KeyframeEffect constructed with a two property four keyframe sequence roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a single keyframe sequence with omitted offset
|
||||||
|
Fail A KeyframeEffect constructed with a single keyframe sequence with omitted offset roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a single keyframe sequence with null offset
|
||||||
|
Pass A KeyframeEffect constructed with a single keyframe sequence with null offset roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a single keyframe sequence with string offset
|
||||||
|
Pass A KeyframeEffect constructed with a single keyframe sequence with string offset roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a single keyframe sequence with a single calc() offset
|
||||||
|
Fail A KeyframeEffect constructed with a single keyframe sequence with a single calc() offset roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a one property keyframe sequence with some omitted offsets
|
||||||
|
Fail A KeyframeEffect constructed with a one property keyframe sequence with some omitted offsets roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a one property keyframe sequence with some null offsets
|
||||||
|
Fail A KeyframeEffect constructed with a one property keyframe sequence with some null offsets roundtrips
|
||||||
|
Fail A KeyframeEffect can be constructed with a two property keyframe sequence with some omitted offsets
|
||||||
|
Fail A KeyframeEffect constructed with a two property keyframe sequence with some omitted offsets roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a one property keyframe sequence with all omitted offsets
|
||||||
|
Fail A KeyframeEffect constructed with a one property keyframe sequence with all omitted offsets roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with different easing values, but the same easing value for a given offset
|
||||||
|
Pass A KeyframeEffect constructed with a keyframe sequence with different easing values, but the same easing value for a given offset roundtrips
|
||||||
|
Pass A KeyframeEffect can be constructed with a keyframe sequence with different composite values, but the same composite value for a given offset
|
||||||
|
Pass A KeyframeEffect constructed with a keyframe sequence with different composite values, but the same composite value for a given offset roundtrips
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an out-of-bounded positive offset
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an out-of-bounded negative offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes not loosely sorted by offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes not loosely sorted by offset even though not all offsets are specified
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with offsets out of range
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes not loosely sorted by offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value as one of the array values
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframe with an invalid easing in the unused part of the array of easings
|
||||||
|
Pass KeyframeEffect constructor throws with empty property-indexed keyframe with an invalid easing
|
||||||
|
Pass KeyframeEffect constructor throws with empty property-indexed keyframe with an invalid easings array
|
||||||
|
Pass KeyframeEffect constructor throws with a keyframe sequence with an invalid easing value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid composite value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid composite value as one of the array values
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an invalid composite value
|
||||||
|
Fail A KeyframeEffect constructed without any KeyframeEffectOptions object
|
||||||
|
Pass A KeyframeEffect constructed by an empty KeyframeEffectOptions object
|
||||||
|
Pass A KeyframeEffect constructed by a normal KeyframeEffectOptions object
|
||||||
|
Pass A KeyframeEffect constructed by a double value
|
||||||
|
Pass A KeyframeEffect constructed by +Infinity
|
||||||
|
Pass A KeyframeEffect constructed by an Infinity duration
|
||||||
|
Pass A KeyframeEffect constructed by an auto duration
|
||||||
|
Pass A KeyframeEffect constructed by an Infinity iterations
|
||||||
|
Pass A KeyframeEffect constructed by an auto fill
|
||||||
|
Pass A KeyframeEffect constructed by a forwards fill
|
||||||
|
Pass Invalid KeyframeEffect option by -Infinity
|
||||||
|
Pass Invalid KeyframeEffect option by NaN
|
||||||
|
Pass Invalid KeyframeEffect option by a negative value
|
||||||
|
Pass Invalid KeyframeEffect option by a negative Infinity duration
|
||||||
|
Pass Invalid KeyframeEffect option by a NaN duration
|
||||||
|
Pass Invalid KeyframeEffect option by a negative duration
|
||||||
|
Pass Invalid KeyframeEffect option by a string duration
|
||||||
|
Pass Invalid KeyframeEffect option by a negative Infinity iterations
|
||||||
|
Pass Invalid KeyframeEffect option by a NaN iterations
|
||||||
|
Pass Invalid KeyframeEffect option by a negative iterations
|
||||||
|
Pass Invalid KeyframeEffect option by a blank easing
|
||||||
|
Pass Invalid KeyframeEffect option by an unrecognized easing
|
||||||
|
Pass Invalid KeyframeEffect option by an 'initial' easing
|
||||||
|
Pass Invalid KeyframeEffect option by an 'inherit' easing
|
||||||
|
Pass Invalid KeyframeEffect option by a variable easing
|
||||||
|
Pass Invalid KeyframeEffect option by a multi-value easing
|
||||||
|
Pass A KeyframeEffect constructed with null target
|
||||||
|
Pass KeyframeEffect constructor propagates exceptions generated by accessing the options object
|
|
@ -0,0 +1,86 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 80 tests
|
||||||
|
|
||||||
|
66 Pass
|
||||||
|
14 Fail
|
||||||
|
Pass Keyframes can be replaced with an empty keyframe
|
||||||
|
Pass Keyframes can be replaced with a one property two value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a one shorthand property two value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a two property two value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a two property property-indexed keyframes specification with different numbers of values
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframes specification with an invalid value
|
||||||
|
Pass Keyframes can be replaced with a one property two value property-indexed keyframes specification that needs to stringify its values
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference in a shorthand property
|
||||||
|
Pass Keyframes can be replaced with a one property one value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a one property one non-array value property-indexed keyframes specification
|
||||||
|
Pass Keyframes can be replaced with a one property two value property-indexed keyframes specification where the first value is invalid
|
||||||
|
Pass Keyframes can be replaced with a one property two value property-indexed keyframes specification where the second value is invalid
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable as the property
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with a single offset
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of offsets
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of offsets that is too short
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of offsets that is too long
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an empty array of offsets
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with an array of offsets with an embedded null value
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with an array of offsets with a trailing null value
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with an array of offsets with leading and trailing null values
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with an array of offsets with adjacent null values
|
||||||
|
Fail Keyframes can be replaced with a property-indexed keyframe with an array of offsets with all null values (and too many at that)
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a single null offset
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of offsets that is not strictly ascending in the unused part of the array
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe without any specified easing
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a single easing
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of easings
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of easings that is too short
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a single-element array of easings
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an empty array of easings
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with an array of easings that is too long
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a single composite operation
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a composite array
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a composite array that is too short
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a composite array that is too long
|
||||||
|
Pass Keyframes can be replaced with a property-indexed keyframe with a single-element composite array
|
||||||
|
Pass Keyframes can be replaced with a one property one keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a one property two keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a two property two keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a one shorthand property two keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a two property (a shorthand and one of its component longhands) two keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a two property keyframe sequence where one property is missing from the first keyframe
|
||||||
|
Pass Keyframes can be replaced with a two property keyframe sequence where one property is missing from the last keyframe
|
||||||
|
Pass Keyframes can be replaced with a one property two keyframe sequence that needs to stringify its values
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with a CSS variable reference
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with a CSS variable reference in a shorthand property
|
||||||
|
Fail Keyframes can be replaced with a keyframe sequence with a CSS variable as its property
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with duplicate values for a given interior offset
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with duplicate values for offsets 0 and 1
|
||||||
|
Pass Keyframes can be replaced with a two property four keyframe sequence
|
||||||
|
Pass Keyframes can be replaced with a single keyframe sequence with omitted offset
|
||||||
|
Fail Keyframes can be replaced with a single keyframe sequence with null offset
|
||||||
|
Pass Keyframes can be replaced with a single keyframe sequence with string offset
|
||||||
|
Fail Keyframes can be replaced with a single keyframe sequence with a single calc() offset
|
||||||
|
Fail Keyframes can be replaced with a one property keyframe sequence with some omitted offsets
|
||||||
|
Fail Keyframes can be replaced with a one property keyframe sequence with some null offsets
|
||||||
|
Fail Keyframes can be replaced with a two property keyframe sequence with some omitted offsets
|
||||||
|
Pass Keyframes can be replaced with a one property keyframe sequence with all omitted offsets
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with different easing values, but the same easing value for a given offset
|
||||||
|
Pass Keyframes can be replaced with a keyframe sequence with different composite values, but the same composite value for a given offset
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an out-of-bounded positive offset
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an out-of-bounded negative offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes not loosely sorted by offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes not loosely sorted by offset even though not all offsets are specified
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with offsets out of range
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes not loosely sorted by offset
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value as one of the array values
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframe with an invalid easing in the unused part of the array of easings
|
||||||
|
Pass KeyframeEffect constructor throws with empty property-indexed keyframe with an invalid easing
|
||||||
|
Pass KeyframeEffect constructor throws with empty property-indexed keyframe with an invalid easings array
|
||||||
|
Pass KeyframeEffect constructor throws with a keyframe sequence with an invalid easing value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid composite value
|
||||||
|
Pass KeyframeEffect constructor throws with property-indexed keyframes with an invalid composite value as one of the array values
|
||||||
|
Pass KeyframeEffect constructor throws with keyframes with an invalid composite value
|
||||||
|
Fail Changes made via setKeyframes should be immediately visible in style
|
|
@ -0,0 +1,363 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>Animatable.animate</title>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate">
|
||||||
|
<script src="../../../resources/testharness.js"></script>
|
||||||
|
<script src="../../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../testcommon.js"></script>
|
||||||
|
<script src="../../resources/easing-tests.js"></script>
|
||||||
|
<script src="../../resources/keyframe-utils.js"></script>
|
||||||
|
<script src="../../resources/keyframe-tests.js"></script>
|
||||||
|
<script src="../../resources/timing-utils.js"></script>
|
||||||
|
<script src="../../resources/timing-tests.js"></script>
|
||||||
|
<style>
|
||||||
|
.pseudo::before {content: '';}
|
||||||
|
.pseudo::after {content: '';}
|
||||||
|
.pseudo::marker {content: '';}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<iframe width="10" height="10" id="iframe"></iframe>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Tests on Element
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null);
|
||||||
|
assert_class_string(anim, 'Animation', 'Returned object is an Animation');
|
||||||
|
}, 'Element.animate() creates an Animation object');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const iframe = window.frames[0];
|
||||||
|
const div = createDiv(t, iframe.document);
|
||||||
|
const anim = Element.prototype.animate.call(div, null);
|
||||||
|
assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype,
|
||||||
|
'The prototype of the created Animation is that defined on'
|
||||||
|
+ ' the relevant global for the target element');
|
||||||
|
assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype,
|
||||||
|
'The prototype of the created Animation is NOT that of'
|
||||||
|
+ ' the current global');
|
||||||
|
}, 'Element.animate() creates an Animation object in the relevant realm of'
|
||||||
|
+ ' the target element');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
const anim = Element.prototype.animate.call(div, null);
|
||||||
|
assert_class_string(anim.effect, 'KeyframeEffect',
|
||||||
|
'Returned Animation has a KeyframeEffect');
|
||||||
|
}, 'Element.animate() creates an Animation object with a KeyframeEffect');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const iframe = window.frames[0];
|
||||||
|
const div = createDiv(t, iframe.document);
|
||||||
|
const anim = Element.prototype.animate.call(div, null);
|
||||||
|
assert_equals(Object.getPrototypeOf(anim.effect),
|
||||||
|
iframe.KeyframeEffect.prototype,
|
||||||
|
'The prototype of the created KeyframeEffect is that defined on'
|
||||||
|
+ ' the relevant global for the target element');
|
||||||
|
assert_not_equals(Object.getPrototypeOf(anim.effect),
|
||||||
|
KeyframeEffect.prototype,
|
||||||
|
'The prototype of the created KeyframeEffect is NOT that of'
|
||||||
|
+ ' the current global');
|
||||||
|
}, 'Element.animate() creates an Animation object with a KeyframeEffect'
|
||||||
|
+ ' that is created in the relevant realm of the target element');
|
||||||
|
|
||||||
|
for (const subtest of gEmptyKeyframeListTests) {
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(subtest, 2000);
|
||||||
|
assert_not_equals(anim, null);
|
||||||
|
}, 'Element.animate() accepts empty keyframe lists ' +
|
||||||
|
`(input: ${JSON.stringify(subtest)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subtest of gKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(subtest.input, 2000);
|
||||||
|
assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output);
|
||||||
|
}, `Element.animate() accepts ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subtest of gInvalidKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
div.animate(subtest.input, 2000);
|
||||||
|
});
|
||||||
|
}, `Element.animate() does not accept ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, 2000);
|
||||||
|
assert_equals(anim.effect.getTiming().duration, 2000);
|
||||||
|
assert_default_timing_except(anim.effect, ['duration']);
|
||||||
|
}, 'Element.animate() accepts a double as an options argument');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null,
|
||||||
|
{ duration: Infinity, fill: 'forwards' });
|
||||||
|
assert_equals(anim.effect.getTiming().duration, Infinity);
|
||||||
|
assert_equals(anim.effect.getTiming().fill, 'forwards');
|
||||||
|
assert_default_timing_except(anim.effect, ['duration', 'fill']);
|
||||||
|
}, 'Element.animate() accepts a KeyframeAnimationOptions argument');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null);
|
||||||
|
assert_default_timing_except(anim.effect, []);
|
||||||
|
}, 'Element.animate() accepts an absent options argument');
|
||||||
|
|
||||||
|
for (const invalid of gBadDelayValues) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate(null, { delay: invalid });
|
||||||
|
});
|
||||||
|
}, `Element.animate() does not accept invalid delay value: ${invalid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, { duration: 'auto' });
|
||||||
|
assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\'');
|
||||||
|
assert_equals(anim.effect.getComputedTiming().duration, 0,
|
||||||
|
'getComputedTiming() after set duration \'auto\'');
|
||||||
|
}, 'Element.animate() accepts a duration of \'auto\' using a dictionary'
|
||||||
|
+ ' object');
|
||||||
|
|
||||||
|
for (const invalid of gBadDurationValues) {
|
||||||
|
if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate(null, invalid);
|
||||||
|
});
|
||||||
|
}, 'Element.animate() does not accept invalid duration value: '
|
||||||
|
+ (typeof invalid === 'string' ? `"${invalid}"` : invalid));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const invalid of gBadDurationValues) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate(null, { duration: invalid });
|
||||||
|
});
|
||||||
|
}, 'Element.animate() does not accept invalid duration value: '
|
||||||
|
+ (typeof invalid === 'string' ? `"${invalid}"` : invalid)
|
||||||
|
+ ' using a dictionary object');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const invalidEasing of gInvalidEasings) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate({ easing: invalidEasing }, 2000);
|
||||||
|
});
|
||||||
|
}, `Element.animate() does not accept invalid easing: '${invalidEasing}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const invalid of gBadIterationStartValues) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate(null, { iterationStart: invalid });
|
||||||
|
});
|
||||||
|
}, 'Element.animate() does not accept invalid iterationStart value: ' +
|
||||||
|
invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const invalid of gBadIterationsValues) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
createDiv(t).animate(null, { iterations: invalid });
|
||||||
|
});
|
||||||
|
}, 'Element.animate() does not accept invalid iterations value: ' +
|
||||||
|
invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, 2000);
|
||||||
|
assert_equals(anim.id, '');
|
||||||
|
}, 'Element.animate() correctly sets the id attribute when no id is specified');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, { id: 'test' });
|
||||||
|
assert_equals(anim.id, 'test');
|
||||||
|
}, 'Element.animate() correctly sets the id attribute');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, 2000);
|
||||||
|
assert_equals(anim.timeline, document.timeline);
|
||||||
|
}, 'Element.animate() correctly sets the Animation\'s timeline');
|
||||||
|
|
||||||
|
async_test(t => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.width = 10;
|
||||||
|
iframe.height = 10;
|
||||||
|
|
||||||
|
iframe.addEventListener('load', t.step_func(() => {
|
||||||
|
const div = createDiv(t, iframe.contentDocument);
|
||||||
|
const anim = div.animate(null, 2000);
|
||||||
|
assert_equals(anim.timeline, iframe.contentDocument.timeline);
|
||||||
|
iframe.remove();
|
||||||
|
t.done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
}, 'Element.animate() correctly sets the Animation\'s timeline when ' +
|
||||||
|
'triggered on an element in a different document');
|
||||||
|
|
||||||
|
for (const subtest of gAnimationTimelineTests) {
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, { timeline: subtest.timeline });
|
||||||
|
assert_not_equals(anim, null,
|
||||||
|
'An animation sohuld be created');
|
||||||
|
assert_equals(anim.timeline, subtest.expectedTimeline,
|
||||||
|
'Animation timeline should be '+
|
||||||
|
subtest.expectedTimelineDescription);
|
||||||
|
}, 'Element.animate() correctly sets the Animation\'s timeline '
|
||||||
|
+ subtest.description + ' in KeyframeAnimationOptions.');
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const anim = createDiv(t).animate(null, 2000);
|
||||||
|
assert_equals(anim.playState, 'running');
|
||||||
|
}, 'Element.animate() calls play on the Animation');
|
||||||
|
|
||||||
|
promise_test(async t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
|
||||||
|
let gotTransition = false;
|
||||||
|
div.addEventListener('transitionrun', () => {
|
||||||
|
gotTransition = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup transition start point.
|
||||||
|
div.style.transition = 'opacity 100s';
|
||||||
|
getComputedStyle(div).opacity;
|
||||||
|
|
||||||
|
// Update specified style but don't flush style.
|
||||||
|
div.style.opacity = '0.5';
|
||||||
|
|
||||||
|
// Trigger a new animation at the same time.
|
||||||
|
const anim = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||||
|
|
||||||
|
// If Element.animate() produces a style change event it will have triggered
|
||||||
|
// a transition.
|
||||||
|
//
|
||||||
|
// If it does NOT produce a style change event, the animation will override
|
||||||
|
// the before-change style and after-change style such that a transition is
|
||||||
|
// never triggered.
|
||||||
|
|
||||||
|
// Wait for the animation to start and then for one more animation
|
||||||
|
// frame to give the transitionrun event a chance to be dispatched.
|
||||||
|
await anim.ready;
|
||||||
|
await waitForAnimationFrames(1);
|
||||||
|
|
||||||
|
assert_false(gotTransition, 'A transition should NOT have been triggered');
|
||||||
|
}, 'Element.animate() does NOT trigger a style change event');
|
||||||
|
|
||||||
|
// Tests on pseudo-elements
|
||||||
|
// Some tests occur twice (on pseudo-elements with and without content)
|
||||||
|
// in order to test both code paths for tree-abiding pseudo-elements in blink.
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::before'});
|
||||||
|
assert_class_string(anim, 'Animation', 'The returned object is an Animation');
|
||||||
|
}, 'animate() with pseudoElement parameter creates an Animation object');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::before'});
|
||||||
|
assert_class_string(anim, 'Animation', 'The returned object is an Animation');
|
||||||
|
}, 'animate() with pseudoElement parameter without content creates an Animation object');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
div.style.display = 'list-item';
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::marker'});
|
||||||
|
assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker');
|
||||||
|
}, 'animate() with pseudoElement parameter creates an Animation object for ::marker');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
div.textContent = 'foo';
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::first-line'});
|
||||||
|
assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line');
|
||||||
|
}, 'animate() with pseudoElement parameter creates an Animation object for ::first-line');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::before'});
|
||||||
|
assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
|
||||||
|
assert_equals(anim.effect.pseudoElement, '::before',
|
||||||
|
'The returned Animation targets the correct selector');
|
||||||
|
}, 'animate() with pseudoElement an Animation object targeting ' +
|
||||||
|
'the correct pseudo-element');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::before'});
|
||||||
|
assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
|
||||||
|
assert_equals(anim.effect.pseudoElement, '::before',
|
||||||
|
'The returned Animation targets the correct selector');
|
||||||
|
}, 'animate() with pseudoElement without content creates an Animation object targeting ' +
|
||||||
|
'the correct pseudo-element');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
div.style.display = 'list-item';
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::marker'});
|
||||||
|
assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
|
||||||
|
assert_equals(anim.effect.pseudoElement, '::marker',
|
||||||
|
'The returned Animation targets the correct selector');
|
||||||
|
}, 'animate() with pseudoElement an Animation object targeting ' +
|
||||||
|
'the correct pseudo-element for ::marker');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
div.textContent = 'foo';
|
||||||
|
const anim = div.animate(null, {pseudoElement: '::first-line'});
|
||||||
|
assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
|
||||||
|
assert_equals(anim.effect.pseudoElement, '::first-line',
|
||||||
|
'The returned Animation targets the correct selector');
|
||||||
|
}, 'animate() with pseudoElement an Animation object targeting ' +
|
||||||
|
'the correct pseudo-element for ::first-line');
|
||||||
|
|
||||||
|
for (const pseudo of [
|
||||||
|
'',
|
||||||
|
'before',
|
||||||
|
':abc',
|
||||||
|
'::abc',
|
||||||
|
]) {
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
assert_throws_dom("SyntaxError", () => {
|
||||||
|
div.animate(null, {pseudoElement: pseudo});
|
||||||
|
});
|
||||||
|
}, `animate() with a non-null invalid pseudoElement '${pseudo}' throws a ` +
|
||||||
|
`SyntaxError`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.animate(null, { pseudoElement: '::placeHOLDER' });
|
||||||
|
}, `animate() with pseudoElement ::placeholder does not throw`);
|
||||||
|
|
||||||
|
promise_test(async t => {
|
||||||
|
const div = createDiv(t);
|
||||||
|
div.classList.add('pseudo');
|
||||||
|
let animBefore = div.animate({opacity: [1, 0]}, {duration: 1, pseudoElement: '::before', fill: 'both'});
|
||||||
|
let animAfter = div.animate({opacity: [1, 0]}, {duration: 1, pseudoElement: '::after', fill: 'both'});
|
||||||
|
await animBefore.finished;
|
||||||
|
await animAfter.finished;
|
||||||
|
// The animation on ::before should not be replaced as it targets a different
|
||||||
|
// pseudo-element.
|
||||||
|
assert_equals(animBefore.replaceState, 'active');
|
||||||
|
assert_equals(animAfter.replaceState, 'active');
|
||||||
|
}, 'Finished fill animation doesn\'t replace animation on a different pseudoElement');
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>KeyframeEffect constructor</title>
|
||||||
|
<link rel="help"
|
||||||
|
href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-keyframeeffect">
|
||||||
|
<link rel="help"
|
||||||
|
href="https://drafts.csswg.org/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly">
|
||||||
|
<script src="../../../resources/testharness.js"></script>
|
||||||
|
<script src="../../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../testcommon.js"></script>
|
||||||
|
<script src="../../resources/easing-tests.js"></script>
|
||||||
|
<script src="../../resources/keyframe-utils.js"></script>
|
||||||
|
<script src="../../resources/keyframe-tests.js"></script>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<div id="target"></div>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const target = document.getElementById('target');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
for (const frames of gEmptyKeyframeListTests) {
|
||||||
|
assert_equals(new KeyframeEffect(target, frames).getKeyframes().length,
|
||||||
|
0, `number of frames for ${JSON.stringify(frames)}`);
|
||||||
|
}
|
||||||
|
}, 'A KeyframeEffect can be constructed with no frames');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
for (const subtest of gEasingParsingTests) {
|
||||||
|
const easing = subtest[0];
|
||||||
|
const expected = subtest[1];
|
||||||
|
const effect = new KeyframeEffect(target, {
|
||||||
|
left: ['10px', '20px']
|
||||||
|
}, { easing: easing });
|
||||||
|
assert_equals(effect.getTiming().easing, expected,
|
||||||
|
`resulting easing for '${easing}'`);
|
||||||
|
}
|
||||||
|
}, 'easing values are parsed correctly when passed to the ' +
|
||||||
|
'KeyframeEffect constructor in KeyframeEffectOptions');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
for (const invalidEasing of gInvalidEasings) {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, null, { easing: invalidEasing });
|
||||||
|
}, `TypeError is thrown for easing '${invalidEasing}'`);
|
||||||
|
}
|
||||||
|
}, 'Invalid easing values are correctly rejected when passed to the ' +
|
||||||
|
'KeyframeEffect constructor in KeyframeEffectOptions');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const getKeyframe =
|
||||||
|
composite => ({ left: [ '10px', '20px' ], composite: composite });
|
||||||
|
for (const composite of gGoodKeyframeCompositeValueTests) {
|
||||||
|
const effect = new KeyframeEffect(target, getKeyframe(composite));
|
||||||
|
assert_equals(effect.getKeyframes()[0].composite, composite,
|
||||||
|
`resulting composite for '${composite}'`);
|
||||||
|
}
|
||||||
|
for (const composite of gBadKeyframeCompositeValueTests) {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, getKeyframe(composite));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 'composite values are parsed correctly when passed to the ' +
|
||||||
|
'KeyframeEffect constructor in property-indexed keyframes');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const getKeyframes = composite =>
|
||||||
|
[
|
||||||
|
{ offset: 0, left: '10px', composite: composite },
|
||||||
|
{ offset: 1, left: '20px' }
|
||||||
|
];
|
||||||
|
for (const composite of gGoodKeyframeCompositeValueTests) {
|
||||||
|
const effect = new KeyframeEffect(target, getKeyframes(composite));
|
||||||
|
assert_equals(effect.getKeyframes()[0].composite, composite,
|
||||||
|
`resulting composite for '${composite}'`);
|
||||||
|
}
|
||||||
|
for (const composite of gBadKeyframeCompositeValueTests) {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, getKeyframes(composite));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 'composite values are parsed correctly when passed to the ' +
|
||||||
|
'KeyframeEffect constructor in regular keyframes');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
for (const composite of gGoodOptionsCompositeValueTests) {
|
||||||
|
const effect = new KeyframeEffect(target, {
|
||||||
|
left: ['10px', '20px']
|
||||||
|
}, { composite });
|
||||||
|
assert_equals(effect.getKeyframes()[0].composite, 'auto',
|
||||||
|
`resulting composite for '${composite}'`);
|
||||||
|
}
|
||||||
|
for (const composite of gBadOptionsCompositeValueTests) {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, {
|
||||||
|
left: ['10px', '20px']
|
||||||
|
}, { composite: composite });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 'composite value is auto if the composite operation specified on the ' +
|
||||||
|
'keyframe effect is being used');
|
||||||
|
|
||||||
|
for (const subtest of gKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, subtest.input);
|
||||||
|
assert_frame_lists_equal(effect.getKeyframes(), subtest.output);
|
||||||
|
}, `A KeyframeEffect can be constructed with ${subtest.desc}`);
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, subtest.input);
|
||||||
|
const secondEffect = new KeyframeEffect(target, effect.getKeyframes());
|
||||||
|
assert_frame_lists_equal(secondEffect.getKeyframes(),
|
||||||
|
effect.getKeyframes());
|
||||||
|
}, `A KeyframeEffect constructed with ${subtest.desc} roundtrips`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subtest of gInvalidKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, subtest.input);
|
||||||
|
});
|
||||||
|
}, `KeyframeEffect constructor throws with ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, { left: ['10px', '20px'] });
|
||||||
|
|
||||||
|
const timing = effect.getTiming();
|
||||||
|
assert_equals(timing.delay, 0, 'default delay');
|
||||||
|
assert_equals(timing.endDelay, 0, 'default endDelay');
|
||||||
|
assert_equals(timing.fill, 'auto', 'default fill');
|
||||||
|
assert_equals(timing.iterations, 1.0, 'default iterations');
|
||||||
|
assert_equals(timing.iterationStart, 0.0, 'default iterationStart');
|
||||||
|
assert_equals(timing.duration, 'auto', 'default duration');
|
||||||
|
assert_equals(timing.direction, 'normal', 'default direction');
|
||||||
|
assert_equals(timing.easing, 'linear', 'default easing');
|
||||||
|
|
||||||
|
assert_equals(effect.composite, 'replace', 'default composite');
|
||||||
|
assert_equals(effect.iterationComposite, 'replace',
|
||||||
|
'default iterationComposite');
|
||||||
|
}, 'A KeyframeEffect constructed without any KeyframeEffectOptions object');
|
||||||
|
|
||||||
|
for (const subtest of gKeyframeEffectOptionTests) {
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, { left: ['10px', '20px'] },
|
||||||
|
subtest.input);
|
||||||
|
|
||||||
|
// Helper function to provide default expected values when the test does
|
||||||
|
// not supply them.
|
||||||
|
const expected = (field, defaultValue) => {
|
||||||
|
return field in subtest.expected ? subtest.expected[field] : defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const timing = effect.getTiming();
|
||||||
|
assert_equals(timing.delay, expected('delay', 0),
|
||||||
|
'timing delay');
|
||||||
|
assert_equals(timing.fill, expected('fill', 'auto'),
|
||||||
|
'timing fill');
|
||||||
|
assert_equals(timing.iterations, expected('iterations', 1),
|
||||||
|
'timing iterations');
|
||||||
|
assert_equals(timing.duration, expected('duration', 'auto'),
|
||||||
|
'timing duration');
|
||||||
|
assert_equals(timing.direction, expected('direction', 'normal'),
|
||||||
|
'timing direction');
|
||||||
|
|
||||||
|
}, `A KeyframeEffect constructed by ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subtest of gInvalidKeyframeEffectOptionTests) {
|
||||||
|
test(t => {
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
new KeyframeEffect(target, { left: ['10px', '20px'] }, subtest.input);
|
||||||
|
});
|
||||||
|
}, `Invalid KeyframeEffect option by ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(null, { left: ['10px', '20px'] },
|
||||||
|
{ duration: 100 * MS_PER_SEC,
|
||||||
|
fill: 'forwards' });
|
||||||
|
assert_equals(effect.target, null,
|
||||||
|
'Effect created with null target has correct target');
|
||||||
|
}, 'A KeyframeEffect constructed with null target');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const test_error = { name: 'test' };
|
||||||
|
|
||||||
|
assert_throws_exactly(test_error, () => {
|
||||||
|
new KeyframeEffect(target, { get left() { throw test_error }})
|
||||||
|
});
|
||||||
|
}, 'KeyframeEffect constructor propagates exceptions generated by accessing'
|
||||||
|
+ ' the options object');
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>KeyframeEffect.setKeyframes</title>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes">
|
||||||
|
<script src="../../../resources/testharness.js"></script>
|
||||||
|
<script src="../../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../testcommon.js"></script>
|
||||||
|
<script src="../../resources/keyframe-utils.js"></script>
|
||||||
|
<script src="../../resources/keyframe-tests.js"></script>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<div id="target"></div>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const target = document.getElementById('target');
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
for (const frame of gEmptyKeyframeListTests) {
|
||||||
|
const effect = new KeyframeEffect(target, {});
|
||||||
|
effect.setKeyframes(frame);
|
||||||
|
assert_frame_lists_equal(effect.getKeyframes(), []);
|
||||||
|
}
|
||||||
|
}, 'Keyframes can be replaced with an empty keyframe');
|
||||||
|
|
||||||
|
for (const subtest of gKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, {});
|
||||||
|
effect.setKeyframes(subtest.input);
|
||||||
|
assert_frame_lists_equal(effect.getKeyframes(), subtest.output);
|
||||||
|
}, `Keyframes can be replaced with ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subtest of gInvalidKeyframesTests) {
|
||||||
|
test(t => {
|
||||||
|
const effect = new KeyframeEffect(target, {});
|
||||||
|
assert_throws_js(TypeError, () => {
|
||||||
|
effect.setKeyframes(subtest.input);
|
||||||
|
});
|
||||||
|
}, `KeyframeEffect constructor throws with ${subtest.desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const frames1 = [ { left: '100px' }, { left: '200px' } ];
|
||||||
|
const frames2 = [ { left: '200px' }, { left: '300px' } ];
|
||||||
|
|
||||||
|
const animation = target.animate(frames1, 1000);
|
||||||
|
animation.currentTime = 500;
|
||||||
|
assert_equals(getComputedStyle(target).left, "150px");
|
||||||
|
|
||||||
|
animation.effect.setKeyframes(frames2);
|
||||||
|
assert_equals(getComputedStyle(target).left, "250px");
|
||||||
|
}, 'Changes made via setKeyframes should be immediately visible in style');
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -0,0 +1,121 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gEasingTests = [
|
||||||
|
{
|
||||||
|
desc: 'step-start function',
|
||||||
|
easing: 'step-start',
|
||||||
|
easingFunction: stepStart(1),
|
||||||
|
serialization: 'steps(1, start)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'steps(1, start) function',
|
||||||
|
easing: 'steps(1, start)',
|
||||||
|
easingFunction: stepStart(1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'steps(2, start) function',
|
||||||
|
easing: 'steps(2, start)',
|
||||||
|
easingFunction: stepStart(2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'step-end function',
|
||||||
|
easing: 'step-end',
|
||||||
|
easingFunction: stepEnd(1),
|
||||||
|
serialization: 'steps(1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'steps(1) function',
|
||||||
|
easing: 'steps(1)',
|
||||||
|
easingFunction: stepEnd(1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'steps(1, end) function',
|
||||||
|
easing: 'steps(1, end)',
|
||||||
|
easingFunction: stepEnd(1),
|
||||||
|
serialization: 'steps(1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'steps(2, end) function',
|
||||||
|
easing: 'steps(2, end)',
|
||||||
|
easingFunction: stepEnd(2),
|
||||||
|
serialization: 'steps(2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'linear function',
|
||||||
|
easing: 'linear', // cubic-bezier(0, 0, 1.0, 1.0)
|
||||||
|
easingFunction: cubicBezier(0, 0, 1.0, 1.0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'ease function',
|
||||||
|
easing: 'ease', // cubic-bezier(0.25, 0.1, 0.25, 1.0)
|
||||||
|
easingFunction: cubicBezier(0.25, 0.1, 0.25, 1.0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'ease-in function',
|
||||||
|
easing: 'ease-in', // cubic-bezier(0.42, 0, 1.0, 1.0)
|
||||||
|
easingFunction: cubicBezier(0.42, 0, 1.0, 1.0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'ease-in-out function',
|
||||||
|
easing: 'ease-in-out', // cubic-bezier(0.42, 0, 0.58, 1.0)
|
||||||
|
easingFunction: cubicBezier(0.42, 0, 0.58, 1.0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'ease-out function',
|
||||||
|
easing: 'ease-out', // cubic-bezier(0, 0, 0.58, 1.0)
|
||||||
|
easingFunction: cubicBezier(0, 0, 0.58, 1.0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'easing function which produces values greater than 1',
|
||||||
|
easing: 'cubic-bezier(0, 1.5, 1, 1.5)',
|
||||||
|
easingFunction: cubicBezier(0, 1.5, 1, 1.5)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'easing function which produces values less than 1',
|
||||||
|
easing: 'cubic-bezier(0, -0.5, 1, -0.5)',
|
||||||
|
easingFunction: cubicBezier(0, -0.5, 1, -0.5)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const gEasingParsingTests = [
|
||||||
|
['linear', 'linear'],
|
||||||
|
['ease-in-out', 'ease-in-out'],
|
||||||
|
['Ease\\2d in-out', 'ease-in-out'],
|
||||||
|
['ease /**/', 'ease'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const gInvalidEasings = [
|
||||||
|
'',
|
||||||
|
'7',
|
||||||
|
'test',
|
||||||
|
'initial',
|
||||||
|
'inherit',
|
||||||
|
'unset',
|
||||||
|
'unrecognized',
|
||||||
|
'var(--x)',
|
||||||
|
'ease-in-out, ease-out',
|
||||||
|
'cubic-bezier(1.1, 0, 1, 1)',
|
||||||
|
'cubic-bezier(0, 0, 1.1, 1)',
|
||||||
|
'cubic-bezier(-0.1, 0, 1, 1)',
|
||||||
|
'cubic-bezier(0, 0, -0.1, 1)',
|
||||||
|
'cubic-bezier(0.1, 0, 4, 0.4)',
|
||||||
|
'steps(-1, start)',
|
||||||
|
'steps(0.1, start)',
|
||||||
|
'steps(3, nowhere)',
|
||||||
|
'steps(-3, end)',
|
||||||
|
'function (a){return a}',
|
||||||
|
'function (x){return x}',
|
||||||
|
'function(x, y){return 0.3}',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Easings that should serialize to the same string
|
||||||
|
const gRoundtripEasings = [
|
||||||
|
'ease',
|
||||||
|
'linear',
|
||||||
|
'ease-in',
|
||||||
|
'ease-out',
|
||||||
|
'ease-in-out',
|
||||||
|
'cubic-bezier(0.1, 5, 0.23, 0)',
|
||||||
|
'steps(3, start)',
|
||||||
|
'steps(3)',
|
||||||
|
];
|
|
@ -0,0 +1,832 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
//
|
||||||
|
// Common keyframe test data
|
||||||
|
//
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Composite values
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gGoodKeyframeCompositeValueTests = [
|
||||||
|
'replace', 'add', 'accumulate', 'auto'
|
||||||
|
];
|
||||||
|
|
||||||
|
const gBadKeyframeCompositeValueTests = [
|
||||||
|
'unrecognised', 'replace ', 'Replace', null
|
||||||
|
];
|
||||||
|
|
||||||
|
const gGoodOptionsCompositeValueTests = [
|
||||||
|
'replace', 'add', 'accumulate'
|
||||||
|
];
|
||||||
|
|
||||||
|
const gBadOptionsCompositeValueTests = [
|
||||||
|
'unrecognised', 'replace ', 'Replace', null
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Keyframes
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gEmptyKeyframeListTests = [
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
undefined,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper methods to make defining computed keyframes more readable.
|
||||||
|
|
||||||
|
const offset = offset => ({
|
||||||
|
offset,
|
||||||
|
computedOffset: offset,
|
||||||
|
});
|
||||||
|
|
||||||
|
const computedOffset = computedOffset => ({
|
||||||
|
offset: null,
|
||||||
|
computedOffset,
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyframe = (offset, props, easing='linear', composite) => {
|
||||||
|
// The object spread operator is not yet available in all browsers so we use
|
||||||
|
// Object.assign instead.
|
||||||
|
const result = {};
|
||||||
|
Object.assign(result, offset, props, { easing });
|
||||||
|
result.composite = composite || 'auto';
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const gKeyframesTests = [
|
||||||
|
|
||||||
|
// ----------- Property-indexed keyframes: property handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a one property two value property-indexed keyframes specification',
|
||||||
|
input: { left: ['10px', '20px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '20px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one shorthand property two value property-indexed keyframes'
|
||||||
|
+ ' specification',
|
||||||
|
input: { margin: ['10px', '10px 20px 30px 40px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { margin: '10px' }),
|
||||||
|
keyframe(computedOffset(1), { margin: '10px 20px 30px 40px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property (one shorthand and one of its longhand components)'
|
||||||
|
+ ' two value property-indexed keyframes specification',
|
||||||
|
input: { marginTop: ['50px', '60px'],
|
||||||
|
margin: ['10px', '10px 20px 30px 40px'] },
|
||||||
|
output: [keyframe(computedOffset(0),
|
||||||
|
{ marginTop: '50px', margin: '10px' }),
|
||||||
|
keyframe(computedOffset(1),
|
||||||
|
{ marginTop: '60px', margin: '10px 20px 30px 40px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property (one shorthand and one of its shorthand components)'
|
||||||
|
+ ' two value property-indexed keyframes specification',
|
||||||
|
input: { border: ['pink', '2px'],
|
||||||
|
borderColor: ['green', 'blue'] },
|
||||||
|
output: [keyframe(computedOffset(0),
|
||||||
|
{ border: 'pink', borderColor: 'green' }),
|
||||||
|
keyframe(computedOffset(1),
|
||||||
|
{ border: '2px', borderColor: 'blue' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property two value property-indexed keyframes specification',
|
||||||
|
input: { left: ['10px', '20px'],
|
||||||
|
top: ['30px', '40px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px', top: '30px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '20px', top: '40px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property property-indexed keyframes specification with'
|
||||||
|
+ ' different numbers of values',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
top: ['40px', '50px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px', top: '40px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px', top: '50px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframes specification with an invalid value',
|
||||||
|
input: { left: ['10px', '20px', '30px', '40px', '50px'],
|
||||||
|
top: ['15px', '25px', 'invalid', '45px', '55px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px', top: '15px' }),
|
||||||
|
keyframe(computedOffset(0.25), { left: '20px', top: '25px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '30px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '40px', top: '45px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '50px', top: '55px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property two value property-indexed keyframes specification'
|
||||||
|
+ ' that needs to stringify its values',
|
||||||
|
input: { opacity: [0, 1] },
|
||||||
|
output: [keyframe(computedOffset(0), { opacity: '0' }),
|
||||||
|
keyframe(computedOffset(1), { opacity: '1' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframes specification with a CSS variable'
|
||||||
|
+ ' reference',
|
||||||
|
input: { left: [ 'var(--dist)', 'calc(var(--dist) + 100px)' ] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: 'var(--dist)' }),
|
||||||
|
keyframe(computedOffset(1), { left: 'calc(var(--dist) + 100px)' })]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframes specification with a CSS variable'
|
||||||
|
+ ' reference in a shorthand property',
|
||||||
|
input: { margin: [ 'var(--dist)', 'calc(var(--dist) + 100px)' ] },
|
||||||
|
output: [keyframe(computedOffset(0),
|
||||||
|
{ margin: 'var(--dist)' }),
|
||||||
|
keyframe(computedOffset(1),
|
||||||
|
{ margin: 'calc(var(--dist) + 100px)' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property one value property-indexed keyframes specification',
|
||||||
|
input: { left: ['10px'] },
|
||||||
|
output: [keyframe(computedOffset(1), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property one non-array value property-indexed keyframes'
|
||||||
|
+ ' specification',
|
||||||
|
input: { left: '10px' },
|
||||||
|
output: [keyframe(computedOffset(1), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property two value property-indexed keyframes specification'
|
||||||
|
+ ' where the first value is invalid',
|
||||||
|
input: { left: ['invalid', '10px'] },
|
||||||
|
output: [keyframe(computedOffset(0), {}),
|
||||||
|
keyframe(computedOffset(1), { left: '10px' })]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property two value property-indexed keyframes specification'
|
||||||
|
+ ' where the second value is invalid',
|
||||||
|
input: { left: ['10px', 'invalid'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(1), {})]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframes specification with a CSS variable as'
|
||||||
|
+ ' the property',
|
||||||
|
input: { '--custom': ['1', '2'] },
|
||||||
|
output: [keyframe(computedOffset(0), { '--custom': '1' }),
|
||||||
|
keyframe(computedOffset(1), { '--custom': '2' })]
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Property-indexed keyframes: offset handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single offset',
|
||||||
|
input: { left: ['10px', '20px', '30px'], offset: 0.5 },
|
||||||
|
output: [keyframe(offset(0.5), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets',
|
||||||
|
input: { left: ['10px', '20px', '30px'], offset: [ 0.1, 0.25, 0.8 ] },
|
||||||
|
output: [keyframe(offset(0.1), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(offset(0.8), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets that is too'
|
||||||
|
+ ' short',
|
||||||
|
input: { left: ['10px', '20px', '30px'], offset: [ 0, 0.25 ] },
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets that is too'
|
||||||
|
+ ' long',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ 0, 0.25, 0.5, 0.75, 1 ] },
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(offset(0.5), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an empty array of offsets',
|
||||||
|
input: { left: ['10px', '20px', '30px'], offset: [] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets with an'
|
||||||
|
+ ' embedded null value',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ 0, null, 0.5 ] },
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.25), { left: '20px' }),
|
||||||
|
keyframe(offset(0.5), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets with a'
|
||||||
|
+ ' trailing null value',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ 0, 0.25, null ] },
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets with leading'
|
||||||
|
+ ' and trailing null values',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ null, 0.25, null ] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets with'
|
||||||
|
+ ' adjacent null values',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ null, null, 0.5 ] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.25), { left: '20px' }),
|
||||||
|
keyframe(offset(0.5), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets with'
|
||||||
|
+ ' all null values (and too many at that)',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ null, null, null, null, null ] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single null offset',
|
||||||
|
input: { left: ['10px', '20px', '30px'], offset: null },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of offsets that is not'
|
||||||
|
+ ' strictly ascending in the unused part of the array',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
offset: [ 0, 0.2, 0.8, 0.6 ] },
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.2), { left: '20px' }),
|
||||||
|
keyframe(offset(0.8), { left: '30px' })],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Property-indexed keyframes: easing handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe without any specified easing',
|
||||||
|
input: { left: ['10px', '20px', '30px'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'linear'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'linear'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'linear')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single easing',
|
||||||
|
input: { left: ['10px', '20px', '30px'], easing: 'ease-in' },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'ease-in')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of easings',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
easing: ['ease-in', 'ease-out', 'ease-in-out'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'ease-out'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'ease-in-out')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of easings that is too'
|
||||||
|
+ ' short',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
easing: ['ease-in', 'ease-out'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'ease-out'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'ease-in')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single-element array of'
|
||||||
|
+ ' easings',
|
||||||
|
input: { left: ['10px', '20px', '30px'], easing: ['ease-in'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'ease-in'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'ease-in')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an empty array of easings',
|
||||||
|
input: { left: ['10px', '20px', '30px'], easing: [] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'linear'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'linear'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'linear')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with an array of easings that is too'
|
||||||
|
+ ' long',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
easing: ['steps(1)', 'steps(2)', 'steps(3)', 'steps(4)'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'steps(1)'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'steps(2)'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'steps(3)')],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Property-indexed keyframes: composite handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single composite operation',
|
||||||
|
input: { left: ['10px', '20px', '30px'], composite: 'add' },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'linear', 'add'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'linear', 'add'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'linear', 'add')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a composite array',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
composite: ['add', 'replace', 'accumulate'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' },
|
||||||
|
'linear', 'add'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' },
|
||||||
|
'linear', 'replace'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' },
|
||||||
|
'linear', 'accumulate')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a composite array that is too'
|
||||||
|
+ ' short',
|
||||||
|
input: { left: ['10px', '20px', '30px', '40px', '50px'],
|
||||||
|
composite: ['add', 'replace'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' },
|
||||||
|
'linear', 'add'),
|
||||||
|
keyframe(computedOffset(0.25), { left: '20px' },
|
||||||
|
'linear', 'replace'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '30px' },
|
||||||
|
'linear', 'add'),
|
||||||
|
keyframe(computedOffset(0.75), { left: '40px' },
|
||||||
|
'linear', 'replace'),
|
||||||
|
keyframe(computedOffset(1), { left: '50px' },
|
||||||
|
'linear', 'add')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a composite array that is too'
|
||||||
|
+ ' long',
|
||||||
|
input: { left: ['10px', '20px'],
|
||||||
|
composite: ['add', 'replace', 'accumulate'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' },
|
||||||
|
'linear', 'add'),
|
||||||
|
keyframe(computedOffset(1), { left: '20px' },
|
||||||
|
'linear', 'replace')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a property-indexed keyframe with a single-element composite array',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
composite: ['add'] },
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }, 'linear', 'add'),
|
||||||
|
keyframe(computedOffset(0.5), { left: '20px' }, 'linear', 'add'),
|
||||||
|
keyframe(computedOffset(1), { left: '30px' }, 'linear', 'add')],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Keyframe sequence: property handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a one property one keyframe sequence',
|
||||||
|
input: [{ offset: 1, left: '10px' }],
|
||||||
|
output: [keyframe(offset(1), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property two keyframe sequence',
|
||||||
|
input: [{ offset: 0, left: '10px' },
|
||||||
|
{ offset: 1, left: '20px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(1), { left: '20px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property two keyframe sequence',
|
||||||
|
input: [{ offset: 0, left: '10px', top: '30px' },
|
||||||
|
{ offset: 1, left: '20px', top: '40px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px', top: '30px' }),
|
||||||
|
keyframe(offset(1), { left: '20px', top: '40px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one shorthand property two keyframe sequence',
|
||||||
|
input: [{ offset: 0, margin: '10px' },
|
||||||
|
{ offset: 1, margin: '20px 30px 40px 50px' }],
|
||||||
|
output: [keyframe(offset(0), { margin: '10px' }),
|
||||||
|
keyframe(offset(1), { margin: '20px 30px 40px 50px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property (a shorthand and one of its component longhands)'
|
||||||
|
+ ' two keyframe sequence',
|
||||||
|
input: [{ offset: 0, margin: '10px', marginTop: '20px' },
|
||||||
|
{ offset: 1, marginTop: '70px', margin: '30px 40px 50px 60px' }],
|
||||||
|
output: [keyframe(offset(0), { margin: '10px', marginTop: '20px' }),
|
||||||
|
keyframe(offset(1), { marginTop: '70px',
|
||||||
|
margin: '30px 40px 50px 60px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property keyframe sequence where one property is missing'
|
||||||
|
+ ' from the first keyframe',
|
||||||
|
input: [{ offset: 0, left: '10px' },
|
||||||
|
{ offset: 1, left: '20px', top: '30px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(1), { left: '20px', top: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property keyframe sequence where one property is missing'
|
||||||
|
+ ' from the last keyframe',
|
||||||
|
input: [{ offset: 0, left: '10px', top: '20px' },
|
||||||
|
{ offset: 1, left: '30px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px', top: '20px' }),
|
||||||
|
keyframe(offset(1), { left: '30px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property two keyframe sequence that needs to stringify'
|
||||||
|
+ ' its values',
|
||||||
|
input: [{ offset: 0, opacity: 0 },
|
||||||
|
{ offset: 1, opacity: 1 }],
|
||||||
|
output: [keyframe(offset(0), { opacity: '0' }),
|
||||||
|
keyframe(offset(1), { opacity: '1' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with a CSS variable reference',
|
||||||
|
input: [{ left: 'var(--dist)' },
|
||||||
|
{ left: 'calc(var(--dist) + 100px)' }],
|
||||||
|
output: [keyframe(computedOffset(0), { left: 'var(--dist)' }),
|
||||||
|
keyframe(computedOffset(1), { left: 'calc(var(--dist) + 100px)' })]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with a CSS variable reference in a shorthand'
|
||||||
|
+ ' property',
|
||||||
|
input: [{ margin: 'var(--dist)' },
|
||||||
|
{ margin: 'calc(var(--dist) + 100px)' }],
|
||||||
|
output: [keyframe(computedOffset(0),
|
||||||
|
{ margin: 'var(--dist)' }),
|
||||||
|
keyframe(computedOffset(1),
|
||||||
|
{ margin: 'calc(var(--dist) + 100px)' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with a CSS variable as its property',
|
||||||
|
input: [{ '--custom': 'a' },
|
||||||
|
{ '--custom': 'b' }],
|
||||||
|
output: [keyframe(computedOffset(0), { '--custom': 'a' }),
|
||||||
|
keyframe(computedOffset(1), { '--custom': 'b' })]
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Keyframe sequence: offset handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with duplicate values for a given interior'
|
||||||
|
+ ' offset',
|
||||||
|
input: [{ offset: 0.0, left: '10px' },
|
||||||
|
{ offset: 0.5, left: '20px' },
|
||||||
|
{ offset: 0.5, left: '30px' },
|
||||||
|
{ offset: 0.5, left: '40px' },
|
||||||
|
{ offset: 1.0, left: '50px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.5), { left: '20px' }),
|
||||||
|
keyframe(offset(0.5), { left: '30px' }),
|
||||||
|
keyframe(offset(0.5), { left: '40px' }),
|
||||||
|
keyframe(offset(1), { left: '50px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with duplicate values for offsets 0 and 1',
|
||||||
|
input: [{ offset: 0, left: '10px' },
|
||||||
|
{ offset: 0, left: '20px' },
|
||||||
|
{ offset: 0, left: '30px' },
|
||||||
|
{ offset: 1, left: '40px' },
|
||||||
|
{ offset: 1, left: '50px' },
|
||||||
|
{ offset: 1, left: '60px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0), { left: '20px' }),
|
||||||
|
keyframe(offset(0), { left: '30px' }),
|
||||||
|
keyframe(offset(1), { left: '40px' }),
|
||||||
|
keyframe(offset(1), { left: '50px' }),
|
||||||
|
keyframe(offset(1), { left: '60px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property four keyframe sequence',
|
||||||
|
input: [{ offset: 0, left: '10px' },
|
||||||
|
{ offset: 0, top: '20px' },
|
||||||
|
{ offset: 1, top: '30px' },
|
||||||
|
{ offset: 1, left: '40px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0), { top: '20px' }),
|
||||||
|
keyframe(offset(1), { top: '30px' }),
|
||||||
|
keyframe(offset(1), { left: '40px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a single keyframe sequence with omitted offset',
|
||||||
|
input: [{ left: '10px' }],
|
||||||
|
output: [keyframe(computedOffset(1), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a single keyframe sequence with null offset',
|
||||||
|
input: [{ offset: null, left: '10px' }],
|
||||||
|
output: [keyframe(computedOffset(1), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a single keyframe sequence with string offset',
|
||||||
|
input: [{ offset: '0.5', left: '10px' }],
|
||||||
|
output: [keyframe(offset(0.5), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a single keyframe sequence with a single calc() offset',
|
||||||
|
input: [{ offset: 'calc(0.5)', left: '10px' }],
|
||||||
|
output: [keyframe(offset(0.5), { left: '10px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property keyframe sequence with some omitted offsets',
|
||||||
|
input: [{ offset: 0.00, left: '10px' },
|
||||||
|
{ offset: 0.25, left: '20px' },
|
||||||
|
{ left: '30px' },
|
||||||
|
{ left: '40px' },
|
||||||
|
{ offset: 1.00, left: '50px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '30px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '40px' }),
|
||||||
|
keyframe(offset(1), { left: '50px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property keyframe sequence with some null offsets',
|
||||||
|
input: [{ offset: 0.00, left: '10px' },
|
||||||
|
{ offset: 0.25, left: '20px' },
|
||||||
|
{ offset: null, left: '30px' },
|
||||||
|
{ offset: null, left: '40px' },
|
||||||
|
{ offset: 1.00, left: '50px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }),
|
||||||
|
keyframe(offset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '30px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '40px' }),
|
||||||
|
keyframe(offset(1), { left: '50px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a two property keyframe sequence with some omitted offsets',
|
||||||
|
input: [{ offset: 0.00, left: '10px', top: '20px' },
|
||||||
|
{ offset: 0.25, left: '30px' },
|
||||||
|
{ left: '40px' },
|
||||||
|
{ left: '50px', top: '60px' },
|
||||||
|
{ offset: 1.00, left: '70px', top: '80px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px', top: '20px' }),
|
||||||
|
keyframe(offset(0.25), { left: '30px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '40px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '50px', top: '60px' }),
|
||||||
|
keyframe(offset(1), { left: '70px', top: '80px' })],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a one property keyframe sequence with all omitted offsets',
|
||||||
|
input: [{ left: '10px' },
|
||||||
|
{ left: '20px' },
|
||||||
|
{ left: '30px' },
|
||||||
|
{ left: '40px' },
|
||||||
|
{ left: '50px' }],
|
||||||
|
output: [keyframe(computedOffset(0), { left: '10px' }),
|
||||||
|
keyframe(computedOffset(0.25), { left: '20px' }),
|
||||||
|
keyframe(computedOffset(0.5), { left: '30px' }),
|
||||||
|
keyframe(computedOffset(0.75), { left: '40px' }),
|
||||||
|
keyframe(computedOffset(1), { left: '50px' })],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Keyframe sequence: easing handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with different easing values, but the same'
|
||||||
|
+ ' easing value for a given offset',
|
||||||
|
input: [{ offset: 0.0, easing: 'ease', left: '10px'},
|
||||||
|
{ offset: 0.0, easing: 'ease', top: '20px'},
|
||||||
|
{ offset: 0.5, easing: 'linear', left: '30px' },
|
||||||
|
{ offset: 0.5, easing: 'linear', top: '40px' },
|
||||||
|
{ offset: 1.0, easing: 'step-end', left: '50px' },
|
||||||
|
{ offset: 1.0, easing: 'step-end', top: '60px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }, 'ease'),
|
||||||
|
keyframe(offset(0), { top: '20px' }, 'ease'),
|
||||||
|
keyframe(offset(0.5), { left: '30px' }, 'linear'),
|
||||||
|
keyframe(offset(0.5), { top: '40px' }, 'linear'),
|
||||||
|
keyframe(offset(1), { left: '50px' }, 'steps(1)'),
|
||||||
|
keyframe(offset(1), { top: '60px' }, 'steps(1)')],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------- Keyframe sequence: composite handling -----------
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with different composite values, but the'
|
||||||
|
+ ' same composite value for a given offset',
|
||||||
|
input: [{ offset: 0.0, composite: 'replace', left: '10px' },
|
||||||
|
{ offset: 0.0, composite: 'replace', top: '20px' },
|
||||||
|
{ offset: 0.5, composite: 'add', left: '30px' },
|
||||||
|
{ offset: 0.5, composite: 'add', top: '40px' },
|
||||||
|
{ offset: 1.0, composite: 'replace', left: '50px' },
|
||||||
|
{ offset: 1.0, composite: 'replace', top: '60px' }],
|
||||||
|
output: [keyframe(offset(0), { left: '10px' }, 'linear', 'replace'),
|
||||||
|
keyframe(offset(0), { top: '20px' }, 'linear', 'replace'),
|
||||||
|
keyframe(offset(0.5), { left: '30px' }, 'linear', 'add'),
|
||||||
|
keyframe(offset(0.5), { top: '40px' }, 'linear', 'add'),
|
||||||
|
keyframe(offset(1), { left: '50px' }, 'linear', 'replace'),
|
||||||
|
keyframe(offset(1), { top: '60px' }, 'linear', 'replace')],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const gInvalidKeyframesTests = [
|
||||||
|
{
|
||||||
|
desc: 'keyframes with an out-of-bounded positive offset',
|
||||||
|
input: [ { opacity: 0 },
|
||||||
|
{ opacity: 0.5, offset: 2 },
|
||||||
|
{ opacity: 1 } ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'keyframes with an out-of-bounded negative offset',
|
||||||
|
input: [ { opacity: 0 },
|
||||||
|
{ opacity: 0.5, offset: -1 },
|
||||||
|
{ opacity: 1 } ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes not loosely sorted by offset',
|
||||||
|
input: { opacity: [ 0, 1 ], offset: [ 1, 0 ] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes not loosely sorted by offset even'
|
||||||
|
+ ' though not all offsets are specified',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ], offset: [ 0.5, 0 ] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes with offsets out of range',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ], offset: [ 0, 1.1 ] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'keyframes not loosely sorted by offset',
|
||||||
|
input: [ { opacity: 0, offset: 1 },
|
||||||
|
{ opacity: 1, offset: 0 } ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes with an invalid easing value',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ],
|
||||||
|
easing: 'inherit' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes with an invalid easing value as one of'
|
||||||
|
+ ' the array values',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ],
|
||||||
|
easing: [ 'ease-in', 'inherit' ] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframe with an invalid easing in the unused'
|
||||||
|
+ ' part of the array of easings',
|
||||||
|
input: { left: ['10px', '20px', '30px'],
|
||||||
|
easing: ['steps(1)', 'steps(2)', 'steps(3)', 'invalid'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'empty property-indexed keyframe with an invalid easing',
|
||||||
|
input: { easing: 'invalid' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'empty property-indexed keyframe with an invalid easings array',
|
||||||
|
input: { easing: ['invalid'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a keyframe sequence with an invalid easing value',
|
||||||
|
input: [ { opacity: 0, easing: 'jumpy' },
|
||||||
|
{ opacity: 1 } ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes with an invalid composite value',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ],
|
||||||
|
composite: 'alternate' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'property-indexed keyframes with an invalid composite value as one'
|
||||||
|
+ ' of the array values',
|
||||||
|
input: { opacity: [ 0, 0.5, 1 ],
|
||||||
|
composite: [ 'add', 'alternate' ] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'keyframes with an invalid composite value',
|
||||||
|
input: [ { opacity: 0, composite: 'alternate' },
|
||||||
|
{ opacity: 1 } ],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const gKeyframeSerializationTests = [
|
||||||
|
{
|
||||||
|
desc: 'a on keyframe sequence which requires value serilaization of its'
|
||||||
|
+ ' values',
|
||||||
|
input: [{offset: 0, backgroundColor: 'rgb(1,2,3)' }],
|
||||||
|
output: [keyframe(offset(0), { backgroundColor: 'rgb(1, 2, 3)' })],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// KeyframeEffectOptions
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gKeyframeEffectOptionTests = [
|
||||||
|
{
|
||||||
|
desc: 'an empty KeyframeEffectOptions object',
|
||||||
|
input: { },
|
||||||
|
expected: { },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a normal KeyframeEffectOptions object',
|
||||||
|
input: { delay: 1000,
|
||||||
|
fill: 'auto',
|
||||||
|
iterations: 5.5,
|
||||||
|
duration: 'auto',
|
||||||
|
direction: 'alternate' },
|
||||||
|
expected: { delay: 1000,
|
||||||
|
fill: 'auto',
|
||||||
|
iterations: 5.5,
|
||||||
|
duration: 'auto',
|
||||||
|
direction: 'alternate' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a double value',
|
||||||
|
input: 3000,
|
||||||
|
expected: { duration: 3000 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: '+Infinity',
|
||||||
|
input: Infinity,
|
||||||
|
expected: { duration: Infinity },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'an Infinity duration',
|
||||||
|
input: { duration: Infinity },
|
||||||
|
expected: { duration: Infinity },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'an auto duration',
|
||||||
|
input: { duration: 'auto' },
|
||||||
|
expected: { duration: 'auto' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'an Infinity iterations',
|
||||||
|
input: { iterations: Infinity },
|
||||||
|
expected: { iterations: Infinity },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'an auto fill',
|
||||||
|
input: { fill: 'auto' },
|
||||||
|
expected: { fill: 'auto' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'a forwards fill',
|
||||||
|
input: { fill: 'forwards' },
|
||||||
|
expected: { fill: 'forwards' },
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const gInvalidKeyframeEffectOptionTests = [
|
||||||
|
{ desc: '-Infinity', input: -Infinity },
|
||||||
|
{ desc: 'NaN', input: NaN },
|
||||||
|
{ desc: 'a negative value', input: -1 },
|
||||||
|
{ desc: 'a negative Infinity duration', input: { duration: -Infinity } },
|
||||||
|
{ desc: 'a NaN duration', input: { duration: NaN } },
|
||||||
|
{ desc: 'a negative duration', input: { duration: -1 } },
|
||||||
|
{ desc: 'a string duration', input: { duration: 'merrychristmas' } },
|
||||||
|
{ desc: 'a negative Infinity iterations', input: { iterations: -Infinity} },
|
||||||
|
{ desc: 'a NaN iterations', input: { iterations: NaN } },
|
||||||
|
{ desc: 'a negative iterations', input: { iterations: -1 } },
|
||||||
|
{ desc: 'a blank easing', input: { easing: '' } },
|
||||||
|
{ desc: 'an unrecognized easing', input: { easing: 'unrecognised' } },
|
||||||
|
{ desc: 'an \'initial\' easing', input: { easing: 'initial' } },
|
||||||
|
{ desc: 'an \'inherit\' easing', input: { easing: 'inherit' } },
|
||||||
|
{ desc: 'a variable easing', input: { easing: 'var(--x)' } },
|
||||||
|
{ desc: 'a multi-value easing', input: { easing: 'ease-in-out, ease-out' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// There is currently only ScrollTimeline that can be constructed and used here
|
||||||
|
// beyond document timeline. Given that ScrollTimeline is not stable as of yet
|
||||||
|
// it's tested in scroll-animations/animation-with-animatable-interface.html.
|
||||||
|
const gAnimationTimelineTests = [
|
||||||
|
{
|
||||||
|
expectedTimeline: document.timeline,
|
||||||
|
expectedTimelineDescription: 'document.timeline',
|
||||||
|
description: 'with no timeline parameter'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeline: undefined,
|
||||||
|
expectedTimeline: document.timeline,
|
||||||
|
expectedTimelineDescription: 'document.timeline',
|
||||||
|
description: 'with undefined timeline'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeline: null,
|
||||||
|
expectedTimeline: null,
|
||||||
|
expectedTimelineDescription: 'null',
|
||||||
|
description: 'with null timeline'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeline: document.timeline,
|
||||||
|
expectedTimeline: document.timeline,
|
||||||
|
expectedTimelineDescription: 'document.timeline',
|
||||||
|
description: 'with DocumentTimeline'
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,51 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// =======================================
|
||||||
|
//
|
||||||
|
// Utility functions for testing keyframes
|
||||||
|
//
|
||||||
|
// =======================================
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Helper functions
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test equality between two lists of computed keyframes
|
||||||
|
* @param {Array.<ComputedKeyframe>} a - actual computed keyframes
|
||||||
|
* @param {Array.<ComputedKeyframe>} b - expected computed keyframes
|
||||||
|
*/
|
||||||
|
function assert_frame_lists_equal(a, b, message) {
|
||||||
|
assert_equals(a.length, b.length, `number of frames: ${(message || '')}`);
|
||||||
|
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
||||||
|
assert_frames_equal(a[i], b[i],
|
||||||
|
`ComputedKeyframe #${i}: ${(message || '')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper for assert_frame_lists_equal */
|
||||||
|
function assert_frames_equal(a, b, name) {
|
||||||
|
assert_equals(Object.keys(a).sort().toString(),
|
||||||
|
Object.keys(b).sort().toString(),
|
||||||
|
`properties on ${name} should match`);
|
||||||
|
// Iterates sorted keys to ensure stable failures.
|
||||||
|
for (const p of Object.keys(a).sort()) {
|
||||||
|
if (typeof b[p] == 'number')
|
||||||
|
assert_approx_equals(a[p], b[p], 1e-6, `value for '${p}' on ${name}`);
|
||||||
|
else if (typeof b[p] == 'object') {
|
||||||
|
for (const key in b[p]) {
|
||||||
|
if (typeof b[p][key] == 'number') {
|
||||||
|
assert_approx_equals(a[p][key], b[p][key], 1e-6,
|
||||||
|
`value for '${p}.${key}' on ${name}`);
|
||||||
|
} else {
|
||||||
|
assert_equals((a[p][key] || 'undefined').toString(),
|
||||||
|
b[p][key].toString(),
|
||||||
|
`value for '${p}.${key}' on ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
assert_equals(a[p], b[p], `value for '${p}' on ${name}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// =================================
|
||||||
|
//
|
||||||
|
// Common timing parameter test data
|
||||||
|
//
|
||||||
|
// =================================
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Delay values
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gBadDelayValues = [
|
||||||
|
NaN, Infinity, -Infinity
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Duration values
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gGoodDurationValues = [
|
||||||
|
{ specified: 123.45, computed: 123.45 },
|
||||||
|
{ specified: 'auto', computed: 0 },
|
||||||
|
{ specified: Infinity, computed: Infinity },
|
||||||
|
];
|
||||||
|
|
||||||
|
const gBadDurationValues = [
|
||||||
|
-1, NaN, -Infinity, 'abc', '100'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// iterationStart values
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gBadIterationStartValues = [
|
||||||
|
-1, NaN, Infinity, -Infinity
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// iterations values
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
const gBadIterationsValues = [
|
||||||
|
-1, -Infinity, NaN
|
||||||
|
];
|
|
@ -0,0 +1,52 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// =======================================
|
||||||
|
//
|
||||||
|
// Utility functions for testing timing
|
||||||
|
//
|
||||||
|
// =======================================
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Helper functions
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// Utility function to check that a subset of timing properties have their
|
||||||
|
// default values.
|
||||||
|
function assert_default_timing_except(effect, propertiesToSkip) {
|
||||||
|
const defaults = {
|
||||||
|
delay: 0,
|
||||||
|
endDelay: 0,
|
||||||
|
fill: 'auto',
|
||||||
|
iterationStart: 0,
|
||||||
|
iterations: 1,
|
||||||
|
duration: 'auto',
|
||||||
|
direction: 'normal',
|
||||||
|
easing: 'linear',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const prop of Object.keys(defaults)) {
|
||||||
|
if (propertiesToSkip.includes(prop)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equals(
|
||||||
|
effect.getTiming()[prop],
|
||||||
|
defaults[prop],
|
||||||
|
`${prop} parameter has default value:`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForAnimationTime(animation, time) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
function raf() {
|
||||||
|
if (animation.currentTime < time) {
|
||||||
|
requestAnimationFrame(raf);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestAnimationFrame(raf);
|
||||||
|
});
|
||||||
|
}
|
351
Tests/LibWeb/Text/input/wpt-import/web-animations/testcommon.js
Normal file
351
Tests/LibWeb/Text/input/wpt-import/web-animations/testcommon.js
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const MS_PER_SEC = 1000;
|
||||||
|
|
||||||
|
// The recommended minimum precision to use for time values[1].
|
||||||
|
//
|
||||||
|
// [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
|
||||||
|
const TIME_PRECISION = 0.0005; // ms
|
||||||
|
|
||||||
|
// Allow implementations to substitute an alternative method for comparing
|
||||||
|
// times based on their precision requirements.
|
||||||
|
if (!window.assert_times_equal) {
|
||||||
|
window.assert_times_equal = (actual, expected, description) => {
|
||||||
|
assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow implementations to substitute an alternative method for comparing
|
||||||
|
// times based on their precision requirements.
|
||||||
|
if (!window.assert_time_greater_than_equal) {
|
||||||
|
window.assert_time_greater_than_equal = (actual, expected, description) => {
|
||||||
|
assert_greater_than_equal(actual, expected - 2 * TIME_PRECISION,
|
||||||
|
description);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow implementations to substitute an alternative method for comparing
|
||||||
|
// a time value based on its precision requirements with a fixed value.
|
||||||
|
if (!window.assert_time_equals_literal) {
|
||||||
|
window.assert_time_equals_literal = (actual, expected, description) => {
|
||||||
|
if (Math.abs(expected) === Infinity) {
|
||||||
|
assert_equals(actual, expected, description);
|
||||||
|
} else {
|
||||||
|
assert_approx_equals(actual, expected, TIME_PRECISION, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates div element, appends it to the document body and
|
||||||
|
// removes the created element during test cleanup
|
||||||
|
function createDiv(test, doc) {
|
||||||
|
return createElement(test, 'div', doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates element of given tagName, appends it to the document body and
|
||||||
|
// removes the created element during test cleanup
|
||||||
|
// if tagName is null or undefined, returns div element
|
||||||
|
function createElement(test, tagName, doc) {
|
||||||
|
if (!doc) {
|
||||||
|
doc = document;
|
||||||
|
}
|
||||||
|
const element = doc.createElement(tagName || 'div');
|
||||||
|
doc.body.appendChild(element);
|
||||||
|
test.add_cleanup(() => {
|
||||||
|
element.remove();
|
||||||
|
});
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a style element with the specified rules, appends it to the document
|
||||||
|
// head and removes the created element during test cleanup.
|
||||||
|
// |rules| is an object. For example:
|
||||||
|
// { '@keyframes anim': '' ,
|
||||||
|
// '.className': 'animation: anim 100s;' };
|
||||||
|
// or
|
||||||
|
// { '.className1::before': 'content: ""; width: 0px; transition: all 10s;',
|
||||||
|
// '.className2::before': 'width: 100px;' };
|
||||||
|
// The object property name could be a keyframes name, or a selector.
|
||||||
|
// The object property value is declarations which are property:value pairs
|
||||||
|
// split by a space.
|
||||||
|
function createStyle(test, rules, doc) {
|
||||||
|
if (!doc) {
|
||||||
|
doc = document;
|
||||||
|
}
|
||||||
|
const extraStyle = doc.createElement('style');
|
||||||
|
doc.head.appendChild(extraStyle);
|
||||||
|
if (rules) {
|
||||||
|
const sheet = extraStyle.sheet;
|
||||||
|
for (const selector in rules) {
|
||||||
|
sheet.insertRule(`${selector}{${rules[selector]}}`,
|
||||||
|
sheet.cssRules.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test.add_cleanup(() => {
|
||||||
|
extraStyle.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
|
||||||
|
function cubicBezier(x1, y1, x2, y2) {
|
||||||
|
const xForT = t => {
|
||||||
|
const omt = 1-t;
|
||||||
|
return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
|
||||||
|
};
|
||||||
|
|
||||||
|
const yForT = t => {
|
||||||
|
const omt = 1-t;
|
||||||
|
return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tForX = x => {
|
||||||
|
// Binary subdivision.
|
||||||
|
let mint = 0, maxt = 1;
|
||||||
|
for (let i = 0; i < 30; ++i) {
|
||||||
|
const guesst = (mint + maxt) / 2;
|
||||||
|
const guessx = xForT(guesst);
|
||||||
|
if (x < guessx) {
|
||||||
|
maxt = guesst;
|
||||||
|
} else {
|
||||||
|
mint = guesst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (mint + maxt) / 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
return x => {
|
||||||
|
if (x == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (x == 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return yForT(tForX(x));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepEnd(nsteps) {
|
||||||
|
return x => Math.floor(x * nsteps) / nsteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepStart(nsteps) {
|
||||||
|
return x => {
|
||||||
|
const result = Math.floor(x * nsteps + 1.0) / nsteps;
|
||||||
|
return (result > 1.0) ? 1.0 : result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForAnimationFrames(frameCount) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
function handleFrame() {
|
||||||
|
if (--frameCount <= 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
window.requestAnimationFrame(handleFrame); // wait another frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(handleFrame);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continually calls requestAnimationFrame until |minDelay| has elapsed
|
||||||
|
// as recorded using document.timeline.currentTime (i.e. frame time not
|
||||||
|
// wall-clock time).
|
||||||
|
function waitForAnimationFramesWithDelay(minDelay) {
|
||||||
|
const startTime = document.timeline.currentTime;
|
||||||
|
return new Promise(resolve => {
|
||||||
|
(function handleFrame() {
|
||||||
|
if (document.timeline.currentTime - startTime >= minDelay) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
window.requestAnimationFrame(handleFrame);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAndWaitForFrameUpdate(callback) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
callback();
|
||||||
|
window.requestAnimationFrame(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waits for a requestAnimationFrame callback in the next refresh driver tick.
|
||||||
|
function waitForNextFrame() {
|
||||||
|
const timeAtStart = document.timeline.currentTime;
|
||||||
|
return new Promise(resolve => {
|
||||||
|
(function handleFrame() {
|
||||||
|
if (timeAtStart === document.timeline.currentTime) {
|
||||||
|
window.requestAnimationFrame(handleFrame);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function insertFrameAndAwaitLoad(test, iframe, doc) {
|
||||||
|
const eventWatcher = new EventWatcher(test, iframe, ['load']);
|
||||||
|
const event_promise = eventWatcher.wait_for('load');
|
||||||
|
|
||||||
|
doc.body.appendChild(iframe);
|
||||||
|
test.add_cleanup(() => { doc.body.removeChild(iframe); });
|
||||||
|
|
||||||
|
await event_promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 'matrix()' or 'matrix3d()' function string generated from an array.
|
||||||
|
function createMatrixFromArray(array) {
|
||||||
|
return (array.length == 16 ? 'matrix3d' : 'matrix') + `(${array.join()})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 'matrix3d()' function string equivalent to
|
||||||
|
// 'rotate3d(x, y, z, radian)'.
|
||||||
|
function rotate3dToMatrix3d(x, y, z, radian) {
|
||||||
|
return createMatrixFromArray(rotate3dToMatrix(x, y, z, radian));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array of the 4x4 matrix equivalent to 'rotate3d(x, y, z, radian)'.
|
||||||
|
// https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined
|
||||||
|
function rotate3dToMatrix(x, y, z, radian) {
|
||||||
|
const sc = Math.sin(radian / 2) * Math.cos(radian / 2);
|
||||||
|
const sq = Math.sin(radian / 2) * Math.sin(radian / 2);
|
||||||
|
|
||||||
|
// Normalize the vector.
|
||||||
|
const length = Math.sqrt(x*x + y*y + z*z);
|
||||||
|
x /= length;
|
||||||
|
y /= length;
|
||||||
|
z /= length;
|
||||||
|
|
||||||
|
return [
|
||||||
|
1 - 2 * (y*y + z*z) * sq,
|
||||||
|
2 * (x * y * sq + z * sc),
|
||||||
|
2 * (x * z * sq - y * sc),
|
||||||
|
0,
|
||||||
|
2 * (x * y * sq - z * sc),
|
||||||
|
1 - 2 * (x*x + z*z) * sq,
|
||||||
|
2 * (y * z * sq + x * sc),
|
||||||
|
0,
|
||||||
|
2 * (x * z * sq + y * sc),
|
||||||
|
2 * (y * z * sq - x * sc),
|
||||||
|
1 - 2 * (x*x + y*y) * sq,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare matrix string like 'matrix(1, 0, 0, 1, 100, 0)' with tolerances.
|
||||||
|
function assert_matrix_equals(actual, expected, description) {
|
||||||
|
const matrixRegExp = /^matrix(?:3d)*\((.+)\)/;
|
||||||
|
assert_regexp_match(actual, matrixRegExp,
|
||||||
|
'Actual value is not a matrix')
|
||||||
|
assert_regexp_match(expected, matrixRegExp,
|
||||||
|
'Expected value is not a matrix');
|
||||||
|
|
||||||
|
const actualMatrixArray =
|
||||||
|
actual.match(matrixRegExp)[1].split(',').map(Number);
|
||||||
|
const expectedMatrixArray =
|
||||||
|
expected.match(matrixRegExp)[1].split(',').map(Number);
|
||||||
|
|
||||||
|
assert_equals(actualMatrixArray.length, expectedMatrixArray.length,
|
||||||
|
`dimension of the matrix: ${description}`);
|
||||||
|
for (let i = 0; i < actualMatrixArray.length; i++) {
|
||||||
|
assert_approx_equals(actualMatrixArray[i], expectedMatrixArray[i], 0.0001,
|
||||||
|
`expected ${expected} but got ${actual}: ${description}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare rotate3d vector like '0 1 0 45deg' with tolerances.
|
||||||
|
function assert_rotate3d_equals(actual, expected, description) {
|
||||||
|
const rotationRegExp =/^((([+-]?\d+(\.+\d+)?\s){3})?\d+(\.+\d+)?)deg/;
|
||||||
|
|
||||||
|
assert_regexp_match(actual, rotationRegExp,
|
||||||
|
'Actual value is not a rotate3d vector')
|
||||||
|
assert_regexp_match(expected, rotationRegExp,
|
||||||
|
'Expected value is not a rotate3d vector');
|
||||||
|
|
||||||
|
const actualRotationVector =
|
||||||
|
actual.match(rotationRegExp)[1].split(' ').map(Number);
|
||||||
|
const expectedRotationVector =
|
||||||
|
expected.match(rotationRegExp)[1].split(' ').map(Number);
|
||||||
|
|
||||||
|
assert_equals(actualRotationVector.length, expectedRotationVector.length,
|
||||||
|
`dimension of the matrix: ${description}`);
|
||||||
|
for (let i = 0; i < actualRotationVector.length; i++) {
|
||||||
|
assert_approx_equals(
|
||||||
|
actualRotationVector[i],
|
||||||
|
expectedRotationVector[i],
|
||||||
|
0.0001,
|
||||||
|
`expected ${expected} but got ${actual}: ${description}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_phase_at_time(animation, phase, currentTime) {
|
||||||
|
animation.currentTime = currentTime;
|
||||||
|
assert_phase(animation, phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_phase(animation, phase) {
|
||||||
|
const fillMode = animation.effect.getTiming().fill;
|
||||||
|
const currentTime = animation.currentTime;
|
||||||
|
|
||||||
|
if (phase === 'active') {
|
||||||
|
// If the fill mode is 'none', then progress will only be non-null if we
|
||||||
|
// are in the active phase, except for progress-based timelines where
|
||||||
|
// currentTime = 100% is still 'active'.
|
||||||
|
animation.effect.updateTiming({ fill: 'none' });
|
||||||
|
if ('ScrollTimeline' in window && animation.timeline instanceof ScrollTimeline) {
|
||||||
|
const isActive = animation.currentTime?.toString() == "100%" ||
|
||||||
|
animation.effect.getComputedTiming().progress != null;
|
||||||
|
assert_true(isActive,
|
||||||
|
'Animation effect is in active phase when current time ' +
|
||||||
|
`is ${currentTime}.`);
|
||||||
|
} else {
|
||||||
|
assert_not_equals(animation.effect.getComputedTiming().progress, null,
|
||||||
|
'Animation effect is in active phase when current time ' +
|
||||||
|
`is ${currentTime}.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The easiest way to distinguish between the 'before' phase and the 'after'
|
||||||
|
// phase is to toggle the fill mode. For example, if the progress is null
|
||||||
|
// when the fill mode is 'none' but non-null when the fill mode is
|
||||||
|
// 'backwards' then we are in the before phase.
|
||||||
|
animation.effect.updateTiming({ fill: 'none' });
|
||||||
|
assert_equals(animation.effect.getComputedTiming().progress, null,
|
||||||
|
`Animation effect is in ${phase} phase when current time ` +
|
||||||
|
`is ${currentTime} (progress is null with 'none' fill mode)`);
|
||||||
|
|
||||||
|
animation.effect.updateTiming({
|
||||||
|
fill: phase === 'before' ? 'backwards' : 'forwards',
|
||||||
|
});
|
||||||
|
assert_not_equals(animation.effect.getComputedTiming().progress, null,
|
||||||
|
`Animation effect is in ${phase} phase when current ` +
|
||||||
|
`time is ${currentTime} (progress is non-null with ` +
|
||||||
|
`appropriate fill mode)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset fill mode to avoid side-effects.
|
||||||
|
animation.effect.updateTiming({ fill: fillMode });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use with reftest-wait to wait until compositor commits are no longer deferred
|
||||||
|
// before taking the screenshot.
|
||||||
|
// crbug.com/1378671
|
||||||
|
async function waitForCompositorReady() {
|
||||||
|
const animation =
|
||||||
|
document.body.animate({ opacity: [ 0, 1 ] }, {duration: 1 });
|
||||||
|
return animation.finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function takeScreenshotOnAnimationsReady() {
|
||||||
|
await Promise.all(document.getAnimations().map(a => a.ready));
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue