@f0rbit/overview 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/overview +10 -0
- package/dist/overview.js +10361 -0
- package/package.json +22 -15
- package/bunfig.toml +0 -7
- package/packages/core/__tests__/concurrency.test.ts +0 -111
- package/packages/core/__tests__/helpers.ts +0 -60
- package/packages/core/__tests__/integration/git-status.test.ts +0 -62
- package/packages/core/__tests__/integration/scanner.test.ts +0 -140
- package/packages/core/__tests__/ocn.test.ts +0 -164
- package/packages/core/package.json +0 -13
- package/packages/core/src/cache.ts +0 -31
- package/packages/core/src/concurrency.ts +0 -44
- package/packages/core/src/devpad.ts +0 -61
- package/packages/core/src/git-graph.ts +0 -54
- package/packages/core/src/git-stats.ts +0 -201
- package/packages/core/src/git-status.ts +0 -316
- package/packages/core/src/github.ts +0 -286
- package/packages/core/src/index.ts +0 -58
- package/packages/core/src/ocn.ts +0 -74
- package/packages/core/src/scanner.ts +0 -118
- package/packages/core/src/types.ts +0 -199
- package/packages/core/src/watcher.ts +0 -128
- package/packages/core/src/worktree.ts +0 -80
- package/packages/core/tsconfig.json +0 -5
- package/packages/render/bunfig.toml +0 -8
- package/packages/render/jsx-runtime.d.ts +0 -3
- package/packages/render/package.json +0 -18
- package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
- package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -304
- package/packages/render/src/components/git-graph.tsx +0 -127
- package/packages/render/src/components/help-overlay.tsx +0 -108
- package/packages/render/src/components/index.ts +0 -7
- package/packages/render/src/components/repo-list.tsx +0 -127
- package/packages/render/src/components/stats-panel.tsx +0 -116
- package/packages/render/src/components/status-badge.tsx +0 -70
- package/packages/render/src/components/status-bar.tsx +0 -56
- package/packages/render/src/components/widget-container.tsx +0 -286
- package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
- package/packages/render/src/components/widgets/branch-list.tsx +0 -93
- package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
- package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
- package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
- package/packages/render/src/components/widgets/file-changes.tsx +0 -78
- package/packages/render/src/components/widgets/git-status.tsx +0 -125
- package/packages/render/src/components/widgets/github-ci.tsx +0 -98
- package/packages/render/src/components/widgets/github-issues.tsx +0 -101
- package/packages/render/src/components/widgets/github-prs.tsx +0 -119
- package/packages/render/src/components/widgets/github-release.tsx +0 -73
- package/packages/render/src/components/widgets/index.ts +0 -12
- package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
- package/packages/render/src/components/widgets/registry.ts +0 -23
- package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
- package/packages/render/src/config/index.ts +0 -104
- package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
- package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
- package/packages/render/src/lib/actions.ts +0 -68
- package/packages/render/src/lib/fetch-context.ts +0 -102
- package/packages/render/src/lib/filter.ts +0 -94
- package/packages/render/src/lib/format.ts +0 -36
- package/packages/render/src/lib/use-devpad.ts +0 -167
- package/packages/render/src/lib/use-github.ts +0 -75
- package/packages/render/src/lib/widget-grid.ts +0 -204
- package/packages/render/src/lib/widget-state.ts +0 -96
- package/packages/render/src/overview.tsx +0 -16
- package/packages/render/src/screens/index.ts +0 -1
- package/packages/render/src/screens/main-screen.tsx +0 -410
- package/packages/render/src/theme/index.ts +0 -37
- package/packages/render/tsconfig.json +0 -9
- package/tsconfig.json +0 -23
|
@@ -1,665 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import type { WidgetId } from "@overview/core";
|
|
3
|
-
import {
|
|
4
|
-
resolveSpan,
|
|
5
|
-
computeRows,
|
|
6
|
-
buildBorderLine,
|
|
7
|
-
buildBorderLineWithTitle,
|
|
8
|
-
contentWidth,
|
|
9
|
-
getWidgetBorderSides,
|
|
10
|
-
type GridWidget,
|
|
11
|
-
type GridRow,
|
|
12
|
-
} from "../widget-grid";
|
|
13
|
-
|
|
14
|
-
// ── helpers ────────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
function makeWidget(id: WidgetId, span: "full" | "half" | "third" | "auto", enabled = true, collapsed = false, priority = 0): GridWidget {
|
|
17
|
-
return {
|
|
18
|
-
id,
|
|
19
|
-
size_hint: { span, min_height: 2 },
|
|
20
|
-
config: { id, enabled, priority, collapsed },
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function row(columns: 1 | 2 | 3, ...widgets: GridWidget[]): GridRow {
|
|
25
|
-
return { widgets, columns };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ── resolveSpan ────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
describe("resolveSpan", () => {
|
|
31
|
-
test("half at 60 cols → half", () => {
|
|
32
|
-
expect(resolveSpan("half", 60)).toBe("half");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("half at 50 cols → half", () => {
|
|
36
|
-
expect(resolveSpan("half", 50)).toBe("half");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("half at 40 cols → half (boundary)", () => {
|
|
40
|
-
expect(resolveSpan("half", 40)).toBe("half");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("half at 39 cols → full", () => {
|
|
44
|
-
expect(resolveSpan("half", 39)).toBe("full");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("full at any width → full", () => {
|
|
48
|
-
expect(resolveSpan("full", 120)).toBe("full");
|
|
49
|
-
expect(resolveSpan("full", 30)).toBe("full");
|
|
50
|
-
expect(resolveSpan("full", 1)).toBe("full");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("auto at any width → full", () => {
|
|
54
|
-
expect(resolveSpan("auto", 120)).toBe("full");
|
|
55
|
-
expect(resolveSpan("auto", 30)).toBe("full");
|
|
56
|
-
expect(resolveSpan("auto", 1)).toBe("full");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("third at 60 cols → third", () => {
|
|
60
|
-
expect(resolveSpan("third", 60)).toBe("third");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("third at 59 cols → half (cascading fallback)", () => {
|
|
64
|
-
expect(resolveSpan("third", 59)).toBe("half");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("third at 40 cols → half", () => {
|
|
68
|
-
expect(resolveSpan("third", 40)).toBe("half");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("third at 39 cols → full (double fallback)", () => {
|
|
72
|
-
expect(resolveSpan("third", 39)).toBe("full");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("third at 100 cols → third", () => {
|
|
76
|
-
expect(resolveSpan("third", 100)).toBe("third");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ── computeRows ────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
describe("computeRows", () => {
|
|
83
|
-
test("two half-width widgets at wide panel → 1 two-column row", () => {
|
|
84
|
-
const widgets = [
|
|
85
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
86
|
-
makeWidget("repo-meta", "half", true, false, 1),
|
|
87
|
-
];
|
|
88
|
-
const rows = computeRows(widgets, 80);
|
|
89
|
-
|
|
90
|
-
expect(rows).toHaveLength(1);
|
|
91
|
-
expect(rows[0].columns).toBe(2);
|
|
92
|
-
expect(rows[0].widgets).toHaveLength(2);
|
|
93
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
94
|
-
expect(rows[0].widgets[1].id).toBe("repo-meta");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("half + full → sorted by priority: half(p0) before full(p1)", () => {
|
|
98
|
-
const widgets = [
|
|
99
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
100
|
-
makeWidget("recent-commits", "full", true, false, 1),
|
|
101
|
-
];
|
|
102
|
-
const rows = computeRows(widgets, 80);
|
|
103
|
-
|
|
104
|
-
expect(rows).toHaveLength(2);
|
|
105
|
-
expect(rows[0].columns).toBe(1);
|
|
106
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
107
|
-
expect(rows[1].columns).toBe(1);
|
|
108
|
-
expect(rows[1].widgets[0].id).toBe("recent-commits");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("full + half + half → full(p0) first, half pair(p1) second", () => {
|
|
112
|
-
const widgets = [
|
|
113
|
-
makeWidget("recent-commits", "full", true, false, 0),
|
|
114
|
-
makeWidget("git-status", "half", true, false, 1),
|
|
115
|
-
makeWidget("repo-meta", "half", true, false, 2),
|
|
116
|
-
];
|
|
117
|
-
const rows = computeRows(widgets, 80);
|
|
118
|
-
|
|
119
|
-
expect(rows).toHaveLength(2);
|
|
120
|
-
expect(rows[0].columns).toBe(1);
|
|
121
|
-
expect(rows[0].widgets[0].id).toBe("recent-commits");
|
|
122
|
-
expect(rows[1].columns).toBe(2);
|
|
123
|
-
expect(rows[1].widgets[0].id).toBe("git-status");
|
|
124
|
-
expect(rows[1].widgets[1].id).toBe("repo-meta");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("three half-width → 2-column row + 1-column row", () => {
|
|
128
|
-
const widgets = [
|
|
129
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
130
|
-
makeWidget("repo-meta", "half", true, false, 1),
|
|
131
|
-
makeWidget("branch-list", "half", true, false, 2),
|
|
132
|
-
];
|
|
133
|
-
const rows = computeRows(widgets, 80);
|
|
134
|
-
|
|
135
|
-
expect(rows).toHaveLength(2);
|
|
136
|
-
expect(rows[0].columns).toBe(2);
|
|
137
|
-
expect(rows[0].widgets).toHaveLength(2);
|
|
138
|
-
expect(rows[1].columns).toBe(1);
|
|
139
|
-
expect(rows[1].widgets).toHaveLength(1);
|
|
140
|
-
expect(rows[1].widgets[0].id).toBe("branch-list");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("single full-width → 1 one-column row", () => {
|
|
144
|
-
const widgets = [makeWidget("recent-commits", "full")];
|
|
145
|
-
const rows = computeRows(widgets, 80);
|
|
146
|
-
|
|
147
|
-
expect(rows).toHaveLength(1);
|
|
148
|
-
expect(rows[0].columns).toBe(1);
|
|
149
|
-
expect(rows[0].widgets[0].id).toBe("recent-commits");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("narrow panel (< 40) → all half become full, each own row", () => {
|
|
153
|
-
const widgets = [
|
|
154
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
155
|
-
makeWidget("repo-meta", "half", true, false, 1),
|
|
156
|
-
makeWidget("branch-list", "half", true, false, 2),
|
|
157
|
-
];
|
|
158
|
-
const rows = computeRows(widgets, 39);
|
|
159
|
-
|
|
160
|
-
expect(rows).toHaveLength(3);
|
|
161
|
-
rows.forEach((r) => {
|
|
162
|
-
expect(r.columns).toBe(1);
|
|
163
|
-
expect(r.widgets).toHaveLength(1);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("empty widgets → empty rows array", () => {
|
|
168
|
-
expect(computeRows([], 80)).toEqual([]);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("disabled widgets are excluded from rows", () => {
|
|
172
|
-
const widgets = [
|
|
173
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
174
|
-
makeWidget("repo-meta", "half", false, false, 1),
|
|
175
|
-
makeWidget("branch-list", "half", true, false, 2),
|
|
176
|
-
];
|
|
177
|
-
const rows = computeRows(widgets, 80);
|
|
178
|
-
|
|
179
|
-
expect(rows).toHaveLength(1);
|
|
180
|
-
expect(rows[0].columns).toBe(2);
|
|
181
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
182
|
-
expect(rows[0].widgets[1].id).toBe("branch-list");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("order is preserved for same-type widgets", () => {
|
|
186
|
-
const widgets = [
|
|
187
|
-
makeWidget("github-prs", "half", true, false, 0),
|
|
188
|
-
makeWidget("github-issues", "half", true, false, 1),
|
|
189
|
-
makeWidget("github-ci", "half", true, false, 2),
|
|
190
|
-
makeWidget("devpad-tasks", "half", true, false, 3),
|
|
191
|
-
];
|
|
192
|
-
const rows = computeRows(widgets, 80);
|
|
193
|
-
|
|
194
|
-
expect(rows).toHaveLength(2);
|
|
195
|
-
expect(rows[0].widgets[0].id).toBe("github-prs");
|
|
196
|
-
expect(rows[0].widgets[1].id).toBe("github-issues");
|
|
197
|
-
expect(rows[1].widgets[0].id).toBe("github-ci");
|
|
198
|
-
expect(rows[1].widgets[1].id).toBe("devpad-tasks");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("non-contiguous halfs pair together", () => {
|
|
202
|
-
const widgets = [
|
|
203
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
204
|
-
makeWidget("recent-commits", "full", true, false, 1),
|
|
205
|
-
makeWidget("repo-meta", "half", true, false, 2),
|
|
206
|
-
];
|
|
207
|
-
const rows = computeRows(widgets, 80);
|
|
208
|
-
|
|
209
|
-
expect(rows).toHaveLength(2);
|
|
210
|
-
// half pair has min priority 0, full has priority 1 → pair first
|
|
211
|
-
expect(rows[0].columns).toBe(2);
|
|
212
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
213
|
-
expect(rows[0].widgets[1].id).toBe("repo-meta");
|
|
214
|
-
expect(rows[1].columns).toBe(1);
|
|
215
|
-
expect(rows[1].widgets[0].id).toBe("recent-commits");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("full-width at priority 0 comes before halfs at priority 3+4", () => {
|
|
219
|
-
const widgets = [
|
|
220
|
-
makeWidget("recent-commits", "full", true, false, 0),
|
|
221
|
-
makeWidget("git-status", "half", true, false, 3),
|
|
222
|
-
makeWidget("repo-meta", "half", true, false, 4),
|
|
223
|
-
];
|
|
224
|
-
const rows = computeRows(widgets, 80);
|
|
225
|
-
|
|
226
|
-
expect(rows).toHaveLength(2);
|
|
227
|
-
expect(rows[0].columns).toBe(1);
|
|
228
|
-
expect(rows[0].widgets[0].id).toBe("recent-commits");
|
|
229
|
-
expect(rows[1].columns).toBe(2);
|
|
230
|
-
expect(rows[1].widgets[0].id).toBe("git-status");
|
|
231
|
-
expect(rows[1].widgets[1].id).toBe("repo-meta");
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("odd number of halfs → trailing half gets 1-column row", () => {
|
|
235
|
-
const widgets = [
|
|
236
|
-
makeWidget("git-status", "half", true, false, 0),
|
|
237
|
-
makeWidget("repo-meta", "half", true, false, 1),
|
|
238
|
-
makeWidget("branch-list", "half", true, false, 2),
|
|
239
|
-
makeWidget("recent-commits", "half", true, false, 3),
|
|
240
|
-
makeWidget("github-prs", "half", true, false, 4),
|
|
241
|
-
];
|
|
242
|
-
const rows = computeRows(widgets, 80);
|
|
243
|
-
|
|
244
|
-
expect(rows).toHaveLength(3);
|
|
245
|
-
expect(rows[0].columns).toBe(2);
|
|
246
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
247
|
-
expect(rows[0].widgets[1].id).toBe("repo-meta");
|
|
248
|
-
expect(rows[1].columns).toBe(2);
|
|
249
|
-
expect(rows[1].widgets[0].id).toBe("branch-list");
|
|
250
|
-
expect(rows[1].widgets[1].id).toBe("recent-commits");
|
|
251
|
-
expect(rows[2].columns).toBe(1);
|
|
252
|
-
expect(rows[2].widgets[0].id).toBe("github-prs");
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test("three third-width widgets → 1 three-column row", () => {
|
|
256
|
-
const widgets = [
|
|
257
|
-
makeWidget("git-status", "third", true, false, 0),
|
|
258
|
-
makeWidget("repo-meta", "third", true, false, 1),
|
|
259
|
-
makeWidget("github-ci", "third", true, false, 2),
|
|
260
|
-
];
|
|
261
|
-
const rows = computeRows(widgets, 80);
|
|
262
|
-
|
|
263
|
-
expect(rows).toHaveLength(1);
|
|
264
|
-
expect(rows[0].columns).toBe(3);
|
|
265
|
-
expect(rows[0].widgets).toHaveLength(3);
|
|
266
|
-
expect(rows[0].widgets[0].id).toBe("git-status");
|
|
267
|
-
expect(rows[0].widgets[1].id).toBe("repo-meta");
|
|
268
|
-
expect(rows[0].widgets[2].id).toBe("github-ci");
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
test("four thirds → 3-col row + 1-col row (auto-expand)", () => {
|
|
272
|
-
const widgets = [
|
|
273
|
-
makeWidget("git-status", "third", true, false, 0),
|
|
274
|
-
makeWidget("repo-meta", "third", true, false, 1),
|
|
275
|
-
makeWidget("github-ci", "third", true, false, 2),
|
|
276
|
-
makeWidget("commit-activity", "third", true, false, 3),
|
|
277
|
-
];
|
|
278
|
-
const rows = computeRows(widgets, 80);
|
|
279
|
-
|
|
280
|
-
expect(rows).toHaveLength(2);
|
|
281
|
-
expect(rows[0].columns).toBe(3);
|
|
282
|
-
expect(rows[0].widgets).toHaveLength(3);
|
|
283
|
-
expect(rows[1].columns).toBe(1);
|
|
284
|
-
expect(rows[1].widgets).toHaveLength(1);
|
|
285
|
-
expect(rows[1].widgets[0].id).toBe("commit-activity");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("five thirds → 3-col row + 2-col row (auto-expand)", () => {
|
|
289
|
-
const widgets = [
|
|
290
|
-
makeWidget("git-status", "third", true, false, 0),
|
|
291
|
-
makeWidget("repo-meta", "third", true, false, 1),
|
|
292
|
-
makeWidget("github-ci", "third", true, false, 2),
|
|
293
|
-
makeWidget("commit-activity", "third", true, false, 3),
|
|
294
|
-
makeWidget("github-release", "third", true, false, 4),
|
|
295
|
-
];
|
|
296
|
-
const rows = computeRows(widgets, 80);
|
|
297
|
-
|
|
298
|
-
expect(rows).toHaveLength(2);
|
|
299
|
-
expect(rows[0].columns).toBe(3);
|
|
300
|
-
expect(rows[0].widgets).toHaveLength(3);
|
|
301
|
-
expect(rows[1].columns).toBe(2);
|
|
302
|
-
expect(rows[1].widgets).toHaveLength(2);
|
|
303
|
-
expect(rows[1].widgets[0].id).toBe("commit-activity");
|
|
304
|
-
expect(rows[1].widgets[1].id).toBe("github-release");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("mixed thirds + halfs + fulls", () => {
|
|
308
|
-
const widgets = [
|
|
309
|
-
makeWidget("recent-commits", "full", true, false, 0),
|
|
310
|
-
makeWidget("git-status", "third", true, false, 1),
|
|
311
|
-
makeWidget("repo-meta", "third", true, false, 2),
|
|
312
|
-
makeWidget("github-ci", "third", true, false, 3),
|
|
313
|
-
makeWidget("branch-list", "half", true, false, 4),
|
|
314
|
-
makeWidget("github-prs", "half", true, false, 5),
|
|
315
|
-
];
|
|
316
|
-
const rows = computeRows(widgets, 80);
|
|
317
|
-
|
|
318
|
-
expect(rows).toHaveLength(3);
|
|
319
|
-
// Sort by min priority: full(0), thirds(1), halfs(4)
|
|
320
|
-
expect(rows[0].columns).toBe(1);
|
|
321
|
-
expect(rows[0].widgets[0].id).toBe("recent-commits");
|
|
322
|
-
expect(rows[1].columns).toBe(3);
|
|
323
|
-
expect(rows[1].widgets[0].id).toBe("git-status");
|
|
324
|
-
expect(rows[2].columns).toBe(2);
|
|
325
|
-
expect(rows[2].widgets[0].id).toBe("branch-list");
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("thirds at narrow panel (<60) fall back to half pairing", () => {
|
|
329
|
-
const widgets = [
|
|
330
|
-
makeWidget("git-status", "third", true, false, 0),
|
|
331
|
-
makeWidget("repo-meta", "third", true, false, 1),
|
|
332
|
-
makeWidget("github-ci", "third", true, false, 2),
|
|
333
|
-
];
|
|
334
|
-
const rows = computeRows(widgets, 50);
|
|
335
|
-
|
|
336
|
-
// thirds resolve to half at 50 cols → paired as halfs
|
|
337
|
-
expect(rows).toHaveLength(2);
|
|
338
|
-
expect(rows[0].columns).toBe(2);
|
|
339
|
-
expect(rows[0].widgets).toHaveLength(2);
|
|
340
|
-
expect(rows[1].columns).toBe(1);
|
|
341
|
-
expect(rows[1].widgets).toHaveLength(1);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
test("thirds at very narrow panel (<40) fall back to full", () => {
|
|
345
|
-
const widgets = [
|
|
346
|
-
makeWidget("git-status", "third", true, false, 0),
|
|
347
|
-
makeWidget("repo-meta", "third", true, false, 1),
|
|
348
|
-
makeWidget("github-ci", "third", true, false, 2),
|
|
349
|
-
];
|
|
350
|
-
const rows = computeRows(widgets, 35);
|
|
351
|
-
|
|
352
|
-
// thirds resolve to full at <40 cols → each gets own row
|
|
353
|
-
expect(rows).toHaveLength(3);
|
|
354
|
-
rows.forEach((r) => {
|
|
355
|
-
expect(r.columns).toBe(1);
|
|
356
|
-
expect(r.widgets).toHaveLength(1);
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// ── buildBorderLine ────────────────────────────────────────────────────────
|
|
362
|
-
|
|
363
|
-
describe("buildBorderLine", () => {
|
|
364
|
-
const W = 20;
|
|
365
|
-
|
|
366
|
-
const oneCol = row(1, makeWidget("git-status", "full"));
|
|
367
|
-
const twoCol = row(2, makeWidget("git-status", "half"), makeWidget("repo-meta", "half"));
|
|
368
|
-
const threeCol = row(3, makeWidget("git-status", "third"), makeWidget("repo-meta", "third"), makeWidget("github-ci", "third"));
|
|
369
|
-
|
|
370
|
-
test("top border, 1-col next row → corners + horizontal fill", () => {
|
|
371
|
-
const line = buildBorderLine("top", W, null, oneCol);
|
|
372
|
-
expect(line.length).toBe(W);
|
|
373
|
-
expect(line[0]).toBe("╭");
|
|
374
|
-
expect(line[W - 1]).toBe("╮");
|
|
375
|
-
// No junction — all interior chars are horizontal
|
|
376
|
-
for (let i = 1; i < W - 1; i++) {
|
|
377
|
-
expect(line[i]).toBe("─");
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
test("top border, 2-col next row → has ┬ at midpoint", () => {
|
|
382
|
-
const line = buildBorderLine("top", W, null, twoCol);
|
|
383
|
-
expect(line.length).toBe(W);
|
|
384
|
-
expect(line[0]).toBe("╭");
|
|
385
|
-
expect(line[W - 1]).toBe("╮");
|
|
386
|
-
expect(line[10]).toBe("┬");
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
test("bottom border, 1-col prev row → corners only", () => {
|
|
390
|
-
const line = buildBorderLine("bottom", W, oneCol, null);
|
|
391
|
-
expect(line.length).toBe(W);
|
|
392
|
-
expect(line[0]).toBe("╰");
|
|
393
|
-
expect(line[W - 1]).toBe("╯");
|
|
394
|
-
for (let i = 1; i < W - 1; i++) {
|
|
395
|
-
expect(line[i]).toBe("─");
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("bottom border, 2-col prev row → has ┴ at midpoint", () => {
|
|
400
|
-
const line = buildBorderLine("bottom", W, twoCol, null);
|
|
401
|
-
expect(line.length).toBe(W);
|
|
402
|
-
expect(line[0]).toBe("╰");
|
|
403
|
-
expect(line[W - 1]).toBe("╯");
|
|
404
|
-
expect(line[10]).toBe("┴");
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test("mid border, 2-col prev → 1-col next → ┴ at midpoint", () => {
|
|
408
|
-
const line = buildBorderLine("mid", W, twoCol, oneCol);
|
|
409
|
-
expect(line.length).toBe(W);
|
|
410
|
-
expect(line[0]).toBe("├");
|
|
411
|
-
expect(line[W - 1]).toBe("┤");
|
|
412
|
-
expect(line[10]).toBe("┴");
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
test("mid border, 1-col prev → 2-col next → ┬ at midpoint", () => {
|
|
416
|
-
const line = buildBorderLine("mid", W, oneCol, twoCol);
|
|
417
|
-
expect(line.length).toBe(W);
|
|
418
|
-
expect(line[0]).toBe("├");
|
|
419
|
-
expect(line[W - 1]).toBe("┤");
|
|
420
|
-
expect(line[10]).toBe("┬");
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
test("mid border, 2-col prev → 2-col next → ┼ at midpoint", () => {
|
|
424
|
-
const line = buildBorderLine("mid", W, twoCol, twoCol);
|
|
425
|
-
expect(line.length).toBe(W);
|
|
426
|
-
expect(line[0]).toBe("├");
|
|
427
|
-
expect(line[W - 1]).toBe("┤");
|
|
428
|
-
expect(line[10]).toBe("┼");
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test("mid border, 1-col prev → 1-col next → no junction", () => {
|
|
432
|
-
const line = buildBorderLine("mid", W, oneCol, oneCol);
|
|
433
|
-
expect(line.length).toBe(W);
|
|
434
|
-
expect(line[0]).toBe("├");
|
|
435
|
-
expect(line[W - 1]).toBe("┤");
|
|
436
|
-
for (let i = 1; i < W - 1; i++) {
|
|
437
|
-
expect(line[i]).toBe("─");
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
test("line length always equals total_width", () => {
|
|
442
|
-
const widths = [1, 2, 5, 20, 80, 120];
|
|
443
|
-
for (const w of widths) {
|
|
444
|
-
expect(buildBorderLine("top", w, null, oneCol).length).toBe(w);
|
|
445
|
-
expect(buildBorderLine("mid", w, oneCol, twoCol).length).toBe(w);
|
|
446
|
-
expect(buildBorderLine("bottom", w, twoCol, null).length).toBe(w);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test("first char is always left corner/T, last is always right corner/T", () => {
|
|
451
|
-
const cases: ["top" | "mid" | "bottom", string, string][] = [
|
|
452
|
-
["top", "╭", "╮"],
|
|
453
|
-
["mid", "├", "┤"],
|
|
454
|
-
["bottom", "╰", "╯"],
|
|
455
|
-
];
|
|
456
|
-
for (const [type, left, right] of cases) {
|
|
457
|
-
const line = buildBorderLine(type, W, oneCol, oneCol);
|
|
458
|
-
expect(line[0]).toBe(left);
|
|
459
|
-
expect(line[W - 1]).toBe(right);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
test("odd total_width: junction at floor(width/2)", () => {
|
|
464
|
-
const odd_w = 21;
|
|
465
|
-
const line = buildBorderLine("top", odd_w, null, twoCol);
|
|
466
|
-
expect(line.length).toBe(odd_w);
|
|
467
|
-
expect(line[Math.floor(odd_w / 2)]).toBe("┬");
|
|
468
|
-
expect(line[0]).toBe("╭");
|
|
469
|
-
expect(line[odd_w - 1]).toBe("╮");
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
test("top border, 3-col next row → has two junctions", () => {
|
|
473
|
-
const W = 30;
|
|
474
|
-
const line = buildBorderLine("top", W, null, threeCol);
|
|
475
|
-
expect(line.length).toBe(W);
|
|
476
|
-
expect(line[0]).toBe("╭");
|
|
477
|
-
expect(line[W - 1]).toBe("╮");
|
|
478
|
-
expect(line[10]).toBe("┬"); // Math.floor(30/3) = 10
|
|
479
|
-
expect(line[20]).toBe("┬"); // Math.floor(60/3) = 20
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test("bottom border, 3-col prev row → has two ┴ junctions", () => {
|
|
483
|
-
const W = 30;
|
|
484
|
-
const line = buildBorderLine("bottom", W, threeCol, null);
|
|
485
|
-
expect(line.length).toBe(W);
|
|
486
|
-
expect(line[0]).toBe("╰");
|
|
487
|
-
expect(line[W - 1]).toBe("╯");
|
|
488
|
-
expect(line[10]).toBe("┴");
|
|
489
|
-
expect(line[20]).toBe("┴");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
test("mid border, 3-col → 3-col → has two ┼ junctions", () => {
|
|
493
|
-
const W = 30;
|
|
494
|
-
const line = buildBorderLine("mid", W, threeCol, threeCol);
|
|
495
|
-
expect(line.length).toBe(W);
|
|
496
|
-
expect(line[0]).toBe("├");
|
|
497
|
-
expect(line[W - 1]).toBe("┤");
|
|
498
|
-
expect(line[10]).toBe("┼");
|
|
499
|
-
expect(line[20]).toBe("┼");
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
test("mid border, 3-col → 1-col → two ┴ junctions", () => {
|
|
503
|
-
const W = 30;
|
|
504
|
-
const line = buildBorderLine("mid", W, threeCol, oneCol);
|
|
505
|
-
expect(line.length).toBe(W);
|
|
506
|
-
expect(line[0]).toBe("├");
|
|
507
|
-
expect(line[W - 1]).toBe("┤");
|
|
508
|
-
expect(line[10]).toBe("┴");
|
|
509
|
-
expect(line[20]).toBe("┴");
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
test("mid border, 1-col → 3-col → two ┬ junctions", () => {
|
|
513
|
-
const W = 30;
|
|
514
|
-
const line = buildBorderLine("mid", W, oneCol, threeCol);
|
|
515
|
-
expect(line.length).toBe(W);
|
|
516
|
-
expect(line[0]).toBe("├");
|
|
517
|
-
expect(line[W - 1]).toBe("┤");
|
|
518
|
-
expect(line[10]).toBe("┬");
|
|
519
|
-
expect(line[20]).toBe("┬");
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
test("mid border, 3-col → 2-col → mixed junctions", () => {
|
|
523
|
-
// 3-col junctions at 10, 20 (for W=30)
|
|
524
|
-
// 2-col junction at 15 (for W=30)
|
|
525
|
-
// At 10: in prev only → ┴
|
|
526
|
-
// At 15: in next only → ┬
|
|
527
|
-
// At 20: in prev only → ┴
|
|
528
|
-
const W = 30;
|
|
529
|
-
const line = buildBorderLine("mid", W, threeCol, twoCol);
|
|
530
|
-
expect(line.length).toBe(W);
|
|
531
|
-
expect(line[0]).toBe("├");
|
|
532
|
-
expect(line[W - 1]).toBe("┤");
|
|
533
|
-
expect(line[10]).toBe("┴"); // from 3-col above only
|
|
534
|
-
expect(line[15]).toBe("┬"); // from 2-col below only
|
|
535
|
-
expect(line[20]).toBe("┴"); // from 3-col above only
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test("mid border, 2-col → 3-col → mixed junctions", () => {
|
|
539
|
-
const W = 30;
|
|
540
|
-
const line = buildBorderLine("mid", W, twoCol, threeCol);
|
|
541
|
-
expect(line.length).toBe(W);
|
|
542
|
-
expect(line[0]).toBe("├");
|
|
543
|
-
expect(line[W - 1]).toBe("┤");
|
|
544
|
-
expect(line[10]).toBe("┬"); // from 3-col below only
|
|
545
|
-
expect(line[15]).toBe("┴"); // from 2-col above only
|
|
546
|
-
expect(line[20]).toBe("┬"); // from 3-col below only
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// ── buildBorderLineWithTitle ───────────────────────────────────────────────
|
|
551
|
-
|
|
552
|
-
describe("buildBorderLineWithTitle", () => {
|
|
553
|
-
const base = buildBorderLine("top", 20, null, row(1, makeWidget("git-status", "full")));
|
|
554
|
-
|
|
555
|
-
test("inserts title into border line after first char", () => {
|
|
556
|
-
const result = buildBorderLineWithTitle(base, "Hello");
|
|
557
|
-
expect(result.length).toBe(20);
|
|
558
|
-
expect(result[0]).toBe("╭");
|
|
559
|
-
expect(result.slice(1, 8)).toBe(" Hello ");
|
|
560
|
-
expect(result[19]).toBe("╮");
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
test("empty title returns unchanged line", () => {
|
|
564
|
-
expect(buildBorderLineWithTitle(base, "")).toBe(base);
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test("very long title gets truncated with …", () => {
|
|
568
|
-
const long_title = "A".repeat(50);
|
|
569
|
-
const result = buildBorderLineWithTitle(base, long_title);
|
|
570
|
-
expect(result.length).toBe(20);
|
|
571
|
-
expect(result).toContain("…");
|
|
572
|
-
expect(result[0]).toBe("╭");
|
|
573
|
-
expect(result[19]).toBe("╮");
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
test("title fits exactly (boundary)", () => {
|
|
577
|
-
// line length 20, corners take 2 chars → 18 interior
|
|
578
|
-
// title_str = ` ${title} ` must be < line.length - 2 (18) to avoid truncation
|
|
579
|
-
// max non-truncated title_str length = 17 → title length = 15
|
|
580
|
-
const exact_title = "A".repeat(15);
|
|
581
|
-
const result = buildBorderLineWithTitle(base, exact_title);
|
|
582
|
-
expect(result.length).toBe(20);
|
|
583
|
-
expect(result[0]).toBe("╭");
|
|
584
|
-
expect(result[19]).toBe("╮");
|
|
585
|
-
expect(result).toContain(exact_title);
|
|
586
|
-
expect(result).not.toContain("…");
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// ── getWidgetBorderSides ───────────────────────────────────────────────────
|
|
591
|
-
|
|
592
|
-
describe("getWidgetBorderSides", () => {
|
|
593
|
-
test("single-column row gets both sides", () => {
|
|
594
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "full")], columns: 1 };
|
|
595
|
-
expect(getWidgetBorderSides(r, 0)).toEqual(["left", "right"]);
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
test("two-column row: left widget gets left only", () => {
|
|
599
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "half"), makeWidget("repo-meta", "half")], columns: 2 };
|
|
600
|
-
expect(getWidgetBorderSides(r, 0)).toEqual(["left"]);
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
test("two-column row: right widget gets left and right", () => {
|
|
604
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "half"), makeWidget("repo-meta", "half")], columns: 2 };
|
|
605
|
-
expect(getWidgetBorderSides(r, 1)).toEqual(["left", "right"]);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
test("three-column row: left widget gets left only", () => {
|
|
609
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "third"), makeWidget("repo-meta", "third"), makeWidget("github-ci", "third")], columns: 3 };
|
|
610
|
-
expect(getWidgetBorderSides(r, 0)).toEqual(["left"]);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
test("three-column row: middle widget gets left only", () => {
|
|
614
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "third"), makeWidget("repo-meta", "third"), makeWidget("github-ci", "third")], columns: 3 };
|
|
615
|
-
expect(getWidgetBorderSides(r, 1)).toEqual(["left"]);
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
test("three-column row: right widget gets left and right", () => {
|
|
619
|
-
const r: GridRow = { widgets: [makeWidget("git-status", "third"), makeWidget("repo-meta", "third"), makeWidget("github-ci", "third")], columns: 3 };
|
|
620
|
-
expect(getWidgetBorderSides(r, 2)).toEqual(["left", "right"]);
|
|
621
|
-
});
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// ── contentWidth ───────────────────────────────────────────────────────────
|
|
625
|
-
|
|
626
|
-
describe("contentWidth", () => {
|
|
627
|
-
test("full span at 60 → 58", () => {
|
|
628
|
-
expect(contentWidth("full", 60)).toBe(58);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
test("half span at 60 → 28", () => {
|
|
632
|
-
expect(contentWidth("half", 60)).toBe(28);
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
test("half span at 39 → 37 (falls back to full)", () => {
|
|
636
|
-
expect(contentWidth("half", 39)).toBe(37);
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
test("returns at least 1 for very small widths", () => {
|
|
640
|
-
expect(contentWidth("full", 1)).toBe(1);
|
|
641
|
-
expect(contentWidth("full", 2)).toBe(1);
|
|
642
|
-
expect(contentWidth("half", 2)).toBe(1);
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
test("third span at 90 → 29", () => {
|
|
646
|
-
// Math.floor(90/3) = 30, minus 1 border = 29
|
|
647
|
-
expect(contentWidth("third", 90)).toBe(29);
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
test("third span at 60 → 19", () => {
|
|
651
|
-
// Math.floor(60/3) = 20, minus 1 border = 19
|
|
652
|
-
expect(contentWidth("third", 60)).toBe(19);
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
test("third span at 50 → falls back to half → 23", () => {
|
|
656
|
-
// At 50 cols, third resolves to half
|
|
657
|
-
// junction = Math.floor(50/2) = 25, content = 50 - 25 - 2 = 23
|
|
658
|
-
expect(contentWidth("third", 50)).toBe(23);
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
test("third span at 39 → falls back to full → 37", () => {
|
|
662
|
-
// At 39 cols, third resolves to full
|
|
663
|
-
expect(contentWidth("third", 39)).toBe(37);
|
|
664
|
-
});
|
|
665
|
-
});
|