ci: fix flakey defer test (#60461)

This uses a fake timer scheduler implementation to ensure timer tests do not cause flakiness, similar to the incremental hydration tests.

PR Close #60461
This commit is contained in:
Jessica Janiuk
2025-03-19 10:26:22 -04:00
committed by Pawel Kozlowski
parent 7e65b9f6c0
commit c7cacbf523
+39 -19
View File
@@ -48,6 +48,7 @@ import {getInjectorResolutionPath} from '@angular/core/src/render3/util/injector
import {ActivatedRoute, provideRouter, Router, RouterOutlet} from '@angular/router';
import {ChainedInjector} from '@angular/core/src/render3/chained_injector';
import {global} from '../../src/util/global';
import {TimerScheduler} from '@angular/core/src/defer/timer_scheduler';
/**
* Clears all associated directive defs from a given component class.
@@ -94,15 +95,6 @@ function allPendingDynamicImports() {
return dynamicImportOf(null, 10);
}
/**
* Invoke a callback function after a specified amount of time (in ms).
*/
function timer(delay: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => resolve(), delay);
});
}
/**
* Allows to verify behavior of defer blocks by providing a set of
* [time, expected output] pairs. Also allows to provide a function
@@ -129,6 +121,22 @@ async function verifyTimeline(
}
}
class FakeTimerScheduler {
cbs: VoidFunction[] = [];
add(delay: number, callback: VoidFunction) {
this.cbs.push(callback);
}
remove(callback: VoidFunction) {
/* noop */
}
invoke() {
for (const cb of this.cbs) {
cb();
}
}
}
/**
* Given a template, creates a component fixture and returns
* a set of helper functions to trigger rendering of prefetching
@@ -3161,9 +3169,14 @@ describe('@defer', () => {
};
TestBed.configureTestingModule({
providers: [{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor}],
providers: [
{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor},
{provide: TimerScheduler, useClass: FakeTimerScheduler},
],
});
const fakeScheduler = TestBed.inject(TimerScheduler) as unknown as FakeTimerScheduler;
clearDirectiveDefs(RootCmp);
const fixture = TestBed.createComponent(RootCmp);
@@ -3176,7 +3189,7 @@ describe('@defer', () => {
// Make sure loading function is not yet invoked.
expect(loadingFnInvokedTimes).toBe(0);
await timer(1000);
fakeScheduler.invoke();
await allPendingDynamicImports(); // fetching dependencies of the defer block
fixture.detectChanges();
@@ -3218,24 +3231,26 @@ describe('@defer', () => {
})
class RootCmp {}
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [{provide: TimerScheduler, useClass: FakeTimerScheduler}],
});
const fakeScheduler = TestBed.inject(TimerScheduler) as unknown as FakeTimerScheduler;
clearDirectiveDefs(RootCmp);
const fixture = TestBed.createComponent(RootCmp);
fixture.detectChanges();
expect(fixture.nativeElement.outerHTML).toContain('placeholder[top]');
await timer(110);
fixture.detectChanges();
fakeScheduler.invoke();
await allPendingDynamicImports(); // fetching dependencies of the defer block
// Verify primary blocks content after triggering top-level @defer.
expect(fixture.nativeElement.outerHTML).toContain('primary[top]');
expect(fixture.nativeElement.outerHTML).toContain('placeholder[nested]');
await timer(110);
fixture.detectChanges();
fakeScheduler.invoke();
await allPendingDynamicImports(); // fetching dependencies of the defer block
// Verify that nested @defer block was triggered as well.
expect(fixture.nativeElement.outerHTML).toContain('primary[top]');
@@ -3282,9 +3297,14 @@ describe('@defer', () => {
};
TestBed.configureTestingModule({
providers: [{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor}],
providers: [
{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor},
{provide: TimerScheduler, useClass: FakeTimerScheduler},
],
});
const fakeScheduler = TestBed.inject(TimerScheduler) as unknown as FakeTimerScheduler;
clearDirectiveDefs(RootCmp);
const fixture = TestBed.createComponent(RootCmp);
@@ -3297,7 +3317,7 @@ describe('@defer', () => {
// Make sure loading function is not yet invoked.
expect(loadingFnInvokedTimes).toBe(0);
await timer(200);
fakeScheduler.invoke();
await allPendingDynamicImports(); // fetching dependencies of the defer block
fixture.detectChanges();