1import * as fs from 'fs';
2import { vol } from 'memfs';
3import * as path from 'path';
4
5import { getConfig } from '../Config';
6
7const fsReal = jest.requireActual('fs') as typeof fs;
8
9jest.mock('fs');
10
11describe(getConfig, () => {
12  afterAll(() => {
13    vol.reset();
14  });
15
16  // Tests the following:
17  // - All supported languages are working
18  // - ensure `app.config` has higher priority to `app`
19  // - generated `.expo` object is created and the language hint is added
20  describe('language support', () => {
21    beforeEach(() => {
22      delete process.env.EXPO_DEBUG;
23      vol.reset();
24    });
25    it('parses a ts config', () => {
26      vol.fromJSON(
27        {
28          // Read this TS file so we can validate the types
29          'app.config.ts': fsReal.readFileSync(
30            path.join(__dirname, './fixtures/ts/app.config.ts'),
31            'utf8'
32          ),
33          'package.json': JSON.stringify({
34            name: 'ts-config-test',
35            version: '1.0.0',
36          }),
37        },
38        '/'
39      );
40
41      const { exp } = getConfig('/', {
42        skipSDKVersionRequirement: true,
43      });
44      // @ts-ignore: foo property is not defined
45      expect(exp.foo).toBe('bar+value');
46      expect(exp.name).toBe('rewrote+ts-config-test');
47      expect(exp._internal).toStrictEqual({
48        dynamicConfigPath: '/app.config.ts',
49        isDebug: false,
50        packageJsonPath: '/package.json',
51        projectRoot: '/',
52        staticConfigPath: null,
53      });
54    });
55    it('parses a js config', () => {
56      vol.fromJSON(
57        {
58          'app.json': JSON.stringify({
59            expo: {
60              foo: 'invalid',
61              slug: 'someslug',
62            },
63          }),
64          'package.json': JSON.stringify({
65            name: 'js-config-test',
66            version: '1.0.0',
67          }),
68          // Config exporting a function on the module.exports object
69          'app.config.js': `module.exports = function ({ config }) {
70            config.foo = 'bar';
71            if (config.name) config.name += '+config';
72            if (config.slug) config.slug += '+config';
73            return config;
74          };`,
75        },
76        '/'
77      );
78
79      // ensure config is composed (package.json values still exist)
80      const { exp, dynamicConfigPath, staticConfigPath } = getConfig('/', {
81        skipSDKVersionRequirement: true,
82      });
83      expect(dynamicConfigPath).toBe('/app.config.js');
84      expect(staticConfigPath).toBe('/app.json');
85
86      // @ts-ignore: foo property is not defined
87      expect(exp.foo).toBe('bar');
88      // Ensure the config is passed the package.json values
89      expect(exp.name).toBe('js-config-test+config');
90      // Ensures that the app.json is read and passed to the method
91      expect(exp.slug).toBe('someslug+config');
92      expect(exp._internal).toStrictEqual({
93        dynamicConfigPath: '/app.config.js',
94        isDebug: false,
95        packageJsonPath: '/package.json',
96        projectRoot: '/',
97        staticConfigPath: '/app.json',
98      });
99    });
100    it('parses a js config with export default', () => {
101      vol.fromJSON(
102        {
103          'package.json': JSON.stringify({
104            name: 'js-config-test',
105            version: '1.0.0',
106          }),
107          // Config exporting a function as default
108          'app.config.js': `export default function ({ config }) {
109            config.foo = 'bar';
110            if (config.name) config.name += '+config-default';
111            return config;
112          }`,
113        },
114        '/'
115      );
116      const { exp, staticConfigPath } = getConfig('/', {
117        skipSDKVersionRequirement: true,
118      });
119      // @ts-ignore: foo property is not defined
120      expect(exp.foo).toBe('bar');
121      expect(exp.name).toBe('js-config-test+config-default');
122      // Static is undefined when a custom path is a dynamic config.
123      expect(staticConfigPath).toBe(null);
124    });
125    it('parses a js config that exports json', () => {
126      vol.fromJSON(
127        {
128          'package.json': JSON.stringify({
129            name: 'js-config-test',
130            version: '1.0.0',
131          }),
132          // Config exporting an object (JSON)
133          'app.config.js': `module.exports = {
134            foo: 'bar',
135            name: 'cool+export-json_app.config',
136          };`,
137        },
138        '/'
139      );
140
141      const { exp } = getConfig('/', {
142        skipSDKVersionRequirement: true,
143      });
144      // @ts-ignore: foo property is not defined
145      expect(exp.foo).toBe('bar');
146      expect(exp.name).toBe('cool+export-json_app.config');
147    });
148  });
149
150  describe('behavior', () => {
151    beforeEach(() => {
152      delete process.env.EXPO_DEBUG;
153      vol.reset();
154    });
155
156    it(`skips plugin parsing`, () => {
157      vol.fromJSON(
158        {
159          'app.json': JSON.stringify({
160            expo: {
161              name: 'app-expo-name',
162              plugins: ['__missing-plugin'],
163            },
164          }),
165          'package.json': JSON.stringify({
166            version: '1.0.0',
167          }),
168        },
169        '/'
170      );
171      const { exp } = getConfig('/', {
172        skipSDKVersionRequirement: true,
173        skipPlugins: true,
174      });
175      expect(exp.plugins).toBeUndefined();
176    });
177    it(`skips JS plugin parsing`, () => {
178      vol.fromJSON(
179        {
180          'app.config.js': `module.exports = {
181            foo: 'bar',
182            name: 'cool+export-json_app.config',
183            plugins: [(config)=> { config.name ='custom'; return config; }]
184          };`,
185          'package.json': JSON.stringify({
186            version: '1.0.0',
187          }),
188        },
189        '/'
190      );
191      const { exp } = getConfig('/', {
192        skipSDKVersionRequirement: true,
193        skipPlugins: true,
194      });
195      expect(exp.name).toBe('cool+export-json_app.config');
196      expect(exp.plugins).toBeUndefined();
197    });
198    it(`applies JS plugins`, () => {
199      vol.fromJSON(
200        {
201          'app.config.js': `module.exports = {
202            foo: 'bar',
203            name: 'cool+export-json_app.config',
204            plugins: [(config)=> { config.name ='custom'; return config; }]
205          };`,
206          'package.json': JSON.stringify({
207            version: '1.0.0',
208          }),
209        },
210        '/'
211      );
212      const { exp } = getConfig('/', {
213        skipSDKVersionRequirement: true,
214        skipPlugins: false,
215      });
216      expect(exp.name).toBe('custom');
217      expect(exp.plugins).toBeDefined();
218    });
219    it(`throws when plugins are missing`, () => {
220      vol.fromJSON(
221        {
222          'app.json': JSON.stringify({
223            expo: {
224              name: 'app-expo-name',
225              plugins: ['__missing-plugin'],
226            },
227          }),
228          'package.json': JSON.stringify({
229            version: '1.0.0',
230          }),
231        },
232        '/'
233      );
234      expect(() =>
235        getConfig('/', {
236          skipSDKVersionRequirement: true,
237          skipPlugins: false,
238        })
239      ).toThrow(/Failed to resolve plugin for module "__missing-plugin" relative to "\/"/);
240    });
241  });
242});
243