@cueloop/client 0.1.0-alpha.4 → 0.1.0-alpha.41
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/native/build.sh +56 -0
- package/native/darwin-arm64/libcueloopvt.dylib +0 -0
- package/native/src/shim.c +107 -0
- package/package.json +34 -24
- package/src/App.autoclose.test.tsx +137 -0
- package/src/App.diff.test.tsx +158 -22
- package/src/App.export.test.tsx +44 -37
- package/src/App.plan-review.test.tsx +632 -0
- package/src/App.readonly.test.tsx +60 -36
- package/src/App.rendering.test.tsx +132 -0
- package/src/App.review-panel.test.tsx +136 -0
- package/src/App.submit-confirm.test.tsx +220 -0
- package/src/App.test.tsx +128 -69
- package/src/App.toast.test.tsx +75 -0
- package/src/App.tsx +0 -0
- package/src/App.walk.test.tsx +288 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +38 -0
- package/src/clipboard.test.ts +23 -0
- package/src/clipboard.ts +30 -0
- package/src/components/AnnotationCard.compose-rows.test.ts +49 -0
- package/src/components/AnnotationCard.stories.tsx +94 -0
- package/src/components/AnnotationCard.tsx +228 -0
- package/src/components/Breadcrumb.stories.tsx +19 -0
- package/src/components/Breadcrumb.tsx +44 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +83 -0
- package/src/components/CompletionOverlay.stories.tsx +23 -0
- package/src/components/CompletionOverlay.tsx +59 -0
- package/src/components/ConfirmCard.stories.tsx +56 -0
- package/src/components/ConfirmCard.tsx +135 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +64 -0
- package/src/components/DiffSheet.scroll.test.tsx +123 -0
- package/src/components/DiffSheet.stories.tsx +78 -0
- package/src/components/DiffSheet.syntax.test.tsx +62 -0
- package/src/components/DiffSheet.tsx +355 -0
- package/src/components/InboxList.stories.tsx +31 -0
- package/src/components/InboxList.tsx +62 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +58 -0
- package/src/components/MarkerPopover.stories.tsx +58 -0
- package/src/components/MarkerPopover.tsx +102 -0
- package/src/components/MenuBar.stories.tsx +37 -0
- package/src/components/MenuBar.tsx +74 -0
- package/src/components/PlanSheet.stories.tsx +113 -0
- package/src/components/PlanSheet.tsx +389 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.tsx +78 -0
- package/src/components/ReviewPanel.stories.tsx +86 -0
- package/src/components/ReviewPanel.tsx +101 -0
- package/src/components/ReviewRail.stories.tsx +220 -0
- package/src/components/ReviewRail.tsx +324 -0
- package/src/components/SettingsDialog.stories.tsx +111 -0
- package/src/components/SettingsDialog.tsx +141 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +108 -0
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +44 -0
- package/src/components/WalkWizard.stories.tsx +100 -0
- package/src/components/WalkWizard.tsx +152 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +1829 -0
- package/src/components/agent-launcher.stories.tsx +26 -0
- package/src/components/agent-launcher.test.tsx +21 -0
- package/src/components/agent-launcher.tsx +180 -0
- package/src/components/diff-sheet-layout.test.ts +127 -0
- package/src/components/diff-sheet-layout.ts +167 -0
- package/src/components/primitives/Button.stories.tsx +32 -0
- package/src/components/primitives/Button.tsx +48 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +59 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.tsx +64 -0
- package/src/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/components/primitives/StatusBar.tsx +22 -0
- package/src/components/primitives/Tabs.stories.tsx +29 -0
- package/src/components/primitives/Tabs.tsx +98 -0
- package/src/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/components/primitives/Toolbar.tsx +14 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +14 -0
- package/src/components/quick-actions-editor.stories.tsx +54 -0
- package/src/components/quick-actions-editor.tsx +136 -0
- package/src/components/stories-app.tsx +123 -0
- package/src/components/stories.test.tsx +62 -0
- package/src/components/story-fixtures.ts +98 -0
- package/src/components/story.ts +69 -0
- package/src/components/syntax-highlight.ts +92 -0
- package/src/components/terminal-pane.test.tsx +40 -0
- package/src/components/terminal-pane.ts +174 -0
- package/src/components/theme-context.stories.tsx +46 -0
- package/src/components/theme-context.tsx +36 -0
- package/src/components/truncate-text.ts +6 -0
- package/src/config.test.ts +430 -31
- package/src/config.ts +304 -26
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +233 -0
- package/src/diff-intraline.test.ts +164 -0
- package/src/diff-intraline.ts +132 -0
- package/src/diff-syntax.test.ts +96 -0
- package/src/diff-syntax.ts +154 -0
- package/src/editor.test.ts +143 -0
- package/src/editor.ts +123 -9
- package/src/ghostty-terminal.test.ts +45 -0
- package/src/ghostty-terminal.ts +168 -0
- package/src/herdr.test.ts +41 -0
- package/src/herdr.ts +27 -0
- package/src/index.ts +10 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +30 -0
- package/src/intent-dispatch.test.ts +492 -0
- package/src/intent-dispatch.ts +428 -0
- package/src/key-bindings.test.ts +152 -0
- package/src/key-bindings.ts +424 -0
- package/src/keymap.test.ts +622 -0
- package/src/keymap.ts +339 -0
- package/src/review-panel.test.ts +113 -0
- package/src/review-panel.ts +83 -0
- package/src/run.ts +9 -5
- package/src/serve.test.ts +24 -9
- package/src/serve.ts +7 -7
- package/src/session-controller.curate.test.ts +253 -0
- package/src/session-controller.share.test.ts +309 -0
- package/src/session-controller.ts +900 -0
- package/src/share.ts +113 -0
- package/src/test-support.test.ts +55 -0
- package/src/test-support.ts +187 -0
- package/src/theme-presets.ts +168 -0
- package/src/theme.ts +36 -15
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +19 -8
- package/src/view-diff.ts +34 -23
- package/src/view-plan.test.ts +294 -0
- package/src/view-plan.ts +408 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +70 -0
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The intent reducer in isolation: with the App component's dependencies
|
|
3
|
+
* mocked, each Intent maps to exactly the controller call or state update it
|
|
4
|
+
* should - the navigation clamps, the annotation wrap-around, the verdict
|
|
5
|
+
* cycle, and the submit-reveal fix, none of which needed a rendered TUI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
9
|
+
import type { ReviewSession } from "@cueloop/schema";
|
|
10
|
+
import { createIntentDispatch, type IntentDispatchDeps } from "./intent-dispatch";
|
|
11
|
+
import type { DisplayBlock } from "./view-plan";
|
|
12
|
+
|
|
13
|
+
function block(text: string): DisplayBlock {
|
|
14
|
+
return { text, kind: "text", work: true } as unknown as DisplayBlock;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function sessionWith(annotationIds: string[]): ReviewSession {
|
|
18
|
+
return {
|
|
19
|
+
annotations: annotationIds.map((id) => ({ id })),
|
|
20
|
+
workingCopy: undefined,
|
|
21
|
+
} as unknown as ReviewSession;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A controller where every verb is a mock; annotate returns undefined by default. */
|
|
25
|
+
function baseController() {
|
|
26
|
+
return {
|
|
27
|
+
setStatus: mock(),
|
|
28
|
+
open: mock(),
|
|
29
|
+
deleteSession: mock(),
|
|
30
|
+
setSelfName: mock(),
|
|
31
|
+
cut: mock(),
|
|
32
|
+
curationItems: mock(() => []),
|
|
33
|
+
restoreCuration: mock(),
|
|
34
|
+
annotate: mock(),
|
|
35
|
+
updateAnnotation: mock(),
|
|
36
|
+
removeAnnotation: mock(),
|
|
37
|
+
walkStart: mock(),
|
|
38
|
+
walkForward: mock(),
|
|
39
|
+
walkBack: mock(),
|
|
40
|
+
walkLeave: mock(),
|
|
41
|
+
submit: mock(),
|
|
42
|
+
share: mock(),
|
|
43
|
+
finishReview: mock(),
|
|
44
|
+
optInAutoClose: mock(),
|
|
45
|
+
dismissCompletion: mock(),
|
|
46
|
+
saveReviewPanel: mock(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A deps bag where every effect is a mock and every read has a plain default. */
|
|
51
|
+
function makeDeps(overrides: Partial<IntentDispatchDeps> = {}): IntentDispatchDeps {
|
|
52
|
+
const controller = baseController();
|
|
53
|
+
return {
|
|
54
|
+
controller: controller as unknown as IntentDispatchDeps["controller"],
|
|
55
|
+
onExit: mock(),
|
|
56
|
+
isDiff: false,
|
|
57
|
+
display: [],
|
|
58
|
+
rows: [],
|
|
59
|
+
cursor: 0,
|
|
60
|
+
inbox: null,
|
|
61
|
+
inboxCursor: 0,
|
|
62
|
+
mode: { type: "normal" },
|
|
63
|
+
session: null,
|
|
64
|
+
reviewMode: "expanded",
|
|
65
|
+
reviewWidth: 34,
|
|
66
|
+
terminalWidth: 120,
|
|
67
|
+
focusedAnnotationId: undefined,
|
|
68
|
+
selectedCurationId: undefined,
|
|
69
|
+
authorNames: {},
|
|
70
|
+
quickActions: [],
|
|
71
|
+
renameAuthor: mock(),
|
|
72
|
+
liveInput: { current: "" },
|
|
73
|
+
reviewWidthRef: { current: 34 },
|
|
74
|
+
planSheetRef: { current: null },
|
|
75
|
+
setCursor: mock(),
|
|
76
|
+
setInboxCursor: mock(),
|
|
77
|
+
setMode: mock(),
|
|
78
|
+
setReviewMode: mock(),
|
|
79
|
+
setReviewWidth: mock(),
|
|
80
|
+
setRailTab: mock(),
|
|
81
|
+
setFocusedAnnotationId: mock(),
|
|
82
|
+
setSelectedCurationId: mock(),
|
|
83
|
+
setPulsedAnnotationId: mock(),
|
|
84
|
+
selectCardFromDocument: mock(),
|
|
85
|
+
runEditorHandOff: mock(),
|
|
86
|
+
openCardEdit: mock(),
|
|
87
|
+
...overrides,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
describe("move", () => {
|
|
92
|
+
test("down clamps at the last row and up clamps at the first", () => {
|
|
93
|
+
// Arrange
|
|
94
|
+
const deps = makeDeps({ display: [block("a"), block("b"), block("c")] });
|
|
95
|
+
const dispatch = createIntentDispatch(deps);
|
|
96
|
+
|
|
97
|
+
// Act
|
|
98
|
+
dispatch({ type: "move", to: "down" });
|
|
99
|
+
|
|
100
|
+
// Assert
|
|
101
|
+
const advance = (deps.setCursor as ReturnType<typeof mock>).mock.calls[0]![0] as (
|
|
102
|
+
current: number,
|
|
103
|
+
) => number;
|
|
104
|
+
expect(advance(2)).toBe(2); // already at the end, stays
|
|
105
|
+
expect(advance(0)).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("bottom jumps to the last navigable row", () => {
|
|
109
|
+
// Arrange
|
|
110
|
+
const deps = makeDeps({ display: [block("a"), block("b"), block("c")] });
|
|
111
|
+
const dispatch = createIntentDispatch(deps);
|
|
112
|
+
|
|
113
|
+
// Act
|
|
114
|
+
dispatch({ type: "move", to: "bottom" });
|
|
115
|
+
|
|
116
|
+
// Assert
|
|
117
|
+
expect(deps.setCursor).toHaveBeenCalledWith(2);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("annotation navigation", () => {
|
|
122
|
+
test("nextAnnotation wraps from the last annotation back to the first", () => {
|
|
123
|
+
// Arrange
|
|
124
|
+
const deps = makeDeps({ session: sessionWith(["a1", "a2", "a3"]), focusedAnnotationId: "a3" });
|
|
125
|
+
const dispatch = createIntentDispatch(deps);
|
|
126
|
+
|
|
127
|
+
// Act
|
|
128
|
+
dispatch({ type: "nextAnnotation" });
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
expect(deps.selectCardFromDocument).toHaveBeenCalledWith("a1");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("prevAnnotation wraps from the first annotation to the last", () => {
|
|
135
|
+
// Arrange
|
|
136
|
+
const deps = makeDeps({ session: sessionWith(["a1", "a2", "a3"]), focusedAnnotationId: "a1" });
|
|
137
|
+
const dispatch = createIntentDispatch(deps);
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
dispatch({ type: "prevAnnotation" });
|
|
141
|
+
|
|
142
|
+
// Assert
|
|
143
|
+
expect(deps.selectCardFromDocument).toHaveBeenCalledWith("a3");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("cycling skips annotations a revision already addressed", () => {
|
|
147
|
+
// Arrange: a2 is addressed, so next from a1 lands on a3
|
|
148
|
+
const session = sessionWith(["a1", "a2", "a3"]);
|
|
149
|
+
(session.annotations[1] as { resolution?: object }).resolution = {
|
|
150
|
+
revision: 2,
|
|
151
|
+
source: "agent",
|
|
152
|
+
};
|
|
153
|
+
const deps = makeDeps({ session, focusedAnnotationId: "a1" });
|
|
154
|
+
const dispatch = createIntentDispatch(deps);
|
|
155
|
+
|
|
156
|
+
// Act
|
|
157
|
+
dispatch({ type: "nextAnnotation" });
|
|
158
|
+
|
|
159
|
+
// Assert
|
|
160
|
+
expect(deps.selectCardFromDocument).toHaveBeenCalledWith("a3");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("cycleVerdict", () => {
|
|
165
|
+
test("advances through the verdict list in the submit overlay", () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const deps = makeDeps({ mode: { type: "submit", verdict: "approve", summary: "" } });
|
|
168
|
+
const dispatch = createIntentDispatch(deps);
|
|
169
|
+
|
|
170
|
+
// Act
|
|
171
|
+
dispatch({ type: "cycleVerdict", direction: 1 });
|
|
172
|
+
|
|
173
|
+
// Assert
|
|
174
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
175
|
+
type: "submit",
|
|
176
|
+
verdict: "request_changes",
|
|
177
|
+
summary: "",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("marker-actions popover", () => {
|
|
183
|
+
const span = { displayIndex: 3, wordIndex: 0, wordEnd: 0, start: 2, end: 9 };
|
|
184
|
+
|
|
185
|
+
test("spanCut cuts the span's block and closes the popover", () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const deps = makeDeps({ mode: { type: "span", span } });
|
|
188
|
+
const dispatch = createIntentDispatch(deps);
|
|
189
|
+
|
|
190
|
+
// Act
|
|
191
|
+
dispatch({ type: "spanCut" });
|
|
192
|
+
|
|
193
|
+
// Assert
|
|
194
|
+
expect(deps.controller.cut).toHaveBeenCalledWith(3);
|
|
195
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("openSpanActions enters the list at index 0", () => {
|
|
199
|
+
// Arrange
|
|
200
|
+
const deps = makeDeps({ mode: { type: "span", span } });
|
|
201
|
+
const dispatch = createIntentDispatch(deps);
|
|
202
|
+
|
|
203
|
+
// Act
|
|
204
|
+
dispatch({ type: "openSpanActions" });
|
|
205
|
+
|
|
206
|
+
// Assert
|
|
207
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "spanActions", span, index: 0 });
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("moveSpanAction clamps within the actions list", () => {
|
|
211
|
+
// Arrange
|
|
212
|
+
const deps = makeDeps({
|
|
213
|
+
mode: { type: "spanActions", span, index: 1 },
|
|
214
|
+
quickActions: [{ prompt: "one" }, { prompt: "two" }],
|
|
215
|
+
});
|
|
216
|
+
const dispatch = createIntentDispatch(deps);
|
|
217
|
+
|
|
218
|
+
// Act
|
|
219
|
+
dispatch({ type: "moveSpanAction", direction: 1 });
|
|
220
|
+
|
|
221
|
+
// Assert - already at the last row, so the index holds
|
|
222
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "spanActions", span, index: 1 });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("pickSpanAction inserts the preset comment and returns to normal", () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const annotate = mock(() => "an_new");
|
|
228
|
+
const deps = makeDeps({
|
|
229
|
+
mode: { type: "spanActions", span, index: 1 },
|
|
230
|
+
session: sessionWith([]),
|
|
231
|
+
quickActions: [{ prompt: "Needs a test" }, { prompt: "YAGNI", metadata: "cut scope" }],
|
|
232
|
+
controller: { ...baseController(), annotate } as unknown as IntentDispatchDeps["controller"],
|
|
233
|
+
});
|
|
234
|
+
const dispatch = createIntentDispatch(deps);
|
|
235
|
+
|
|
236
|
+
// Act
|
|
237
|
+
dispatch({ type: "pickSpanAction" });
|
|
238
|
+
|
|
239
|
+
// Assert - metadata joins the prompt with a blank line
|
|
240
|
+
expect(annotate).toHaveBeenCalledWith("comment", 3, 2, 9, "YAGNI\n\ncut scope");
|
|
241
|
+
expect(deps.setFocusedAnnotationId).toHaveBeenCalledWith("an_new");
|
|
242
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("pickSpanAction honors an explicit index (mouse click)", () => {
|
|
246
|
+
// Arrange
|
|
247
|
+
const annotate = mock(() => "an_new");
|
|
248
|
+
const deps = makeDeps({
|
|
249
|
+
mode: { type: "spanActions", span, index: 0 },
|
|
250
|
+
session: sessionWith([]),
|
|
251
|
+
quickActions: [{ prompt: "Needs a test" }, { prompt: "Extract the duplication" }],
|
|
252
|
+
controller: { ...baseController(), annotate } as unknown as IntentDispatchDeps["controller"],
|
|
253
|
+
});
|
|
254
|
+
const dispatch = createIntentDispatch(deps);
|
|
255
|
+
|
|
256
|
+
// Act
|
|
257
|
+
dispatch({ type: "pickSpanAction", index: 1 });
|
|
258
|
+
|
|
259
|
+
// Assert
|
|
260
|
+
expect(annotate).toHaveBeenCalledWith("comment", 3, 2, 9, "Extract the duplication");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("closeSpanActions returns to the span toolbar", () => {
|
|
264
|
+
// Arrange
|
|
265
|
+
const deps = makeDeps({ mode: { type: "spanActions", span, index: 2 } });
|
|
266
|
+
const dispatch = createIntentDispatch(deps);
|
|
267
|
+
|
|
268
|
+
// Act
|
|
269
|
+
dispatch({ type: "closeSpanActions" });
|
|
270
|
+
|
|
271
|
+
// Assert
|
|
272
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "span", span });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("openSubmit", () => {
|
|
277
|
+
test("force-opens the review rail so the confirm card can never be hidden", () => {
|
|
278
|
+
// Arrange
|
|
279
|
+
const deps = makeDeps({ session: sessionWith([]), reviewMode: "hidden" });
|
|
280
|
+
const dispatch = createIntentDispatch(deps);
|
|
281
|
+
|
|
282
|
+
// Act
|
|
283
|
+
dispatch({ type: "openSubmit" });
|
|
284
|
+
|
|
285
|
+
// Assert
|
|
286
|
+
expect(deps.setReviewMode).toHaveBeenCalledWith("expanded");
|
|
287
|
+
expect(deps.setRailTab).toHaveBeenCalledWith("review");
|
|
288
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "submit", verdict: "approve", summary: "" });
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("does nothing without a session", () => {
|
|
292
|
+
// Arrange
|
|
293
|
+
const deps = makeDeps({ session: null });
|
|
294
|
+
const dispatch = createIntentDispatch(deps);
|
|
295
|
+
|
|
296
|
+
// Act
|
|
297
|
+
dispatch({ type: "openSubmit" });
|
|
298
|
+
|
|
299
|
+
// Assert
|
|
300
|
+
expect(deps.setMode).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe("share", () => {
|
|
305
|
+
test("dispatches to the controller's share", () => {
|
|
306
|
+
// Arrange
|
|
307
|
+
const deps = makeDeps();
|
|
308
|
+
const dispatch = createIntentDispatch(deps);
|
|
309
|
+
|
|
310
|
+
// Act
|
|
311
|
+
dispatch({ type: "share" });
|
|
312
|
+
|
|
313
|
+
// Assert
|
|
314
|
+
expect(deps.controller.share).toHaveBeenCalled();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("restoreCuration", () => {
|
|
319
|
+
const items = [
|
|
320
|
+
{
|
|
321
|
+
id: "diff:f#0#hunk",
|
|
322
|
+
source: "diff" as const,
|
|
323
|
+
label: "f:1 - hunk",
|
|
324
|
+
preview: [],
|
|
325
|
+
revealIndex: 1,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "diff:f#1#2",
|
|
329
|
+
source: "diff" as const,
|
|
330
|
+
label: "f:9 - change",
|
|
331
|
+
preview: [],
|
|
332
|
+
revealIndex: 9,
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
test("restores the selected item and clears the selection", () => {
|
|
337
|
+
// Arrange
|
|
338
|
+
const deps = makeDeps({ selectedCurationId: "diff:f#1#2" });
|
|
339
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue(items);
|
|
340
|
+
const dispatch = createIntentDispatch(deps);
|
|
341
|
+
|
|
342
|
+
// Act
|
|
343
|
+
dispatch({ type: "restoreCuration" });
|
|
344
|
+
|
|
345
|
+
// Assert
|
|
346
|
+
expect(deps.controller.restoreCuration).toHaveBeenCalledWith("diff:f#1#2");
|
|
347
|
+
expect(deps.setSelectedCurationId).toHaveBeenCalledWith(undefined);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("with nothing selected, undoes the last removal", () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const deps = makeDeps({ selectedCurationId: undefined });
|
|
353
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue(items);
|
|
354
|
+
const dispatch = createIntentDispatch(deps);
|
|
355
|
+
|
|
356
|
+
// Act
|
|
357
|
+
dispatch({ type: "restoreCuration" });
|
|
358
|
+
|
|
359
|
+
// Assert
|
|
360
|
+
expect(deps.controller.restoreCuration).toHaveBeenCalledWith("diff:f#1#2");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("does nothing when there is nothing curated out", () => {
|
|
364
|
+
// Arrange
|
|
365
|
+
const deps = makeDeps();
|
|
366
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue([]);
|
|
367
|
+
const dispatch = createIntentDispatch(deps);
|
|
368
|
+
|
|
369
|
+
// Act
|
|
370
|
+
dispatch({ type: "restoreCuration" });
|
|
371
|
+
|
|
372
|
+
// Assert
|
|
373
|
+
expect(deps.controller.restoreCuration).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe("exit", () => {
|
|
378
|
+
test("calls the exit hook with 0", () => {
|
|
379
|
+
// Arrange
|
|
380
|
+
const deps = makeDeps();
|
|
381
|
+
const dispatch = createIntentDispatch(deps);
|
|
382
|
+
|
|
383
|
+
// Act
|
|
384
|
+
dispatch({ type: "exit" });
|
|
385
|
+
|
|
386
|
+
// Assert
|
|
387
|
+
expect(deps.onExit).toHaveBeenCalledWith(0);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("inbox delete", () => {
|
|
392
|
+
test("requestDeleteSession opens the confirm on the cursor row", () => {
|
|
393
|
+
// Arrange
|
|
394
|
+
const inbox = [{ id: "ses_1", artifact: { meta: { title: "Plan A" } } }] as never;
|
|
395
|
+
const deps = makeDeps({ inbox, inboxCursor: 0 });
|
|
396
|
+
|
|
397
|
+
// Act
|
|
398
|
+
createIntentDispatch(deps)({ type: "requestDeleteSession" });
|
|
399
|
+
|
|
400
|
+
// Assert
|
|
401
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
402
|
+
type: "confirmDelete",
|
|
403
|
+
sessionId: "ses_1",
|
|
404
|
+
title: "Plan A",
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("confirmDialog deletes the session and closes", () => {
|
|
409
|
+
// Arrange
|
|
410
|
+
const deps = makeDeps({ mode: { type: "confirmDelete", sessionId: "ses_1", title: "Plan A" } });
|
|
411
|
+
|
|
412
|
+
// Act
|
|
413
|
+
createIntentDispatch(deps)({ type: "confirmDialog" });
|
|
414
|
+
|
|
415
|
+
// Assert
|
|
416
|
+
expect(deps.controller.deleteSession).toHaveBeenCalledWith("ses_1");
|
|
417
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("rename author", () => {
|
|
422
|
+
test("openRename seeds the prompt with the author's current local name", () => {
|
|
423
|
+
// Arrange
|
|
424
|
+
const session = { annotations: [{ id: "a1", author: "SHA256:x" }] } as never;
|
|
425
|
+
const deps = makeDeps({
|
|
426
|
+
session,
|
|
427
|
+
focusedAnnotationId: "a1",
|
|
428
|
+
authorNames: { "SHA256:x": "Alex" },
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Act
|
|
432
|
+
createIntentDispatch(deps)({ type: "openRename" });
|
|
433
|
+
|
|
434
|
+
// Assert
|
|
435
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
436
|
+
type: "rename",
|
|
437
|
+
authorId: "SHA256:x",
|
|
438
|
+
text: "Alex",
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("openRename on your own note does nothing but explain", () => {
|
|
443
|
+
// Arrange
|
|
444
|
+
const session = { annotations: [{ id: "a1" }] } as never;
|
|
445
|
+
const deps = makeDeps({ session, focusedAnnotationId: "a1" });
|
|
446
|
+
|
|
447
|
+
// Act
|
|
448
|
+
createIntentDispatch(deps)({ type: "openRename" });
|
|
449
|
+
|
|
450
|
+
// Assert
|
|
451
|
+
expect(deps.controller.setStatus).toHaveBeenCalled();
|
|
452
|
+
expect(deps.setMode).not.toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("confirmDialog in rename mode persists the trimmed name and closes", () => {
|
|
456
|
+
// Arrange
|
|
457
|
+
const deps = makeDeps({ mode: { type: "rename", authorId: "SHA256:x", text: " Alex " } });
|
|
458
|
+
|
|
459
|
+
// Act
|
|
460
|
+
createIntentDispatch(deps)({ type: "confirmDialog" });
|
|
461
|
+
|
|
462
|
+
// Assert
|
|
463
|
+
expect(deps.renameAuthor).toHaveBeenCalledWith("SHA256:x", "Alex");
|
|
464
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
describe("collaborator self-name", () => {
|
|
469
|
+
test("confirmDialog in nameSelf mode records the trimmed name and closes", () => {
|
|
470
|
+
// Arrange
|
|
471
|
+
const deps = makeDeps({ mode: { type: "nameSelf", text: " Robin " } });
|
|
472
|
+
|
|
473
|
+
// Act
|
|
474
|
+
createIntentDispatch(deps)({ type: "confirmDialog" });
|
|
475
|
+
|
|
476
|
+
// Assert
|
|
477
|
+
expect(deps.controller.setSelfName).toHaveBeenCalledWith("Robin");
|
|
478
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("an empty name still closes - the collaborator stays anonymous", () => {
|
|
482
|
+
// Arrange
|
|
483
|
+
const deps = makeDeps({ mode: { type: "nameSelf", text: " " } });
|
|
484
|
+
|
|
485
|
+
// Act
|
|
486
|
+
createIntentDispatch(deps)({ type: "confirmDialog" });
|
|
487
|
+
|
|
488
|
+
// Assert
|
|
489
|
+
expect(deps.controller.setSelfName).toHaveBeenCalledWith("");
|
|
490
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
491
|
+
});
|
|
492
|
+
});
|