@assistant-ui/react-ink 0.0.12 β†’ 0.0.13

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.
@@ -0,0 +1,246 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createTextBufferState,
4
+ getGraphemeAt,
5
+ textBufferReducer,
6
+ type TextBufferAction,
7
+ type TextBufferState,
8
+ } from "../primitives/composer/useTextBuffer";
9
+
10
+ const reduce = (
11
+ state: TextBufferState,
12
+ ...actions: TextBufferAction[]
13
+ ): TextBufferState => {
14
+ return actions.reduce(textBufferReducer, state);
15
+ };
16
+
17
+ describe("textBufferReducer", () => {
18
+ it("inserts text at the cursor and advances the cursor", () => {
19
+ const state = reduce(
20
+ createTextBufferState("helo"),
21
+ { type: "set-cursor", cursorOffset: 2 },
22
+ { type: "insert", text: "l" },
23
+ );
24
+
25
+ expect(state.text).toBe("hello");
26
+ expect(state.cursorOffset).toBe(3);
27
+ });
28
+
29
+ it("deletes backward and forward at the cursor", () => {
30
+ const backward = reduce(
31
+ createTextBufferState("hello"),
32
+ { type: "set-cursor", cursorOffset: 3 },
33
+ { type: "delete-backward" },
34
+ );
35
+ const forward = reduce(
36
+ createTextBufferState("hello"),
37
+ { type: "set-cursor", cursorOffset: 2 },
38
+ { type: "delete-forward" },
39
+ );
40
+
41
+ expect(backward.text).toBe("helo");
42
+ expect(backward.cursorOffset).toBe(2);
43
+ expect(forward.text).toBe("helo");
44
+ expect(forward.cursorOffset).toBe(2);
45
+ });
46
+
47
+ it("moves home and end across the whole buffer in single-line mode", () => {
48
+ const home = reduce(
49
+ createTextBufferState("hello"),
50
+ { type: "set-cursor", cursorOffset: 4 },
51
+ { type: "move-home", multiLine: false },
52
+ );
53
+ const end = reduce(home, { type: "move-end", multiLine: false });
54
+
55
+ expect(home.cursorOffset).toBe(0);
56
+ expect(end.cursorOffset).toBe(5);
57
+ });
58
+
59
+ it("moves home and end within the current line in multi-line mode", () => {
60
+ const home = reduce(
61
+ createTextBufferState("one\ntwo\nthree"),
62
+ { type: "set-cursor", cursorOffset: 6 },
63
+ { type: "move-home", multiLine: true },
64
+ );
65
+ const end = reduce(home, { type: "move-end", multiLine: true });
66
+
67
+ expect(home.cursorOffset).toBe(4);
68
+ expect(end.cursorOffset).toBe(7);
69
+ });
70
+
71
+ it("preserves preferred column when moving vertically", () => {
72
+ const movedUp = reduce(
73
+ createTextBufferState("abcde\nxy\n123456"),
74
+ { type: "set-cursor", cursorOffset: 11 },
75
+ { type: "move-up" },
76
+ );
77
+ const movedDown = reduce(movedUp, { type: "move-down" });
78
+
79
+ expect(movedUp.cursorOffset).toBe(8);
80
+ expect(movedDown.cursorOffset).toBe(11);
81
+ });
82
+
83
+ it("moves by words and deletes the previous word", () => {
84
+ const movedLeft = reduce(createTextBufferState("alpha beta gamma"), {
85
+ type: "move-word-left",
86
+ });
87
+ const movedRight = reduce(
88
+ createTextBufferState("alpha beta gamma"),
89
+ { type: "set-cursor", cursorOffset: 0 },
90
+ { type: "move-word-right" },
91
+ );
92
+ const killed = reduce(createTextBufferState("alpha beta gamma"), {
93
+ type: "kill-word-backward",
94
+ });
95
+
96
+ expect(movedLeft.cursorOffset).toBe(11);
97
+ expect(movedRight.cursorOffset).toBe(5);
98
+ expect(killed.text).toBe("alpha beta ");
99
+ expect(killed.cursorOffset).toBe(11);
100
+ });
101
+
102
+ it("joins the next line when killing forward at end-of-line in multi-line mode", () => {
103
+ const state = reduce(
104
+ createTextBufferState("one\ntwo\nthree"),
105
+ { type: "set-cursor", cursorOffset: 3 },
106
+ { type: "kill-end", multiLine: true },
107
+ );
108
+
109
+ expect(state.text).toBe("onetwo\nthree");
110
+ expect(state.cursorOffset).toBe(3);
111
+ });
112
+
113
+ it("kills to line boundaries in multi-line mode", () => {
114
+ const killStart = reduce(
115
+ createTextBufferState("one\ntwo\nthree"),
116
+ { type: "set-cursor", cursorOffset: 6 },
117
+ { type: "kill-start", multiLine: true },
118
+ );
119
+ const killEnd = reduce(
120
+ createTextBufferState("one\ntwo\nthree"),
121
+ { type: "set-cursor", cursorOffset: 5 },
122
+ { type: "kill-end", multiLine: true },
123
+ );
124
+
125
+ expect(killStart.text).toBe("one\no\nthree");
126
+ expect(killStart.cursorOffset).toBe(4);
127
+ expect(killEnd.text).toBe("one\nt\nthree");
128
+ expect(killEnd.cursorOffset).toBe(5);
129
+ });
130
+
131
+ it("replaces text from external sync and resets cursor to the end", () => {
132
+ const state = reduce(createTextBufferState("hello"), {
133
+ type: "set-text",
134
+ text: "reset",
135
+ });
136
+
137
+ expect(state.text).toBe("reset");
138
+ expect(state.cursorOffset).toBe(5);
139
+ });
140
+
141
+ it("does not corrupt the buffer when killing from line start at offset 0", () => {
142
+ const state = reduce(
143
+ createTextBufferState("\nhello"),
144
+ { type: "set-cursor", cursorOffset: 0 },
145
+ { type: "kill-start", multiLine: true },
146
+ );
147
+
148
+ expect(state.text).toBe("\nhello");
149
+ expect(state.cursorOffset).toBe(0);
150
+ });
151
+
152
+ it("keeps the cursor at column 0 when moving home on a line beginning with a newline", () => {
153
+ const state = reduce(
154
+ createTextBufferState("\nhello"),
155
+ { type: "set-cursor", cursorOffset: 0 },
156
+ { type: "move-home", multiLine: true },
157
+ );
158
+
159
+ expect(state.cursorOffset).toBe(0);
160
+ });
161
+
162
+ it("steps over surrogate pairs when moving and deleting", () => {
163
+ const emoji = "πŸ˜€";
164
+ const movedRight = reduce(
165
+ createTextBufferState(`a${emoji}b`),
166
+ { type: "set-cursor", cursorOffset: 1 },
167
+ { type: "move-right" },
168
+ );
169
+ const movedLeft = reduce(
170
+ createTextBufferState(`a${emoji}b`),
171
+ { type: "set-cursor", cursorOffset: 3 },
172
+ { type: "move-left" },
173
+ );
174
+ const deletedBackward = reduce(
175
+ createTextBufferState(`a${emoji}b`),
176
+ { type: "set-cursor", cursorOffset: 3 },
177
+ { type: "delete-backward" },
178
+ );
179
+ const deletedForward = reduce(
180
+ createTextBufferState(`a${emoji}b`),
181
+ { type: "set-cursor", cursorOffset: 1 },
182
+ { type: "delete-forward" },
183
+ );
184
+
185
+ expect(movedRight.cursorOffset).toBe(3);
186
+ expect(movedLeft.cursorOffset).toBe(1);
187
+ expect(deletedBackward.text).toBe("ab");
188
+ expect(deletedBackward.cursorOffset).toBe(1);
189
+ expect(deletedForward.text).toBe("ab");
190
+ expect(deletedForward.cursorOffset).toBe(1);
191
+ });
192
+
193
+ it("treats grapheme clusters as a single character", () => {
194
+ const skinToned = "πŸ‘πŸ½";
195
+ const deletedBackward = reduce(
196
+ createTextBufferState(`a${skinToned}b`),
197
+ { type: "set-cursor", cursorOffset: 1 + skinToned.length },
198
+ { type: "delete-backward" },
199
+ );
200
+ const deletedForward = reduce(
201
+ createTextBufferState(`a${skinToned}b`),
202
+ { type: "set-cursor", cursorOffset: 1 },
203
+ { type: "delete-forward" },
204
+ );
205
+ const movedRight = reduce(
206
+ createTextBufferState(`a${skinToned}b`),
207
+ { type: "set-cursor", cursorOffset: 1 },
208
+ { type: "move-right" },
209
+ );
210
+
211
+ expect(deletedBackward.text).toBe("ab");
212
+ expect(deletedForward.text).toBe("ab");
213
+ expect(movedRight.cursorOffset).toBe(1 + skinToned.length);
214
+ });
215
+
216
+ it("returns the full grapheme cluster at an offset", () => {
217
+ expect(getGraphemeAt("ab", 0)).toBe("a");
218
+ expect(getGraphemeAt("aπŸ˜€b", 1)).toBe("πŸ˜€");
219
+ expect(getGraphemeAt("aπŸ˜€b", 3)).toBe("b");
220
+ expect(getGraphemeAt("a", 1)).toBe("");
221
+ expect(getGraphemeAt("a\nb", 1)).toBe("\n");
222
+ expect(getGraphemeAt("πŸ‘πŸ½", 0)).toBe("πŸ‘πŸ½");
223
+ });
224
+
225
+ it("advances by ideograph for CJK text without spaces", () => {
226
+ const movedRight = reduce(
227
+ createTextBufferState("δ½ ε₯½δΈ–η•Œ"),
228
+ { type: "set-cursor", cursorOffset: 0 },
229
+ { type: "move-word-right" },
230
+ );
231
+
232
+ expect(movedRight.cursorOffset).toBeGreaterThan(0);
233
+ expect(movedRight.cursorOffset).toBeLessThanOrEqual(4);
234
+ });
235
+
236
+ it("kills the next word forward", () => {
237
+ const state = reduce(
238
+ createTextBufferState("alpha beta gamma"),
239
+ { type: "set-cursor", cursorOffset: 0 },
240
+ { type: "kill-word-forward" },
241
+ );
242
+
243
+ expect(state.text).toBe(" beta gamma");
244
+ expect(state.cursorOffset).toBe(0);
245
+ });
246
+ });