@cueloop/client 0.1.0-alpha.77 → 0.1.0-alpha.79
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 +14 -13
- package/src/App.autoclose.test.tsx +3 -3
- package/src/App.diff.test.tsx +22 -5
- package/src/App.export.test.tsx +1 -2
- package/src/App.focus.test.ts +1 -59
- package/src/App.identity.test.tsx +3 -3
- package/src/App.plan-render.test.tsx +209 -0
- package/src/App.plan-review.test.tsx +91 -26
- package/src/App.readonly.test.tsx +6 -5
- package/src/App.share-dialog.test.tsx +172 -0
- package/src/App.submit-confirm.test.tsx +39 -8
- package/src/App.test.tsx +33 -30
- package/src/App.toast.test.tsx +11 -4
- package/src/App.tsx +263 -274
- package/src/App.walk.test.tsx +11 -10
- package/src/App.workbench.test.tsx +86 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
- package/src/annotation-palette.ts +1 -0
- package/src/ansi-screen.test.ts +28 -0
- package/src/ansi-screen.ts +72 -0
- package/src/app-screens.tsx +19 -9
- package/src/app-view-model.ts +3 -1
- package/src/components/AnnotatableFileView.tsx +1 -1
- package/src/components/AnnotationCards.tsx +18 -2
- package/src/components/AnsiScreen.stories.tsx +12 -0
- package/src/components/AnsiScreen.tsx +28 -0
- package/src/components/CompletionOverlay.stories.tsx +15 -1
- package/src/components/CompletionOverlay.tsx +22 -5
- package/src/components/ConfirmCard.stories.tsx +2 -0
- package/src/components/ConfirmCard.tsx +45 -18
- package/src/components/ConfirmDialog.stories.tsx +3 -3
- package/src/components/ConfirmDialog.tsx +11 -12
- package/src/components/DiffContentView.tsx +18 -11
- package/src/components/DiscussionMarkerRail.tsx +9 -4
- package/src/components/FileContentsView.tsx +1 -1
- package/src/components/GridTabContent.tsx +8 -2
- package/src/components/HandleChipsInput.stories.tsx +24 -0
- package/src/components/HandleChipsInput.test.tsx +35 -0
- package/src/components/HandleChipsInput.tsx +145 -0
- package/src/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/components/MarkdownGridBlock.tsx +72 -0
- package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/components/MarkdownThreadEditor.tsx +162 -0
- package/src/components/NavModeHint.stories.tsx +40 -0
- package/src/components/NavModeHint.tsx +31 -0
- package/src/components/PromptDialog.test.tsx +2 -2
- package/src/components/PromptDialog.tsx +9 -10
- package/src/components/SettingsDialog.tsx +1 -0
- package/src/components/ShareDialog.stories.tsx +121 -0
- package/src/components/ShareDialog.tsx +552 -0
- package/src/components/SlashComposer.stories.tsx +37 -0
- package/src/components/SlashComposer.tsx +87 -0
- package/src/components/ThreadFooter.tsx +4 -1
- package/src/components/ThreadTree.tsx +12 -7
- package/src/components/ThreadView.selection.test.tsx +88 -1
- package/src/components/ThreadView.slash.test.tsx +0 -36
- package/src/components/ThreadView.tsx +200 -67
- package/src/components/Toast.stories.tsx +7 -0
- package/src/components/Toast.tsx +15 -8
- package/src/components/WalkWizard.stories.tsx +2 -2
- package/src/components/WalkWizard.tsx +10 -12
- package/src/components/__snapshots__/stories.test.tsx.snap +436 -182
- package/src/components/primitives/Button.tsx +6 -2
- package/src/components/primitives/Card.tsx +5 -2
- package/src/components/primitives/Dialog.tsx +2 -2
- package/src/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/components/primitives/DialogActions.tsx +39 -0
- package/src/components/primitives/Switch.stories.tsx +17 -0
- package/src/components/primitives/Switch.tsx +32 -0
- package/src/components/primitives/Tree.tsx +5 -1
- package/src/components/primitives/frame.ts +5 -0
- package/src/components/session-tree.ts +7 -6
- package/src/components/share-dialog-store.ts +58 -0
- package/src/components/stories-app.tsx +13 -4
- package/src/components/stories.test.tsx +12 -4
- package/src/components/story-tree.test.ts +32 -24
- package/src/components/story.ts +32 -15
- package/src/config.test.ts +18 -0
- package/src/config.ts +8 -1
- package/src/diff-fold.ts +1 -1
- package/src/diff-intraline.test.ts +75 -2
- package/src/diff-intraline.ts +95 -26
- package/src/intent-dispatch.test.ts +12 -3
- package/src/intent-dispatch.ts +10 -25
- package/src/key-bindings.ts +0 -6
- package/src/keymap.test.ts +2 -2
- package/src/keymap.ts +0 -2
- package/src/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown-grid.test.ts +101 -0
- package/src/markdown-grid.ts +203 -0
- package/src/markdown-highlight.test.ts +61 -0
- package/src/markdown-highlight.ts +122 -0
- package/src/pasted-image.test.ts +67 -0
- package/src/pasted-image.ts +60 -0
- package/src/prototype-browser.test.ts +9 -1
- package/src/prototype-browser.ts +22 -0
- package/src/run.ts +39 -25
- package/src/serve.ts +1 -1
- package/src/share.ts +16 -2
- package/src/slash-palette.test.ts +17 -0
- package/src/slash-palette.ts +11 -0
- package/src/test-support.ts +14 -0
- package/src/thread-chords.test.ts +55 -178
- package/src/thread-chords.ts +78 -169
- package/src/thread-controller.curate.test.ts +1 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.live-diff.test.ts +3 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.share.test.ts +60 -5
- package/src/thread-controller.switch-cache.test.ts +176 -0
- package/src/thread-controller.tree.test.ts +6 -1
- package/src/thread-controller.ts +386 -78
- package/src/use-annotation-surface.tsx +88 -63
- package/src/use-settings-dialog.tsx +14 -3
- package/src/use-thread-body-editing.tsx +58 -0
- package/src/view-plan.test.ts +56 -6
- package/src/view-plan.ts +154 -5
- package/src/App.share-choice.test.tsx +0 -105
- package/src/components/ManageAccessDialog.stories.tsx +0 -36
- package/src/components/ManageAccessDialog.test.tsx +0 -94
- package/src/components/ManageAccessDialog.tsx +0 -120
- package/src/components/ShareChoiceDialog.stories.tsx +0 -34
- package/src/components/ShareChoiceDialog.test.tsx +0 -103
- package/src/components/ShareChoiceDialog.tsx +0 -82
- package/src/components/ThreadsSidebar.stories.tsx +0 -36
- package/src/components/ThreadsSidebar.tsx +0 -71
- package/src/components/agent-launcher.stories.tsx +0 -26
- package/src/components/agent-launcher.test.tsx +0 -50
- package/src/components/agent-launcher.tsx +0 -149
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** The share shortcut opens the two-column share dialog reachable from any view. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
9
|
+
import { DaemonServer } from "@cueloop/daemon";
|
|
10
|
+
import type { Thread } from "@cueloop/schema";
|
|
11
|
+
import { App } from "./App";
|
|
12
|
+
import type { ShareTransport } from "./thread-controller";
|
|
13
|
+
import {
|
|
14
|
+
isolateUserConfig,
|
|
15
|
+
press,
|
|
16
|
+
pressKey,
|
|
17
|
+
settle,
|
|
18
|
+
waitForText,
|
|
19
|
+
waitForTextGone,
|
|
20
|
+
} from "./test-support";
|
|
21
|
+
|
|
22
|
+
const shareTransport: ShareTransport = {
|
|
23
|
+
publish: mock(async () => ({ line: "ssh p_share01@cueloop.dev", copied: true })),
|
|
24
|
+
pull: mock(async () => {
|
|
25
|
+
throw new Error("Unexpected share pull");
|
|
26
|
+
}),
|
|
27
|
+
push: mock(async () => {}),
|
|
28
|
+
watch: () => () => {},
|
|
29
|
+
revoke: async () => {},
|
|
30
|
+
parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
|
|
31
|
+
formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
|
|
32
|
+
collaboratorAnnotations: () => [],
|
|
33
|
+
mergeFromShare: () => ({ annotations: [] }),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const PLAN = `# Migration Plan\n\n## Context\n\nThe daemon persists sessions to disk atomically.\n`;
|
|
37
|
+
|
|
38
|
+
let home: string;
|
|
39
|
+
let restoreUserConfig: () => void;
|
|
40
|
+
let server: DaemonServer;
|
|
41
|
+
let session: Thread;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
home = mkdtempSync(join(tmpdir(), "cueloop-share-choice-"));
|
|
45
|
+
restoreUserConfig = isolateUserConfig(home);
|
|
46
|
+
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
47
|
+
server.start();
|
|
48
|
+
session = server.core.sessionCreate({
|
|
49
|
+
workspace: { repoRoot: "/repo", branch: "main" },
|
|
50
|
+
artifact: {
|
|
51
|
+
type: "plan",
|
|
52
|
+
content: PLAN,
|
|
53
|
+
meta: { title: "Migration Plan", planPath: "plan.md" },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
restoreUserConfig();
|
|
59
|
+
server.stop();
|
|
60
|
+
rmSync(home, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("share dialog", () => {
|
|
64
|
+
test("the shortcut opens the two-column share dialog", async () => {
|
|
65
|
+
// Arrange
|
|
66
|
+
const setup = await testRender(
|
|
67
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
68
|
+
{ width: 120, height: 32 },
|
|
69
|
+
);
|
|
70
|
+
await waitForText(setup, "cueloop");
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
74
|
+
|
|
75
|
+
// Assert - the nav categories and the empty-state new-link affordance
|
|
76
|
+
await waitForText(setup, "share externally");
|
|
77
|
+
await waitForText(setup, "export");
|
|
78
|
+
await waitForText(setup, "+ new link");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("new link opens the wizard, and require auth reveals the allowlist", async () => {
|
|
82
|
+
// Arrange
|
|
83
|
+
const setup = await testRender(
|
|
84
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
85
|
+
{ width: 120, height: 32 },
|
|
86
|
+
);
|
|
87
|
+
await waitForText(setup, "cueloop");
|
|
88
|
+
|
|
89
|
+
// Act: open the dialog, step into the body, activate "+ new link"
|
|
90
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
91
|
+
await waitForText(setup, "+ new link");
|
|
92
|
+
await press(setup, "enter");
|
|
93
|
+
await press(setup, "enter");
|
|
94
|
+
|
|
95
|
+
// Assert - the wizard's fields
|
|
96
|
+
await waitForText(setup, "link name");
|
|
97
|
+
await waitForText(setup, "require auth");
|
|
98
|
+
|
|
99
|
+
// Act: focus the auth row and switch it on (enter toggles the focused row)
|
|
100
|
+
await press(setup, "down");
|
|
101
|
+
await press(setup, "enter");
|
|
102
|
+
|
|
103
|
+
// Assert - the handles input appears with its grayed placeholder
|
|
104
|
+
await waitForText(setup, "handle");
|
|
105
|
+
|
|
106
|
+
// Act: backspace on the empty allowlist collapses require auth back off
|
|
107
|
+
await press(setup, "backspace");
|
|
108
|
+
|
|
109
|
+
// Assert - the handles section is gone
|
|
110
|
+
await waitForTextGone(setup, "handle");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("a private link with no handles cannot be created", async () => {
|
|
114
|
+
// Arrange
|
|
115
|
+
const setup = await testRender(
|
|
116
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
117
|
+
{ width: 120, height: 32 },
|
|
118
|
+
);
|
|
119
|
+
await waitForText(setup, "cueloop");
|
|
120
|
+
|
|
121
|
+
// Act: open the wizard, turn auth on but add no handles, then try to create
|
|
122
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
123
|
+
await waitForText(setup, "+ new link");
|
|
124
|
+
await press(setup, "enter"); // into the body
|
|
125
|
+
await press(setup, "enter"); // open the wizard
|
|
126
|
+
await waitForText(setup, "require auth");
|
|
127
|
+
await press(setup, "down"); // focus the auth row
|
|
128
|
+
await press(setup, "enter"); // toggle auth on (focus moves to the empty allowlist)
|
|
129
|
+
await press(setup, "down"); // move to the create action
|
|
130
|
+
await press(setup, "enter"); // create is blocked while the allowlist is empty
|
|
131
|
+
await settle(setup);
|
|
132
|
+
|
|
133
|
+
// Assert - the wizard is still open (nothing was created and the dialog did not fall back to the list)
|
|
134
|
+
expect(setup.captureCharFrame()).toContain("require auth");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("right enters the links body and left returns to the nav", async () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
const setup = await testRender(
|
|
140
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
141
|
+
{ width: 120, height: 32 },
|
|
142
|
+
);
|
|
143
|
+
await waitForText(setup, "cueloop");
|
|
144
|
+
|
|
145
|
+
// Act + Assert: right arrow crosses into the body (its hint appears)
|
|
146
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
147
|
+
await waitForText(setup, "+ new link");
|
|
148
|
+
await press(setup, "right");
|
|
149
|
+
await waitForText(setup, "h back");
|
|
150
|
+
|
|
151
|
+
// Act + Assert: left arrow returns to the nav (its hint returns)
|
|
152
|
+
await press(setup, "left");
|
|
153
|
+
await waitForText(setup, "l/enter open");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("escape closes the dialog", async () => {
|
|
157
|
+
// Arrange
|
|
158
|
+
const setup = await testRender(
|
|
159
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
160
|
+
{ width: 120, height: 32 },
|
|
161
|
+
);
|
|
162
|
+
await waitForText(setup, "cueloop");
|
|
163
|
+
|
|
164
|
+
// Act
|
|
165
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
166
|
+
await waitForText(setup, "share externally");
|
|
167
|
+
await press(setup, "escape");
|
|
168
|
+
|
|
169
|
+
// Assert
|
|
170
|
+
await waitForTextGone(setup, "share externally");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -99,8 +99,8 @@ describe("send message confirm", () => {
|
|
|
99
99
|
const frame = setup.captureCharFrame();
|
|
100
100
|
|
|
101
101
|
expect(frame).toContain("[Approve]");
|
|
102
|
-
expect(frame).toContain(" send
|
|
103
|
-
expect(frame).toContain("
|
|
102
|
+
expect(frame).toContain(" send ");
|
|
103
|
+
expect(frame).toContain(" cancel ");
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test("left/right cycles the verdict selector in the overlay", async () => {
|
|
@@ -146,7 +146,7 @@ describe("send message confirm", () => {
|
|
|
146
146
|
// Assert - a bare ESC settles after the parser's escape-sequence window
|
|
147
147
|
const frame = await waitForTextGone(setup, "[Approve]");
|
|
148
148
|
|
|
149
|
-
expect(frame).not.toContain("
|
|
149
|
+
expect(frame).not.toContain(" cancel ");
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
test("enter in the overlay resolves the session through the controller", async () => {
|
|
@@ -157,12 +157,14 @@ describe("send message confirm", () => {
|
|
|
157
157
|
// Act
|
|
158
158
|
await pressKey(setup, "RETURN", { meta: true });
|
|
159
159
|
|
|
160
|
-
// Assert
|
|
161
|
-
await waitForText(setup, "[
|
|
160
|
+
// Assert - opens on the default verdict, then cycle to request changes
|
|
161
|
+
await waitForText(setup, "[Approve]");
|
|
162
|
+
await press(setup, "right");
|
|
163
|
+
await waitForText(setup, "[Changes]");
|
|
162
164
|
|
|
163
165
|
// Act
|
|
164
166
|
await setup.mockInput.typeText("Tighten the steps.");
|
|
165
|
-
await
|
|
167
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
166
168
|
|
|
167
169
|
// Assert - the completion flow after submit is unchanged
|
|
168
170
|
await waitForText(setup, "feedback sent");
|
|
@@ -172,6 +174,35 @@ describe("send message confirm", () => {
|
|
|
172
174
|
expect(stored.verdict!.kind).toBe("request_changes");
|
|
173
175
|
});
|
|
174
176
|
|
|
177
|
+
test("typing / in the summary opens the skills/actions palette", async () => {
|
|
178
|
+
// Arrange
|
|
179
|
+
seedAnnotations(1);
|
|
180
|
+
const setup = await renderApp();
|
|
181
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
182
|
+
await waitForText(setup, "[Approve]");
|
|
183
|
+
|
|
184
|
+
// Act
|
|
185
|
+
await setup.mockInput.typeText("/restate");
|
|
186
|
+
|
|
187
|
+
// Assert - the same slash palette the inline composer shows
|
|
188
|
+
await waitForText(setup, "restate-simplified");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("pasting an image into the summary drops in an [Image #n] placeholder", async () => {
|
|
192
|
+
// Arrange
|
|
193
|
+
const setup = await renderApp();
|
|
194
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
195
|
+
await waitForText(setup, "[Approve]");
|
|
196
|
+
|
|
197
|
+
// Act - a burst of control bytes stands in for the binary an image paste delivers
|
|
198
|
+
await setup.mockInput.typeText("look at this ");
|
|
199
|
+
await setup.mockInput.pasteBracketedText("");
|
|
200
|
+
|
|
201
|
+
// Assert - the raw bytes never reach the draft; a numbered placeholder does
|
|
202
|
+
await waitForText(setup, "[Image #1]");
|
|
203
|
+
expect(setup.captureCharFrame()).toContain("look at this [Image #1]");
|
|
204
|
+
});
|
|
205
|
+
|
|
175
206
|
test("read-only observers cannot open the confirm overlay", async () => {
|
|
176
207
|
// Arrange
|
|
177
208
|
seedAnnotations(1);
|
|
@@ -184,8 +215,8 @@ describe("send message confirm", () => {
|
|
|
184
215
|
// Assert - no overlay, and the session stays pending
|
|
185
216
|
const frame = setup.captureCharFrame();
|
|
186
217
|
|
|
187
|
-
expect(frame).not.toContain("[
|
|
188
|
-
expect(frame).not.toContain("
|
|
218
|
+
expect(frame).not.toContain("[Approve]");
|
|
219
|
+
expect(frame).not.toContain(" cancel ");
|
|
189
220
|
expect(server.core.sessionGet(session.id).status).toBe("pending");
|
|
190
221
|
});
|
|
191
222
|
});
|
package/src/App.test.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isolateUserConfig,
|
|
16
16
|
locateText,
|
|
17
17
|
press,
|
|
18
|
+
navCommand,
|
|
18
19
|
pressKey,
|
|
19
20
|
renderReadyApp,
|
|
20
21
|
typeText as type,
|
|
@@ -152,7 +153,7 @@ describe("thread view grammar", () => {
|
|
|
152
153
|
await clickText(setup, "move the store");
|
|
153
154
|
|
|
154
155
|
// Act
|
|
155
|
-
await
|
|
156
|
+
await navCommand(setup, "x");
|
|
156
157
|
|
|
157
158
|
// Assert - the cut lands in the working copy
|
|
158
159
|
await waitForState(
|
|
@@ -161,31 +162,31 @@ describe("thread view grammar", () => {
|
|
|
161
162
|
);
|
|
162
163
|
|
|
163
164
|
// Act
|
|
164
|
-
await
|
|
165
|
+
await navCommand(setup, "x");
|
|
165
166
|
|
|
166
167
|
// Assert
|
|
167
168
|
await waitForState(setup, () => server.core.sessionGet(session.id).workingCopy === undefined);
|
|
168
169
|
});
|
|
169
170
|
|
|
170
|
-
test("ctrl+e
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
171
|
+
test("ctrl+e opens the inline editor; the header toggles to normal and leaving tracks the edit", async () => {
|
|
172
|
+
// Arrange
|
|
173
|
+
const setup = await renderApp();
|
|
174
|
+
|
|
175
|
+
// Act - open the inline markdown editor over the thread body
|
|
176
|
+
await pressKey(setup, "e", { ctrl: true });
|
|
177
|
+
|
|
178
|
+
// Assert - the editor owns the pane (its save hint shows) and the header offers the way back
|
|
179
|
+
await waitForText(setup, "save & close");
|
|
180
|
+
expect(setup.captureCharFrame()).toContain("normal");
|
|
181
|
+
|
|
182
|
+
// Act - type into the body, then leave through the header toggle
|
|
183
|
+
await type(setup, "MORE");
|
|
184
|
+
await clickText(setup, "normal");
|
|
185
|
+
|
|
186
|
+
// Assert - the edit is tracked into the working copy, and the read-only view is back
|
|
187
|
+
await waitForState(setup, () =>
|
|
188
|
+
(server.core.sessionGet(session.id).workingCopy ?? "").includes("MORE"),
|
|
189
|
+
);
|
|
189
190
|
});
|
|
190
191
|
});
|
|
191
192
|
|
|
@@ -202,12 +203,14 @@ describe("submit", () => {
|
|
|
202
203
|
// Act: with no composer open the same chord opens submit
|
|
203
204
|
await pressKey(setup, "RETURN", { meta: true });
|
|
204
205
|
|
|
205
|
-
// Assert
|
|
206
|
-
await waitForText(setup, "[
|
|
206
|
+
// Assert - the card opens on the default verdict, approve
|
|
207
|
+
await waitForText(setup, "[Approve]");
|
|
207
208
|
|
|
208
|
-
// Act
|
|
209
|
+
// Act - cycle to request changes, then send with a summary
|
|
210
|
+
await press(setup, "right");
|
|
211
|
+
await waitForText(setup, "[Changes]");
|
|
209
212
|
await type(setup, "Expand the steps.");
|
|
210
|
-
await
|
|
213
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
211
214
|
|
|
212
215
|
// Assert
|
|
213
216
|
await waitForText(setup, "feedback sent");
|
|
@@ -231,7 +234,7 @@ describe("submit", () => {
|
|
|
231
234
|
await waitForText(setup, "[Approve]"); // no pending items → approve default
|
|
232
235
|
|
|
233
236
|
// Act
|
|
234
|
-
await
|
|
237
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
235
238
|
|
|
236
239
|
// Assert
|
|
237
240
|
await waitForState(setup, () => server.core.sessionGet(session.id).verdict !== undefined);
|
|
@@ -333,7 +336,7 @@ describe("no-thread shell", () => {
|
|
|
333
336
|
|
|
334
337
|
// Assert - the settings dialog with its Keybinds leaf appears
|
|
335
338
|
await waitForText(setup, "Keybinds");
|
|
336
|
-
expect(setup.captureCharFrame()).toContain("
|
|
339
|
+
expect(setup.captureCharFrame()).toContain("settings");
|
|
337
340
|
|
|
338
341
|
// Act - escape closes the menu (not a trap), and the thread nav still works
|
|
339
342
|
await press(setup, "escape");
|
|
@@ -364,7 +367,7 @@ describe("the thread view and the menu", () => {
|
|
|
364
367
|
// Assert - the thread grammar, not the plan sheet's
|
|
365
368
|
const dialog = setup.captureCharFrame();
|
|
366
369
|
|
|
367
|
-
expect(dialog).toContain("
|
|
370
|
+
expect(dialog).toContain("nav mode");
|
|
368
371
|
expect(dialog).toContain("place the caret");
|
|
369
372
|
expect(dialog).not.toContain("grow/shrink");
|
|
370
373
|
|
|
@@ -385,13 +388,13 @@ describe("the thread view and the menu", () => {
|
|
|
385
388
|
await setup.mockMouse.click(1, 0);
|
|
386
389
|
await waitForText(setup, "Keybinds");
|
|
387
390
|
|
|
388
|
-
// the nav folder is the last "
|
|
391
|
+
// the nav folder is the last "settings" on screen (the first is the dialog title)
|
|
389
392
|
const lines = setup.captureCharFrame().split("\n");
|
|
390
393
|
let folderRow = -1;
|
|
391
394
|
let folderColumn = -1;
|
|
392
395
|
|
|
393
396
|
lines.forEach((line, row) => {
|
|
394
|
-
const column = line.indexOf("
|
|
397
|
+
const column = line.indexOf("settings");
|
|
395
398
|
|
|
396
399
|
if (column !== -1) {
|
|
397
400
|
folderColumn = column;
|
package/src/App.toast.test.tsx
CHANGED
|
@@ -28,7 +28,9 @@ const shareTransport: ShareTransport = {
|
|
|
28
28
|
}),
|
|
29
29
|
push: mock(async () => {}),
|
|
30
30
|
watch: () => () => {},
|
|
31
|
+
revoke: async () => {},
|
|
31
32
|
parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
|
|
33
|
+
formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
|
|
32
34
|
collaboratorAnnotations: () => [],
|
|
33
35
|
mergeFromShare: () => ({ annotations: [] }),
|
|
34
36
|
};
|
|
@@ -73,11 +75,16 @@ describe("share toast", () => {
|
|
|
73
75
|
|
|
74
76
|
await waitForText(setup, "cueloop");
|
|
75
77
|
|
|
76
|
-
// Act: share opens the
|
|
78
|
+
// Act: share opens the dialog, the new-link wizard publishes and raises the toast, then a composer under it
|
|
77
79
|
await pressKey(setup, "s", { ctrl: true });
|
|
78
|
-
await waitForText(setup, "
|
|
79
|
-
await press(setup, "enter");
|
|
80
|
-
await
|
|
80
|
+
await waitForText(setup, "+ new link");
|
|
81
|
+
await press(setup, "enter"); // step into the links body
|
|
82
|
+
await press(setup, "enter"); // activate "+ new link" - the wizard opens
|
|
83
|
+
await waitForText(setup, "link name");
|
|
84
|
+
await press(setup, "enter"); // the name field submits and creates the link
|
|
85
|
+
await waitForText(setup, "link copied");
|
|
86
|
+
await press(setup, "escape"); // close the dialog; the non-modal toast stays up
|
|
87
|
+
await waitForTextGone(setup, "share externally");
|
|
81
88
|
await clickText(setup, "daemon");
|
|
82
89
|
await typeText(setup, "x");
|
|
83
90
|
await waitForText(setup, "● x");
|