@cueloop/client 0.1.0-alpha.91 → 0.1.0-alpha.93
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/package.json +5 -5
- package/src/App.deleted-scroll.test.tsx +186 -0
- package/src/App.diff.test.tsx +3 -4
- package/src/App.test.tsx +1 -0
- package/src/App.toast.test.tsx +27 -1
- package/src/App.tsx +21 -12
- package/src/App.workbench.test.tsx +208 -2
- package/src/app-screens.tsx +34 -8
- package/src/components/AnnotatableFileView.test.tsx +48 -1
- package/src/components/AppShell.zoom.test.tsx +16 -3
- package/src/components/ChangesColumn.tsx +10 -4
- package/src/components/ChangesFileTree.test.tsx +122 -4
- package/src/components/DiffContentView.scroll.test.tsx +285 -1
- package/src/components/DiffContentView.tsx +214 -56
- package/src/components/EditorGrid.split.test.tsx +118 -0
- package/src/components/EditorGrid.stories.tsx +27 -0
- package/src/components/EditorGrid.tsx +170 -50
- package/src/components/EditorGrid.zoom.test.tsx +65 -1
- package/src/components/GridTabContent.actions.test.tsx +151 -0
- package/src/components/GridTabContent.stories.tsx +2 -2
- package/src/components/GridTabContent.tsx +5 -1
- package/src/components/ProjectTreeView.test.tsx +166 -8
- package/src/components/ProjectTreeView.tsx +53 -27
- package/src/components/ScrollArea.tsx +21 -2
- package/src/components/Toast.tsx +3 -1
- package/src/components/WelcomePlayground.test.tsx +28 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +41 -21
- package/src/components/editor-grid.test.ts +87 -0
- package/src/components/editor-grid.ts +142 -6
- package/src/components/primitives/Tree.tsx +51 -15
- package/src/diff-syntax.test.ts +15 -2
- package/src/diff-syntax.ts +13 -5
- package/src/thread-controller.curate.test.ts +27 -0
- package/src/thread-controller.ts +4 -6
- package/src/use-annotation-surface.tsx +86 -19
- package/src/use-changes-workbench.ts +30 -18
- package/src/use-frame-measure.ts +40 -7
- package/src/use-settings-dialog.tsx +1 -13
- package/src/use-shared-keyboard.ts +55 -0
- package/src/use-terminal-virtualizer.ts +8 -2
|
@@ -107,9 +107,24 @@ test("walking the caret down past wrapped discussion cards keeps it on screen an
|
|
|
107
107
|
await press(setup, "down");
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// A key advances the viewport by at most one visual line, even across a tall card.
|
|
111
111
|
for (let step = 1; step < scrollTops.length; step++) {
|
|
112
112
|
expect(scrollTops[step]!).toBeGreaterThanOrEqual(scrollTops[step - 1]!);
|
|
113
|
+
expect(scrollTops[step]! - scrollTops[step - 1]!).toBeLessThanOrEqual(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Walk back through the same cards: upward reveal must also stay one row at a time.
|
|
117
|
+
const upwardTops: number[] = [];
|
|
118
|
+
|
|
119
|
+
for (let step = 0; step < codeRowCount + 20; step++) {
|
|
120
|
+
upwardTops.push(scrollbox.scrollTop);
|
|
121
|
+
screenRows.push(caretScreenRow());
|
|
122
|
+
await press(setup, "up");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (let step = 1; step < upwardTops.length; step++) {
|
|
126
|
+
expect(upwardTops[step]!).toBeLessThanOrEqual(upwardTops[step - 1]!);
|
|
127
|
+
expect(upwardTops[step - 1]! - upwardTops[step]!).toBeLessThanOrEqual(1);
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
// Assert - the caret never scrolls off screen while it walks past each wrapped card
|
|
@@ -120,6 +135,220 @@ test("walking the caret down past wrapped discussion cards keeps it on screen an
|
|
|
120
135
|
setup.renderer.destroy();
|
|
121
136
|
}, 25000);
|
|
122
137
|
|
|
138
|
+
test("wheel input accelerates a continuous gesture and settles back to precise steps", async () => {
|
|
139
|
+
const rows = diffRows(tallPatch(80));
|
|
140
|
+
const setup = await testRender(
|
|
141
|
+
<DiffContentView
|
|
142
|
+
rows={rows}
|
|
143
|
+
session={fixtureDiffSession()}
|
|
144
|
+
marks={marksByRows([], rows)}
|
|
145
|
+
quickActions={[]}
|
|
146
|
+
observer={false}
|
|
147
|
+
onAnnotate={noop}
|
|
148
|
+
onReply={noop}
|
|
149
|
+
onUpdateAnnotation={noop}
|
|
150
|
+
onExit={noop}
|
|
151
|
+
/>,
|
|
152
|
+
{ width: 60, height: 12 },
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
await settle(setup);
|
|
156
|
+
const found = findById(setup.renderer.root, "diff-scroll");
|
|
157
|
+
|
|
158
|
+
if (!(found instanceof ScrollBoxRenderable)) throw new Error("diff-scroll is not a scrollbox");
|
|
159
|
+
const scrollbox = found;
|
|
160
|
+
const acceleration = scrollbox.scrollAcceleration;
|
|
161
|
+
const first = acceleration.tick(1000);
|
|
162
|
+
const burst = Array.from({ length: 10 }, (_, index) => acceleration.tick(1016 + index * 16));
|
|
163
|
+
const tail = acceleration.tick(1400);
|
|
164
|
+
|
|
165
|
+
expect(scrollbox.verticalScrollBar.scrollStep).toBe(1);
|
|
166
|
+
expect(first).toBe(1);
|
|
167
|
+
expect(Math.max(...burst)).toBeGreaterThan(first);
|
|
168
|
+
expect(tail).toBe(1);
|
|
169
|
+
|
|
170
|
+
acceleration.reset();
|
|
171
|
+
await setup.mockMouse.scroll(20, 5, "down");
|
|
172
|
+
await settle(setup);
|
|
173
|
+
expect(scrollbox.scrollTop).toBeGreaterThan(0);
|
|
174
|
+
|
|
175
|
+
setup.renderer.destroy();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("wheel scrolling up keeps the caret on the bottom visible code row", async () => {
|
|
179
|
+
const rows = diffRows(tallPatch(80));
|
|
180
|
+
const setup = await testRender(
|
|
181
|
+
<DiffContentView
|
|
182
|
+
rows={rows}
|
|
183
|
+
session={fixtureDiffSession()}
|
|
184
|
+
marks={marksByRows([], rows)}
|
|
185
|
+
quickActions={[]}
|
|
186
|
+
observer={false}
|
|
187
|
+
onAnnotate={noop}
|
|
188
|
+
onReply={noop}
|
|
189
|
+
onUpdateAnnotation={noop}
|
|
190
|
+
onExit={noop}
|
|
191
|
+
/>,
|
|
192
|
+
{ width: 60, height: 12 },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await settle(setup);
|
|
196
|
+
const found = findById(setup.renderer.root, "diff-scroll");
|
|
197
|
+
|
|
198
|
+
if (!(found instanceof ScrollBoxRenderable)) throw new Error("diff-scroll is not a scrollbox");
|
|
199
|
+
const scrollbox = found;
|
|
200
|
+
|
|
201
|
+
for (const key of Array.from({ length: 30 }, () => "down" as const)) {
|
|
202
|
+
// eslint-disable-next-line no-await-in-loop
|
|
203
|
+
await press(setup, key);
|
|
204
|
+
}
|
|
205
|
+
const before = scrollbox.scrollTop;
|
|
206
|
+
const markerRows = (): number[] =>
|
|
207
|
+
setup
|
|
208
|
+
.captureCharFrame()
|
|
209
|
+
.split("\n")
|
|
210
|
+
.flatMap((line, row) => (line.includes("▎") ? [row] : []));
|
|
211
|
+
const top = scrollbox.viewport.screenY;
|
|
212
|
+
const bottom = top + scrollbox.viewport.height - 1;
|
|
213
|
+
|
|
214
|
+
for (const direction of Array.from({ length: 8 }, () => "up" as const)) {
|
|
215
|
+
// eslint-disable-next-line no-await-in-loop
|
|
216
|
+
await setup.mockMouse.scroll(20, 5, direction);
|
|
217
|
+
// eslint-disable-next-line no-await-in-loop
|
|
218
|
+
await settle(setup);
|
|
219
|
+
expect(markerRows()).toEqual([bottom]);
|
|
220
|
+
}
|
|
221
|
+
expect(scrollbox.scrollTop).toBeLessThan(before);
|
|
222
|
+
for (const direction of Array.from({ length: 20 }, () => "down" as const)) {
|
|
223
|
+
// eslint-disable-next-line no-await-in-loop
|
|
224
|
+
await setup.mockMouse.scroll(20, 5, direction);
|
|
225
|
+
// eslint-disable-next-line no-await-in-loop
|
|
226
|
+
await settle(setup);
|
|
227
|
+
expect(markerRows()).toHaveLength(1);
|
|
228
|
+
}
|
|
229
|
+
expect(markerRows()).toEqual([top]);
|
|
230
|
+
setup.renderer.destroy();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("rapid wheel scrolling through a large wrapped diff does not loop React updates", async () => {
|
|
234
|
+
const filePatches = Array.from({ length: 80 }, (_, fileIndex) => [
|
|
235
|
+
`diff --git a/file-${fileIndex}.ts b/file-${fileIndex}.ts`,
|
|
236
|
+
`--- a/file-${fileIndex}.ts`,
|
|
237
|
+
`+++ b/file-${fileIndex}.ts`,
|
|
238
|
+
"@@ -0,0 +1,12 @@",
|
|
239
|
+
...Array.from(
|
|
240
|
+
{ length: 12 },
|
|
241
|
+
(_, lineIndex) => `+line ${lineIndex} ${"dense source text ".repeat(80)}`,
|
|
242
|
+
),
|
|
243
|
+
]).flat();
|
|
244
|
+
const rows = diffRows([...filePatches, ""].join("\n"));
|
|
245
|
+
const StatefulDiff = (): React.ReactNode => {
|
|
246
|
+
const [, setCursor] = React.useState(0);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<DiffContentView
|
|
250
|
+
rows={rows}
|
|
251
|
+
session={fixtureDiffSession()}
|
|
252
|
+
marks={marksByRows([], rows)}
|
|
253
|
+
quickActions={[]}
|
|
254
|
+
observer={false}
|
|
255
|
+
onCursorChange={setCursor}
|
|
256
|
+
onAnnotate={noop}
|
|
257
|
+
onReply={noop}
|
|
258
|
+
onUpdateAnnotation={noop}
|
|
259
|
+
onExit={noop}
|
|
260
|
+
/>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
const setup = await testRender(<StatefulDiff />, { width: 60, height: 12 });
|
|
264
|
+
|
|
265
|
+
await settle(setup);
|
|
266
|
+
const found = findById(setup.renderer.root, "diff-scroll");
|
|
267
|
+
|
|
268
|
+
if (!(found instanceof ScrollBoxRenderable)) throw new Error("diff-scroll is not a scrollbox");
|
|
269
|
+
|
|
270
|
+
for (const [index, direction] of Array.from({ length: 240 }, () => "down" as const).entries()) {
|
|
271
|
+
// eslint-disable-next-line no-await-in-loop
|
|
272
|
+
await setup.mockMouse.scroll(20, 5, direction);
|
|
273
|
+
if (index % 12 === 0) {
|
|
274
|
+
// eslint-disable-next-line no-await-in-loop
|
|
275
|
+
await settle(setup);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
await settle(setup);
|
|
279
|
+
expect(found.scrollTop).toBeGreaterThan(0);
|
|
280
|
+
setup.renderer.destroy();
|
|
281
|
+
}, 60000);
|
|
282
|
+
|
|
283
|
+
test("walking long wrapped code lines advances the viewport one visual row per key", async () => {
|
|
284
|
+
const lines = Array.from(
|
|
285
|
+
{ length: 36 },
|
|
286
|
+
(_, index) =>
|
|
287
|
+
`+line ${String(index).padStart(2, "0")} ${"a paragraph with several wrapped phrases ".repeat(5)}`,
|
|
288
|
+
);
|
|
289
|
+
const rows = diffRows(
|
|
290
|
+
[
|
|
291
|
+
"diff --git a/long.md b/long.md",
|
|
292
|
+
"--- a/long.md",
|
|
293
|
+
"+++ b/long.md",
|
|
294
|
+
"@@ -0,0 +1,36 @@",
|
|
295
|
+
...lines,
|
|
296
|
+
"",
|
|
297
|
+
].join("\n"),
|
|
298
|
+
);
|
|
299
|
+
const setup = await testRender(
|
|
300
|
+
<DiffContentView
|
|
301
|
+
rows={rows}
|
|
302
|
+
session={fixtureDiffSession()}
|
|
303
|
+
marks={marksByRows([], rows)}
|
|
304
|
+
quickActions={[]}
|
|
305
|
+
observer={false}
|
|
306
|
+
onAnnotate={noop}
|
|
307
|
+
onReply={noop}
|
|
308
|
+
onUpdateAnnotation={noop}
|
|
309
|
+
onExit={noop}
|
|
310
|
+
/>,
|
|
311
|
+
{ width: 60, height: 12 },
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
await settle(setup);
|
|
315
|
+
const found = findById(setup.renderer.root, "diff-scroll");
|
|
316
|
+
|
|
317
|
+
if (!(found instanceof ScrollBoxRenderable)) throw new Error("diff-scroll is not a scrollbox");
|
|
318
|
+
const scrollbox = found;
|
|
319
|
+
const steps: number[] = [];
|
|
320
|
+
const caretCell = annotationPaletteFor(DARK).caretCell;
|
|
321
|
+
|
|
322
|
+
for (let index = 0; index < 45; index++) {
|
|
323
|
+
const before = scrollbox.scrollTop;
|
|
324
|
+
|
|
325
|
+
await press(setup, "down");
|
|
326
|
+
steps.push(scrollbox.scrollTop - before);
|
|
327
|
+
const caretRow = setup
|
|
328
|
+
.captureSpans()
|
|
329
|
+
.lines.findIndex((line) => line.spans.some((span) => hex(span.bg) === caretCell));
|
|
330
|
+
const markerRows = setup
|
|
331
|
+
.captureCharFrame()
|
|
332
|
+
.split("\n")
|
|
333
|
+
.flatMap((line, row) => (line.includes("▎") ? [row] : []));
|
|
334
|
+
|
|
335
|
+
expect(caretRow).toBeGreaterThanOrEqual(0);
|
|
336
|
+
expect(markerRows).toEqual([caretRow]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
expect(Math.max(...steps)).toBeLessThanOrEqual(1);
|
|
340
|
+
expect(steps.some((step) => step > 0)).toBe(true);
|
|
341
|
+
await setup.mockMouse.scroll(20, 5, "up");
|
|
342
|
+
await settle(setup);
|
|
343
|
+
const wrappedMarkerRows = setup
|
|
344
|
+
.captureCharFrame()
|
|
345
|
+
.split("\n")
|
|
346
|
+
.flatMap((line, row) => (line.includes("▎") ? [row] : []));
|
|
347
|
+
|
|
348
|
+
expect(wrappedMarkerRows).toEqual([scrollbox.viewport.screenY + scrollbox.viewport.height - 1]);
|
|
349
|
+
setup.renderer.destroy();
|
|
350
|
+
});
|
|
351
|
+
|
|
123
352
|
test("opening a comment on the diff's last line reveals its composer", async () => {
|
|
124
353
|
const rows = diffRows(tallPatch(24));
|
|
125
354
|
const setup = await testRender(
|
|
@@ -147,3 +376,58 @@ test("opening a comment on the diff's last line reveals its composer", async ()
|
|
|
147
376
|
expect(setup.captureCharFrame()).toContain("● bottom comment");
|
|
148
377
|
setup.renderer.destroy();
|
|
149
378
|
}, 25000);
|
|
379
|
+
|
|
380
|
+
test("Up keeps the caret on the first screen row while wrapped lines scroll", async () => {
|
|
381
|
+
const lines = Array.from({ length: 24 }, (_, index) =>
|
|
382
|
+
index % 3 === 2 ? "-" : `-line ${index} ${"wrapped words here ".repeat(5)}`,
|
|
383
|
+
);
|
|
384
|
+
const rows = diffRows(
|
|
385
|
+
[
|
|
386
|
+
"diff --git a/long.md b/long.md",
|
|
387
|
+
"--- a/long.md",
|
|
388
|
+
"+++ /dev/null",
|
|
389
|
+
"@@ -1,24 +0,0 @@",
|
|
390
|
+
...lines,
|
|
391
|
+
"",
|
|
392
|
+
].join("\n"),
|
|
393
|
+
);
|
|
394
|
+
const setup = await testRender(
|
|
395
|
+
<DiffContentView
|
|
396
|
+
rows={rows}
|
|
397
|
+
session={fixtureDiffSession()}
|
|
398
|
+
marks={marksByRows([], rows)}
|
|
399
|
+
quickActions={[]}
|
|
400
|
+
observer={false}
|
|
401
|
+
onAnnotate={noop}
|
|
402
|
+
onReply={noop}
|
|
403
|
+
onUpdateAnnotation={noop}
|
|
404
|
+
onExit={noop}
|
|
405
|
+
/>,
|
|
406
|
+
{ width: 60, height: 12 },
|
|
407
|
+
);
|
|
408
|
+
await settle(setup);
|
|
409
|
+
const found = findById(setup.renderer.root, "diff-scroll");
|
|
410
|
+
|
|
411
|
+
if (!(found instanceof ScrollBoxRenderable)) throw new Error("diff-scroll is not a scrollbox");
|
|
412
|
+
const scrollbox = found;
|
|
413
|
+
|
|
414
|
+
for (let index = 0; index < 40; index++) await press(setup, "down");
|
|
415
|
+
let previousTop = scrollbox.scrollTop;
|
|
416
|
+
let scrolled = false;
|
|
417
|
+
|
|
418
|
+
for (let index = 0; index < 80; index++) {
|
|
419
|
+
await press(setup, "up");
|
|
420
|
+
const frame = setup.captureCharFrame().split("\n");
|
|
421
|
+
const marker = frame.findIndex((line) => line.includes("▎"));
|
|
422
|
+
const top = scrollbox.scrollTop;
|
|
423
|
+
|
|
424
|
+
expect(previousTop - top).toBeLessThanOrEqual(1);
|
|
425
|
+
if (top < previousTop) {
|
|
426
|
+
expect(marker).toBe(0);
|
|
427
|
+
scrolled = true;
|
|
428
|
+
}
|
|
429
|
+
previousTop = top;
|
|
430
|
+
}
|
|
431
|
+
expect(scrolled).toBe(true);
|
|
432
|
+
setup.renderer.destroy();
|
|
433
|
+
});
|