@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Observer mode (tier 2): the App with readOnly renders and navigates like the controller's, but every mutating
|
|
1
|
+
/** Observer mode (tier 2): the App with readOnly renders and navigates like the controller's, but every mutating primitive answers "observer - read-only" and leaves daemon state untouched. */
|
|
2
2
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
@@ -9,8 +9,15 @@ import { testRender } from "@opentui/react/test-utils";
|
|
|
9
9
|
import { DaemonServer } from "@cueloop/daemon";
|
|
10
10
|
import { makeAnchor, parseBlocks, type ReviewSession } from "@cueloop/schema";
|
|
11
11
|
import { App } from "./App";
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
clickText,
|
|
14
|
+
dragText,
|
|
15
|
+
frameRow,
|
|
16
|
+
isolateUserConfig,
|
|
17
|
+
pressKey,
|
|
18
|
+
typeText,
|
|
19
|
+
waitForText,
|
|
20
|
+
} from "./test-support";
|
|
14
21
|
|
|
15
22
|
const PLAN = `# Migration Plan
|
|
16
23
|
|
|
@@ -60,7 +67,9 @@ async function renderObserver() {
|
|
|
60
67
|
return setup;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
type Setup = Awaited<ReturnType<typeof renderObserver>>;
|
|
71
|
+
|
|
72
|
+
/** Session state that any mutating primitive would change. */
|
|
64
73
|
function snapshot() {
|
|
65
74
|
const stored = server.core.sessionGet(session.id);
|
|
66
75
|
|
|
@@ -72,79 +81,110 @@ function snapshot() {
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
describe("observer rendering", () => {
|
|
75
|
-
test("shows the observer badge
|
|
84
|
+
test("shows the observer badge in the header", async () => {
|
|
76
85
|
// Arrange
|
|
77
86
|
const setup = await renderObserver();
|
|
78
87
|
|
|
79
88
|
// Assert
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(frame).toContain("· observer");
|
|
83
|
-
expect(frame).toContain("menu");
|
|
89
|
+
expect(setup.captureCharFrame()).toContain("· observer");
|
|
84
90
|
});
|
|
85
91
|
});
|
|
86
92
|
|
|
87
|
-
describe("observer
|
|
88
|
-
|
|
89
|
-
["
|
|
90
|
-
["x", "
|
|
91
|
-
["e", "
|
|
92
|
-
["enter", "
|
|
93
|
-
]
|
|
94
|
-
|
|
93
|
+
describe("observer primitives are blocked", () => {
|
|
94
|
+
const attempts: Array<[string, (setup: Setup) => Promise<void>]> = [
|
|
95
|
+
["comment (typing)", (setup) => typeText(setup, "c")],
|
|
96
|
+
["cut (option+x)", (setup) => pressKey(setup, "x", { meta: true })],
|
|
97
|
+
["edit (ctrl+e)", (setup) => pressKey(setup, "e", { ctrl: true })],
|
|
98
|
+
["submit (cmd+enter)", (setup) => pressKey(setup, "RETURN", { meta: true })],
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const [primitive, attempt] of attempts) {
|
|
102
|
+
test(`${primitive} answers observer - read-only and mutates nothing`, async () => {
|
|
95
103
|
const setup = await renderObserver();
|
|
96
104
|
const before = snapshot();
|
|
97
105
|
|
|
98
|
-
await
|
|
99
|
-
await
|
|
100
|
-
await press(setup, key);
|
|
106
|
+
await clickText(setup, "daemon");
|
|
107
|
+
await attempt(setup);
|
|
101
108
|
const frame = await waitForText(setup, "observer - read-only");
|
|
102
109
|
|
|
103
|
-
// no
|
|
104
|
-
expect(frame).not.toContain(
|
|
105
|
-
expect(frame).not.toContain("
|
|
110
|
+
// no draft card and no submit card ever appear
|
|
111
|
+
expect(frame).not.toContain("● c");
|
|
112
|
+
expect(frame).not.toContain("[Approve]");
|
|
106
113
|
expect(snapshot()).toEqual(before);
|
|
107
114
|
});
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
test("
|
|
117
|
+
test("typing over a marked span is blocked too", async () => {
|
|
111
118
|
// Arrange
|
|
112
119
|
const setup = await renderObserver();
|
|
113
120
|
|
|
114
121
|
// Act
|
|
115
|
-
await
|
|
116
|
-
await
|
|
117
|
-
await press(setup, "v");
|
|
118
|
-
await press(setup, "c");
|
|
119
|
-
await setup.renderOnce();
|
|
122
|
+
await dragText(setup, "The daemon", "daemon persists", "daemon".length);
|
|
123
|
+
await typeText(setup, "c");
|
|
120
124
|
|
|
121
125
|
// Assert
|
|
122
|
-
const frame = setup
|
|
126
|
+
const frame = await waitForText(setup, "observer - read-only");
|
|
123
127
|
|
|
124
|
-
expect(frame).toContain("
|
|
125
|
-
expect(frame).not.toContain('comment on "');
|
|
128
|
+
expect(frame).not.toContain("● c");
|
|
126
129
|
expect(snapshot().annotations).toBe(0);
|
|
127
130
|
});
|
|
128
131
|
});
|
|
129
132
|
|
|
133
|
+
describe("a resolved review is read-only for its owner too", () => {
|
|
134
|
+
test("typing, deleting a card, and editing answer review submitted - read-only", async () => {
|
|
135
|
+
// Arrange: a comment exists and the verdict is in
|
|
136
|
+
server.core.sessionAnnotate(session.id, {
|
|
137
|
+
id: "a_done",
|
|
138
|
+
kind: "comment",
|
|
139
|
+
anchor: makeAnchor(parseBlocks(PLAN), 2, 0, 10),
|
|
140
|
+
body: "settled",
|
|
141
|
+
});
|
|
142
|
+
server.core.sessionResolve(session.id, "approve", "");
|
|
143
|
+
const setup = await testRender(<App home={home} sessionId={session.id} />, {
|
|
144
|
+
width: 120,
|
|
145
|
+
height: 32,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await waitForText(setup, "settled");
|
|
149
|
+
const before = snapshot();
|
|
150
|
+
|
|
151
|
+
// Act + Assert: a draft never opens
|
|
152
|
+
await clickText(setup, "daemon");
|
|
153
|
+
await typeText(setup, "c");
|
|
154
|
+
await waitForText(setup, "review submitted - read-only");
|
|
155
|
+
expect(setup.captureCharFrame()).not.toContain("● c");
|
|
156
|
+
|
|
157
|
+
// Act + Assert: the focused card cannot be deleted or edited
|
|
158
|
+
await pressKey(setup, "n", { meta: true });
|
|
159
|
+
await pressKey(setup, "BACKSPACE", { meta: true });
|
|
160
|
+
await pressKey(setup, "e", { meta: true });
|
|
161
|
+
await pressKey(setup, "x", { meta: true });
|
|
162
|
+
expect(snapshot()).toEqual(before);
|
|
163
|
+
expect(server.core.sessionGet(session.id).annotations).toHaveLength(1);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
130
167
|
describe("observer navigation still works", () => {
|
|
131
|
-
test("
|
|
168
|
+
test("↓ moves the caret between blocks", async () => {
|
|
132
169
|
// Arrange
|
|
133
170
|
const setup = await renderObserver();
|
|
134
171
|
|
|
135
172
|
// Act
|
|
136
|
-
await
|
|
137
|
-
await
|
|
173
|
+
await pressKey(setup, "ARROW_DOWN");
|
|
174
|
+
await pressKey(setup, "ARROW_DOWN");
|
|
138
175
|
await setup.renderOnce();
|
|
139
176
|
|
|
140
|
-
// Assert
|
|
141
|
-
const
|
|
142
|
-
|
|
177
|
+
// Assert: the caret cell is painted on the paragraph's row
|
|
178
|
+
const caretRow = setup
|
|
179
|
+
.captureSpans()
|
|
180
|
+
.lines.findIndex((line) =>
|
|
181
|
+
line.spans.some((span) => span.bg.toInts().slice(0, 3).join() === "86,91,104"),
|
|
182
|
+
);
|
|
143
183
|
|
|
144
|
-
expect(
|
|
184
|
+
expect(caretRow).toBe(frameRow(setup, "persists sessions"));
|
|
145
185
|
});
|
|
146
186
|
|
|
147
|
-
test("n focuses annotations made by the controller", async () => {
|
|
187
|
+
test("option+n focuses annotations made by the controller", async () => {
|
|
148
188
|
// Arrange
|
|
149
189
|
server.core.sessionAnnotate(session.id, {
|
|
150
190
|
id: "a_obs1",
|
|
@@ -154,29 +194,12 @@ describe("observer navigation still works", () => {
|
|
|
154
194
|
});
|
|
155
195
|
const setup = await renderObserver();
|
|
156
196
|
|
|
157
|
-
// Act
|
|
158
|
-
await
|
|
197
|
+
// Act - the observer navigates to the annotation without mutating anything
|
|
198
|
+
await pressKey(setup, "n", { meta: true });
|
|
159
199
|
await setup.renderOnce();
|
|
160
200
|
|
|
161
|
-
// Assert -
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await waitForState(setup, () => hasBorderColor(setup, DARK.blue));
|
|
201
|
+
// Assert - the observer reads the controller's comment inline in the thread
|
|
202
|
+
await waitForText(setup, "From the controller.");
|
|
203
|
+
expect(snapshot().annotations).toBe(1);
|
|
165
204
|
});
|
|
166
205
|
});
|
|
167
|
-
|
|
168
|
-
/** Whether any border character (box-drawing) is painted in the given foreground hex. */
|
|
169
|
-
function hasBorderColor(setup: Awaited<ReturnType<typeof renderObserver>>, hex: string): boolean {
|
|
170
|
-
for (const line of setup.captureSpans().lines) {
|
|
171
|
-
for (const span of line.spans) {
|
|
172
|
-
if (!/[╭╮╰╯│─┌┐└┘]/.test(span.text)) continue;
|
|
173
|
-
const [red, green, blue] = span.fg.toInts();
|
|
174
|
-
const rendered =
|
|
175
|
-
"#" + [red, green, blue].map((part) => part.toString(16).padStart(2, "0")).join("");
|
|
176
|
-
|
|
177
|
-
if (rendered === hex) return true;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
@@ -83,10 +83,9 @@ describe("code blocks", () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
describe("block spacing", () => {
|
|
86
|
-
// the review
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
const planColumn = (line: string): string => line.split("│")[1]?.trimEnd() ?? "";
|
|
86
|
+
// the review rail paints box edges on the right, so read the document
|
|
87
|
+
// column (everything left of the first rail glyph) before asserting spacing
|
|
88
|
+
const planColumn = (line: string): string => line.split(/[│╭╰]/)[0]!.trimEnd();
|
|
90
89
|
|
|
91
90
|
test("a code block never glues to the list item above it", async () => {
|
|
92
91
|
// Arrange
|
|
@@ -129,8 +128,8 @@ describe("block spacing", () => {
|
|
|
129
128
|
.captureCharFrame()
|
|
130
129
|
.split("\n")
|
|
131
130
|
.map((line) => line.trimEnd());
|
|
132
|
-
const one = lines.findIndex((line) => line.includes("
|
|
131
|
+
const one = lines.findIndex((line) => line.includes("· one"));
|
|
133
132
|
|
|
134
|
-
expect(lines[one + 1]).toContain("
|
|
133
|
+
expect(lines[one + 1]).toContain("· two");
|
|
135
134
|
});
|
|
136
135
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The
|
|
1
|
+
/** The send-message confirm: cmd+enter opens a centered overlay with a verdict selector, summary input, and word-buttons. Char-frame assertions over the real App and an in-process daemon, like App.test.tsx. */
|
|
2
2
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
@@ -9,7 +9,14 @@ import { testRender } from "@opentui/react/test-utils";
|
|
|
9
9
|
import { DaemonServer } from "@cueloop/daemon";
|
|
10
10
|
import { makeAnchor, parseBlocks, type ReviewSession } from "@cueloop/schema";
|
|
11
11
|
import { App } from "./App";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isolateUserConfig,
|
|
14
|
+
press,
|
|
15
|
+
pressKey,
|
|
16
|
+
settle,
|
|
17
|
+
waitForText,
|
|
18
|
+
waitForTextGone,
|
|
19
|
+
} from "./test-support";
|
|
13
20
|
|
|
14
21
|
const PLAN = `# Migration Plan
|
|
15
22
|
|
|
@@ -76,33 +83,31 @@ function seedAnnotations(count: number): void {
|
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
describe("
|
|
80
|
-
test("
|
|
86
|
+
describe("send message confirm", () => {
|
|
87
|
+
test("cmd+enter opens the send-message confirm overlay", async () => {
|
|
81
88
|
// Arrange
|
|
82
89
|
const setup = await renderApp();
|
|
83
90
|
|
|
84
|
-
// Assert
|
|
85
|
-
expect(setup.captureCharFrame()).toContain("
|
|
91
|
+
// Assert - the footer carries the send control before the overlay opens
|
|
92
|
+
expect(setup.captureCharFrame()).toContain("send message");
|
|
86
93
|
|
|
87
94
|
// Act
|
|
88
|
-
await
|
|
95
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
89
96
|
|
|
90
|
-
// Assert
|
|
91
|
-
await waitForText(setup, "
|
|
97
|
+
// Assert - the overlay: verdict selector and word-buttons
|
|
98
|
+
await waitForText(setup, "[Approve]"); // nothing pending: approve default
|
|
92
99
|
const frame = setup.captureCharFrame();
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
expect(frame).toContain("
|
|
96
|
-
expect(frame).toContain("[Approve]"); // nothing pending: approve default
|
|
97
|
-
expect(frame).toContain(" Submit ");
|
|
101
|
+
expect(frame).toContain("[Approve]");
|
|
102
|
+
expect(frame).toContain(" send message ");
|
|
98
103
|
expect(frame).toContain(" Cancel ");
|
|
99
104
|
});
|
|
100
105
|
|
|
101
|
-
test("left/right cycles the verdict selector in the
|
|
106
|
+
test("left/right cycles the verdict selector in the overlay", async () => {
|
|
102
107
|
// Arrange
|
|
103
108
|
const setup = await renderApp();
|
|
104
109
|
|
|
105
|
-
await
|
|
110
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
106
111
|
|
|
107
112
|
// Assert
|
|
108
113
|
await waitForText(setup, "[Approve]");
|
|
@@ -126,11 +131,11 @@ describe("rail submit confirm", () => {
|
|
|
126
131
|
await waitForText(setup, "[Changes]");
|
|
127
132
|
});
|
|
128
133
|
|
|
129
|
-
test("esc
|
|
134
|
+
test("esc closes the overlay", async () => {
|
|
130
135
|
// Arrange
|
|
131
136
|
const setup = await renderApp();
|
|
132
137
|
|
|
133
|
-
await
|
|
138
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
134
139
|
|
|
135
140
|
// Assert
|
|
136
141
|
await waitForText(setup, "[Approve]");
|
|
@@ -138,34 +143,28 @@ describe("rail submit confirm", () => {
|
|
|
138
143
|
// Act
|
|
139
144
|
await press(setup, "escape");
|
|
140
145
|
|
|
141
|
-
// Assert
|
|
142
|
-
// a bare ESC settles after the parser's escape-sequence window
|
|
146
|
+
// Assert - a bare ESC settles after the parser's escape-sequence window
|
|
143
147
|
const frame = await waitForTextGone(setup, "[Approve]");
|
|
144
148
|
|
|
145
|
-
expect(frame).not.toContain("
|
|
146
|
-
expect(frame).toContain("Submit review");
|
|
149
|
+
expect(frame).not.toContain(" Cancel ");
|
|
147
150
|
});
|
|
148
151
|
|
|
149
|
-
test("enter in the
|
|
152
|
+
test("enter in the overlay resolves the session through the controller", async () => {
|
|
150
153
|
// Arrange
|
|
151
154
|
seedAnnotations(1);
|
|
152
155
|
const setup = await renderApp();
|
|
153
156
|
|
|
154
157
|
// Act
|
|
155
|
-
await
|
|
158
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
156
159
|
|
|
157
160
|
// Assert
|
|
158
161
|
await waitForText(setup, "[Changes]"); // pending items: request changes default
|
|
159
|
-
const frame = setup.captureCharFrame();
|
|
160
|
-
|
|
161
|
-
expect(frame).toContain("submit review");
|
|
162
162
|
|
|
163
163
|
// Act
|
|
164
164
|
await setup.mockInput.typeText("Tighten the steps.");
|
|
165
165
|
await press(setup, "enter");
|
|
166
166
|
|
|
167
|
-
// Assert
|
|
168
|
-
// the completion flow after submit is unchanged
|
|
167
|
+
// Assert - the completion flow after submit is unchanged
|
|
169
168
|
await waitForText(setup, "feedback sent");
|
|
170
169
|
const stored = server.core.sessionGet(session.id);
|
|
171
170
|
|
|
@@ -173,57 +172,20 @@ describe("rail submit confirm", () => {
|
|
|
173
172
|
expect(stored.verdict!.kind).toBe("request_changes");
|
|
174
173
|
});
|
|
175
174
|
|
|
176
|
-
test("
|
|
177
|
-
// Arrange
|
|
178
|
-
seedAnnotations(12);
|
|
179
|
-
const setup = await renderApp();
|
|
180
|
-
|
|
181
|
-
// Act
|
|
182
|
-
// walk annotation focus to the last card: the rail scrolls it into view
|
|
183
|
-
for (let index = 0; index < 12; index++) await press(setup, "n");
|
|
184
|
-
|
|
185
|
-
// Assert
|
|
186
|
-
await waitForText(setup, "note 12");
|
|
187
|
-
expect(setup.captureCharFrame()).not.toContain("note 01");
|
|
188
|
-
|
|
189
|
-
// Act
|
|
190
|
-
await press(setup, "enter");
|
|
191
|
-
|
|
192
|
-
// Assert
|
|
193
|
-
await waitForText(setup, "[Changes]");
|
|
194
|
-
// the card is pinned outside the scrollbox; the stack stays scrolled off the top
|
|
195
|
-
expect(setup.captureCharFrame()).not.toContain("note 01");
|
|
196
|
-
|
|
197
|
-
// Act
|
|
198
|
-
// the stack still scrolls with the card open: wheel over the rail
|
|
199
|
-
// brings the last card back into view while the confirm card stays put
|
|
200
|
-
for (let turn = 0; turn < 12; turn++) await setup.mockMouse.scroll(100, 10, "down");
|
|
201
|
-
await settle(setup);
|
|
202
|
-
|
|
203
|
-
// Assert
|
|
204
|
-
await waitForText(setup, "note 12");
|
|
205
|
-
const scrolledWithCard = setup.captureCharFrame();
|
|
206
|
-
|
|
207
|
-
expect(scrolledWithCard).toContain("submit review");
|
|
208
|
-
expect(scrolledWithCard).not.toContain("note 01");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("read-only observers never see the confirm card", async () => {
|
|
175
|
+
test("read-only observers cannot open the confirm overlay", async () => {
|
|
212
176
|
// Arrange
|
|
213
177
|
seedAnnotations(1);
|
|
214
178
|
const setup = await renderApp({ readOnly: true });
|
|
215
179
|
|
|
216
180
|
// Act
|
|
217
|
-
await
|
|
181
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
182
|
+
await settle(setup);
|
|
218
183
|
|
|
219
|
-
// Assert
|
|
220
|
-
await waitForText(setup, "observer - read-only");
|
|
184
|
+
// Assert - no overlay, and the session stays pending
|
|
221
185
|
const frame = setup.captureCharFrame();
|
|
222
186
|
|
|
223
|
-
expect(frame).toContain("observer - read-only");
|
|
224
|
-
expect(frame).not.toContain("1 annotations");
|
|
225
187
|
expect(frame).not.toContain("[Changes]");
|
|
226
|
-
expect(
|
|
188
|
+
expect(frame).not.toContain(" Cancel ");
|
|
227
189
|
expect(server.core.sessionGet(session.id).status).toBe("pending");
|
|
228
190
|
});
|
|
229
191
|
});
|