Fix finished-promise recreation on animation completion - #82
Conversation
teardown() replaced the just-resolved finished promise with a fresh unresolved one, so awaiting or chaining an animation after it completed would hang forever. The finished promise is now only recreated when a finished animation is replayed via play(). - BaseAnimation/NativeAnimation: add a `finished` getter and read it fresh in then(). - MainThreadAnimation: teardown() resolves without recreating; cancel() no longer recreates either. - AcceleratedAnimation: stop() resolves without recreating, since a stopped animation can never be replayed. - GroupPlaybackControls: add a `finished` getter that awaits members' own finished promises, falling back to the member itself for controls that predate the getter. Empty groups still resolve immediately. Fixes motiondivision/motion#3145 and motiondivision/motion#3144. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
🦋 Changeset detectedLatest commit: fbc41ca The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
Stacked PR — base is
jonathonrp-fix-animation-replay-and-speed(#76), notmain. This depends on #76 and should be merged after it.Ports the behavioural fix for upstream motiondivision/motion#3145 and motiondivision/motion#3144. No architectural refactor was attempted: upstream's #3148 rewrite (the
WithPromisemixin,GroupAnimation/GroupAnimationWithThensplit) is deliberately not included — the equivalent semantics are implemented inside the existing class structure.The bug
MainThreadAnimation.teardown()resolved the finished promise and then immediately replaced it with a fresh, unresolved one. Anything that awaited or chained the controls after the animation had completed waited forever, becauseBaseAnimation.thenreadcurrentFinishedPromise— by then the new, never-resolved instance.GroupPlaybackControls.thencompounded it by passing the member controls toPromise.allas raw one-shot thenables.Changes
BaseAnimation— adds afinishedgetter returning the current finished promise;then()now reads it fresh (this.finished.then(...)).MainThreadAnimation—teardown()resolves the finished promise but no longer recreates it;cancel()no longer recreates it either, so a cancelled animation still resolves its awaiters. The promise is recreated only inplay()when replaying a finished animation (added in Fix animation replay and playback speed correctness #76).AcceleratedAnimation—stop()resolves without recreating, sinceisStoppedprevents the animation from ever being replayed.play()still recreates the promise for a finished WAAPI animation.NativeAnimation— adds the samefinishedgetter and routesthen()through it. Its existing resolve/recreate semantics were already correct and were left alone.GroupPlaybackControls— adds afinishedgetter that awaits each member's ownfinishedpromise, falling back to the member itself (a thenable) when it doesn't expose one, so third-party/legacyAnimationPlaybackControlsimplementations keep working.then()routes through it. An empty group (e.g.new GroupPlaybackControls([])fromanimation/interfaces/motion-value.ts) still resolves immediately.animation/types.ts—AnimationPlaybackControls.finishedadded as optional so existing implementors don't break.Regression tests
All new tests use a
withTimeouthelper (added toanimators/__tests__/utils.ts) so a hang fails fast instead of stalling the suite.MainThreadAnimation.spec.ts→describe('finished promise'):awaitresolves once the animation completesawaitafter natural completion still resolves (core bug).then()fires after natural completionGroupPlaybackControls.spec.ts→describe('finished promise'):7.
awaitresolves once all animations complete8. a second
awaitafter natural completion still resolves9.
.then()fires after natural completion10. an empty group resolves immediately
11. falls back to members that do not expose
finishedVerified by
git stash-ing only the source edits: 8 of these fail without the fix (the rest exercise paths that were already correct or the new API surface), and all pass with it.Test results
bun run test:run(full suite): 129 files passed, 1 skipped — 644 tests passed, 1 skipped, no type errorsbun x vitest --run src/animation: 24 files, 218 tests passed, no type errorsnoThenProperty,useOptionalChain, CRLF format noise)bun.lockis not included in this PR