1/* eslint-env jest */
2import JsonFile from '@expo/json-file';
3import execa from 'execa';
4import fs from 'fs-extra';
5import klawSync from 'klaw-sync';
6import path from 'path';
7
8import { execute, projectRoot, getLoadedModulesAsync, setupTestProjectAsync, bin } from './utils';
9
10const originalForceColor = process.env.FORCE_COLOR;
11const originalCI = process.env.CI;
12
13beforeAll(async () => {
14  await fs.mkdir(projectRoot, { recursive: true });
15  process.env.FORCE_COLOR = '0';
16  process.env.CI = '1';
17});
18
19afterAll(() => {
20  process.env.FORCE_COLOR = originalForceColor;
21  process.env.CI = originalCI;
22});
23
24it('loads expected modules by default', async () => {
25  const modules = await getLoadedModulesAsync(
26    `require('../../build/src/export/web').expoExportWeb`
27  );
28  expect(modules).toStrictEqual([
29    '../node_modules/ansi-styles/index.js',
30    '../node_modules/arg/index.js',
31    '../node_modules/chalk/source/index.js',
32    '../node_modules/chalk/source/util.js',
33    '../node_modules/has-flag/index.js',
34    '../node_modules/supports-color/index.js',
35    '@expo/cli/build/src/export/web/index.js',
36    '@expo/cli/build/src/log.js',
37    '@expo/cli/build/src/utils/args.js',
38    '@expo/cli/build/src/utils/errors.js',
39  ]);
40});
41
42it('runs `npx expo export:web --help`', async () => {
43  const results = await execute('export:web', '--help');
44  expect(results.stdout).toMatchInlineSnapshot(`
45    "
46      Info
47        Export the static files of the web app for hosting on a web server
48
49      Usage
50        $ npx expo export:web <dir>
51
52      Options
53        <dir>                         Directory of the Expo project. Default: Current working directory
54        --dev                         Bundle in development mode
55        -c, --clear                   Clear the bundler cache
56        -h, --help                    Usage info
57    "
58  `);
59});
60
61it(
62  'runs `npx expo export:web`',
63  async () => {
64    const projectRoot = await setupTestProjectAsync('basic-export-web', 'with-web');
65    // `npx expo export:web`
66    await execa('node', [bin, 'export:web'], {
67      cwd: projectRoot,
68    });
69
70    const outputDir = path.join(projectRoot, 'web-build');
71    // List output files with sizes for snapshotting.
72    // This is to make sure that any changes to the output are intentional.
73    // Posix path formatting is used to make paths the same across OSes.
74    const files = klawSync(outputDir)
75      .map((entry) => {
76        if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
77          return null;
78        }
79        return path.posix.relative(outputDir, entry.path);
80      })
81      .filter(Boolean);
82
83    expect(await JsonFile.readAsync(path.resolve(outputDir, 'asset-manifest.json'))).toEqual({
84      entrypoints: [
85        expect.stringMatching(/static\/js\/runtime~app\.[a-z\d]+\.js/),
86        expect.stringMatching(/static\/js\/\d\.[a-z\d]+\.chunk\.js/),
87        expect.stringMatching(/static\/js\/app\.[a-z\d]+\.chunk\.js/),
88      ],
89      files: {
90        'app.js': expect.stringMatching(/static\/js\/app\.[a-z\d]+\.chunk\.js/),
91        'app.js.map': expect.stringMatching(/static\/js\/app\.[a-z\d]+\.chunk\.js\.map/),
92        'index.html': '/index.html',
93        'manifest.json': '/manifest.json',
94        'runtime~app.js': expect.stringMatching(/static\/js\/runtime~app\.[a-z\d]+\.js/),
95        'runtime~app.js.map': expect.stringMatching(/static\/js\/runtime~app\.[a-z\d]+\.js\.map/),
96        'serve.json': '/serve.json',
97        'static/js/2.6566e185.chunk.js': expect.stringMatching(
98          /static\/js\/\d\.[a-z\d]+\.chunk\.js/
99        ),
100        'static/js/2.6566e185.chunk.js.LICENSE.txt': expect.stringMatching(
101          /static\/js\/\d\.[a-z\d]+\.chunk\.js\.LICENSE\.txt/
102        ),
103        'static/js/2.6566e185.chunk.js.map': expect.stringMatching(
104          /static\/js\/\d\.[a-z\d]+\.chunk\.js\.map/
105        ),
106      },
107    });
108    expect(await JsonFile.readAsync(path.resolve(outputDir, 'manifest.json'))).toEqual({
109      display: 'standalone',
110      lang: 'en',
111      name: 'basic-export-web',
112      prefer_related_applications: true,
113      related_applications: [
114        {
115          id: 'com.example.minimal',
116          platform: 'itunes',
117        },
118        {
119          id: 'com.example.minimal',
120          platform: 'play',
121          url: 'http://play.google.com/store/apps/details?id=com.example.minimal',
122        },
123      ],
124      short_name: 'basic-export-web',
125      start_url: '/?utm_source=web_app_manifest',
126    });
127    expect(await JsonFile.readAsync(path.resolve(outputDir, 'serve.json'))).toEqual({
128      headers: [
129        {
130          headers: [
131            {
132              key: 'Cache-Control',
133              value: 'public, max-age=31536000, immutable',
134            },
135          ],
136          source: 'static/**/*.js',
137        },
138      ],
139    });
140
141    // If this changes then everything else probably changed as well.
142    expect(files).toEqual([
143      'asset-manifest.json',
144      'index.html',
145      'manifest.json',
146      'serve.json',
147      expect.stringMatching(/static\/js\/\d\.[a-z\d]+\.chunk\.js/),
148      expect.stringMatching(/static\/js\/\d\.[a-z\d]+\.chunk\.js\.LICENSE\.txt/),
149      expect.stringMatching(/static\/js\/\d\.[a-z\d]+\.chunk\.js\.map/),
150      expect.stringMatching(/static\/js\/app\.[a-z\d]+\.chunk\.js/),
151      expect.stringMatching(/static\/js\/app\.[a-z\d]+\.chunk\.js\.map/),
152      expect.stringMatching(/static\/js\/runtime~app\.[a-z\d]+\.js/),
153      expect.stringMatching(/static\/js\/runtime~app\.[a-z\d]+\.js\.map/),
154    ]);
155  },
156  // Could take 45s depending on how fast npm installs
157  120 * 1000
158);
159