1dc51e206SEvan Bacon/* eslint-env jest */
2dc51e206SEvan Baconimport JsonFile from '@expo/json-file';
3dc51e206SEvan Baconimport execa from 'execa';
4dc51e206SEvan Baconimport fs from 'fs-extra';
5dc51e206SEvan Baconimport klawSync from 'klaw-sync';
6dc51e206SEvan Baconimport path from 'path';
7dc51e206SEvan Bacon
80a6ddb20SEvan Baconimport {
90a6ddb20SEvan Bacon  execute,
100a6ddb20SEvan Bacon  projectRoot,
110a6ddb20SEvan Bacon  getLoadedModulesAsync,
120a6ddb20SEvan Bacon  setupTestProjectAsync,
130a6ddb20SEvan Bacon  bin,
140a6ddb20SEvan Bacon  ensurePortFreeAsync,
150a6ddb20SEvan Bacon} from './utils';
16dc51e206SEvan Bacon
17dc51e206SEvan Baconconst originalForceColor = process.env.FORCE_COLOR;
18dc51e206SEvan Baconconst originalCI = process.env.CI;
19dc51e206SEvan Bacon
20dc51e206SEvan BaconbeforeAll(async () => {
21dc51e206SEvan Bacon  await fs.mkdir(projectRoot, { recursive: true });
22dc51e206SEvan Bacon  process.env.FORCE_COLOR = '0';
23dc51e206SEvan Bacon  process.env.CI = '1';
24f479be69SEvan Bacon  process.env._EXPO_E2E_USE_PATH_ALIASES = '1';
259580591fSEvan Bacon  delete process.env.EXPO_WEB_OUTPUT_MODE;
26dc51e206SEvan Bacon});
27dc51e206SEvan Bacon
28dc51e206SEvan BaconafterAll(() => {
29dc51e206SEvan Bacon  process.env.FORCE_COLOR = originalForceColor;
30dc51e206SEvan Bacon  process.env.CI = originalCI;
31f479be69SEvan Bacon  delete process.env._EXPO_E2E_USE_PATH_ALIASES;
32dc51e206SEvan Bacon});
33dc51e206SEvan Bacon
34dc51e206SEvan Baconit('loads expected modules by default', async () => {
35dc51e206SEvan Bacon  const modules = await getLoadedModulesAsync(`require('../../build/src/export').expoExport`);
36dc51e206SEvan Bacon  expect(modules).toStrictEqual([
374067174dSWill Schurman    '../node_modules/ansi-styles/index.js',
38dc51e206SEvan Bacon    '../node_modules/arg/index.js',
39dc51e206SEvan Bacon    '../node_modules/chalk/source/index.js',
40dc51e206SEvan Bacon    '../node_modules/chalk/source/util.js',
41dc51e206SEvan Bacon    '../node_modules/has-flag/index.js',
42dc51e206SEvan Bacon    '../node_modules/supports-color/index.js',
43dc51e206SEvan Bacon    '@expo/cli/build/src/export/index.js',
44dc51e206SEvan Bacon    '@expo/cli/build/src/log.js',
45dc51e206SEvan Bacon    '@expo/cli/build/src/utils/args.js',
46dc51e206SEvan Bacon    '@expo/cli/build/src/utils/errors.js',
47dc51e206SEvan Bacon  ]);
48dc51e206SEvan Bacon});
49dc51e206SEvan Bacon
50dc51e206SEvan Baconit('runs `npx expo export --help`', async () => {
51dc51e206SEvan Bacon  const results = await execute('export', '--help');
52dc51e206SEvan Bacon  expect(results.stdout).toMatchInlineSnapshot(`
53dc51e206SEvan Bacon    "
5483d464dcSEvan Bacon      Info
55dc51e206SEvan Bacon        Export the static files of the app for hosting it on a web server
56dc51e206SEvan Bacon
57dc51e206SEvan Bacon      Usage
5883d464dcSEvan Bacon        $ npx expo export <dir>
59dc51e206SEvan Bacon
60dc51e206SEvan Bacon      Options
6183d464dcSEvan Bacon        <dir>                      Directory of the Expo project. Default: Current working directory
62dc51e206SEvan Bacon        --dev                      Configure static files for developing locally using a non-https server
6383d464dcSEvan Bacon        --output-dir <dir>         The directory to export the static files to. Default: dist
64dc51e206SEvan Bacon        --max-workers <number>     Maximum number of tasks to allow the bundler to spawn
65dc51e206SEvan Bacon        --dump-assetmap            Dump the asset map for further processing
66dc51e206SEvan Bacon        --dump-sourcemap           Dump the source map for debugging the JS bundle
67185ef548SEvan Bacon        -p, --platform <platform>  Options: android, ios, web, all. Default: all
681a3d836eSEvan Bacon        --no-minify                Prevent minifying source
69dc51e206SEvan Bacon        -c, --clear                Clear the bundler cache
7083d464dcSEvan Bacon        -h, --help                 Usage info
71dc51e206SEvan Bacon    "
72dc51e206SEvan Bacon  `);
73dc51e206SEvan Bacon});
74dc51e206SEvan Bacon
750a6ddb20SEvan Bacondescribe('server', () => {
7647d62600SKudo Chien  beforeEach(() => ensurePortFreeAsync(8081));
77dc51e206SEvan Bacon  it(
78dc51e206SEvan Bacon    'runs `npx expo export`',
79dc51e206SEvan Bacon    async () => {
80dc51e206SEvan Bacon      const projectRoot = await setupTestProjectAsync('basic-export', 'with-assets');
81dc51e206SEvan Bacon      // `npx expo export`
82dc51e206SEvan Bacon      await execa('node', [bin, 'export', '--dump-sourcemap', '--dump-assetmap'], {
83dc51e206SEvan Bacon        cwd: projectRoot,
84dc51e206SEvan Bacon      });
85dc51e206SEvan Bacon
86dc51e206SEvan Bacon      const outputDir = path.join(projectRoot, 'dist');
87dc51e206SEvan Bacon      // List output files with sizes for snapshotting.
88dc51e206SEvan Bacon      // This is to make sure that any changes to the output are intentional.
89dc51e206SEvan Bacon      // Posix path formatting is used to make paths the same across OSes.
90dc51e206SEvan Bacon      const files = klawSync(outputDir)
91dc51e206SEvan Bacon        .map((entry) => {
92dc51e206SEvan Bacon          if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
93dc51e206SEvan Bacon            return null;
94dc51e206SEvan Bacon          }
95dc51e206SEvan Bacon          return path.posix.relative(outputDir, entry.path);
96dc51e206SEvan Bacon        })
97dc51e206SEvan Bacon        .filter(Boolean);
98dc51e206SEvan Bacon
99dc51e206SEvan Bacon      const metadata = await JsonFile.readAsync(path.resolve(outputDir, 'metadata.json'));
100dc51e206SEvan Bacon
101dc51e206SEvan Bacon      expect(metadata).toEqual({
102dc51e206SEvan Bacon        bundler: 'metro',
103dc51e206SEvan Bacon        fileMetadata: {
104dc51e206SEvan Bacon          android: {
105dc51e206SEvan Bacon            assets: [
106dc51e206SEvan Bacon              {
107dc51e206SEvan Bacon                ext: 'png',
108dc51e206SEvan Bacon                path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
109dc51e206SEvan Bacon              },
110dc51e206SEvan Bacon              {
111dc51e206SEvan Bacon                ext: 'png',
112dc51e206SEvan Bacon                path: 'assets/9ce7db807e4147e00df372d053c154c2',
113dc51e206SEvan Bacon              },
114dc51e206SEvan Bacon              {
115dc51e206SEvan Bacon                ext: 'ttf',
116dc51e206SEvan Bacon                path: 'assets/3858f62230ac3c915f300c664312c63f',
117dc51e206SEvan Bacon              },
118dc51e206SEvan Bacon            ],
119*af7ecef3SGabriel Donadel Dall'Agnol            bundle: expect.stringMatching(/bundles\/android-.*\.hbc/),
120dc51e206SEvan Bacon          },
121dc51e206SEvan Bacon          ios: {
122dc51e206SEvan Bacon            assets: [
123dc51e206SEvan Bacon              {
124dc51e206SEvan Bacon                ext: 'png',
125dc51e206SEvan Bacon                path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
126dc51e206SEvan Bacon              },
127dc51e206SEvan Bacon              {
128dc51e206SEvan Bacon                ext: 'png',
129dc51e206SEvan Bacon                path: 'assets/9ce7db807e4147e00df372d053c154c2',
130dc51e206SEvan Bacon              },
131dc51e206SEvan Bacon              {
132dc51e206SEvan Bacon                ext: 'ttf',
133dc51e206SEvan Bacon                path: 'assets/2f334f6c7ca5b2a504bdf8acdee104f3',
134dc51e206SEvan Bacon              },
135dc51e206SEvan Bacon            ],
136*af7ecef3SGabriel Donadel Dall'Agnol            bundle: expect.stringMatching(/bundles\/ios-.*\.hbc/),
137dc51e206SEvan Bacon          },
138dc51e206SEvan Bacon        },
139dc51e206SEvan Bacon        version: 0,
140dc51e206SEvan Bacon      });
141dc51e206SEvan Bacon
142dc51e206SEvan Bacon      const assetmap = await JsonFile.readAsync(path.resolve(outputDir, 'assetmap.json'));
143dc51e206SEvan Bacon      expect(assetmap).toEqual({
144dc51e206SEvan Bacon        '2f334f6c7ca5b2a504bdf8acdee104f3': {
145dc51e206SEvan Bacon          __packager_asset: true,
146dc51e206SEvan Bacon          fileHashes: ['2f334f6c7ca5b2a504bdf8acdee104f3'],
147dc51e206SEvan Bacon          fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
148dc51e206SEvan Bacon          files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ios\.ttf/)],
149dc51e206SEvan Bacon          hash: '2f334f6c7ca5b2a504bdf8acdee104f3',
150dc51e206SEvan Bacon          httpServerLocation: '/assets/assets',
151dc51e206SEvan Bacon          name: 'font',
152dc51e206SEvan Bacon          scales: [1],
153dc51e206SEvan Bacon          type: 'ttf',
154dc51e206SEvan Bacon        },
155dc51e206SEvan Bacon
156dc51e206SEvan Bacon        '3858f62230ac3c915f300c664312c63f': {
157dc51e206SEvan Bacon          __packager_asset: true,
158dc51e206SEvan Bacon          fileHashes: ['3858f62230ac3c915f300c664312c63f'],
159dc51e206SEvan Bacon          fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
160dc51e206SEvan Bacon          files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ttf/)],
161dc51e206SEvan Bacon          hash: '3858f62230ac3c915f300c664312c63f',
162dc51e206SEvan Bacon          httpServerLocation: '/assets/assets',
163dc51e206SEvan Bacon          name: 'font',
164dc51e206SEvan Bacon          scales: [1],
165dc51e206SEvan Bacon          type: 'ttf',
166dc51e206SEvan Bacon        },
167dc51e206SEvan Bacon        d48d481475a80809fcf9253a765193d1: {
168dc51e206SEvan Bacon          __packager_asset: true,
169dc51e206SEvan Bacon          fileHashes: ['fb960eb5e4eb49ec8786c7f6c4a57ce2', '9ce7db807e4147e00df372d053c154c2'],
170dc51e206SEvan Bacon          fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
171dc51e206SEvan Bacon          files: [
172dc51e206SEvan Bacon            expect.stringMatching(/\/.*\/basic-export\/assets\/icon\.png/),
173dc51e206SEvan Bacon            expect.stringMatching(/\/.*\/basic-export\/assets\/icon@2x\.png/),
174dc51e206SEvan Bacon          ],
175dc51e206SEvan Bacon          hash: 'd48d481475a80809fcf9253a765193d1',
176dc51e206SEvan Bacon          height: 1,
177dc51e206SEvan Bacon          httpServerLocation: '/assets/assets',
178dc51e206SEvan Bacon          name: 'icon',
179dc51e206SEvan Bacon          scales: [1, 2],
180dc51e206SEvan Bacon          type: 'png',
181dc51e206SEvan Bacon          width: 1,
182dc51e206SEvan Bacon        },
183dc51e206SEvan Bacon      });
184dc51e206SEvan Bacon
185dc51e206SEvan Bacon      // If this changes then everything else probably changed as well.
186dc51e206SEvan Bacon      expect(files).toEqual([
187dc51e206SEvan Bacon        'assetmap.json',
188dc51e206SEvan Bacon        'assets/2f334f6c7ca5b2a504bdf8acdee104f3',
189dc51e206SEvan Bacon        'assets/3858f62230ac3c915f300c664312c63f',
190dc51e206SEvan Bacon        'assets/9ce7db807e4147e00df372d053c154c2',
1916d6b81f9SEvan Bacon        'assets/assets/font.ttf',
1926d6b81f9SEvan Bacon        'assets/assets/icon.png',
1936d6b81f9SEvan Bacon        'assets/assets/icon@2x.png',
1946d6b81f9SEvan Bacon
195dc51e206SEvan Bacon        'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
196*af7ecef3SGabriel Donadel Dall'Agnol        expect.stringMatching(/bundles\/android-[\w\d]+\.hbc/),
197dc51e206SEvan Bacon        expect.stringMatching(/bundles\/android-[\w\d]+\.map/),
198*af7ecef3SGabriel Donadel Dall'Agnol        expect.stringMatching(/bundles\/ios-[\w\d]+\.hbc/),
199dc51e206SEvan Bacon        expect.stringMatching(/bundles\/ios-[\w\d]+\.map/),
2006d6b81f9SEvan Bacon        expect.stringMatching(/bundles\/web-[\w\d]+\.js/),
2016d6b81f9SEvan Bacon        expect.stringMatching(/bundles\/web-[\w\d]+\.map/),
202dc51e206SEvan Bacon        'debug.html',
2036d6b81f9SEvan Bacon        'favicon.ico',
2046d6b81f9SEvan Bacon        'index.html',
205dc51e206SEvan Bacon        'metadata.json',
206dc51e206SEvan Bacon      ]);
207dc51e206SEvan Bacon    },
208dc51e206SEvan Bacon    // Could take 45s depending on how fast npm installs
209dc51e206SEvan Bacon    120 * 1000
210dc51e206SEvan Bacon  );
2110a6ddb20SEvan Bacon});
212