testRolesAndHostProtocols
A package's manifest gives its definitions roles, known by their hashes: a component with the stylesheet role is
linked to as <Stylesheet> is (the browser is sent the file, not its text). And it says the version of tcm's host
protocol its code speaks: a tcm that doesn't serve that version won't build an app using it.
import { okIn, tcmIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testRolesAndHostProtocols = () => { const dir = tempDir("roles"); const tcm = (...args: string[]) => okIn(dir, ...args); const write = (file: string, text: string) => fs.writeFileSync(path.join(dir, file), text); tcm("init"); fs.rmSync(path.join(dir, "scratch.ts")); write("styles.css", ".app { color: rebeccapurple; }\n"); tcm("add", "styles.css", "--into", "site"); const ui = (host: number) => `// tcm: namespace ui\nexport const manifest = { name: "ui", npm: false, host: ${host}, roles: { Sheet: "stylesheet" } };\n/** Its look, as text or as the file it's linked to. */\nexport const Sheet = ({ css }: { css: string }) => <style>{css}</style>;\n`; write("ui.tsx", ui(2)); tcm("add", "ui.tsx"); write( "site.tsx", `// tcm: namespace site\nimport { useState } from "react";\nimport { styles } from "#codebase/site";\nimport { Sheet } from "#codebase/ui";\nexport const App = () => {\n const [n, setN] = useState(0);\n return <div className="app"><Sheet css={styles} /><button onClick={() => setN(n + 1)}>{n}</button></div>;\n};\n`, ); tcm("add", "site.tsx"); // a host protocol this tcm doesn't serve: not built const refused = tcmIn(dir, "build", "site.App", "--static", "out"); assert.notEqual(refused.status, 0); assert.match( refused.out, /ui \(ui\) speaks version 2 of tcm's host protocol, and this tcm serves 1: use a tcm that serves it/, ); // one it serves: built, the stylesheet given to the role's component sent as a file the browser links to write("ui.tsx", ui(1)); tcm("update", "ui.tsx"); tcm("build", "site.App", "--static", "out"); const assets = path.join(dir, "out", "_app", "assets"); const files = fs.readdirSync(assets); const js = files .filter((f) => f.endsWith(".js")) .map((f) => fs.readFileSync(path.join(assets, f), "utf8")); assert.ok( js.some( (t) => /styles-[a-z2-7]{8}\.css, linked to \*\//.test(t) || /styles\.css, linked to \*\//.test(t), ), files.join(", "), ); assert.ok( !js.some((t) => t.includes("rebeccapurple")), "the browser's code holds no stylesheet text", ); const css = files.find((f) => /^styles-[a-z2-7]{8}\.css$/.test(f)); assert.ok(css, files.join(", ")); assert.match(fs.readFileSync(path.join(assets, css!), "utf8"), /rebeccapurple/); }