g1t/apps/sudo/app/lib/nav.test.ts
| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { NAV, holdsCurrent, isCurrent, navItems, soonFor, soonItems } from "./nav.ts"; |
| 5 | |
| 6 | test("the sidebar: two top-level links, then sections that fold", () => { |
| 7 | assert.deepEqual( |
| 8 | NAV.map((group) => group.title), |
| 9 | [null, "Customers", "Revenue", "Platform", "Support", "Team"], |
| 10 | ); |
| 11 | assert.deepEqual(NAV[0].items.map((item) => item.to), ["/", "/reach-out"]); |
| 12 | }); |
| 13 | |
| 14 | test("no section holds a single page, and every section has an icon", () => { |
| 15 | for (const group of NAV.filter((group) => group.title)) { |
| 16 | assert.ok(group.items.length >= 2, `${group.title} has more than one page`); |
| 17 | assert.ok(group.icon, `${group.title} has an icon`); |
| 18 | } |
| 19 | }); |
| 20 | |
| 21 | test("the section holding the current page is the one drawn open", () => { |
| 22 | const open = (path: string) => NAV.filter((group) => group.title && holdsCurrent(group, path)).map((group) => group.title); |
| 23 | assert.deepEqual(open("/workspaces/acme"), ["Customers"]); |
| 24 | assert.deepEqual(open("/stripe"), ["Platform"]); |
| 25 | assert.deepEqual(open("/audit"), ["Team"]); |
| 26 | assert.deepEqual(open("/"), []); |
| 27 | assert.deepEqual(open("/reach-out"), []); |
| 28 | }); |
| 29 | |
| 30 | test("every item has its own path, a label and a line about it", () => { |
| 31 | const paths = navItems().map((item) => item.to); |
| 32 | assert.equal(new Set(paths).size, paths.length); |
| 33 | for (const item of navItems()) { |
| 34 | assert.match(item.to, /^\/[a-z-]*$/, item.label); |
| 35 | assert.ok(item.label.length > 0); |
| 36 | assert.ok(item.about.length > 10, `${item.label} says what it is`); |
| 37 | } |
| 38 | }); |
| 39 | |
| 40 | test("the built pages are not marked soon", () => { |
| 41 | const built = navItems().filter((item) => !item.soon).map((item) => item.to); |
| 42 | assert.deepEqual(built, ["/", "/reach-out", "/workspaces", "/enterprises", "/invoices", "/stripe", "/audit"]); |
| 43 | }); |
| 44 | |
| 45 | test("every soon page says what it will do, why, and what it will have", () => { |
| 46 | const soon = soonItems(); |
| 47 | assert.ok(soon.length >= 9); |
| 48 | for (const item of soon) { |
| 49 | assert.ok(item.soon.summary.length >= 1 && item.soon.summary.length <= 4, item.label); |
| 50 | assert.ok(item.soon.plans.length >= 3 && item.soon.plans.length <= 6, item.label); |
| 51 | if (item.soon.meanwhile?.to) assert.match(item.soon.meanwhile.to, /^\//); |
| 52 | } |
| 53 | }); |
| 54 | |
| 55 | test("view as customer promises an audit and a time limit", () => { |
| 56 | const page = soonFor("/view-as"); |
| 57 | assert.ok(page); |
| 58 | const words = page.soon.summary.join(" "); |
| 59 | assert.match(words, /audited/); |
| 60 | assert.match(words, /time-limited/); |
| 61 | }); |
| 62 | |
| 63 | test("soonFor finds a placeholder by its path, and nothing else", () => { |
| 64 | assert.equal(soonFor("/prices")?.label, "Plans & prices"); |
| 65 | assert.equal(soonFor("/prices/")?.label, "Plans & prices"); |
| 66 | assert.equal(soonFor("/invoices"), null); |
| 67 | assert.equal(soonFor("/workspaces"), null); |
| 68 | assert.equal(soonFor("/nope"), null); |
| 69 | }); |
| 70 | |
| 71 | test("isCurrent: an item stays lit on the pages beneath it; / only on itself", () => { |
| 72 | const overview = { to: "/" }; |
| 73 | const workspaces = { to: "/workspaces" }; |
| 74 | assert.ok(isCurrent(overview, "/")); |
| 75 | assert.ok(!isCurrent(overview, "/workspaces")); |
| 76 | assert.ok(isCurrent(workspaces, "/workspaces")); |
| 77 | assert.ok(isCurrent(workspaces, "/workspaces/acme")); |
| 78 | assert.ok(isCurrent(workspaces, "/workspaces/")); |
| 79 | assert.ok(!isCurrent(workspaces, "/workspaces-old")); |
| 80 | assert.ok(isCurrent({ to: "/x", also: ["/y"] }, "/y/z")); |
| 81 | }); |