1import { vol } from 'memfs';
2
3import { getConfigFilePaths, modifyConfigAsync } from '../Config';
4import { getDynamicConfig, getStaticConfig } from '../getConfig';
5
6const mockConfigContext = {} as any;
7
8jest.mock('fs');
9
10describe(modifyConfigAsync, () => {
11  beforeAll(async () => {
12    vol.fromJSON(
13      {
14        'package.json': JSON.stringify({}),
15      },
16      'no-config'
17    );
18    vol.fromJSON(
19      {
20        'package.json': JSON.stringify({}),
21        'app.config.js': 'export default {};',
22        'app.config.json': JSON.stringify({}),
23      },
24      'dynamic-and-static'
25    );
26    vol.fromJSON(
27      {
28        'app.config.json': JSON.stringify(
29          {
30            name: 'app-config-json',
31            slug: 'app-config-json',
32            version: '1.0.0',
33            platforms: [],
34          },
35          null,
36          2
37        ),
38        'app.json': JSON.stringify(
39          {
40            name: 'app-json',
41          },
42          null,
43          2
44        ),
45        'package.json': JSON.stringify({}),
46      },
47      'static-override'
48    );
49  });
50
51  afterAll(() => {
52    vol.reset();
53  });
54
55  it(`can write to a static only config`, async () => {
56    const { type, config } = await modifyConfigAsync(
57      'static-override',
58      { foo: 'bar' } as any,
59      { skipSDKVersionRequirement: true },
60      { dryRun: true }
61    );
62    expect(type).toBe('success');
63    // @ts-ignore: foo property is not defined
64    expect(config.foo).toBe('bar');
65  });
66  it(`cannot write to a dynamic config`, async () => {
67    const { type, config } = await modifyConfigAsync(
68      'dynamic-and-static',
69      {},
70      { skipSDKVersionRequirement: true },
71      { dryRun: true }
72    );
73    expect(type).toBe('warn');
74    expect(config).toBe(null);
75  });
76  it(`cannot write to a project without a config`, async () => {
77    const { type, config } = await modifyConfigAsync(
78      'no-config',
79      {},
80      { skipSDKVersionRequirement: true },
81      { dryRun: true }
82    );
83    expect(type).toBe('fail');
84    expect(config).toBe(null);
85  });
86});
87
88const invalidConfig = `/* eslint-disable */
89module.exports = {
90  name: 'app',
91  >
92  slug: 'app',
93}`;
94
95describe(getDynamicConfig, () => {
96  beforeAll(async () => {
97    vol.fromJSON(
98      {
99        'exports-function.app.config.js': `export default function (config) {
100          return { ...config };
101        }`,
102        'exports-object.app.config.js': `export default {};`,
103      },
104      'dynamic-export-types'
105    );
106    vol.fromJSON(
107      {
108        // This file exists to test that an invalid app.config.ts
109        // gets used instead of defaulting to a valid app.config.js
110        'app.config.js': `export default {};`,
111        // This is a syntax error:
112        'app.config.ts': invalidConfig,
113      },
114      'syntax-error'
115    );
116    vol.fromJSON(
117      {
118        // This is a missing import
119        'app.config.ts': [`import 'foobar'`, 'module.exports = {}'].join('\n'),
120      },
121      'missing-import-error'
122    );
123  });
124
125  afterAll(() => {
126    vol.reset();
127  });
128
129  it(`exports a function`, () => {
130    expect(
131      getDynamicConfig('dynamic-export-types/exports-function.app.config.js', mockConfigContext)
132        .exportedObjectType
133    ).toBe('function');
134  });
135
136  it(`exports an object`, () => {
137    expect(
138      getDynamicConfig('dynamic-export-types/exports-object.app.config.js', mockConfigContext)
139        .exportedObjectType
140    ).toBe('object');
141  });
142
143  // This tests error are thrown properly and ensures that a more specific
144  // config is used instead of defaulting to a valid substitution.
145  it(`throws a useful error for dynamic configs with a syntax error`, () => {
146    const paths = getConfigFilePaths('syntax-error');
147    expect(() => getDynamicConfig(paths.dynamicConfigPath!, mockConfigContext)).toThrowError(
148      /Error .* \(5:7\)/
149    );
150  });
151  // This tests error are thrown properly and ensures that a more specific
152  // config is used instead of defaulting to a valid substitution.
153  it(`throws a useful error for dynamic configs with a missing import`, () => {
154    const paths = getConfigFilePaths('missing-import-error');
155    expect(() => getDynamicConfig(paths.dynamicConfigPath!, mockConfigContext)).toThrowError(
156      /Require stack/
157    );
158  });
159});
160
161describe(getStaticConfig, () => {
162  beforeAll(async () => {
163    vol.fromJSON(
164      {
165        'package.json': JSON.stringify({}),
166        'app.json': JSON.stringify(
167          {
168            name: 'app-json',
169          },
170          null,
171          2
172        ),
173        'app.config.json': JSON.stringify(
174          {
175            name: 'app-config-json',
176            slug: 'app-config-json',
177            version: '1.0.0',
178            platforms: [],
179          },
180          null,
181          2
182        ),
183      },
184      'static-override'
185    );
186  });
187
188  afterAll(() => {
189    vol.reset();
190  });
191
192  // This tests error are thrown properly and ensures that a more specific
193  // config is used instead of defaulting to a valid substitution.
194  it(`uses app.config.json instead of app.json if both exist`, () => {
195    const paths = getConfigFilePaths('static-override');
196    expect(paths.staticConfigPath).toMatch(/app\.config\.json/);
197
198    expect(getStaticConfig(paths.staticConfigPath!).name).toBe('app-config-json');
199  });
200});
201