1111'use strict' ;
1212
1313const {
14+ configureAppForSwift,
1415 findXcodeProjectDirectory,
1516 runIosPrebuild,
1617 runPodDeintegrate,
@@ -19,6 +20,19 @@ const {
1920// Mock child_process module
2021jest . mock ( 'child_process' ) ;
2122
23+ // Mock fs module
24+ jest . mock ( 'fs' ) ;
25+
26+ // Mock path module for absolute paths
27+ jest . mock ( 'path' , ( ) => {
28+ const actualPath = jest . requireActual ( 'path' ) ;
29+ return {
30+ ...actualPath ,
31+ join : jest . fn ( ( ...args ) => args . join ( '/' ) ) ,
32+ relative : jest . fn ( ( from , to ) => to . replace ( from + '/' , '' ) ) ,
33+ } ;
34+ } ) ;
35+
2236// Mock console methods - disable React Native's strict console checking
2337const originalConsole = global . console ;
2438
@@ -468,3 +482,272 @@ describe('runPodDeintegrate', () => {
468482 ) ;
469483 } ) ;
470484} ) ;
485+
486+ describe ( 'configureAppForSwift' , ( ) => {
487+ let mockFs ;
488+ let mockPath ;
489+ let mockConsoleLog ;
490+
491+ beforeEach ( ( ) => {
492+ // Setup mocks
493+ mockFs = require ( 'fs' ) ;
494+ mockPath = require ( 'path' ) ;
495+ mockConsoleLog = console . log ;
496+
497+ // Clear and reset all mocks completely
498+ jest . clearAllMocks ( ) ;
499+ jest . resetAllMocks ( ) ;
500+
501+ // Set up fresh mock implementations
502+ mockFs . existsSync = jest . fn ( ) ;
503+ mockFs . mkdirSync = jest . fn ( ) ;
504+ mockFs . unlinkSync = jest . fn ( ) ;
505+ mockFs . linkSync = jest . fn ( ) ;
506+ mockFs . symlinkSync = jest . fn ( ) ;
507+ mockFs . writeFileSync = jest . fn ( ) ;
508+
509+ // Mock path.join to return realistic paths
510+ mockPath . join . mockImplementation ( ( ...args ) => args . join ( '/' ) ) ;
511+
512+ // Mock path.relative to return realistic relative paths
513+ mockPath . relative . mockImplementation ( ( from , to ) => {
514+ // Simple implementation for tests
515+ return to . replace ( from + '/' , '' ) ;
516+ } ) ;
517+ } ) ;
518+
519+ it ( 'should configure app for Swift integration successfully' , async ( ) => {
520+ // Setup
521+ const reactNativePath = '/path/to/react-native' ;
522+
523+ // Mock file system calls
524+ mockFs . existsSync
525+ . mockReturnValueOnce ( true ) // reactIncludesReactPath exists
526+ . mockReturnValueOnce ( false ) // destUmbrellaPath doesn't exist
527+ . mockReturnValueOnce ( true ) ; // sourceUmbrellaPath exists
528+
529+ mockFs . mkdirSync . mockImplementation ( ( ) => { } ) ;
530+ mockFs . symlinkSync . mockImplementation ( ( ) => { } ) ;
531+ mockFs . writeFileSync . mockImplementation ( ( ) => { } ) ;
532+
533+ // Execute
534+ await configureAppForSwift ( reactNativePath ) ;
535+
536+ // Assert file system calls
537+ expect ( mockFs . existsSync ) . toHaveBeenCalledWith (
538+ '/path/to/react-native/React/includes/React' ,
539+ ) ;
540+ expect ( mockFs . existsSync ) . toHaveBeenCalledWith (
541+ '/path/to/react-native/React/includes/React/React-umbrella.h' ,
542+ ) ;
543+ expect ( mockFs . existsSync ) . toHaveBeenCalledWith (
544+ '/path/to/react-native/scripts/ios-prebuild/React-umbrella.h' ,
545+ ) ;
546+
547+ expect ( mockFs . symlinkSync ) . toHaveBeenCalledWith (
548+ '/path/to/react-native/scripts/ios-prebuild/React-umbrella.h' ,
549+ '/path/to/react-native/React/includes/React/React-umbrella.h' ,
550+ ) ;
551+
552+ expect ( mockFs . linkSync ) . not . toHaveBeenCalled ( ) ;
553+
554+ // Verify module.modulemap content
555+ const expectedModuleMapContent = `framework module React {
556+ umbrella header "/path/to/react-native/React/includes/React/React-umbrella.h"
557+ export *
558+ module * { export * }
559+ }
560+ ` ;
561+ expect ( mockFs . writeFileSync ) . toHaveBeenCalledWith (
562+ '/path/to/react-native/React/includes/module.modulemap' ,
563+ expectedModuleMapContent ,
564+ 'utf8' ,
565+ ) ;
566+
567+ // Verify console output
568+ expect ( mockConsoleLog ) . toHaveBeenCalledWith (
569+ 'Configuring app for Swift integration...' ,
570+ ) ;
571+ expect ( mockConsoleLog ) . toHaveBeenCalledWith (
572+ '✓ Created hardlink: React-umbrella.h -> scripts/ios-prebuild/React-umbrella.h' ,
573+ ) ;
574+ expect ( mockConsoleLog ) . toHaveBeenCalledWith (
575+ '✓ Generated module.modulemap file' ,
576+ ) ;
577+ expect ( mockConsoleLog ) . toHaveBeenCalledWith (
578+ '✓ App configured for Swift integration' ,
579+ ) ;
580+ } ) ;
581+
582+ it ( 'should remove existing hardlink before creating new one' , async ( ) => {
583+ // Setup
584+ const reactNativePath = '/path/to/react-native' ;
585+
586+ mockFs . existsSync
587+ . mockReturnValueOnce ( true ) // reactIncludesReactPath exists
588+ . mockReturnValueOnce ( true ) // destUmbrellaPath exists (should be removed)
589+ . mockReturnValueOnce ( true ) ; // sourceUmbrellaPath exists
590+
591+ mockFs . mkdirSync . mockImplementation ( ( ) => { } ) ;
592+ mockFs . unlinkSync . mockImplementation ( ( ) => { } ) ;
593+ mockFs . linkSync . mockImplementation ( ( ) => { } ) ;
594+ mockFs . writeFileSync . mockImplementation ( ( ) => { } ) ;
595+
596+ // Execute
597+ await configureAppForSwift ( reactNativePath ) ;
598+
599+ // Assert
600+ expect ( mockFs . unlinkSync ) . toHaveBeenCalledWith (
601+ '/path/to/react-native/React/includes/React/React-umbrella.h' ,
602+ ) ;
603+ } ) ;
604+
605+ it ( 'should handle different React Native paths' , async ( ) => {
606+ // Setup
607+ const reactNativePath = '/Users/developer/react-native' ;
608+
609+ mockFs . existsSync
610+ . mockReturnValueOnce ( true )
611+ . mockReturnValueOnce ( false )
612+ . mockReturnValueOnce ( true ) ;
613+
614+ mockFs . linkSync . mockImplementation ( ( ) => { } ) ;
615+ mockFs . writeFileSync . mockImplementation ( ( ) => { } ) ;
616+
617+ // Execute
618+ await configureAppForSwift ( reactNativePath ) ;
619+
620+ // Assert
621+ expect ( mockFs . symlinkSync ) . toHaveBeenCalledWith (
622+ '/Users/developer/react-native/scripts/ios-prebuild/React-umbrella.h' ,
623+ '/Users/developer/react-native/React/includes/React/React-umbrella.h' ,
624+ ) ;
625+
626+ expect ( mockFs . linkSync ) . not . toHaveBeenCalled ( ) ;
627+ } ) ;
628+
629+ it ( 'should throw error when source umbrella header does not exist' , async ( ) => {
630+ // Setup
631+ const reactNativePath = '/path/to/react-native' ;
632+
633+ mockFs . existsSync
634+ . mockReturnValueOnce ( true ) // reactIncludesReactPath exists
635+ . mockReturnValueOnce ( false ) // destUmbrellaPath doesn't exist
636+ . mockReturnValueOnce ( false ) ; // sourceUmbrellaPath doesn't exist
637+
638+ // Execute & Assert
639+ await expect ( configureAppForSwift ( reactNativePath ) ) . rejects . toThrow (
640+ 'Swift configuration failed: Source umbrella header not found: /path/to/react-native/scripts/ios-prebuild/React-umbrella.h' ,
641+ ) ;
642+
643+ expect ( mockConsoleLog ) . toHaveBeenCalledWith (
644+ 'Configuring app for Swift integration...' ,
645+ ) ;
646+ expect ( mockConsoleLog ) . not . toHaveBeenCalledWith (
647+ '✓ App configured for Swift integration' ,
648+ ) ;
649+ } ) ;
650+
651+ it ( 'should throw error when hardlink creation fails' , async ( ) => {
652+ // Setup
653+ const reactNativePath = '/path/to/react-native' ;
654+
655+ mockFs . existsSync
656+ . mockReturnValueOnce ( true )
657+ . mockReturnValueOnce ( false )
658+ . mockReturnValueOnce ( true ) ;
659+
660+ mockFs . symlinkSync . mockImplementation ( ( ) => {
661+ throw new Error ( 'Permission denied' ) ;
662+ } ) ;
663+
664+ // Execute & Assert
665+ await expect ( configureAppForSwift ( reactNativePath ) ) . rejects . toThrow (
666+ 'Swift configuration failed: Permission denied' ,
667+ ) ;
668+ } ) ;
669+
670+ it ( 'should throw error when module.modulemap write fails' , async ( ) => {
671+ // Setup
672+ const reactNativePath = '/path/to/react-native' ;
673+
674+ mockFs . existsSync
675+ . mockReturnValueOnce ( true )
676+ . mockReturnValueOnce ( false )
677+ . mockReturnValueOnce ( true ) ;
678+
679+ mockFs . symlinkSync . mockImplementation ( ( ) => { } ) ;
680+ mockFs . writeFileSync . mockImplementation ( ( ) => {
681+ throw new Error ( 'Disk full' ) ;
682+ } ) ;
683+
684+ // Execute & Assert
685+ await expect ( configureAppForSwift ( reactNativePath ) ) . rejects . toThrow (
686+ 'Swift configuration failed: Disk full' ,
687+ ) ;
688+ } ) ;
689+
690+ it ( 'should throw error when directory creation fails' , async ( ) => {
691+ // Setup
692+ const reactNativePath = '/path/to/react-native' ;
693+
694+ mockFs . existsSync . mockReturnValueOnce ( false ) ; // reactIncludesReactPath doesn't exist
695+
696+ mockFs . mkdirSync . mockImplementation ( ( ) => {
697+ throw new Error ( 'Permission denied' ) ;
698+ } ) ;
699+
700+ // Execute & Assert
701+ await expect ( configureAppForSwift ( reactNativePath ) ) . rejects . toThrow (
702+ 'Swift configuration failed: Permission denied' ,
703+ ) ;
704+ } ) ;
705+
706+ it ( 'should handle file unlink errors gracefully' , async ( ) => {
707+ // Setup
708+ const reactNativePath = '/path/to/react-native' ;
709+
710+ mockFs . existsSync
711+ . mockReturnValueOnce ( true )
712+ . mockReturnValueOnce ( true ) // destUmbrellaPath exists
713+ . mockReturnValueOnce ( true ) ;
714+
715+ mockFs . unlinkSync . mockImplementation ( ( ) => {
716+ throw new Error ( 'File in use' ) ;
717+ } ) ;
718+
719+ // Execute & Assert
720+ await expect ( configureAppForSwift ( reactNativePath ) ) . rejects . toThrow (
721+ 'Swift configuration failed: File in use' ,
722+ ) ;
723+ } ) ;
724+
725+ it ( 'should generate correct module.modulemap content with absolute path' , async ( ) => {
726+ // Setup
727+ const reactNativePath = '/custom/path/to/react-native' ;
728+
729+ mockFs . existsSync
730+ . mockReturnValueOnce ( true )
731+ . mockReturnValueOnce ( false )
732+ . mockReturnValueOnce ( true ) ;
733+
734+ mockFs . linkSync . mockImplementation ( ( ) => { } ) ;
735+ mockFs . writeFileSync . mockImplementation ( ( ) => { } ) ;
736+
737+ // Execute
738+ await configureAppForSwift ( reactNativePath ) ;
739+
740+ // Assert module.modulemap content with correct absolute path
741+ const expectedModuleMapContent = `framework module React {
742+ umbrella header "/custom/path/to/react-native/React/includes/React/React-umbrella.h"
743+ export *
744+ module * { export * }
745+ }
746+ ` ;
747+ expect ( mockFs . writeFileSync ) . toHaveBeenCalledWith (
748+ '/custom/path/to/react-native/React/includes/module.modulemap' ,
749+ expectedModuleMapContent ,
750+ 'utf8' ,
751+ ) ;
752+ } ) ;
753+ } ) ;
0 commit comments