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, getPageHtml, getRouterE2ERoot } from '../utils';
9
10runExportSideEffects();
11
12describe('static-rendering with a custom base path', () => {
13  const projectRoot = getRouterE2ERoot();
14  const outputName = 'dist-static-rendering-asset-prefix';
15  const outputDir = path.join(projectRoot, outputName);
16
17  beforeAll(
18    async () => {
19      const basePath = '/one/two';
20      process.env.EXPO_E2E_BASE_PATH = basePath;
21      await execa('node', [bin, 'export', '-p', 'web', '--clear', '--output-dir', outputName], {
22        cwd: projectRoot,
23        env: {
24          NODE_ENV: 'production',
25          EXPO_E2E_BASE_PATH: basePath,
26          EXPO_USE_STATIC: 'static',
27          E2E_ROUTER_SRC: 'static-rendering',
28          E2E_ROUTER_ASYNC: 'development',
29        },
30      });
31    },
32    // Could take 45s depending on how fast the bundler resolves
33    560 * 1000
34  );
35
36  it(
37    'has expected files',
38    async () => {
39      // List output files with sizes for snapshotting.
40      // This is to make sure that any changes to the output are intentional.
41      // Posix path formatting is used to make paths the same across OSes.
42      const files = klawSync(outputDir)
43        .map((entry) => {
44          if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
45            return null;
46          }
47          return path.posix.relative(outputDir, entry.path);
48        })
49        .filter(Boolean);
50
51      expect(
52        files.find((file) => file?.match(/\_expo\/static\/js\/web\/index-.*\.js/))
53      ).toBeDefined();
54
55      // The wrapper should not be included as a route.
56      expect(files).not.toContain('+html.html');
57      expect(files).not.toContain('_layout.html');
58
59      // Injected by framework
60      expect(files).toContain('_sitemap.html');
61      expect(files).toContain('[...404].html');
62
63      // Normal routes
64      expect(files).toContain('about.html');
65      expect(files).toContain('index.html');
66      expect(files).toContain('styled.html');
67      expect(files).toContain('links.html');
68
69      // generateStaticParams values
70      expect(files).toContain('[post].html');
71      expect(files).toContain('welcome-to-the-universe.html');
72      expect(files).toContain('other.html');
73    },
74    5 * 1000
75  );
76
77  it(
78    'writes assets with prefix',
79    async () => {
80      const indexHtml = await getPageHtml(outputDir, 'index.html');
81
82      const jsFiles = indexHtml.querySelectorAll('script').map((script) => script.attributes.src);
83      expect(jsFiles).toEqual([
84        expect.stringMatching(/\/one\/two\/_expo\/static\/js\/web\/index-.*\.js/),
85      ]);
86
87      const links = indexHtml.querySelectorAll('html > head > link').filter((link) => {
88        // Fonts are tested elsewhere
89        return link.attributes.as !== 'font';
90      });
91
92      const cssFiles = links.map((link) => link.attributes.href);
93
94      cssFiles.forEach((src) => {
95        // Linked to the expected static location
96        expect(src).toMatch(/^\/one\/two\/_expo\/static\/css\/.*\.css$/);
97      });
98
99      const fontLinks = indexHtml.querySelectorAll('html > head > link[as="font"]');
100
101      const fontFiles = fontLinks.map((link) => link.attributes.href);
102
103      fontFiles.forEach((src) => {
104        // Linked to the expected static location
105        expect(src).toMatch(/^\/one\/two\/assets\/.*\.ttf/);
106      });
107
108      for (const file of [...cssFiles, ...jsFiles]) {
109        expect(
110          fs.existsSync(
111            path.join(
112              outputDir,
113              // If we strip the asset prefix, the file should exist.
114              file.replace(/^\/one\/two/, '')
115            )
116          )
117        ).toBe(true);
118      }
119    },
120    5 * 1000
121  );
122
123  it(
124    'supports usePathname in +html files',
125    async () => {
126      const page = await fs.readFile(path.join(outputDir, 'index.html'), 'utf8');
127
128      expect(page).toContain('<meta name="custom-value" content="value"/>');
129
130      // Root element
131      expect(page).toContain('<div id="root">');
132
133      expect(
134        (await getPageHtml(outputDir, 'about.html')).querySelector(
135          'html > head > meta[name="expo-e2e-pathname"]'
136        )?.attributes.content
137      ).toBe('/about');
138
139      expect(
140        (await getPageHtml(outputDir, 'index.html')).querySelector(
141          'html > head > meta[name="expo-e2e-pathname"]'
142        )?.attributes.content
143      ).toBe('/');
144
145      expect(
146        (await getPageHtml(outputDir, 'welcome-to-the-universe.html')).querySelector(
147          'html > head > meta[name="expo-e2e-pathname"]'
148        )?.attributes.content
149      ).toBe('/welcome-to-the-universe');
150    },
151    5 * 1000
152  );
153
154  it(
155    'supports basePath in Links',
156    async () => {
157      expect(
158        (await getPageHtml(outputDir, 'links.html')).querySelector(
159          'body > #root a[data-testid="links-one"]'
160        )?.attributes.href
161      ).toBe('/one/two/about');
162    },
163    5 * 1000
164  );
165});
166