@assistant-ui/react-ink 0.0.12 → 0.0.15
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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +3 -4
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +151 -7
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/useTextBuffer.d.ts +58 -0
- package/dist/primitives/composer/useTextBuffer.d.ts.map +1 -0
- package/dist/primitives/composer/useTextBuffer.js +211 -0
- package/dist/primitives/composer/useTextBuffer.js.map +1 -0
- package/package.json +8 -8
- package/src/index.ts +6 -0
- package/src/primitives/composer/ComposerInput.tsx +194 -10
- package/src/primitives/composer/useTextBuffer.ts +332 -0
- package/src/tests/ComposerInput.test.tsx +527 -0
- package/src/tests/useTextBuffer.test.ts +246 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { cleanup, render } from "ink-testing-library";
|
|
3
|
+
|
|
4
|
+
const mockUseAui = vi.fn();
|
|
5
|
+
const mockUseAuiState = vi.fn();
|
|
6
|
+
const mockUseFocus = vi.fn();
|
|
7
|
+
const mockUseTextBuffer = vi.fn();
|
|
8
|
+
|
|
9
|
+
type UseAuiStateSelector = Parameters<
|
|
10
|
+
typeof import("@assistant-ui/store")["useAuiState"]
|
|
11
|
+
>[0];
|
|
12
|
+
|
|
13
|
+
type InputHandler = (
|
|
14
|
+
input: string,
|
|
15
|
+
key: {
|
|
16
|
+
ctrl?: boolean;
|
|
17
|
+
meta?: boolean;
|
|
18
|
+
shift?: boolean;
|
|
19
|
+
return?: boolean;
|
|
20
|
+
backspace?: boolean;
|
|
21
|
+
delete?: boolean;
|
|
22
|
+
leftArrow?: boolean;
|
|
23
|
+
rightArrow?: boolean;
|
|
24
|
+
upArrow?: boolean;
|
|
25
|
+
downArrow?: boolean;
|
|
26
|
+
home?: boolean;
|
|
27
|
+
end?: boolean;
|
|
28
|
+
},
|
|
29
|
+
) => void;
|
|
30
|
+
|
|
31
|
+
let inputHandler: InputHandler | undefined;
|
|
32
|
+
|
|
33
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
34
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
35
|
+
return {
|
|
36
|
+
...actual,
|
|
37
|
+
useAui: () => mockUseAui(),
|
|
38
|
+
useAuiState: (selector: UseAuiStateSelector) => mockUseAuiState(selector),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
vi.mock("../primitives/composer/useTextBuffer", async (importOriginal) => {
|
|
43
|
+
const actual =
|
|
44
|
+
await importOriginal<
|
|
45
|
+
typeof import("../primitives/composer/useTextBuffer")
|
|
46
|
+
>();
|
|
47
|
+
return {
|
|
48
|
+
...actual,
|
|
49
|
+
useTextBuffer: (text: string) => mockUseTextBuffer(text),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
vi.mock("ink", async (importOriginal) => {
|
|
54
|
+
const actual = await importOriginal<typeof import("ink")>();
|
|
55
|
+
return {
|
|
56
|
+
...actual,
|
|
57
|
+
useFocus: () => mockUseFocus(),
|
|
58
|
+
useInput: (handler: InputHandler) => {
|
|
59
|
+
inputHandler = handler;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
import { ComposerInput } from "../primitives/composer/ComposerInput";
|
|
65
|
+
|
|
66
|
+
const flush = async () => {
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
cleanup();
|
|
72
|
+
vi.clearAllMocks();
|
|
73
|
+
inputHandler = undefined;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("ComposerInput", () => {
|
|
77
|
+
const createBuffer = (text: string, cursorOffset = text.length) => ({
|
|
78
|
+
text,
|
|
79
|
+
cursorOffset,
|
|
80
|
+
preferredColumn: undefined,
|
|
81
|
+
dispatchAction: vi.fn(),
|
|
82
|
+
setText: vi.fn(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("maps navigation and insert keys to the text buffer", async () => {
|
|
86
|
+
const state = { composer: { text: "hello" } };
|
|
87
|
+
const buffer = createBuffer("hello", 3);
|
|
88
|
+
const setText = vi.fn();
|
|
89
|
+
|
|
90
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
91
|
+
selector(state as never),
|
|
92
|
+
);
|
|
93
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
94
|
+
mockUseAui.mockReturnValue({
|
|
95
|
+
composer: () => ({
|
|
96
|
+
send: vi.fn(),
|
|
97
|
+
setText,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
101
|
+
|
|
102
|
+
render(<ComposerInput />);
|
|
103
|
+
await flush();
|
|
104
|
+
|
|
105
|
+
inputHandler?.("", { leftArrow: true });
|
|
106
|
+
inputHandler?.("X", {});
|
|
107
|
+
inputHandler?.("", { backspace: true });
|
|
108
|
+
inputHandler?.("", { delete: true });
|
|
109
|
+
await flush();
|
|
110
|
+
|
|
111
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(1, {
|
|
112
|
+
type: "move-left",
|
|
113
|
+
});
|
|
114
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(2, {
|
|
115
|
+
type: "insert",
|
|
116
|
+
text: "X",
|
|
117
|
+
});
|
|
118
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(3, {
|
|
119
|
+
type: "delete-backward",
|
|
120
|
+
});
|
|
121
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(4, {
|
|
122
|
+
type: "delete-forward",
|
|
123
|
+
});
|
|
124
|
+
expect(setText).toHaveBeenNthCalledWith(1, "heXllo");
|
|
125
|
+
expect(setText).toHaveBeenNthCalledWith(2, "hello");
|
|
126
|
+
expect(setText).toHaveBeenNthCalledWith(3, "helo");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("submits on enter when submitOnEnter is enabled", async () => {
|
|
130
|
+
const send = vi.fn(() => undefined);
|
|
131
|
+
const buffer = createBuffer("hello");
|
|
132
|
+
|
|
133
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
134
|
+
selector({ composer: { text: "hello" } } as never),
|
|
135
|
+
);
|
|
136
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
137
|
+
mockUseAui.mockReturnValue({
|
|
138
|
+
composer: () => ({
|
|
139
|
+
send,
|
|
140
|
+
setText: vi.fn(),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
144
|
+
|
|
145
|
+
render(<ComposerInput submitOnEnter />);
|
|
146
|
+
await flush();
|
|
147
|
+
|
|
148
|
+
inputHandler?.("", { return: true });
|
|
149
|
+
|
|
150
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("uses onSubmit instead of the default send path", async () => {
|
|
154
|
+
const onSubmit = vi.fn();
|
|
155
|
+
const send = vi.fn();
|
|
156
|
+
const buffer = createBuffer("hello");
|
|
157
|
+
|
|
158
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
159
|
+
selector({ composer: { text: "hello" } } as never),
|
|
160
|
+
);
|
|
161
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
162
|
+
mockUseAui.mockReturnValue({
|
|
163
|
+
composer: () => ({
|
|
164
|
+
send,
|
|
165
|
+
setText: vi.fn(),
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
169
|
+
|
|
170
|
+
render(<ComposerInput submitOnEnter onSubmit={onSubmit} />);
|
|
171
|
+
await flush();
|
|
172
|
+
|
|
173
|
+
inputHandler?.("", { return: true });
|
|
174
|
+
|
|
175
|
+
expect(onSubmit).toHaveBeenCalledWith("hello");
|
|
176
|
+
expect(send).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("submits the latest local text after an immediate edit", async () => {
|
|
180
|
+
const onSubmit = vi.fn();
|
|
181
|
+
const buffer = createBuffer("hell", 4);
|
|
182
|
+
|
|
183
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
184
|
+
selector({ composer: { text: "hell" } } as never),
|
|
185
|
+
);
|
|
186
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
187
|
+
mockUseAui.mockReturnValue({
|
|
188
|
+
composer: () => ({
|
|
189
|
+
send: vi.fn(),
|
|
190
|
+
setText: vi.fn(),
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
194
|
+
|
|
195
|
+
render(<ComposerInput submitOnEnter onSubmit={onSubmit} />);
|
|
196
|
+
await flush();
|
|
197
|
+
|
|
198
|
+
inputHandler?.("o", {});
|
|
199
|
+
inputHandler?.("", { return: true });
|
|
200
|
+
|
|
201
|
+
expect(onSubmit).toHaveBeenCalledWith("hello");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("inserts a newline in multi-line mode", async () => {
|
|
205
|
+
const state = { composer: { text: "hello" } };
|
|
206
|
+
const buffer = createBuffer("hello");
|
|
207
|
+
const setText = vi.fn();
|
|
208
|
+
|
|
209
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
210
|
+
selector(state as never),
|
|
211
|
+
);
|
|
212
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
213
|
+
mockUseAui.mockReturnValue({
|
|
214
|
+
composer: () => ({
|
|
215
|
+
send: vi.fn(),
|
|
216
|
+
setText,
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
219
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
220
|
+
|
|
221
|
+
render(<ComposerInput multiLine />);
|
|
222
|
+
await flush();
|
|
223
|
+
|
|
224
|
+
inputHandler?.("", { return: true });
|
|
225
|
+
await flush();
|
|
226
|
+
|
|
227
|
+
expect(buffer.dispatchAction).toHaveBeenCalledWith({
|
|
228
|
+
type: "insert",
|
|
229
|
+
text: "\n",
|
|
230
|
+
});
|
|
231
|
+
expect(setText).toHaveBeenCalledWith("hello\n");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("inserts a newline on ctrl-j in multi-line mode", async () => {
|
|
235
|
+
const state = { composer: { text: "hello" } };
|
|
236
|
+
const buffer = createBuffer("hello");
|
|
237
|
+
const setText = vi.fn();
|
|
238
|
+
|
|
239
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
240
|
+
selector(state as never),
|
|
241
|
+
);
|
|
242
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
243
|
+
mockUseAui.mockReturnValue({
|
|
244
|
+
composer: () => ({
|
|
245
|
+
send: vi.fn(),
|
|
246
|
+
setText,
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
250
|
+
|
|
251
|
+
render(<ComposerInput multiLine submitOnEnter />);
|
|
252
|
+
await flush();
|
|
253
|
+
|
|
254
|
+
inputHandler?.("j", { ctrl: true });
|
|
255
|
+
await flush();
|
|
256
|
+
|
|
257
|
+
expect(buffer.dispatchAction).toHaveBeenCalledWith({
|
|
258
|
+
type: "insert",
|
|
259
|
+
text: "\n",
|
|
260
|
+
});
|
|
261
|
+
expect(setText).toHaveBeenCalledWith("hello\n");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("inserts a newline on shift-enter in multi-line submit mode", async () => {
|
|
265
|
+
const state = { composer: { text: "hello" } };
|
|
266
|
+
const buffer = createBuffer("hello");
|
|
267
|
+
const send = vi.fn();
|
|
268
|
+
const setText = vi.fn();
|
|
269
|
+
|
|
270
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
271
|
+
selector(state as never),
|
|
272
|
+
);
|
|
273
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
274
|
+
mockUseAui.mockReturnValue({
|
|
275
|
+
composer: () => ({
|
|
276
|
+
send,
|
|
277
|
+
setText,
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
281
|
+
|
|
282
|
+
render(<ComposerInput multiLine submitOnEnter />);
|
|
283
|
+
await flush();
|
|
284
|
+
|
|
285
|
+
inputHandler?.("", { return: true, shift: true });
|
|
286
|
+
await flush();
|
|
287
|
+
|
|
288
|
+
expect(buffer.dispatchAction).toHaveBeenCalledWith({
|
|
289
|
+
type: "insert",
|
|
290
|
+
text: "\n",
|
|
291
|
+
});
|
|
292
|
+
expect(setText).toHaveBeenCalledWith("hello\n");
|
|
293
|
+
expect(send).not.toHaveBeenCalled();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("syncs external store resets back into the local buffer", async () => {
|
|
297
|
+
const state = { composer: { text: "hello" } };
|
|
298
|
+
const buffer = createBuffer("hello");
|
|
299
|
+
|
|
300
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
301
|
+
selector(state as never),
|
|
302
|
+
);
|
|
303
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
304
|
+
mockUseAui.mockReturnValue({
|
|
305
|
+
composer: () => ({
|
|
306
|
+
send: vi.fn(),
|
|
307
|
+
setText: vi.fn(),
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
311
|
+
|
|
312
|
+
const instance = render(<ComposerInput placeholder="Type a message..." />);
|
|
313
|
+
await flush();
|
|
314
|
+
|
|
315
|
+
state.composer.text = "";
|
|
316
|
+
instance.rerender(<ComposerInput placeholder="Type a message..." />);
|
|
317
|
+
await flush();
|
|
318
|
+
|
|
319
|
+
expect(buffer.setText).toHaveBeenCalledWith("");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("does not submit on ctrl-j in single-line mode", async () => {
|
|
323
|
+
const send = vi.fn();
|
|
324
|
+
const buffer = createBuffer("hello");
|
|
325
|
+
|
|
326
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
327
|
+
selector({ composer: { text: "hello" } } as never),
|
|
328
|
+
);
|
|
329
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
330
|
+
mockUseAui.mockReturnValue({
|
|
331
|
+
composer: () => ({
|
|
332
|
+
send,
|
|
333
|
+
setText: vi.fn(),
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
337
|
+
|
|
338
|
+
render(<ComposerInput submitOnEnter />);
|
|
339
|
+
await flush();
|
|
340
|
+
|
|
341
|
+
inputHandler?.("j", { ctrl: true, return: true });
|
|
342
|
+
await flush();
|
|
343
|
+
|
|
344
|
+
expect(send).not.toHaveBeenCalled();
|
|
345
|
+
expect(buffer.dispatchAction).not.toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("kills the word after the cursor on alt-d", async () => {
|
|
349
|
+
const buffer = createBuffer("alpha beta gamma", 0);
|
|
350
|
+
const setText = vi.fn();
|
|
351
|
+
|
|
352
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
353
|
+
selector({ composer: { text: "alpha beta gamma" } } as never),
|
|
354
|
+
);
|
|
355
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
356
|
+
mockUseAui.mockReturnValue({
|
|
357
|
+
composer: () => ({
|
|
358
|
+
send: vi.fn(),
|
|
359
|
+
setText,
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
363
|
+
|
|
364
|
+
render(<ComposerInput />);
|
|
365
|
+
await flush();
|
|
366
|
+
|
|
367
|
+
inputHandler?.("d", { meta: true });
|
|
368
|
+
await flush();
|
|
369
|
+
|
|
370
|
+
expect(buffer.dispatchAction).toHaveBeenCalledWith({
|
|
371
|
+
type: "kill-word-forward",
|
|
372
|
+
});
|
|
373
|
+
expect(setText).toHaveBeenCalledWith(" beta gamma");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("dispatches readline-style ctrl bindings", async () => {
|
|
377
|
+
const buffer = createBuffer("alpha beta gamma", 6);
|
|
378
|
+
|
|
379
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
380
|
+
selector({ composer: { text: "alpha beta gamma" } } as never),
|
|
381
|
+
);
|
|
382
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
383
|
+
mockUseAui.mockReturnValue({
|
|
384
|
+
composer: () => ({
|
|
385
|
+
send: vi.fn(),
|
|
386
|
+
setText: vi.fn(),
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
390
|
+
|
|
391
|
+
render(<ComposerInput multiLine />);
|
|
392
|
+
await flush();
|
|
393
|
+
|
|
394
|
+
inputHandler?.("a", { ctrl: true });
|
|
395
|
+
inputHandler?.("e", { ctrl: true });
|
|
396
|
+
inputHandler?.("w", { ctrl: true });
|
|
397
|
+
inputHandler?.("u", { ctrl: true });
|
|
398
|
+
inputHandler?.("k", { ctrl: true });
|
|
399
|
+
inputHandler?.("d", { ctrl: true });
|
|
400
|
+
await flush();
|
|
401
|
+
|
|
402
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(1, {
|
|
403
|
+
type: "move-home",
|
|
404
|
+
multiLine: true,
|
|
405
|
+
});
|
|
406
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(2, {
|
|
407
|
+
type: "move-end",
|
|
408
|
+
multiLine: true,
|
|
409
|
+
});
|
|
410
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(3, {
|
|
411
|
+
type: "kill-word-backward",
|
|
412
|
+
});
|
|
413
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(4, {
|
|
414
|
+
type: "kill-start",
|
|
415
|
+
multiLine: true,
|
|
416
|
+
});
|
|
417
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(5, {
|
|
418
|
+
type: "kill-end",
|
|
419
|
+
multiLine: true,
|
|
420
|
+
});
|
|
421
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(6, {
|
|
422
|
+
type: "delete-forward",
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("dispatches alt-b and alt-f for word navigation", async () => {
|
|
427
|
+
const buffer = createBuffer("alpha beta gamma", 6);
|
|
428
|
+
|
|
429
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
430
|
+
selector({ composer: { text: "alpha beta gamma" } } as never),
|
|
431
|
+
);
|
|
432
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
433
|
+
mockUseAui.mockReturnValue({
|
|
434
|
+
composer: () => ({
|
|
435
|
+
send: vi.fn(),
|
|
436
|
+
setText: vi.fn(),
|
|
437
|
+
}),
|
|
438
|
+
});
|
|
439
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
440
|
+
|
|
441
|
+
render(<ComposerInput />);
|
|
442
|
+
await flush();
|
|
443
|
+
|
|
444
|
+
inputHandler?.("b", { meta: true });
|
|
445
|
+
inputHandler?.("f", { meta: true });
|
|
446
|
+
await flush();
|
|
447
|
+
|
|
448
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(1, {
|
|
449
|
+
type: "move-word-left",
|
|
450
|
+
});
|
|
451
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(2, {
|
|
452
|
+
type: "move-word-right",
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("dispatches Home/End and arrow navigation in multi-line mode", async () => {
|
|
457
|
+
const buffer = createBuffer("one\ntwo\nthree", 5);
|
|
458
|
+
|
|
459
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
460
|
+
selector({ composer: { text: "one\ntwo\nthree" } } as never),
|
|
461
|
+
);
|
|
462
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
463
|
+
mockUseAui.mockReturnValue({
|
|
464
|
+
composer: () => ({
|
|
465
|
+
send: vi.fn(),
|
|
466
|
+
setText: vi.fn(),
|
|
467
|
+
}),
|
|
468
|
+
});
|
|
469
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
470
|
+
|
|
471
|
+
render(<ComposerInput multiLine />);
|
|
472
|
+
await flush();
|
|
473
|
+
|
|
474
|
+
inputHandler?.("", { home: true });
|
|
475
|
+
inputHandler?.("", { end: true });
|
|
476
|
+
inputHandler?.("", { upArrow: true });
|
|
477
|
+
inputHandler?.("", { downArrow: true });
|
|
478
|
+
await flush();
|
|
479
|
+
|
|
480
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(1, {
|
|
481
|
+
type: "move-home",
|
|
482
|
+
multiLine: true,
|
|
483
|
+
});
|
|
484
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(2, {
|
|
485
|
+
type: "move-end",
|
|
486
|
+
multiLine: true,
|
|
487
|
+
});
|
|
488
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(3, {
|
|
489
|
+
type: "move-up",
|
|
490
|
+
});
|
|
491
|
+
expect(buffer.dispatchAction).toHaveBeenNthCalledWith(4, {
|
|
492
|
+
type: "move-down",
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("does not treat local store echoes as external resets", async () => {
|
|
497
|
+
const state = { composer: { text: "hello" } };
|
|
498
|
+
const buffer = createBuffer("hello", 2);
|
|
499
|
+
const setStoreText = vi.fn((text: string) => {
|
|
500
|
+
state.composer.text = text;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
mockUseAuiState.mockImplementation((selector: UseAuiStateSelector) =>
|
|
504
|
+
selector(state as never),
|
|
505
|
+
);
|
|
506
|
+
mockUseTextBuffer.mockReturnValue(buffer);
|
|
507
|
+
mockUseAui.mockReturnValue({
|
|
508
|
+
composer: () => ({
|
|
509
|
+
send: vi.fn(),
|
|
510
|
+
setText: setStoreText,
|
|
511
|
+
}),
|
|
512
|
+
});
|
|
513
|
+
mockUseFocus.mockReturnValue({ isFocused: true });
|
|
514
|
+
|
|
515
|
+
const instance = render(<ComposerInput />);
|
|
516
|
+
await flush();
|
|
517
|
+
|
|
518
|
+
inputHandler?.("", { delete: true });
|
|
519
|
+
await flush();
|
|
520
|
+
|
|
521
|
+
instance.rerender(<ComposerInput />);
|
|
522
|
+
await flush();
|
|
523
|
+
|
|
524
|
+
expect(setStoreText).toHaveBeenCalledWith("helo");
|
|
525
|
+
expect(buffer.setText).not.toHaveBeenCalled();
|
|
526
|
+
});
|
|
527
|
+
});
|