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
|
import { describe, expect, test } from "bun:test";
import { checkFragment, report, worstLevel, type Facts } from "../src/commands/doctor.ts";
function facts(overrides: Partial<Facts> = {}): Facts {
return {
sl0ppijVersion: "sl0ppij 0.45.0+sl0p1",
piVersion: "0.84.1",
configDir: "/home/u/.config/sl0ppij",
configFile: true,
plugins: [
{ name: "pane-finder.wasm", present: true },
{ name: "zjstatus.wasm", present: true },
],
registry: { dev: "/home/u/dev" },
liveSessions: ["dev"],
roots: [{ session: "dev", root: "/home/u/dev", exists: true, projects: 3 }],
fragments: [],
externalTools: [
{ name: "python3", present: true, neededBy: "pir" },
{ name: "fzf", present: true, neededBy: "pir" },
],
...overrides,
};
}
describe("report", () => {
test("a healthy install has nothing to say", () => {
expect(worstLevel(report(facts()))).toBe("ok");
});
test("a missing sl0ppij is fatal, a missing pi is not", () => {
expect(worstLevel(report(facts({ sl0ppijVersion: undefined })))).toBe("fail");
expect(worstLevel(report(facts({ piVersion: undefined })))).toBe("warn");
});
test("missing plugins warn, because the shipped layout needs them", () => {
const checks = report(
facts({ plugins: [{ name: "zjstatus.wasm", present: false }] }),
);
const check = checks.find((c) => c.label.includes("zjstatus"));
expect(check?.level).toBe("warn");
});
// The known packaging hole: pir ships without its runtime deps.
test("warns about pir's undeclared dependencies", () => {
const checks = report(
facts({
externalTools: [
{ name: "python3", present: false, neededBy: "pir" },
{ name: "fzf", present: true, neededBy: "pir" },
],
}),
);
expect(checks.find((c) => c.label === "python3")?.level).toBe("warn");
expect(checks.find((c) => c.label === "fzf")).toBeUndefined(); // present: silent
});
// A missing root is the unmounted-filesystem case, which must never read as
// "delete everything".
test("a missing root warns and suggests mounting before removing", () => {
const checks = report(
facts({ roots: [{ session: "dev", root: "/mnt/dev", exists: false, projects: 0 }] }),
);
const check = checks.find((c) => c.label === "root dev");
expect(check?.level).toBe("warn");
expect(check?.fix).toContain("mount");
});
test("reports whether each root's session is running", () => {
const running = report(facts()).find((c) => c.label === "root dev");
expect(running?.detail).toContain("session live");
const stopped = report(facts({ liveSessions: [] })).find((c) => c.label === "root dev");
expect(stopped?.detail).toContain("not running");
});
test("surfaces a broken fragment", () => {
const checks = report(facts({ fragments: [{ relPath: "alpha", issue: "unbalanced braces" }] }));
expect(checks.find((c) => c.label === "layout alpha")?.level).toBe("warn");
});
});
describe("checkFragment", () => {
test("accepts a plain fragment", () => {
expect(checkFragment('pane purpose="agent:main"')).toBeUndefined();
});
test("accepts nested panes", () => {
expect(checkFragment('pane split_direction="vertical" {\n pane\n pane\n}')).toBeUndefined();
});
test("treats a comments-only file as empty", () => {
expect(checkFragment("// nothing here\n")).toContain("empty");
});
test("catches unbalanced braces", () => {
expect(checkFragment("pane {\n pane\n")).toContain("unbalanced");
});
// The two shapes people will actually get wrong, because every zellij example
// online is a whole layout.
test("catches a whole layout pasted in as a fragment", () => {
expect(checkFragment("layout {\n tab {\n pane\n }\n}")).toContain("pane nodes only");
});
test("catches a tab node, which sl0ppi owns", () => {
expect(checkFragment('tab name="x" {\n pane\n}')).toContain("identity");
});
test("ignores braces inside comments", () => {
expect(checkFragment('pane\n// pane split_direction="v" {')).toBeUndefined();
});
});
|