@@ -554,6 +554,152 @@ describe('coordinator', () => {
554554 coordinator . close ( )
555555 } )
556556
557+ it ( 'should touch devup-ui.css to invalidate Turbopack cache when singleCss=false and new CSS collected' , async ( ) => {
558+ codeExtractSpy . mockReturnValue ( {
559+ code : 'import "./../../df/devup-ui/devup-ui-5.css";\nconst x = 1;' ,
560+ map : undefined ,
561+ cssFile : 'devup-ui-5.css' ,
562+ updatedBaseStyle : false ,
563+ css : '.a{color:yellow}' ,
564+ free : mock ( ) ,
565+ [ Symbol . dispose ] : mock ( ) ,
566+ } )
567+ getCssSpy . mockImplementation (
568+ ( fileNum : number | null , _importMainCss : boolean ) => {
569+ if ( fileNum === null ) return 'base-css'
570+ return `file-css-${ fileNum } `
571+ } ,
572+ )
573+ exportSheetSpy . mockReturnValue ( '{}' )
574+ exportClassMapSpy . mockReturnValue ( '{}' )
575+ exportFileMapSpy . mockReturnValue ( '{}' )
576+
577+ const options = makeOptions ( { singleCss : false } )
578+ const coordinator = startCoordinator ( options )
579+
580+ await new Promise ( ( r ) => setTimeout ( r , 100 ) )
581+
582+ const portStr = ( writeFileSyncSpy . mock . calls [ 0 ] as [ string , string ] ) [ 1 ]
583+ const port = parseInt ( portStr )
584+
585+ const res = await httpRequest (
586+ port ,
587+ 'POST' ,
588+ '/extract' ,
589+ JSON . stringify ( {
590+ filename : 'src/App.tsx' ,
591+ code : 'const x = <Box color="yellow" />' ,
592+ resourcePath : join ( process . cwd ( ) , 'src' , 'App.tsx' ) ,
593+ } ) ,
594+ )
595+
596+ expect ( res . status ) . toBe ( 200 )
597+
598+ // 5 writes: per-file CSS + sheet + classmap + filemap + devup-ui.css invalidation
599+ expect ( writeFileSpy ) . toHaveBeenCalledTimes ( 5 )
600+
601+ // Verify devup-ui.css was written to trigger Turbopack invalidation
602+ const devupUiCssWrite = writeFileSpy . mock . calls . find (
603+ ( call : unknown [ ] ) =>
604+ typeof call [ 0 ] === 'string' && call [ 0 ] . endsWith ( 'devup-ui.css' ) ,
605+ )
606+ expect ( devupUiCssWrite ) . toBeTruthy ( )
607+ // Content should include base CSS + timestamp nonce
608+ const content = devupUiCssWrite ! [ 1 ] as string
609+ expect ( content ) . toContain ( 'base-css' )
610+ expect ( content ) . toMatch ( / \/ \* \d + \* \/ / )
611+
612+ coordinator . close ( )
613+ } )
614+
615+ it ( 'should NOT touch devup-ui.css when singleCss=false but no new CSS collected' , async ( ) => {
616+ codeExtractSpy . mockReturnValue ( {
617+ code : 'const x = 1;' ,
618+ map : undefined ,
619+ cssFile : 'devup-ui-5.css' ,
620+ updatedBaseStyle : false ,
621+ css : undefined , // no new CSS collected
622+ free : mock ( ) ,
623+ [ Symbol . dispose ] : mock ( ) ,
624+ } )
625+ getCssSpy . mockReturnValue ( 'file-css' )
626+ exportSheetSpy . mockReturnValue ( '{}' )
627+ exportClassMapSpy . mockReturnValue ( '{}' )
628+ exportFileMapSpy . mockReturnValue ( '{}' )
629+
630+ const options = makeOptions ( { singleCss : false } )
631+ const coordinator = startCoordinator ( options )
632+
633+ await new Promise ( ( r ) => setTimeout ( r , 100 ) )
634+
635+ const portStr = ( writeFileSyncSpy . mock . calls [ 0 ] as [ string , string ] ) [ 1 ]
636+ const port = parseInt ( portStr )
637+
638+ await httpRequest (
639+ port ,
640+ 'POST' ,
641+ '/extract' ,
642+ JSON . stringify ( {
643+ filename : 'src/App.tsx' ,
644+ code : 'const x = 1' ,
645+ resourcePath : join ( process . cwd ( ) , 'src' , 'App.tsx' ) ,
646+ } ) ,
647+ )
648+
649+ // 4 writes: per-file CSS + sheet + classmap + filemap (NO devup-ui.css touch)
650+ expect ( writeFileSpy ) . toHaveBeenCalledTimes ( 4 )
651+
652+ // Verify NO devup-ui.css write
653+ const devupUiCssWrite = writeFileSpy . mock . calls . find (
654+ ( call : unknown [ ] ) =>
655+ typeof call [ 0 ] === 'string' && call [ 0 ] . endsWith ( 'devup-ui.css' ) ,
656+ )
657+ expect ( devupUiCssWrite ) . toBeUndefined ( )
658+
659+ coordinator . close ( )
660+ } )
661+
662+ it ( 'should NOT touch devup-ui.css for singleCss=true (not needed)' , async ( ) => {
663+ codeExtractSpy . mockReturnValue ( {
664+ code : 'const x = 1;' ,
665+ map : undefined ,
666+ cssFile : 'devup-ui.css' ,
667+ updatedBaseStyle : false ,
668+ css : '.a{color:yellow}' ,
669+ free : mock ( ) ,
670+ [ Symbol . dispose ] : mock ( ) ,
671+ } )
672+ getCssSpy . mockReturnValue ( 'all-styles' )
673+ exportSheetSpy . mockReturnValue ( '{}' )
674+ exportClassMapSpy . mockReturnValue ( '{}' )
675+ exportFileMapSpy . mockReturnValue ( '{}' )
676+
677+ const options = makeOptions ( { singleCss : true } )
678+ const coordinator = startCoordinator ( options )
679+
680+ await new Promise ( ( r ) => setTimeout ( r , 100 ) )
681+
682+ const portStr = ( writeFileSyncSpy . mock . calls [ 0 ] as [ string , string ] ) [ 1 ]
683+ const port = parseInt ( portStr )
684+
685+ await httpRequest (
686+ port ,
687+ 'POST' ,
688+ '/extract' ,
689+ JSON . stringify ( {
690+ filename : 'src/App.tsx' ,
691+ code : 'const x = <Box color="yellow" />' ,
692+ resourcePath : join ( process . cwd ( ) , 'src' , 'App.tsx' ) ,
693+ } ) ,
694+ )
695+
696+ // 4 writes: CSS file (devup-ui.css via cssFile) + sheet + classmap + filemap
697+ // NO additional devup-ui.css invalidation write (singleCss=true doesn't need it)
698+ expect ( writeFileSpy ) . toHaveBeenCalledTimes ( 4 )
699+
700+ coordinator . close ( )
701+ } )
702+
557703 it ( 'should be reset via resetCoordinator while server is active' , async ( ) => {
558704 const options = makeOptions ( )
559705 startCoordinator ( options )
0 commit comments