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 { 9 execute, 10 projectRoot, 11 getLoadedModulesAsync, 12 setupTestProjectAsync, 13 bin, 14 ensurePortFreeAsync, 15} from './utils'; 16 17const originalForceColor = process.env.FORCE_COLOR; 18const originalCI = process.env.CI; 19 20beforeAll(async () => { 21 await fs.mkdir(projectRoot, { recursive: true }); 22 process.env.FORCE_COLOR = '0'; 23 process.env.CI = '1'; 24 process.env._EXPO_E2E_USE_PATH_ALIASES = '1'; 25 delete process.env.EXPO_WEB_OUTPUT_MODE; 26}); 27 28afterAll(() => { 29 process.env.FORCE_COLOR = originalForceColor; 30 process.env.CI = originalCI; 31 delete process.env._EXPO_E2E_USE_PATH_ALIASES; 32}); 33 34it('loads expected modules by default', async () => { 35 const modules = await getLoadedModulesAsync(`require('../../build/src/export').expoExport`); 36 expect(modules).toStrictEqual([ 37 '../node_modules/ansi-styles/index.js', 38 '../node_modules/arg/index.js', 39 '../node_modules/chalk/source/index.js', 40 '../node_modules/chalk/source/util.js', 41 '../node_modules/has-flag/index.js', 42 '../node_modules/supports-color/index.js', 43 '@expo/cli/build/src/export/index.js', 44 '@expo/cli/build/src/log.js', 45 '@expo/cli/build/src/utils/args.js', 46 '@expo/cli/build/src/utils/errors.js', 47 ]); 48}); 49 50it('runs `npx expo export --help`', async () => { 51 const results = await execute('export', '--help'); 52 expect(results.stdout).toMatchInlineSnapshot(` 53 " 54 Info 55 Export the static files of the app for hosting it on a web server 56 57 Usage 58 $ npx expo export <dir> 59 60 Options 61 <dir> Directory of the Expo project. Default: Current working directory 62 --dev Configure static files for developing locally using a non-https server 63 --output-dir <dir> The directory to export the static files to. Default: dist 64 --max-workers <number> Maximum number of tasks to allow the bundler to spawn 65 --dump-assetmap Dump the asset map for further processing 66 --dump-sourcemap Dump the source map for debugging the JS bundle 67 -p, --platform <platform> Options: android, ios, web, all. Default: all 68 -c, --clear Clear the bundler cache 69 -h, --help Usage info 70 " 71 `); 72}); 73 74describe('server', () => { 75 beforeEach(() => ensurePortFreeAsync(19000)); 76 it( 77 'runs `npx expo export`', 78 async () => { 79 const projectRoot = await setupTestProjectAsync('basic-export', 'with-assets'); 80 // `npx expo export` 81 await execa('node', [bin, 'export', '--dump-sourcemap', '--dump-assetmap'], { 82 cwd: projectRoot, 83 }); 84 85 const outputDir = path.join(projectRoot, 'dist'); 86 // List output files with sizes for snapshotting. 87 // This is to make sure that any changes to the output are intentional. 88 // Posix path formatting is used to make paths the same across OSes. 89 const files = klawSync(outputDir) 90 .map((entry) => { 91 if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 92 return null; 93 } 94 return path.posix.relative(outputDir, entry.path); 95 }) 96 .filter(Boolean); 97 98 const metadata = await JsonFile.readAsync(path.resolve(outputDir, 'metadata.json')); 99 100 expect(metadata).toEqual({ 101 bundler: 'metro', 102 fileMetadata: { 103 android: { 104 assets: [ 105 { 106 ext: 'png', 107 path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2', 108 }, 109 { 110 ext: 'png', 111 path: 'assets/9ce7db807e4147e00df372d053c154c2', 112 }, 113 { 114 ext: 'ttf', 115 path: 'assets/3858f62230ac3c915f300c664312c63f', 116 }, 117 ], 118 bundle: expect.stringMatching(/bundles\/android-.*\.js/), 119 }, 120 ios: { 121 assets: [ 122 { 123 ext: 'png', 124 path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2', 125 }, 126 { 127 ext: 'png', 128 path: 'assets/9ce7db807e4147e00df372d053c154c2', 129 }, 130 { 131 ext: 'ttf', 132 path: 'assets/2f334f6c7ca5b2a504bdf8acdee104f3', 133 }, 134 ], 135 bundle: expect.stringMatching(/bundles\/ios-.*\.js/), 136 }, 137 web: { 138 assets: [ 139 { 140 ext: 'png', 141 path: 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2', 142 }, 143 { 144 ext: 'png', 145 path: 'assets/9ce7db807e4147e00df372d053c154c2', 146 }, 147 { 148 ext: 'ttf', 149 path: 'assets/3858f62230ac3c915f300c664312c63f', 150 }, 151 ], 152 bundle: expect.stringMatching(/bundles\/web-.*\.js/), 153 }, 154 }, 155 version: 0, 156 }); 157 158 const assetmap = await JsonFile.readAsync(path.resolve(outputDir, 'assetmap.json')); 159 expect(assetmap).toEqual({ 160 '2f334f6c7ca5b2a504bdf8acdee104f3': { 161 __packager_asset: true, 162 fileHashes: ['2f334f6c7ca5b2a504bdf8acdee104f3'], 163 fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/), 164 files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ios\.ttf/)], 165 hash: '2f334f6c7ca5b2a504bdf8acdee104f3', 166 httpServerLocation: '/assets/assets', 167 name: 'font', 168 scales: [1], 169 type: 'ttf', 170 }, 171 172 '3858f62230ac3c915f300c664312c63f': { 173 __packager_asset: true, 174 fileHashes: ['3858f62230ac3c915f300c664312c63f'], 175 fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/), 176 files: [expect.stringMatching(/\/.*\/basic-export\/assets\/font\.ttf/)], 177 hash: '3858f62230ac3c915f300c664312c63f', 178 httpServerLocation: '/assets/assets', 179 name: 'font', 180 scales: [1], 181 type: 'ttf', 182 }, 183 d48d481475a80809fcf9253a765193d1: { 184 __packager_asset: true, 185 fileHashes: ['fb960eb5e4eb49ec8786c7f6c4a57ce2', '9ce7db807e4147e00df372d053c154c2'], 186 fileSystemLocation: expect.stringMatching(/\/.*\/basic-export\/assets/), 187 files: [ 188 expect.stringMatching(/\/.*\/basic-export\/assets\/icon\.png/), 189 expect.stringMatching(/\/.*\/basic-export\/assets\/icon@2x\.png/), 190 ], 191 hash: 'd48d481475a80809fcf9253a765193d1', 192 height: 1, 193 httpServerLocation: '/assets/assets', 194 name: 'icon', 195 scales: [1, 2], 196 type: 'png', 197 width: 1, 198 }, 199 }); 200 201 // If this changes then everything else probably changed as well. 202 expect(files).toEqual([ 203 'assetmap.json', 204 'assets/2f334f6c7ca5b2a504bdf8acdee104f3', 205 'assets/3858f62230ac3c915f300c664312c63f', 206 'assets/9ce7db807e4147e00df372d053c154c2', 207 'assets/assets/font.ttf', 208 'assets/assets/icon.png', 209 'assets/assets/icon@2x.png', 210 211 'assets/fb960eb5e4eb49ec8786c7f6c4a57ce2', 212 expect.stringMatching(/bundles\/android-[\w\d]+\.js/), 213 expect.stringMatching(/bundles\/android-[\w\d]+\.map/), 214 expect.stringMatching(/bundles\/ios-[\w\d]+\.js/), 215 expect.stringMatching(/bundles\/ios-[\w\d]+\.map/), 216 expect.stringMatching(/bundles\/web-[\w\d]+\.js/), 217 expect.stringMatching(/bundles\/web-[\w\d]+\.map/), 218 'debug.html', 219 'drawable-mdpi/assets_icon.png', 220 'drawable-xhdpi/assets_icon.png', 221 'favicon.ico', 222 'index.html', 223 'metadata.json', 224 'raw/assets_font.ttf', 225 ]); 226 }, 227 // Could take 45s depending on how fast npm installs 228 120 * 1000 229 ); 230 231 xit( 232 'runs `npx expo export -p web` for static rendering', 233 async () => { 234 const projectRoot = await setupTestProjectAsync('export-router', 'with-router', '48.0.0'); 235 await execa('node', [bin, 'export', '-p', 'web'], { 236 cwd: projectRoot, 237 env: { 238 EXPO_WEB_OUTPUT_MODE: 'static', 239 }, 240 }); 241 242 const outputDir = path.join(projectRoot, 'dist'); 243 // List output files with sizes for snapshotting. 244 // This is to make sure that any changes to the output are intentional. 245 // Posix path formatting is used to make paths the same across OSes. 246 const files = klawSync(outputDir) 247 .map((entry) => { 248 if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 249 return null; 250 } 251 return path.posix.relative(outputDir, entry.path); 252 }) 253 .filter(Boolean); 254 255 const metadata = await JsonFile.readAsync(path.resolve(outputDir, 'metadata.json')); 256 257 expect(metadata).toEqual({ 258 bundler: 'metro', 259 fileMetadata: { 260 web: { 261 assets: expect.anything(), 262 bundle: expect.stringMatching(/bundles\/web-.*\.js/), 263 }, 264 }, 265 version: 0, 266 }); 267 268 // If this changes then everything else probably changed as well. 269 expect(files).toEqual([ 270 '[...404].html', 271 '_sitemap.html', 272 'about.html', 273 'assets/35ba0eaec5a4f5ed12ca16fabeae451d', 274 'assets/369745d4a4a6fa62fa0ed495f89aa964', 275 'assets/4f355ba1efca4b9c0e7a6271af047f61', 276 'assets/5223c8d9b0d08b82a5670fb5f71faf78', 277 'assets/52dec48a970c0a4eed4119cd1252ab09', 278 'assets/5b50965d3dfbc518fe50ce36c314a6ec', 279 'assets/817aca47ff3cea63020753d336e628a4', 280 'assets/b2de8e638d92e0f719fa92fa4085e02a', 281 'assets/cbbeac683d803ac27cefb817787d2bfa', 282 'assets/e62addcde857ebdb7342e6b9f1095e97', 283 expect.stringMatching(/bundles\/web-[\w\d]+\.js/), 284 'favicon.ico', 285 'index.html', 286 'metadata.json', 287 ]); 288 289 const about = await fs.readFile(path.join(outputDir, 'about.html'), 'utf8'); 290 291 // Route-specific head tags 292 expect(about).toContain(`<title data-rh="true">About | Website</title>`); 293 294 // Nested head tags from layout route 295 expect(about).toContain('<meta data-rh="true" name="fake" content="bar"/>'); 296 297 // Root element 298 expect(about).toContain('<div id="root">'); 299 // Content of the page 300 expect(about).toContain('data-testid="content">About</div>'); 301 302 // <script src="/bundles/web-c91ecb663cfce9b9e90e28d253e72e0a.js" defer> 303 const sanitizedAbout = about.replace( 304 /<script src="\/bundles\/.*" defer>/g, 305 '<script src="/bundles/[mock].js" defer>' 306 ); 307 expect(sanitizedAbout).toMatchSnapshot(); 308 }, 309 // Could take 45s depending on how fast npm installs 310 240 * 1000 311 ); 312}); 313