Public: anyone can read this; the crew can change it.
A repository's about column draw §5's card, and .about leave the markup #4248
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4144-a-repository-s-about-column-draw-5-s-car → main 2 commits fork 1 thread · 1 unresolved
2 files
changed +83
−0
test/repo-about-card.test.ts added +71
| @@ -0,0 +1,71 @@ | ||
| 1 | /** | |
| 2 | * A repository's About column is §5's card, not `.about` (#4144): the old | |
| 3 | * name is gone from the markup, and every word, link and form stays. | |
| 4 | */ | |
| 5 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 6 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 7 | import { tmpdir } from "node:os"; | |
| 8 | import { join } from "node:path"; | |
| 9 | import { createRepo } from "../src/actions/repos.ts"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { createUser } from "../src/core/users.ts"; | |
| 12 | import { repoDir } from "../src/git/repos.ts"; | |
| 13 | import { commitFiles } from "../src/git/write.ts"; | |
| 14 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 15 | import { quietLogs } from "./helpers/globals.ts"; | |
| 16 | ||
| 17 | quietLogs(); | |
| 18 | ||
| 19 | let dataDir: string; | |
| 20 | let app: App; | |
| 21 | let servers: Servers; | |
| 22 | ||
| 23 | beforeAll(async () => { | |
| 24 | dataDir = await mkdtemp(join(tmpdir(), "forge-about-card-")); | |
| 25 | app = createApp({ dataDir, port: 0, host: "forge.test" }); | |
| 26 | servers = await serve(app); | |
| 27 | const made = await createUser(app.db, { | |
| 28 | origin: "seed", | |
| 29 | trusted: true, | |
| 30 | username: "olive", | |
| 31 | email: "[email protected]", | |
| 32 | password: "a-good-passphrase", | |
| 33 | adminIfFirst: true, | |
| 34 | }); | |
| 35 | if (!made.ok) throw new Error(made.error); | |
| 36 | const r = await createRepo(app, made.value, { | |
| 37 | name: "yard", | |
| 38 | visibility: "public", | |
| 39 | description: "a small yard", | |
| 40 | }); | |
| 41 | if (!r.ok) throw new Error(String(r.error)); | |
| 42 | const c = await commitFiles(repoDir(app.reposRoot, "olive", "yard"), { | |
| 43 | branch: "main", | |
| 44 | message: "first", | |
| 45 | files: [{ path: "README.md", content: "# yard\n" }], | |
| 46 | author: { name: "olive", email: "[email protected]" }, | |
| 47 | }); | |
| 48 | if (!c.ok) throw new Error("no commit"); | |
| 49 | }); | |
| 50 | ||
| 51 | afterAll(async () => { | |
| 52 | await servers.stop(); | |
| 53 | await rm(dataDir, { recursive: true, force: true }); | |
| 54 | }); | |
| 55 | ||
| 56 | test("the About column is a card with §5 rows and no .about", async () => { | |
| 57 | const r = await fetch(`${servers.url}/olive/yard`); | |
| 58 | expect(r.status).toBe(200); | |
| 59 | const html = await r.text(); | |
| 60 | expect(html).not.toMatch(/class="[^"]*\babout\b/); | |
| 61 | expect(html).not.toContain("<dl"); | |
| 62 | const rail = html.match(/<aside[^>]*>[\s\S]*?<\/aside>/)?.[0] ?? ""; | |
| 63 | expect(rail).toContain('class="card"'); | |
| 64 | expect(rail).toContain("<h2>About</h2>"); | |
| 65 | expect(rail).toContain("a small yard"); | |
| 66 | for (const word of ["Visibility", "Default", "Protection", "Branches"]) | |
| 67 | expect(rail).toContain(word); | |
| 68 | expect(rail).toContain("Clone over HTTPS"); | |
| 69 | expect(rail).toContain("olive/yard.git"); | |
| 70 | expect(rail).toContain("Report it sealed"); | |
| 71 | }); |
test/web-browse.test.ts +12
| @@ -271,6 +271,18 @@ describe("browsing an origin repository on the web", () => { | ||
| 271 | 271 | expect(html).toContain(`Open on ${new URL(fake.url).host}`); |
| 272 | 272 | }); |
| 273 | 273 | |
| 274 | test("the About rail is a card: no .about, same words, same mirror form", async () => { | |
| 275 | const html = await bodyOf(alice, browse); | |
| 276 | expect(html).not.toMatch(/class="[^"]*\babout\b/); | |
| 277 | expect(html).not.toContain("<dl"); | |
| 278 | const rail = html.match(/<aside[^>]*>[\s\S]*?<\/aside>/)?.[0] ?? ""; | |
| 279 | expect(rail).toContain("<h2>About</h2>"); | |
| 280 | for (const word of ["Where it lives", "Origin", "Last push", "Visibility", "Default"]) | |
| 281 | expect(rail).toContain(word); | |
| 282 | expect(rail).toContain(`action="${browse}/mirror"`); | |
| 283 | expect(rail).toContain("Mirror it"); | |
| 284 | }); | |
| 285 | ||
| 274 | 286 | test("a live browse never writes to the repository it is reading", async () => { |
| 275 | 287 | for (const path of [ |
| 276 | 288 | browse, |
new above
offline
The first <aside> is matched. Anchor on aria-label="About" so another aside cannot satisfy this.