1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
import { describe, expect, test } from "bun:test";
import { isNoOp, planTabs, type TabInfo } from "../src/core/plan.ts";
import { projectPurpose } from "../src/core/purpose.ts";
const ROOT = "/home/user/dev";
/** Every path under the root exists unless a test says otherwise. */
function existsAll(): (p: string) => boolean {
return () => true;
}
function existsOnly(paths: string[]): (p: string) => boolean {
const set = new Set(paths);
return (p) => set.has(p);
}
function tab(position: number, name: string, purpose?: string): TabInfo {
return { position, name, purpose };
}
describe("planTabs", () => {
test("creates one tab per discovered project, alphabetically", () => {
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: ["zeta", "alpha", "mid"],
tabs: [],
pathExists: existsAll(),
});
expect(plan.create.map((c) => c.relPath)).toEqual(["alpha", "mid", "zeta"]);
expect(plan.close).toHaveLength(0);
});
// THE property. If this ever fails, the tool is worse than useless.
test("is a no-op when every project already has its tab", () => {
const projects = ["alpha", "mid", "zeta"];
const tabs = projects.map((p, i) => tab(i, p, projectPurpose(p)));
const plan = planTabs({
root: ROOT,
rootExists: true,
projects,
tabs,
pathExists: existsAll(),
});
expect(isNoOp(plan)).toBe(true);
expect(plan.keep).toHaveLength(3);
});
test("applying the plan twice converges (second run is a no-op)", () => {
const projects = ["alpha", "beta"];
let tabs: TabInfo[] = [];
const first = planTabs({ root: ROOT, rootExists: true, projects, tabs, pathExists: existsAll() });
// simulate applying it
tabs = first.create.map((c, i) => tab(i, c.relPath, c.purpose));
const second = planTabs({ root: ROOT, rootExists: true, projects, tabs, pathExists: existsAll() });
expect(first.create).toHaveLength(2);
expect(isNoOp(second)).toBe(true);
});
test("creates only what is missing", () => {
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: ["alpha", "beta"],
tabs: [tab(0, "alpha", projectPurpose("alpha"))],
pathExists: existsAll(),
});
expect(plan.create.map((c) => c.relPath)).toEqual(["beta"]);
expect(plan.keep).toHaveLength(1);
});
test("closes a tab whose project directory is gone", () => {
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: ["alpha"],
tabs: [tab(0, "alpha", projectPurpose("alpha")), tab(1, "deleted", projectPurpose("deleted"))],
pathExists: existsOnly([`${ROOT}/alpha`]),
});
expect(plan.close.map((t) => t.name)).toEqual(["deleted"]);
});
// The mount guard: an unmounted root looks exactly like mass deletion.
test("closes NOTHING when the root itself is missing", () => {
const plan = planTabs({
root: ROOT,
rootExists: false,
projects: [],
tabs: [
tab(0, "alpha", projectPurpose("alpha")),
tab(1, "beta", projectPurpose("beta")),
],
pathExists: existsOnly([]),
});
expect(plan.close).toHaveLength(0);
expect(plan.keep).toHaveLength(2);
});
test("keeps an existing project that simply was not discovered (nested / marker removed)", () => {
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: [],
tabs: [tab(0, "nested", projectPurpose("group/nested"))],
pathExists: existsOnly([`${ROOT}/group/nested`]),
});
expect(plan.close).toHaveLength(0);
expect(plan.keep).toHaveLength(1);
});
test("never touches tabs without a project purpose", () => {
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: ["alpha"],
tabs: [tab(0, "scratch"), tab(1, "notes", "human:notes")],
pathExists: existsOnly([`${ROOT}/alpha`]),
});
expect(plan.close).toHaveLength(0);
expect(plan.foreign.map((t) => t.name)).toEqual(["scratch", "notes"]);
expect(plan.create.map((c) => c.relPath)).toEqual(["alpha"]);
});
// A purpose that had to be truncated+hashed cannot be turned back into a
// path, so we must not "verify" it and close the tab.
test("keeps a tab whose purpose cannot be reconstructed", () => {
const long = "a".repeat(120);
const plan = planTabs({
root: ROOT,
rootExists: true,
projects: [],
tabs: [tab(0, "long", projectPurpose(long))],
pathExists: existsOnly([]), // nothing exists
});
expect(plan.close).toHaveLength(0);
});
});
|