1/* eslint-env jest */ 2import execa from 'execa'; 3import fs from 'fs-extra'; 4import klawSync from 'klaw-sync'; 5import path from 'path'; 6 7import { runExportSideEffects } from './export-side-effects'; 8import { bin, getRouterE2ERoot, getPageHtml } from '../utils'; 9 10runExportSideEffects(); 11 12describe('exports with single-page', () => { 13 const projectRoot = getRouterE2ERoot(); 14 const outputName = 'dist-spa'; 15 const outputDir = path.join(projectRoot, outputName); 16 17 beforeAll( 18 async () => { 19 await execa('node', [bin, 'export', '-p', 'web', '--clear', '--output-dir', outputName], { 20 cwd: projectRoot, 21 env: { 22 NODE_ENV: 'production', 23 EXPO_USE_STATIC: '0', 24 E2E_ROUTER_SRC: 'static-rendering', 25 E2E_ROUTER_ASYNC: 'development', 26 }, 27 }); 28 }, 29 // Could take 45s depending on how fast the bundler resolves 30 560 * 1000 31 ); 32 33 it( 34 'has expected files', 35 async () => { 36 // List output files with sizes for snapshotting. 37 // This is to make sure that any changes to the output are intentional. 38 // Posix path formatting is used to make paths the same across OSes. 39 const files = klawSync(outputDir) 40 .map((entry) => { 41 if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 42 return null; 43 } 44 return path.posix.relative(outputDir, entry.path); 45 }) 46 .filter(Boolean); 47 48 // The wrapper should not be included as a route. 49 expect(files).not.toContain('+html.html'); 50 expect(files).not.toContain('_layout.html'); 51 expect(files).not.toContain('about.html'); 52 53 // Single page 54 expect(files).toContain('index.html'); 55 56 // expect(files).toBe([ 57 // '_expo/static/css/global-67b6bc5b348b2db946e81c5f0040f565.css', 58 // '_expo/static/css/test.module-2e80684560f145dd976dab27f0bd1062.css', 59 // 'assets/35ba0eaec5a4f5ed12ca16fabeae451d', 60 // 'assets/369745d4a4a6fa62fa0ed495f89aa964', 61 // 'assets/4f355ba1efca4b9c0e7a6271af047f61', 62 // 'assets/5223c8d9b0d08b82a5670fb5f71faf78', 63 // 'assets/563d5e3294b67811d0a1aede6f601e30', 64 // 'assets/5974eb3e1c5314e8d5a822702d7d0740', 65 // 'assets/5b50965d3dfbc518fe50ce36c314a6ec', 66 // 'assets/817aca47ff3cea63020753d336e628a4', 67 // 'assets/9d9c5644f55c2f6e4b7f247c378b2fe9', 68 // 'assets/__packages/@expo/metro-runtime/assets/alert-triangle.png', 69 // 'assets/__packages/@expo/metro-runtime/assets/chevron-left.png', 70 // 'assets/__packages/@expo/metro-runtime/assets/chevron-right.png', 71 // 'assets/__packages/@expo/metro-runtime/assets/close.png', 72 // 'assets/__packages/@expo/metro-runtime/assets/loader.png', 73 // 'assets/__packages/expo-router/assets/error.png', 74 // 'assets/__packages/expo-router/assets/file.png', 75 // 'assets/__packages/expo-router/assets/forward.png', 76 // 'assets/__packages/expo-router/assets/pkg.png', 77 // 'assets/b6c297a501e289394b0bc5dc69c265e6', 78 // 'assets/e62addcde857ebdb7342e6b9f1095e97', 79 // 'bundles/web-e5c890886338c2296ecf737befa2cc66.js', 80 // 'index.html', 81 // 'metadata.json', 82 // ]); 83 }, 84 2 * 1000 85 ); 86 87 it( 88 'can use environment variables', 89 async () => { 90 const indexHtml = await getPageHtml(outputDir, 'index.html'); 91 92 const queryMeta = (name: string) => 93 indexHtml.querySelector(`html > head > meta[name="${name}"]`)?.attributes.content; 94 95 [ 96 'expo-e2e-public-env-var', 97 'expo-e2e-private-env-var', 98 'expo-e2e-public-env-var-client', 99 'expo-e2e-private-env-var-client', 100 ].forEach((name) => { 101 expect(queryMeta(name)).not.toBeDefined(); 102 }); 103 104 indexHtml.querySelectorAll('script').forEach((script) => { 105 const jsBundle = fs.readFileSync(path.join(outputDir, script.attributes.src), 'utf8'); 106 107 // Ensure the bundle is valid 108 expect(jsBundle).toMatch('__BUNDLE_START_TIME__'); 109 // Ensure the non-public env var is not included in the bundle 110 expect(jsBundle).not.toMatch('not-public-value'); 111 }); 112 }, 113 2 * 1000 114 ); 115 116 it( 117 'static styles are injected', 118 async () => { 119 const indexHtml = await getPageHtml(outputDir, 'index.html'); 120 expect(indexHtml.querySelectorAll('html > head > style')?.length).toBe( 121 // Whatever was in the template 122 1 123 ); 124 }, 125 2 * 1000 126 ); 127 128 // Static CSS works mostly the same across static and SPA modes. 129 it( 130 'statically extracts CSS', 131 async () => { 132 // Unfortunately, the CSS is injected in every page for now since we don't have bundle splitting. 133 const indexHtml = await getPageHtml(outputDir, 'index.html'); 134 135 const links = indexHtml.querySelectorAll('html > head > link'); 136 expect(links.length).toBe( 137 // Global CSS and CSS Module 138 4 139 ); 140 141 links.forEach((link) => { 142 // Linked to the expected static location 143 expect(link.attributes.href).toMatch(/^\/_expo\/static\/css\/.*\.css$/); 144 }); 145 146 expect(links[0].toString()).toMatch( 147 /<link rel="preload" href="\/_expo\/static\/css\/global-[\d\w]+\.css" as="style">/ 148 ); 149 expect(links[1].toString()).toMatch( 150 /<link rel="stylesheet" href="\/_expo\/static\/css\/global-[\d\w]+\.css">/ 151 ); 152 // CSS Module 153 expect(links[2].toString()).toMatch( 154 /<link rel="preload" href="\/_expo\/static\/css\/test\.module-[\d\w]+\.css" as="style">/ 155 ); 156 expect(links[3].toString()).toMatch( 157 /<link rel="stylesheet" href="\/_expo\/static\/css\/test\.module-[\d\w]+\.css">/ 158 ); 159 160 expect( 161 fs.readFileSync(path.join(outputDir, links[0].attributes.href), 'utf-8') 162 ).toMatchInlineSnapshot(`"div{background:#0ff}"`); 163 164 // CSS Module 165 expect( 166 fs.readFileSync(path.join(outputDir, links[2].attributes.href), 'utf-8') 167 ).toMatchInlineSnapshot(`".HPV33q_text{color:#1e90ff}"`); 168 }, 169 2 * 1000 170 ); 171 172 it( 173 'does not statically render the head', 174 async () => { 175 const indexHtml = await getPageHtml(outputDir, 'index.html'); 176 expect(indexHtml.querySelector('html > head > title')?.innerText).toBe('Router E2E'); 177 178 expect(indexHtml.querySelector('html > head > meta[name="description"]')).toBeNull(); 179 expect( 180 // Nested from app/_layout.js 181 indexHtml.querySelector('html > head > meta[name="expo-nested-layout"]') 182 ).toBeNull(); 183 }, 184 2 * 1000 185 ); 186}); 187