refactor(core): clarify comments on enter animation queuing (#64550)

This just updates the comments and adds some tests to verify some of the queuing behavior for enter animations.

PR Close #64550
This commit is contained in:
Jessica Janiuk
2025-10-20 15:30:15 -07:00
parent 1923e52b12
commit 44127bf53f
2 changed files with 63 additions and 4 deletions
@@ -77,8 +77,11 @@ export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEn
initializeAnimationQueueScheduler(lView[INJECTOR]);
// TODO(thePunderWoman): it's unclear why we need to queue animations here, but without this,
// animating through host bindings fails
// We have to queue here due to the animation instruction being invoked after the element
// instruction. The DOM node has to exist before we can queue an animation. Any node that
// is not inside of control flow needs to get queued here. For nodes inside of control
// flow, those are queued in node_manipulation.ts and are deduped by a Set in the animation
// queue.
queueEnterAnimations(lView[INJECTOR], getLViewEnterAnimations(lView));
return ɵɵanimateEnter; // For chaining
@@ -202,8 +205,11 @@ export function ɵɵanimateEnterListener(value: AnimationFunction): typeof ɵɵa
initializeAnimationQueueScheduler(lView[INJECTOR]);
// TODO(thePunderWoman): it's unclear why we need to queue animations here, but without this,
// animating through host bindings fails
// We have to queue here due to the animation instruction being invoked after the element
// instruction. The DOM node has to exist before we can queue an animation. Any node that
// is not inside of control flow needs to get queued here. For nodes inside of control
// flow, those are queued in node_manipulation.ts and are deduped by a Set in the animation
// queue.
queueEnterAnimations(lView[INJECTOR], getLViewEnterAnimations(lView));
return ɵɵanimateEnterListener;
@@ -886,6 +886,59 @@ describe('Animation', () => {
expect(cmp.el.nativeElement.outerHTML).toContain('class="slide-in"');
}));
it('should call animation function on entry when animation is specified with no control flow', fakeAsync(() => {
@Component({
selector: 'test-cmp',
styles: styles,
template: '<div><p (animate.enter)="slideIn($event)">I should slide in</p></div>',
encapsulation: ViewEncapsulation.None,
})
class TestComponent {
count = signal(0);
slideIn(event: AnimationCallbackEvent) {
this.count.update((c) => (c += 1));
event.animationComplete();
}
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestComponent);
const cmp = fixture.componentInstance;
fixture.detectChanges();
tickAnimationFrames(1);
expect(cmp.count()).toBe(1);
}));
it('should call animation function only once on entry when animation is specified with control flow', fakeAsync(() => {
@Component({
selector: 'test-cmp',
styles: styles,
template:
'<div>@if(show()) {<p (animate.enter)="slideIn($event)">I should slide in</p>}</div>',
encapsulation: ViewEncapsulation.None,
})
class TestComponent {
count = signal(0);
show = signal(false);
slideIn(event: AnimationCallbackEvent) {
this.count.update((c) => (c += 1));
event.animationComplete();
}
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestComponent);
const cmp = fixture.componentInstance;
fixture.detectChanges();
tickAnimationFrames(1);
expect(cmp.count()).toBe(0);
cmp.show.update((s) => !s);
fixture.detectChanges();
tickAnimationFrames(1);
expect(cmp.count()).toBe(1);
}));
it('should apply classes on entry when animation is specified', fakeAsync(() => {
@Component({
selector: 'test-cmp',