repoworkspace.test.ts

5.4 KB · raw

1// Covers §24's selected-repo workspace: the summary, detected and shown languages, where the2// description comes from, and the repo's releases.34import { Database } from "bun:sqlite";5import { beforeAll, describe, expect, test } from "bun:test";6import { mkdtemp, writeFile } from "node:fs/promises";7import { tmpdir } from "node:os";8import { join } from "node:path";9import { openKeprDb } from "../src/server/db.ts";10import { Browser, type Instance, instance } from "./browser.ts";11import { makeRepos, writeRelease } from "./fixture.ts";1213describe("the selected repo's workspace (§24)", () => {14	let inst: Instance;15	let browser: Browser;16	const workspace = async () => (await browser.get("/admin/repos?repo=app")).text();17	const save = (fields: Record<string, string>) =>18		browser.post("/admin/repos/app", { public: "1", sort_order: "0", ...fields });1920	beforeAll(async () => {21		const repos = await makeRepos(["app"]);22		const work = join(repos.bucketDir, "..", "work", "app");23		await writeFile(join(work, "main.ts"), "export const a = 1;\nexport const b = 2;\n");24		await writeFile(join(work, "tool.py"), "print(1)\n");25		repos.koh("app", "save", "add sources");26		repos.koh("app", "offer");27		const head = JSON.parse(repos.koh("app", "view", "--json")).head_id as string;28		for (const version of ["1.0.0", "1.1.0"]) {29			await writeRelease(repos.bucketDir, "app", {30				version,31				save: head,32				files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array([1]) }],33			});34		}35		inst = await instance(repos.bucketDir);36		inst.kepr.run(37			`INSERT INTO issues (repo, number, title, body, type, filed_at)38			 VALUES ('app', 1, 'Broken', 'It broke', 'bug', 0)`,39		);40		browser = new Browser(inst.app);41		await browser.signIn();42		await save({});43		await inst.app.request("/app/releases/1.1.0/linux-x86_64");44		await inst.app.request("/app/releases/1.1.0/linux-x86_64");45	});4647	test("summarises saves, lines, the newest version, open issues and downloads", async () => {48		const page = await workspace();49		expect(page).toContain('class="admin-vitals"');50		expect(page).toMatch(/<strong>2<\/strong><span class="mono">saves<\/span>/);51		expect(page).toMatch(/<strong>3<\/strong><span class="mono">lines<\/span>/);52		expect(page).toMatch(/<strong>1\.1\.0<\/strong><span class="mono">newest version<\/span>/);53		expect(page).toMatch(/<strong>1<\/strong><span class="mono">open issues<\/span>/);54		expect(page).toMatch(/<strong>2<\/strong><span class="mono">downloads<\/span>/);55	});5657	test("lists the detected languages with their lines", async () => {58		const page = await workspace();59		expect(page).toContain('TypeScript <span class="muted">2 lines</span>');60		expect(page).toContain('Python <span class="muted">1 line</span>');61	});6263	test("says where the description comes from and links to its page", async () => {64		expect(await workspace()).toContain("description: from the README");65		inst.kepr.run("INSERT INTO repo_text (repo, description) VALUES ('app', 'Own words')");66		const page = await workspace();67		expect(page).toContain("description: your own words");68		expect(page).toContain('<a href="/app">edit on its page ↗</a>');69		inst.kepr.run("DELETE FROM repo_text");70	});7172	test("lists its releases with visibility and downloads, linking to the Releases section", async () => {73		inst.kepr.run(74			"INSERT INTO release_settings (repo, version, hidden) VALUES ('app', '1.0.0', 1)",75		);76		const page = await workspace();77		expect(page).toContain('href="/admin/releases#app-1.1.0"');78		expect(page).toMatch(/1\.1\.0[\s\S]*?shown · 2 downloads/);79		expect(page).toMatch(/1\.0\.0[\s\S]*?hidden · 0 downloads/);80		inst.kepr.run("DELETE FROM release_settings");81	});8283	test("shown languages replace the header's detected language; blank brings it back", async () => {84		const header = async () => (await inst.app.request("/app")).text();85		expect(await header()).toContain('<span class="lang-badge">TypeScript</span>');86		expect((await save({ languages: " Zig ,  TypeScript" })).status).toBe(303);87		const shown = await header();88		expect(shown).toContain('<span class="lang-badge">Zig</span>');89		expect(shown).toContain('<span class="lang-badge">TypeScript</span>');90		expect(await workspace()).toContain('value="Zig, TypeScript"');91		expect((await save({ languages: "" })).status).toBe(303);92		const detected = await header();93		expect(detected).not.toContain('<span class="lang-badge">Zig</span>');94		expect(detected).toContain('<span class="lang-badge">TypeScript</span>');95	});9697	test("refuses more than five languages, a long name or a control character", async () => {98		const refusals: [string, string][] = [99			["a, b, c, d, e, f", "up to five languages."],100			["x".repeat(31), "a language name is at most 30 characters."],101			["Zig\u0007", "a language name is printable text."],102		];103		for (const [languages, error] of refusals) {104			const response = await save({ languages });105			expect(response.status).toBe(400);106			expect(await response.text()).toContain(error);107		}108		expect(await workspace()).not.toContain('value="Zig');109	});110});111112test("an older kepr.db gains the shown-languages column when opened", async () => {113	const dir = await mkdtemp(join(tmpdir(), "kepr-old-db-"));114	const old = new Database(join(dir, "kepr.db"));115	old.run("CREATE TABLE repos (name TEXT PRIMARY KEY, public INTEGER NOT NULL DEFAULT 0)");116	old.run("INSERT INTO repos (name) VALUES ('app')");117	old.close();118	const db = openKeprDb(dir);119	expect(db.query<{ languages: string }, []>("SELECT languages FROM repos").get()?.languages).toBe(120		"",121	);122});