105863844SEvan Bacon/* eslint-env jest */ 205863844SEvan Baconimport execa from 'execa'; 305863844SEvan Baconimport fs from 'fs-extra'; 405863844SEvan Baconimport klawSync from 'klaw-sync'; 505863844SEvan Baconimport path from 'path'; 605863844SEvan Bacon 705863844SEvan Baconimport { runExportSideEffects } from './export-side-effects'; 805863844SEvan Baconimport { bin, getRouterE2ERoot, getPageHtml } from '../utils'; 905863844SEvan Bacon 1005863844SEvan BaconrunExportSideEffects(); 1105863844SEvan Bacon 1205863844SEvan Bacondescribe('exports with single-page', () => { 1305863844SEvan Bacon const projectRoot = getRouterE2ERoot(); 1405863844SEvan Bacon const outputName = 'dist-spa'; 1505863844SEvan Bacon const outputDir = path.join(projectRoot, outputName); 1605863844SEvan Bacon 1705863844SEvan Bacon beforeAll( 1805863844SEvan Bacon async () => { 1905863844SEvan Bacon await execa('node', [bin, 'export', '-p', 'web', '--clear', '--output-dir', outputName], { 2005863844SEvan Bacon cwd: projectRoot, 2105863844SEvan Bacon env: { 2205863844SEvan Bacon NODE_ENV: 'production', 23*46f023faSEvan Bacon EXPO_USE_STATIC: 'single', 2405863844SEvan Bacon E2E_ROUTER_SRC: 'static-rendering', 2505863844SEvan Bacon E2E_ROUTER_ASYNC: 'development', 2605863844SEvan Bacon }, 2705863844SEvan Bacon }); 2805863844SEvan Bacon }, 2905863844SEvan Bacon // Could take 45s depending on how fast the bundler resolves 3005863844SEvan Bacon 560 * 1000 3105863844SEvan Bacon ); 3205863844SEvan Bacon 3305863844SEvan Bacon it( 3405863844SEvan Bacon 'has expected files', 3505863844SEvan Bacon async () => { 3605863844SEvan Bacon // List output files with sizes for snapshotting. 3705863844SEvan Bacon // This is to make sure that any changes to the output are intentional. 3805863844SEvan Bacon // Posix path formatting is used to make paths the same across OSes. 3905863844SEvan Bacon const files = klawSync(outputDir) 4005863844SEvan Bacon .map((entry) => { 4105863844SEvan Bacon if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 4205863844SEvan Bacon return null; 4305863844SEvan Bacon } 4405863844SEvan Bacon return path.posix.relative(outputDir, entry.path); 4505863844SEvan Bacon }) 4605863844SEvan Bacon .filter(Boolean); 4705863844SEvan Bacon 4805863844SEvan Bacon // The wrapper should not be included as a route. 4905863844SEvan Bacon expect(files).not.toContain('+html.html'); 5005863844SEvan Bacon expect(files).not.toContain('_layout.html'); 5105863844SEvan Bacon expect(files).not.toContain('about.html'); 5205863844SEvan Bacon 5305863844SEvan Bacon // Single page 5405863844SEvan Bacon expect(files).toContain('index.html'); 5505863844SEvan Bacon 5605863844SEvan Bacon // expect(files).toBe([ 5705863844SEvan Bacon // '_expo/static/css/global-67b6bc5b348b2db946e81c5f0040f565.css', 5805863844SEvan Bacon // '_expo/static/css/test.module-2e80684560f145dd976dab27f0bd1062.css', 5905863844SEvan Bacon // 'assets/35ba0eaec5a4f5ed12ca16fabeae451d', 6005863844SEvan Bacon // 'assets/369745d4a4a6fa62fa0ed495f89aa964', 6105863844SEvan Bacon // 'assets/4f355ba1efca4b9c0e7a6271af047f61', 6205863844SEvan Bacon // 'assets/5223c8d9b0d08b82a5670fb5f71faf78', 6305863844SEvan Bacon // 'assets/563d5e3294b67811d0a1aede6f601e30', 6405863844SEvan Bacon // 'assets/5974eb3e1c5314e8d5a822702d7d0740', 6505863844SEvan Bacon // 'assets/5b50965d3dfbc518fe50ce36c314a6ec', 6605863844SEvan Bacon // 'assets/817aca47ff3cea63020753d336e628a4', 6705863844SEvan Bacon // 'assets/9d9c5644f55c2f6e4b7f247c378b2fe9', 6805863844SEvan Bacon // 'assets/__packages/@expo/metro-runtime/assets/alert-triangle.png', 6905863844SEvan Bacon // 'assets/__packages/@expo/metro-runtime/assets/chevron-left.png', 7005863844SEvan Bacon // 'assets/__packages/@expo/metro-runtime/assets/chevron-right.png', 7105863844SEvan Bacon // 'assets/__packages/@expo/metro-runtime/assets/close.png', 7205863844SEvan Bacon // 'assets/__packages/@expo/metro-runtime/assets/loader.png', 7305863844SEvan Bacon // 'assets/__packages/expo-router/assets/error.png', 7405863844SEvan Bacon // 'assets/__packages/expo-router/assets/file.png', 7505863844SEvan Bacon // 'assets/__packages/expo-router/assets/forward.png', 7605863844SEvan Bacon // 'assets/__packages/expo-router/assets/pkg.png', 7705863844SEvan Bacon // 'assets/b6c297a501e289394b0bc5dc69c265e6', 7805863844SEvan Bacon // 'assets/e62addcde857ebdb7342e6b9f1095e97', 7905863844SEvan Bacon // 'bundles/web-e5c890886338c2296ecf737befa2cc66.js', 8005863844SEvan Bacon // 'index.html', 8105863844SEvan Bacon // 'metadata.json', 8205863844SEvan Bacon // ]); 8305863844SEvan Bacon }, 8405863844SEvan Bacon 2 * 1000 8505863844SEvan Bacon ); 8605863844SEvan Bacon 8705863844SEvan Bacon it( 8805863844SEvan Bacon 'can use environment variables', 8905863844SEvan Bacon async () => { 9005863844SEvan Bacon const indexHtml = await getPageHtml(outputDir, 'index.html'); 9105863844SEvan Bacon 9205863844SEvan Bacon const queryMeta = (name: string) => 9305863844SEvan Bacon indexHtml.querySelector(`html > head > meta[name="${name}"]`)?.attributes.content; 9405863844SEvan Bacon 9505863844SEvan Bacon [ 9605863844SEvan Bacon 'expo-e2e-public-env-var', 9705863844SEvan Bacon 'expo-e2e-private-env-var', 9805863844SEvan Bacon 'expo-e2e-public-env-var-client', 9905863844SEvan Bacon 'expo-e2e-private-env-var-client', 10005863844SEvan Bacon ].forEach((name) => { 10105863844SEvan Bacon expect(queryMeta(name)).not.toBeDefined(); 10205863844SEvan Bacon }); 10305863844SEvan Bacon 10405863844SEvan Bacon indexHtml.querySelectorAll('script').forEach((script) => { 10505863844SEvan Bacon const jsBundle = fs.readFileSync(path.join(outputDir, script.attributes.src), 'utf8'); 10605863844SEvan Bacon 10705863844SEvan Bacon // Ensure the bundle is valid 10805863844SEvan Bacon expect(jsBundle).toMatch('__BUNDLE_START_TIME__'); 10905863844SEvan Bacon // Ensure the non-public env var is not included in the bundle 11005863844SEvan Bacon expect(jsBundle).not.toMatch('not-public-value'); 11105863844SEvan Bacon }); 11205863844SEvan Bacon }, 11305863844SEvan Bacon 2 * 1000 11405863844SEvan Bacon ); 11505863844SEvan Bacon 11605863844SEvan Bacon it( 11705863844SEvan Bacon 'static styles are injected', 11805863844SEvan Bacon async () => { 11905863844SEvan Bacon const indexHtml = await getPageHtml(outputDir, 'index.html'); 12005863844SEvan Bacon expect(indexHtml.querySelectorAll('html > head > style')?.length).toBe( 12105863844SEvan Bacon // Whatever was in the template 12205863844SEvan Bacon 1 12305863844SEvan Bacon ); 12405863844SEvan Bacon }, 12505863844SEvan Bacon 2 * 1000 12605863844SEvan Bacon ); 12705863844SEvan Bacon 12805863844SEvan Bacon // Static CSS works mostly the same across static and SPA modes. 12905863844SEvan Bacon it( 13005863844SEvan Bacon 'statically extracts CSS', 13105863844SEvan Bacon async () => { 13205863844SEvan Bacon // Unfortunately, the CSS is injected in every page for now since we don't have bundle splitting. 13305863844SEvan Bacon const indexHtml = await getPageHtml(outputDir, 'index.html'); 13405863844SEvan Bacon 13505863844SEvan Bacon const links = indexHtml.querySelectorAll('html > head > link'); 13605863844SEvan Bacon expect(links.length).toBe( 13705863844SEvan Bacon // Global CSS and CSS Module 13805863844SEvan Bacon 4 13905863844SEvan Bacon ); 14005863844SEvan Bacon 14105863844SEvan Bacon links.forEach((link) => { 14205863844SEvan Bacon // Linked to the expected static location 14305863844SEvan Bacon expect(link.attributes.href).toMatch(/^\/_expo\/static\/css\/.*\.css$/); 14405863844SEvan Bacon }); 14505863844SEvan Bacon 14605863844SEvan Bacon expect(links[0].toString()).toMatch( 14705863844SEvan Bacon /<link rel="preload" href="\/_expo\/static\/css\/global-[\d\w]+\.css" as="style">/ 14805863844SEvan Bacon ); 14905863844SEvan Bacon expect(links[1].toString()).toMatch( 15005863844SEvan Bacon /<link rel="stylesheet" href="\/_expo\/static\/css\/global-[\d\w]+\.css">/ 15105863844SEvan Bacon ); 15205863844SEvan Bacon // CSS Module 15305863844SEvan Bacon expect(links[2].toString()).toMatch( 15405863844SEvan Bacon /<link rel="preload" href="\/_expo\/static\/css\/test\.module-[\d\w]+\.css" as="style">/ 15505863844SEvan Bacon ); 15605863844SEvan Bacon expect(links[3].toString()).toMatch( 15705863844SEvan Bacon /<link rel="stylesheet" href="\/_expo\/static\/css\/test\.module-[\d\w]+\.css">/ 15805863844SEvan Bacon ); 15905863844SEvan Bacon 16005863844SEvan Bacon expect( 16105863844SEvan Bacon fs.readFileSync(path.join(outputDir, links[0].attributes.href), 'utf-8') 16205863844SEvan Bacon ).toMatchInlineSnapshot(`"div{background:#0ff}"`); 16305863844SEvan Bacon 16405863844SEvan Bacon // CSS Module 16505863844SEvan Bacon expect( 16605863844SEvan Bacon fs.readFileSync(path.join(outputDir, links[2].attributes.href), 'utf-8') 16705863844SEvan Bacon ).toMatchInlineSnapshot(`".HPV33q_text{color:#1e90ff}"`); 16805863844SEvan Bacon }, 16905863844SEvan Bacon 2 * 1000 17005863844SEvan Bacon ); 17105863844SEvan Bacon 17205863844SEvan Bacon it( 17305863844SEvan Bacon 'does not statically render the head', 17405863844SEvan Bacon async () => { 17505863844SEvan Bacon const indexHtml = await getPageHtml(outputDir, 'index.html'); 17605863844SEvan Bacon expect(indexHtml.querySelector('html > head > title')?.innerText).toBe('Router E2E'); 17705863844SEvan Bacon 17805863844SEvan Bacon expect(indexHtml.querySelector('html > head > meta[name="description"]')).toBeNull(); 17905863844SEvan Bacon expect( 18005863844SEvan Bacon // Nested from app/_layout.js 18105863844SEvan Bacon indexHtml.querySelector('html > head > meta[name="expo-nested-layout"]') 18205863844SEvan Bacon ).toBeNull(); 18305863844SEvan Bacon }, 18405863844SEvan Bacon 2 * 1000 18505863844SEvan Bacon ); 18605863844SEvan Bacon}); 187