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(`require('../../build/src/export').expoExport`);
26  expect(modules).toStrictEqual([
27    '../node_modules/ansi-styles/index.js',
28    '../node_modules/arg/index.js',
29    '../node_modules/chalk/source/index.js',
30    '../node_modules/chalk/source/util.js',
31    '../node_modules/has-flag/index.js',
32    '../node_modules/supports-color/index.js',
33    '@expo/cli/build/src/export/index.js',
34    '@expo/cli/build/src/log.js',
35    '@expo/cli/build/src/utils/args.js',
36    '@expo/cli/build/src/utils/errors.js',
37  ]);
38});
39
40it('runs `npx expo export --help`', async () => {
41  const results = await execute('export', '--help');
42  expect(results.stdout).toMatchInlineSnapshot(`
43    "
44      Info
45        Export the static files of the app for hosting it on a web server
46
47      Usage
48        $ npx expo export <dir>
49
50      Options
51        <dir>                      Directory of the Expo project. Default: Current working directory
52        --dev                      Configure static files for developing locally using a non-https server
53        --output-dir <dir>         The directory to export the static files to. Default: dist
54        --max-workers <number>     Maximum number of tasks to allow the bundler to spawn
55        --dump-assetmap            Dump the asset map for further processing
56        --dump-sourcemap           Dump the source map for debugging the JS bundle
57        -p, --platform <platform>  Options: android, ios, web, all. Default: all
58        -c, --clear                Clear the bundler cache
59        -h, --help                 Usage info
60    "
61  `);
62});
63
64it(
65  'runs `npx expo export`',
66  async () => {
67    const projectRoot = await setupTestProjectAsync('basic-export', 'with-assets');
68    // `npx expo export`
69    await execa('node', [bin, 'export', '--dump-sourcemap', '--dump-assetmap'], {
70      cwd: projectRoot,
71    });
72
73    const outputDir = path.join(projectRoot, 'dist');
74    // List output files with sizes for snapshotting.
75    // This is to make sure that any changes to the output are intentional.
76    // Posix path formatting is used to make paths the same across OSes.
77    const files = klawSync(outputDir)
78      .map((entry) => {
79        if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
80          return null;
81        }
82        return path.posix.relative(outputDir, entry.path);
83      })
84      .filter(Boolean);
85
86    const metadata = await JsonFile.readAsync(path.resolve(outputDir, 'metadata.json'));
87
88    expect(metadata).toEqual({
89      bundler: 'metro',
90      fileMetadata: {
91        android: {
92          assets: [
93            {
94              ext: 'png',
95              path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
96            },
97            {
98              ext: 'png',
99              path: 'assets/9ce7db807e4147e00df372d053c154c2',
100            },
101            {
102              ext: 'ttf',
103              path: 'assets/3858f62230ac3c915f300c664312c63f',
104            },
105          ],
106          bundle: expect.stringMatching(/bundles\/android-.*\.js/),
107        },
108        ios: {
109          assets: [
110            {
111              ext: 'png',
112              path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
113            },
114            {
115              ext: 'png',
116              path: 'assets/9ce7db807e4147e00df372d053c154c2',
117            },
118            {
119              ext: 'ttf',
120              path: 'assets/2f334f6c7ca5b2a504bdf8acdee104f3',
121            },
122          ],
123          bundle: expect.stringMatching(/bundles\/ios-.*\.js/),
124        },
125        web: {
126          assets: [
127            {
128              ext: 'png',
129              path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
130            },
131            {
132              ext: 'png',
133              path: 'assets/9ce7db807e4147e00df372d053c154c2',
134            },
135            {
136              ext: 'ttf',
137              path: 'assets/3858f62230ac3c915f300c664312c63f',
138            },
139          ],
140          bundle: expect.stringMatching(/bundles\/web-.*\.js/),
141        },
142      },
143      version: 0,
144    });
145
146    const assetmap = await JsonFile.readAsync(path.resolve(outputDir, 'assetmap.json'));
147    expect(assetmap).toEqual({
148      '2f334f6c7ca5b2a504bdf8acdee104f3': {
149        __packager_asset: true,
150        fileHashes: ['2f334f6c7ca5b2a504bdf8acdee104f3'],
151        fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
152        files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ios\.ttf/)],
153        hash: '2f334f6c7ca5b2a504bdf8acdee104f3',
154        httpServerLocation: '/assets/assets',
155        name: 'font',
156        scales: [1],
157        type: 'ttf',
158      },
159
160      '3858f62230ac3c915f300c664312c63f': {
161        __packager_asset: true,
162        fileHashes: ['3858f62230ac3c915f300c664312c63f'],
163        fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
164        files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ttf/)],
165        hash: '3858f62230ac3c915f300c664312c63f',
166        httpServerLocation: '/assets/assets',
167        name: 'font',
168        scales: [1],
169        type: 'ttf',
170      },
171      d48d481475a80809fcf9253a765193d1: {
172        __packager_asset: true,
173        fileHashes: ['fb960eb5e4eb49ec8786c7f6c4a57ce2', '9ce7db807e4147e00df372d053c154c2'],
174        fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/),
175        files: [
176          expect.stringMatching(/\/.*\/basic-export\/assets\/icon\.png/),
177          expect.stringMatching(/\/.*\/basic-export\/assets\/icon@2x\.png/),
178        ],
179        hash: 'd48d481475a80809fcf9253a765193d1',
180        height: 1,
181        httpServerLocation: '/assets/assets',
182        name: 'icon',
183        scales: [1, 2],
184        type: 'png',
185        width: 1,
186      },
187    });
188
189    // If this changes then everything else probably changed as well.
190    expect(files).toEqual([
191      'assetmap.json',
192      'assets/2f334f6c7ca5b2a504bdf8acdee104f3',
193      'assets/3858f62230ac3c915f300c664312c63f',
194      'assets/9ce7db807e4147e00df372d053c154c2',
195      'assets/assets/font.ttf',
196      'assets/assets/icon.png',
197      'assets/assets/icon@2x.png',
198
199      'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2',
200      expect.stringMatching(/bundles\/android-[\w\d]+\.js/),
201      expect.stringMatching(/bundles\/android-[\w\d]+\.map/),
202      expect.stringMatching(/bundles\/ios-[\w\d]+\.js/),
203      expect.stringMatching(/bundles\/ios-[\w\d]+\.map/),
204      expect.stringMatching(/bundles\/web-[\w\d]+\.js/),
205      expect.stringMatching(/bundles\/web-[\w\d]+\.map/),
206      'debug.html',
207      'drawable-mdpi/assets_icon.png',
208      'drawable-xhdpi/assets_icon.png',
209      'favicon.ico',
210      'index.html',
211      'metadata.json',
212      'raw/assets_font.ttf',
213    ]);
214  },
215  // Could take 45s depending on how fast npm installs
216  120 * 1000
217);
218