@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.
Files changed (69) hide show
  1. package/bin/overview +10 -0
  2. package/dist/overview.js +10361 -0
  3. package/package.json +22 -15
  4. package/bunfig.toml +0 -7
  5. package/packages/core/__tests__/concurrency.test.ts +0 -111
  6. package/packages/core/__tests__/helpers.ts +0 -60
  7. package/packages/core/__tests__/integration/git-status.test.ts +0 -62
  8. package/packages/core/__tests__/integration/scanner.test.ts +0 -140
  9. package/packages/core/__tests__/ocn.test.ts +0 -164
  10. package/packages/core/package.json +0 -13
  11. package/packages/core/src/cache.ts +0 -31
  12. package/packages/core/src/concurrency.ts +0 -44
  13. package/packages/core/src/devpad.ts +0 -61
  14. package/packages/core/src/git-graph.ts +0 -54
  15. package/packages/core/src/git-stats.ts +0 -201
  16. package/packages/core/src/git-status.ts +0 -316
  17. package/packages/core/src/github.ts +0 -286
  18. package/packages/core/src/index.ts +0 -58
  19. package/packages/core/src/ocn.ts +0 -74
  20. package/packages/core/src/scanner.ts +0 -118
  21. package/packages/core/src/types.ts +0 -199
  22. package/packages/core/src/watcher.ts +0 -128
  23. package/packages/core/src/worktree.ts +0 -80
  24. package/packages/core/tsconfig.json +0 -5
  25. package/packages/render/bunfig.toml +0 -8
  26. package/packages/render/jsx-runtime.d.ts +0 -3
  27. package/packages/render/package.json +0 -18
  28. package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
  29. package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -304
  30. package/packages/render/src/components/git-graph.tsx +0 -127
  31. package/packages/render/src/components/help-overlay.tsx +0 -108
  32. package/packages/render/src/components/index.ts +0 -7
  33. package/packages/render/src/components/repo-list.tsx +0 -127
  34. package/packages/render/src/components/stats-panel.tsx +0 -116
  35. package/packages/render/src/components/status-badge.tsx +0 -70
  36. package/packages/render/src/components/status-bar.tsx +0 -56
  37. package/packages/render/src/components/widget-container.tsx +0 -286
  38. package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
  39. package/packages/render/src/components/widgets/branch-list.tsx +0 -93
  40. package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
  41. package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
  42. package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
  43. package/packages/render/src/components/widgets/file-changes.tsx +0 -78
  44. package/packages/render/src/components/widgets/git-status.tsx +0 -125
  45. package/packages/render/src/components/widgets/github-ci.tsx +0 -98
  46. package/packages/render/src/components/widgets/github-issues.tsx +0 -101
  47. package/packages/render/src/components/widgets/github-prs.tsx +0 -119
  48. package/packages/render/src/components/widgets/github-release.tsx +0 -73
  49. package/packages/render/src/components/widgets/index.ts +0 -12
  50. package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
  51. package/packages/render/src/components/widgets/registry.ts +0 -23
  52. package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
  53. package/packages/render/src/config/index.ts +0 -104
  54. package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
  55. package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
  56. package/packages/render/src/lib/actions.ts +0 -68
  57. package/packages/render/src/lib/fetch-context.ts +0 -102
  58. package/packages/render/src/lib/filter.ts +0 -94
  59. package/packages/render/src/lib/format.ts +0 -36
  60. package/packages/render/src/lib/use-devpad.ts +0 -167
  61. package/packages/render/src/lib/use-github.ts +0 -75
  62. package/packages/render/src/lib/widget-grid.ts +0 -204
  63. package/packages/render/src/lib/widget-state.ts +0 -96
  64. package/packages/render/src/overview.tsx +0 -16
  65. package/packages/render/src/screens/index.ts +0 -1
  66. package/packages/render/src/screens/main-screen.tsx +0 -410
  67. package/packages/render/src/theme/index.ts +0 -37
  68. package/packages/render/tsconfig.json +0 -9
  69. 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
- });