import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { ROOT_GROUP, UNTAGGED_GROUP, UNMATCHED_GROUP, accentScale, queryGroupColor, queryOrder, defaultGraphPrefs, folderOf, groupColor, groupNodes, graphPalette, normalizeGraphPrefs, } from "../client/graphPrefs.ts"; describe("graph preferences", () => { it("groups by folder at one or two levels, with the root as its own bucket", () => { assert.equal(folderOf("a/b/c/note.md", 1), "a"); assert.equal(folderOf("a/b/c/note.md", 2), "a/b"); assert.equal(folderOf("note.md", 1), ROOT_GROUP); const { groups, of } = groupNodes( [ { id: "essays/one.md", title: "one", links: 1, tags: [] }, { id: "essays/two.md", title: "two", links: 0, tags: [] }, { id: "daily/x.md", title: "x", links: 2, tags: [] }, { id: "loose.md", title: "loose", links: 0, tags: [] }, ], "folder", 1, ); assert.deepEqual(groups.map((g) => [g.name, g.count]), [["essays", 2], ["daily", 1], [ROOT_GROUP, 1]]); assert.equal(of.get("loose.md"), ROOT_GROUP); }); it("puts a many-tagged note under its most common tag (ties to the first written), and the tagless under one bucket", () => { const { groups, of } = groupNodes( [ { id: "a.md", title: "a", links: 1, tags: ["physics", "draft"] }, { id: "b.md", title: "b", links: 1, tags: ["physics"] }, { id: "c.md", title: "c", links: 1, tags: ["history", "draft"] }, { id: "d.md", title: "d", links: 1, tags: [] }, ], "tag", 1, ); assert.equal(of.get("a.md"), "physics"); assert.equal(of.get("c.md"), "draft"); assert.equal(of.get("d.md"), UNTAGGED_GROUP); assert.equal(groups[0].name, "physics"); }); it("colours the biggest groups from the palette, the rest by a stable hue, and honours overrides", () => { const dark = graphPalette(true); assert.equal(groupColor("essays", 0, {}, true, "#ccc"), dark[0]); assert.equal(groupColor("essays", 0, { essays: "#123456" }, true, "#ccc"), "#123456"); assert.equal(groupColor(ROOT_GROUP, 0, {}, true, "#ccc"), "#ccc"); const thirteenth = groupColor("thirteenth", 12, {}, true, "#ccc"); assert.match(thirteenth, /^#[0-9a-f]{6}$/); assert.equal(thirteenth, groupColor("thirteenth", 12, {}, true, "#ccc")); assert.notEqual(thirteenth, groupColor("fourteenth", 13, {}, true, "#ccc")); }); it("normalises anything back into a valid preferences object", () => { assert.deepEqual(normalizeGraphPrefs(null), defaultGraphPrefs()); const p = normalizeGraphPrefs({ colorBy: "tag", folderDepth: 7, groupColors: { tag: { physics: "#ABCDEF", bad: "red" } }, hiddenGroups: { tag: ["draft", 3] }, minLinks: 99, forces: { repulsion: 100, linkDistance: "x", gravity: -1 }, display: { nodeScale: 0.1, glow: false }, }); assert.equal(p.colorBy, "tag"); assert.equal(p.folderDepth, 1); assert.deepEqual(p.groupColors.tag, { physics: "#abcdef" }); assert.deepEqual(p.hiddenGroups.tag, ["draft"]); assert.equal(p.minLinks, 50); assert.equal(p.forces.repulsion, 2.5); assert.equal(p.forces.linkDistance, 235); assert.equal(p.forces.gravity, 0); assert.equal(p.display.nodeScale, 0.6); assert.equal(p.display.glow, false); }); }); describe("tag gatherings", () => { const nodes = [ { id: "a.md", title: "a", links: 1, tags: ["raft", "systems"] }, { id: "b.md", title: "b", links: 1, tags: ["paxos"] }, { id: "c.md", title: "c", links: 1, tags: ["poetry", "raft"] }, ]; it("colours every gathered tag as one group, whatever the case or the #", () => { const { of, groups } = groupNodes(nodes, "tag", 1, { pick: "common", gatherings: [{ name: "distributed", tags: "#Raft, paxos" }], }); assert.equal(of.get("a.md"), "distributed"); assert.equal(of.get("b.md"), "distributed"); assert.equal(of.get("c.md"), "distributed"); assert.equal(groups[0].name, "distributed"); assert.equal(groups[0].count, 3); }); it("can take the FIRST tag in the note instead of the most shared", () => { const { of } = groupNodes(nodes, "tag", 1, { pick: "first", gatherings: [] }); assert.equal(of.get("c.md"), "poetry"); const common = groupNodes(nodes, "tag", 1, { pick: "common", gatherings: [] }); assert.equal(common.of.get("c.md"), "raft"); }); it("survives a stored preferences object with junk gatherings", () => { const prefs = normalizeGraphPrefs({ tagPick: "first", tagGroups: [{ name: "x", tags: "a b" }, null, 3, { name: 5 }] }); assert.equal(prefs.tagPick, "first"); assert.deepEqual(prefs.tagGroups, [ { name: "x", tags: "a b" }, { name: "", tags: "" }, ]); }); }); describe("graph groups by query", () => { const nodes = [ { id: "Physics/a.md", title: "a", links: 1, tags: ["physics"] }, { id: "Physics/b.md", title: "b", links: 1, tags: ["physics", "draft"] }, { id: "Kitchen/c.md", title: "c", links: 1, tags: ["recipes"] }, { id: "loose.md", title: "loose", links: 0, tags: [] }, ]; const matches = new Map>([ ["tag:physics", new Set(["Physics/a.md", "Physics/b.md"])], ["tag:draft", new Set(["Physics/b.md"])], ["tag:recipes", new Set(["Kitchen/c.md"])], ]); it("the first query that names a note wins it; the rest is one neutral bucket, in the rows' order", () => { const { groups, of } = groupNodes(nodes, "query", 1, undefined, { groups: [{ query: "tag:draft ", color: null }, { query: "tag:physics", color: null }, { query: "", color: null }, { query: "tag:nothing", color: null }], matches, }); assert.equal(of.get("Physics/b.md"), "tag:draft"); assert.equal(of.get("Physics/a.md"), "tag:physics"); assert.equal(of.get("Kitchen/c.md"), UNMATCHED_GROUP); assert.deepEqual(groups.map((g) => [g.name, g.count]), [["tag:draft", 1], ["tag:physics", 1], ["tag:nothing", 0], [UNMATCHED_GROUP, 2]]); }); it("a query the server has not answered names nothing yet", () => { const { of } = groupNodes(nodes, "query", 1, undefined, { groups: [{ query: "tag:physics", color: null }], matches: new Map() }); assert.equal(of.get("Physics/a.md"), UNMATCHED_GROUP); }); it("keeps the rows' order and drops blanks and repeats", () => { assert.deepEqual(queryOrder([{ query: " b ", color: null }, { query: "", color: null }, { query: "a", color: null }, { query: "b", color: null }]), ["b", "a"]); }); it("the accent scale walks the theme's hue; a set colour wins; the unmatched bucket is neutral", () => { const scale = accentScale("#58a6ff", 6, true); assert.equal(scale.length, 6); assert.equal(new Set(scale).size, 6); assert.ok(scale.every((c) => /^#[0-9a-f]{6}$/.test(c))); // Grey has no hue: still six distinct colours. Not a hex: the palette. assert.equal(new Set(accentScale("#808080", 6, false)).size, 6); assert.deepEqual(accentScale("gold", 3, true), graphPalette(true).slice(0, 3)); const groups = [{ query: "tag:a", color: null }, { query: "tag:b", color: "#123456" }]; assert.equal(queryGroupColor(groups, "tag:a", "#58a6ff", true), scale[0]); assert.equal(queryGroupColor(groups, "tag:b", "#58a6ff", true), "#123456"); assert.equal(groupColor(UNMATCHED_GROUP, 0, {}, true, "#ccc"), "#ccc"); }); it("survives a stored preferences object with junk query rows, capped at six", () => { const prefs = normalizeGraphPrefs({ colorBy: "query", queryGroups: [{ query: "tag:x", color: "#ABCDEF" }, { query: 4, color: "red" }, null, ...Array(8).fill({ query: "q", color: null })] }); assert.equal(prefs.colorBy, "query"); assert.equal(prefs.queryGroups.length, 6); assert.deepEqual(prefs.queryGroups[0], { query: "tag:x", color: "#abcdef" }); assert.deepEqual(prefs.queryGroups[1], { query: "", color: null }); assert.deepEqual(prefs.hiddenGroups.query, []); }); });