testPagesShellsAreKept
tcm start renders a page's shell once and sends it at once: what renders the same every time (worked out from the code), with the fallbacks of what's live; then what's live, rendered for each request, streams in after it
import { CHROME, hydrated, withPage } from "#codebase/tcmts/cli/test/browser"; import { okIn, plainEnv, tcmBin, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness"; import { SHELL_SITE } from "#codebase/tcmts/cli/test/e2e/prod"; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import fs from "node:fs"; import http from "node:http"; import path from "node:path"; export const testPagesShellsAreKept = () => withChildren(async (keep) => { const dir = tempDir("shells"); const tcm = (...args: string[]) => okIn(dir, ...args); tcm("init"); tcm("lib", "add", "tcm", "server", "--at", "lib.server"); fs.rmSync(path.join(dir, "scratch.ts")); tcm("lib", "add", "tcm", "react", "--at", "news.lib.react"); fs.writeFileSync(path.join(dir, "news.tsx"), SHELL_SITE); tcm("add", "news.tsx"); tcm("build", "news.App"); const apps = path.join(dir, ".tcm", "apps"); const built = fs.readdirSync(apps).find((f) => /^[a-z2-7]{52}$/.test(f))!; const manifest = JSON.parse( fs.readFileSync(path.join(apps, built, "tcm-app.json"), "utf8"), ) as { shells?: boolean }; assert.equal(manifest.shells, true, "its browser code reads no clock: its shells are kept"); let log = ""; const url = await new Promise<string>((resolve, reject) => { const child = keep( spawn(process.execPath, [tcmBin(), "start", "news.App", "--port", "0"], { cwd: dir, env: plainEnv(), }), ); const seen = (d: string) => { log += d; const m = /url\s+(http:\/\/\S+)/.exec(log); if (m) resolve(m[1]!); }; child.stdout.setEncoding("utf8").on("data", seen); child.stderr.setEncoding("utf8").on("data", seen); child.on("exit", (code) => reject(new Error(`tcm start exited ${code}:\n${log}`))); }); // a page, as it arrives: when what's in it first came const timed = async () => { const started = performance.now(); const res = await fetch(url, { headers: { accept: "text/html" } }); const reader = res.body!.getReader(); const text = new TextDecoder(); let html = ""; const at: Record<string, number> = {}; for (;;) { const { value, done } = await reader.read(); if (done) break; html += text.decode(value, { stream: true }); for (const mark of ["<h1>News</h1>", "counting", "views</p>"]) if (at[mark] === undefined && html.includes(mark)) at[mark] = performance.now() - started; } return { html, at }; }; // its code, said at once, as Early Hints const hinted = await new Promise<string>((resolve, reject) => { const r = http.get(url, { headers: { accept: "text/html" } }, (res) => { res.resume(); res.on("end", () => resolve("")); }); r.on("information", (info) => { if (info.statusCode === 103) resolve(String(info.headers.link ?? "")); }); r.on("error", reject); }); assert.match(hinted, /<\/_app\/assets\/[^>]+\.js>; rel=modulepreload/); const count = (page: string) => Number(/(\d+)(?:<!-- -->)? views<\/p>/.exec(page)?.[1] ?? "none"); const once = await timed(); const { html, at } = await timed(); assert.ok( at["<h1>News</h1>"]! < 300, `the shell at once (${Math.round(at["<h1>News</h1>"]!)}ms)`, ); // (marks that came in one piece came together) assert.ok(at["counting"]! - at["<h1>News</h1>"]! < 5, "with the live part's fallback"); assert.ok( at["views</p>"]! - at["<h1>News</h1>"]! > 250, `and the live part after it, rendered for this request: ${JSON.stringify(at)}\n${log}`, ); assert.ok(count(once.html) >= 1, once.html); assert.equal( count(html), count(once.html) + 1, `each request's own: ${count(once.html)}, then ${count(html)}`, ); assert.match(html, /var __TCM_TREES__ = /); if (!CHROME) return console.log("skipped the browser: no Chrome"); await withPage(url, async (p) => { assert.ok(await hydrated(p)); assert.match( await p.waitFor<string>(`document.querySelector(".views")?.textContent ?? ""`, (t) => /\d views/.test(t ?? ""), ), new RegExp(`${count(html) + 1} views`), ); assert.equal(await p.evaluate(`document.querySelector("h1").textContent`), "News"); assert.deepEqual(p.errors, [], "it hydrates as it was sent"); }); })