diff --git a/packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js b/packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js index 16be0053302..6df8c34434c 100644 --- a/packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js +++ b/packages/react-native/scripts/swiftpm/__tests__/prepare-app-utils-test.js @@ -15,6 +15,7 @@ const { findXcodeProjectDirectory, runIosPrebuild, runPodDeintegrate, + setBuildFromSource, } = require('../prepare-app-utils'); // Mock child_process module @@ -189,6 +190,237 @@ describe('findXcodeProjectDirectory', () => { }); }); +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; diff --git a/packages/react-native/scripts/swiftpm/prepare-app-utils.js b/packages/react-native/scripts/swiftpm/prepare-app-utils.js index 30b08df95d9..902094a145b 100644 --- a/packages/react-native/scripts/swiftpm/prepare-app-utils.js +++ b/packages/react-native/scripts/swiftpm/prepare-app-utils.js @@ -165,9 +165,51 @@ async function configureAppForSwift( } } +/** + * Set BUILD_FROM_SOURCE to true in Package.swift + */ +async function setBuildFromSource( + reactNativePath /*: string */, +) /*: Promise */ { + const packageSwiftPath = path.join(reactNativePath, 'Package.swift'); + + if (!fs.existsSync(packageSwiftPath)) { + throw new Error(`Package.swift not found at: ${packageSwiftPath}`); + } + + try { + console.log(`Updating BUILD_FROM_SOURCE in: ${packageSwiftPath}`); + + const content = fs.readFileSync(packageSwiftPath, 'utf8'); + + // Check if BUILD_FROM_SOURCE = false exists and replace it with true + if (content.includes('let BUILD_FROM_SOURCE = false')) { + const updatedContent = content.replace( + /let BUILD_FROM_SOURCE = false/g, + 'let BUILD_FROM_SOURCE = true', + ); + fs.writeFileSync(packageSwiftPath, updatedContent, 'utf8'); + console.log('✓ BUILD_FROM_SOURCE set to true in Package.swift'); + } else if (content.includes('let BUILD_FROM_SOURCE = true')) { + console.log( + '✓ BUILD_FROM_SOURCE is already set to true in Package.swift', + ); + } else { + console.warn( + '⚠️ BUILD_FROM_SOURCE declaration not found in Package.swift', + ); + } + } catch (error) { + throw new Error( + `Failed to update BUILD_FROM_SOURCE in Package.swift: ${error.message}`, + ); + } +} + module.exports = { findXcodeProjectDirectory, runPodDeintegrate, runIosPrebuild, configureAppForSwift, + setBuildFromSource, };