LibWeb: Import tests related to CSSTransition

This commit is contained in:
Callum Law
2025-08-13 23:38:10 +12:00
committed by Sam Atkins
parent 087601832a
commit 589529e081
Notes: github-actions[bot] 2025-08-18 10:19:56 +00:00
20 changed files with 3235 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSTransition.currentTime</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#csstransition">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="support/helper.js"></script>
<div id="log"></div>
<script>
'use strict';
const marginLeft = div => parseFloat(getComputedStyle(div).marginLeft);
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
assert_equals(
animation.currentTime,
0,
'currentTime should initially be zero'
);
await animation.ready;
const seekTime = 150 * MS_PER_SEC;
animation.currentTime = seekTime;
assert_time_equals_literal(
animation.currentTime,
seekTime,
'currentTime is updated'
);
assert_equals(getComputedStyle(div).marginLeft, '150px');
}, 'currentTime can be used to seek a CSS transition');
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
const eventWatcher = new EventWatcher(t, div, 'transitionend');
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
await animation.ready;
const marginLeft = () => parseFloat(getComputedStyle(div).marginLeft);
assert_equals(marginLeft(div), 100);
animation.currentTime = 100 * MS_PER_SEC;
assert_equals(marginLeft(div), 100);
animation.currentTime = 150 * MS_PER_SEC;
assert_equals(marginLeft(div), 150);
animation.currentTime = 200 * MS_PER_SEC;
await eventWatcher.wait_for('transitionend');
assert_equals(marginLeft(div), 200);
}, 'Skipping forwards through transition');
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
const eventWatcher = new EventWatcher(t, div, 'transitionend');
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
await animation.ready;
// Unlike in the case of CSS animations, we cannot skip to the end and skip
// backwards since when we reach the end the transition effect is removed and
// changes to the Animation object no longer affect the element. For
// this reason we only skip forwards as far as the 50% through point.
animation.currentTime = 150 * MS_PER_SEC;
assert_equals(marginLeft(div), 150);
animation.currentTime = 100 * MS_PER_SEC;
assert_equals(marginLeft(div), 100);
}, 'Skipping backwards through transition');
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
await animation.ready;
assert_throws_js(
TypeError,
() => {
animation.currentTime = null;
},
'Expect TypeError exception on trying to set Animation.currentTime to null'
);
}, 'Setting currentTime to null on a CSS transition throws');
test(t => {
const div = addDiv(t);
div.style.left = '0px';
getComputedStyle(div).transitionProperty;
div.style.transition = 'left 100s ease-in';
div.style.left = '100px';
const transition = div.getAnimations()[0];
// Seek to the middle. Note, this is not equivalent to 50% progress since the
// timing-function is non-linear.
transition.currentTime = 50 * MS_PER_SEC;
const portion = transition.effect.getComputedTiming().progress;
// Reverse the transition.
div.style.left = '0px';
const reversedTransition = div.getAnimations()[0];
// If the transition reversing behavior does not advance the previous
// transition to the time set by currentTime, start and end values will both
// be 0px and no transition will be produced.
assert_not_equals(reversedTransition, undefined,
"A reversed transition is produced");
const expectedDuration = 100 * MS_PER_SEC * portion;
assert_approx_equals(
reversedTransition.effect.getComputedTiming().activeDuration,
expectedDuration,
1,
"The reversed transition has correctly reduced duration"
);
}, "Transition reversing behavior respects currentTime and uses the " +
"transition's current position.");
</script>

View File

@@ -0,0 +1,73 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSTransition.ready</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#csstransition">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="support/helper.js"></script>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const div = addDiv(t);
// Set up pending transition
div.style.transform = 'translate(0px)';
getComputedStyle(div).transform;
div.style.transition = 'transform 100s';
div.style.transform = 'translate(10px)';
getComputedStyle(div).transform;
const animation = div.getAnimations()[0];
assert_true(animation.pending, 'Animation is initially pending');
const readyPromise = animation.ready;
// Now remove transform from transition-property and flush styles
div.style.transitionProperty = 'none';
getComputedStyle(div).transitionProperty;
try {
await readyPromise;
assert_unreached('ready promise was fulfilled');
} catch (err) {
assert_equals(err.name, 'AbortError',
'ready promise is rejected with AbortError');
assert_equals(animation.playState, 'idle',
'Animation is idle after transition was canceled');
}
}, 'ready promise is rejected when a transition is canceled by updating'
+ ' transition-property');
promise_test(async t => {
const div = addDiv(t);
// Set up pending transition
div.style.marginLeft = '0px';
getComputedStyle(div).marginLeft;
div.style.transition = 'margin-left 100s';
div.style.marginLeft = '100px';
getComputedStyle(div).marginLeft;
const animation = div.getAnimations()[0];
assert_true(animation.pending, 'Animation is initially pending');
const readyPromise = animation.ready;
// Update the transition to animate to something not-interpolable
div.style.marginLeft = 'auto';
getComputedStyle(div).marginLeft;
try {
await readyPromise;
assert_unreached('ready promise was fulfilled');
} catch (err) {
assert_equals(err.name, 'AbortError',
'ready promise is rejected with AbortError');
assert_equals(animation.playState, 'idle',
'Animation is idle after transition was canceled');
}
}, 'ready promise is rejected when a transition is canceled by changing'
+ ' the transition property to something not interpolable');
</script>

View File

@@ -0,0 +1,124 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSTransition.startTime</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#csstransition">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="support/helper.js"></script>
<div id="log"></div>
<script>
'use strict';
test(t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
assert_equals(animation.startTime, null, 'startTime is unresolved');
}, 'The start time of a newly-created transition is unresolved');
promise_test(async t => {
const div = addDiv(t);
div.style.left = '0px';
div.style.top = '0px';
getComputedStyle(div).transitionProperty;
div.style.transition = 'all 100s';
div.style.left = '100px';
div.style.top = '100px';
let animations = div.getAnimations();
assert_equals(animations.length, 2);
await waitForAllAnimations(animations);
assert_equals(animations[0].startTime, animations[1].startTime,
'CSS transitions started together have the same start time');
await waitForAnimationFrames(1);
div.style.backgroundColor = 'green';
animations = div.getAnimations();
assert_equals(animations.length, 3);
await waitForAllAnimations(animations);
assert_less_than(animations[1].startTime, animations[2].startTime,
'A CSS transition added in a later frame has a later start time');
}, 'The start time of transitions is based on when they are generated');
test(t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
const timelineTime = animation.timeline.currentTime;
animation.startTime = timelineTime;
assert_times_equal(
animation.startTime,
timelineTime,
'Check setting of startTime actually works'
);
}, 'The start time of a transition can be set');
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
await animation.ready;
const timelineTime = animation.timeline.currentTime;
const marginLeft = () => parseFloat(getComputedStyle(div).marginLeft);
animation.startTime = timelineTime - 100 * MS_PER_SEC;
assert_equals(marginLeft(), 100);
animation.startTime = timelineTime - 150 * MS_PER_SEC;
assert_equals(marginLeft(), 150);
}, 'The start time can be set to seek a transition');
promise_test(async t => {
const div = addDiv(t, {
style: 'margin-left: 100px; transition: margin-left 100s linear 100s',
});
const eventWatcher = new EventWatcher(t, div, [
'transitionstart',
'transitionend',
]);
getComputedStyle(div).marginLeft;
div.style.marginLeft = '200px';
const animation = div.getAnimations()[0];
await animation.ready;
const timelineTime = animation.timeline.currentTime;
animation.startTime = timelineTime - 100 * MS_PER_SEC;
await frameTimeout(
eventWatcher.wait_for('transitionstart'),
2,
'transitionstart'
);
animation.startTime = timelineTime - 200 * MS_PER_SEC;
await frameTimeout(
eventWatcher.wait_for('transitionend'),
2,
'transitionend'
);
}, 'Seeking a transition using start time dispatches transition events');
</script>

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>transition timing function</title>
<link rel="help" href="https://drafts.csswg.org/css-transitions-1/#transition-timing-function-property">
<style>
.box {
position: relative;
left: 0;
height: 100px;
width: 100px;
margin: 10px;
background-color: blue;
transition: left 1s linear;
}
.animating > .box {
left: 400px;
}
</style>
</head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/css-transitions/support/helper.js"></script>
<script src="../../../css/css-easing/testcommon.js"></script>
<script>
window.onload = () => {
function verifyPosition(element_id, expected) {
const element = document.getElementById(element_id);
const actual = Math.round(parseFloat(getComputedStyle(element).left));
assert_equals(actual, expected, `verify ${element_id} left`);
}
function easing(x1, y1, x2, y2) {
return Math.round(400 * cubicBezier(x1, y1, x2, y2)(0.5));
}
function ease() {
return easing(0.25, 0.1, 0.25, 1); // 321
}
function easeIn() {
return easing(0.42, 0, 1, 1); // 126
}
function easeOut() {
return easing(0.0, 0.0, 0.58, 1.0); // 274
}
function easeInOut() {
return easing(0.42, 0.0, 0.58, 1.0); // 200
}
promise_test(async t => {
// Make sure we have rendered the page before making the style change
// to ensure we get transitions.
await waitForAnimationFrames(2);
promises = [];
document.getElementById('container').className = 'animating';
document.getAnimations().forEach(anim => {
anim.pause();
anim.currentTime = 500;
promises.push(anim.ready);
});
await Promise.all(promises).then(() => {
assert_equals(promises.length, 6, 'Unexpected animation count');
verifyPosition('box1', 200); // linear easing
verifyPosition('box2', ease());
verifyPosition('box3', easeIn());
verifyPosition('box4', easeOut());
verifyPosition('box5', easeInOut());
verifyPosition('box6', 400);
});
}, 'Ensure that transition easing functions are properly applied.');
};
</script>
<body>
<div id="container">
<div id="box1" class="box" style="transition-timing-function: linear;"></div>
<div id="box2" class="box" style="transition-timing-function: ease;"></div>
<div id="box3" class="box" style="transition-timing-function: ease-in;"></div>
<div id="box4" class="box" style="transition-timing-function: ease-out;"></div>
<div id="box5" class="box" style="transition-timing-function: ease-in-out;"></div>
<div id="box6" class="box" style="transition-timing-function: linear(0, 1, 0);"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,512 @@
<!doctype html>
<meta charset=utf-8>
<title>CSS transition event dispatch</title>
<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#event-dispatch">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="support/helper.js"></script>
<div id="log"></div>
<script>
'use strict';
// All transition events should be received on the next animation frame. If
// two animation frames pass before receiving the expected events then we
// can immediately fail the current test.
const transitionEventsTimeout = () => {
return waitForAnimationFrames(2);
};
const setupTransition = (t, transitionStyle) => {
const div = addDiv(t, { style: 'transition: ' + transitionStyle });
const watcher = new EventWatcher(t, div, [ 'transitionrun',
'transitionstart',
'transitionend',
'transitioncancel' ],
transitionEventsTimeout);
getComputedStyle(div).marginLeft;
div.style.marginLeft = '100px';
const transition = div.getAnimations()[0];
return { transition, watcher, div };
};
// On the next frame (i.e. when events are queued), whether or not the
// transition is still pending depends on the implementation.
promise_test(async t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
const evt = await watcher.wait_for('transitionrun');
assert_equals(evt.elapsedTime, 0.0);
}, 'Idle -> Pending or Before');
promise_test(async t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Force the transition to leave the idle phase
transition.startTime = document.timeline.currentTime;
const evt = await watcher.wait_for('transitionrun');
assert_equals(evt.elapsedTime, 0.0);
}, 'Idle -> Before');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
transition.pause();
const events = await watcher.wait_for(['transitionrun', 'transitionstart'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 0.0);
}, 'Idle or Pending -> Active');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to After phase.
transition.finish();
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', 'transitionend'],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 0.0);
assert_equals(events[2].elapsedTime, 100.0);
}, 'Idle or Pending -> After');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]);
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Before -> Idle (display: none)');
promise_test(async t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]);
// Make idle
transition.timeline = null;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Before -> Idle (Animation.timeline = null)');
promise_test(async t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]);
transition.currentTime = 100 * MS_PER_SEC;
const evt = await watcher.wait_for('transitionstart');
assert_equals(evt.elapsedTime, 0.0);
}, 'Before -> Active');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]);
// Seek to After phase.
transition.currentTime = 200 * MS_PER_SEC;
const events = await watcher.wait_for(['transitionstart', 'transitionend'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 100.0);
}, 'Before -> After');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(t, 'margin-left 100s');
// Seek to Active start position.
transition.pause();
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, no delay (display: none)');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s');
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
transition.currentTime = 0;
transition.timeline = null;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, no delay (Animation.timeline = null)');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
// Pause so the currentTime is fixed and we can accurately compare the event
// time in transition cancel events.
transition.pause();
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, with positive delay (display: none)');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
transition.currentTime = 100 * MS_PER_SEC;
transition.timeline = null;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, with positive delay (Animation.timeline = null)');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s -50s');
// Pause so the currentTime is fixed and we can accurately compare the event
// time in transition cancel events.
transition.pause();
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 50.0);
}, 'Active -> Idle, with negative delay (display: none)');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s -50s');
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
transition.currentTime = 50 * MS_PER_SEC;
transition.timeline = null;
const evt = await watcher.wait_for('transitioncancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, with negative delay (Animation.timeline = null)');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Seek to Before phase.
transition.currentTime = 0;
const evt = await watcher.wait_for('transitionend');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Before');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Seek to After phase.
transition.currentTime = 200 * MS_PER_SEC;
const evt = await watcher.wait_for('transitionend');
assert_equals(evt.elapsedTime, 100.0);
}, 'Active -> After');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to After phase.
transition.finish();
await watcher.wait_for([ 'transitionrun',
'transitionstart',
'transitionend' ]);
// Seek to Before phase.
transition.currentTime = 0;
const events = await watcher.wait_for(['transitionstart', 'transitionend'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 100.0);
assert_equals(events[1].elapsedTime, 0.0);
}, 'After -> Before');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s');
// Seek to After phase.
transition.finish();
await watcher.wait_for([ 'transitionrun',
'transitionstart',
'transitionend' ]);
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
const evt = await watcher.wait_for('transitionstart');
assert_equals(evt.elapsedTime, 100.0);
}, 'After -> Active');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s -50s');
const events = await watcher.wait_for(['transitionrun', 'transitionstart'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 50.0);
assert_equals(events[1].elapsedTime, 50.0);
transition.finish();
const evt = await watcher.wait_for('transitionend');
assert_equals(evt.elapsedTime, 100.0);
}, 'Calculating the interval start and end time with negative start delay.');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(
t,
'margin-left 100s 100s'
);
await watcher.wait_for('transitionrun');
// We can't set the end delay via generated effect timing
// because mutating CSS transitions is not specced yet.
transition.effect = new KeyframeEffect(
div,
{ marginLeft: ['0px', '100px'] },
{
duration: 100 * MS_PER_SEC,
endDelay: -50 * MS_PER_SEC,
}
);
// Seek to Before and play.
transition.cancel();
transition.play();
const events = await watcher.wait_for(
['transitioncancel', 'transitionrun', 'transitionstart'],
{ record: 'all' }
);
assert_equals(events[2].elapsedTime, 0.0);
// Seek to After phase.
transition.finish();
const evt = await watcher.wait_for('transitionend');
assert_equals(evt.elapsedTime, 50.0);
}, 'Calculating the interval start and end time with negative end delay.');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
await watcher.wait_for('transitionrun');
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
await watcher.wait_for('transitioncancel');
transition.cancel();
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Call Animation.cancel after canceling transition.');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
await watcher.wait_for('transitionrun');
// Make idle
transition.cancel();
transition.play();
await watcher.wait_for([ 'transitioncancel',
'transitionrun' ]);
}, 'Restart transition after canceling transition immediately');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
await watcher.wait_for('transitionrun');
// Make idle
div.style.display = 'none';
getComputedStyle(div).marginLeft;
transition.play();
transition.cancel();
await watcher.wait_for('transitioncancel');
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Call Animation.cancel after restarting transition immediately');
promise_test(async t => {
const { transition, watcher } = setupTransition(t, 'margin-left 100s');
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
// Make idle
transition.timeline = null;
await watcher.wait_for('transitioncancel');
transition.timeline = document.timeline;
transition.play();
await watcher.wait_for(['transitionrun', 'transitionstart']);
}, 'Set timeline and play transition after clear the timeline');
promise_test(async t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s');
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
transition.cancel();
await watcher.wait_for('transitioncancel');
// Make After phase
transition.effect = null;
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Set null target effect after canceling the transition');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(t, 'margin-left 100s');
await watcher.wait_for([ 'transitionrun', 'transitionstart' ]);
transition.effect = null;
await watcher.wait_for('transitionend');
transition.cancel();
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Cancel the transition after clearing the target effect');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(t, 'margin-left 100s');
// Seek to After phase.
transition.finish();
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', 'transitionend'],
{
record: 'all',
}
);
transition.cancel();
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Cancel the transition after it finishes');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(t, 'margin-left 100s');
transition.currentTime = 50 * MS_PER_SEC;
await watcher.wait_for(['transitionrun', 'transitionstart']);
// Replace the running transition.
div.style.marginLeft = '200px';
// transitioncancel event should be fired before transitionrun because we
// expect to cancel the running transition first.
await watcher.wait_for(
['transitioncancel', 'transitionrun', 'transitionstart']
);
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Replacing a running transition should get transitioncancel earlier than ' +
'transitionrun and transitionstart');
promise_test(async t => {
const div =
addDiv(t, { style: 'transition: margin-left 100s, margin-top 100s' });
const watcher = new EventWatcher(t, div, [ 'transitionrun',
'transitioncancel' ],
transitionEventsTimeout);
getComputedStyle(div).marginLeft;
div.style.marginLeft = '100px';
div.style.marginTop = '100px';
const transitions = div.getAnimations();
transitions[0].currentTime = 50 * MS_PER_SEC;
transitions[1].currentTime = 50 * MS_PER_SEC;
await watcher.wait_for(['transitionrun', 'transitionrun']);
// Replace both running transitions.
div.style.marginLeft = '200px';
div.style.marginTop = '200px';
await watcher.wait_for([
// Cancel events show first because their transition generations are
// smaller than the new ones.
'transitioncancel', 'transitioncancel',
'transitionrun', 'transitionrun'
]);
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Replacing two running transitions on the same target should get two ' +
'transitioncancel events earlier than two transitionrun events, per ' +
'transition generation');
promise_test(async t => {
const { transition, watcher, div } = setupTransition(t, 'margin-left 100s');
transition.currentTime = 50 * MS_PER_SEC;
await watcher.wait_for(['transitionrun', 'transitionstart']);
// We need to wait for a while to reproduce the potential bug in Gecko.
await new Promise(resolve => t.step_timeout(resolve, 100));
// Replace the running transition.
div.style.marginLeft = '200px';
getComputedStyle(div).marginLeft;
// transitioncancel event should be fired before transitionrun because we
// expect to cancel the running transition first.
await watcher.wait_for(
['transitioncancel', 'transitionrun', 'transitionstart']
);
// Then wait a couple of frames and check that no event was dispatched
await waitForAnimationFrames(2);
}, 'Replacing a running transition and forcing to flush the style together ' +
'should get the correct event order');
</script>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-transitions/#starting">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
div { height: inherit; }
#outer { height: 50px; }
#outer.collapsed {
height: 0px;
transition: height 0.01s;
}
</style>
<div id="outer">
<div>
<div id="inner">You should only see a flash of red.</div>
</div>
</div>
<script>
promise_test(t => {
outer.offsetTop;
outer.className = "collapsed";
return (new Promise((resolve) => outer.addEventListener("transitionend", resolve))).then(() => {
assert_equals(getComputedStyle(inner).height, "0px");
});
}, "Transitioned height, explicitly inherited down two DOM levels, should inherit correctly");
</script>

View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Transitions Test: Intermediate Property Values</title>
<meta name="timeout" content="long">
<meta name="assert" content="Test checks that value ranges between start and end while transitioning">
<link rel="help" href="http://www.w3.org/TR/css3-transitions/#transitions">
<link rel="help" href="https://drafts.csswg.org/web-animations-1/#animation-type">
<link rel="author" title="Rodney Rehm" href="http://rodneyrehm.de/en/">
<meta name="flags" content="dom">
<script src="../../resources/testharness.js" type="text/javascript"></script>
<script src="../../resources/testharnessreport.js" type="text/javascript"></script>
<script src="./support/vendorPrefix.js" type="text/javascript"></script>
<script src="./support/helper.js" type="text/javascript"></script>
<script src="./support/runParallelAsyncHarness.js" type="text/javascript"></script>
<script src="./support/generalParallelTest.js" type="text/javascript"></script>
<script src="./support/properties.js" type="text/javascript"></script>
<style type="text/css">
#offscreen {
position: absolute;
top: -100000px;
left: -100000px;
width: 100000px;
height: 100000px;
}
</style>
</head>
<body>
<!-- required by testharnessreport.js -->
<div id="log"></div>
<!-- elements used for testing -->
<div id="fixture" class="fixture">
<div class="container">
<div class="transition">Text sample</div>
</div>
</div>
<div id="offscreen"></div>
<!--
SEE ./support/README.md for an abstract explanation of the test procedure
http://test.csswg.org/source/contributors/rodneyrehm/submitted/css3-transitions/README.md
-->
<script>
// this test takes its time, give it a minute to run
var timeout = 60000;
setup({timeout: timeout});
var tests = getPropertyTests();
// for testing, limit to a couple of iterations
// tests = tests.slice(10, 30);
// or filter using one of:
// tests = filterPropertyTests(tests, "background-color color(rgba)");
// tests = filterPropertyTests(tests, ["background-color color(rgba)", ...]);
// tests = filterPropertyTests(tests, /^background-color/);
// general transition-duration
var duration = '2s';
runParallelAsyncHarness({
// array of test data
tests: tests,
// the number of tests to run in parallel
testsPerSlice: 50,
// milliseconds to wait before calling teardown and ending test
duration: parseFloat(duration) * 1000,
// prepare individual test
setup: function(data, options) {
var styles = {
'.fixture': {},
'.container': data.parentStyle,
'.container.to': {},
'.container.how': {},
'.transition': data.from,
'.transition.to' : data.to,
'.transition.how' : {transition: 'all ' + duration + ' linear 0s'}
};
generalParallelTest.setup(data, options);
generalParallelTest.addStyles(data, options, styles);
},
// cleanup after individual test
teardown: generalParallelTest.teardown,
// invoked prior to running a slice of tests
sliceStart: generalParallelTest.sliceStart,
// invoked after transitions have started
transitionsStarted: generalParallelTest.transitionsStarted,
// invoked after running a slice of tests
sliceDone: generalParallelTest.sliceDone,
// test cases, make them as granular as possible
cases: {
// test property values while transitioning
// values.start kicks off a transition
'values': {
// run actual test, assertions can be used here!
start: function(test, data, options) {
// identify initial and target values
generalParallelTest.getStyle(data);
// make sure values differ, if they don't, the property could most likely not be parsed
assert_not_equals(data.transition.from, data.transition.to, "initial and target values may not match");
// kick off the transition
generalParallelTest.startTransition(data);
// make sure we didn't get the target value immediately.
// If we did, there wouldn't be a transition!
var current = data.transition.computedStyle(data.property);
assert_not_equals(current, data.transition.to, "must not be target value after start");
},
done: function(test, data, options) {
// make sure the property's value were neither initial nor target while transitioning
test.step(generalParallelTest.assertIntermediateValuesFunc(data, 'transition'));
}
},
// test TransitionEnd events
'events': {
done: function(test, data, options) {
// make sure there were no events on parent
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'container', ""));
// make sure we got the event for the tested property only
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'transition', addVendorPrefix(data.property) + ":" + duration));
}
}
},
// called once all tests are done
done: generalParallelTest.done
});
</script>
</body>
</html>

View File

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Transitions Test: transitioning inherited property values</title>
<meta name="timeout" content="long">
<meta name="assert" content="Test checks that inherited property values that are transitioned on a parent element don't start a transition">
<link rel="help" title="3. Starting of transitions" href="http://www.w3.org/TR/css3-transitions/#starting">
<link rel="author" title="Rodney Rehm" href="http://rodneyrehm.de/en/">
<meta name="flags" content="dom ">
<script src="../../resources/testharness.js" type="text/javascript"></script>
<script src="../../resources/testharnessreport.js" type="text/javascript"></script>
<script src="./support/vendorPrefix.js" type="text/javascript"></script>
<script src="./support/helper.js" type="text/javascript"></script>
<script src="./support/runParallelAsyncHarness.js" type="text/javascript"></script>
<script src="./support/generalParallelTest.js" type="text/javascript"></script>
<script src="./support/properties.js" type="text/javascript"></script>
<style type="text/css">
#offscreen {
position: absolute;
top: -100000px;
left: -100000px;
width: 100000px;
height: 100000px;
}
</style>
</head>
<body>
<!-- required by testharnessreport.js -->
<div id="log"></div>
<!-- elements used for testing -->
<div id="fixture" class="fixture">
<div class="container">
<div class="transition">Text sample</div>
</div>
</div>
<div id="offscreen"></div>
<!--
SEE ./support/README.md for an abstract explanation of the test procedure
http://test.csswg.org/source/contributors/rodneyrehm/submitted/css3-transitions/README.md
-->
<script>
// http://www.w3.org/TR/css3-transitions/#starting
// Implementations also must not start a transition when the computed value changes because
// it is inherited (directly or indirectly) from another element that is transitioning the same property.
// this test takes its time, give it a minute to run
var timeout = 60000;
setup({timeout: timeout});
var tests = getPropertyTests();
// for testing, limit to a couple of iterations
// tests = tests.slice(10, 30);
// or filter using one of:
// tests = filterPropertyTests(tests, "background-color color(rgba)");
// tests = filterPropertyTests(tests, ["background-color color(rgba)", ...]);
// tests = filterPropertyTests(tests, /^background-color/);
// general transition-duration
var duration = '2s';
runParallelAsyncHarness({
// array of test data
tests: tests,
// the number of tests to run in parallel
testsPerSlice: 50,
// milliseconds to wait before calling teardown and ending test
duration: parseFloat(duration) * 1000,
// prepare individual test
setup: function(data, options) {
// clone and overwrite initial styles to be
// applied to #transition
var inherited = extend({}, data.from);
inherited[data.property] = 'inherit';
var styles = {
// as we're testing inheritance, #fixture is our new parent
'.fixture': data.parentStyle,
// all styles including transition apply to to #container so they
// can inherit down to #transition
'.container': extend({}, data.parentStyle, data.from),
'.container.to': data.to,
'.container.how': {transition: addVendorPrefix(data.property) + ' ' + duration + ' linear 0s'},
// #transition only inherits and listens for transition events
'.transition': inherited,
'.transition.to' : {},
'.transition.how' : {transition: addVendorPrefix(data.property) + ' ' + duration + ' linear 0s'}
};
generalParallelTest.setup(data, options);
generalParallelTest.addStyles(data, options, styles);
},
// cleanup after individual test
teardown: generalParallelTest.teardown,
// invoked prior to running a slice of tests
sliceStart: generalParallelTest.sliceStart,
// invoked after transitions have started
transitionsStarted: generalParallelTest.transitionsStarted,
// invoked after running a slice of tests
sliceDone: generalParallelTest.sliceDone,
// test cases, make them as granular as possible
cases: {
// test property values while transitioning
// values.start kicks off a transition
'values': {
// run actual test, assertions can be used here!
start: function(test, data, options) {
// identify initial and target values
generalParallelTest.getStyle(data);
// make sure values differ, if they don't, the property could most likely not be parsed
assert_not_equals(data.transition.from, data.transition.to, "initial and target values may not match");
// kick off the transition
generalParallelTest.startTransition(data);
// make sure we didn't get the target value immediately.
// If we did, there wouldn't be a transition!
var current = data.transition.computedStyle(data.property);
assert_not_equals(current, data.transition.to, "must not be target value after start");
},
done: function(test, data, options) {
// make sure the property's value were neither initial nor target while transitioning
test.step(generalParallelTest.assertIntermediateValuesFunc(data, 'transition'));
}
},
// test TransitionEnd events
'events': {
done: function(test, data, options) {
// make sure there were no events on parent
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'container', addVendorPrefix(data.property) + ":" + duration));
// make sure we got the event for the tested property only
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'transition', ""));
}
}
},
// called once all tests are done
done: generalParallelTest.done
});
</script>
</body>
</html>

View File

@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Transitions Test: transitioning inherited property values</title>
<meta name="timeout" content="long">
<meta name="assert" content="Test checks that inherited property values that are not transitioned on a parent element start a transition">
<link rel="help" title="3. Starting of transitions" href="http://www.w3.org/TR/css3-transitions/#starting">
<link rel="author" title="Rodney Rehm" href="http://rodneyrehm.de/en/">
<meta name="flags" content="dom ">
<script src="../../resources/testharness.js" type="text/javascript"></script>
<script src="../../resources/testharnessreport.js" type="text/javascript"></script>
<script src="./support/vendorPrefix.js" type="text/javascript"></script>
<script src="./support/helper.js" type="text/javascript"></script>
<script src="./support/runParallelAsyncHarness.js" type="text/javascript"></script>
<script src="./support/generalParallelTest.js" type="text/javascript"></script>
<script src="./support/properties.js" type="text/javascript"></script>
<style type="text/css">
#offscreen {
position: absolute;
top: -100000px;
left: -100000px;
width: 100000px;
height: 100000px;
}
</style>
</head>
<body>
<!-- required by testharnessreport.js -->
<div id="log"></div>
<!-- elements used for testing -->
<div id="fixture" class="fixture">
<div class="container">
<div class="transition">Text sample</div>
</div>
</div>
<div id="offscreen"></div>
<!--
SEE ./support/README.md for an abstract explanation of the test procedure
http://test.csswg.org/source/contributors/rodneyrehm/submitted/css3-transitions/README.md
-->
<script>
// http://www.w3.org/TR/css3-transitions/#starting
// Implementations also must not start a transition when the computed value changes because
// it is inherited (directly or indirectly) from another element that is transitioning the same property.
// Note: Parent element doesn't transition, so above quote doesn't apply!
// this test takes its time, give it a minute to run
var timeout = 60000;
setup({timeout: timeout});
var tests = getPropertyTests();
// for testing, limit to a couple of iterations
// tests = tests.slice(10, 30);
// or filter using one of:
// tests = filterPropertyTests(tests, "background-color color(rgba)");
// tests = filterPropertyTests(tests, ["background-color color(rgba)", ...]);
// tests = filterPropertyTests(tests, /^background-color/);
// general transition-duration
var duration = '2s';
runParallelAsyncHarness({
// array of test data
tests: tests,
// the number of tests to run in parallel
testsPerSlice: 50,
// milliseconds to wait before calling teardown and ending test
duration: parseFloat(duration) * 1000,
// prepare individual test
setup: function(data, options) {
// clone and overwrite initial styles to be
// applied to #transition
var inherited = extend({}, data.from);
inherited[data.property] = 'inherit';
var styles = {
// as we're testing inheritance, #fixture is our new parent
'.fixture': data.parentStyle,
// all styles including transition apply to to #container so they
// can inherit down to #transition
'.container': extend({}, data.parentStyle, data.from),
'.container.to': data.to,
'.container.how': {},
// #transition only inherits and listens for transition events
'.transition': inherited,
'.transition.to' : {},
'.transition.how' : {transition: addVendorPrefix(data.property) + ' ' + duration + ' linear 0s'}
};
generalParallelTest.setup(data, options);
generalParallelTest.addStyles(data, options, styles);
},
// cleanup after individual test
teardown: generalParallelTest.teardown,
// invoked prior to running a slice of tests
sliceStart: generalParallelTest.sliceStart,
// invoked after transitions have started
transitionsStarted: generalParallelTest.transitionsStarted,
// invoked after running a slice of tests
sliceDone: generalParallelTest.sliceDone,
// test cases, make them as granular as possible
cases: {
// test property values while transitioning
// values.start kicks off a transition
'values': {
// run actual test, assertions can be used here!
start: function(test, data, options) {
// identify initial and target values
generalParallelTest.getStyle(data);
// make sure values differ, if they don't, the property could most likely not be parsed
assert_not_equals(data.transition.from, data.transition.to, "initial and target values may not match");
// kick off the transition
generalParallelTest.startTransition(data);
// make sure we didn't get the target value immediately.
// If we did, there wouldn't be a transition!
var current = data.transition.computedStyle(data.property);
assert_not_equals(current, data.transition.to, "must not be target value after start");
},
done: function(test, data, options) {
// make sure the property's value were neither initial nor target while transitioning
test.step(generalParallelTest.assertIntermediateValuesFunc(data, 'transition'));
}
},
// test TransitionEnd events
'events': {
done: function(test, data, options) {
// make sure there were no events on parent
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'container', ""));
// make sure we got the event for the tested property only
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'transition', addVendorPrefix(data.property) + ":" + duration));
}
}
},
// called once all tests are done
done: generalParallelTest.done
});
</script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title>CSS Transitions Test: Changing transition properties mid-transition</title>
<meta name="assert" content="When transition properties are changed mid-transition, the original transition completes and new transition parameters apply to subsequent changes">
<link rel="help" href="https://drafts.csswg.org/css-transitions/#starting">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="./support/helper.js"></script>
</head>
<body>
<div id="log"></div>
<script>
promise_test(async t => {
const div = addDiv(t, {
style: 'transition: width 100ms; width: 0px;'
});
// Flush initial state
getComputedStyle(div).width;
// Start first transition to 100px
div.style.width = '100px';
// Wait until transition starts running
await new Promise(resolve => {
div.addEventListener('transitionrun', resolve, { once: true });
});
// MID-TRANSITION CHANGE: Switch to 0s duration + 100ms delay
div.style.transition = 'width 0s 100ms';
assert_not_equals(
getComputedStyle(div).width,
'100px',
'Width should not reach 100px yet'
);
// Trigger new width change mid-transition
div.style.width = '0px';
assert_not_equals(
getComputedStyle(div).width,
'0px',
'Width should not changed to 0px immediately'
);
await waitForTransitionEnd(div);
// Final computed style
assert_equals(
getComputedStyle(div).width,
'0px',
'Final width should be 0px'
);
}, 'Mid-transition transition changes affect subsequent transitions');
</script>
</body>
</html>