testServerComponentsKeptAndRenderedAhead

tcmts.cli.test.e2e.prod.testServerComponentsKeptAndRenderedAhead · #l6fuql6m · term

tcm start: a server component that renders the same for the same props (worked out from its code) is asked for with GET and kept for good, by the server and the browser, and through server function calls; and what a page shows is rendered before the browser goes there, when a link to it is pressed

import { encode } from "#codebase/tcmts/cli/codec";
import { CHROME, hydrated, withPage } from "#codebase/tcmts/cli/test/browser";
import { hashOf } from "#codebase/tcmts/cli/test/e2e/examples";
import { okIn, plainEnv, tcmBin, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { CACHED_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 path from "node:path";

export const testServerComponentsKeptAndRenderedAhead = () =>
  withChildren(async (keep) => {
    const dir = tempDir("kept components");
    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", "shop.lib.react");
    fs.writeFileSync(path.join(dir, "shop.tsx"), CACHED_SITE);
    tcm("add", "shop.tsx");
    const greeting = hashOf("shop.Greeting", dir);
    const visitsHash = hashOf("shop.Visits", dir);
    tcm("build", "shop.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 {
      cacheable?: string[];
    };
    assert.deepEqual(
      manifest.cacheable,
      [greeting],
      "Greeting renders the same for the same props; Visits reads a table",
    );
    const url = await new Promise<string>((resolve, reject) => {
      const child = keep(
        spawn(process.execPath, [tcmBin(), "start", "shop.App", "--port", "0"], {
          cwd: dir,
          env: plainEnv(),
        }),
      );
      let out = "";
      child.stdout.setEncoding("utf8").on("data", (d: string) => {
        out += d;
        const m = /url\s+(http:\/\/\S+)/.exec(out);
        if (m) resolve(m[1]!);
      });
      child.on("exit", (code) => reject(new Error(`tcm start exited ${code}:\n${out}`)));
    });
    // asked for with GET, its props in the URL, and kept for good; the table's isn't
    const asked = new URL(
      `__tcm/rpc/${greeting}?p=${encodeURIComponent(encode([{ name: "x" }]))}`,
      url,
    );
    const first = await fetch(asked);
    assert.equal(first.status, 200);
    assert.equal(first.headers.get("cache-control"), "public, max-age=31536000, immutable");
    const tree = await first.text();
    assert.match(tree, /Hello/);
    assert.equal(await (await fetch(asked)).text(), tree, "the same, kept");
    assert.equal(
      (await fetch(new URL(`__tcm/rpc/${visitsHash}?p=${encodeURIComponent(encode([{}]))}`, url)))
        .status,
      405,
    );
    // a page's trees, rendered ahead
    const ahead = await fetch(new URL("__tcm/page?path=/about", url));
    assert.equal(ahead.status, 200);
    assert.match(await ahead.text(), /about/);
    if (!CHROME) return console.log("skipped the browser: no Chrome");
    await withPage(url, async (p) => {
      assert.ok(await hydrated(p));
      const requests = (part: string) =>
        `performance.getEntriesByType("resource").filter((r) => r.name.includes(${JSON.stringify(part)})).length`;
      await p.evaluate(
        `[...document.querySelectorAll("a")].find((a) => a.textContent === "about").dispatchEvent(new PointerEvent("pointerdown", { bubbles: true }))`,
      );
      assert.equal(
        await p.waitFor<number>(requests("/__tcm/page"), (n) => n > 0),
        1,
        "rendered ahead when pressed",
      );
      await new Promise((r) => setTimeout(r, 300));
      const before = await p.evaluate<number>(requests("/__tcm/rpc/"));
      await p.evaluate(
        `[...document.querySelectorAll("a")].find((a) => a.textContent === "about").click()`,
      );
      assert.equal(
        await p.waitFor<string>(
          `document.querySelector("h1").textContent`,
          (t) => t === "Hello about",
        ),
        "Hello about",
      );
      assert.equal(
        await p.evaluate<number>(requests("/__tcm/rpc/")),
        before,
        "nothing more asked for when it's followed",
      );
      // a server function call: the table's tree is asked for again; Greeting's, which can't have changed, isn't
      await p.evaluate(`document.querySelector("button").click()`);
      assert.equal(
        await p.waitFor<string>(
          `document.querySelector(".visits").textContent`,
          (t) => t === "1 visits",
        ),
        "1 visits",
      );
      assert.equal(
        await p.evaluate<number>(requests(`/__tcm/rpc/${greeting}`)),
        0,
        "Greeting's tree is kept",
      );
      assert.deepEqual(p.errors, []);
    });
  })

uses