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