@cueloop/client 0.1.0-alpha.64 → 0.1.0-alpha.66
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.autoclose.test.tsx +2 -2
- package/src/App.diff.test.tsx +8 -73
- package/src/App.export.test.tsx +2 -2
- package/src/App.plan-review.test.tsx +60 -412
- package/src/App.prototype.test.tsx +1 -8
- package/src/App.readonly.test.tsx +85 -62
- package/src/App.rendering.test.tsx +5 -6
- package/src/App.submit-confirm.test.tsx +32 -70
- package/src/App.test.tsx +136 -66
- package/src/App.toast.test.tsx +17 -8
- package/src/App.tsx +243 -284
- package/src/App.walk.test.tsx +1 -1
- package/src/app-screens.tsx +67 -52
- package/src/app-view-model.ts +6 -100
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +2 -2
- package/src/components/AppShell.stories.tsx +84 -0
- package/src/components/AppShell.tsx +127 -0
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +147 -0
- package/src/components/CodeBlock.stories.tsx +1 -1
- package/src/components/CompletionOverlay.stories.tsx +1 -1
- package/src/components/ConfirmCard.stories.tsx +1 -1
- package/src/components/ConfirmCard.tsx +3 -4
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/DiffSheet.stories.tsx +1 -1
- package/src/components/InboxList.stories.tsx +23 -8
- package/src/components/InboxList.tsx +45 -19
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/KeybindsDialog.tsx +61 -17
- package/src/components/MarkerPopover.stories.tsx +1 -1
- package/src/components/MenuBar.stories.tsx +3 -12
- package/src/components/MenuBar.tsx +25 -56
- package/src/components/PromptDialog.stories.tsx +1 -1
- package/src/components/PrototypeSheet.stories.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +15 -1
- package/src/components/ReviewRail.stories.tsx +15 -1
- package/src/components/ReviewRail.tsx +13 -6
- package/src/components/SettingsDialog.stories.tsx +7 -1
- package/src/components/SettingsDialog.tsx +112 -66
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.tsx +49 -0
- package/src/components/ThreadView.selection.test.tsx +812 -0
- package/src/components/ThreadView.slash.test.tsx +113 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +1708 -0
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +57 -0
- package/src/components/Toast.stories.tsx +1 -1
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +156 -0
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +463 -305
- package/src/components/agent-launcher.stories.tsx +1 -1
- package/src/components/file-tree.test.ts +45 -0
- package/src/components/file-tree.ts +57 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +1 -1
- package/src/components/primitives/Card.stories.tsx +1 -1
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +43 -0
- package/src/components/primitives/StatusBar.stories.tsx +1 -1
- package/src/components/primitives/Tabs.stories.tsx +1 -1
- package/src/components/primitives/Toolbar.stories.tsx +1 -1
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +87 -0
- package/src/components/primitives/icons.ts +38 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +1 -1
- package/src/components/session-tree.test.ts +114 -0
- package/src/components/session-tree.ts +94 -0
- package/src/components/stories-app.tsx +142 -82
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +55 -0
- package/src/components/theme-context.stories.tsx +1 -1
- package/src/diff-hunk-curate.ts +4 -104
- package/src/index.ts +1 -0
- package/src/intent-dispatch.test.ts +136 -3
- package/src/intent-dispatch.ts +92 -24
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +4 -4
- package/src/keymap.ts +14 -7
- package/src/session-controller.curate.test.ts +89 -2
- package/src/session-controller.share.test.ts +195 -55
- package/src/session-controller.tree.test.ts +228 -0
- package/src/session-controller.ts +331 -72
- package/src/share.ts +114 -16
- package/src/test-support.test.ts +193 -1
- package/src/test-support.ts +64 -0
- package/src/theme.ts +4 -4
- package/src/thread-chords.test.ts +154 -0
- package/src/thread-chords.ts +144 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +148 -0
- package/src/use-settings-dialog.tsx +9 -0
- package/src/view-plan.ts +29 -12
- package/src/App.review-panel.test.tsx +0 -143
- package/src/components/PlanSheet.stories.tsx +0 -113
- package/src/components/PlanSheet.tsx +0 -419
- package/src/components/plan-sheet-run-style.ts +0 -88
package/src/App.test.tsx
CHANGED
|
@@ -10,8 +10,12 @@ import { DaemonServer } from "@cueloop/daemon";
|
|
|
10
10
|
import type { ReviewSession } from "@cueloop/schema";
|
|
11
11
|
import { App } from "./App";
|
|
12
12
|
import {
|
|
13
|
+
clickText,
|
|
14
|
+
dragText,
|
|
15
|
+
frameRow,
|
|
13
16
|
isolateUserConfig,
|
|
14
17
|
press,
|
|
18
|
+
pressKey,
|
|
15
19
|
typeText as type,
|
|
16
20
|
waitForState,
|
|
17
21
|
waitForText,
|
|
@@ -67,7 +71,7 @@ async function renderApp(sessionId?: string) {
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
describe("plan rendering", () => {
|
|
70
|
-
test("renders the plan with headings, list markers, and the
|
|
74
|
+
test("renders the plan with headings, list markers, and the footer", async () => {
|
|
71
75
|
// Arrange
|
|
72
76
|
const setup = await renderApp();
|
|
73
77
|
|
|
@@ -77,96 +81,97 @@ describe("plan rendering", () => {
|
|
|
77
81
|
expect(frame).toContain("Migration Plan");
|
|
78
82
|
expect(frame).toContain("Context");
|
|
79
83
|
expect(frame).toContain("persists sessions to disk atomically");
|
|
80
|
-
expect(frame).toContain("
|
|
81
|
-
expect(frame).toContain("
|
|
82
|
-
expect(frame).toContain("Submit review");
|
|
84
|
+
expect(frame).toContain("· move the store");
|
|
85
|
+
expect(frame).toContain("send message");
|
|
83
86
|
});
|
|
84
|
-
});
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
test("a direct open still populates the Threads sidebar with other pending reviews", async () => {
|
|
89
|
+
// Arrange - a second pending review exists alongside the one opened directly
|
|
90
|
+
server.core.sessionCreate({
|
|
91
|
+
workspace: { repoRoot: "/repo", branch: "main" },
|
|
92
|
+
artifact: { type: "plan", content: "# Other Plan\n", meta: { title: "Other Plan" } },
|
|
93
|
+
});
|
|
89
94
|
const setup = await renderApp();
|
|
90
95
|
|
|
91
|
-
// Act
|
|
92
|
-
await
|
|
93
|
-
await press(setup, "j");
|
|
94
|
-
await setup.renderOnce();
|
|
95
|
-
|
|
96
|
-
// Assert
|
|
97
|
-
const lines = setup.captureCharFrame().split("\n");
|
|
98
|
-
const cursorLine = lines.find((line) => line.includes("▎"))!;
|
|
96
|
+
// Act - open the collapsed Threads sidebar (the mirrored panel toggle by the gear)
|
|
97
|
+
await setup.mockMouse.click(3, 1);
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
// Assert - the sidebar lists the other pending review, so you can jump to it
|
|
100
|
+
await waitForText(setup, "Other Plan");
|
|
101
101
|
});
|
|
102
|
+
});
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
describe("thread view grammar", () => {
|
|
105
|
+
test("↓ moves the caret between blocks and typing anchors to the block under it", async () => {
|
|
104
106
|
// Arrange
|
|
105
107
|
const setup = await renderApp();
|
|
106
108
|
|
|
107
|
-
// Act
|
|
108
|
-
await
|
|
109
|
-
await
|
|
110
|
-
await
|
|
111
|
-
await setup.renderOnce();
|
|
109
|
+
// Act: two blocks down lands on the paragraph; a printable opens a draft there
|
|
110
|
+
await pressKey(setup, "ARROW_DOWN");
|
|
111
|
+
await pressKey(setup, "ARROW_DOWN");
|
|
112
|
+
await type(setup, "x");
|
|
112
113
|
|
|
113
|
-
// Assert
|
|
114
|
-
await waitForText(setup,
|
|
114
|
+
// Assert: the draft card sits right under the paragraph's row
|
|
115
|
+
await waitForText(setup, "● x");
|
|
116
|
+
expect(frameRow(setup, "● x")).toBeGreaterThan(frameRow(setup, "persists sessions"));
|
|
117
|
+
expect(frameRow(setup, "● x")).toBeLessThan(frameRow(setup, "Steps"));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("comment flow: click a word, type a body, cmd+⏎ saves to the daemon", async () => {
|
|
121
|
+
// Arrange
|
|
122
|
+
const setup = await renderApp();
|
|
115
123
|
|
|
116
124
|
// Act
|
|
125
|
+
await clickText(setup, "daemon");
|
|
117
126
|
await type(setup, "Define atomically.");
|
|
118
|
-
await
|
|
127
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
119
128
|
|
|
120
129
|
// Assert
|
|
121
|
-
await
|
|
130
|
+
await waitForState(setup, () => server.core.sessionGet(session.id).annotations.length === 1);
|
|
122
131
|
const stored = server.core.sessionGet(session.id);
|
|
123
132
|
|
|
124
133
|
expect(stored.annotations.length).toBe(1);
|
|
125
134
|
expect(stored.annotations[0]!.body).toBe("Define atomically.");
|
|
135
|
+
expect(stored.annotations[0]!.anchor.quote).toBe("daemon");
|
|
126
136
|
});
|
|
127
137
|
|
|
128
|
-
test("
|
|
138
|
+
test("a drag marks the exact span the comment anchors to", async () => {
|
|
129
139
|
// Arrange
|
|
130
140
|
const setup = await renderApp();
|
|
131
141
|
|
|
132
|
-
// Act
|
|
133
|
-
await
|
|
134
|
-
await press(setup, "j");
|
|
135
|
-
await press(setup, "v");
|
|
136
|
-
await press(setup, "l"); // "The daemon"
|
|
137
|
-
await press(setup, "c");
|
|
142
|
+
// Act: from the start of "The" to the end of "daemon"
|
|
143
|
+
await dragText(setup, "The daemon", "daemon persists", "daemon".length);
|
|
138
144
|
await type(setup, "Which daemon?");
|
|
139
|
-
await
|
|
145
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
140
146
|
|
|
141
147
|
// Assert
|
|
142
148
|
await waitForState(setup, () => server.core.sessionGet(session.id).annotations.length === 1);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
expect(stored.annotations[0]!.anchor.quote).toBe("The daemon");
|
|
149
|
+
expect(server.core.sessionGet(session.id).annotations[0]!.anchor.quote).toBe("The daemon");
|
|
146
150
|
});
|
|
147
151
|
|
|
148
|
-
test("x cuts
|
|
152
|
+
test("option+x cuts the caret's block into the working copy; option+x restores it", async () => {
|
|
149
153
|
// Arrange
|
|
150
154
|
const setup = await renderApp();
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
for (let i = 0; i < 4; i++) await press(setup, "j");
|
|
156
|
+
await clickText(setup, "move the store");
|
|
154
157
|
|
|
155
158
|
// Act
|
|
156
|
-
await
|
|
159
|
+
await pressKey(setup, "x", { meta: true });
|
|
157
160
|
|
|
158
|
-
// Assert - the
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
+
// Assert - the cut lands in the working copy
|
|
162
|
+
await waitForState(
|
|
163
|
+
setup,
|
|
164
|
+
() => !(server.core.sessionGet(session.id).workingCopy ?? "").includes("move the store"),
|
|
165
|
+
);
|
|
161
166
|
|
|
162
167
|
// Act
|
|
163
|
-
await
|
|
168
|
+
await pressKey(setup, "x", { meta: true });
|
|
164
169
|
|
|
165
170
|
// Assert
|
|
166
171
|
await waitForState(setup, () => server.core.sessionGet(session.id).workingCopy === undefined);
|
|
167
172
|
});
|
|
168
173
|
|
|
169
|
-
test("e runs $EDITOR on the working copy and tracks the diff", async () => {
|
|
174
|
+
test("ctrl+e runs $EDITOR on the working copy and tracks the diff", async () => {
|
|
170
175
|
const script = join(home, "fake-editor.sh");
|
|
171
176
|
|
|
172
177
|
await Bun.write(script, `#!/bin/sh\nsed -i '' 's/atomically/very atomically/' "$1"\n`);
|
|
@@ -177,7 +182,7 @@ describe("keyboard grammar", () => {
|
|
|
177
182
|
const setup = await renderApp();
|
|
178
183
|
|
|
179
184
|
// Act
|
|
180
|
-
await
|
|
185
|
+
await pressKey(setup, "e", { ctrl: true });
|
|
181
186
|
|
|
182
187
|
// Assert
|
|
183
188
|
await waitForText(setup, "[edited]");
|
|
@@ -189,19 +194,17 @@ describe("keyboard grammar", () => {
|
|
|
189
194
|
});
|
|
190
195
|
|
|
191
196
|
describe("submit", () => {
|
|
192
|
-
test("
|
|
197
|
+
test("cmd+⏎ opens the rail confirm card; verdict + summary resolve the session", async () => {
|
|
193
198
|
// Arrange
|
|
194
199
|
const setup = await renderApp();
|
|
195
200
|
|
|
196
|
-
await
|
|
197
|
-
await press(setup, "j");
|
|
198
|
-
await press(setup, "c");
|
|
201
|
+
await clickText(setup, "daemon");
|
|
199
202
|
await type(setup, "Needs a phase list.");
|
|
200
|
-
await
|
|
203
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
201
204
|
await waitForText(setup, "Needs a phase list");
|
|
202
205
|
|
|
203
|
-
// Act
|
|
204
|
-
await
|
|
206
|
+
// Act: with no composer open the same chord opens submit
|
|
207
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
205
208
|
|
|
206
209
|
// Assert
|
|
207
210
|
await waitForText(setup, "[Changes]");
|
|
@@ -226,7 +229,7 @@ describe("submit", () => {
|
|
|
226
229
|
const setup = await renderApp();
|
|
227
230
|
|
|
228
231
|
// Act
|
|
229
|
-
await
|
|
232
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
230
233
|
|
|
231
234
|
// Assert
|
|
232
235
|
await waitForText(setup, "[Approve]"); // no pending items → approve default
|
|
@@ -241,25 +244,24 @@ describe("submit", () => {
|
|
|
241
244
|
});
|
|
242
245
|
|
|
243
246
|
describe("inbox", () => {
|
|
244
|
-
test("inbox mode wears the review
|
|
247
|
+
test("inbox mode wears the review header and opens a session", async () => {
|
|
245
248
|
// Arrange
|
|
246
249
|
const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
|
|
247
250
|
|
|
248
251
|
await waitForText(setup, "cueloop");
|
|
249
252
|
|
|
250
|
-
// Assert - the same header
|
|
253
|
+
// Assert - the same header as review, plus the session row
|
|
251
254
|
const frame = setup.captureCharFrame();
|
|
252
255
|
|
|
253
256
|
expect(frame).toContain("cueloop · resume");
|
|
254
257
|
expect(frame).toContain("Migration Plan");
|
|
255
|
-
expect(frame).toContain("menu"); // the shared MenuBar sits at the bottom
|
|
256
258
|
expect(frame).not.toContain("inbox ("); // the old inline header is gone
|
|
257
259
|
|
|
258
260
|
// Act
|
|
259
261
|
await press(setup, "enter");
|
|
260
262
|
|
|
261
263
|
// Assert
|
|
262
|
-
await waitForText(setup, "
|
|
264
|
+
await waitForText(setup, "send message");
|
|
263
265
|
});
|
|
264
266
|
|
|
265
267
|
test("the menu opens from the inbox and escape is not a trap", async () => {
|
|
@@ -267,12 +269,9 @@ describe("inbox", () => {
|
|
|
267
269
|
const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
|
|
268
270
|
|
|
269
271
|
await waitForText(setup, "cueloop · resume");
|
|
270
|
-
const lines = setup.captureCharFrame().split("\n");
|
|
271
|
-
const menuRow = lines.findIndex((line) => line.includes("menu"));
|
|
272
|
-
const menuColumn = lines[menuRow]!.indexOf("menu");
|
|
273
272
|
|
|
274
|
-
// Act - open the drop-
|
|
275
|
-
await setup.mockMouse.click(
|
|
273
|
+
// Act - open the drop-down from the top-left settings gear (header content row)
|
|
274
|
+
await setup.mockMouse.click(1, 1);
|
|
276
275
|
|
|
277
276
|
// Assert - the drop-up options appear
|
|
278
277
|
await waitForText(setup, "Keybinds");
|
|
@@ -284,6 +283,77 @@ describe("inbox", () => {
|
|
|
284
283
|
await press(setup, "enter");
|
|
285
284
|
|
|
286
285
|
// Assert
|
|
287
|
-
await waitForText(setup, "
|
|
286
|
+
await waitForText(setup, "send message");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("the thread view and the menu", () => {
|
|
291
|
+
test("the keybinds dialog lists the thread view grammar, keeps keys away from the text, and closes on escape", async () => {
|
|
292
|
+
// Arrange
|
|
293
|
+
const setup = await renderApp();
|
|
294
|
+
|
|
295
|
+
await waitForText(setup, "The daemon persists");
|
|
296
|
+
|
|
297
|
+
// Act - open the menu from the top-left settings gear, then the keybinds dialog
|
|
298
|
+
await setup.mockMouse.click(1, 1);
|
|
299
|
+
await waitForText(setup, "Keybinds");
|
|
300
|
+
const dropUp = setup.captureCharFrame().split("\n");
|
|
301
|
+
const keybindsRow = dropUp.findIndex((line) => line.includes("Keybinds"));
|
|
302
|
+
|
|
303
|
+
await setup.mockMouse.click(dropUp[keybindsRow]!.indexOf("Keybinds") + 1, keybindsRow);
|
|
304
|
+
await waitForText(setup, "mark text, across blocks");
|
|
305
|
+
|
|
306
|
+
// Assert - the thread grammar, not the plan sheet's
|
|
307
|
+
const dialog = setup.captureCharFrame();
|
|
308
|
+
|
|
309
|
+
expect(dialog).toContain("⌘⌥m");
|
|
310
|
+
expect(dialog).toContain("place the caret");
|
|
311
|
+
expect(dialog).not.toContain("grow/shrink");
|
|
312
|
+
|
|
313
|
+
// Act - a printable behind the dialog must not open a comment; escape closes it
|
|
314
|
+
await press(setup, "x");
|
|
315
|
+
expect(setup.captureCharFrame()).not.toContain("● x");
|
|
316
|
+
await press(setup, "escape");
|
|
317
|
+
|
|
318
|
+
// Assert
|
|
319
|
+
await waitForState(setup, () => !setup.captureCharFrame().includes("mark text, across blocks"));
|
|
320
|
+
expect(setup.captureCharFrame()).not.toContain("● x");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("clicking the Settings nav group is inert and never corrupts the active category", async () => {
|
|
324
|
+
// Arrange - open the settings dialog from the gear
|
|
325
|
+
const setup = await renderApp();
|
|
326
|
+
|
|
327
|
+
await setup.mockMouse.click(1, 1);
|
|
328
|
+
await waitForText(setup, "Keybinds");
|
|
329
|
+
|
|
330
|
+
// the nav folder is the last "Settings" on screen (the first is the dialog title)
|
|
331
|
+
const lines = setup.captureCharFrame().split("\n");
|
|
332
|
+
let folderRow = -1;
|
|
333
|
+
let folderColumn = -1;
|
|
334
|
+
|
|
335
|
+
lines.forEach((line, row) => {
|
|
336
|
+
const column = line.indexOf("Settings");
|
|
337
|
+
|
|
338
|
+
if (column !== -1) {
|
|
339
|
+
folderColumn = column;
|
|
340
|
+
folderRow = row;
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Act - click the synthetic Settings group folder, then press a nav key. The
|
|
345
|
+
// key handler dereferences the active category, so a corrupt id would throw
|
|
346
|
+
// (the global key handler swallows the throw into console.error).
|
|
347
|
+
const keyHandlerErrors: string[] = [];
|
|
348
|
+
const originalConsoleError = console.error;
|
|
349
|
+
|
|
350
|
+
console.error = (...args: unknown[]) => keyHandlerErrors.push(args.map(String).join(" "));
|
|
351
|
+
await setup.mockMouse.click(folderColumn + 1, folderRow);
|
|
352
|
+
await pressKey(setup, "ARROW_DOWN");
|
|
353
|
+
console.error = originalConsoleError;
|
|
354
|
+
|
|
355
|
+
// Assert - no key-handler crash, and the dialog is still up
|
|
356
|
+
expect(keyHandlerErrors.filter((line) => line.includes("keypress handler"))).toEqual([]);
|
|
357
|
+
expect(setup.captureCharFrame()).toContain("Keybinds");
|
|
288
358
|
});
|
|
289
359
|
});
|
package/src/App.toast.test.tsx
CHANGED
|
@@ -10,7 +10,15 @@ import { DaemonServer } from "@cueloop/daemon";
|
|
|
10
10
|
import type { ReviewSession } from "@cueloop/schema";
|
|
11
11
|
import { App } from "./App";
|
|
12
12
|
import type { ShareTransport } from "./session-controller";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
clickText,
|
|
15
|
+
isolateUserConfig,
|
|
16
|
+
press,
|
|
17
|
+
pressKey,
|
|
18
|
+
typeText,
|
|
19
|
+
waitForText,
|
|
20
|
+
waitForTextGone,
|
|
21
|
+
} from "./test-support";
|
|
14
22
|
|
|
15
23
|
const publishShare = mock(async () => ({ line: "ssh p_share01@cueloop.dev", copied: true }));
|
|
16
24
|
const shareTransport: ShareTransport = {
|
|
@@ -19,8 +27,10 @@ const shareTransport: ShareTransport = {
|
|
|
19
27
|
throw new Error("Unexpected share pull");
|
|
20
28
|
}),
|
|
21
29
|
push: mock(async () => {}),
|
|
30
|
+
watch: () => () => {},
|
|
22
31
|
parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
|
|
23
32
|
collaboratorAnnotations: () => [],
|
|
33
|
+
mergeFromShare: () => ({ annotations: [] }),
|
|
24
34
|
};
|
|
25
35
|
|
|
26
36
|
const PLAN = `# Migration Plan\n\n## Context\n\nThe daemon persists sessions to disk atomically.\n`;
|
|
@@ -63,18 +73,17 @@ describe("share toast", () => {
|
|
|
63
73
|
|
|
64
74
|
await waitForText(setup, "cueloop");
|
|
65
75
|
|
|
66
|
-
// Act: share raises the toast, then open a composer under it
|
|
67
|
-
await
|
|
76
|
+
// Act: share raises the toast, then open a composer under it by typing
|
|
77
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
68
78
|
await waitForText(setup, "share link copied");
|
|
69
|
-
await
|
|
70
|
-
await
|
|
71
|
-
await
|
|
72
|
-
await waitForText(setup, 'comment on "');
|
|
79
|
+
await clickText(setup, "daemon");
|
|
80
|
+
await typeText(setup, "x");
|
|
81
|
+
await waitForText(setup, "● x");
|
|
73
82
|
|
|
74
83
|
// Act: escape must reach the composer, not get eaten by the toast
|
|
75
84
|
await press(setup, "escape");
|
|
76
85
|
|
|
77
86
|
// Assert: the composer closed on the first escape
|
|
78
|
-
await waitForTextGone(setup,
|
|
87
|
+
await waitForTextGone(setup, "● x");
|
|
79
88
|
});
|
|
80
89
|
});
|