testStaticSitesRenderEveryPage
tcm build --static renders every page an app links to, from /, as it's built: server components run then, their trees kept as files, so the pages are there before any code runs, and the app goes from one to another in the browser with no server
import { CHROME, hydrated, withPage } from "#codebase/tcmts/cli/test/browser"; import { staticHost } from "#codebase/tcmts/cli/test/e2e/examples"; import { okIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import { PAGES_SITE } from "#codebase/tcmts/cli/test/e2e/prod"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testStaticSitesRenderEveryPage = async () => { const dir = tempDir("static pages"); const tcm = (...args: string[]) => okIn(dir, ...args); tcm("init"); fs.rmSync(path.join(dir, "scratch.ts")); tcm("lib", "add", "tcm", "react", "--at", "site.lib.react"); fs.writeFileSync(path.join(dir, "site.tsx"), PAGES_SITE); tcm("add", "site.tsx"); assert.match( tcm("build", "site.App", "--static", "out"), /wrote site\.App as a static site into out \(3 page\(s\)/, ); const out = path.join(dir, "out"); const read = (f: string) => fs.readFileSync(path.join(out, f), "utf8"); assert.match(read("index.html"), /<h1>Home<\/h1>/); assert.match(read("about.html"), /<h1>About us<\/h1>/); assert.match(read("blog/first.html"), /<h1>First post<\/h1>/); assert.match(read("about.html"), /var __TCM_STATIC__ = "site\.[0-9a-f]{32}";/); assert.doesNotMatch(read("404.html"), /<h1>/, "a path with no page renders in the browser"); assert.equal( fs.readdirSync(path.join(out, "_app", "trees")).length, 3, "a tree for each page's props", ); // each named by its content, and the map of them, which every page names, too: a browser keeps them for good for (const f of fs.readdirSync(path.join(out, "_app", "trees"))) assert.match(f, /^t\.[0-9a-f]{32}\.txt$/); // (Flight's text, production's: no debugging information, so a build makes the same files each time) for (const f of fs.readdirSync(path.join(out, "_app", "trees"))) assert.doesNotMatch(read(`_app/trees/${f}`), /"stack"|:D\{"time"/); const mapFile = fs .readdirSync(path.join(out, "_app")) .find((f) => /^site\.[0-9a-f]{32}\.json$/.test(f)); assert.ok(mapFile, "the site's map"); assert.match( read("about.html"), new RegExp(`var __TCM_STATIC__ = "${mapFile!.replace(/\.json$/, "").replace(".", "\\.")}";`), ); const map = JSON.parse(read(`_app/${mapFile}`)) as { trees: Record<string, string>; pages: Record<string, string[]>; }; assert.deepEqual( Object.keys(map.pages).sort(), ["/", "/about", "/blog/first"], "which trees each page shows", ); assert.equal(Object.keys(map.trees).length, 3); if (!CHROME) return console.log("skipped the browser: no Chrome"); const host = await staticHost(out); try { await withPage(`${host.url}/`, async (p) => { assert.ok(await hydrated(p)); // the pages its links in view go to: what they show is read while the page is idle, with nothing pointed at const trees = `performance.getEntriesByType("resource").filter((r) => r.name.includes("/_app/trees/")).length`; assert.equal( await p.waitFor<number>(trees, (n) => n >= 2), 2, "about's and first's, before either is followed", ); await p.evaluate( `window.__stayed = 1; [...document.querySelectorAll("a")].find((a) => a.textContent === "about").click()`, ); assert.equal( await p.waitFor<string>( `document.querySelector("h1").textContent`, (t) => t === "About us", ), "About us", "the next page's tree, from its file", ); assert.equal( await p.evaluate(`location.pathname + ":" + window.__stayed`), "/about:1", "followed in the app", ); await p.evaluate( `[...document.querySelectorAll("a")].find((a) => a.textContent === "first").click()`, ); assert.equal( await p.waitFor<string>( `document.querySelector("h1").textContent`, (t) => t === "First post", ), "First post", ); assert.equal( await p.evaluate<number>(trees), 2, "and nothing read again when they're followed", ); assert.deepEqual(p.errors, []); }); } finally { await host.close(); } }