153b4c0b0SEvan Bacon/* eslint-env jest */ 253b4c0b0SEvan Baconimport JsonFile from '@expo/json-file'; 3e1bb5bdfSKudo Chienimport assert from 'assert'; 453b4c0b0SEvan Baconimport execa from 'execa'; 553b4c0b0SEvan Baconimport fs from 'fs-extra'; 653b4c0b0SEvan Baconimport klawSync from 'klaw-sync'; 753b4c0b0SEvan Baconimport path from 'path'; 853b4c0b0SEvan Bacon 953b4c0b0SEvan Baconimport { execute, projectRoot, getLoadedModulesAsync, setupTestProjectAsync, bin } from './utils'; 1053b4c0b0SEvan Bacon 1153b4c0b0SEvan Baconconst originalForceColor = process.env.FORCE_COLOR; 1253b4c0b0SEvan Baconconst originalCI = process.env.CI; 1353b4c0b0SEvan Bacon 1453b4c0b0SEvan BaconbeforeAll(async () => { 1553b4c0b0SEvan Bacon await fs.mkdir(projectRoot, { recursive: true }); 1653b4c0b0SEvan Bacon process.env.FORCE_COLOR = '0'; 1753b4c0b0SEvan Bacon process.env.CI = '1'; 1853b4c0b0SEvan Bacon}); 1953b4c0b0SEvan Bacon 2053b4c0b0SEvan BaconafterAll(() => { 2153b4c0b0SEvan Bacon process.env.FORCE_COLOR = originalForceColor; 2253b4c0b0SEvan Bacon process.env.CI = originalCI; 2353b4c0b0SEvan Bacon}); 2453b4c0b0SEvan Bacon 2553b4c0b0SEvan Baconit('loads expected modules by default', async () => { 2653b4c0b0SEvan Bacon const modules = await getLoadedModulesAsync( 2753b4c0b0SEvan Bacon `require('../../build/src/export/web').expoExportWeb` 2853b4c0b0SEvan Bacon ); 2953b4c0b0SEvan Bacon expect(modules).toStrictEqual([ 30*4067174dSWill Schurman '../node_modules/ansi-styles/index.js', 3153b4c0b0SEvan Bacon '../node_modules/arg/index.js', 3253b4c0b0SEvan Bacon '../node_modules/chalk/source/index.js', 3353b4c0b0SEvan Bacon '../node_modules/chalk/source/util.js', 3453b4c0b0SEvan Bacon '../node_modules/has-flag/index.js', 3553b4c0b0SEvan Bacon '../node_modules/supports-color/index.js', 3653b4c0b0SEvan Bacon '@expo/cli/build/src/export/web/index.js', 3753b4c0b0SEvan Bacon '@expo/cli/build/src/log.js', 3853b4c0b0SEvan Bacon '@expo/cli/build/src/utils/args.js', 3953b4c0b0SEvan Bacon '@expo/cli/build/src/utils/errors.js', 4053b4c0b0SEvan Bacon ]); 4153b4c0b0SEvan Bacon}); 4253b4c0b0SEvan Bacon 4353b4c0b0SEvan Baconit('runs `npx expo export:web --help`', async () => { 4453b4c0b0SEvan Bacon const results = await execute('export:web', '--help'); 4553b4c0b0SEvan Bacon expect(results.stdout).toMatchInlineSnapshot(` 4653b4c0b0SEvan Bacon " 4753b4c0b0SEvan Bacon Info 4853b4c0b0SEvan Bacon Export the static files of the web app for hosting on a web server 4953b4c0b0SEvan Bacon 5053b4c0b0SEvan Bacon Usage 5153b4c0b0SEvan Bacon $ npx expo export:web <dir> 5253b4c0b0SEvan Bacon 5353b4c0b0SEvan Bacon Options 5453b4c0b0SEvan Bacon <dir> Directory of the Expo project. Default: Current working directory 5553b4c0b0SEvan Bacon --dev Bundle in development mode 5653b4c0b0SEvan Bacon -c, --clear Clear the bundler cache 5753b4c0b0SEvan Bacon -h, --help Usage info 5853b4c0b0SEvan Bacon " 5953b4c0b0SEvan Bacon `); 6053b4c0b0SEvan Bacon}); 6153b4c0b0SEvan Bacon 6253b4c0b0SEvan Baconit( 6353b4c0b0SEvan Bacon 'runs `npx expo export:web`', 6453b4c0b0SEvan Bacon async () => { 6553b4c0b0SEvan Bacon const projectRoot = await setupTestProjectAsync('basic-export-web', 'with-web'); 6653b4c0b0SEvan Bacon // `npx expo export:web` 6753b4c0b0SEvan Bacon await execa('node', [bin, 'export:web'], { 6853b4c0b0SEvan Bacon cwd: projectRoot, 6953b4c0b0SEvan Bacon }); 7053b4c0b0SEvan Bacon 7153b4c0b0SEvan Bacon const outputDir = path.join(projectRoot, 'web-build'); 7253b4c0b0SEvan Bacon // List output files with sizes for snapshotting. 7353b4c0b0SEvan Bacon // This is to make sure that any changes to the output are intentional. 7453b4c0b0SEvan Bacon // Posix path formatting is used to make paths the same across OSes. 7553b4c0b0SEvan Bacon const files = klawSync(outputDir) 7653b4c0b0SEvan Bacon .map((entry) => { 7753b4c0b0SEvan Bacon if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 7853b4c0b0SEvan Bacon return null; 7953b4c0b0SEvan Bacon } 8053b4c0b0SEvan Bacon return path.posix.relative(outputDir, entry.path); 8153b4c0b0SEvan Bacon }) 8253b4c0b0SEvan Bacon .filter(Boolean); 8353b4c0b0SEvan Bacon 84dfe12d45SEvan Bacon const assetsManifest = await JsonFile.readAsync(path.resolve(outputDir, 'asset-manifest.json')); 85dfe12d45SEvan Bacon expect(assetsManifest.entrypoints).toEqual([ 86425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/\d+\.[a-z\d]+\.js/), 87425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/main\.[a-z\d]+\.js/), 88dfe12d45SEvan Bacon ]); 89dfe12d45SEvan Bacon 90dfe12d45SEvan Bacon const knownFiles = [ 91425e7f7fSEvan Bacon ['main.js', expect.stringMatching(/static\/js\/main\.[a-z\d]+\.js/)], 92dfe12d45SEvan Bacon ['index.html', '/index.html'], 93dfe12d45SEvan Bacon ['manifest.json', '/manifest.json'], 94dfe12d45SEvan Bacon ['serve.json', '/serve.json'], 95dfe12d45SEvan Bacon ]; 96dfe12d45SEvan Bacon 97e1bb5bdfSKudo Chien assert(assetsManifest.files); 98425e7f7fSEvan Bacon console.log(assetsManifest.files); 99dfe12d45SEvan Bacon for (const [key, value] of knownFiles) { 100e1bb5bdfSKudo Chien const files = assetsManifest.files as Record<string, string>; 101e1bb5bdfSKudo Chien expect(files[key]).toEqual(value); 102e1bb5bdfSKudo Chien delete files[key]; 103dfe12d45SEvan Bacon } 104dfe12d45SEvan Bacon 105e1bb5bdfSKudo Chien for (const [key, value] of Object.entries(assetsManifest?.files ?? {})) { 106425e7f7fSEvan Bacon expect(key).toMatch(/(static\/js\/)?(\d+|main)\.[a-z\d]+\.js(\.LICENSE\.txt|\.map)?/); 107425e7f7fSEvan Bacon expect(value).toMatch(/(static\/js\/)?(\d+|main)\.[a-z\d]+\.js(\.LICENSE\.txt|\.map)?/); 108dfe12d45SEvan Bacon } 109dfe12d45SEvan Bacon 11053b4c0b0SEvan Bacon expect(await JsonFile.readAsync(path.resolve(outputDir, 'manifest.json'))).toEqual({ 11153b4c0b0SEvan Bacon display: 'standalone', 11253b4c0b0SEvan Bacon lang: 'en', 11353b4c0b0SEvan Bacon name: 'basic-export-web', 11453b4c0b0SEvan Bacon prefer_related_applications: true, 11553b4c0b0SEvan Bacon related_applications: [ 11653b4c0b0SEvan Bacon { 11753b4c0b0SEvan Bacon id: 'com.example.minimal', 11853b4c0b0SEvan Bacon platform: 'itunes', 11953b4c0b0SEvan Bacon }, 12053b4c0b0SEvan Bacon { 12153b4c0b0SEvan Bacon id: 'com.example.minimal', 12253b4c0b0SEvan Bacon platform: 'play', 12353b4c0b0SEvan Bacon url: 'http://play.google.com/store/apps/details?id=com.example.minimal', 12453b4c0b0SEvan Bacon }, 12553b4c0b0SEvan Bacon ], 12653b4c0b0SEvan Bacon short_name: 'basic-export-web', 12753b4c0b0SEvan Bacon start_url: '/?utm_source=web_app_manifest', 12853b4c0b0SEvan Bacon }); 12953b4c0b0SEvan Bacon expect(await JsonFile.readAsync(path.resolve(outputDir, 'serve.json'))).toEqual({ 13053b4c0b0SEvan Bacon headers: [ 13153b4c0b0SEvan Bacon { 13253b4c0b0SEvan Bacon headers: [ 13353b4c0b0SEvan Bacon { 13453b4c0b0SEvan Bacon key: 'Cache-Control', 13553b4c0b0SEvan Bacon value: 'public, max-age=31536000, immutable', 13653b4c0b0SEvan Bacon }, 13753b4c0b0SEvan Bacon ], 13853b4c0b0SEvan Bacon source: 'static/**/*.js', 13953b4c0b0SEvan Bacon }, 14053b4c0b0SEvan Bacon ], 14153b4c0b0SEvan Bacon }); 14253b4c0b0SEvan Bacon 14353b4c0b0SEvan Bacon // If this changes then everything else probably changed as well. 14453b4c0b0SEvan Bacon expect(files).toEqual([ 14553b4c0b0SEvan Bacon 'asset-manifest.json', 14653b4c0b0SEvan Bacon 'index.html', 14753b4c0b0SEvan Bacon 'manifest.json', 14853b4c0b0SEvan Bacon 'serve.json', 149425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/\d+\.[a-z\d]+\.js/), 150425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/\d+\.[a-z\d]+\.js\.LICENSE\.txt/), 151425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/\d+\.[a-z\d]+\.js\.map/), 152425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/main\.[a-z\d]+\.js/), 153425e7f7fSEvan Bacon expect.stringMatching(/static\/js\/main\.[a-z\d]+\.js\.map/), 15453b4c0b0SEvan Bacon ]); 15553b4c0b0SEvan Bacon }, 15653b4c0b0SEvan Bacon // Could take 45s depending on how fast npm installs 15753b4c0b0SEvan Bacon 120 * 1000 15853b4c0b0SEvan Bacon); 159