Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /web-animations/interfaces/Animation/finished.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<meta charset=utf-8>
<title>Animation.finished</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../testcommon.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
return animation.ready.then(() => {
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise is the same object when playing starts');
animation.pause();
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise does not change when pausing');
animation.play();
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise does not change when play() unpauses');
animation.currentTime = 100 * MS_PER_SEC;
return animation.finished;
}).then(() => {
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise is the same object when playing completes');
});
}, 'Test pausing then playing does not change the finished promise');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
let previousFinishedPromise = animation.finished;
animation.finish();
return animation.finished.then(() => {
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise is the same object when playing completes');
animation.play();
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise changes when replaying animation');
previousFinishedPromise = animation.finished;
animation.play();
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise is the same after redundant play() call');
});
}, 'Test restarting a finished animation');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
let previousFinishedPromise;
animation.finish();
return animation.finished.then(() => {
previousFinishedPromise = animation.finished;
animation.playbackRate = -1;
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise should be replaced when reversing a ' +
'finished promise');
animation.currentTime = 0;
return animation.finished;
}).then(() => {
previousFinishedPromise = animation.finished;
animation.play();
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise is replaced after play() call on ' +
'finished, reversed animation');
});
}, 'Test restarting a reversed finished animation');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
animation.finish();
return animation.finished.then(() => {
animation.currentTime = 100 * MS_PER_SEC + 1000;
assert_equals(animation.finished, previousFinishedPromise,
'Finished promise is unchanged jumping past end of ' +
'finished animation');
});
}, 'Test redundant finishing of animation');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
// Setup callback to run if finished promise is resolved
let finishPromiseResolved = false;
animation.finished.then(() => {
finishPromiseResolved = true;
});
return animation.ready.then(() => {
// Jump to mid-way in interval and pause
animation.currentTime = 100 * MS_PER_SEC / 2;
animation.pause();
return animation.ready;
}).then(() => {
// Jump to the end
// (But don't use finish() since that should unpause as well)
animation.currentTime = 100 * MS_PER_SEC;
return waitForAnimationFrames(2);
}).then(() => {
assert_false(finishPromiseResolved,
'Finished promise should not resolve when paused');
});
}, 'Finished promise does not resolve when paused');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
// Setup callback to run if finished promise is resolved
let finishPromiseResolved = false;
animation.finished.then(() => {
finishPromiseResolved = true;
});
return animation.ready.then(() => {
// Jump to mid-way in interval and pause
animation.currentTime = 100 * MS_PER_SEC / 2;
animation.pause();
// Jump to the end
animation.currentTime = 100 * MS_PER_SEC;
return waitForAnimationFrames(2);
}).then(() => {
assert_false(finishPromiseResolved,
'Finished promise should not resolve when pause-pending');
});
}, 'Finished promise does not resolve when pause-pending');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
animation.finish();
return animation.finished.then(resolvedAnimation => {
assert_equals(resolvedAnimation, animation,
'Object identity of animation passed to Promise callback'
+ ' matches the animation object owning the Promise');
});
}, 'The finished promise is fulfilled with its Animation');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
// Set up listeners on finished promise
const retPromise = animation.finished.then(() => {
assert_unreached('finished promise was fulfilled');
}).catch(err => {
assert_equals(err.name, 'AbortError',
'finished promise is rejected with AbortError');
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise should change after the original is ' +
'rejected');
});
animation.cancel();
return retPromise;
}, 'finished promise is rejected when an animation is canceled by calling ' +
'cancel()');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
animation.finish();
return animation.finished.then(() => {
animation.cancel();
assert_not_equals(animation.finished, previousFinishedPromise,
'A new finished promise should be created when'
+ ' canceling a finished animation');
});
}, 'canceling an already-finished animation replaces the finished promise');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const HALF_DUR = 100 * MS_PER_SEC / 2;
const QUARTER_DUR = 100 * MS_PER_SEC / 4;
let gotNextFrame = false;
let currentTimeBeforeShortening;
animation.currentTime = HALF_DUR;
return animation.ready.then(() => {
currentTimeBeforeShortening = animation.currentTime;
animation.effect.updateTiming({ duration: QUARTER_DUR });
// Below we use gotNextFrame to check that shortening of the animation
// duration causes the finished promise to resolve, rather than it just
// getting resolved on the next animation frame. This relies on the fact
// that the promises are resolved as a micro-task before the next frame
// happens.
waitForAnimationFrames(1).then(() => {
gotNextFrame = true;
});
return animation.finished;
}).then(() => {
assert_false(gotNextFrame, 'shortening of the animation duration should ' +
'resolve the finished promise');
assert_equals(animation.currentTime, currentTimeBeforeShortening,
'currentTime should be unchanged when duration shortened');
const previousFinishedPromise = animation.finished;
animation.effect.updateTiming({ duration: 100 * MS_PER_SEC });
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise should change after lengthening the ' +
'duration causes the animation to become active');
});
}, 'Test finished promise changes for animation duration changes');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const retPromise = animation.ready.then(() => {
animation.playbackRate = 0;
animation.currentTime = 100 * MS_PER_SEC + 1000;
return waitForAnimationFrames(2);
});
animation.finished.then(t.step_func(() => {
assert_unreached('finished promise should not resolve when playbackRate ' +
'is zero');
}));
return retPromise;
}, 'Test finished promise changes when playbackRate == 0');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
return animation.ready.then(() => {
animation.playbackRate = -1;
return animation.finished;
});
}, 'Test finished promise resolves when reaching to the natural boundary.');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
animation.finish();
return animation.finished.then(() => {
animation.currentTime = 0;
assert_not_equals(animation.finished, previousFinishedPromise,
'Finished promise should change once a prior ' +
'finished promise resolved and the animation ' +
'falls out finished state');
});
}, 'Test finished promise changes when a prior finished promise resolved ' +
'and the animation falls out finished state');
test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
animation.currentTime = 100 * MS_PER_SEC;
animation.currentTime = 100 * MS_PER_SEC / 2;
assert_equals(animation.finished, previousFinishedPromise,
'No new finished promise generated when finished state ' +
'is checked asynchronously');
}, 'Test no new finished promise generated when finished state ' +
'is checked asynchronously');
test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
const previousFinishedPromise = animation.finished;
animation.finish();
animation.currentTime = 100 * MS_PER_SEC / 2;
assert_not_equals(animation.finished, previousFinishedPromise,
'New finished promise generated when finished state ' +
'is checked synchronously');
}, 'Test new finished promise generated when finished state ' +
'is checked synchronously');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
let resolvedFinished = false;
animation.finished.then(() => {
resolvedFinished = true;
});
return animation.ready.then(() => {
animation.finish();
animation.currentTime = 100 * MS_PER_SEC / 2;
}).then(() => {
assert_true(resolvedFinished,
'Animation.finished should be resolved even if ' +
'the finished state is changed soon');
});
}, 'Test synchronous finished promise resolved even if finished state ' +
'is changed soon');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
let resolvedFinished = false;
animation.finished.then(() => {
resolvedFinished = true;
});
return animation.ready.then(() => {
animation.currentTime = 100 * MS_PER_SEC;
animation.finish();
}).then(() => {
assert_true(resolvedFinished,
'Animation.finished should be resolved soon after finish() is ' +
'called even if there are other asynchronous promises just before it');
});
}, 'Test synchronous finished promise resolved even if asynchronous ' +
'finished promise happens just before synchronous promise');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
animation.finished.then(t.step_func(() => {
assert_unreached('Animation.finished should not be resolved');
}));
return animation.ready.then(() => {
animation.currentTime = 100 * MS_PER_SEC;
animation.currentTime = 100 * MS_PER_SEC / 2;
});
}, 'Test finished promise is not resolved when the animation ' +
'falls out finished state immediately');
promise_test(t => {
const div = createDiv(t);
const animation = div.animate({}, 100 * MS_PER_SEC);
return animation.ready.then(() => {
animation.currentTime = 100 * MS_PER_SEC;
animation.finished.then(t.step_func(() => {
assert_unreached('Animation.finished should not be resolved');
}));
animation.currentTime = 0;
});
}, 'Test finished promise is not resolved once the animation ' +
'falls out finished state even though the current finished ' +
'promise is generated soon after animation state became finished');
promise_test(t => {
const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
let ready = false;
animation.ready.then(
t.step_func(() => {
ready = true;
}),
t.unreached_func('Ready promise must not be rejected')
);
const testSuccess = animation.finished.then(
t.step_func(() => {
assert_true(ready, 'Ready promise has resolved');
}),
t.unreached_func('Finished promise must not be rejected')
);
const timeout = waitForAnimationFrames(3).then(() => {
return Promise.reject('Finished promise did not arrive in time');
});
animation.finish();
return Promise.race([timeout, testSuccess]);
}, 'Finished promise should be resolved after the ready promise is resolved');
promise_test(t => {
const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
let caught = false;
animation.ready.then(
t.unreached_func('Ready promise must not be resolved'),
t.step_func(() => {
caught = true;
})
);
const testSuccess = animation.finished.then(
t.unreached_func('Finished promise must not be resolved'),
t.step_func(() => {
assert_true(caught, 'Ready promise has been rejected');
})
);
const timeout = waitForAnimationFrames(3).then(() => {
return Promise.reject('Finished promise was not rejected in time');
});
animation.cancel();
return Promise.race([timeout, testSuccess]);
}, 'Finished promise should be rejected after the ready promise is rejected');
promise_test(async t => {
const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
// Ensure the finished promise is created
const finished = animation.finished;
window.addEventListener(
'unhandledrejection',
t.unreached_func('Should not get an unhandled rejection')
);
animation.cancel();
// Wait a moment to allow a chance for the event to be dispatched.
await waitForAnimationFrames(2);
}, 'Finished promise does not report an unhandledrejection when rejected');
</script>
</body>