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