@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,780 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { testRender } from "@opentui/solid";
|
|
3
|
-
import { createSignal, Show } from "solid-js";
|
|
4
|
-
import type { ScrollBoxRenderable, Renderable } from "@opentui/core";
|
|
5
|
-
|
|
6
|
-
describe("scrollbox behavior", () => {
|
|
7
|
-
// Test 1: Simple case — many text lines in a scrollbox
|
|
8
|
-
// Render a scrollbox with height=20 containing 50 lines of text
|
|
9
|
-
// Verify scrollHeight >= 50
|
|
10
|
-
test("simple text content reports correct scrollHeight", async () => {
|
|
11
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
12
|
-
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i}`);
|
|
13
|
-
|
|
14
|
-
const { renderOnce } = await testRender(
|
|
15
|
-
() => (
|
|
16
|
-
<box width={60} height={20}>
|
|
17
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
18
|
-
<box flexDirection="column">
|
|
19
|
-
{lines.map((l) => (
|
|
20
|
-
<text content={l} />
|
|
21
|
-
))}
|
|
22
|
-
</box>
|
|
23
|
-
</scrollbox>
|
|
24
|
-
</box>
|
|
25
|
-
),
|
|
26
|
-
{ width: 60, height: 20 },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
await renderOnce();
|
|
30
|
-
|
|
31
|
-
console.log("Test 1 - Simple text:");
|
|
32
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
33
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
34
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
35
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
36
|
-
|
|
37
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(50);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Test 2: Box with flexDirection column wrapping text — mirrors our widget-container structure
|
|
41
|
-
// Render a scrollbox with height=20 containing a column box with children that are
|
|
42
|
-
// alternating <text> and <box> elements (simulating border lines + widget row boxes)
|
|
43
|
-
test("nested boxes report correct scrollHeight", async () => {
|
|
44
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
45
|
-
const rows = Array.from({ length: 10 }, (_, i) => i);
|
|
46
|
-
|
|
47
|
-
const { renderOnce } = await testRender(
|
|
48
|
-
() => (
|
|
49
|
-
<box width={60} height={20}>
|
|
50
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
51
|
-
<box flexDirection="column" width={60} flexShrink={0}>
|
|
52
|
-
{rows.map((i) => (
|
|
53
|
-
<>
|
|
54
|
-
<text content={`═══ Border ${i} ═══`} />
|
|
55
|
-
<box flexDirection="row" width={60}>
|
|
56
|
-
<box
|
|
57
|
-
flexDirection="column"
|
|
58
|
-
width={30}
|
|
59
|
-
minHeight={5}
|
|
60
|
-
border={["left", "right"]}
|
|
61
|
-
borderStyle="rounded"
|
|
62
|
-
>
|
|
63
|
-
<text content={`Widget ${i}a`} />
|
|
64
|
-
<text content="line 2" />
|
|
65
|
-
<text content="line 3" />
|
|
66
|
-
</box>
|
|
67
|
-
<box
|
|
68
|
-
flexDirection="column"
|
|
69
|
-
width={30}
|
|
70
|
-
minHeight={5}
|
|
71
|
-
border={["left", "right"]}
|
|
72
|
-
borderStyle="rounded"
|
|
73
|
-
>
|
|
74
|
-
<text content={`Widget ${i}b`} />
|
|
75
|
-
<text content="line 2" />
|
|
76
|
-
<text content="line 3" />
|
|
77
|
-
</box>
|
|
78
|
-
</box>
|
|
79
|
-
</>
|
|
80
|
-
))}
|
|
81
|
-
<text content="═══ Bottom Border ═══" />
|
|
82
|
-
</box>
|
|
83
|
-
</scrollbox>
|
|
84
|
-
</box>
|
|
85
|
-
),
|
|
86
|
-
{ width: 60, height: 20 },
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
await renderOnce();
|
|
90
|
-
|
|
91
|
-
// Expected: 10 border lines + 10 row boxes (each minHeight=5) + 1 bottom border = 10 + 50 + 1 = 61
|
|
92
|
-
console.log("Test 2 - Nested boxes (mirrors widget-container):");
|
|
93
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
94
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
95
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
96
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
97
|
-
|
|
98
|
-
// The total content should be at least 61 lines (10 borders + 10*5 rows + 1 bottom)
|
|
99
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(60);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Test 3: Verify scrollTo can actually reach the bottom
|
|
103
|
-
test("scrollTo can reach the last row", async () => {
|
|
104
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
105
|
-
const rows = Array.from({ length: 10 }, (_, i) => i);
|
|
106
|
-
|
|
107
|
-
const { renderOnce } = await testRender(
|
|
108
|
-
() => (
|
|
109
|
-
<box width={60} height={20}>
|
|
110
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
111
|
-
<box flexDirection="column" width={60} flexShrink={0}>
|
|
112
|
-
{rows.map((i) => (
|
|
113
|
-
<>
|
|
114
|
-
<text content={`═══ Border ${i} ═══`} />
|
|
115
|
-
<box flexDirection="row" width={60}>
|
|
116
|
-
<box
|
|
117
|
-
flexDirection="column"
|
|
118
|
-
width={30}
|
|
119
|
-
minHeight={5}
|
|
120
|
-
border={["left", "right"]}
|
|
121
|
-
borderStyle="rounded"
|
|
122
|
-
>
|
|
123
|
-
<text content={`Widget ${i}a`} />
|
|
124
|
-
</box>
|
|
125
|
-
<box
|
|
126
|
-
flexDirection="column"
|
|
127
|
-
width={30}
|
|
128
|
-
minHeight={5}
|
|
129
|
-
border={["left", "right"]}
|
|
130
|
-
borderStyle="rounded"
|
|
131
|
-
>
|
|
132
|
-
<text content={`Widget ${i}b`} />
|
|
133
|
-
</box>
|
|
134
|
-
</box>
|
|
135
|
-
</>
|
|
136
|
-
))}
|
|
137
|
-
<text content="═══ Bottom Border ═══" />
|
|
138
|
-
</box>
|
|
139
|
-
</scrollbox>
|
|
140
|
-
</box>
|
|
141
|
-
),
|
|
142
|
-
{ width: 60, height: 20 },
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
await renderOnce();
|
|
146
|
-
|
|
147
|
-
const max_scroll = sb_ref!.scrollHeight - (sb_ref!.viewport?.height ?? sb_ref!.height);
|
|
148
|
-
const content_bottom = sb_ref!.scrollHeight;
|
|
149
|
-
|
|
150
|
-
console.log("Test 3 - scrollTo reach:");
|
|
151
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
152
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
153
|
-
console.log(` max_scroll=${max_scroll}`);
|
|
154
|
-
console.log(` content_bottom=${content_bottom}`);
|
|
155
|
-
|
|
156
|
-
// Try to scroll to the bottom
|
|
157
|
-
sb_ref!.scrollTo({ x: 0, y: max_scroll });
|
|
158
|
-
console.log(` after scrollTo(${max_scroll}): scrollTop=${sb_ref!.scrollTop}`);
|
|
159
|
-
|
|
160
|
-
// The scrollTop should actually reach max_scroll
|
|
161
|
-
expect(sb_ref!.scrollTop).toBe(max_scroll);
|
|
162
|
-
|
|
163
|
-
// The last content should be visible (scrollTop + viewport >= content bottom)
|
|
164
|
-
expect(sb_ref!.scrollTop + (sb_ref!.viewport?.height ?? sb_ref!.height)).toBeGreaterThanOrEqual(
|
|
165
|
-
content_bottom,
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Test 4: Fragment children (JSX <> </>) — this is what the widget-container uses
|
|
170
|
-
// This tests if Fragment children cause yoga to miscalculate heights
|
|
171
|
-
test("fragment children in scrollbox column", async () => {
|
|
172
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
173
|
-
const items = Array.from({ length: 15 }, (_, i) => i);
|
|
174
|
-
|
|
175
|
-
const { renderOnce } = await testRender(
|
|
176
|
-
() => (
|
|
177
|
-
<box width={60} height={20}>
|
|
178
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
179
|
-
<box flexDirection="column" width={60} flexShrink={0}>
|
|
180
|
-
{items.map((i) => (
|
|
181
|
-
<>
|
|
182
|
-
<text content={`Header ${i}`} />
|
|
183
|
-
<box flexDirection="column" minHeight={3}>
|
|
184
|
-
<text content={`Content ${i} line 1`} />
|
|
185
|
-
<text content={`Content ${i} line 2`} />
|
|
186
|
-
</box>
|
|
187
|
-
</>
|
|
188
|
-
))}
|
|
189
|
-
</box>
|
|
190
|
-
</scrollbox>
|
|
191
|
-
</box>
|
|
192
|
-
),
|
|
193
|
-
{ width: 60, height: 20 },
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
await renderOnce();
|
|
197
|
-
|
|
198
|
-
// Expected: 15 headers + 15 boxes (each minHeight=3) = 15 + 45 = 60
|
|
199
|
-
console.log("Test 4 - Fragment children:");
|
|
200
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
201
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
202
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
203
|
-
|
|
204
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(60);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Test 5: Without intermediate box — children directly in scrollbox
|
|
208
|
-
test("children directly in scrollbox (no intermediate box)", async () => {
|
|
209
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
210
|
-
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i}`);
|
|
211
|
-
|
|
212
|
-
const { renderOnce } = await testRender(
|
|
213
|
-
() => (
|
|
214
|
-
<box width={60} height={20}>
|
|
215
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
216
|
-
{lines.map((l) => (
|
|
217
|
-
<text content={l} />
|
|
218
|
-
))}
|
|
219
|
-
</scrollbox>
|
|
220
|
-
</box>
|
|
221
|
-
),
|
|
222
|
-
{ width: 60, height: 20 },
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
await renderOnce();
|
|
226
|
-
|
|
227
|
-
console.log("Test 5 - Direct children (no intermediate box):");
|
|
228
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
229
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
230
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
231
|
-
|
|
232
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(50);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// Test 6: With the outer box having explicit height (like widget-container does with height prop)
|
|
236
|
-
test("scrollbox inside box with explicit height", async () => {
|
|
237
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
238
|
-
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i}`);
|
|
239
|
-
|
|
240
|
-
const { renderOnce } = await testRender(
|
|
241
|
-
() => (
|
|
242
|
-
<box flexDirection="column" width={60} height={30}>
|
|
243
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
244
|
-
<box flexDirection="column" width={60} flexShrink={0}>
|
|
245
|
-
{lines.map((l) => (
|
|
246
|
-
<text content={l} />
|
|
247
|
-
))}
|
|
248
|
-
</box>
|
|
249
|
-
</scrollbox>
|
|
250
|
-
</box>
|
|
251
|
-
),
|
|
252
|
-
{ width: 60, height: 30 },
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
await renderOnce();
|
|
256
|
-
|
|
257
|
-
console.log("Test 6 - Explicit outer height:");
|
|
258
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
259
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
260
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
261
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
262
|
-
|
|
263
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(50);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Test 7: Mimic the EXACT widget-container structure as closely as possible
|
|
267
|
-
test("exact widget-container structure mimicry", async () => {
|
|
268
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
269
|
-
const row_refs: Renderable[] = [];
|
|
270
|
-
|
|
271
|
-
// Simulate 9 rows of widgets (matching the real app's ~9 rows)
|
|
272
|
-
const row_configs = [
|
|
273
|
-
{ cols: 2, h: 10 }, // row 0: two half-width widgets
|
|
274
|
-
{ cols: 1, h: 7 }, // row 1: full width
|
|
275
|
-
{ cols: 2, h: 3 }, // row 2: two half-width
|
|
276
|
-
{ cols: 1, h: 13 }, // row 3: full width tall
|
|
277
|
-
{ cols: 2, h: 3 }, // row 4: two half-width
|
|
278
|
-
{ cols: 2, h: 7 }, // row 5: two half-width
|
|
279
|
-
{ cols: 1, h: 10 }, // row 6: full width
|
|
280
|
-
{ cols: 2, h: 4 }, // row 7: two half-width
|
|
281
|
-
{ cols: 1, h: 3 }, // row 8: full width
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
const total_width = 80;
|
|
285
|
-
|
|
286
|
-
const { renderOnce } = await testRender(
|
|
287
|
-
() => (
|
|
288
|
-
<box flexDirection="column" width={total_width} height={26}>
|
|
289
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
290
|
-
<box flexDirection="column" width={total_width} flexShrink={0}>
|
|
291
|
-
{row_configs.map((rc, row_idx) => {
|
|
292
|
-
const border_line = "─".repeat(total_width);
|
|
293
|
-
const half = Math.floor(total_width / 2);
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<>
|
|
297
|
-
<text content={border_line} />
|
|
298
|
-
<box
|
|
299
|
-
ref={(el: Renderable) => {
|
|
300
|
-
row_refs[row_idx] = el;
|
|
301
|
-
}}
|
|
302
|
-
flexDirection="row"
|
|
303
|
-
alignItems="stretch"
|
|
304
|
-
width={total_width}
|
|
305
|
-
>
|
|
306
|
-
{rc.cols === 1 ? (
|
|
307
|
-
<box
|
|
308
|
-
width={total_width}
|
|
309
|
-
border={["left", "right"]}
|
|
310
|
-
borderStyle="rounded"
|
|
311
|
-
flexDirection="column"
|
|
312
|
-
minHeight={rc.h}
|
|
313
|
-
overflow="hidden"
|
|
314
|
-
>
|
|
315
|
-
<text content={`Widget row ${row_idx}`} />
|
|
316
|
-
{Array.from({ length: rc.h - 1 }, (_, j) => (
|
|
317
|
-
<text content={` content line ${j}`} />
|
|
318
|
-
))}
|
|
319
|
-
</box>
|
|
320
|
-
) : (
|
|
321
|
-
<>
|
|
322
|
-
<box
|
|
323
|
-
width={half}
|
|
324
|
-
border={["left", "right"]}
|
|
325
|
-
borderStyle="rounded"
|
|
326
|
-
flexDirection="column"
|
|
327
|
-
minHeight={rc.h}
|
|
328
|
-
overflow="hidden"
|
|
329
|
-
>
|
|
330
|
-
<text content={`Widget row ${row_idx}a`} />
|
|
331
|
-
</box>
|
|
332
|
-
<box
|
|
333
|
-
width={total_width - half}
|
|
334
|
-
border={["left", "right"]}
|
|
335
|
-
borderStyle="rounded"
|
|
336
|
-
flexDirection="column"
|
|
337
|
-
minHeight={rc.h}
|
|
338
|
-
overflow="hidden"
|
|
339
|
-
>
|
|
340
|
-
<text content={`Widget row ${row_idx}b`} />
|
|
341
|
-
</box>
|
|
342
|
-
</>
|
|
343
|
-
)}
|
|
344
|
-
</box>
|
|
345
|
-
</>
|
|
346
|
-
);
|
|
347
|
-
})}
|
|
348
|
-
<text content={"─".repeat(total_width)} />
|
|
349
|
-
</box>
|
|
350
|
-
</scrollbox>
|
|
351
|
-
</box>
|
|
352
|
-
),
|
|
353
|
-
{ width: 80, height: 26 },
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
await renderOnce();
|
|
357
|
-
|
|
358
|
-
// Expected content height:
|
|
359
|
-
// 9 border lines + sum of row heights (10+7+3+13+3+7+10+4+3 = 60) + 1 bottom border = 70
|
|
360
|
-
// BUT with border on boxes, each box adds 0 height (left/right borders don't add height)
|
|
361
|
-
// So total = 9 + 60 + 1 = 70
|
|
362
|
-
console.log("Test 7 - Exact widget-container mimicry:");
|
|
363
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
364
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
365
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
366
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
367
|
-
|
|
368
|
-
let cumulative = 0;
|
|
369
|
-
for (let i = 0; i < row_configs.length; i++) {
|
|
370
|
-
const ref = row_refs[i];
|
|
371
|
-
if (ref) {
|
|
372
|
-
const content_y = ref.y + sb_ref!.scrollTop;
|
|
373
|
-
console.log(
|
|
374
|
-
` row ${i}: y=${ref.y} content_y=${content_y} height=${ref.height} (expected minHeight=${row_configs[i]!.h})`,
|
|
375
|
-
);
|
|
376
|
-
cumulative = content_y + ref.height;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
console.log(` cumulative content bottom: ${cumulative}`);
|
|
380
|
-
console.log(` + border lines: ~${cumulative + 1} (bottom border)`);
|
|
381
|
-
|
|
382
|
-
// Check if we can scroll to make the last row visible
|
|
383
|
-
const vp_h = sb_ref!.viewport?.height ?? sb_ref!.height;
|
|
384
|
-
const needed_scroll = cumulative + 1 - vp_h; // to see bottom border
|
|
385
|
-
console.log(` needed scroll to see bottom: ${needed_scroll}`);
|
|
386
|
-
console.log(` max allowed scroll: ${sb_ref!.scrollHeight - vp_h}`);
|
|
387
|
-
|
|
388
|
-
sb_ref!.scrollTo({ x: 0, y: needed_scroll });
|
|
389
|
-
console.log(` after scrollTo(${needed_scroll}): scrollTop=${sb_ref!.scrollTop}`);
|
|
390
|
-
|
|
391
|
-
// This should NOT be clamped
|
|
392
|
-
expect(sb_ref!.scrollTop).toBe(needed_scroll);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Test 8: scrollbox inside height="50%" container (real app layout)
|
|
396
|
-
test("scrollbox inside height=50% container (real app layout)", async () => {
|
|
397
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
398
|
-
const row_refs: Renderable[] = [];
|
|
399
|
-
|
|
400
|
-
const row_configs = [
|
|
401
|
-
{ cols: 2, h: 10 },
|
|
402
|
-
{ cols: 1, h: 7 },
|
|
403
|
-
{ cols: 2, h: 3 },
|
|
404
|
-
{ cols: 1, h: 13 },
|
|
405
|
-
{ cols: 2, h: 3 },
|
|
406
|
-
{ cols: 2, h: 7 },
|
|
407
|
-
{ cols: 1, h: 10 },
|
|
408
|
-
{ cols: 2, h: 4 },
|
|
409
|
-
{ cols: 1, h: 3 },
|
|
410
|
-
];
|
|
411
|
-
|
|
412
|
-
const right_panel_width = 80;
|
|
413
|
-
|
|
414
|
-
const { renderOnce } = await testRender(
|
|
415
|
-
() => (
|
|
416
|
-
<box flexDirection="column" width="100%" height="100%">
|
|
417
|
-
<box height={1}>
|
|
418
|
-
<text content="header" />
|
|
419
|
-
</box>
|
|
420
|
-
<box flexDirection="row" flexGrow={1}>
|
|
421
|
-
<box width={40}>
|
|
422
|
-
<text content="left panel" />
|
|
423
|
-
</box>
|
|
424
|
-
<box flexDirection="column" flexGrow={1}>
|
|
425
|
-
<box height="50%">
|
|
426
|
-
<text content="git graph placeholder" />
|
|
427
|
-
</box>
|
|
428
|
-
<box flexDirection="column" width={right_panel_width} height="50%">
|
|
429
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
430
|
-
<box flexDirection="column" width={right_panel_width} flexShrink={0}>
|
|
431
|
-
{row_configs.map((rc, row_idx) => {
|
|
432
|
-
const border_line = "─".repeat(right_panel_width);
|
|
433
|
-
const half = Math.floor(right_panel_width / 2);
|
|
434
|
-
return (
|
|
435
|
-
<>
|
|
436
|
-
<text content={border_line} />
|
|
437
|
-
<box
|
|
438
|
-
ref={(el: Renderable) => {
|
|
439
|
-
row_refs[row_idx] = el;
|
|
440
|
-
}}
|
|
441
|
-
flexDirection="row"
|
|
442
|
-
alignItems="stretch"
|
|
443
|
-
width={right_panel_width}
|
|
444
|
-
>
|
|
445
|
-
{rc.cols === 1 ? (
|
|
446
|
-
<box
|
|
447
|
-
width={right_panel_width}
|
|
448
|
-
border={["left", "right"]}
|
|
449
|
-
borderStyle="rounded"
|
|
450
|
-
flexDirection="column"
|
|
451
|
-
minHeight={rc.h}
|
|
452
|
-
overflow="hidden"
|
|
453
|
-
>
|
|
454
|
-
<text content={`Widget row ${row_idx}`} />
|
|
455
|
-
{Array.from({ length: rc.h - 1 }, (_, j) => (
|
|
456
|
-
<text content={` content line ${j}`} />
|
|
457
|
-
))}
|
|
458
|
-
</box>
|
|
459
|
-
) : (
|
|
460
|
-
<>
|
|
461
|
-
<box
|
|
462
|
-
width={half}
|
|
463
|
-
border={["left", "right"]}
|
|
464
|
-
borderStyle="rounded"
|
|
465
|
-
flexDirection="column"
|
|
466
|
-
minHeight={rc.h}
|
|
467
|
-
overflow="hidden"
|
|
468
|
-
>
|
|
469
|
-
<text content={`Widget row ${row_idx}a`} />
|
|
470
|
-
</box>
|
|
471
|
-
<box
|
|
472
|
-
width={right_panel_width - half}
|
|
473
|
-
border={["left", "right"]}
|
|
474
|
-
borderStyle="rounded"
|
|
475
|
-
flexDirection="column"
|
|
476
|
-
minHeight={rc.h}
|
|
477
|
-
overflow="hidden"
|
|
478
|
-
>
|
|
479
|
-
<text content={`Widget row ${row_idx}b`} />
|
|
480
|
-
</box>
|
|
481
|
-
</>
|
|
482
|
-
)}
|
|
483
|
-
</box>
|
|
484
|
-
</>
|
|
485
|
-
);
|
|
486
|
-
})}
|
|
487
|
-
<text content={"─".repeat(right_panel_width)} />
|
|
488
|
-
</box>
|
|
489
|
-
</scrollbox>
|
|
490
|
-
</box>
|
|
491
|
-
</box>
|
|
492
|
-
</box>
|
|
493
|
-
<box height={1}>
|
|
494
|
-
<text content="status bar" />
|
|
495
|
-
</box>
|
|
496
|
-
</box>
|
|
497
|
-
),
|
|
498
|
-
{ width: 120, height: 40 },
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
await renderOnce();
|
|
502
|
-
|
|
503
|
-
console.log("Test 8 - scrollbox inside height=50% container:");
|
|
504
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
505
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
506
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
507
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
508
|
-
|
|
509
|
-
let cumulative = 0;
|
|
510
|
-
for (let i = 0; i < row_configs.length; i++) {
|
|
511
|
-
const ref = row_refs[i];
|
|
512
|
-
if (ref) {
|
|
513
|
-
const content_y = ref.y + sb_ref!.scrollTop;
|
|
514
|
-
console.log(
|
|
515
|
-
` row ${i}: y=${ref.y} content_y=${content_y} height=${ref.height} (expected minHeight=${row_configs[i]!.h})`,
|
|
516
|
-
);
|
|
517
|
-
cumulative = content_y + ref.height;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
console.log(` cumulative content bottom: ${cumulative}`);
|
|
521
|
-
console.log(` + border lines: ~${cumulative + 1} (bottom border)`);
|
|
522
|
-
|
|
523
|
-
const vp_h = sb_ref!.viewport?.height ?? sb_ref!.height;
|
|
524
|
-
const needed_scroll = cumulative + 1 - vp_h;
|
|
525
|
-
console.log(` needed scroll to see bottom: ${needed_scroll}`);
|
|
526
|
-
console.log(` max allowed scroll: ${sb_ref!.scrollHeight - vp_h}`);
|
|
527
|
-
|
|
528
|
-
// scrollHeight should be 70 (same as test 7 — 9 borders + 60 row heights + 1 bottom)
|
|
529
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(70);
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// Test 9: scrollbox with flexGrow=1 inside percentage container (simplified)
|
|
533
|
-
test("scrollbox with flexGrow=1 inside percentage container", async () => {
|
|
534
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
535
|
-
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i}`);
|
|
536
|
-
|
|
537
|
-
const { renderOnce } = await testRender(
|
|
538
|
-
() => (
|
|
539
|
-
<box flexDirection="column" width="100%" height="100%">
|
|
540
|
-
<box height="50%">
|
|
541
|
-
<text content="top half" />
|
|
542
|
-
</box>
|
|
543
|
-
<box height="50%" flexDirection="column">
|
|
544
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
545
|
-
<box flexDirection="column" flexShrink={0}>
|
|
546
|
-
{lines.map((l) => (
|
|
547
|
-
<text content={l} />
|
|
548
|
-
))}
|
|
549
|
-
</box>
|
|
550
|
-
</scrollbox>
|
|
551
|
-
</box>
|
|
552
|
-
</box>
|
|
553
|
-
),
|
|
554
|
-
{ width: 60, height: 40 },
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
await renderOnce();
|
|
558
|
-
|
|
559
|
-
console.log("Test 9 - flexGrow=1 inside percentage container:");
|
|
560
|
-
console.log(` scrollHeight=${sb_ref!.scrollHeight}`);
|
|
561
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
562
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
563
|
-
console.log(` sb_ref.height=${sb_ref!.height}`);
|
|
564
|
-
|
|
565
|
-
expect(sb_ref!.scrollHeight).toBeGreaterThanOrEqual(50);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Test 10: Dynamic content — simulate Show/fallback height changes
|
|
569
|
-
test("dynamic content with Show/createSignal updates scrollHeight", async () => {
|
|
570
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
571
|
-
const [data, setData] = createSignal<{ loaded: true } | null>(null);
|
|
572
|
-
|
|
573
|
-
const widgets = Array.from({ length: 8 }, (_, i) => i);
|
|
574
|
-
|
|
575
|
-
const { renderOnce } = await testRender(
|
|
576
|
-
() => (
|
|
577
|
-
<box flexDirection="column" width={60} height={20}>
|
|
578
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
579
|
-
<box flexDirection="column" width={60} flexShrink={0}>
|
|
580
|
-
{widgets.map((i) => (
|
|
581
|
-
<>
|
|
582
|
-
<text content={`── Widget ${i} ──`} />
|
|
583
|
-
<box flexDirection="column" minHeight={1}>
|
|
584
|
-
<Show
|
|
585
|
-
when={data()}
|
|
586
|
-
fallback={<text content="loading..." />}
|
|
587
|
-
>
|
|
588
|
-
<text content={`Widget ${i} line 1`} />
|
|
589
|
-
<text content={`Widget ${i} line 2`} />
|
|
590
|
-
<text content={`Widget ${i} line 3`} />
|
|
591
|
-
<text content={`Widget ${i} line 4`} />
|
|
592
|
-
<text content={`Widget ${i} line 5`} />
|
|
593
|
-
</Show>
|
|
594
|
-
</box>
|
|
595
|
-
</>
|
|
596
|
-
))}
|
|
597
|
-
</box>
|
|
598
|
-
</scrollbox>
|
|
599
|
-
</box>
|
|
600
|
-
),
|
|
601
|
-
{ width: 60, height: 20 },
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
await renderOnce();
|
|
605
|
-
|
|
606
|
-
const scroll_height_before = sb_ref!.scrollHeight;
|
|
607
|
-
console.log("Test 10 - Dynamic content (before setData):");
|
|
608
|
-
console.log(` scrollHeight=${scroll_height_before}`);
|
|
609
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
610
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
611
|
-
|
|
612
|
-
// Before: 8 headers + 8 boxes (each 1 line fallback) = 8 + 8 = 16
|
|
613
|
-
// (minHeight=1 so each box is at least 1)
|
|
614
|
-
expect(scroll_height_before).toBeGreaterThanOrEqual(16);
|
|
615
|
-
|
|
616
|
-
// Now trigger the reactive update
|
|
617
|
-
setData({ loaded: true });
|
|
618
|
-
await renderOnce();
|
|
619
|
-
|
|
620
|
-
const scroll_height_after = sb_ref!.scrollHeight;
|
|
621
|
-
console.log("Test 10 - Dynamic content (after setData):");
|
|
622
|
-
console.log(` scrollHeight=${scroll_height_after}`);
|
|
623
|
-
console.log(` viewport.height=${sb_ref!.viewport?.height}`);
|
|
624
|
-
console.log(` content.height=${sb_ref!.content?.height}`);
|
|
625
|
-
|
|
626
|
-
// After: 8 headers + 8 boxes (each 5 lines) = 8 + 40 = 48
|
|
627
|
-
expect(scroll_height_after).toBeGreaterThanOrEqual(48);
|
|
628
|
-
expect(scroll_height_after).toBeGreaterThan(scroll_height_before);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
// Test 11: Verify scroll-to-focused logic works in nested layout
|
|
632
|
-
// This is the actual bug reproduction — el.y is screen-absolute
|
|
633
|
-
test("scroll-to-focused uses content-relative coordinates in nested layout", async () => {
|
|
634
|
-
let sb_ref: ScrollBoxRenderable | undefined;
|
|
635
|
-
const row_refs: Renderable[] = [];
|
|
636
|
-
|
|
637
|
-
const row_configs = [
|
|
638
|
-
{ cols: 2, h: 10 },
|
|
639
|
-
{ cols: 1, h: 7 },
|
|
640
|
-
{ cols: 2, h: 3 },
|
|
641
|
-
{ cols: 1, h: 13 },
|
|
642
|
-
{ cols: 2, h: 3 },
|
|
643
|
-
{ cols: 2, h: 7 },
|
|
644
|
-
{ cols: 1, h: 10 },
|
|
645
|
-
{ cols: 2, h: 4 },
|
|
646
|
-
{ cols: 1, h: 3 },
|
|
647
|
-
];
|
|
648
|
-
|
|
649
|
-
const right_panel_width = 80;
|
|
650
|
-
|
|
651
|
-
const { renderOnce } = await testRender(
|
|
652
|
-
() => (
|
|
653
|
-
<box flexDirection="column" width="100%" height="100%">
|
|
654
|
-
<box height={1}>
|
|
655
|
-
<text content="header" />
|
|
656
|
-
</box>
|
|
657
|
-
<box flexDirection="row" flexGrow={1}>
|
|
658
|
-
<box width={40}>
|
|
659
|
-
<text content="left panel" />
|
|
660
|
-
</box>
|
|
661
|
-
<box flexDirection="column" flexGrow={1}>
|
|
662
|
-
<box height="50%">
|
|
663
|
-
<text content="git graph placeholder" />
|
|
664
|
-
</box>
|
|
665
|
-
<box flexDirection="column" width={right_panel_width} height="50%">
|
|
666
|
-
<scrollbox ref={sb_ref} flexGrow={1}>
|
|
667
|
-
<box flexDirection="column" width={right_panel_width} flexShrink={0}>
|
|
668
|
-
{row_configs.map((rc, row_idx) => {
|
|
669
|
-
const border_line = "─".repeat(right_panel_width);
|
|
670
|
-
const half = Math.floor(right_panel_width / 2);
|
|
671
|
-
return (
|
|
672
|
-
<>
|
|
673
|
-
<text content={border_line} />
|
|
674
|
-
<box
|
|
675
|
-
ref={(el: Renderable) => {
|
|
676
|
-
row_refs[row_idx] = el;
|
|
677
|
-
}}
|
|
678
|
-
flexDirection="row"
|
|
679
|
-
alignItems="stretch"
|
|
680
|
-
width={right_panel_width}
|
|
681
|
-
>
|
|
682
|
-
{rc.cols === 1 ? (
|
|
683
|
-
<box
|
|
684
|
-
width={right_panel_width}
|
|
685
|
-
border={["left", "right"]}
|
|
686
|
-
borderStyle="rounded"
|
|
687
|
-
flexDirection="column"
|
|
688
|
-
minHeight={rc.h}
|
|
689
|
-
overflow="hidden"
|
|
690
|
-
>
|
|
691
|
-
<text content={`Widget row ${row_idx}`} />
|
|
692
|
-
{Array.from({ length: rc.h - 1 }, (_, j) => (
|
|
693
|
-
<text content={` content line ${j}`} />
|
|
694
|
-
))}
|
|
695
|
-
</box>
|
|
696
|
-
) : (
|
|
697
|
-
<>
|
|
698
|
-
<box
|
|
699
|
-
width={half}
|
|
700
|
-
border={["left", "right"]}
|
|
701
|
-
borderStyle="rounded"
|
|
702
|
-
flexDirection="column"
|
|
703
|
-
minHeight={rc.h}
|
|
704
|
-
overflow="hidden"
|
|
705
|
-
>
|
|
706
|
-
<text content={`Widget row ${row_idx}a`} />
|
|
707
|
-
</box>
|
|
708
|
-
<box
|
|
709
|
-
width={right_panel_width - half}
|
|
710
|
-
border={["left", "right"]}
|
|
711
|
-
borderStyle="rounded"
|
|
712
|
-
flexDirection="column"
|
|
713
|
-
minHeight={rc.h}
|
|
714
|
-
overflow="hidden"
|
|
715
|
-
>
|
|
716
|
-
<text content={`Widget row ${row_idx}b`} />
|
|
717
|
-
</box>
|
|
718
|
-
</>
|
|
719
|
-
)}
|
|
720
|
-
</box>
|
|
721
|
-
</>
|
|
722
|
-
);
|
|
723
|
-
})}
|
|
724
|
-
<text content={"─".repeat(right_panel_width)} />
|
|
725
|
-
</box>
|
|
726
|
-
</scrollbox>
|
|
727
|
-
</box>
|
|
728
|
-
</box>
|
|
729
|
-
</box>
|
|
730
|
-
<box height={1}>
|
|
731
|
-
<text content="status bar" />
|
|
732
|
-
</box>
|
|
733
|
-
</box>
|
|
734
|
-
),
|
|
735
|
-
{ width: 120, height: 40 },
|
|
736
|
-
);
|
|
737
|
-
|
|
738
|
-
await renderOnce();
|
|
739
|
-
|
|
740
|
-
const scroll_height = sb_ref!.scrollHeight;
|
|
741
|
-
const vp_height = sb_ref!.viewport?.height ?? sb_ref!.height;
|
|
742
|
-
const content_origin_y = sb_ref!.content.y;
|
|
743
|
-
|
|
744
|
-
console.log("Test 11 - Content-relative coordinate check:");
|
|
745
|
-
console.log(` scrollHeight=${scroll_height}, viewport.height=${vp_height}`);
|
|
746
|
-
console.log(` content.y (screen)=${content_origin_y}`);
|
|
747
|
-
|
|
748
|
-
// For each row, compute content-relative y using the CORRECT formula
|
|
749
|
-
for (let i = 0; i < row_configs.length; i++) {
|
|
750
|
-
const ref = row_refs[i];
|
|
751
|
-
if (ref) {
|
|
752
|
-
// CORRECT: content-relative position
|
|
753
|
-
const content_y = ref.y - content_origin_y;
|
|
754
|
-
console.log(` row ${i}: screen_y=${ref.y} content_y=${content_y} height=${ref.height}`);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Now simulate scrollToFocused for the LAST row (row 8)
|
|
759
|
-
const last_ref = row_refs[8]!;
|
|
760
|
-
const content_y = last_ref.y - content_origin_y;
|
|
761
|
-
const content_h = last_ref.height;
|
|
762
|
-
const region_top = content_y - 1; // 1 line above for border
|
|
763
|
-
const region_bottom = content_y + content_h + 1; // 1 line below for bottom border (last row)
|
|
764
|
-
const region_height = region_bottom - region_top;
|
|
765
|
-
|
|
766
|
-
console.log(` last row region: [${region_top}, ${region_bottom}] h=${region_height}`);
|
|
767
|
-
|
|
768
|
-
// To see the bottom, scroll so region_bottom aligns with viewport bottom
|
|
769
|
-
const needed_scroll = region_bottom - vp_height;
|
|
770
|
-
console.log(` needed_scroll=${needed_scroll}`);
|
|
771
|
-
console.log(` max_scroll=${scroll_height - vp_height}`);
|
|
772
|
-
|
|
773
|
-
// The needed scroll should be within the allowed range
|
|
774
|
-
expect(needed_scroll).toBeLessThanOrEqual(scroll_height - vp_height);
|
|
775
|
-
|
|
776
|
-
// Actually scroll there
|
|
777
|
-
sb_ref!.scrollTo({ x: 0, y: needed_scroll });
|
|
778
|
-
expect(sb_ref!.scrollTop).toBe(needed_scroll);
|
|
779
|
-
});
|
|
780
|
-
});
|