1082815dcSEvan Baconimport fs from 'fs'; 2082815dcSEvan Baconimport { vol } from 'memfs'; 3082815dcSEvan Bacon 4*8a424bebSJames Ideimport rnFixture from './fixtures/react-native-project'; 5082815dcSEvan Baconimport { ExportedConfig, Mod } from '../../Plugin.types'; 6082815dcSEvan Baconimport { compileModsAsync } from '../mod-compiler'; 7082815dcSEvan Baconimport { withMod } from '../withMod'; 8082815dcSEvan Bacon 9082815dcSEvan Baconjest.mock('fs'); 10082815dcSEvan Bacon 11082815dcSEvan Bacondescribe(compileModsAsync, () => { 12082815dcSEvan Bacon const projectRoot = '/app'; 13ed3bd27bSEvan Bacon const originalWarn = console.warn; 14082815dcSEvan Bacon beforeEach(async () => { 15ed3bd27bSEvan Bacon console.warn = jest.fn(); 16082815dcSEvan Bacon // Trick XDL Info.plist reading 17082815dcSEvan Bacon Object.defineProperty(process, 'platform', { 18082815dcSEvan Bacon value: 'not-darwin', 19082815dcSEvan Bacon }); 20082815dcSEvan Bacon vol.fromJSON(rnFixture, projectRoot); 21082815dcSEvan Bacon }); 22082815dcSEvan Bacon 23082815dcSEvan Bacon afterEach(() => { 24ed3bd27bSEvan Bacon console.warn = originalWarn; 25082815dcSEvan Bacon vol.reset(); 26082815dcSEvan Bacon }); 27082815dcSEvan Bacon 28082815dcSEvan Bacon it('skips missing providers in loose mode', async () => { 29082815dcSEvan Bacon // A basic plugin exported from an app.json 30082815dcSEvan Bacon let exportedConfig: ExportedConfig = { 31082815dcSEvan Bacon name: 'app', 32082815dcSEvan Bacon slug: '', 33082815dcSEvan Bacon mods: null, 34082815dcSEvan Bacon }; 35082815dcSEvan Bacon 36082815dcSEvan Bacon const action: Mod<any> = jest.fn((props) => { 37082815dcSEvan Bacon // Capitalize app name 38082815dcSEvan Bacon props.name = (props.name as string).toUpperCase(); 39082815dcSEvan Bacon return props; 40082815dcSEvan Bacon }); 41082815dcSEvan Bacon // Apply mod 42082815dcSEvan Bacon exportedConfig = withMod<any>(exportedConfig, { 43082815dcSEvan Bacon platform: 'android', 44082815dcSEvan Bacon mod: 'custom', 45082815dcSEvan Bacon action, 46082815dcSEvan Bacon }); 47082815dcSEvan Bacon 48082815dcSEvan Bacon const config = await compileModsAsync(exportedConfig, { 49082815dcSEvan Bacon projectRoot, 50082815dcSEvan Bacon assertMissingModProviders: false, 51082815dcSEvan Bacon }); 52082815dcSEvan Bacon 53082815dcSEvan Bacon expect(config.name).toBe('app'); 54082815dcSEvan Bacon // Base mods are skipped when no mods are applied, these shouldn't be defined. 55082815dcSEvan Bacon expect(config.ios?.infoPlist).toBeUndefined(); 56082815dcSEvan Bacon expect(config.ios?.entitlements).toBeUndefined(); 57082815dcSEvan Bacon // Adds base mods 58082815dcSEvan Bacon expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 59082815dcSEvan Bacon 60082815dcSEvan Bacon expect(action).not.toBeCalled(); 61082815dcSEvan Bacon }); 62082815dcSEvan Bacon 63082815dcSEvan Bacon it('asserts missing providers', async () => { 64082815dcSEvan Bacon // A basic plugin exported from an app.json 65082815dcSEvan Bacon let exportedConfig: ExportedConfig = { 66082815dcSEvan Bacon name: 'app', 67082815dcSEvan Bacon slug: '', 68082815dcSEvan Bacon mods: null, 69082815dcSEvan Bacon }; 70082815dcSEvan Bacon 71082815dcSEvan Bacon // Apply mod 72082815dcSEvan Bacon exportedConfig = withMod<any>(exportedConfig, { 73082815dcSEvan Bacon platform: 'android', 74082815dcSEvan Bacon mod: 'custom', 75082815dcSEvan Bacon action(config) { 76082815dcSEvan Bacon return config; 77082815dcSEvan Bacon }, 78082815dcSEvan Bacon }); 79082815dcSEvan Bacon 80082815dcSEvan Bacon await expect( 81082815dcSEvan Bacon compileModsAsync(exportedConfig, { projectRoot, assertMissingModProviders: true }) 82082815dcSEvan Bacon ).rejects.toThrow( 83082815dcSEvan Bacon `Initial base modifier for "android.custom" is not a provider and therefore will not provide modResults to child mods` 84082815dcSEvan Bacon ); 85082815dcSEvan Bacon }); 86082815dcSEvan Bacon 87082815dcSEvan Bacon it('compiles with no mods', async () => { 88082815dcSEvan Bacon // A basic plugin exported from an app.json 89082815dcSEvan Bacon const exportedConfig: ExportedConfig = { 90082815dcSEvan Bacon name: 'app', 91082815dcSEvan Bacon slug: '', 92082815dcSEvan Bacon mods: null, 93082815dcSEvan Bacon }; 94082815dcSEvan Bacon const config = await compileModsAsync(exportedConfig, { projectRoot }); 95082815dcSEvan Bacon 96082815dcSEvan Bacon expect(config.name).toBe('app'); 97082815dcSEvan Bacon // Base mods are skipped when no mods are applied, these shouldn't be defined. 98082815dcSEvan Bacon expect(config.ios?.infoPlist).toBeUndefined(); 99082815dcSEvan Bacon expect(config.ios?.entitlements).toBeUndefined(); 100082815dcSEvan Bacon // Adds base mods 101082815dcSEvan Bacon expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 102082815dcSEvan Bacon }); 103082815dcSEvan Bacon 104082815dcSEvan Bacon it('compiles mods', async () => { 105082815dcSEvan Bacon // A basic plugin exported from an app.json 106082815dcSEvan Bacon let internalValue = ''; 107082815dcSEvan Bacon const exportedConfig: ExportedConfig = { 108082815dcSEvan Bacon name: 'app', 109082815dcSEvan Bacon slug: '', 110082815dcSEvan Bacon mods: { 111082815dcSEvan Bacon ios: { 112082815dcSEvan Bacon async infoPlist(config) { 113082815dcSEvan Bacon // Store the incoming value 114082815dcSEvan Bacon internalValue = config.modResults.CFBundleDevelopmentRegion; 115082815dcSEvan Bacon // Modify the data 116082815dcSEvan Bacon config.modResults.CFBundleDevelopmentRegion = 117082815dcSEvan Bacon 'CFBundleDevelopmentRegion-crazy-random-value'; 118082815dcSEvan Bacon return config; 119082815dcSEvan Bacon }, 120082815dcSEvan Bacon }, 121082815dcSEvan Bacon }, 122082815dcSEvan Bacon }; 123082815dcSEvan Bacon 124082815dcSEvan Bacon // Apply mod plugin 125082815dcSEvan Bacon const config = await compileModsAsync(exportedConfig, { projectRoot }); 126082815dcSEvan Bacon 127ed3bd27bSEvan Bacon expect(internalValue).toBe('$(DEVELOPMENT_LANGUAGE)'); 128082815dcSEvan Bacon 129082815dcSEvan Bacon // App config should have been modified 130082815dcSEvan Bacon expect(config.name).toBe('app'); 131082815dcSEvan Bacon expect(config.ios.infoPlist).toBeDefined(); 132082815dcSEvan Bacon // No entitlements mod means this won't be defined 133082815dcSEvan Bacon expect(config.ios.entitlements).toBeUndefined(); 134082815dcSEvan Bacon 135082815dcSEvan Bacon // Plugins should all be functions 136082815dcSEvan Bacon expect(Object.values(config.mods.ios).every((value) => typeof value === 'function')).toBe(true); 137082815dcSEvan Bacon 138082815dcSEvan Bacon // Test that the actual file was rewritten. 139ed3bd27bSEvan Bacon const data = await fs.promises.readFile('/app/ios/HelloWorld/Info.plist', 'utf8'); 140082815dcSEvan Bacon expect(data).toMatch(/CFBundleDevelopmentRegion-crazy-random-value/); 141082815dcSEvan Bacon }); 142082815dcSEvan Bacon 143082815dcSEvan Bacon for (const invalid of [[{}], null, 7]) { 144082815dcSEvan Bacon it(`throws on invalid mod results (${invalid})`, async () => { 145082815dcSEvan Bacon // A basic plugin exported from an app.json 146082815dcSEvan Bacon const exportedConfig: ExportedConfig = { 147082815dcSEvan Bacon name: 'app', 148082815dcSEvan Bacon slug: '', 149082815dcSEvan Bacon mods: { 150082815dcSEvan Bacon ios: { 151082815dcSEvan Bacon async infoPlist(config) { 152082815dcSEvan Bacon // Return an invalid config 153082815dcSEvan Bacon return invalid as any; 154082815dcSEvan Bacon }, 155082815dcSEvan Bacon }, 156082815dcSEvan Bacon }, 157082815dcSEvan Bacon }; 158082815dcSEvan Bacon 159082815dcSEvan Bacon // Apply mod plugin 160082815dcSEvan Bacon await expect(compileModsAsync(exportedConfig, { projectRoot })).rejects.toThrow( 161082815dcSEvan Bacon /Mod `mods.ios.infoPlist` evaluated to an object that is not a valid project config/ 162082815dcSEvan Bacon ); 163082815dcSEvan Bacon }); 164082815dcSEvan Bacon } 165082815dcSEvan Bacon}); 166