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