1import fs from 'fs'; 2import { vol } from 'memfs'; 3 4import { ExportedConfig, Mod } from '../../Plugin.types'; 5import { compileModsAsync } from '../mod-compiler'; 6import { withMod } from '../withMod'; 7import rnFixture from './fixtures/react-native-project'; 8 9jest.mock('fs'); 10 11describe(compileModsAsync, () => { 12 const projectRoot = '/app'; 13 beforeEach(async () => { 14 // Trick XDL Info.plist reading 15 Object.defineProperty(process, 'platform', { 16 value: 'not-darwin', 17 }); 18 vol.fromJSON(rnFixture, projectRoot); 19 }); 20 21 afterEach(() => { 22 vol.reset(); 23 }); 24 25 it('skips missing providers in loose mode', async () => { 26 // A basic plugin exported from an app.json 27 let exportedConfig: ExportedConfig = { 28 name: 'app', 29 slug: '', 30 mods: null, 31 }; 32 33 const action: Mod<any> = jest.fn((props) => { 34 // Capitalize app name 35 props.name = (props.name as string).toUpperCase(); 36 return props; 37 }); 38 // Apply mod 39 exportedConfig = withMod<any>(exportedConfig, { 40 platform: 'android', 41 mod: 'custom', 42 action, 43 }); 44 45 const config = await compileModsAsync(exportedConfig, { 46 projectRoot, 47 assertMissingModProviders: false, 48 }); 49 50 expect(config.name).toBe('app'); 51 // Base mods are skipped when no mods are applied, these shouldn't be defined. 52 expect(config.ios?.infoPlist).toBeUndefined(); 53 expect(config.ios?.entitlements).toBeUndefined(); 54 // Adds base mods 55 expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 56 57 expect(action).not.toBeCalled(); 58 }); 59 60 it('asserts missing providers', async () => { 61 // A basic plugin exported from an app.json 62 let exportedConfig: ExportedConfig = { 63 name: 'app', 64 slug: '', 65 mods: null, 66 }; 67 68 // Apply mod 69 exportedConfig = withMod<any>(exportedConfig, { 70 platform: 'android', 71 mod: 'custom', 72 action(config) { 73 return config; 74 }, 75 }); 76 77 await expect( 78 compileModsAsync(exportedConfig, { projectRoot, assertMissingModProviders: true }) 79 ).rejects.toThrow( 80 `Initial base modifier for "android.custom" is not a provider and therefore will not provide modResults to child mods` 81 ); 82 }); 83 84 it('compiles with no mods', async () => { 85 // A basic plugin exported from an app.json 86 const exportedConfig: ExportedConfig = { 87 name: 'app', 88 slug: '', 89 mods: null, 90 }; 91 const config = await compileModsAsync(exportedConfig, { projectRoot }); 92 93 expect(config.name).toBe('app'); 94 // Base mods are skipped when no mods are applied, these shouldn't be defined. 95 expect(config.ios?.infoPlist).toBeUndefined(); 96 expect(config.ios?.entitlements).toBeUndefined(); 97 // Adds base mods 98 expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 99 }); 100 101 it('compiles mods', async () => { 102 // A basic plugin exported from an app.json 103 let internalValue = ''; 104 const exportedConfig: ExportedConfig = { 105 name: 'app', 106 slug: '', 107 mods: { 108 ios: { 109 async infoPlist(config) { 110 // Store the incoming value 111 internalValue = config.modResults.CFBundleDevelopmentRegion; 112 // Modify the data 113 config.modResults.CFBundleDevelopmentRegion = 114 'CFBundleDevelopmentRegion-crazy-random-value'; 115 return config; 116 }, 117 }, 118 }, 119 }; 120 121 // Apply mod plugin 122 const config = await compileModsAsync(exportedConfig, { projectRoot }); 123 124 expect(internalValue).toBe('en'); 125 126 // App config should have been modified 127 expect(config.name).toBe('app'); 128 expect(config.ios.infoPlist).toBeDefined(); 129 // No entitlements mod means this won't be defined 130 expect(config.ios.entitlements).toBeUndefined(); 131 132 // Plugins should all be functions 133 expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 134 135 // Test that the actual file was rewritten. 136 const data = await fs.promises.readFile('/app/ios/ReactNativeProject/Info.plist', 'utf8'); 137 expect(data).toMatch(/CFBundleDevelopmentRegion-crazy-random-value/); 138 }); 139 140 for (const invalid of [[{}], null, 7]) { 141 it(`throws on invalid mod results (${invalid})`, async () => { 142 // A basic plugin exported from an app.json 143 const exportedConfig: ExportedConfig = { 144 name: 'app', 145 slug: '', 146 mods: { 147 ios: { 148 async infoPlist(config) { 149 // Return an invalid config 150 return invalid as any; 151 }, 152 }, 153 }, 154 }; 155 156 // Apply mod plugin 157 await expect(compileModsAsync(exportedConfig, { projectRoot })).rejects.toThrow( 158 /Mod `mods.ios.infoPlist` evaluated to an object that is not a valid project config/ 159 ); 160 }); 161 } 162}); 163