diff --git a/packages/devtools/src/__tests__/cd-diagnosis-real.test.ts b/packages/devtools/src/__tests__/cd-diagnosis-real.test.ts index 827be14a..17553b0d 100644 --- a/packages/devtools/src/__tests__/cd-diagnosis-real.test.ts +++ b/packages/devtools/src/__tests__/cd-diagnosis-real.test.ts @@ -2,12 +2,12 @@ import '@angular/compiler'; import * as core from '@angular/core'; import { Component } from '@angular/core'; -import { afterEach, describe, expect, it } from 'vitest'; +import { setPostSignalSetFn } from '@angular/core/primitives/signals'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { attachChangeDetection, type CdReport } from '../cd-overlay.ts'; import { findCdScheduler } from '../cd-diagnosis.ts'; import { angularAtLeast, bootstrapRoot, STANDALONE, type BootedApp } from './angular-version.ts'; -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const ngOf = () => (globalThis as { ng?: unknown }).ng; const supported = angularAtLeast('20.0'); const signal = (value: T, debugName: string) => @@ -78,6 +78,13 @@ Component({ `, })(Shell); +/** Whether a signal write hook is set, such as the one a recording adds once its import resolves. */ +function hasSignalWriteHook(): boolean { + const hook = setPostSignalSetFn(null); + setPostSignalSetFn(hook); + return hook !== null; +} + function fakeRpc() { const handlers = new Map unknown>(); const reports: CdReport[] = []; @@ -110,13 +117,13 @@ describe.skipIf(!supported)('change detection diagnosis with a real app', () => document.body.innerHTML = ''; app = await bootstrapRoot(Shell); app.appRef.tick(); - await wait(20); + await app.appRef.whenStable(); const { my, handlers, reports } = fakeRpc(); const cd = attachChangeDetection(my, 'p1', ngOf, 50); stop = cd.stop; handlers.get('change-detection-record')!({ pageId: 'p1', on: true }); // The signal write hook loads with a dynamic import. - await wait(20); + await vi.waitFor(() => expect(hasSignalWriteHook()).toBe(true), { timeout: 3000 }); return { async report() { await cd.push(true); @@ -135,7 +142,7 @@ describe.skipIf(!supported)('change detection diagnosis with a real app', () => it('credits a click to its event, target and listener component, with the signal it wrote', async () => { const recording = await record(); (document.querySelector('app-counter button') as HTMLButtonElement).click(); - await wait(40); + await app!.appRef.whenStable(); const report = await recording.report(); const cycle = report.cycles.find((c) => c.cause?.kind === 'event'); expect(cycle?.cause).toMatchObject({ @@ -154,8 +161,14 @@ describe.skipIf(!supported)('change detection diagnosis with a real app', () => const counter = (ngOf() as { getComponent(el: Element): Counter }).getComponent( document.querySelector('app-counter')!, ); - setTimeout(() => counter.bump(), 0); - await wait(60); + await new Promise((resolve) => + setTimeout(() => { + counter.bump(); + resolve(); + }, 0), + ); + // The write schedules a tick, which Angular tracks as a pending task. + await app!.appRef.whenStable(); const report = await recording.report(); const cause = report.cycles.find((c) => c.cause?.signal === 'count')?.cause; expect(cause?.kind).toBe('signal'); @@ -168,7 +181,7 @@ describe.skipIf(!supported)('change detection diagnosis with a real app', () => const recording = await record(); for (let i = 0; i < 3; i++) { (document.getElementById('add') as HTMLButtonElement).click(); - await wait(40); + await app!.appRef.whenStable(); } const report = await recording.report(); const diagnosis = report.diagnosis!; diff --git a/packages/devtools/src/__tests__/page-highlight.test.ts b/packages/devtools/src/__tests__/page-highlight.test.ts index 7903bae9..3bf79f9c 100644 --- a/packages/devtools/src/__tests__/page-highlight.test.ts +++ b/packages/devtools/src/__tests__/page-highlight.test.ts @@ -1,5 +1,8 @@ // @vitest-environment jsdom import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +// Loaded with the file, not inside a test: its module graph takes seconds to +// transform, which would count against that test's timeout. +import { initOverlay } from '../overlay.ts'; import { clearHighlight, HIGHLIGHT_SAFETY_MS, @@ -118,9 +121,10 @@ describe('page highlight', () => { it('keeps a panel highlight until a clear arrives, and an agent highlight clears itself', async () => { vi.stubGlobal('BroadcastChannel', undefined); - const { initOverlay } = await import('../overlay.ts'); const stop = await initOverlay(); - vi.useFakeTimers(); + // Only the clear timers are under test. A faked requestAnimationFrame would + // replay the box's follow loop every 16 ms over the minute advanced below. + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); try { document.body.innerHTML = ''; place(document.querySelector('app-card')!, { x: 1, y: 2, width: 3, height: 4 }); @@ -148,7 +152,6 @@ describe('page highlight', () => { it('accepts an SVG element from the highlight-in-page request', async () => { vi.stubGlobal('BroadcastChannel', undefined); - const { initOverlay } = await import('../overlay.ts'); const stop = await initOverlay(); try { document.body.innerHTML = '';