ink-prompt 0.2.2 → 0.2.4
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/README.md +2 -2
- package/dist/components/MultilineInput/AtomicBlocks.d.ts +10 -20
- package/dist/components/MultilineInput/AtomicBlocks.js +33 -42
- package/dist/components/MultilineInput/BlockMarker.d.ts +19 -0
- package/dist/components/MultilineInput/BlockMarker.js +83 -0
- package/dist/components/MultilineInput/BlockRegistry.d.ts +39 -0
- package/dist/components/MultilineInput/BlockRegistry.js +236 -0
- package/dist/components/MultilineInput/BlockTypes.d.ts +22 -0
- package/dist/components/MultilineInput/ImageTypes.d.ts +0 -2
- package/dist/components/MultilineInput/ImageTypes.js +1 -2
- package/dist/components/MultilineInput/ImageValidator.js +1 -1
- package/dist/components/MultilineInput/KeyHandler.d.ts +1 -1
- package/dist/components/MultilineInput/TextBuffer.d.ts +5 -31
- package/dist/components/MultilineInput/TextBuffer.js +91 -161
- package/dist/components/MultilineInput/TextRenderer.d.ts +6 -7
- package/dist/components/MultilineInput/TextRenderer.js +7 -7
- package/dist/components/MultilineInput/__tests__/BlockMarker.test.js +130 -0
- package/dist/components/MultilineInput/__tests__/BlockRegistry.test.js +225 -0
- package/dist/components/MultilineInput/__tests__/Placeholder.integration.test.js +44 -65
- package/dist/components/MultilineInput/__tests__/TextBuffer_images.test.js +10 -31
- package/dist/components/MultilineInput/__tests__/TextRenderer_images.test.js +27 -13
- package/dist/components/MultilineInput/__tests__/integration_images.test.js +2 -4
- package/dist/components/MultilineInput/__tests__/useTextInput_images.test.js +30 -29
- package/dist/components/MultilineInput/index.d.ts +6 -6
- package/dist/components/MultilineInput/index.js +56 -13
- package/dist/components/MultilineInput/types.d.ts +0 -20
- package/dist/components/MultilineInput/useTextInput.d.ts +4 -11
- package/dist/components/MultilineInput/useTextInput.js +79 -76
- package/package.json +1 -1
- package/dist/components/MultilineInput/Placeholder.d.ts +0 -30
- package/dist/components/MultilineInput/Placeholder.js +0 -200
- package/dist/components/MultilineInput/__tests__/Placeholder.test.js +0 -235
- package/dist/examples/examples/basic.js +0 -9
- package/dist/examples/src/components/MultilineInput/KeyHandler.d.ts +0 -15
- package/dist/examples/src/components/MultilineInput/KeyHandler.js +0 -97
- package/dist/examples/src/components/MultilineInput/TextBuffer.d.ts +0 -34
- package/dist/examples/src/components/MultilineInput/TextBuffer.js +0 -127
- package/dist/examples/src/components/MultilineInput/TextRenderer.d.ts +0 -24
- package/dist/examples/src/components/MultilineInput/TextRenderer.js +0 -72
- package/dist/examples/src/components/MultilineInput/__tests__/KeyHandler.test.js +0 -115
- package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/TextBuffer.test.js +0 -254
- package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/TextRenderer.test.js +0 -176
- package/dist/examples/src/components/MultilineInput/__tests__/integration.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/integration.test.js +0 -71
- package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.d.ts +0 -1
- package/dist/examples/src/components/MultilineInput/__tests__/useTextInput.test.js +0 -65
- package/dist/examples/src/components/MultilineInput/index.d.ts +0 -39
- package/dist/examples/src/components/MultilineInput/index.js +0 -82
- package/dist/examples/src/components/MultilineInput/types.d.ts +0 -55
- package/dist/examples/src/components/MultilineInput/types.js +0 -1
- package/dist/examples/src/components/MultilineInput/useTextInput.d.ts +0 -16
- package/dist/examples/src/components/MultilineInput/useTextInput.js +0 -82
- package/dist/examples/src/hello.test.d.ts +0 -1
- package/dist/examples/src/hello.test.js +0 -13
- package/dist/examples/src/index.d.ts +0 -2
- package/dist/examples/src/index.js +0 -2
- /package/dist/components/MultilineInput/{__tests__/Placeholder.test.d.ts → BlockTypes.js} +0 -0
- /package/dist/{examples/examples/basic.d.ts → components/MultilineInput/__tests__/BlockMarker.test.d.ts} +0 -0
- /package/dist/{examples/src/components/MultilineInput/__tests__/KeyHandler.test.d.ts → components/MultilineInput/__tests__/BlockRegistry.test.d.ts} +0 -0
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { handleKey } from '../KeyHandler';
|
|
3
|
-
describe('KeyHandler', () => {
|
|
4
|
-
let actions;
|
|
5
|
-
let buffer;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
actions = {
|
|
8
|
-
insert: vi.fn(),
|
|
9
|
-
delete: vi.fn(),
|
|
10
|
-
newLine: vi.fn(),
|
|
11
|
-
moveCursor: vi.fn(),
|
|
12
|
-
undo: vi.fn(),
|
|
13
|
-
redo: vi.fn(),
|
|
14
|
-
setText: vi.fn(),
|
|
15
|
-
submit: vi.fn(),
|
|
16
|
-
};
|
|
17
|
-
buffer = { lines: [''] };
|
|
18
|
-
});
|
|
19
|
-
describe('Navigation', () => {
|
|
20
|
-
it('handles ArrowUp', () => {
|
|
21
|
-
handleKey({ upArrow: true }, 'up', buffer, actions);
|
|
22
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('up');
|
|
23
|
-
});
|
|
24
|
-
it('handles ArrowDown', () => {
|
|
25
|
-
handleKey({ downArrow: true }, 'down', buffer, actions);
|
|
26
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('down');
|
|
27
|
-
});
|
|
28
|
-
it('handles ArrowLeft', () => {
|
|
29
|
-
handleKey({ leftArrow: true }, 'left', buffer, actions);
|
|
30
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('left');
|
|
31
|
-
});
|
|
32
|
-
it('handles ArrowRight', () => {
|
|
33
|
-
handleKey({ rightArrow: true }, 'right', buffer, actions);
|
|
34
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('right');
|
|
35
|
-
});
|
|
36
|
-
it('handles Home', () => {
|
|
37
|
-
// Ink doesn't have a specific 'home' property usually, but we check for special keys
|
|
38
|
-
// Often represented as key.home if using a specific library or just checking input
|
|
39
|
-
// For Ink's useInput, we might receive specific sequences.
|
|
40
|
-
// Assuming we handle it via checking `key` object properties if available, or input string if it maps.
|
|
41
|
-
// However, standard Ink `Key` interface has `home` and `end`?
|
|
42
|
-
// Let's assume standard Ink Key interface.
|
|
43
|
-
handleKey({ home: true }, '', buffer, actions);
|
|
44
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('lineStart');
|
|
45
|
-
});
|
|
46
|
-
it('handles End', () => {
|
|
47
|
-
handleKey({ end: true }, '', buffer, actions);
|
|
48
|
-
expect(actions.moveCursor).toHaveBeenCalledWith('lineEnd');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('Editing', () => {
|
|
52
|
-
it('handles Backspace', () => {
|
|
53
|
-
handleKey({ backspace: true }, 'backspace', buffer, actions);
|
|
54
|
-
expect(actions.delete).toHaveBeenCalled();
|
|
55
|
-
});
|
|
56
|
-
it('handles Delete', () => {
|
|
57
|
-
handleKey({ delete: true }, 'delete', buffer, actions);
|
|
58
|
-
expect(actions.delete).toHaveBeenCalled(); // Our current simple delete handles backspace, we might need forward delete later
|
|
59
|
-
});
|
|
60
|
-
it('handles Ctrl+J (NewLine)', () => {
|
|
61
|
-
handleKey({ ctrl: true }, 'j', buffer, actions);
|
|
62
|
-
expect(actions.newLine).toHaveBeenCalled();
|
|
63
|
-
});
|
|
64
|
-
it('handles regular text insertion', () => {
|
|
65
|
-
handleKey({}, 'a', buffer, actions);
|
|
66
|
-
expect(actions.insert).toHaveBeenCalledWith('a');
|
|
67
|
-
});
|
|
68
|
-
it('ignores control keys without text', () => {
|
|
69
|
-
handleKey({ ctrl: true }, '', buffer, actions);
|
|
70
|
-
expect(actions.insert).not.toHaveBeenCalled();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe('History', () => {
|
|
74
|
-
it('handles Ctrl+Z (Undo)', () => {
|
|
75
|
-
handleKey({ ctrl: true }, 'z', buffer, actions);
|
|
76
|
-
expect(actions.undo).toHaveBeenCalled();
|
|
77
|
-
});
|
|
78
|
-
it('handles Ctrl+Y (Redo)', () => {
|
|
79
|
-
handleKey({ ctrl: true }, 'y', buffer, actions);
|
|
80
|
-
expect(actions.redo).toHaveBeenCalled();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
describe('Submission', () => {
|
|
84
|
-
it('handles Enter as submit by default', () => {
|
|
85
|
-
buffer = { lines: ['hello'] };
|
|
86
|
-
handleKey({ return: true }, 'return', buffer, actions);
|
|
87
|
-
expect(actions.submit).toHaveBeenCalled();
|
|
88
|
-
expect(actions.newLine).not.toHaveBeenCalled();
|
|
89
|
-
});
|
|
90
|
-
it('handles Enter as newline if line ends with backslash', () => {
|
|
91
|
-
buffer = { lines: ['hello\\'] };
|
|
92
|
-
const cursor = { line: 0, column: 6 };
|
|
93
|
-
handleKey({ return: true }, 'return', buffer, actions, cursor);
|
|
94
|
-
// It should probably remove the backslash first.
|
|
95
|
-
// Since we are mocking actions, we can't easily verify the state change between calls unless we implement a fake.
|
|
96
|
-
// But we can check call order.
|
|
97
|
-
expect(actions.delete).toHaveBeenCalled();
|
|
98
|
-
expect(actions.newLine).toHaveBeenCalled();
|
|
99
|
-
expect(actions.submit).not.toHaveBeenCalled();
|
|
100
|
-
});
|
|
101
|
-
it('handles Enter as newline if line ends with backslash (multiple lines)', () => {
|
|
102
|
-
// Cursor is implicitly at the end for this logic usually, or we need to pass cursor to handleKey?
|
|
103
|
-
// The plan says `handleKey` takes `buffer`. It probably needs `cursor` too to know which line we are on?
|
|
104
|
-
// Ah, `TextBuffer` logic usually needs cursor.
|
|
105
|
-
// If `handleKey` decides based on "current line", it needs to know the current line.
|
|
106
|
-
// So `handleKey` signature should probably include `cursor`.
|
|
107
|
-
const cursor = { line: 1, column: 6 };
|
|
108
|
-
buffer = { lines: ['first', 'second\\'] };
|
|
109
|
-
handleKey({ return: true }, 'return', buffer, actions, cursor);
|
|
110
|
-
expect(actions.delete).toHaveBeenCalled();
|
|
111
|
-
expect(actions.newLine).toHaveBeenCalled();
|
|
112
|
-
expect(actions.submit).not.toHaveBeenCalled();
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createBuffer, insertChar, deleteChar, insertNewLine, moveCursor, getTextContent, } from '../TextBuffer';
|
|
3
|
-
describe('TextBuffer', () => {
|
|
4
|
-
describe('createBuffer', () => {
|
|
5
|
-
it('creates empty buffer with single empty line', () => {
|
|
6
|
-
const buffer = createBuffer();
|
|
7
|
-
expect(buffer).toEqual({ lines: [''] });
|
|
8
|
-
});
|
|
9
|
-
it('creates buffer from single line text', () => {
|
|
10
|
-
const buffer = createBuffer('hello');
|
|
11
|
-
expect(buffer).toEqual({ lines: ['hello'] });
|
|
12
|
-
});
|
|
13
|
-
it('creates buffer from multi-line text', () => {
|
|
14
|
-
const buffer = createBuffer('line1\nline2\nline3');
|
|
15
|
-
expect(buffer).toEqual({ lines: ['line1', 'line2', 'line3'] });
|
|
16
|
-
});
|
|
17
|
-
it('handles text ending with newline', () => {
|
|
18
|
-
const buffer = createBuffer('hello\n');
|
|
19
|
-
expect(buffer).toEqual({ lines: ['hello', ''] });
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
describe('insertChar', () => {
|
|
23
|
-
it('inserts character into empty buffer', () => {
|
|
24
|
-
const buffer = createBuffer();
|
|
25
|
-
const cursor = { line: 0, column: 0 };
|
|
26
|
-
const result = insertChar(buffer, cursor, 'a');
|
|
27
|
-
expect(result.buffer).toEqual({ lines: ['a'] });
|
|
28
|
-
expect(result.cursor).toEqual({ line: 0, column: 1 });
|
|
29
|
-
});
|
|
30
|
-
it('inserts character at end of line', () => {
|
|
31
|
-
const buffer = createBuffer('hello');
|
|
32
|
-
const cursor = { line: 0, column: 5 };
|
|
33
|
-
const result = insertChar(buffer, cursor, '!');
|
|
34
|
-
expect(result.buffer).toEqual({ lines: ['hello!'] });
|
|
35
|
-
expect(result.cursor).toEqual({ line: 0, column: 6 });
|
|
36
|
-
});
|
|
37
|
-
it('inserts character in middle of line', () => {
|
|
38
|
-
const buffer = createBuffer('hllo');
|
|
39
|
-
const cursor = { line: 0, column: 1 };
|
|
40
|
-
const result = insertChar(buffer, cursor, 'e');
|
|
41
|
-
expect(result.buffer).toEqual({ lines: ['hello'] });
|
|
42
|
-
expect(result.cursor).toEqual({ line: 0, column: 2 });
|
|
43
|
-
});
|
|
44
|
-
it('inserts character at line start', () => {
|
|
45
|
-
const buffer = createBuffer('ello');
|
|
46
|
-
const cursor = { line: 0, column: 0 };
|
|
47
|
-
const result = insertChar(buffer, cursor, 'h');
|
|
48
|
-
expect(result.buffer).toEqual({ lines: ['hello'] });
|
|
49
|
-
expect(result.cursor).toEqual({ line: 0, column: 1 });
|
|
50
|
-
});
|
|
51
|
-
it('inserts into specific line of multi-line buffer', () => {
|
|
52
|
-
const buffer = createBuffer('line1\nline2\nline3');
|
|
53
|
-
const cursor = { line: 1, column: 4 };
|
|
54
|
-
const result = insertChar(buffer, cursor, 'X');
|
|
55
|
-
expect(result.buffer).toEqual({ lines: ['line1', 'lineX2', 'line3'] });
|
|
56
|
-
expect(result.cursor).toEqual({ line: 1, column: 5 });
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe('deleteChar (backspace)', () => {
|
|
60
|
-
it('does nothing when cursor at buffer start', () => {
|
|
61
|
-
const buffer = createBuffer('hello');
|
|
62
|
-
const cursor = { line: 0, column: 0 };
|
|
63
|
-
const result = deleteChar(buffer, cursor);
|
|
64
|
-
expect(result.buffer).toEqual({ lines: ['hello'] });
|
|
65
|
-
expect(result.cursor).toEqual({ line: 0, column: 0 });
|
|
66
|
-
});
|
|
67
|
-
it('deletes character before cursor', () => {
|
|
68
|
-
const buffer = createBuffer('hello');
|
|
69
|
-
const cursor = { line: 0, column: 5 };
|
|
70
|
-
const result = deleteChar(buffer, cursor);
|
|
71
|
-
expect(result.buffer).toEqual({ lines: ['hell'] });
|
|
72
|
-
expect(result.cursor).toEqual({ line: 0, column: 4 });
|
|
73
|
-
});
|
|
74
|
-
it('deletes character in middle of line', () => {
|
|
75
|
-
const buffer = createBuffer('heello');
|
|
76
|
-
const cursor = { line: 0, column: 3 };
|
|
77
|
-
const result = deleteChar(buffer, cursor);
|
|
78
|
-
expect(result.buffer).toEqual({ lines: ['hello'] });
|
|
79
|
-
expect(result.cursor).toEqual({ line: 0, column: 2 });
|
|
80
|
-
});
|
|
81
|
-
it('merges with previous line when at line start', () => {
|
|
82
|
-
const buffer = createBuffer('hello\nworld');
|
|
83
|
-
const cursor = { line: 1, column: 0 };
|
|
84
|
-
const result = deleteChar(buffer, cursor);
|
|
85
|
-
expect(result.buffer).toEqual({ lines: ['helloworld'] });
|
|
86
|
-
expect(result.cursor).toEqual({ line: 0, column: 5 });
|
|
87
|
-
});
|
|
88
|
-
it('merges empty line with previous', () => {
|
|
89
|
-
const buffer = createBuffer('hello\n');
|
|
90
|
-
const cursor = { line: 1, column: 0 };
|
|
91
|
-
const result = deleteChar(buffer, cursor);
|
|
92
|
-
expect(result.buffer).toEqual({ lines: ['hello'] });
|
|
93
|
-
expect(result.cursor).toEqual({ line: 0, column: 5 });
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
describe('insertNewLine', () => {
|
|
97
|
-
it('splits line at cursor position', () => {
|
|
98
|
-
const buffer = createBuffer('helloworld');
|
|
99
|
-
const cursor = { line: 0, column: 5 };
|
|
100
|
-
const result = insertNewLine(buffer, cursor);
|
|
101
|
-
expect(result.buffer).toEqual({ lines: ['hello', 'world'] });
|
|
102
|
-
expect(result.cursor).toEqual({ line: 1, column: 0 });
|
|
103
|
-
});
|
|
104
|
-
it('creates new line at line start', () => {
|
|
105
|
-
const buffer = createBuffer('hello');
|
|
106
|
-
const cursor = { line: 0, column: 0 };
|
|
107
|
-
const result = insertNewLine(buffer, cursor);
|
|
108
|
-
expect(result.buffer).toEqual({ lines: ['', 'hello'] });
|
|
109
|
-
expect(result.cursor).toEqual({ line: 1, column: 0 });
|
|
110
|
-
});
|
|
111
|
-
it('creates new line at line end', () => {
|
|
112
|
-
const buffer = createBuffer('hello');
|
|
113
|
-
const cursor = { line: 0, column: 5 };
|
|
114
|
-
const result = insertNewLine(buffer, cursor);
|
|
115
|
-
expect(result.buffer).toEqual({ lines: ['hello', ''] });
|
|
116
|
-
expect(result.cursor).toEqual({ line: 1, column: 0 });
|
|
117
|
-
});
|
|
118
|
-
it('inserts new line in middle of multi-line buffer', () => {
|
|
119
|
-
const buffer = createBuffer('line1\nline2\nline3');
|
|
120
|
-
const cursor = { line: 1, column: 2 };
|
|
121
|
-
const result = insertNewLine(buffer, cursor);
|
|
122
|
-
expect(result.buffer).toEqual({ lines: ['line1', 'li', 'ne2', 'line3'] });
|
|
123
|
-
expect(result.cursor).toEqual({ line: 2, column: 0 });
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
describe('moveCursor', () => {
|
|
127
|
-
describe('left', () => {
|
|
128
|
-
it('moves left within line', () => {
|
|
129
|
-
const buffer = createBuffer('hello');
|
|
130
|
-
const cursor = { line: 0, column: 3 };
|
|
131
|
-
const result = moveCursor(buffer, cursor, 'left');
|
|
132
|
-
expect(result).toEqual({ line: 0, column: 2 });
|
|
133
|
-
});
|
|
134
|
-
it('wraps to end of previous line', () => {
|
|
135
|
-
const buffer = createBuffer('hello\nworld');
|
|
136
|
-
const cursor = { line: 1, column: 0 };
|
|
137
|
-
const result = moveCursor(buffer, cursor, 'left');
|
|
138
|
-
expect(result).toEqual({ line: 0, column: 5 });
|
|
139
|
-
});
|
|
140
|
-
it('stays at buffer start', () => {
|
|
141
|
-
const buffer = createBuffer('hello');
|
|
142
|
-
const cursor = { line: 0, column: 0 };
|
|
143
|
-
const result = moveCursor(buffer, cursor, 'left');
|
|
144
|
-
expect(result).toEqual({ line: 0, column: 0 });
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
describe('right', () => {
|
|
148
|
-
it('moves right within line', () => {
|
|
149
|
-
const buffer = createBuffer('hello');
|
|
150
|
-
const cursor = { line: 0, column: 2 };
|
|
151
|
-
const result = moveCursor(buffer, cursor, 'right');
|
|
152
|
-
expect(result).toEqual({ line: 0, column: 3 });
|
|
153
|
-
});
|
|
154
|
-
it('wraps to start of next line', () => {
|
|
155
|
-
const buffer = createBuffer('hello\nworld');
|
|
156
|
-
const cursor = { line: 0, column: 5 };
|
|
157
|
-
const result = moveCursor(buffer, cursor, 'right');
|
|
158
|
-
expect(result).toEqual({ line: 1, column: 0 });
|
|
159
|
-
});
|
|
160
|
-
it('stays at buffer end', () => {
|
|
161
|
-
const buffer = createBuffer('hello');
|
|
162
|
-
const cursor = { line: 0, column: 5 };
|
|
163
|
-
const result = moveCursor(buffer, cursor, 'right');
|
|
164
|
-
expect(result).toEqual({ line: 0, column: 5 });
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('up', () => {
|
|
168
|
-
it('moves up maintaining column', () => {
|
|
169
|
-
const buffer = createBuffer('hello\nworld');
|
|
170
|
-
const cursor = { line: 1, column: 3 };
|
|
171
|
-
const result = moveCursor(buffer, cursor, 'up');
|
|
172
|
-
expect(result).toEqual({ line: 0, column: 3 });
|
|
173
|
-
});
|
|
174
|
-
it('clamps column to shorter line', () => {
|
|
175
|
-
const buffer = createBuffer('hi\nhello');
|
|
176
|
-
const cursor = { line: 1, column: 4 };
|
|
177
|
-
const result = moveCursor(buffer, cursor, 'up');
|
|
178
|
-
expect(result).toEqual({ line: 0, column: 2 });
|
|
179
|
-
});
|
|
180
|
-
it('stays on first line', () => {
|
|
181
|
-
const buffer = createBuffer('hello');
|
|
182
|
-
const cursor = { line: 0, column: 2 };
|
|
183
|
-
const result = moveCursor(buffer, cursor, 'up');
|
|
184
|
-
expect(result).toEqual({ line: 0, column: 2 });
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
describe('down', () => {
|
|
188
|
-
it('moves down maintaining column', () => {
|
|
189
|
-
const buffer = createBuffer('hello\nworld');
|
|
190
|
-
const cursor = { line: 0, column: 3 };
|
|
191
|
-
const result = moveCursor(buffer, cursor, 'down');
|
|
192
|
-
expect(result).toEqual({ line: 1, column: 3 });
|
|
193
|
-
});
|
|
194
|
-
it('clamps column to shorter line', () => {
|
|
195
|
-
const buffer = createBuffer('hello\nhi');
|
|
196
|
-
const cursor = { line: 0, column: 4 };
|
|
197
|
-
const result = moveCursor(buffer, cursor, 'down');
|
|
198
|
-
expect(result).toEqual({ line: 1, column: 2 });
|
|
199
|
-
});
|
|
200
|
-
it('stays on last line', () => {
|
|
201
|
-
const buffer = createBuffer('hello');
|
|
202
|
-
const cursor = { line: 0, column: 2 };
|
|
203
|
-
const result = moveCursor(buffer, cursor, 'down');
|
|
204
|
-
expect(result).toEqual({ line: 0, column: 2 });
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
describe('lineStart', () => {
|
|
208
|
-
it('moves to start of line', () => {
|
|
209
|
-
const buffer = createBuffer('hello');
|
|
210
|
-
const cursor = { line: 0, column: 3 };
|
|
211
|
-
const result = moveCursor(buffer, cursor, 'lineStart');
|
|
212
|
-
expect(result).toEqual({ line: 0, column: 0 });
|
|
213
|
-
});
|
|
214
|
-
it('stays at start if already there', () => {
|
|
215
|
-
const buffer = createBuffer('hello');
|
|
216
|
-
const cursor = { line: 0, column: 0 };
|
|
217
|
-
const result = moveCursor(buffer, cursor, 'lineStart');
|
|
218
|
-
expect(result).toEqual({ line: 0, column: 0 });
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe('lineEnd', () => {
|
|
222
|
-
it('moves to end of line', () => {
|
|
223
|
-
const buffer = createBuffer('hello');
|
|
224
|
-
const cursor = { line: 0, column: 2 };
|
|
225
|
-
const result = moveCursor(buffer, cursor, 'lineEnd');
|
|
226
|
-
expect(result).toEqual({ line: 0, column: 5 });
|
|
227
|
-
});
|
|
228
|
-
it('stays at end if already there', () => {
|
|
229
|
-
const buffer = createBuffer('hello');
|
|
230
|
-
const cursor = { line: 0, column: 5 };
|
|
231
|
-
const result = moveCursor(buffer, cursor, 'lineEnd');
|
|
232
|
-
expect(result).toEqual({ line: 0, column: 5 });
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
describe('getTextContent', () => {
|
|
237
|
-
it('returns empty string for empty buffer', () => {
|
|
238
|
-
const buffer = createBuffer();
|
|
239
|
-
expect(getTextContent(buffer)).toBe('');
|
|
240
|
-
});
|
|
241
|
-
it('returns single line content', () => {
|
|
242
|
-
const buffer = createBuffer('hello');
|
|
243
|
-
expect(getTextContent(buffer)).toBe('hello');
|
|
244
|
-
});
|
|
245
|
-
it('joins multiple lines with newlines', () => {
|
|
246
|
-
const buffer = createBuffer('line1\nline2\nline3');
|
|
247
|
-
expect(getTextContent(buffer)).toBe('line1\nline2\nline3');
|
|
248
|
-
});
|
|
249
|
-
it('preserves empty lines', () => {
|
|
250
|
-
const buffer = createBuffer('hello\n\nworld');
|
|
251
|
-
expect(getTextContent(buffer)).toBe('hello\n\nworld');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
import { render } from '@testing-library/react';
|
|
4
|
-
import { TextRenderer, wrapLines } from '../TextRenderer';
|
|
5
|
-
describe('wrapLines', () => {
|
|
6
|
-
describe('no wrapping needed', () => {
|
|
7
|
-
it('returns single line unchanged when shorter than width', () => {
|
|
8
|
-
const buffer = { lines: ['hello'] };
|
|
9
|
-
const cursor = { line: 0, column: 5 };
|
|
10
|
-
const result = wrapLines(buffer, cursor, 80);
|
|
11
|
-
expect(result.visualLines).toEqual(['hello']);
|
|
12
|
-
expect(result.cursorVisualRow).toBe(0);
|
|
13
|
-
expect(result.cursorVisualCol).toBe(5);
|
|
14
|
-
});
|
|
15
|
-
it('returns multiple lines unchanged when all shorter than width', () => {
|
|
16
|
-
const buffer = { lines: ['hello', 'world'] };
|
|
17
|
-
const cursor = { line: 1, column: 3 };
|
|
18
|
-
const result = wrapLines(buffer, cursor, 80);
|
|
19
|
-
expect(result.visualLines).toEqual(['hello', 'world']);
|
|
20
|
-
expect(result.cursorVisualRow).toBe(1);
|
|
21
|
-
expect(result.cursorVisualCol).toBe(3);
|
|
22
|
-
});
|
|
23
|
-
it('handles empty buffer', () => {
|
|
24
|
-
const buffer = { lines: [''] };
|
|
25
|
-
const cursor = { line: 0, column: 0 };
|
|
26
|
-
const result = wrapLines(buffer, cursor, 80);
|
|
27
|
-
expect(result.visualLines).toEqual(['']);
|
|
28
|
-
expect(result.cursorVisualRow).toBe(0);
|
|
29
|
-
expect(result.cursorVisualCol).toBe(0);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe('wrapping single line', () => {
|
|
33
|
-
it('wraps line exceeding width', () => {
|
|
34
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
35
|
-
const cursor = { line: 0, column: 0 };
|
|
36
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
37
|
-
expect(result.visualLines).toEqual(['abcde', 'fghij']);
|
|
38
|
-
});
|
|
39
|
-
it('wraps line into multiple visual lines', () => {
|
|
40
|
-
const buffer = { lines: ['abcdefghijklmno'] };
|
|
41
|
-
const cursor = { line: 0, column: 0 };
|
|
42
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
43
|
-
expect(result.visualLines).toEqual(['abcde', 'fghij', 'klmno']);
|
|
44
|
-
});
|
|
45
|
-
it('handles exact width match (no extra empty line)', () => {
|
|
46
|
-
const buffer = { lines: ['abcde'] };
|
|
47
|
-
const cursor = { line: 0, column: 5 };
|
|
48
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
49
|
-
expect(result.visualLines).toEqual(['abcde']);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
describe('cursor position in wrapped lines', () => {
|
|
53
|
-
it('cursor on first visual row', () => {
|
|
54
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
55
|
-
const cursor = { line: 0, column: 3 };
|
|
56
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
57
|
-
expect(result.cursorVisualRow).toBe(0);
|
|
58
|
-
expect(result.cursorVisualCol).toBe(3);
|
|
59
|
-
});
|
|
60
|
-
it('cursor on second visual row', () => {
|
|
61
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
62
|
-
const cursor = { line: 0, column: 7 };
|
|
63
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
64
|
-
expect(result.cursorVisualRow).toBe(1);
|
|
65
|
-
expect(result.cursorVisualCol).toBe(2);
|
|
66
|
-
});
|
|
67
|
-
it('cursor at wrap boundary (end of first visual row)', () => {
|
|
68
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
69
|
-
const cursor = { line: 0, column: 5 };
|
|
70
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
71
|
-
expect(result.cursorVisualRow).toBe(1);
|
|
72
|
-
expect(result.cursorVisualCol).toBe(0);
|
|
73
|
-
});
|
|
74
|
-
it('cursor at end of wrapped line', () => {
|
|
75
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
76
|
-
const cursor = { line: 0, column: 10 };
|
|
77
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
78
|
-
expect(result.cursorVisualRow).toBe(1);
|
|
79
|
-
expect(result.cursorVisualCol).toBe(5);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
describe('wrapping multiple lines', () => {
|
|
83
|
-
it('wraps only the line that exceeds width', () => {
|
|
84
|
-
const buffer = { lines: ['hi', 'abcdefghij'] };
|
|
85
|
-
const cursor = { line: 0, column: 0 };
|
|
86
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
87
|
-
expect(result.visualLines).toEqual(['hi', 'abcde', 'fghij']);
|
|
88
|
-
});
|
|
89
|
-
it('cursor on second logical line that wraps', () => {
|
|
90
|
-
const buffer = { lines: ['hi', 'abcdefghij'] };
|
|
91
|
-
const cursor = { line: 1, column: 7 };
|
|
92
|
-
const result = wrapLines(buffer, cursor, 5);
|
|
93
|
-
expect(result.visualLines).toEqual(['hi', 'abcde', 'fghij']);
|
|
94
|
-
expect(result.cursorVisualRow).toBe(2);
|
|
95
|
-
expect(result.cursorVisualCol).toBe(2);
|
|
96
|
-
});
|
|
97
|
-
it('handles multiple wrapped lines', () => {
|
|
98
|
-
const buffer = { lines: ['abcdefg', 'hijklmn'] };
|
|
99
|
-
const cursor = { line: 1, column: 5 };
|
|
100
|
-
const result = wrapLines(buffer, cursor, 4);
|
|
101
|
-
expect(result.visualLines).toEqual(['abcd', 'efg', 'hijk', 'lmn']);
|
|
102
|
-
expect(result.cursorVisualRow).toBe(3);
|
|
103
|
-
expect(result.cursorVisualCol).toBe(1);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
describe('TextRenderer', () => {
|
|
108
|
-
describe('basic rendering', () => {
|
|
109
|
-
it('renders single line', () => {
|
|
110
|
-
const buffer = { lines: ['hello'] };
|
|
111
|
-
const cursor = { line: 0, column: 0 };
|
|
112
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor }));
|
|
113
|
-
expect(container.textContent).toContain('hello');
|
|
114
|
-
});
|
|
115
|
-
it('renders multiple lines', () => {
|
|
116
|
-
const buffer = { lines: ['hello', 'world'] };
|
|
117
|
-
const cursor = { line: 0, column: 0 };
|
|
118
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor }));
|
|
119
|
-
expect(container.textContent).toContain('hello');
|
|
120
|
-
expect(container.textContent).toContain('world');
|
|
121
|
-
});
|
|
122
|
-
it('renders empty buffer', () => {
|
|
123
|
-
const buffer = { lines: [''] };
|
|
124
|
-
const cursor = { line: 0, column: 0 };
|
|
125
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor }));
|
|
126
|
-
// Should render cursor character in empty buffer
|
|
127
|
-
expect(container.textContent).toContain('█');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
describe('cursor display', () => {
|
|
131
|
-
it('shows cursor at start of line', () => {
|
|
132
|
-
const buffer = { lines: ['hello'] };
|
|
133
|
-
const cursor = { line: 0, column: 0 };
|
|
134
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, showCursor: true }));
|
|
135
|
-
// Cursor should be rendered before 'hello'
|
|
136
|
-
expect(container.textContent).toContain('█hello');
|
|
137
|
-
});
|
|
138
|
-
it('shows cursor at end of line', () => {
|
|
139
|
-
const buffer = { lines: ['hello'] };
|
|
140
|
-
const cursor = { line: 0, column: 5 };
|
|
141
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, showCursor: true }));
|
|
142
|
-
expect(container.textContent).toContain('hello█');
|
|
143
|
-
});
|
|
144
|
-
it('shows cursor in middle of line', () => {
|
|
145
|
-
const buffer = { lines: ['hello'] };
|
|
146
|
-
const cursor = { line: 0, column: 2 };
|
|
147
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, showCursor: true }));
|
|
148
|
-
expect(container.textContent).toContain('he█llo');
|
|
149
|
-
});
|
|
150
|
-
it('hides cursor when showCursor is false', () => {
|
|
151
|
-
const buffer = { lines: ['hello'] };
|
|
152
|
-
const cursor = { line: 0, column: 0 };
|
|
153
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, showCursor: false }));
|
|
154
|
-
expect(container.textContent).not.toContain('█');
|
|
155
|
-
expect(container.textContent).toContain('hello');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
describe('word wrapping in render', () => {
|
|
159
|
-
it('renders wrapped lines', () => {
|
|
160
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
161
|
-
const cursor = { line: 0, column: 0 };
|
|
162
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, width: 5 }));
|
|
163
|
-
// Both visual lines should be present
|
|
164
|
-
expect(container.textContent).toContain('abcde');
|
|
165
|
-
expect(container.textContent).toContain('fghij');
|
|
166
|
-
});
|
|
167
|
-
it('shows cursor correctly on wrapped line', () => {
|
|
168
|
-
const buffer = { lines: ['abcdefghij'] };
|
|
169
|
-
const cursor = { line: 0, column: 7 };
|
|
170
|
-
const { container } = render(React.createElement(TextRenderer, { buffer: buffer, cursor: cursor, width: 5, showCursor: true }));
|
|
171
|
-
// Cursor at position 7 wraps to second visual row, column 2
|
|
172
|
-
// So we should see 'fg█hij' on the second visual row
|
|
173
|
-
expect(container.textContent).toContain('fg█hij');
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
-
import { render } from '@testing-library/react';
|
|
4
|
-
import { MultilineInputCore } from '../index';
|
|
5
|
-
/**
|
|
6
|
-
* Integration tests for MultilineInputCore.
|
|
7
|
-
*
|
|
8
|
-
* These tests validate the component's rendering and props behavior.
|
|
9
|
-
* We test MultilineInputCore instead of MultilineInput to avoid requiring
|
|
10
|
-
* the Ink runtime (useInput, useStdout hooks).
|
|
11
|
-
*
|
|
12
|
-
* Keyboard handling is thoroughly tested via KeyHandler.test.ts.
|
|
13
|
-
* State management is tested via useTextInput.test.ts.
|
|
14
|
-
*/
|
|
15
|
-
describe('MultilineInputCore', () => {
|
|
16
|
-
describe('Rendering', () => {
|
|
17
|
-
it('renders empty input with cursor', () => {
|
|
18
|
-
const { container } = render(React.createElement(MultilineInputCore, null));
|
|
19
|
-
expect(container.textContent).toContain('█');
|
|
20
|
-
});
|
|
21
|
-
it('renders with initial value', () => {
|
|
22
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "hello" }));
|
|
23
|
-
expect(container.textContent).toContain('hello');
|
|
24
|
-
});
|
|
25
|
-
it('renders multiline value', () => {
|
|
26
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "line1\\nline2" }));
|
|
27
|
-
expect(container.textContent).toContain('line1');
|
|
28
|
-
expect(container.textContent).toContain('line2');
|
|
29
|
-
});
|
|
30
|
-
it('shows cursor at end of value', () => {
|
|
31
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "hi" }));
|
|
32
|
-
expect(container.textContent).toContain('hi█');
|
|
33
|
-
});
|
|
34
|
-
it('hides cursor when showCursor is false', () => {
|
|
35
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "hello", showCursor: false }));
|
|
36
|
-
expect(container.textContent).not.toContain('█');
|
|
37
|
-
expect(container.textContent).toContain('hello');
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
describe('Props', () => {
|
|
41
|
-
it('accepts width prop for word wrapping', () => {
|
|
42
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "abcdefghij", width: 5 }));
|
|
43
|
-
// Should wrap at width 5
|
|
44
|
-
expect(container.textContent).toContain('abcde');
|
|
45
|
-
expect(container.textContent).toContain('fghij');
|
|
46
|
-
});
|
|
47
|
-
it('calls onChange on initial render with value', () => {
|
|
48
|
-
const onChange = vi.fn();
|
|
49
|
-
render(React.createElement(MultilineInputCore, { value: "test", onChange: onChange }));
|
|
50
|
-
// onChange is called with the initial value
|
|
51
|
-
expect(onChange).toHaveBeenCalledWith('test');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe('Placeholder', () => {
|
|
55
|
-
it('shows placeholder when empty and cursor hidden', () => {
|
|
56
|
-
const { container } = render(React.createElement(MultilineInputCore, { placeholder: "Type here...", showCursor: false }));
|
|
57
|
-
expect(container.textContent).toContain('Type here...');
|
|
58
|
-
});
|
|
59
|
-
it('does not show placeholder when showCursor is true', () => {
|
|
60
|
-
const { container } = render(React.createElement(MultilineInputCore, { placeholder: "Type here...", showCursor: true }));
|
|
61
|
-
// Shows cursor instead, not placeholder
|
|
62
|
-
expect(container.textContent).toContain('█');
|
|
63
|
-
expect(container.textContent).not.toContain('Type here...');
|
|
64
|
-
});
|
|
65
|
-
it('does not show placeholder when has value', () => {
|
|
66
|
-
const { container } = render(React.createElement(MultilineInputCore, { value: "hello", placeholder: "Type here...", showCursor: false }));
|
|
67
|
-
expect(container.textContent).toContain('hello');
|
|
68
|
-
expect(container.textContent).not.toContain('Type here...');
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|