refactor(core): Migrate more tests off fakeAsync

This will prevent to polute the agent context with outdated/bad practices.

(cherry picked from commit c1025a0510)
This commit is contained in:
Matthieu Riegler
2026-07-27 00:01:54 +02:00
committed by Alex Rickabaugh
parent 8c2e9838ff
commit 1167626e5b
8 changed files with 278 additions and 293 deletions
@@ -1,4 +1,4 @@
load("//tools:defaults.bzl", "angular_jasmine_test", "js_binary", "js_run_binary", "ng_project", "ng_web_test_suite", "ts_project")
load("//tools:defaults.bzl", "js_binary", "js_run_binary", "ng_project", "ts_project", "zoneless_jasmine_test", "zoneless_web_test_suite")
package(default_visibility = ["//visibility:private"])
@@ -61,28 +61,28 @@ ng_project(
deps = TEST_DEPS,
)
angular_jasmine_test(
zoneless_jasmine_test(
name = "test",
data = [
":test_lib",
],
)
angular_jasmine_test(
zoneless_jasmine_test(
name = "test_jit",
data = [
":test_jit_lib",
],
)
ng_web_test_suite(
zoneless_web_test_suite(
name = "test_web",
deps = [
":test_lib",
],
)
ng_web_test_suite(
zoneless_web_test_suite(
name = "test_jit_web",
deps = [
":test_jit_lib",
@@ -29,11 +29,10 @@ import {
ViewContainerRef,
} from '@angular/core';
import {By} from '@angular/platform-browser';
import {isNode} from '@angular/private/testing';
import {isNode, timeout, useAutoTick} from '@angular/private/testing';
import {Subscription} from 'rxjs';
import {SIGNAL} from '../../../primitives/signals';
import {fakeAsync, TestBed, tick} from '../../../testing';
import {tickAnimationFrames} from '../../animation_utils/tick_animation_frames';
import {TestBed} from '../../../testing';
describe('signal inputs', () => {
beforeEach(() =>
@@ -311,12 +310,14 @@ describe('signal inputs', () => {
});
describe('animation API', () => {
useAutoTick();
if (isNode) {
it('should pass', () => expect(true).toBe(true));
return;
}
it('should support signal inputs', fakeAsync(() => {
it('should support signal inputs', async () => {
const styles = `
.slide-in {
animation: slide-in 1ms;
@@ -366,7 +367,7 @@ describe('signal inputs', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
const childCmp = fixture.debugElement.query(By.css('p'));
expect(childCmp.nativeElement.className).toContain('fade-in');
@@ -375,11 +376,11 @@ describe('signal inputs', () => {
new AnimationEvent('animationend', {animationName: 'fade-in'}),
);
fixture.detectChanges();
tick();
await fixture.whenStable();
expect(childCmp.nativeElement.className).not.toContain('fade-in');
}));
});
it('should support content projection', fakeAsync(() => {
it('should support content projection', async () => {
const animateStyles = `
.fade-in {
animation: fade 1ms forwards;
@@ -467,14 +468,14 @@ describe('signal inputs', () => {
// show first time
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
const enterAppContent = fixture.nativeElement.querySelector('app-content');
expect(enterAppContent).not.toBeNull();
expect(enterAppContent.className).toEqual('fade-in');
enterAppContent.dispatchEvent(new AnimationEvent('animationstart'));
enterAppContent.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
tick();
await fixture.whenStable();
expect(enterAppContent.className).not.toEqual('fade-in');
expect(fixture.debugElement.query(By.css('app-content'))).not.toBeNull();
@@ -482,7 +483,7 @@ describe('signal inputs', () => {
// hide first time
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
const leaveAppContent = fixture.nativeElement.querySelector('app-content');
expect(fixture.nativeElement.outerHTML).toContain('app-content class="fade-out"');
@@ -490,7 +491,7 @@ describe('signal inputs', () => {
leaveAppContent.dispatchEvent(new AnimationEvent('animationstart'));
leaveAppContent.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
expect(fixture.debugElement.query(By.css('app-content'))).toBeNull();
@@ -499,7 +500,7 @@ describe('signal inputs', () => {
// show second time
fixture.detectChanges();
fixture.changeDetectorRef.markForCheck();
tickAnimationFrames(1);
await timeout(17);
const enterAppContent2 = fixture.nativeElement.querySelector('app-content');
expect(enterAppContent2).not.toBeNull();
@@ -508,7 +509,7 @@ describe('signal inputs', () => {
fadeInEl.dispatchEvent(new AnimationEvent('animationstart'));
fadeInEl.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
expect(fixture.nativeElement.querySelector('app-content').className).not.toEqual('fade-in');
expect(fixture.debugElement.query(By.css('app-content'))).not.toBeNull();
@@ -517,7 +518,7 @@ describe('signal inputs', () => {
// hide second time
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
const leaveAppContent2 = fixture.nativeElement.querySelector('app-content');
expect(fixture.nativeElement.outerHTML).toContain('app-content class="fade-out"');
@@ -525,12 +526,12 @@ describe('signal inputs', () => {
leaveAppContent2.dispatchEvent(new AnimationEvent('animationstart'));
leaveAppContent2.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade'}));
fixture.detectChanges();
tickAnimationFrames(1);
await timeout(17);
expect(fixture.debugElement.query(By.css('app-content'))).toBeNull();
}));
});
it('should run animations using the root injector so that the animation queue still runs when the component is destroyed before afterNextRender occurs', fakeAsync(() => {
it('should run animations using the root injector so that the animation queue still runs when the component is destroyed before afterNextRender occurs', async () => {
const animateStyles = `
.fade-out {
animation: fade-out 100ms;
@@ -643,7 +644,7 @@ describe('signal inputs', () => {
// remove the item from the list
fixture.componentInstance.removeItem(fixture.componentInstance.list()[0].id);
fixture.detectChanges(); // Detect changes for TestComponent to trigger leave animation
tickAnimationFrames(1); // Allow animation to start (will add 'fade-out' class)
await timeout(1); // Allow animation to start (will add 'fade-out' class)
const fadingOut = fixture.nativeElement.querySelector('notification');
@@ -654,9 +655,9 @@ describe('signal inputs', () => {
notification.dispatchEvent(
new AnimationEvent('animationend', {animationName: 'fade-out', bubbles: true}),
);
tick(300); // Advance timers by animation duration (0.5s)
await timeout(300); // Advance timers by animation duration (0.5s)
fixture.detectChanges(); // Detect changes after animation completes and element is removed
expect(fixture.nativeElement.querySelector('notification')).toBeNull(); // Verify element is removed
}));
});
});
});
@@ -40,13 +40,7 @@ import {
ViewChildren,
ViewContainerRef,
} from '../../src/core';
import {
ComponentFixture,
ComponentFixtureAutoDetect,
fakeAsync,
TestBed,
tick,
} from '../../testing';
import {ComponentFixture, ComponentFixtureAutoDetect, TestBed} from '../../testing';
describe('change detection', () => {
beforeEach(() => {
@@ -121,7 +115,6 @@ describe('change detection', () => {
@Component({
selector: 'onpush',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class OnPushComponent {
checks = 0;
@@ -220,7 +213,6 @@ describe('change detection', () => {
@Component({
template: `<ng-template #vm="vm" viewManipulation></ng-template>`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ViewManipulation],
})
class App {}
@@ -230,7 +222,6 @@ describe('change detection', () => {
<button (click)="noop()">Trigger change detection</button>
<div>{{ increment() }}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class DynamicComp {
increment() {
@@ -272,7 +263,6 @@ describe('change detection', () => {
selector: 'child',
template: '<ng-container *viewManipulation>{{data()}}</ng-container>',
imports: [ViewManipulation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ChildComponent {
data = data;
@@ -312,7 +302,6 @@ describe('change detection', () => {
@Component({
selector: `test-cmpt`,
template: `{{ counter }}|<ng-template #vc></ng-template>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestCmpt {
counter = 0;
@@ -326,7 +315,6 @@ describe('change detection', () => {
@Component({
selector: 'dynamic-cmpt',
template: `dynamic|{{ binding }}`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class DynamicCmpt {
@Input() binding = 'binding';
@@ -429,7 +417,6 @@ describe('change detection', () => {
describe('OnPush', () => {
@Component({
selector: 'my-comp',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `{{ doCheckCount }} - {{ name }} <button (click)="onClick()"></button>`,
standalone: false,
})
@@ -570,7 +557,6 @@ describe('change detection', () => {
@Component({
selector: 'button-parent',
template: '{{ doCheckCount }} - <my-comp></my-comp>',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class ButtonParent implements DoCheck {
@@ -624,7 +610,7 @@ describe('change detection', () => {
expect(fixture.nativeElement.textContent.trim()).toEqual('3 - 2 - Nancy');
});
it('should check parent OnPush components when child directive on a template emits event', fakeAsync(() => {
it('should check parent OnPush components when child directive on a template emits event', async () => {
@Directive({
selector: '[emitter]',
standalone: false,
@@ -642,7 +628,6 @@ describe('change detection', () => {
@Component({
selector: 'my-app',
template: '{{message}} <ng-template emitter (event)="message = $event"></ng-template>',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class MyApp {
@@ -655,10 +640,10 @@ describe('change detection', () => {
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toEqual('initial message');
tick();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toEqual('new message');
}));
});
});
describe('ChangeDetectorRef', () => {
@@ -666,7 +651,6 @@ describe('change detection', () => {
@Component({
selector: 'my-comp',
template: '{{ name }}',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class MyComp implements DoCheck {
@@ -1193,7 +1177,6 @@ describe('change detection', () => {
@Component({
selector: 'on-push-comp',
template: '{{ value }}',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class OnPushComp {
@@ -1238,7 +1221,6 @@ describe('change detection', () => {
@Component({
selector: 'on-push-comp',
template: '{{ value }}',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class OnPushComp implements DoCheck {
@@ -1255,7 +1237,6 @@ describe('change detection', () => {
@Component({
template: '{{ value }} - <on-push-comp></on-push-comp>',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class OnPushParent {
@@ -1317,7 +1298,6 @@ describe('change detection', () => {
it('should ensure OnPush components in embedded views are checked', () => {
@Component({
template: '{{ value }} - <on-push-comp *ngIf="showing"></on-push-comp>',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
class EmbeddedViewParent {
@@ -1358,7 +1338,6 @@ describe('change detection', () => {
it('async pipe should trigger CD for embedded views where the declaration and insertion views are different', () => {
@Component({
selector: 'insertion',
changeDetection: ChangeDetectionStrategy.OnPush,
template: ` <ng-container [ngTemplateOutlet]="template"> </ng-container> `,
standalone: false,
})
@@ -1369,7 +1348,6 @@ describe('change detection', () => {
// This component uses async pipe (which calls markForCheck) in a view that has different
// insertion and declaration views.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<insertion [template]="ref"></insertion>
<ng-template #ref>
@@ -1537,7 +1515,6 @@ describe('change detection', () => {
});
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: '{{state}}{{resolveReadPromise()}}',
})
class MyApp {
@@ -1617,7 +1594,6 @@ describe('change detection', () => {
@Component({
template: '{{state}}',
changeDetection: ChangeDetectionStrategy.OnPush,
})
class NotUnidirectionalDataFlow {
state = 1;
@@ -1653,7 +1629,6 @@ describe('change detection', () => {
it(`should be able to mark component as dirty from within ${hookName}`, () => {
@Component({
selector: 'on-push-comp',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<p>{{ text }}</p>`,
standalone: false,
})
@@ -1705,7 +1680,6 @@ describe('change detection', () => {
it(`should not be able to mark component as dirty from within ${hookName}`, () => {
@Component({
selector: 'on-push-comp',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<p>{{ text }}</p>`,
standalone: false,
})
+174 -158
View File
@@ -7,7 +7,7 @@
*/
import {CommonModule, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
import {isBrowser} from '@angular/private/testing';
import {isBrowser, timeout} from '@angular/private/testing';
import {ActivatedRoute, provideRouter, Router, RouterOutlet} from '@angular/router';
import {Console} from '../../src/console';
import {
@@ -48,15 +48,7 @@ import {ChainedInjector} from '../../src/render3/chained_injector';
import {getComponentDef} from '../../src/render3/def_getters';
import {getInjectorResolutionPath} from '../../src/render3/util/injector_discovery_utils';
import {global} from '../../src/util/global';
import {
ComponentFixture,
DeferBlockBehavior,
DeferBlockState,
fakeAsync,
flush,
TestBed,
tick,
} from '../../testing';
import {ComponentFixture, DeferBlockBehavior, DeferBlockState, TestBed} from '../../testing';
/**
* Clears all associated directive defs from a given component class.
@@ -113,18 +105,41 @@ async function verifyTimeline(
fixture: ComponentFixture<unknown>,
...slots: Array<[time: number, expected: string | VoidFunction]>
) {
let actualTime = 0;
for (let i = 0; i < slots.length; i++) {
const timeToWait = i === 0 ? slots[0][0] : slots[i][0] - slots[i - 1][0];
const targetVirtualTime = slots[i][0];
const slotValue = slots[i][1];
// This is an action, just invoke a function.
if (typeof slotValue === 'function') {
const waitTime = Math.max(0, targetVirtualTime - actualTime);
if (waitTime > 0) {
await timeout(waitTime);
actualTime += waitTime;
}
slotValue();
}
tick(timeToWait);
fixture.detectChanges();
if (typeof slotValue === 'string') {
const actual = fixture.nativeElement.textContent.trim();
expect(actual).withContext(`${slots[i][0]}ms`).toBe(slotValue);
} else {
const waitTime = Math.max(0, targetVirtualTime - actualTime);
if (waitTime > 0) {
await timeout(waitTime);
actualTime += waitTime;
}
let actual = '';
let waited = 0;
const maxWait = 40; // max ms to poll for the expected state
while (waited <= maxWait) {
fixture.detectChanges();
actual = fixture.nativeElement.textContent.trim();
if (actual === slotValue) {
break;
}
await timeout(1);
waited += 1;
actualTime += 1;
}
expect(actual).withContext(`${targetVirtualTime}ms (actual ${actualTime}ms)`).toBe(slotValue);
}
}
}
@@ -884,7 +899,7 @@ describe('@defer', () => {
});
describe('minimum and after conditions', () => {
it('should support minimum and after conditions', fakeAsync(() => {
it('should support minimum and after conditions', async () => {
const {trigger, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -897,7 +912,7 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[50, 'Placeholder'],
[100, trigger(170)],
@@ -906,9 +921,9 @@ describe('@defer', () => {
[300, 'Loading'],
[450, 'Main'],
);
}));
});
it('should support @placeholder with `minimum`', fakeAsync(() => {
it('should support @placeholder with `minimum`', async () => {
const {trigger, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -917,10 +932,10 @@ describe('@defer', () => {
}
`);
verifyTimeline(fixture, [0, trigger(40)], [90, 'Placeholder'], [100, 'Main']);
}));
await verifyTimeline(fixture, [0, trigger(40)], [90, 'Placeholder'], [100, 'Main']);
});
it('should keep rendering @placeholder if trigger happened later', fakeAsync(() => {
it('should keep rendering @placeholder if trigger happened later', async () => {
const {trigger, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -929,18 +944,18 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[0, 'Placeholder'],
[50, trigger(20)],
[90, 'Placeholder'],
[100, 'Main'],
);
}));
});
it(
'should transition from @placeholder to primary content ' + 'if it was prefetched',
fakeAsync(() => {
async () => {
const {trigger, triggerPrefetch, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -949,7 +964,7 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[0, 'Placeholder'],
[20, triggerPrefetch(20)],
@@ -957,10 +972,10 @@ describe('@defer', () => {
[200, trigger(0)],
[225, 'Main'],
);
}),
},
);
it('should support @loading with `minimum`', fakeAsync(() => {
it('should support @loading with `minimum`', async () => {
const {trigger, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -969,7 +984,7 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[0, trigger(20)],
// Even though loading happened in 20ms,
@@ -978,9 +993,9 @@ describe('@defer', () => {
[95, 'Loading'],
[100, 'Main'],
);
}));
});
it('should support @loading with `after` and `minimum`', fakeAsync(() => {
it('should support @loading with `after` and `minimum`', async () => {
const {trigger, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -989,7 +1004,7 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[0, trigger(150)],
[50, ''],
@@ -1000,9 +1015,9 @@ describe('@defer', () => {
// Render main content after `after` + `minimum` ms.
[300, 'Main'],
);
}));
});
it('should skip @loading when resources were prefetched', fakeAsync(() => {
it('should skip @loading when resources were prefetched', async () => {
const {trigger, triggerPrefetch, fixture} = createFixture(`
@defer (when trigger; prefetch when prefetchTrigger) {
<nested-cmp [block]="'Main'" />
@@ -1011,7 +1026,7 @@ describe('@defer', () => {
}
`);
verifyTimeline(
await verifyTimeline(
fixture,
[0, triggerPrefetch(50)],
[50, ''],
@@ -1021,7 +1036,7 @@ describe('@defer', () => {
// resources were already preloaded.
[125, 'Main'],
);
}));
});
});
describe('error handling', () => {
@@ -2882,7 +2897,7 @@ describe('@defer', () => {
// Note: these cases specifically use `on interaction`, however
// the resolution logic is the same for all triggers.
describe('trigger resolution', () => {
it('should resolve a trigger is outside the defer block', fakeAsync(() => {
it('should resolve a trigger is outside the defer block', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -2910,11 +2925,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger on a component outside the defer block', fakeAsync(() => {
it('should resolve a trigger on a component outside the defer block', async () => {
@Component({
selector: 'some-comp',
template: '<button></button>',
@@ -2950,11 +2965,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger that is on a parent element', fakeAsync(() => {
it('should resolve a trigger that is on a parent element', async () => {
@Component({
template: `
<button #trigger>
@@ -2980,11 +2995,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger that is inside a parent embedded view', fakeAsync(() => {
it('should resolve a trigger that is inside a parent embedded view', async () => {
@Component({
template: `
@if (cond) {
@@ -3014,11 +3029,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger that is on a component in a parent embedded view', fakeAsync(() => {
it('should resolve a trigger that is on a component in a parent embedded view', async () => {
@Component({
selector: 'some-comp',
template: '<button></button>',
@@ -3056,11 +3071,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger that is inside the placeholder', fakeAsync(() => {
it('should resolve a trigger that is inside the placeholder', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3085,11 +3100,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should resolve a trigger that is a component inside the placeholder', fakeAsync(() => {
it('should resolve a trigger that is a component inside the placeholder', async () => {
@Component({
selector: 'some-comp',
template: '<button></button>',
@@ -3122,13 +3137,13 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
});
describe('interaction triggers', () => {
it('should load the deferred content when the trigger is clicked', fakeAsync(() => {
it('should load the deferred content when the trigger is clicked', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3150,11 +3165,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should load the deferred content when the trigger receives a keyboard event', fakeAsync(() => {
it('should load the deferred content when the trigger receives a keyboard event', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3182,11 +3197,11 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('keydown'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should load the deferred content when an implicit trigger is clicked', fakeAsync(() => {
it('should load the deferred content when an implicit trigger is clicked', async () => {
@Component({
template: `
@defer (on interaction) {
@@ -3206,12 +3221,12 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should load the deferred content if a child of the trigger is clicked', fakeAsync(() => {
it('should load the deferred content if a child of the trigger is clicked', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3237,11 +3252,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should support multiple deferred blocks with the same trigger', fakeAsync(() => {
it('should support multiple deferred blocks with the same trigger', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3269,11 +3284,11 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content 1 Main content 2');
}));
});
it('should unbind the trigger events when the deferred block is loaded', fakeAsync(() => {
it('should unbind the trigger events when the deferred block is loaded', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3294,14 +3309,14 @@ describe('@defer', () => {
button.click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('keydown', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should unbind the trigger events when the trigger is destroyed', fakeAsync(() => {
it('should unbind the trigger events when the trigger is destroyed', async () => {
@Component({
template: `
@if (renderBlock) {
@@ -3330,9 +3345,9 @@ describe('@defer', () => {
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('keydown', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should unbind the trigger events when the deferred block is destroyed', fakeAsync(() => {
it('should unbind the trigger events when the deferred block is destroyed', async () => {
@Component({
template: `
@if (renderBlock) {
@@ -3362,9 +3377,9 @@ describe('@defer', () => {
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('keydown', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should remove placeholder content on interaction', fakeAsync(() => {
it('should remove placeholder content on interaction', async () => {
@Component({
template: `
@defer (on interaction(trigger)) {
@@ -3393,11 +3408,11 @@ describe('@defer', () => {
zone.run(() => {
button.click();
});
tick();
await timeout(0);
expect(componentRef.location.nativeElement.innerHTML).not.toContain('<div>placeholder</div>');
}));
});
it('should prefetch resources on interaction', fakeAsync(() => {
it('should prefetch resources on interaction', async () => {
@Component({
selector: 'root-app',
template: `
@@ -3439,12 +3454,12 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
it('should prefetch resources on interaction with an implicit trigger', fakeAsync(() => {
it('should prefetch resources on interaction with an implicit trigger', async () => {
@Component({
selector: 'root-app',
template: `
@@ -3487,14 +3502,14 @@ describe('@defer', () => {
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
});
describe('hover triggers', () => {
it('should load the deferred content when the trigger is hovered', fakeAsync(() => {
it('should load the deferred content when the trigger is hovered', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3522,11 +3537,11 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should load the deferred content with an implicit trigger element', fakeAsync(() => {
it('should load the deferred content with an implicit trigger element', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3552,12 +3567,12 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should support multiple deferred blocks with the same hover trigger', fakeAsync(() => {
it('should support multiple deferred blocks with the same hover trigger', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3591,11 +3606,11 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content 1 Main content 2');
}));
});
it('should unbind the trigger events when the deferred block is loaded', fakeAsync(() => {
it('should unbind the trigger events when the deferred block is loaded', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3621,15 +3636,15 @@ describe('@defer', () => {
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(spy).toHaveBeenCalledTimes(3);
expect(spy).toHaveBeenCalledWith('mouseenter', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('mouseover', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('focusin', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should unbind the trigger events when the trigger is destroyed', fakeAsync(() => {
it('should unbind the trigger events when the trigger is destroyed', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3664,9 +3679,9 @@ describe('@defer', () => {
expect(spy).toHaveBeenCalledWith('mouseenter', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('mouseover', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('focusin', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should unbind the trigger events when the deferred block is destroyed', fakeAsync(() => {
it('should unbind the trigger events when the deferred block is destroyed', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3702,9 +3717,9 @@ describe('@defer', () => {
expect(spy).toHaveBeenCalledWith('mouseenter', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('mouseover', jasmine.any(Function), jasmine.any(Object));
expect(spy).toHaveBeenCalledWith('focusin', jasmine.any(Function), jasmine.any(Object));
}));
});
it('should prefetch resources on hover', fakeAsync(() => {
it('should prefetch resources on hover', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3752,12 +3767,12 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
it('should prefetch resources when an implicit trigger is hovered', fakeAsync(() => {
it('should prefetch resources when an implicit trigger is hovered', async () => {
// Domino doesn't support creating custom events so we have to skip this test.
if (!isBrowser) {
return;
@@ -3806,10 +3821,10 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
});
describe('`on timer` triggers', () => {
@@ -4028,7 +4043,7 @@ describe('@defer', () => {
expect(loadingFnInvokedTimes).toBe(1);
});
it('should trigger prefetching and rendering based on `on timer` condition', fakeAsync(() => {
it('should trigger prefetching and rendering based on `on timer` condition', async () => {
const {fixture} = createFixture(`
@defer (on timer(200ms); prefetch on timer(100ms)) {
<nested-cmp [block]="'Main'" />
@@ -4037,10 +4052,10 @@ describe('@defer', () => {
}
`);
verifyTimeline(fixture, [50, 'Placeholder'], [150, 'Placeholder'], [250, 'Main']);
}));
await verifyTimeline(fixture, [50, 'Placeholder'], [150, 'Placeholder'], [250, 'Main']);
});
it('should clear timeout callbacks when defer block is triggered', fakeAsync(() => {
it('should clear timeout callbacks when defer block is triggered', async () => {
const setSpy = spyOn(globalThis, 'setTimeout');
const clearSpy = spyOn(globalThis, 'clearTimeout');
@@ -4071,7 +4086,7 @@ describe('@defer', () => {
// condition was triggered, which resulted in timers cleanup.
expect(setSpy).toHaveBeenCalledTimes(2);
expect(clearSpy).toHaveBeenCalledTimes(2);
}));
});
});
describe('viewport triggers', () => {
@@ -4172,7 +4187,7 @@ describe('@defer', () => {
}
}
it('should load the deferred content when the trigger is in the viewport', fakeAsync(() => {
it('should load the deferred content when the trigger is in the viewport', async () => {
@Component({
template: `
@defer (on viewport(trigger)) {
@@ -4196,11 +4211,11 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should load the deferred content when an implicit trigger is in the viewport', fakeAsync(() => {
it('should load the deferred content when an implicit trigger is in the viewport', async () => {
@Component({
template: `
@defer (on viewport) {
@@ -4222,12 +4237,12 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should not load the content if the trigger is not in the view yet', fakeAsync(() => {
it('should not load the content if the trigger is not in the view yet', async () => {
@Component({
template: `
@defer (on viewport(trigger)) {
@@ -4251,22 +4266,22 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, false);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Placeholder');
MockIntersectionObserver.invokeCallbacksForElement(button, false);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Placeholder');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content');
}));
});
it('should support multiple deferred blocks with the same trigger', fakeAsync(() => {
it('should support multiple deferred blocks with the same trigger', async () => {
@Component({
template: `
@defer (on viewport(trigger)) {
@@ -4295,11 +4310,11 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(fixture.nativeElement.textContent.trim()).toBe('Main content 1 Main content 2');
}));
});
it('should stop observing the trigger when the deferred block is loaded', fakeAsync(() => {
it('should stop observing the trigger when the deferred block is loaded', async () => {
@Component({
template: `
@defer (on viewport(trigger)) {
@@ -4322,13 +4337,13 @@ describe('@defer', () => {
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(activeObservers.length).toBe(1);
expect(activeObservers[0].observedElements.size).toBe(0);
}));
});
it('should stop observing the trigger when the trigger is destroyed', fakeAsync(() => {
it('should stop observing the trigger when the trigger is destroyed', async () => {
@Component({
template: `
@if (renderBlock) {
@@ -4358,9 +4373,9 @@ describe('@defer', () => {
expect(activeObservers.length).toBe(1);
expect(activeObservers[0].observedElements.size).toBe(0);
}));
});
it('should stop observing the trigger when the deferred block is destroyed', fakeAsync(() => {
it('should stop observing the trigger when the deferred block is destroyed', async () => {
@Component({
template: `
@if (renderBlock) {
@@ -4391,9 +4406,9 @@ describe('@defer', () => {
expect(activeObservers.length).toBe(1);
expect(activeObservers[0].observedElements.size).toBe(0);
}));
});
it('should disconnect the intersection observer once all deferred blocks have been loaded', fakeAsync(() => {
it('should disconnect the intersection observer once all deferred blocks have been loaded', async () => {
@Component({
template: `
<button #triggerOne></button>
@@ -4432,9 +4447,9 @@ describe('@defer', () => {
expect(disconnectSpy).toHaveBeenCalled();
expect(observer.observedElements.size).toBe(0);
}));
});
it('should prefetch resources when the trigger comes into the viewport', fakeAsync(() => {
it('should prefetch resources when the trigger comes into the viewport', async () => {
@Component({
selector: 'root-app',
template: `
@@ -4477,12 +4492,12 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
it('should prefetch resources when an implicit trigger comes into the viewport', fakeAsync(() => {
it('should prefetch resources when an implicit trigger comes into the viewport', async () => {
@Component({
selector: 'root-app',
template: `
@@ -4526,12 +4541,12 @@ describe('@defer', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
MockIntersectionObserver.invokeCallbacksForElement(button, true);
fixture.detectChanges();
flush();
await fixture.whenStable();
expect(loadingFnInvokedTimes).toBe(1);
}));
});
it('should load deferred content in a loop', fakeAsync(() => {
it('should load deferred content in a loop', async () => {
// prettier-ignore
@Component({
template: `
@@ -4540,7 +4555,8 @@ describe('@defer', () => {
@placeholder {<button>p{{item}} </button>}
}
`,
changeDetection: ChangeDetectionStrategy.Eager,})
changeDetection: ChangeDetectionStrategy.Eager,
})
class MyCmp {
items = [1, 2, 3, 4, 5, 6];
}
@@ -4557,7 +4573,7 @@ describe('@defer', () => {
for (let i = 0; i < items.length / 2; i++) {
MockIntersectionObserver.invokeCallbacksForElement(buttons[i], true);
fixture.detectChanges();
flush();
await fixture.whenStable();
}
expect(fixture.nativeElement.textContent.trim()).toBe('d1 d2 d3 p4 p5 p6');
@@ -4565,12 +4581,12 @@ describe('@defer', () => {
for (let i = items.length / 2; i < items.length; i++) {
MockIntersectionObserver.invokeCallbacksForElement(buttons[i], true);
fixture.detectChanges();
flush();
await fixture.whenStable();
}
expect(fixture.nativeElement.textContent.trim()).toBe('d1 d2 d3 d4 d5 d6');
}));
});
it('should take the `on viewport` options into account when creating IntersectionObserver', fakeAsync(() => {
it('should take the `on viewport` options into account when creating IntersectionObserver', async () => {
@Component({
template: `
@defer (on viewport({trigger, rootMargin: '123px', threshold: 0.5})) {
@@ -4591,9 +4607,9 @@ describe('@defer', () => {
expect(activeObservers[0].observedElements.size).toBe(1);
expect(activeObservers[0].observedElements.has(button)).toBe(true);
expect(activeObservers[0].options).toEqual({rootMargin: '123px', threshold: 0.5});
}));
});
it('should take the `prefetch on viewport` options into account when creating IntersectionObserver', fakeAsync(() => {
it('should take the `prefetch on viewport` options into account when creating IntersectionObserver', async () => {
@Component({
template: `
@defer (prefetch on viewport({trigger, rootMargin: '123px', threshold: 0.5})) {
@@ -4614,9 +4630,9 @@ describe('@defer', () => {
expect(activeObservers[0].observedElements.size).toBe(1);
expect(activeObservers[0].observedElements.has(button)).toBe(true);
expect(activeObservers[0].options).toEqual({rootMargin: '123px', threshold: 0.5});
}));
});
it('should create different intersection observers depending on their options', fakeAsync(() => {
it('should create different intersection observers depending on their options', async () => {
@Component({
template: `
@defer (on viewport(trigger)) {
@@ -4658,7 +4674,7 @@ describe('@defer', () => {
expect(activeObservers[2].observedElements.size).toBe(1);
expect(activeObservers[2].observedElements.has(button)).toBe(true);
expect(activeObservers[2].options).toEqual({rootMargin: '1vh'});
}));
});
it('should not attach observer if rendering manually', async () => {
@Component({
@@ -59,7 +59,7 @@ import {
getInjectorProviders,
getInjectorResolutionPath,
} from '../../src/render3/util/injector_discovery_utils';
import {fakeAsync} from '../../testing';
import {TestBed} from '../../testing/src/test_bed';
describe('setProfiler', () => {
@@ -936,14 +936,14 @@ describe('getInjectorProviders', () => {
expect(myServiceProviderRecord!.token).toBe(MyService);
});
it('should be able to determine providers in an injector that was created manually', fakeAsync(() => {
it('should be able to determine providers in an injector that was created manually', () => {
class MyService {}
const injector = Injector.create({providers: [MyService]}) as EnvironmentInjector;
const providers = getInjectorProviders(injector);
expect(providers.length).toBe(1);
expect(providers[0].token).toBe(MyService);
expect(providers[0].provider).toBe(MyService);
}));
});
it('should be able to get injector providers for element injectors created by components rendering in an ngFor', () => {
class MyService {}
@@ -40,7 +40,8 @@ import {getLViewById} from '../../src/render3/interfaces/lview_tracking';
import {isLView} from '../../src/render3/interfaces/type_checks';
import {ID, LView, PARENT, TVIEW} from '../../src/render3/interfaces/view';
import {getLView} from '../../src/render3/state';
import {fakeAsync, flushMicrotasks, TestBed} from '../../testing';
import {TestBed} from '../../testing';
import {timeout} from '@angular/private/testing';
describe('acceptance integration tests', () => {
beforeEach(() => {
@@ -2890,8 +2891,8 @@ describe('acceptance integration tests', () => {
{{ $any(val)?.foo!.bar }}
`,
standalone: false,
changeDetection: ChangeDetectionStrategy.Eager,})
changeDetection: ChangeDetectionStrategy.Eager,
})
class Comp {
val: any = null;
@@ -3487,7 +3488,7 @@ describe('acceptance integration tests', () => {
});
describe('animations', () => {
it('should apply triggers for a list of items when they are sorted and reSorted', fakeAsync(() => {
it('should apply triggers for a list of items when they are sorted and reSorted', async () => {
interface Item {
value: any;
id: number;
@@ -3567,11 +3568,11 @@ describe('acceptance integration tests', () => {
elements = queryAll(fixture.nativeElement, 'animation-comp');
expect(elements.length).toEqual(5);
expect(elements.map((e) => e.textContent?.trim())).toEqual(['1', '2', '4', '5', '3']);
completeAnimations();
await completeAnimations();
fixture.componentInstance.showWarningMessage = true;
fixture.detectChanges();
completeAnimations();
await completeAnimations();
elements = queryAll(fixture.nativeElement, 'animation-comp');
expect(elements.length).toEqual(0);
@@ -3582,9 +3583,9 @@ describe('acceptance integration tests', () => {
elements = queryAll(fixture.nativeElement, 'animation-comp');
expect(elements.length).toEqual(5);
}));
});
it('should insert and remove views in the correct order when animations are present', fakeAsync(() => {
it('should insert and remove views in the correct order when animations are present', async () => {
@Component({
animations: [
trigger('root', [transition('* => *', [])]),
@@ -3653,7 +3654,7 @@ describe('acceptance integration tests', () => {
});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
completeAnimations();
await completeAnimations();
const comp = fixture.componentInstance;
expect(comp.log).toEqual([
'root', // insertion of the inner-comp content
@@ -3663,7 +3664,7 @@ describe('acceptance integration tests', () => {
comp.log = [];
comp.showIfContents = false;
fixture.detectChanges();
completeAnimations();
await completeAnimations();
expect(comp.log).toEqual([
'host', // insertion of the inner-comp content
@@ -3674,20 +3675,20 @@ describe('acceptance integration tests', () => {
comp.log = [];
comp.showRoot = false;
fixture.detectChanges();
completeAnimations();
await completeAnimations();
expect(comp.log).toEqual([
'root', // removal the root div container
'host', // removal of the inner-comp content
'inner', // removal of the inner comp element
]);
}));
});
});
describe('arrow functions', () => {
it('should support a basic arrow function in an event listener', () => {
@Component({
template: `<button (click)="value.update(prev => prev + 1)">Increment</button>`,
template: `<button (click)="value.update((prev) => prev + 1)">Increment</button>`,
changeDetection: ChangeDetectionStrategy.Eager,
})
@@ -3715,7 +3716,9 @@ describe('acceptance integration tests', () => {
@if (true) {
@let grandchild = 'grandchild';
<button
(click)="value.update(prev => prev + '->' + grandchild + '->' + child + '->' + parent)"
(click)="
value.update((prev) => prev + '->' + grandchild + '->' + child + '->' + parent)
"
>
Assign
</button>
@@ -3740,7 +3743,7 @@ describe('acceptance integration tests', () => {
it('should support an arrow function in a binding', () => {
@Component({
template: `Result: {{((a) => a + b)(1)}}`,
template: `Result: {{ ((a) => a + b)(1) }}`,
changeDetection: ChangeDetectionStrategy.Eager,
})
@@ -3860,7 +3863,12 @@ describe('acceptance integration tests', () => {
@if (true) {
Result:
{{(a => b => c => d => a + b + c + d + componentProp + topLevelLet + nestedLet)(1)(2)(3)(4)}}
{{
(
(a) => (b) => (c) => (d) =>
a + b + c + d + componentProp + topLevelLet + nestedLet
)(1)(2)(3)(4)
}}
}
}
`,
@@ -3884,7 +3892,7 @@ describe('acceptance integration tests', () => {
@Component({
template: `
@if (true) {
Result: {{(() => componentProp?.a?.b?.c?.()?.()?.()?.())()}}.
Result: {{ (() => componentProp?.a?.b?.c?.()?.()?.()?.())() }}.
}
`,
@@ -3932,7 +3940,7 @@ describe('acceptance integration tests', () => {
}
@Component({
template: `<button test [callback]="() => prop = prop + 1"></button> `,
template: `<button test [callback]="() => (prop = prop + 1)"></button> `,
imports: [TestDir],
changeDetection: ChangeDetectionStrategy.Eager,
@@ -3953,7 +3961,7 @@ describe('acceptance integration tests', () => {
it('should be able to use $event in an arrow function', () => {
@Component({
template: `<button (click)="value.update(prev => $event.type + prev)">Click</button>`,
template: `<button (click)="value.update((prev) => $event.type + prev)">Click</button>`,
changeDetection: ChangeDetectionStrategy.Eager,
})
@@ -3974,7 +3982,7 @@ describe('acceptance integration tests', () => {
@Component({
template: `
@for (item of items; track $index) {
{{ item }}: {{(() => prefix + ($even ? 'even' : 'odd'))()}}
{{ item }}: {{ (() => prefix + ($even ? 'even' : 'odd'))() }}
}
`,
@@ -3999,11 +4007,11 @@ describe('acceptance integration tests', () => {
});
});
function completeAnimations() {
flushMicrotasks();
async function completeAnimations() {
await timeout(0);
const log = MockAnimationDriver.log as MockAnimationPlayer[];
log.forEach((player) => player.finish());
flushMicrotasks();
await timeout(0);
}
function arraySwap(arr: any[], indexA: number, indexB: number): void {
@@ -27,7 +27,7 @@ import {
import {setInjectorProfiler} from '../../src/render3/debug/injector_profiler';
import type {DebugSignalGraphEdge, DebugSignalGraphNode} from '../../primitives/devtools';
import {getSignalGraph} from '../../src/render3/util/signal_debug';
import {fakeAsync, TestBed, tick} from '../../testing';
import {TestBed} from '../../testing';
describe('getSignalGraph', () => {
beforeEach(() => {
@@ -58,16 +58,14 @@ describe('getSignalGraph', () => {
}));
}
it('should return the signal graph for a component with signals', fakeAsync(() => {
it('should return the signal graph for a component with signals', async () => {
@Component({selector: 'component-with-signals', template: `{{ primitiveSignal() }}`})
class WithSignals {
primitiveSignal = signal(123, {debugName: 'primitiveSignal'});
}
TestBed.configureTestingModule({imports: [WithSignals]});
const fixture = TestBed.createComponent(WithSignals);
await fixture.whenStable();
tick();
fixture.detectChanges();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -86,9 +84,9 @@ describe('getSignalGraph', () => {
expect(signalNode).toBeDefined();
expect(signalNode.label).toBe('primitiveSignal');
expect(signalNode.value).toBe(123);
}));
});
it('should return the signal graph for a component with effects', fakeAsync(() => {
it('should return the signal graph for a component with effects', async () => {
@Component({selector: 'component-with-effect', template: ``})
class WithEffect {
stateFromEffect = 0;
@@ -104,11 +102,10 @@ describe('getSignalGraph', () => {
);
}
}
TestBed.configureTestingModule({imports: [WithEffect]});
const fixture = TestBed.createComponent(WithEffect);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -130,9 +127,9 @@ describe('getSignalGraph', () => {
const edgesWithNodes = mapEdgeIndicesIntoNodes(edges, nodes);
expect(edgesWithNodes).toContain({consumer: effectNode, producer: signalNode});
}));
});
it('should return the signal graph for a component with a computed', fakeAsync(() => {
it('should return the signal graph for a component with a computed', async () => {
@Component({selector: 'component-with-computed', template: `{{ computedSignal() }}`})
class WithComputed {
primitiveSignal = signal(123, {debugName: 'primitiveSignal'});
@@ -141,11 +138,9 @@ describe('getSignalGraph', () => {
debugName: 'computedSignal',
});
}
TestBed.configureTestingModule({imports: [WithComputed]});
const fixture = TestBed.createComponent(WithComputed);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -192,9 +187,9 @@ describe('getSignalGraph', () => {
consumer: computedSignalNode,
producer: primitiveSignal2Node,
});
}));
});
it('should return the signal graph for a component with unused reactive nodes', fakeAsync(() => {
it('should return the signal graph for a component with unused reactive nodes', async () => {
@Component({selector: 'component-with-unused-signal', template: ``})
class WithUnusedReactiveNodes {
primitiveSignal = signal(123, {debugName: 'primitiveSignal'});
@@ -202,11 +197,9 @@ describe('getSignalGraph', () => {
debugName: 'computedSignal',
});
}
TestBed.configureTestingModule({imports: [WithUnusedReactiveNodes]});
const fixture = TestBed.createComponent(WithUnusedReactiveNodes);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -214,19 +207,17 @@ describe('getSignalGraph', () => {
const {nodes, edges} = signalGraph;
expect(nodes.length).toBe(0);
expect(edges.length).toBe(0);
}));
});
it('should return the signal graph for a component with no component effect signal dependencies', fakeAsync(() => {
it('should return the signal graph for a component with no component effect signal dependencies', async () => {
@Component({selector: 'component-with-zero-effect', template: ``})
class WithNoEffectSignalDependencies {
primitiveSignal = signal(123, {debugName: 'primitiveSignal'});
primitiveSignalEffect = effect(() => {}, {debugName: 'primitiveSignalEffect'});
}
TestBed.configureTestingModule({imports: [WithNoEffectSignalDependencies]});
const fixture = TestBed.createComponent(WithNoEffectSignalDependencies);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -234,16 +225,14 @@ describe('getSignalGraph', () => {
const {nodes, edges} = signalGraph;
expect(nodes.length).toBe(1); // 1 effect node detected
expect(edges.length).toBe(0);
}));
});
it('should return the signal graph for a component with no signal dependencies in the template or component effects', fakeAsync(() => {
it('should return the signal graph for a component with no signal dependencies in the template or component effects', async () => {
@Component({selector: 'component-with-no-effect-dependencies', template: ``})
class WithNoEffectDependencies {}
TestBed.configureTestingModule({imports: [WithNoEffectDependencies]});
const fixture = TestBed.createComponent(WithNoEffectDependencies);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -251,9 +240,9 @@ describe('getSignalGraph', () => {
const {nodes, edges} = signalGraph;
expect(nodes.length).toBe(0);
expect(edges.length).toBe(0);
}));
});
it('should capture signals created in external services in the signal graph', fakeAsync(() => {
it('should capture signals created in external services in the signal graph', async () => {
@Injectable()
class ExternalService {
oneTwoThree = signal(123, {debugName: 'oneTwoThree'});
@@ -277,11 +266,9 @@ describe('getSignalGraph', () => {
);
}
}
TestBed.configureTestingModule({imports: [WithExternalService]});
const fixture = TestBed.createComponent(WithExternalService);
tick();
fixture.detectChanges();
await fixture.whenStable();
const injector = fixture.componentRef.injector;
const signalGraph = getSignalGraph(injector);
@@ -311,9 +298,9 @@ describe('getSignalGraph', () => {
consumer: externalServiceEffectNode,
producer: fourFiveSixNode,
});
}));
});
it('should capture signals created in directives in the signal graph', () => {
it('should capture signals created in directives in the signal graph', async () => {
@Directive({
selector: '[myDirective]',
})
@@ -335,9 +322,8 @@ describe('getSignalGraph', () => {
})
class WithDirective {}
TestBed.configureTestingModule({imports: [WithDirective]});
const fixture = TestBed.createComponent(WithDirective);
fixture.detectChanges();
await fixture.whenStable();
const element = fixture.nativeElement.querySelector('#element-with-directive');
// get the directive instance
@@ -366,7 +352,7 @@ describe('getSignalGraph', () => {
expect(edgesWithNodes).toContain({consumer: barNode!, producer: fooNode!});
});
it('should capture signals created in different directives in the signal graph', () => {
it('should capture signals created in different directives in the signal graph', async () => {
@Directive({
selector: '[myDirectiveA]',
})
@@ -402,9 +388,9 @@ describe('getSignalGraph', () => {
})
class WithMultipleDirectives {}
TestBed.configureTestingModule({imports: [WithMultipleDirectives]});
const fixture = TestBed.createComponent(WithMultipleDirectives);
fixture.detectChanges();
await fixture.whenStable();
const element = fixture.nativeElement.querySelector('#element-with-directives');
// get the directive instances
const directiveInstances = getDirectives(element);
@@ -445,12 +431,12 @@ describe('getSignalGraph', () => {
expect(edgesWithNodes).toContain({consumer: effectDNode!, producer: signalCNode!});
});
it('should stop tracking effect when ref is destroyed', () => {
it('should stop tracking effect when ref is destroyed', async () => {
@Component({template: ''})
class App {}
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
await fixture.whenStable();
const injector = TestBed.inject(ApplicationRef).injector;
expect(getFrameworkDIDebugData().resolverToEffects.has(injector)).toBe(false);
@@ -462,12 +448,12 @@ describe('getSignalGraph', () => {
expect(getFrameworkDIDebugData().resolverToEffects.get(injector)?.length).toBe(0);
});
it('should stop tracking afterRenderEffect when ref is destroyed', () => {
it('should stop tracking afterRenderEffect when ref is destroyed', async () => {
@Component({template: ''})
class App {}
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
await fixture.whenStable();
const injector = TestBed.inject(ApplicationRef).injector;
expect(getFrameworkDIDebugData().resolverToEffects.has(injector)).toBe(false);
@@ -15,7 +15,7 @@ import {
ɵTracingService as TracingService,
ɵTracingSnapshot as TracingSnapshot,
} from '../../src/core';
import {fakeAsync, TestBed} from '../../testing';
import {TestBed} from '../../testing';
describe('TracingService', () => {
beforeEach(() => {
@@ -86,7 +86,7 @@ describe('TracingService', () => {
expect(actions).toEqual([TracingAction.CHANGE_DETECTION, TracingAction.CHANGE_DETECTION]);
});
it('should take a snapshot after `afterRender`', fakeAsync(() => {
it('should take a snapshot after `afterRender`', async () => {
TestBed.configureTestingModule({
providers: [{provide: TracingService, useValue: mockTracingService}],
});
@@ -107,9 +107,9 @@ describe('TracingService', () => {
TracingAction.CHANGE_DETECTION,
TracingAction.AFTER_NEXT_RENDER,
]);
}));
});
it('should be able to wrap event listeners through the tracing service', fakeAsync(() => {
it('should be able to wrap event listeners through the tracing service', async () => {
TestBed.configureTestingModule({
providers: [{provide: TracingService, useValue: mockTracingService}],
});
@@ -120,7 +120,7 @@ describe('TracingService', () => {
}
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
await fixture.whenStable();
expect(listeners).toEqual([
{event: 'click', handler: jasmine.any(Function)},
@@ -129,10 +129,10 @@ describe('TracingService', () => {
expect(clickCount).toBe(0);
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
await fixture.whenStable();
expect(clickCount).toBe(1);
}));
});
it('should trace component creations', () => {
TestBed.configureTestingModule({