on 2026.09.23.2004 help sign in
diagonal/test/organize.test.ts
108 lines 5.3 kB history raw
1import { describe, expect, it } from "vitest";
2import { existingGroupsFor, makeBatches, maxGroupsFor, organizable, planFromResult } from "../src/background/organize";
3import { newGroupRecord } from "../src/background/state";
4import { emptyState } from "../src/background/state";
6const t = (n: number, over: { title?: string; description?: string } = {}) => ({
7 id: n, title: over.title ?? `Tab ${n}`, url: `https://site${n}.com/page`, description: over.description,
8});
10describe("batching", () => {
11 it("caps batches at 18 items", () => {
12 const b = makeBatches(Array.from({ length: 60 }, (_, i) => t(i)));
13 expect(b.map((x) => x.length)).toEqual([18, 18, 18, 6]);
14 });
16 it("also respects the character budget", () => {
17 const long = "x".repeat(300);
18 const b = makeBatches(Array.from({ length: 30 }, (_, i) => t(i, { title: long, description: long })), 18, 5000);
19 for (const batch of b) expect(batch.reduce((n, x) => n + 120 + x.url.length + 300 + 12, 0)).toBeLessThanOrEqual(5000 + 500);
20 expect(b.length).toBeGreaterThan(2);
21 expect(b.flat()).toHaveLength(30);
22 });
24 it("maxGroups = min(8, ceil(items / 3))", () => {
25 expect(maxGroupsFor(18)).toBe(6);
26 expect(maxGroupsFor(4)).toBe(2);
27 expect(maxGroupsFor(60)).toBe(8);
28 });
30 it("only takes ungrouped, unpinned web tabs", () => {
31 expect(organizable({ groupId: -1, pinned: false, url: "https://a.com" })).toBe(true);
32 expect(organizable({ groupId: 3, pinned: false, url: "https://a.com" })).toBe(false);
33 expect(organizable({ groupId: -1, pinned: true, url: "https://a.com" })).toBe(false);
34 expect(organizable({ groupId: -1, pinned: false, url: "brave://settings" })).toBe(false);
35 });
36});
38describe("applying a reply", () => {
39 const ids = [100, 101, 102, 103, 104, 105];
40 const color = () => "cyan" as const;
42 it("maps indexes to tabs, joins existing groups, folds singletons into leftovers", () => {
43 const plan = planFromResult(
44 {
45 groups: [
46 { title: "Lisbon trip", emoji: "โœˆ๏ธ", color: "blue", existing: -1, members: [0, 1, 2] },
47 { title: "", emoji: "", color: "", existing: 0, members: [3] },
48 { title: "Lonely one", emoji: "๐Ÿ“š", color: "red", existing: -1, members: [4] },
49 ],
50 leftovers: [],
51 },
52 ids, [555], 2, 8, color,
53 );
54 expect(plan.creates).toEqual([{ tabIds: [100, 101, 102], label: "Lisbon trip", emoji: "โœˆ๏ธ", color: "blue" }]);
55 expect(plan.joins).toEqual([{ groupId: 555, tabIds: [103] }]);
56 expect(plan.leftovers.sort()).toEqual([104, 105]);
57 });
59 it("drops duplicate and out-of-range indexes; every tab ends up exactly once", () => {
60 const plan = planFromResult(
61 { groups: [{ title: "Rust async", emoji: "๐Ÿฆ€", color: "nope", members: [0, 0, 1, 9, -1] }, { title: "Cooking ideas", emoji: "๐Ÿณ", color: "green", members: [1, 2] }], leftovers: [] },
62 ids, [], 2, 8, color,
63 );
64 const all = [...plan.creates.flatMap((c) => c.tabIds), ...plan.joins.flatMap((j) => j.tabIds), ...plan.leftovers];
65 expect(all.sort()).toEqual([...ids].sort());
66 expect(plan.creates[0].color).toBe("cyan"); // invalid colour replaced
67 });
69 it("keeps at most maxGroups new groups, largest first", () => {
70 const plan = planFromResult(
71 { groups: [{ title: "Aa bb", emoji: "", color: "red", members: [0, 1] }, { title: "Cc dd", emoji: "", color: "red", members: [2, 3, 4] }], leftovers: [] },
72 ids, [], 2, 1, color,
73 );
74 expect(plan.creates.map((c) => c.tabIds)).toEqual([[102, 103, 104]]);
75 expect(plan.leftovers.sort()).toEqual([100, 101, 105]);
76 });
78 it("an invalid label keeps the group but leaves naming to the naming loop", () => {
79 const plan = planFromResult({ groups: [{ title: "Tabs", emoji: "๐Ÿฆ€", color: "red", members: [0, 1] }], leftovers: [] }, ids, [], 2, 8, color);
80 expect(plan.creates[0].label).toBeUndefined();
81 });
83 it("honours organizeMinGroupSize", () => {
84 const plan = planFromResult({ groups: [{ title: "Rust async", emoji: "๐Ÿฆ€", color: "red", members: [0, 1] }], leftovers: [] }, ids, [], 3, 8, color);
85 expect(plan.creates).toEqual([]);
86 });
87});
89describe("existing groups", () => {
90 it("lists managed topic groups in the window with sample titles, capped at 12", () => {
91 const s = emptyState();
92 for (let i = 0; i < 15; i++) s.groups[i] = newGroupRecord(i, 1, "organize", "red", { stripTitle: `โœˆ๏ธ Trip ${i}` });
93 s.groups[50] = newGroupRecord(50, 1, "tidy", "grey", { stripTitle: "Parked" });
94 s.groups[51] = newGroupRecord(51, 2, "organize", "grey", { stripTitle: "Other window" });
95 s.tabs[1] = { id: 1, windowId: 1, groupId: 0, index: 0, url: "u", title: "Hotel", pinned: false, createdAt: 0, lastActivatedAt: 0 };
96 const { ids, list } = existingGroupsFor(s, 1);
97 expect(ids).toHaveLength(12);
98 expect(ids).not.toContain(50);
99 expect(ids).not.toContain(51);
100 expect(list[0]).toEqual({ g: 0, title: "Trip 0", samples: ["Hotel"] });
101 });
103 it("groups created by batch n come first for batch n+1", () => {
104 const s = emptyState();
105 for (let i = 0; i < 15; i++) s.groups[i] = newGroupRecord(i, 1, "organize", "red", { stripTitle: `G ${i}` });
106 expect(existingGroupsFor(s, 1, [14]).ids[0]).toBe(14);
107 });
108});