testEachBranchHasItsOwnViews
Each branch has its own views (its definitions as files), as each git worktree has its own checkout, and nothing
reads another's: the codebase's own branch's are in .tcm/src, a session's in its directory (written as something
needs them, a stack trace say), and a branch only read (--branch) is rendered, nothing written. What a session
changed, code and a comment alone, is what search, view and at with --branch see from outside it; stack
traces of the same code point at each branch's own views; and main's views, as an editor reads them, are untouched.
import { okIn, plainEnv, strip, tcmBin, tcmIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testEachBranchHasItsOwnViews = () => { const dir = tempDir("views per branch"); const tcm = (...args: string[]) => okIn(dir, ...args); const on = (...args: string[]) => { const r = spawnSync(process.execPath, [tcmBin(), ...args], { cwd: dir, encoding: "utf8", env: { ...plainEnv(), TCM_BRANCH: "session/s" }, }); return { status: r.status, out: strip(r.stdout + r.stderr) }; }; const okOn = (...args: string[]) => { const r = on(...args); if (r.status !== 0) throw new Error(`tcm ${args.join(" ")} (on session/s) exited ${r.status}:\n${r.out}`); return r.out; }; const write = (file: string, text: string) => fs.writeFileSync(path.join(dir, file), text); const main = path.join(dir, ".tcm", "src"); const own = path.join(dir, ".tcm", "sessions", "session_s", "src"); const files = (d: string) => fs.existsSync(d) ? (fs.readdirSync(d, { recursive: true }) as string[]) .filter((f) => fs.statSync(path.join(d, f)).isFile()) .sort() .map((f) => [f, fs.readFileSync(path.join(d, f), "utf8")]) : []; tcm("init"); fs.rmSync(path.join(dir, "scratch.ts")); write( "lib.ts", `// tcm: namespace lib export const square = (x: number): number => x * x; export const area = (side: number): number => square(side); /** Halves. */ export function half(n: number): number { // a number, halved return n / 2; } export function boom(): number { throw new Error("boom"); } `, ); tcm("add", "lib.ts"); tcm("branch", "session/s"); const mainViews = files(main); assert.match(fs.readFileSync(path.join(main, "lib", "area.ts"), "utf8"), /square\(side\)/); // the session: code (area uses a new definition) and a comment alone (half reads differently, the same hash) write( "s.ts", `// tcm: namespace lib export const cube = (x: number): number => x * x * x; export const area = (side: number): number => cube(side) / side; /** Halves. */ export function half(n: number): number { // the other half's gone return n / 2; } `, ); okOn("update", "./s.ts"); assert.match(okOn("history", "1"), /lib\.half +#[a-z2-7]+ reads differently/); const ownViews = files(own); // read from outside the session (the plain CLI, on main): the session's text, never main's files const theirs = ["--branch", "session/s"]; assert.match(tcm("search", "cube", ...theirs), /lib\.area/); assert.match(tcm("search", "other half", ...theirs), /lib\.half/); assert.doesNotMatch(tcm("search", "a number, halved", ...theirs), /lib\.half/); assert.match( tcm("view", "lib.area", "lib.half", ...theirs), /cube\(side\)[\s\S]*the other half's gone/, ); const sessionView = path.join(".tcm", "sessions", "session_s", "src", "lib", "half.ts"); assert.match(tcm("at", `${sessionView}:3`, ...theirs), /^lib\.half /m); assert.match( tcm("at", path.join(".tcm", "sessions", "session_s", "src", "lib", "cube.ts"), ...theirs), /^lib\.cube /m, ); // another branch's view is that branch's to say const elsewhere = tcmIn(dir, "at", sessionView); assert.notEqual(elsewhere.status, 0); assert.match(elsewhere.out, /a view of session\/s's definitions[\s\S]*--branch session\/s/); const mains = tcmIn(dir, "at", path.join(".tcm", "src", "lib", "half.ts"), ...theirs); assert.notEqual(mains.status, 0); assert.match(mains.out, /a view of main's definitions, not session\/s's/); // and reading wrote nothing of the session's assert.deepEqual(files(own), ownViews); // a stack trace on the session points at its own views, written for it; main's, of the same code, at main's const trace = (out: string, views: string) => assert.ok(out.includes(`at boom (${path.join(views, "lib", "boom.ts")}:`), out); trace(on("run", "lib.boom").out, own); assert.match( fs.readFileSync(path.join(own, "lib", "boom.ts"), "utf8"), /throw new Error\("boom"\)/, ); trace(tcmIn(dir, "run", "lib.boom").out, main); trace(on("run", "lib.boom").out, own); // main as it was: its own text, and its views as the editor reads them assert.doesNotMatch(tcm("search", "cube"), /lib\.area/); assert.match(tcm("search", "a number, halved"), /lib\.half/); assert.match(tcm("at", path.join(".tcm", "src", "lib", "half.ts")), /^lib\.half /m); assert.deepEqual(files(main), mainViews); assert.match(fs.readFileSync(path.join(main, "lib", "half.ts"), "utf8"), /a number, halved/); assert.ok(!fs.existsSync(path.join(main, "lib", "cube.ts"))); }