Skip to content
78 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { MAX_PAGE_STATE, cleanCover, cleanIcon, cleanSource, cleanTarget, cleanTitle, decodeCursor, depthOf, encodeCursor, listLimit, pageState, sharedTops, subtreeHeight, treeNodes } from "./list.ts";
5
6test("a page carries the saved document as base64, and none when there is none or it is too large", () => {
7 assert.equal(pageState(null), null);
8 assert.equal(pageState(new Uint8Array(0).buffer), null);
9 const bytes = new Uint8Array(70_000).map((_, i) => i % 251);
10 const carried = pageState(bytes.buffer);
11 assert.ok(carried);
12 assert.deepEqual(Uint8Array.from(atob(carried!), (c) => c.charCodeAt(0)), bytes);
13 // D1 may hand a BLOB back as an array of numbers.
14 assert.equal(pageState([104, 105]), btoa("hi"));
15 assert.equal(pageState(new Uint8Array(MAX_PAGE_STATE + 1).buffer), null);
16});
17
18test("titles, icons, covers and sources are cleaned", () => {
19 assert.equal(cleanTitle(" Q4 roadmap \n"), "Q4 roadmap");
20 assert.equal(cleanTitle("x".repeat(300)).length, 200);
21 assert.equal(cleanIcon(""), null);
22 assert.equal(cleanIcon("📐 extra words"), "📐 ex");
23 assert.equal(cleanCover("gradient:3"), "gradient:3");
24 assert.equal(cleanCover("javascript:alert(1)"), null);
25 assert.deepEqual(cleanSource({ title: "Launch thread", href: "/acme/-/chat/general?thread=1" }), { title: "Launch thread", href: "/acme/-/chat/general?thread=1" });
26 assert.equal(cleanSource({ title: "x", href: "https://evil.example" }), null);
27 assert.equal(cleanSource({ title: "x", href: "//evil.example" }), null);
28 assert.deepEqual(cleanTarget({ kind: "section", heading: " Risks " }), { kind: "section", heading: "Risks" });
29 assert.equal(cleanTarget({ kind: "nope" }), null);
30});
31
32test("the cursor goes there and back, and nonsense is no cursor", () => {
33 const c = { k: "2026-10-09T03:00:00.000Z", id: "fol_01jb2k7x9hfq0b3zj0f5s2m8ra" };
34 assert.deepEqual(decodeCursor(encodeCursor(c)), c);
35 assert.equal(decodeCursor("not a cursor"), null);
36 assert.equal(decodeCursor(null), null);
37 assert.equal(listLimit(undefined), 30);
38 assert.equal(listLimit(500), 100);
39 assert.equal(listLimit(0), 30);
40});
41
42test("a tree puts a row under its parent only when the parent is shown", () => {
43 const rows = [
44 { id: "b", kind: "doc" as const, parent_id: "a", position: 2, title: "B", icon: null, inherit: 1 },
45 { id: "a", kind: "doc" as const, parent_id: null, position: 1, title: "A", icon: null, inherit: 1 },
46 { id: "c", kind: "doc" as const, parent_id: "hidden", position: 3, title: "C", icon: null, inherit: 0 },
47 ];
48 const tree = treeNodes(rows, new Set(["b"]));
49 assert.deepEqual(
50 tree.map((n) => [n.id, n.parent_id, n.restricted, n.stale ?? false]),
51 [
52 ["a", null, false, false],
53 ["b", "a", false, true],
54 ["c", null, true, false],
55 ],
56 );
57 assert.equal(tree[0]!.slug, "a-a");
58});
59
60test("shared tops leave out children of what is shown and anything shown elsewhere", () => {
61 const readable = [
62 { id: "top", parent_id: "secret" },
63 { id: "kid", parent_id: "top" },
64 { id: "loose", parent_id: null },
65 { id: "inspace", parent_id: null },
66 { id: "underspace", parent_id: "spacedoc" },
67 ];
68 assert.deepEqual(
69 sharedTops(readable, new Set(["inspace", "spacedoc"])).map((r) => r.id),
70 ["top", "loose"],
71 );
72});
73
74test("depth and height come from paths", () => {
75 assert.equal(depthOf("/a/"), 1);
76 assert.equal(depthOf("/a/b/c/"), 3);
77 assert.equal(subtreeHeight({ path: "/a/b/" }, [{ path: "/a/b/" }, { path: "/a/b/c/" }, { path: "/a/b/c/d/" }]), 2);
78});