/
githubmr
/
facebook-react-native
Обзор
Документация
Войти
/
githubmr
/
facebook-react-native
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js
1 633 строки
47 KB
Riccardo Cipolleschi
Add function to coordinate the headers preparation for the app (#53677)
14 окт 2025, 02:30
14 окт 2025, 02:30
b44d4b4
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format * @noflow */ 'use strict'; const { configureAppForSwift, createHardlinks, findXcodeProjectDirectory, generateCodegenArtifacts, prepareHeaders, runIosPrebuild, runPodDeintegrate, setBuildFromSource, } = require('../prepare-app-utils'); // Mock child_process module jest.mock('child_process'); // Mock fs module jest.mock('fs'); // Mock headers-utils module jest.mock('../headers-utils'); // Mock prepare-app-dependencies-headers module jest.mock('../prepare-app-dependencies-headers'); // Mock codegen executor module jest.mock('../../codegen/generate-artifacts-executor'); // Mock path module for absolute paths jest.mock('path', () => { const actualPath = jest.requireActual('path'); return { ...actualPath, join: jest.fn((...args) => args.join('/')), relative: jest.fn((from, to) => to.replace(from + '/', '')), }; }); describe('createHardlinks', () => { let mockSymlinkReactNativeHeaders; let mockSymlinkThirdPartyDependenciesHeaders; let mockPath; let mockConsoleLog; beforeEach(() => { // Setup mocks const headersUtils = require('../headers-utils'); const prepareAppDependenciesHeaders = require('../prepare-app-dependencies-headers'); mockSymlinkReactNativeHeaders = prepareAppDependenciesHeaders.symlinkReactNativeHeaders; mockSymlinkThirdPartyDependenciesHeaders = headersUtils.symlinkThirdPartyDependenciesHeaders; mockPath = require('path'); mockConsoleLog = console.log; // Clear and reset all mocks completely jest.clearAllMocks(); jest.resetAllMocks(); // Set up fresh mock implementations mockSymlinkReactNativeHeaders.mockImplementation(() => {}); mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {}); // Mock path.join to return realistic paths mockPath.join.mockImplementation((...args) => args.join('/')); }); it('should create hard links successfully', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockSymlinkReactNativeHeaders.mockImplementation(() => {}); mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {}); // Execute await createHardlinks(reactNativePath); // Assert const expectedReactIncludesPath = '/path/to/react-native/React'; expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledTimes(1); expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledTimes(1); // Verify console output expect(mockConsoleLog).toHaveBeenCalledWith( 'Creating hard links for React Native headers...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ React Native hard links created in React/includes', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Creating hard links for third-party dependencies...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ Third-party dependencies hard links created in React/includes', ); }); it('should throw error when symlinkReactNativeHeaders fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const originalError = new Error('React Native headers linking failed'); mockSymlinkReactNativeHeaders.mockImplementation(() => { throw originalError; }); // Execute & Assert await expect(createHardlinks(reactNativePath)).rejects.toThrow( 'Hard link creation failed: React Native headers linking failed', ); const expectedReactIncludesPath = '/path/to/react-native/React'; expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkThirdPartyDependenciesHeaders).not.toHaveBeenCalled(); expect(mockConsoleLog).toHaveBeenCalledWith( 'Creating hard links for React Native headers...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ React Native hard links created in React/includes', ); }); it('should throw error when symlinkThirdPartyDependenciesHeaders fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const originalError = new Error('Third-party dependencies linking failed'); mockSymlinkReactNativeHeaders.mockImplementation(() => {}); mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => { throw originalError; }); // Execute & Assert await expect(createHardlinks(reactNativePath)).rejects.toThrow( 'Hard link creation failed: Third-party dependencies linking failed', ); const expectedReactIncludesPath = '/path/to/react-native/React'; expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Creating hard links for React Native headers...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ React Native hard links created in React/includes', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Creating hard links for third-party dependencies...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ Third-party dependencies hard links created in React/includes', ); }); it('should handle different React Native paths correctly', async () => { // Setup const reactNativePath = '/Users/developer/custom-react-native-path'; mockSymlinkReactNativeHeaders.mockImplementation(() => {}); mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {}); // Execute await createHardlinks(reactNativePath); // Assert const expectedReactIncludesPath = '/Users/developer/custom-react-native-path/React'; expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); }); it('should handle paths with spaces correctly', async () => { // Setup const reactNativePath = '/path/to/react native project'; mockSymlinkReactNativeHeaders.mockImplementation(() => {}); mockSymlinkThirdPartyDependenciesHeaders.mockImplementation(() => {}); // Execute await createHardlinks(reactNativePath); // Assert const expectedReactIncludesPath = '/path/to/react native project/React'; expect(mockSymlinkReactNativeHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); expect(mockSymlinkThirdPartyDependenciesHeaders).toHaveBeenCalledWith( reactNativePath, expectedReactIncludesPath, 'includes', ); }); }); describe('generateCodegenArtifacts', () => { let mockCodegenExecutor; let mockConsoleLog; beforeEach(() => { // Setup mocks mockCodegenExecutor = require('../../codegen/generate-artifacts-executor'); mockConsoleLog = console.log; // Clear and reset all mocks completely jest.clearAllMocks(); jest.resetAllMocks(); // Set up fresh mock implementations mockCodegenExecutor.execute = jest.fn(); }); it('should run codegen successfully', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appPath = '/path/to/app'; const appIosPath = '/path/to/app/ios'; mockCodegenExecutor.execute.mockImplementation(() => {}); // Execute await generateCodegenArtifacts(reactNativePath, appPath, appIosPath); // Assert expect(mockCodegenExecutor.execute).toHaveBeenCalledWith( appPath, 'ios', appIosPath, 'app', ); expect(mockCodegenExecutor.execute).toHaveBeenCalledTimes(1); // Verify console output expect(mockConsoleLog).toHaveBeenCalledWith( 'Generating codegen artifacts...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ Codegen artifacts generated', ); }); it('should throw error when codegen execution fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appPath = '/path/to/app'; const appIosPath = '/path/to/app/ios'; const originalError = new Error('Codegen failed to generate artifacts'); mockCodegenExecutor.execute.mockImplementation(() => { throw originalError; }); // Execute & Assert await expect( generateCodegenArtifacts(reactNativePath, appPath, appIosPath), ).rejects.toThrow( 'Codegen generation failed: Codegen failed to generate artifacts', ); expect(mockCodegenExecutor.execute).toHaveBeenCalledWith( appPath, 'ios', appIosPath, 'app', ); expect(mockCodegenExecutor.execute).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toHaveBeenCalledWith( 'Generating codegen artifacts...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ Codegen artifacts generated', ); }); it('should handle different paths correctly', async () => { // Setup const reactNativePath = '/Users/developer/react-native'; const appPath = '/Users/developer/MyApp'; const appIosPath = '/Users/developer/MyApp/ios'; mockCodegenExecutor.execute.mockImplementation(() => {}); // Execute await generateCodegenArtifacts(reactNativePath, appPath, appIosPath); // Assert expect(mockCodegenExecutor.execute).toHaveBeenCalledWith( appPath, 'ios', appIosPath, 'app', ); }); it('should handle paths with spaces correctly', async () => { // Setup const reactNativePath = '/path/to/react native'; const appPath = '/path/to/my app'; const appIosPath = '/path/to/my app/ios folder'; mockCodegenExecutor.execute.mockImplementation(() => {}); // Execute await generateCodegenArtifacts(reactNativePath, appPath, appIosPath); // Assert expect(mockCodegenExecutor.execute).toHaveBeenCalledWith( appPath, 'ios', appIosPath, 'app', ); }); }); describe('prepareHeaders', () => { let mockPrepareAppDependenciesHeaders; let mockPath; let mockConsoleLog; beforeEach(() => { // Setup mocks const prepareAppDependenciesHeadersModule = require('../prepare-app-dependencies-headers'); mockPrepareAppDependenciesHeaders = prepareAppDependenciesHeadersModule.prepareAppDependenciesHeaders; mockPath = require('path'); mockConsoleLog = console.log; // Clear and reset all mocks completely jest.clearAllMocks(); jest.resetAllMocks(); // Set up fresh mock implementations mockPrepareAppDependenciesHeaders.mockImplementation(() => {}); // Mock path.join to return realistic paths mockPath.join.mockImplementation((...args) => args.join('/')); }); it('should prepare all three types of headers successfully', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appIosPath = '/path/to/app/ios'; mockPrepareAppDependenciesHeaders.mockImplementation(() => {}); // Execute await prepareHeaders(reactNativePath, appIosPath); // Assert const expectedOutputFolder = '/path/to/app/ios/build/generated/ios/ReactAppDependencyProvider'; const expectedCodegenOutputFolder = '/path/to/app/ios/build/generated/ios/ReactCodegen'; // Verify all three calls with correct parameters expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(3); // 1. Codegen headers expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 1, reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); // 2. React Native headers expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 2, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'react-native', ); // 3. Third-party dependencies headers expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 3, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'third-party-dependencies', ); // Verify console output expect(mockConsoleLog).toHaveBeenCalledWith('Preparing codegen headers...'); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Codegen headers prepared'); expect(mockConsoleLog).toHaveBeenCalledWith( 'Preparing react-native headers...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ React Native headers prepared', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Preparing third-party dependencies headers...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ Third-party dependencies headers prepared', ); }); it('should throw error when codegen header preparation fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appIosPath = '/path/to/app/ios'; const originalError = new Error('Codegen header preparation failed'); mockPrepareAppDependenciesHeaders.mockImplementationOnce(() => { throw originalError; }); // Execute & Assert await expect(prepareHeaders(reactNativePath, appIosPath)).rejects.toThrow( 'Header preparation failed: Codegen header preparation failed', ); const expectedOutputFolder = '/path/to/app/ios/build/generated/ios/ReactAppDependencyProvider'; expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(1); expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledWith( reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); expect(mockConsoleLog).toHaveBeenCalledWith('Preparing codegen headers...'); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ Codegen headers prepared', ); }); it('should throw error when react-native header preparation fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appIosPath = '/path/to/app/ios'; const originalError = new Error('React Native header preparation failed'); mockPrepareAppDependenciesHeaders .mockImplementationOnce(() => {}) // First call succeeds .mockImplementationOnce(() => { // Second call fails throw originalError; }); // Execute & Assert await expect(prepareHeaders(reactNativePath, appIosPath)).rejects.toThrow( 'Header preparation failed: React Native header preparation failed', ); const expectedOutputFolder = '/path/to/app/ios/build/generated/ios/ReactAppDependencyProvider'; const expectedCodegenOutputFolder = '/path/to/app/ios/build/generated/ios/ReactCodegen'; expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(2); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 1, reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 2, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'react-native', ); expect(mockConsoleLog).toHaveBeenCalledWith('Preparing codegen headers...'); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Codegen headers prepared'); expect(mockConsoleLog).toHaveBeenCalledWith( 'Preparing react-native headers...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ React Native headers prepared', ); }); it('should throw error when third-party dependencies header preparation fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const appIosPath = '/path/to/app/ios'; const originalError = new Error( 'Third-party dependencies header preparation failed', ); mockPrepareAppDependenciesHeaders .mockImplementationOnce(() => {}) // First call succeeds .mockImplementationOnce(() => {}) // Second call succeeds .mockImplementationOnce(() => { // Third call fails throw originalError; }); // Execute & Assert await expect(prepareHeaders(reactNativePath, appIosPath)).rejects.toThrow( 'Header preparation failed: Third-party dependencies header preparation failed', ); const expectedOutputFolder = '/path/to/app/ios/build/generated/ios/ReactAppDependencyProvider'; const expectedCodegenOutputFolder = '/path/to/app/ios/build/generated/ios/ReactCodegen'; expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(3); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 1, reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 2, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'react-native', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 3, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'third-party-dependencies', ); expect(mockConsoleLog).toHaveBeenCalledWith('Preparing codegen headers...'); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Codegen headers prepared'); expect(mockConsoleLog).toHaveBeenCalledWith( 'Preparing react-native headers...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ React Native headers prepared', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Preparing third-party dependencies headers...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ Third-party dependencies headers prepared', ); }); it('should handle different paths correctly', async () => { // Setup const reactNativePath = '/Users/developer/react-native'; const appIosPath = '/Users/developer/MyApp/ios'; mockPrepareAppDependenciesHeaders.mockImplementation(() => {}); // Execute await prepareHeaders(reactNativePath, appIosPath); // Assert const expectedOutputFolder = '/Users/developer/MyApp/ios/build/generated/ios/ReactAppDependencyProvider'; const expectedCodegenOutputFolder = '/Users/developer/MyApp/ios/build/generated/ios/ReactCodegen'; expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(3); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 1, reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 2, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'react-native', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 3, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'third-party-dependencies', ); }); it('should handle paths with spaces correctly', async () => { // Setup const reactNativePath = '/path/to/react native'; const appIosPath = '/path/to/my app/ios folder'; mockPrepareAppDependenciesHeaders.mockImplementation(() => {}); // Execute await prepareHeaders(reactNativePath, appIosPath); // Assert const expectedOutputFolder = '/path/to/my app/ios folder/build/generated/ios/ReactAppDependencyProvider'; const expectedCodegenOutputFolder = '/path/to/my app/ios folder/build/generated/ios/ReactCodegen'; expect(mockPrepareAppDependenciesHeaders).toHaveBeenCalledTimes(3); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 1, reactNativePath, appIosPath, expectedOutputFolder, 'codegen', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 2, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'react-native', ); expect(mockPrepareAppDependenciesHeaders).toHaveBeenNthCalledWith( 3, reactNativePath, appIosPath, expectedCodegenOutputFolder, 'third-party-dependencies', ); }); }); // Mock console methods - disable React Native's strict console checking const originalConsole = global.console; beforeAll(() => { global.console = { ...originalConsole, log: jest.fn(), warn: jest.fn(), }; }); afterAll(() => { global.console = originalConsole; }); describe('findXcodeProjectDirectory', () => { let mockExecSync; beforeEach(() => { // Setup mock const childProcess = require('child_process'); mockExecSync = childProcess.execSync; // Reset all mocks jest.clearAllMocks(); }); it('should find Xcode project directory successfully', () => { // Setup const appPath = '/path/to/app'; const xcodeProjectName = 'MyApp.xcodeproj'; const mockResult = '/path/to/app/ios/MyApp.xcodeproj'; mockExecSync.mockReturnValue(mockResult + '\n'); // Execute const result = findXcodeProjectDirectory(appPath, xcodeProjectName); // Assert expect(result).toBe('/path/to/app/ios'); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); expect(mockExecSync).toHaveBeenCalledTimes(1); }); it('should find Xcode project in nested subdirectory', () => { // Setup const appPath = '/Users/developer/ReactNativeApp'; const xcodeProjectName = 'ReactNativeApp.xcodeproj'; const mockResult = '/Users/developer/ReactNativeApp/ios/sub/ReactNativeApp.xcodeproj'; mockExecSync.mockReturnValue(mockResult + '\n'); // Execute const result = findXcodeProjectDirectory(appPath, xcodeProjectName); // Assert expect(result).toBe('/Users/developer/ReactNativeApp/ios/sub'); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); }); it('should handle project found at root level', () => { // Setup const appPath = '/path/to/project'; const xcodeProjectName = 'RootProject.xcodeproj'; const mockResult = '/path/to/project/RootProject.xcodeproj'; mockExecSync.mockReturnValue(mockResult + '\n'); // Execute const result = findXcodeProjectDirectory(appPath, xcodeProjectName); // Assert expect(result).toBe('/path/to/project'); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); }); it('should handle paths with spaces in directory names', () => { // Setup const appPath = '/path/to/my app'; const xcodeProjectName = 'My App.xcodeproj'; const mockResult = '/path/to/my app/ios folder/My App.xcodeproj'; mockExecSync.mockReturnValue(mockResult + '\n'); // Execute const result = findXcodeProjectDirectory(appPath, xcodeProjectName); // Assert expect(result).toBe('/path/to/my app/ios folder'); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); }); it('should throw error when Xcode project is not found', () => { // Setup const appPath = '/path/to/app'; const xcodeProjectName = 'NonExistent.xcodeproj'; mockExecSync.mockReturnValue(''); // Execute & Assert expect(() => findXcodeProjectDirectory(appPath, xcodeProjectName)).toThrow( `Xcode project 'NonExistent.xcodeproj' not found in '/path/to/app' or its subdirectories`, ); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); }); it('should throw error when find command returns only whitespace', () => { // Setup const appPath = '/path/to/app'; const xcodeProjectName = 'Missing.xcodeproj'; mockExecSync.mockReturnValue(' \n \t '); // Execute & Assert expect(() => findXcodeProjectDirectory(appPath, xcodeProjectName)).toThrow( `Xcode project 'Missing.xcodeproj' not found in '/path/to/app' or its subdirectories`, ); }); it('should properly escape quotes in app path', () => { // Setup const appPath = '/path/to/app with "quotes"'; const xcodeProjectName = 'MyApp.xcodeproj'; const mockResult = '/path/to/app with "quotes"/ios/MyApp.xcodeproj'; mockExecSync.mockReturnValue(mockResult + '\n'); // Execute const result = findXcodeProjectDirectory(appPath, xcodeProjectName); // Assert expect(result).toBe('/path/to/app with "quotes"/ios'); expect(mockExecSync).toHaveBeenCalledWith( `find "${appPath}" -name "${xcodeProjectName}" -type d -print`, {encoding: 'utf8'}, ); }); }); describe('setBuildFromSource', () => { let mockFs; let mockPath; let mockConsoleLog; let mockConsoleWarn; beforeEach(() => { // Setup mocks mockFs = require('fs'); mockPath = require('path'); mockConsoleLog = console.log; mockConsoleWarn = console.warn; // Clear and reset all mocks completely jest.clearAllMocks(); jest.resetAllMocks(); // Set up fresh mock implementations mockFs.existsSync = jest.fn(); mockFs.readFileSync = jest.fn(); mockFs.writeFileSync = jest.fn(); // Mock path.join to return realistic paths mockPath.join.mockImplementation((...args) => args.join('/')); }); it('should update BUILD_FROM_SOURCE from false to true successfully', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockPackageSwiftContent = ` // Package.swift import PackageDescription let BUILD_FROM_SOURCE = false let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // rest of package ) `; const expectedUpdatedContent = ` // Package.swift import PackageDescription let BUILD_FROM_SOURCE = true let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // rest of package ) `; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await setBuildFromSource(reactNativePath); // Assert expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', ); expect(mockFs.readFileSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', 'utf8', ); expect(mockFs.writeFileSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', expectedUpdatedContent, 'utf8', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ BUILD_FROM_SOURCE set to true in Package.swift', ); expect(mockConsoleWarn).not.toHaveBeenCalled(); }); it('should handle when BUILD_FROM_SOURCE is already true', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockPackageSwiftContent = ` // Package.swift import PackageDescription let BUILD_FROM_SOURCE = true let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // rest of package ) `; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await setBuildFromSource(reactNativePath); // Assert expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', ); expect(mockFs.readFileSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', 'utf8', ); expect(mockFs.writeFileSync).not.toHaveBeenCalled(); // Should not write when already true expect(mockConsoleLog).toHaveBeenCalledWith( 'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ BUILD_FROM_SOURCE is already set to true in Package.swift', ); expect(mockConsoleWarn).not.toHaveBeenCalled(); }); it('should warn when BUILD_FROM_SOURCE declaration is not found', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockPackageSwiftContent = ` // Package.swift import PackageDescription let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // rest of package without BUILD_FROM_SOURCE ) `; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await setBuildFromSource(reactNativePath); // Assert expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', ); expect(mockFs.readFileSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', 'utf8', ); expect(mockFs.writeFileSync).not.toHaveBeenCalled(); // Should not write when declaration not found expect(mockConsoleLog).toHaveBeenCalledWith( 'Updating BUILD_FROM_SOURCE in: /path/to/react-native/Package.swift', ); expect(mockConsoleWarn).toHaveBeenCalledWith( '⚠️ BUILD_FROM_SOURCE declaration not found in Package.swift', ); }); it('should throw error when Package.swift does not exist', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync.mockReturnValue(false); // Execute & Assert await expect(setBuildFromSource(reactNativePath)).rejects.toThrow( 'Package.swift not found at: /path/to/react-native/Package.swift', ); expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', ); expect(mockFs.readFileSync).not.toHaveBeenCalled(); expect(mockFs.writeFileSync).not.toHaveBeenCalled(); expect(mockConsoleLog).not.toHaveBeenCalled(); }); it('should handle multiple BUILD_FROM_SOURCE occurrences', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockPackageSwiftContent = ` // Package.swift import PackageDescription let BUILD_FROM_SOURCE = false // Some comment about BUILD_FROM_SOURCE = false let anotherVar = "BUILD_FROM_SOURCE = false in string" let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // Another BUILD_FROM_SOURCE = false comment ) `; const expectedUpdatedContent = ` // Package.swift import PackageDescription let BUILD_FROM_SOURCE = true // Some comment about BUILD_FROM_SOURCE = false let anotherVar = "BUILD_FROM_SOURCE = false in string" let package = Package( name: "ReactNative", platforms: [.iOS(.v13)], // Another BUILD_FROM_SOURCE = false comment ) `; mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockReturnValue(mockPackageSwiftContent); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await setBuildFromSource(reactNativePath); // Assert - should replace only the declaration, not comments or strings expect(mockFs.writeFileSync).toHaveBeenCalledWith( '/path/to/react-native/Package.swift', expectedUpdatedContent, 'utf8', ); }); }); describe('runIosPrebuild', () => { let mockExecSync; let mockConsoleLog; let originalProcessEnv; beforeEach(() => { // Setup mocks const childProcess = require('child_process'); mockExecSync = childProcess.execSync; mockConsoleLog = console.log; // Store original process.env to restore later originalProcessEnv = process.env; // Reset all mocks jest.clearAllMocks(); }); afterEach(() => { // Restore original process.env process.env = originalProcessEnv; }); it('should run iOS prebuild successfully with nightly versions', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockExecSync.mockReturnValue(undefined); // Execute await runIosPrebuild(reactNativePath); // Assert expect(mockExecSync).toHaveBeenCalledWith('node scripts/ios-prebuild -s', { cwd: reactNativePath, env: { ...originalProcessEnv, RN_DEP_VERSION: 'nightly', HERMES_VERSION: 'nightly', }, stdio: 'inherit', }); expect(mockExecSync).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running iOS prebuild with nightly versions...', ); expect(mockConsoleLog).toHaveBeenCalledWith('✓ iOS prebuild completed'); expect(mockConsoleLog).toHaveBeenCalledTimes(2); }); it('should handle different React Native paths', async () => { // Setup const reactNativePath = '/Users/developer/react-native'; mockExecSync.mockReturnValue(undefined); // Execute await runIosPrebuild(reactNativePath); // Assert expect(mockExecSync).toHaveBeenCalledWith('node scripts/ios-prebuild -s', { cwd: reactNativePath, env: { ...originalProcessEnv, RN_DEP_VERSION: 'nightly', HERMES_VERSION: 'nightly', }, stdio: 'inherit', }); }); it('should handle paths with spaces correctly', async () => { // Setup const reactNativePath = '/path/to/react native project'; mockExecSync.mockReturnValue(undefined); // Execute await runIosPrebuild(reactNativePath); // Assert expect(mockExecSync).toHaveBeenCalledWith('node scripts/ios-prebuild -s', { cwd: reactNativePath, env: { ...originalProcessEnv, RN_DEP_VERSION: 'nightly', HERMES_VERSION: 'nightly', }, stdio: 'inherit', }); }); it('should throw error when iOS prebuild fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockError = new Error('Build failed'); mockExecSync.mockImplementation(() => { throw mockError; }); // Execute & Assert await expect(runIosPrebuild(reactNativePath)).rejects.toThrow( 'iOS prebuild failed: Build failed', ); expect(mockExecSync).toHaveBeenCalledWith('node scripts/ios-prebuild -s', { cwd: reactNativePath, env: { ...originalProcessEnv, RN_DEP_VERSION: 'nightly', HERMES_VERSION: 'nightly', }, stdio: 'inherit', }); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running iOS prebuild with nightly versions...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith('✓ iOS prebuild completed'); }); it('should handle script not found error', async () => { // Setup const reactNativePath = '/path/to/react-native'; const mockError = new Error('Cannot find module scripts/ios-prebuild'); mockExecSync.mockImplementation(() => { throw mockError; }); // Execute & Assert await expect(runIosPrebuild(reactNativePath)).rejects.toThrow( 'iOS prebuild failed: Cannot find module scripts/ios-prebuild', ); }); it('should handle root directory path', async () => { // Setup const reactNativePath = '/'; mockExecSync.mockReturnValue(undefined); // Execute await runIosPrebuild(reactNativePath); // Assert expect(mockExecSync).toHaveBeenCalledWith('node scripts/ios-prebuild -s', { cwd: '/', env: { ...originalProcessEnv, RN_DEP_VERSION: 'nightly', HERMES_VERSION: 'nightly', }, stdio: 'inherit', }); }); }); describe('runPodDeintegrate', () => { let mockExecSync; let mockConsoleLog; let mockConsoleWarn; beforeEach(() => { // Setup mocks const childProcess = require('child_process'); mockExecSync = childProcess.execSync; mockConsoleLog = console.log; mockConsoleWarn = console.warn; // Reset all mocks jest.clearAllMocks(); }); it('should run pod deintegrate successfully', async () => { // Setup const appIosPath = '/path/to/app/ios'; mockExecSync.mockReturnValue(undefined); // Execute await runPodDeintegrate(appIosPath); // Assert expect(mockExecSync).toHaveBeenCalledWith('pod deintegrate', { cwd: appIosPath, stdio: 'inherit', }); expect(mockExecSync).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running pod deintegrate in: /path/to/app/ios', ); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Pod deintegrate completed'); expect(mockConsoleLog).toHaveBeenCalledTimes(2); expect(mockConsoleWarn).not.toHaveBeenCalled(); }); it('should handle different iOS directory paths', async () => { // Setup const appIosPath = '/Users/developer/MyApp/ios'; mockExecSync.mockReturnValue(undefined); // Execute await runPodDeintegrate(appIosPath); // Assert expect(mockExecSync).toHaveBeenCalledWith('pod deintegrate', { cwd: appIosPath, stdio: 'inherit', }); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running pod deintegrate in: /Users/developer/MyApp/ios', ); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Pod deintegrate completed'); }); it('should handle paths with spaces correctly', async () => { // Setup const appIosPath = '/path/to/my app/ios folder'; mockExecSync.mockReturnValue(undefined); // Execute await runPodDeintegrate(appIosPath); // Assert expect(mockExecSync).toHaveBeenCalledWith('pod deintegrate', { cwd: appIosPath, stdio: 'inherit', }); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running pod deintegrate in: /path/to/my app/ios folder', ); expect(mockConsoleLog).toHaveBeenCalledWith('✓ Pod deintegrate completed'); }); it('should handle pod deintegrate command failure gracefully', async () => { // Setup const appIosPath = '/path/to/app/ios'; const mockError = new Error('No Podfile.lock found'); mockExecSync.mockImplementation(() => { throw mockError; }); // Execute await runPodDeintegrate(appIosPath); // Assert expect(mockExecSync).toHaveBeenCalledWith('pod deintegrate', { cwd: appIosPath, stdio: 'inherit', }); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running pod deintegrate in: /path/to/app/ios', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ Pod deintegrate completed', ); expect(mockConsoleWarn).toHaveBeenCalledWith( '⚠️ Pod deintegrate failed (this might be expected if no Podfile.lock exists)', ); expect(mockConsoleWarn).toHaveBeenCalledTimes(1); }); it('should handle command not found error', async () => { // Setup const appIosPath = '/path/to/app/ios'; const mockError = new Error('command not found: pod'); mockExecSync.mockImplementation(() => { throw mockError; }); // Execute await runPodDeintegrate(appIosPath); // Assert expect(mockExecSync).toHaveBeenCalledWith('pod deintegrate', { cwd: appIosPath, stdio: 'inherit', }); expect(mockConsoleLog).toHaveBeenCalledWith( 'Running pod deintegrate in: /path/to/app/ios', ); expect(mockConsoleWarn).toHaveBeenCalledWith( '⚠️ Pod deintegrate failed (this might be expected if no Podfile.lock exists)', ); }); }); describe('configureAppForSwift', () => { let mockFs; let mockPath; let mockConsoleLog; beforeEach(() => { // Setup mocks mockFs = require('fs'); mockPath = require('path'); mockConsoleLog = console.log; // Clear and reset all mocks completely jest.clearAllMocks(); jest.resetAllMocks(); // Set up fresh mock implementations mockFs.existsSync = jest.fn(); mockFs.mkdirSync = jest.fn(); mockFs.unlinkSync = jest.fn(); mockFs.linkSync = jest.fn(); mockFs.symlinkSync = jest.fn(); mockFs.writeFileSync = jest.fn(); // Mock path.join to return realistic paths mockPath.join.mockImplementation((...args) => args.join('/')); // Mock path.relative to return realistic relative paths mockPath.relative.mockImplementation((from, to) => { // Simple implementation for tests return to.replace(from + '/', ''); }); }); it('should configure app for Swift integration successfully', async () => { // Setup const reactNativePath = '/path/to/react-native'; // Mock file system calls mockFs.existsSync .mockReturnValueOnce(true) // reactIncludesReactPath exists .mockReturnValueOnce(false) // destUmbrellaPath doesn't exist .mockReturnValueOnce(true); // sourceUmbrellaPath exists mockFs.mkdirSync.mockImplementation(() => {}); mockFs.symlinkSync.mockImplementation(() => {}); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await configureAppForSwift(reactNativePath); // Assert file system calls expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/React/includes/React', ); expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/React/includes/React/React-umbrella.h', ); expect(mockFs.existsSync).toHaveBeenCalledWith( '/path/to/react-native/scripts/ios-prebuild/React-umbrella.h', ); expect(mockFs.symlinkSync).toHaveBeenCalledWith( '/path/to/react-native/scripts/ios-prebuild/React-umbrella.h', '/path/to/react-native/React/includes/React/React-umbrella.h', ); expect(mockFs.linkSync).not.toHaveBeenCalled(); // Verify module.modulemap content const expectedModuleMapContent = `framework module React { umbrella header "/path/to/react-native/React/includes/React/React-umbrella.h" export * module * { export * } } `; expect(mockFs.writeFileSync).toHaveBeenCalledWith( '/path/to/react-native/React/includes/module.modulemap', expectedModuleMapContent, 'utf8', ); // Verify console output expect(mockConsoleLog).toHaveBeenCalledWith( 'Configuring app for Swift integration...', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ Created hardlink: React-umbrella.h -> scripts/ios-prebuild/React-umbrella.h', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ Generated module.modulemap file', ); expect(mockConsoleLog).toHaveBeenCalledWith( '✓ App configured for Swift integration', ); }); it('should remove existing hardlink before creating new one', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) // reactIncludesReactPath exists .mockReturnValueOnce(true) // destUmbrellaPath exists (should be removed) .mockReturnValueOnce(true); // sourceUmbrellaPath exists mockFs.mkdirSync.mockImplementation(() => {}); mockFs.unlinkSync.mockImplementation(() => {}); mockFs.linkSync.mockImplementation(() => {}); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await configureAppForSwift(reactNativePath); // Assert expect(mockFs.unlinkSync).toHaveBeenCalledWith( '/path/to/react-native/React/includes/React/React-umbrella.h', ); }); it('should handle different React Native paths', async () => { // Setup const reactNativePath = '/Users/developer/react-native'; mockFs.existsSync .mockReturnValueOnce(true) .mockReturnValueOnce(false) .mockReturnValueOnce(true); mockFs.linkSync.mockImplementation(() => {}); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await configureAppForSwift(reactNativePath); // Assert expect(mockFs.symlinkSync).toHaveBeenCalledWith( '/Users/developer/react-native/scripts/ios-prebuild/React-umbrella.h', '/Users/developer/react-native/React/includes/React/React-umbrella.h', ); expect(mockFs.linkSync).not.toHaveBeenCalled(); }); it('should throw error when source umbrella header does not exist', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) // reactIncludesReactPath exists .mockReturnValueOnce(false) // destUmbrellaPath doesn't exist .mockReturnValueOnce(false); // sourceUmbrellaPath doesn't exist // Execute & Assert await expect(configureAppForSwift(reactNativePath)).rejects.toThrow( 'Swift configuration failed: Source umbrella header not found: /path/to/react-native/scripts/ios-prebuild/React-umbrella.h', ); expect(mockConsoleLog).toHaveBeenCalledWith( 'Configuring app for Swift integration...', ); expect(mockConsoleLog).not.toHaveBeenCalledWith( '✓ App configured for Swift integration', ); }); it('should throw error when hardlink creation fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) .mockReturnValueOnce(false) .mockReturnValueOnce(true); mockFs.symlinkSync.mockImplementation(() => { throw new Error('Permission denied'); }); // Execute & Assert await expect(configureAppForSwift(reactNativePath)).rejects.toThrow( 'Swift configuration failed: Permission denied', ); }); it('should throw error when module.modulemap write fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) .mockReturnValueOnce(false) .mockReturnValueOnce(true); mockFs.symlinkSync.mockImplementation(() => {}); mockFs.writeFileSync.mockImplementation(() => { throw new Error('Disk full'); }); // Execute & Assert await expect(configureAppForSwift(reactNativePath)).rejects.toThrow( 'Swift configuration failed: Disk full', ); }); it('should throw error when directory creation fails', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync.mockReturnValueOnce(false); // reactIncludesReactPath doesn't exist mockFs.mkdirSync.mockImplementation(() => { throw new Error('Permission denied'); }); // Execute & Assert await expect(configureAppForSwift(reactNativePath)).rejects.toThrow( 'Swift configuration failed: Permission denied', ); }); it('should handle file unlink errors gracefully', async () => { // Setup const reactNativePath = '/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) .mockReturnValueOnce(true) // destUmbrellaPath exists .mockReturnValueOnce(true); mockFs.unlinkSync.mockImplementation(() => { throw new Error('File in use'); }); // Execute & Assert await expect(configureAppForSwift(reactNativePath)).rejects.toThrow( 'Swift configuration failed: File in use', ); }); it('should generate correct module.modulemap content with absolute path', async () => { // Setup const reactNativePath = '/custom/path/to/react-native'; mockFs.existsSync .mockReturnValueOnce(true) .mockReturnValueOnce(false) .mockReturnValueOnce(true); mockFs.linkSync.mockImplementation(() => {}); mockFs.writeFileSync.mockImplementation(() => {}); // Execute await configureAppForSwift(reactNativePath); // Assert module.modulemap content with correct absolute path const expectedModuleMapContent = `framework module React { umbrella header "/custom/path/to/react-native/React/includes/React/React-umbrella.h" export * module * { export * } } `; expect(mockFs.writeFileSync).toHaveBeenCalledWith( '/custom/path/to/react-native/React/includes/module.modulemap', expectedModuleMapContent, 'utf8', ); }); });