Skip to content

Commit a920f87

Browse files
thePunderWomanAndrewKushnir
authored andcommitted
ci: fix timer test flakiness (angular#60310)
This replaces the TimerScheduler entirely and ensures the callback is called immediately. This should prevent any further flakiness. PR Close angular#60310
1 parent f2a8006 commit a920f87

File tree

1 file changed

+106
-109
lines changed

1 file changed

+106
-109
lines changed

packages/platform-server/test/incremental_hydration_spec.ts

Lines changed: 106 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {JSACTION_BLOCK_ELEMENT_MAP} from '@angular/core/src/hydration/tokens';
4848
import {JSACTION_EVENT_CONTRACT} from '@angular/core/src/event_delegation_utils';
4949
import {provideRouter, RouterLink, RouterOutlet, Routes} from '@angular/router';
5050
import {MockPlatformLocation} from '@angular/common/testing';
51+
import {TimerScheduler} from '@angular/core/src/defer/timer_scheduler';
5152

5253
/**
5354
* Emulates a dynamic import promise.
@@ -1341,14 +1342,19 @@ describe('platform-server partial hydration integration', () => {
13411342
});
13421343

13431344
describe('timer', () => {
1344-
const TEST_TIMEOUT = 10_000; // 10 seconds
1345-
1346-
it(
1347-
'top level timer',
1348-
async () => {
1349-
@Component({
1350-
selector: 'app',
1351-
template: `
1345+
class FakeTimerScheduler {
1346+
add(delay: number, callback: VoidFunction) {
1347+
callback();
1348+
}
1349+
remove(callback: VoidFunction) {
1350+
/* noop */
1351+
}
1352+
}
1353+
1354+
it('top level timer', async () => {
1355+
@Component({
1356+
selector: 'app',
1357+
template: `
13521358
<main (click)="fnA()">
13531359
@defer (hydrate on timer(150)) {
13541360
<article>
@@ -1360,62 +1366,56 @@ describe('platform-server partial hydration integration', () => {
13601366
}
13611367
</main>
13621368
`,
1363-
})
1364-
class SimpleComponent {
1365-
value = signal('start');
1366-
fnA() {}
1367-
fnB() {
1368-
this.value.set('end');
1369-
}
1369+
})
1370+
class SimpleComponent {
1371+
value = signal('start');
1372+
fnA() {}
1373+
fnB() {
1374+
this.value.set('end');
13701375
}
1376+
}
13711377

1372-
const appId = 'custom-app-id';
1373-
const providers = [{provide: APP_ID, useValue: appId}];
1374-
const hydrationFeatures = () => [withIncrementalHydration()];
1375-
1376-
const html = await ssr(SimpleComponent, {envProviders: providers, hydrationFeatures});
1377-
const ssrContents = getAppContents(html);
1378-
1379-
// <main> uses "eager" `custom-app-id` namespace.
1380-
expect(ssrContents).toContain('<main jsaction="click:;');
1381-
// <div>s inside a defer block have `d0` as a namespace.
1382-
expect(ssrContents).toContain('<article>');
1383-
// Outer defer block is rendered.
1384-
expect(ssrContents).toContain('defer block rendered');
1385-
1386-
// Internal cleanup before we do server->client transition in this test.
1387-
resetTViewsFor(SimpleComponent);
1388-
1389-
////////////////////////////////
1390-
const doc = getDocument();
1391-
const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent, {
1392-
envProviders: [...providers, {provide: PLATFORM_ID, useValue: 'browser'}],
1393-
hydrationFeatures,
1394-
});
1395-
const compRef = getComponentRef<SimpleComponent>(appRef);
1396-
appRef.tick();
1397-
await appRef.whenStable();
1398-
1399-
const appHostNode = compRef.location.nativeElement;
1400-
1401-
expect(appHostNode.outerHTML).toContain('<article>');
1402-
1403-
await timeout(500); // wait for timer
1404-
await appRef.whenStable();
1405-
await allPendingDynamicImports();
1406-
appRef.tick();
1407-
1408-
expect(appHostNode.outerHTML).toContain('<span id="test">start</span>');
1409-
},
1410-
TEST_TIMEOUT,
1411-
);
1378+
const appId = 'custom-app-id';
1379+
const providers = [
1380+
{provide: APP_ID, useValue: appId},
1381+
{provide: TimerScheduler, useClass: FakeTimerScheduler},
1382+
];
1383+
const hydrationFeatures = () => [withIncrementalHydration()];
1384+
1385+
const html = await ssr(SimpleComponent, {envProviders: providers, hydrationFeatures});
1386+
const ssrContents = getAppContents(html);
14121387

1413-
it(
1414-
'nested timer',
1415-
async () => {
1416-
@Component({
1417-
selector: 'app',
1418-
template: `
1388+
// <main> uses "eager" `custom-app-id` namespace.
1389+
expect(ssrContents).toContain('<main jsaction="click:;');
1390+
// <div>s inside a defer block have `d0` as a namespace.
1391+
expect(ssrContents).toContain('<article>');
1392+
// Outer defer block is rendered.
1393+
expect(ssrContents).toContain('defer block rendered');
1394+
1395+
// Internal cleanup before we do server->client transition in this test.
1396+
resetTViewsFor(SimpleComponent);
1397+
1398+
////////////////////////////////
1399+
const doc = getDocument();
1400+
const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent, {
1401+
envProviders: [...providers, {provide: PLATFORM_ID, useValue: 'browser'}],
1402+
hydrationFeatures,
1403+
});
1404+
const compRef = getComponentRef<SimpleComponent>(appRef);
1405+
await appRef.whenStable();
1406+
1407+
const appHostNode = compRef.location.nativeElement;
1408+
1409+
expect(appHostNode.outerHTML).toContain('<article>');
1410+
await allPendingDynamicImports();
1411+
1412+
expect(appHostNode.outerHTML).toContain('<span id="test">start</span>');
1413+
});
1414+
1415+
it('nested timer', async () => {
1416+
@Component({
1417+
selector: 'app',
1418+
template: `
14191419
<main (click)="fnA()">
14201420
@defer (on viewport; hydrate on interaction) {
14211421
<div id="main" (click)="fnA()">
@@ -1434,57 +1434,54 @@ describe('platform-server partial hydration integration', () => {
14341434
}
14351435
</main>
14361436
`,
1437-
})
1438-
class SimpleComponent {
1439-
value = signal('start');
1440-
fnA() {}
1441-
constructor() {
1442-
if (!isPlatformServer(inject(PLATFORM_ID))) {
1443-
this.value.set('end');
1444-
}
1437+
})
1438+
class SimpleComponent {
1439+
value = signal('start');
1440+
fnA() {}
1441+
constructor() {
1442+
if (!isPlatformServer(inject(PLATFORM_ID))) {
1443+
this.value.set('end');
14451444
}
14461445
}
1446+
}
14471447

1448-
const appId = 'custom-app-id';
1449-
const providers = [{provide: APP_ID, useValue: appId}];
1450-
const hydrationFeatures = () => [withIncrementalHydration()];
1451-
1452-
const html = await ssr(SimpleComponent, {envProviders: providers, hydrationFeatures});
1453-
const ssrContents = getAppContents(html);
1454-
1455-
// <main> uses "eager" `custom-app-id` namespace.
1456-
expect(ssrContents).toContain('<main jsaction="click:;');
1457-
// <div>s inside a defer block have `d0` as a namespace.
1458-
expect(ssrContents).toContain('<article>');
1459-
// Outer defer block is rendered.
1460-
expect(ssrContents).toContain('defer block rendered');
1461-
1462-
// Internal cleanup before we do server->client transition in this test.
1463-
resetTViewsFor(SimpleComponent);
1464-
1465-
////////////////////////////////
1466-
const doc = getDocument();
1467-
const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent, {
1468-
envProviders: [...providers, {provide: PLATFORM_ID, useValue: 'browser'}],
1469-
hydrationFeatures,
1470-
});
1471-
const compRef = getComponentRef<SimpleComponent>(appRef);
1472-
appRef.tick();
1473-
await appRef.whenStable();
1474-
1475-
const appHostNode = compRef.location.nativeElement;
1476-
1477-
expect(appHostNode.outerHTML).toContain('<article>');
1478-
1479-
await timeout(500); // wait for timer
1480-
await appRef.whenStable();
1481-
await allPendingDynamicImports();
1482-
appRef.tick();
1483-
1484-
expect(appHostNode.outerHTML).toContain('<span id="test">end</span>');
1485-
},
1486-
TEST_TIMEOUT,
1487-
);
1448+
const appId = 'custom-app-id';
1449+
const providers = [
1450+
{provide: APP_ID, useValue: appId},
1451+
{provide: TimerScheduler, useClass: FakeTimerScheduler},
1452+
];
1453+
const hydrationFeatures = () => [withIncrementalHydration()];
1454+
1455+
const html = await ssr(SimpleComponent, {envProviders: providers, hydrationFeatures});
1456+
const ssrContents = getAppContents(html);
1457+
1458+
// <main> uses "eager" `custom-app-id` namespace.
1459+
expect(ssrContents).toContain('<main jsaction="click:;');
1460+
// <div>s inside a defer block have `d0` as a namespace.
1461+
expect(ssrContents).toContain('<article>');
1462+
// Outer defer block is rendered.
1463+
expect(ssrContents).toContain('defer block rendered');
1464+
1465+
// Internal cleanup before we do server->client transition in this test.
1466+
resetTViewsFor(SimpleComponent);
1467+
1468+
////////////////////////////////
1469+
const doc = getDocument();
1470+
const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent, {
1471+
envProviders: [...providers, {provide: PLATFORM_ID, useValue: 'browser'}],
1472+
hydrationFeatures,
1473+
});
1474+
const compRef = getComponentRef<SimpleComponent>(appRef);
1475+
await appRef.whenStable();
1476+
1477+
const appHostNode = compRef.location.nativeElement;
1478+
1479+
expect(appHostNode.outerHTML).toContain('<article>');
1480+
1481+
await allPendingDynamicImports();
1482+
1483+
expect(appHostNode.outerHTML).toContain('<span id="test">end</span>');
1484+
});
14881485
});
14891486

14901487
it('when', async () => {

0 commit comments

Comments
 (0)