@@ -74,6 +74,7 @@ vi.mock('../../../src/nitro/setup/extend-loader', () => ({
7474// Mock scanner
7575vi . mock ( '../../../src/nitro/setup/scanner' , ( ) => ( {
7676 shouldScanLocalFiles : vi . fn ( ( ) => true ) ,
77+ performGraphQLScan : vi . fn ( ) . mockResolvedValue ( undefined ) ,
7778} ) )
7879
7980/**
@@ -385,6 +386,190 @@ describe('setupFileWatcher', () => {
385386 expect ( ignored ( '/project/server/graphql/config.json' ) ) . toBe ( true )
386387 } )
387388 } )
389+
390+ describe ( 'file change handling (processChanges)' , ( ) => {
391+ it ( 'should call performGraphQLScan when server file changes' , async ( ) => {
392+ const { watch } = await import ( 'chokidar' )
393+ const { performGraphQLScan } = await import ( '../../../src/nitro/setup/scanner' )
394+
395+ // Create a mock watcher that captures the 'all' event handler
396+ let allEventHandler : ( ( event : string , path : string ) => void ) | null = null
397+ const mockWatcher = {
398+ on : vi . fn ( ( event : string , handler : ( event : string , path : string ) => void ) => {
399+ if ( event === 'all' ) {
400+ allEventHandler = handler
401+ }
402+ return mockWatcher
403+ } ) ,
404+ close : vi . fn ( ) ,
405+ }
406+ vi . mocked ( watch ) . mockReturnValue ( mockWatcher as any )
407+
408+ const nitro = createMockNitro ( )
409+ const watchDirs = [ '/project/server/graphql' ]
410+
411+ setupFileWatcher ( nitro , watchDirs )
412+
413+ // Simulate a .graphql file change
414+ expect ( allEventHandler ) . not . toBeNull ( )
415+ allEventHandler ! ( 'change' , '/project/server/graphql/schema.graphql' )
416+
417+ // Wait for debounced function to be called
418+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
419+
420+ expect ( performGraphQLScan ) . toHaveBeenCalledWith ( nitro , { silent : true , isRescan : true } )
421+ } )
422+
423+ it ( 'should call performGraphQLScan when resolver file changes' , async ( ) => {
424+ const { watch } = await import ( 'chokidar' )
425+ const { performGraphQLScan } = await import ( '../../../src/nitro/setup/scanner' )
426+
427+ let allEventHandler : ( ( event : string , path : string ) => void ) | null = null
428+ const mockWatcher = {
429+ on : vi . fn ( ( event : string , handler : ( event : string , path : string ) => void ) => {
430+ if ( event === 'all' ) {
431+ allEventHandler = handler
432+ }
433+ return mockWatcher
434+ } ) ,
435+ close : vi . fn ( ) ,
436+ }
437+ vi . mocked ( watch ) . mockReturnValue ( mockWatcher as any )
438+
439+ const nitro = createMockNitro ( )
440+ const watchDirs = [ '/project/server/graphql' ]
441+
442+ setupFileWatcher ( nitro , watchDirs )
443+
444+ // Simulate a .resolver.ts file change
445+ allEventHandler ! ( 'change' , '/project/server/graphql/user.resolver.ts' )
446+
447+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
448+
449+ expect ( performGraphQLScan ) . toHaveBeenCalledWith ( nitro , { silent : true , isRescan : true } )
450+ } )
451+
452+ it ( 'should NOT call performGraphQLScan for non-graphql files' , async ( ) => {
453+ const { watch } = await import ( 'chokidar' )
454+ const { performGraphQLScan } = await import ( '../../../src/nitro/setup/scanner' )
455+
456+ vi . mocked ( performGraphQLScan ) . mockClear ( )
457+
458+ let allEventHandler : ( ( event : string , path : string ) => void ) | null = null
459+ const mockWatcher = {
460+ on : vi . fn ( ( event : string , handler : ( event : string , path : string ) => void ) => {
461+ if ( event === 'all' ) {
462+ allEventHandler = handler
463+ }
464+ return mockWatcher
465+ } ) ,
466+ close : vi . fn ( ) ,
467+ }
468+ vi . mocked ( watch ) . mockReturnValue ( mockWatcher as any )
469+
470+ const nitro = createMockNitro ( )
471+ const watchDirs = [ '/project/server/graphql' ]
472+
473+ setupFileWatcher ( nitro , watchDirs )
474+
475+ // Simulate a non-graphql file change
476+ allEventHandler ! ( 'change' , '/project/server/graphql/utils.ts' )
477+
478+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
479+
480+ expect ( performGraphQLScan ) . not . toHaveBeenCalled ( )
481+ } )
482+
483+ it ( 'should NOT call performGraphQLScan for sdk.ts files' , async ( ) => {
484+ const { watch } = await import ( 'chokidar' )
485+ const { performGraphQLScan } = await import ( '../../../src/nitro/setup/scanner' )
486+
487+ vi . mocked ( performGraphQLScan ) . mockClear ( )
488+
489+ let allEventHandler : ( ( event : string , path : string ) => void ) | null = null
490+ const mockWatcher = {
491+ on : vi . fn ( ( event : string , handler : ( event : string , path : string ) => void ) => {
492+ if ( event === 'all' ) {
493+ allEventHandler = handler
494+ }
495+ return mockWatcher
496+ } ) ,
497+ close : vi . fn ( ) ,
498+ }
499+ vi . mocked ( watch ) . mockReturnValue ( mockWatcher as any )
500+
501+ const nitro = createMockNitro ( )
502+ const watchDirs = [ '/project/server/graphql' ]
503+
504+ setupFileWatcher ( nitro , watchDirs )
505+
506+ // Simulate sdk.ts file change (should be ignored)
507+ allEventHandler ! ( 'change' , '/project/graphql/sdk.ts' )
508+
509+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
510+
511+ expect ( performGraphQLScan ) . not . toHaveBeenCalled ( )
512+ } )
513+
514+ it ( 'should use performGraphQLScan with isRescan:true to respect skipLocalScan during file changes' , async ( ) => {
515+ // This test verifies the fix for the bug where skipLocalScan was ignored during rescan.
516+ // Previously, processChanges called NitroAdapter.scanSchemas directly which ignored skipLocalScan.
517+ // Now it calls performGraphQLScan which properly handles skipLocalScan.
518+ const { watch } = await import ( 'chokidar' )
519+ const { performGraphQLScan } = await import ( '../../../src/nitro/setup/scanner' )
520+ const { NitroAdapter } = await import ( '../../../src/nitro/adapter' )
521+
522+ vi . mocked ( performGraphQLScan ) . mockClear ( )
523+ vi . mocked ( NitroAdapter . scanSchemas ) . mockClear ( )
524+ vi . mocked ( NitroAdapter . scanResolvers ) . mockClear ( )
525+ vi . mocked ( NitroAdapter . scanDirectives ) . mockClear ( )
526+
527+ let allEventHandler : ( ( event : string , path : string ) => void ) | null = null
528+ const mockWatcher = {
529+ on : vi . fn ( ( event : string , handler : ( event : string , path : string ) => void ) => {
530+ if ( event === 'all' ) {
531+ allEventHandler = handler
532+ }
533+ return mockWatcher
534+ } ) ,
535+ close : vi . fn ( ) ,
536+ }
537+ vi . mocked ( watch ) . mockReturnValue ( mockWatcher as any )
538+
539+ // Create nitro with skipLocalScan: true
540+ const nitro = createMockNitro ( {
541+ options : {
542+ rootDir : '/project' ,
543+ dev : true ,
544+ framework : { name : 'nitro' } ,
545+ graphql : { skipLocalScan : true } ,
546+ ignore : [ ] ,
547+ } as any ,
548+ graphql : {
549+ buildDir : '/project/.nitro' ,
550+ serverDir : '/project/server/graphql' ,
551+ clientDir : '/project/graphql' ,
552+ } as any ,
553+ } )
554+ // Watch extend package directory (simulating extend: ['./auth'])
555+ const watchDirs = [ '/packages/auth/server/graphql' ]
556+
557+ setupFileWatcher ( nitro , watchDirs )
558+
559+ // Simulate a file change in extend package (path includes 'server/graphql' so it's detected as server file)
560+ allEventHandler ! ( 'change' , '/packages/auth/server/graphql/schema.graphql' )
561+
562+ await new Promise ( resolve => setTimeout ( resolve , 10 ) )
563+
564+ // Should call performGraphQLScan (which respects skipLocalScan)
565+ expect ( performGraphQLScan ) . toHaveBeenCalledWith ( nitro , { silent : true , isRescan : true } )
566+
567+ // Should NOT call NitroAdapter methods directly (old buggy behavior)
568+ expect ( NitroAdapter . scanSchemas ) . not . toHaveBeenCalled ( )
569+ expect ( NitroAdapter . scanResolvers ) . not . toHaveBeenCalled ( )
570+ expect ( NitroAdapter . scanDirectives ) . not . toHaveBeenCalled ( )
571+ } )
572+ } )
388573} )
389574
390575describe ( 'getWatchDirectories' , ( ) => {
0 commit comments