1- import { cleanup , render , waitFor } from "@testing-library/react" ;
2- import { describe , expect , test , vitest , beforeEach } from "vitest" ;
1+ import { cleanup , render } from "@testing-library/react" ;
2+ import { describe , expect , test , vitest , beforeEach , afterEach } from "vitest" ;
33import {
44 CustomerDetailed ,
55 CustomerGhost ,
66 Project ,
7+ ProjectDetailed ,
78 ProjectGhost ,
9+ advanceSleepTimer ,
810 customerMocks ,
911 getCustomerNameGhostIds ,
1012 projectMocks ,
1113} from "./testMocks" ;
1214import { QueryClient , QueryClientProvider } from "@tanstack/react-query" ;
13- import { invalidateGhostsById } from "./invalidate" ;
15+ import { invalidateGhosts } from "./invalidate" ;
16+ import { makeGhost } from "./makeGhost" ;
17+
18+ beforeEach ( ( ) => {
19+ vitest . useFakeTimers ( ) ;
20+ } ) ;
21+
22+ afterEach ( ( ) => {
23+ vitest . runOnlyPendingTimers ( ) ;
24+ vitest . useRealTimers ( ) ;
25+ } ) ;
1426
1527test ( "Pre Test" , async ( ) => {
1628 const project = new Project ( "P1" ) ;
1729 expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 0 ) ;
1830 expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 0 ) ;
1931
20- const detailedProject = await project . getDetailed ( ) ;
32+ const [ detailedProject ] = await Promise . all ( [
33+ project . getDetailed ( ) ,
34+ advanceSleepTimer ( ) ,
35+ ] ) ;
2136 expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
2237 expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 0 ) ;
2338 expect ( detailedProject . name ) . toBe ( "Project P1" ) ;
2439
25- const customer = await detailedProject . customer . getDetailed ( ) ;
40+ const [ customer ] = await Promise . all ( [
41+ detailedProject . customer . getDetailed ( ) ,
42+ advanceSleepTimer ( ) ,
43+ ] ) ;
44+ await vitest . runOnlyPendingTimersAsync ( ) ;
45+
2646 expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
2747 expect ( customer . id ) . toBe ( "C1" ) ;
2848} ) ;
@@ -42,7 +62,8 @@ describe("Await", () => {
4262 expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 0 ) ;
4363 expect ( transform ) . toHaveBeenCalledTimes ( 0 ) ;
4464
45- await customerNameGhost ;
65+ await Promise . all ( [ customerNameGhost , advanceSleepTimer ( 2 ) ] ) ;
66+ await vitest . runOnlyPendingTimersAsync ( ) ;
4667
4768 expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
4869 expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -51,60 +72,40 @@ describe("Await", () => {
5172 } ) ;
5273
5374 test ( "simple usage" , async ( ) => {
54- const customerName = await ProjectGhost . ofId ( "Project A" )
55- . getDetailed ( )
56- . customer . getDetailed ( )
57- . getName ( ) ;
58- expect ( customerName ) . toBe ( "Customer C1" ) ;
59- } ) ;
60-
61- test ( "with transform" , async ( ) => {
62- const customerName = await ProjectGhost . ofId ( "Project A" )
63- . getDetailed ( )
64- . customer . getDetailed ( )
65- . getName ( )
66- . transform ( ( name ) => name . toUpperCase ( ) ) ;
67- expect ( customerName ) . toBe ( "CUSTOMER C1" ) ;
68- } ) ;
69-
70- test ( "with undefined result in call stack" , async ( ) => {
71- const customerName = await ProjectGhost . ofId ( "Project A" )
72- . findDetailed ( )
73- . customer . getDetailed ( )
74- . getName ( )
75- . transform ( ( name ) => {
76- expect ( name ) . toBeUndefined ( ) ;
77- return name ?. toUpperCase ( ) ;
78- } ) ;
79- expect ( customerName ) . toBeUndefined ( ) ;
80- } ) ;
81-
82- test ( "simple usage" , async ( ) => {
83- const customerName = await ProjectGhost . ofId ( "Project A" )
84- . getDetailed ( )
85- . customer . getDetailed ( )
86- . getName ( ) ;
75+ const [ customerName ] = await Promise . all ( [
76+ ProjectGhost . ofId ( "Project A" )
77+ . getDetailed ( )
78+ . customer . getDetailed ( )
79+ . getName ( ) ,
80+ advanceSleepTimer ( 2 ) ,
81+ ] ) ;
8782 expect ( customerName ) . toBe ( "Customer C1" ) ;
8883 } ) ;
8984
9085 test ( "with transform" , async ( ) => {
91- const customerName = await ProjectGhost . ofId ( "Project A" )
92- . getDetailed ( )
93- . customer . getDetailed ( )
94- . getName ( )
95- . transform ( ( name ) => name . toUpperCase ( ) ) ;
86+ const [ customerName ] = await Promise . all ( [
87+ ProjectGhost . ofId ( "Project A" )
88+ . getDetailed ( )
89+ . customer . getDetailed ( )
90+ . getName ( )
91+ . transform ( ( name ) => name . toUpperCase ( ) ) ,
92+ advanceSleepTimer ( 2 ) ,
93+ ] ) ;
9694 expect ( customerName ) . toBe ( "CUSTOMER C1" ) ;
9795 } ) ;
9896
9997 test ( "with undefined result in call stack" , async ( ) => {
100- const customerName = await ProjectGhost . ofId ( "Project A" )
101- . findDetailed ( )
102- . customer . getDetailed ( )
103- . getName ( )
104- . transform ( ( name ) => {
105- expect ( name ) . toBeUndefined ( ) ;
106- return name ?. toUpperCase ( ) ;
107- } ) ;
98+ const [ customerName ] = await Promise . all ( [
99+ ProjectGhost . ofId ( "Project A" )
100+ . findDetailed ( )
101+ . customer . getDetailed ( )
102+ . getName ( )
103+ . transform ( ( name ) => {
104+ expect ( name ) . toBeUndefined ( ) ;
105+ return name ?. toUpperCase ( ) ;
106+ } ) ,
107+ advanceSleepTimer ( 2 ) ,
108+ ] ) ;
108109 expect ( customerName ) . toBeUndefined ( ) ;
109110 } ) ;
110111} ) ;
@@ -141,7 +142,10 @@ describe("Hooks", () => {
141142 } ) ;
142143
143144 if ( waitForSuspense ) {
144- await ui . findByTestId ( "hook-ready" ) ;
145+ await Promise . all ( [
146+ ui . findByTestId ( "hook-ready" ) ,
147+ vitest . runOnlyPendingTimersAsync ( ) ,
148+ ] ) ;
145149 }
146150
147151 return {
@@ -235,17 +239,16 @@ describe("Hooks", () => {
235239 . getName ( )
236240 . useGhost ( ) ,
237241 ) ;
242+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
243+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
244+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 1 ) ;
238245
239246 result . current ?. invalidate ( ) ;
240- await waitFor ( ( ) =>
241- expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
242- ) ;
243- await waitFor ( ( ) =>
244- expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
245- ) ;
246- await waitFor ( ( ) =>
247- expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ,
248- ) ;
247+ await vitest . runOnlyPendingTimersAsync ( ) ;
248+
249+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
250+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
251+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ;
249252 } ) ;
250253
251254 test ( "ghost.invalidate() triggers re-execution of all async methods" , async ( ) => {
@@ -255,17 +258,16 @@ describe("Hooks", () => {
255258 . getName ( ) ;
256259
257260 await renderHookWithSuspense ( ( ) => ghost . use ( ) ) ;
258- await waitFor ( ( ) =>
259- expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ,
260- ) ;
261+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
262+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
263+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 1 ) ;
261264
262265 ghost . invalidate ( queryClient ) ;
263- await waitFor ( ( ) =>
264- expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
265- ) ;
266- await waitFor ( ( ) =>
267- expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ,
268- ) ;
266+ await vitest . runOnlyPendingTimersAsync ( ) ;
267+
268+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
269+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
270+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ;
269271 } ) ;
270272
271273 test ( "invalidateGhostsById() triggers re-execution of all async methods" , async ( ) => {
@@ -275,50 +277,66 @@ describe("Hooks", () => {
275277 . getName ( ) ;
276278
277279 await renderHookWithSuspense ( ( ) => ghost . use ( ) ) ;
278- await waitFor ( ( ) =>
279- expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ,
280- ) ;
280+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
281+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
282+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 1 ) ;
281283
282- expect ( getCustomerNameGhostIds . current ) . toBeDefined ( ) ;
283- invalidateGhostsById ( queryClient , getCustomerNameGhostIds . current ! ) ;
284- await waitFor ( ( ) =>
285- expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
286- ) ;
287- await waitFor ( ( ) =>
288- expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ,
289- ) ;
284+ invalidateGhosts ( queryClient , getCustomerNameGhostIds . current ! . queryKey ) ;
285+ await vitest . runOnlyPendingTimersAsync ( ) ;
286+
287+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
288+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
289+ expect ( customerMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ;
290290 } ) ;
291291
292292 test ( "ghost.invalidate() invalidates all dependent ghosts" , async ( ) => {
293293 const ghost = ProjectGhost . ofId ( "Project A" ) . getDetailed ( ) ;
294294 const specialGhost = ghost . customer . getDetailed ( ) ;
295295
296296 await renderHookWithSuspense ( ( ) => specialGhost . use ( ) ) ;
297- await waitFor ( ( ) =>
298- expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ,
299- ) ;
297+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
298+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
300299
301300 ghost . invalidate ( queryClient ) ;
302- await waitFor ( ( ) =>
303- expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
304- ) ;
301+ await vitest . runOnlyPendingTimersAsync ( ) ;
302+
303+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
304+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
305305 } ) ;
306306
307- test ( "ghost.invalidate() invalidate previous ghosts" , async ( ) => {
307+ test ( "ghost.invalidate() invalidates previous ghosts" , async ( ) => {
308308 const ghost = ProjectGhost . ofId ( "Project A" ) . getDetailed ( ) ;
309309 const specialGhost = ghost . customer . getDetailed ( ) ;
310310
311311 await renderHookWithSuspense ( ( ) => ghost . use ( ) ) ;
312- await waitFor ( ( ) =>
313- expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ,
314- ) ;
312+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 1 ) ;
313+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 0 ) ;
315314
316315 specialGhost . invalidate ( queryClient ) ;
317- await expect ( ( ) =>
318- waitFor ( ( ) =>
319- expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ,
320- ) ,
321- ) . rejects . toThrow ( ) ;
316+ await vitest . runOnlyPendingTimersAsync ( ) ;
317+
318+ expect ( projectMocks . getDetailed ) . toHaveBeenCalledTimes ( 2 ) ;
319+ expect ( customerMocks . getDetailed ) . toHaveBeenCalledTimes ( 0 ) ;
320+ } ) ;
321+
322+ test ( "target changes invalidates dependent ghosts" , async ( ) => {
323+ const projectGhost = ProjectGhost . ofId ( "Project A" ) . getDetailed ( ) ;
324+
325+ await renderHookWithSuspense ( ( ) =>
326+ makeGhost ( projectGhost . use ( ) ) . getName ( ) . use ( ) ,
327+ ) ;
328+ expect ( projectMocks . getName ) . toHaveBeenCalledTimes ( 1 ) ;
329+
330+ projectMocks . getDetailed = vitest
331+ . fn ( )
332+ . mockImplementation (
333+ ( id ) => new ProjectDetailed ( id , `CHANGED! Project ${ id } ` , "C1" ) ,
334+ ) ;
335+
336+ projectGhost . invalidate ( queryClient ) ;
337+ await vitest . runOnlyPendingTimersAsync ( ) ;
338+
339+ expect ( projectMocks . getName ) . toHaveBeenCalledTimes ( 2 ) ;
322340 } ) ;
323341 } ) ;
324342} ) ;
0 commit comments