@robota-sdk/agent-transport 3.0.0-beta.75 → 3.0.0-beta.76
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 +10 -10
- package/dist/node/headless/index.cjs +1 -1
- package/dist/node/{headless-CT2ibQnr.cjs → headless-OnpVk4-k.cjs} +7 -7
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.ts +1 -6
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +7 -75
- package/src/index.ts +1 -5
- package/src/transport-registry.ts +0 -9
- package/dist/node/http/index.cjs +0 -1
- package/dist/node/http/index.d.ts +0 -2
- package/dist/node/http/index.js +0 -1
- package/dist/node/http-2Jiuflc1.js +0 -2
- package/dist/node/http-2Jiuflc1.js.map +0 -1
- package/dist/node/http-CBAvefLw.cjs +0 -1
- package/dist/node/index-BNccqSpv.d.ts +0 -86
- package/dist/node/index-BNccqSpv.d.ts.map +0 -1
- package/dist/node/index-BUhHIf7X.d.ts +0 -86
- package/dist/node/index-BUhHIf7X.d.ts.map +0 -1
- package/dist/node/index-BnAGE-u9.d.ts +0 -33
- package/dist/node/index-BnAGE-u9.d.ts.map +0 -1
- package/dist/node/index-BrQ4gGw0.d.ts +0 -213
- package/dist/node/index-BrQ4gGw0.d.ts.map +0 -1
- package/dist/node/index-CoeBF21y.d.ts +0 -213
- package/dist/node/index-CoeBF21y.d.ts.map +0 -1
- package/dist/node/index-DHt-2VQ-.d.ts +0 -46
- package/dist/node/index-DHt-2VQ-.d.ts.map +0 -1
- package/dist/node/index-DMwKN5Le.d.ts +0 -33
- package/dist/node/index-DMwKN5Le.d.ts.map +0 -1
- package/dist/node/index-c0M42fsA.d.ts +0 -46
- package/dist/node/index-c0M42fsA.d.ts.map +0 -1
- package/dist/node/mcp/index.cjs +0 -1
- package/dist/node/mcp/index.d.ts +0 -2
- package/dist/node/mcp/index.js +0 -1
- package/dist/node/mcp-BOglBJNy.cjs +0 -1
- package/dist/node/mcp-D3BBVK7C.js +0 -2
- package/dist/node/mcp-D3BBVK7C.js.map +0 -1
- package/dist/node/rolldown-runtime-CMqjfN_6.cjs +0 -1
- package/dist/node/tui/index.cjs +0 -1
- package/dist/node/tui/index.d.ts +0 -2
- package/dist/node/tui/index.js +0 -1
- package/dist/node/tui-CcH5EsQh.js +0 -25
- package/dist/node/tui-CcH5EsQh.js.map +0 -1
- package/dist/node/tui-DznRbcku.cjs +0 -24
- package/dist/node/ws/index.cjs +0 -1
- package/dist/node/ws/index.d.ts +0 -2
- package/dist/node/ws/index.js +0 -1
- package/dist/node/ws-Dc2RUwVs.js +0 -2
- package/dist/node/ws-Dc2RUwVs.js.map +0 -1
- package/dist/node/ws-QNMQn5kg.cjs +0 -1
- package/src/http/__tests__/http-transport.test.ts +0 -55
- package/src/http/__tests__/routes.test.ts +0 -168
- package/src/http/http-transport.ts +0 -41
- package/src/http/index.ts +0 -4
- package/src/http/routes.ts +0 -152
- package/src/mcp/__tests__/mcp-server.test.ts +0 -66
- package/src/mcp/__tests__/mcp-transport.test.ts +0 -46
- package/src/mcp/index.ts +0 -4
- package/src/mcp/mcp-server.ts +0 -163
- package/src/mcp/mcp-transport.ts +0 -48
- package/src/tui/App.tsx +0 -491
- package/src/tui/BackgroundTaskPanel.tsx +0 -36
- package/src/tui/CjkTextInput.tsx +0 -199
- package/src/tui/ConfirmPrompt.tsx +0 -70
- package/src/tui/ContextWarningBanner.tsx +0 -34
- package/src/tui/ExecutionWorkspaceDetailPane.tsx +0 -64
- package/src/tui/ExecutionWorkspaceSwitcher.tsx +0 -187
- package/src/tui/InputArea.tsx +0 -310
- package/src/tui/InteractivePrompt.tsx +0 -59
- package/src/tui/ListPicker.tsx +0 -95
- package/src/tui/MenuSelect.tsx +0 -104
- package/src/tui/MessageList.tsx +0 -284
- package/src/tui/PermissionPrompt.tsx +0 -86
- package/src/tui/PluginTUI.tsx +0 -258
- package/src/tui/SessionPicker.tsx +0 -68
- package/src/tui/SessionStatusBar.tsx +0 -73
- package/src/tui/SlashAutocomplete.tsx +0 -110
- package/src/tui/StatusBar.tsx +0 -236
- package/src/tui/StreamingIndicator.tsx +0 -93
- package/src/tui/TextPrompt.tsx +0 -81
- package/src/tui/ToolCommandOutput.tsx +0 -39
- package/src/tui/ToolDiffBlock.tsx +0 -32
- package/src/tui/TransportTUI.tsx +0 -117
- package/src/tui/TuiInteractionChannel.ts +0 -495
- package/src/tui/UpdateNotice.tsx +0 -14
- package/src/tui/UsageSummaryEntry.tsx +0 -39
- package/src/tui/WaveText.tsx +0 -44
- package/src/tui/__tests__/InteractivePrompt.test.tsx +0 -82
- package/src/tui/__tests__/ListPicker.test.tsx +0 -159
- package/src/tui/__tests__/MenuSelect.test.tsx +0 -103
- package/src/tui/__tests__/PluginTUI.test.tsx +0 -167
- package/src/tui/__tests__/SlashAutocomplete.test.tsx +0 -140
- package/src/tui/__tests__/TextPrompt.test.tsx +0 -98
- package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +0 -239
- package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +0 -297
- package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +0 -124
- package/src/tui/__tests__/UpdateNotice.test.tsx +0 -15
- package/src/tui/__tests__/abort-after-permission.test.tsx +0 -169
- package/src/tui/__tests__/abort-streaming-e2e.test.tsx +0 -183
- package/src/tui/__tests__/background-task-panel.test.tsx +0 -53
- package/src/tui/__tests__/background-task-row-format.test.ts +0 -59
- package/src/tui/__tests__/channel-factory-integration.test.ts +0 -138
- package/src/tui/__tests__/cjk-text-input-flow.test.ts +0 -109
- package/src/tui/__tests__/cjk-text-input.test.ts +0 -191
- package/src/tui/__tests__/command-effect-handler.test.ts +0 -127
- package/src/tui/__tests__/command-output-summary.test.ts +0 -95
- package/src/tui/__tests__/compact-event-bridge.test.ts +0 -20
- package/src/tui/__tests__/confirm-permission-flow.test.ts +0 -130
- package/src/tui/__tests__/confirm-prompt.test.tsx +0 -87
- package/src/tui/__tests__/execution-workspace-switcher.test.tsx +0 -110
- package/src/tui/__tests__/execution-workspace-view-model.test.ts +0 -93
- package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +0 -125
- package/src/tui/__tests__/input-area-flow.test.ts +0 -164
- package/src/tui/__tests__/message-list-rendering.test.tsx +0 -353
- package/src/tui/__tests__/prompt-queue.test.tsx +0 -255
- package/src/tui/__tests__/provider-setup-pty-e2e.test.ts +0 -233
- package/src/tui/__tests__/pty/pty-driver.ts +0 -135
- package/src/tui/__tests__/pty/tui-pty.ptytest.ts +0 -61
- package/src/tui/__tests__/render-channel-options.test.ts +0 -32
- package/src/tui/__tests__/render-markdown.test.ts +0 -72
- package/src/tui/__tests__/selection-flow.test.ts +0 -61
- package/src/tui/__tests__/session-init-poller.test.ts +0 -102
- package/src/tui/__tests__/session-naming.test.ts +0 -64
- package/src/tui/__tests__/session-switch-channel.test.tsx +0 -307
- package/src/tui/__tests__/slash-routing-effects.test.ts +0 -228
- package/src/tui/__tests__/status-activity.test.ts +0 -71
- package/src/tui/__tests__/status-bar.test.tsx +0 -177
- package/src/tui/__tests__/streaming-indicator.test.tsx +0 -137
- package/src/tui/__tests__/text-prompt-flow.test.ts +0 -77
- package/src/tui/__tests__/tui-channel-init-failure.test.ts +0 -57
- package/src/tui/__tests__/tui-state-manager.test.ts +0 -401
- package/src/tui/background-task-row-format.ts +0 -53
- package/src/tui/command-interaction.ts +0 -9
- package/src/tui/command-output-summary.ts +0 -122
- package/src/tui/create-default-tui-cli-adapter.ts +0 -41
- package/src/tui/execution-workspace-view-model.ts +0 -123
- package/src/tui/flows/cjk-text-input-flow.ts +0 -285
- package/src/tui/flows/confirm-prompt-flow.ts +0 -45
- package/src/tui/flows/input-area-flow.ts +0 -189
- package/src/tui/flows/permission-prompt-flow.ts +0 -85
- package/src/tui/flows/selection-flow.ts +0 -126
- package/src/tui/flows/session-init-poller.ts +0 -77
- package/src/tui/flows/text-prompt-flow.ts +0 -98
- package/src/tui/hooks/command-effect-handler.ts +0 -97
- package/src/tui/hooks/command-effect-queue.ts +0 -39
- package/src/tui/hooks/side-effects-types.ts +0 -35
- package/src/tui/hooks/useAutocomplete.ts +0 -87
- package/src/tui/hooks/usePluginCallbacks.ts +0 -31
- package/src/tui/hooks/usePluginScreenData.ts +0 -85
- package/src/tui/hooks/useSideEffects.ts +0 -175
- package/src/tui/hooks/useSlashRouting.ts +0 -118
- package/src/tui/hooks/useStatusLineSettings.ts +0 -37
- package/src/tui/hooks/useTuiChannel.ts +0 -95
- package/src/tui/index.ts +0 -14
- package/src/tui/interactions/CommandConfirm.tsx +0 -36
- package/src/tui/interactions/CommandPicker.tsx +0 -77
- package/src/tui/interactions/__tests__/CommandConfirm.test.tsx +0 -124
- package/src/tui/interactions/__tests__/CommandPicker.test.tsx +0 -138
- package/src/tui/plugin-tui-handlers.ts +0 -163
- package/src/tui/render-markdown.ts +0 -130
- package/src/tui/render.tsx +0 -129
- package/src/tui/session-naming.ts +0 -33
- package/src/tui/status-activity.ts +0 -63
- package/src/tui/tui-cli-adapter-context.tsx +0 -13
- package/src/tui/tui-cli-adapter.ts +0 -25
- package/src/tui/tui-state-manager.ts +0 -226
- package/src/tui/tui-transport.ts +0 -35
- package/src/tui/types.ts +0 -15
- package/src/tui/utils/__tests__/edit-diff.test.ts +0 -426
- package/src/tui/utils/__tests__/paste-detection.test.ts +0 -116
- package/src/tui/utils/__tests__/paste-labels.test.ts +0 -46
- package/src/tui/utils/__tests__/tool-call-extractor.test.ts +0 -227
- package/src/tui/utils/__tests__/tool-diff-summary.test.ts +0 -104
- package/src/tui/utils/edit-diff.ts +0 -153
- package/src/tui/utils/paste-labels.ts +0 -9
- package/src/tui/utils/tool-call-extractor.ts +0 -92
- package/src/tui/utils/tool-diff-summary.ts +0 -75
- package/src/ws/__tests__/ws-handler.test.ts +0 -409
- package/src/ws/__tests__/ws-transport.test.ts +0 -53
- package/src/ws/index.ts +0 -13
- package/src/ws/ws-background-messages.ts +0 -170
- package/src/ws/ws-handler.ts +0 -280
- package/src/ws/ws-protocol.ts +0 -78
- package/src/ws/ws-transport-configurable.ts +0 -128
- package/src/ws/ws-transport.ts +0 -42
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { generateDiffLines, generateDiffLinesWithContext, extractEditDiff } from '../edit-diff.js';
|
|
6
|
-
|
|
7
|
-
describe('generateDiffLines', () => {
|
|
8
|
-
it('single line change: 1 remove + 1 add', () => {
|
|
9
|
-
const lines = generateDiffLines('hello', 'world');
|
|
10
|
-
expect(lines).toEqual([
|
|
11
|
-
{ type: 'remove', text: 'hello', lineNumber: 1 },
|
|
12
|
-
{ type: 'add', text: 'world', lineNumber: 1 },
|
|
13
|
-
]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('multi-line change: each old line is remove, each new line is add', () => {
|
|
17
|
-
const lines = generateDiffLines('line1\nline2\nline3', 'lineA\nlineB');
|
|
18
|
-
expect(lines.filter((l) => l.type === 'remove')).toHaveLength(3);
|
|
19
|
-
expect(lines.filter((l) => l.type === 'add')).toHaveLength(2);
|
|
20
|
-
expect(lines[0]).toEqual({ type: 'remove', text: 'line1', lineNumber: 1 });
|
|
21
|
-
expect(lines[1]).toEqual({ type: 'remove', text: 'line2', lineNumber: 2 });
|
|
22
|
-
expect(lines[2]).toEqual({ type: 'remove', text: 'line3', lineNumber: 3 });
|
|
23
|
-
expect(lines[3]).toEqual({ type: 'add', text: 'lineA', lineNumber: 1 });
|
|
24
|
-
expect(lines[4]).toEqual({ type: 'add', text: 'lineB', lineNumber: 2 });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('identical strings return empty array', () => {
|
|
28
|
-
expect(generateDiffLines('same', 'same')).toEqual([]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('empty old string (new content) returns only add lines', () => {
|
|
32
|
-
const lines = generateDiffLines('', 'new line');
|
|
33
|
-
expect(lines.filter((l) => l.type === 'remove')).toHaveLength(1); // '' splits to ['']
|
|
34
|
-
expect(lines.filter((l) => l.type === 'add')).toHaveLength(1);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('empty new string (deletion) returns only remove lines', () => {
|
|
38
|
-
const lines = generateDiffLines('old line', '');
|
|
39
|
-
expect(lines.filter((l) => l.type === 'remove')).toHaveLength(1);
|
|
40
|
-
expect(lines.filter((l) => l.type === 'add')).toHaveLength(1); // '' splits to ['']
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('marks ALL old lines as remove and ALL new lines as add (no smart diff)', () => {
|
|
44
|
-
// Even when some lines are the same, the function does not detect unchanged lines
|
|
45
|
-
const lines = generateDiffLines('shared\nold', 'shared\nnew');
|
|
46
|
-
expect(lines).toEqual([
|
|
47
|
-
{ type: 'remove', text: 'shared', lineNumber: 1 },
|
|
48
|
-
{ type: 'remove', text: 'old', lineNumber: 2 },
|
|
49
|
-
{ type: 'add', text: 'shared', lineNumber: 1 },
|
|
50
|
-
{ type: 'add', text: 'new', lineNumber: 2 },
|
|
51
|
-
]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('default startLine is 1', () => {
|
|
55
|
-
const lines = generateDiffLines('a', 'b');
|
|
56
|
-
expect(lines[0]).toEqual({ type: 'remove', text: 'a', lineNumber: 1 });
|
|
57
|
-
expect(lines[1]).toEqual({ type: 'add', text: 'b', lineNumber: 1 });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('custom startLine produces correct line numbers', () => {
|
|
61
|
-
const lines = generateDiffLines('a', 'b', 42);
|
|
62
|
-
expect(lines[0]).toEqual({ type: 'remove', text: 'a', lineNumber: 42 });
|
|
63
|
-
expect(lines[1]).toEqual({ type: 'add', text: 'b', lineNumber: 42 });
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('multi-line with startLine: remove lines get startLine, startLine+1, etc.', () => {
|
|
67
|
-
const lines = generateDiffLines('line1\nline2\nline3', 'lineA\nlineB', 10);
|
|
68
|
-
expect(lines[0]).toEqual({ type: 'remove', text: 'line1', lineNumber: 10 });
|
|
69
|
-
expect(lines[1]).toEqual({ type: 'remove', text: 'line2', lineNumber: 11 });
|
|
70
|
-
expect(lines[2]).toEqual({ type: 'remove', text: 'line3', lineNumber: 12 });
|
|
71
|
-
expect(lines[3]).toEqual({ type: 'add', text: 'lineA', lineNumber: 10 });
|
|
72
|
-
expect(lines[4]).toEqual({ type: 'add', text: 'lineB', lineNumber: 11 });
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('extractEditDiff', () => {
|
|
77
|
-
it('Edit tool with valid args returns file and lines', () => {
|
|
78
|
-
const result = extractEditDiff('Edit', {
|
|
79
|
-
file_path: '/src/index.ts',
|
|
80
|
-
old_string: 'hello',
|
|
81
|
-
new_string: 'world',
|
|
82
|
-
});
|
|
83
|
-
expect(result).not.toBeNull();
|
|
84
|
-
expect(result!.file).toBe('/src/index.ts');
|
|
85
|
-
expect(result!.lines).toEqual([
|
|
86
|
-
{ type: 'remove', text: 'hello', lineNumber: 1 },
|
|
87
|
-
{ type: 'add', text: 'world', lineNumber: 1 },
|
|
88
|
-
]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('non-Edit tool returns null', () => {
|
|
92
|
-
expect(
|
|
93
|
-
extractEditDiff('Read', {
|
|
94
|
-
file_path: '/src/index.ts',
|
|
95
|
-
old_string: 'a',
|
|
96
|
-
new_string: 'b',
|
|
97
|
-
}),
|
|
98
|
-
).toBeNull();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('missing toolArgs returns null', () => {
|
|
102
|
-
expect(extractEditDiff('Edit')).toBeNull();
|
|
103
|
-
expect(extractEditDiff('Edit', undefined)).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('missing file_path returns null', () => {
|
|
107
|
-
expect(
|
|
108
|
-
extractEditDiff('Edit', {
|
|
109
|
-
old_string: 'a',
|
|
110
|
-
new_string: 'b',
|
|
111
|
-
}),
|
|
112
|
-
).toBeNull();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('missing old_string returns null', () => {
|
|
116
|
-
expect(
|
|
117
|
-
extractEditDiff('Edit', {
|
|
118
|
-
file_path: '/src/index.ts',
|
|
119
|
-
new_string: 'b',
|
|
120
|
-
}),
|
|
121
|
-
).toBeNull();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('missing new_string returns null', () => {
|
|
125
|
-
expect(
|
|
126
|
-
extractEditDiff('Edit', {
|
|
127
|
-
file_path: '/src/index.ts',
|
|
128
|
-
old_string: 'a',
|
|
129
|
-
}),
|
|
130
|
-
).toBeNull();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('identical old_string and new_string returns null', () => {
|
|
134
|
-
expect(
|
|
135
|
-
extractEditDiff('Edit', {
|
|
136
|
-
file_path: '/src/index.ts',
|
|
137
|
-
old_string: 'same',
|
|
138
|
-
new_string: 'same',
|
|
139
|
-
}),
|
|
140
|
-
).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('handles camelCase field names (filePath, oldString, newString)', () => {
|
|
144
|
-
const result = extractEditDiff('Edit', {
|
|
145
|
-
filePath: '/src/index.ts',
|
|
146
|
-
oldString: 'old',
|
|
147
|
-
newString: 'new',
|
|
148
|
-
});
|
|
149
|
-
expect(result).not.toBeNull();
|
|
150
|
-
expect(result!.file).toBe('/src/index.ts');
|
|
151
|
-
expect(result!.lines).toHaveLength(2);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('handles snake_case field names (file_path, old_string, new_string)', () => {
|
|
155
|
-
const result = extractEditDiff('Edit', {
|
|
156
|
-
file_path: '/src/main.ts',
|
|
157
|
-
old_string: 'foo',
|
|
158
|
-
new_string: 'bar',
|
|
159
|
-
});
|
|
160
|
-
expect(result).not.toBeNull();
|
|
161
|
-
expect(result!.file).toBe('/src/main.ts');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('prefers snake_case over camelCase when both present', () => {
|
|
165
|
-
const result = extractEditDiff('Edit', {
|
|
166
|
-
file_path: '/snake.ts',
|
|
167
|
-
filePath: '/camel.ts',
|
|
168
|
-
old_string: 'a',
|
|
169
|
-
oldString: 'x',
|
|
170
|
-
new_string: 'b',
|
|
171
|
-
newString: 'y',
|
|
172
|
-
});
|
|
173
|
-
expect(result).not.toBeNull();
|
|
174
|
-
expect(result!.file).toBe('/snake.ts');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('accepts optional startLine parameter', () => {
|
|
178
|
-
const result = extractEditDiff(
|
|
179
|
-
'Edit',
|
|
180
|
-
{
|
|
181
|
-
file_path: '/src/index.ts',
|
|
182
|
-
old_string: 'hello',
|
|
183
|
-
new_string: 'world',
|
|
184
|
-
},
|
|
185
|
-
5,
|
|
186
|
-
);
|
|
187
|
-
expect(result).not.toBeNull();
|
|
188
|
-
expect(result!.lines).toEqual([
|
|
189
|
-
{ type: 'remove', text: 'hello', lineNumber: 5 },
|
|
190
|
-
{ type: 'add', text: 'world', lineNumber: 5 },
|
|
191
|
-
]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('defaults startLine to 1 when not provided', () => {
|
|
195
|
-
const result = extractEditDiff('Edit', {
|
|
196
|
-
file_path: '/src/index.ts',
|
|
197
|
-
old_string: 'a',
|
|
198
|
-
new_string: 'b',
|
|
199
|
-
});
|
|
200
|
-
expect(result).not.toBeNull();
|
|
201
|
-
expect(result!.lines[0].lineNumber).toBe(1);
|
|
202
|
-
expect(result!.lines[1].lineNumber).toBe(1);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// ─── Regression tests: context lines, absolute line numbers, startLine resolution ───
|
|
207
|
-
|
|
208
|
-
describe('generateDiffLinesWithContext', () => {
|
|
209
|
-
let tmpDir: string;
|
|
210
|
-
|
|
211
|
-
afterEach(() => {
|
|
212
|
-
if (tmpDir) {
|
|
213
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
function makeTempFile(content: string): string {
|
|
218
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'edit-diff-test-'));
|
|
219
|
-
const filePath = join(tmpDir, 'test.ts');
|
|
220
|
-
writeFileSync(filePath, content, 'utf-8');
|
|
221
|
-
return filePath;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
it('should include 3 context lines before the change', () => {
|
|
225
|
-
// 10-line file, edit line 5 (replaced already in file)
|
|
226
|
-
const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
|
|
227
|
-
// Simulate: line5 was replaced with lineNEW
|
|
228
|
-
const modifiedLines = [...fileLines];
|
|
229
|
-
modifiedLines[4] = 'lineNEW';
|
|
230
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
231
|
-
|
|
232
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
233
|
-
|
|
234
|
-
// Context before: line2, line3, line4
|
|
235
|
-
const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 5);
|
|
236
|
-
expect(contextBefore).toHaveLength(3);
|
|
237
|
-
expect(contextBefore[0]).toEqual({ type: 'context', text: 'line2', lineNumber: 2 });
|
|
238
|
-
expect(contextBefore[1]).toEqual({ type: 'context', text: 'line3', lineNumber: 3 });
|
|
239
|
-
expect(contextBefore[2]).toEqual({ type: 'context', text: 'line4', lineNumber: 4 });
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should include 3 context lines after the change', () => {
|
|
243
|
-
const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
|
|
244
|
-
const modifiedLines = [...fileLines];
|
|
245
|
-
modifiedLines[4] = 'lineNEW';
|
|
246
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
247
|
-
|
|
248
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
249
|
-
|
|
250
|
-
// Context after: line6, line7, line8
|
|
251
|
-
const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 5);
|
|
252
|
-
expect(contextAfter).toHaveLength(3);
|
|
253
|
-
expect(contextAfter[0]).toEqual({ type: 'context', text: 'line6', lineNumber: 6 });
|
|
254
|
-
expect(contextAfter[1]).toEqual({ type: 'context', text: 'line7', lineNumber: 7 });
|
|
255
|
-
expect(contextAfter[2]).toEqual({ type: 'context', text: 'line8', lineNumber: 8 });
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should handle edit at start of file (no lines before)', () => {
|
|
259
|
-
const fileLines = Array.from({ length: 5 }, (_, i) => `line${i + 1}`);
|
|
260
|
-
const modifiedLines = [...fileLines];
|
|
261
|
-
modifiedLines[0] = 'lineNEW';
|
|
262
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
263
|
-
|
|
264
|
-
const result = generateDiffLinesWithContext('line1', 'lineNEW', 1, filePath);
|
|
265
|
-
|
|
266
|
-
const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 1);
|
|
267
|
-
expect(contextBefore).toHaveLength(0);
|
|
268
|
-
|
|
269
|
-
// Context after: line2, line3, line4
|
|
270
|
-
const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 1);
|
|
271
|
-
expect(contextAfter).toHaveLength(3);
|
|
272
|
-
expect(contextAfter[0]).toEqual({ type: 'context', text: 'line2', lineNumber: 2 });
|
|
273
|
-
expect(contextAfter[1]).toEqual({ type: 'context', text: 'line3', lineNumber: 3 });
|
|
274
|
-
expect(contextAfter[2]).toEqual({ type: 'context', text: 'line4', lineNumber: 4 });
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('should handle edit at end of file (no lines after)', () => {
|
|
278
|
-
const fileLines = Array.from({ length: 5 }, (_, i) => `line${i + 1}`);
|
|
279
|
-
const modifiedLines = [...fileLines];
|
|
280
|
-
modifiedLines[4] = 'lineNEW';
|
|
281
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
282
|
-
|
|
283
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
284
|
-
|
|
285
|
-
// Context before: line2, line3, line4
|
|
286
|
-
const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 5);
|
|
287
|
-
expect(contextBefore).toHaveLength(3);
|
|
288
|
-
|
|
289
|
-
// Context after: none (line5 is the last line)
|
|
290
|
-
const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 5);
|
|
291
|
-
expect(contextAfter).toHaveLength(0);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it('context lines should have type "context"', () => {
|
|
295
|
-
const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
|
|
296
|
-
const modifiedLines = [...fileLines];
|
|
297
|
-
modifiedLines[4] = 'lineNEW';
|
|
298
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
299
|
-
|
|
300
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
301
|
-
|
|
302
|
-
const contextLines = result.filter((l) => l.type === 'context');
|
|
303
|
-
expect(contextLines.length).toBeGreaterThan(0);
|
|
304
|
-
for (const line of contextLines) {
|
|
305
|
-
expect(line.type).toBe('context');
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('starts readable context diffs with a hunk header', () => {
|
|
310
|
-
const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
|
|
311
|
-
const modifiedLines = [...fileLines];
|
|
312
|
-
modifiedLines[4] = 'lineNEW';
|
|
313
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
314
|
-
|
|
315
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
316
|
-
|
|
317
|
-
expect(result[0]).toEqual({
|
|
318
|
-
type: 'hunk',
|
|
319
|
-
text: '@@ -2,7 +2,7 @@',
|
|
320
|
-
lineNumber: 2,
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('all lines should have absolute lineNumber', () => {
|
|
325
|
-
const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
|
|
326
|
-
const modifiedLines = [...fileLines];
|
|
327
|
-
modifiedLines[4] = 'lineNEW';
|
|
328
|
-
const filePath = makeTempFile(modifiedLines.join('\n'));
|
|
329
|
-
|
|
330
|
-
const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
|
|
331
|
-
|
|
332
|
-
for (const line of result) {
|
|
333
|
-
expect(typeof line.lineNumber).toBe('number');
|
|
334
|
-
expect(line.lineNumber).toBeGreaterThan(0);
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('returns diff without context when file is not readable', () => {
|
|
339
|
-
const result = generateDiffLinesWithContext('old', 'new', 5, '/nonexistent/path/file.ts');
|
|
340
|
-
|
|
341
|
-
// Should still return diff lines, just no context
|
|
342
|
-
expect(result.length).toBeGreaterThan(0);
|
|
343
|
-
const contextLines = result.filter((l) => l.type === 'context');
|
|
344
|
-
expect(contextLines).toHaveLength(0);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('identical strings return empty array', () => {
|
|
348
|
-
const filePath = makeTempFile('some content');
|
|
349
|
-
const result = generateDiffLinesWithContext('same', 'same', 1, filePath);
|
|
350
|
-
expect(result).toEqual([]);
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
describe('extractEditDiff startLine resolution', () => {
|
|
355
|
-
let tmpDir: string;
|
|
356
|
-
|
|
357
|
-
afterEach(() => {
|
|
358
|
-
if (tmpDir) {
|
|
359
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
function makeTempFile(content: string): string {
|
|
364
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'edit-diff-test-'));
|
|
365
|
-
const filePath = join(tmpDir, 'test.ts');
|
|
366
|
-
writeFileSync(filePath, content, 'utf-8');
|
|
367
|
-
return filePath;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
it('should use provided startLine', () => {
|
|
371
|
-
const result = extractEditDiff(
|
|
372
|
-
'Edit',
|
|
373
|
-
{
|
|
374
|
-
file_path: '/nonexistent/file.ts',
|
|
375
|
-
old_string: 'hello',
|
|
376
|
-
new_string: 'world',
|
|
377
|
-
},
|
|
378
|
-
42,
|
|
379
|
-
);
|
|
380
|
-
expect(result).not.toBeNull();
|
|
381
|
-
const removeLines = result!.lines.filter((l) => l.type === 'remove');
|
|
382
|
-
expect(removeLines[0].lineNumber).toBe(42);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('should resolve startLine from file when not provided', () => {
|
|
386
|
-
// Create file where newStr appears at line 4
|
|
387
|
-
const content = 'line1\nline2\nline3\nREPLACED\nline5';
|
|
388
|
-
const filePath = makeTempFile(content);
|
|
389
|
-
|
|
390
|
-
const result = extractEditDiff('Edit', {
|
|
391
|
-
file_path: filePath,
|
|
392
|
-
old_string: 'original',
|
|
393
|
-
new_string: 'REPLACED',
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
expect(result).not.toBeNull();
|
|
397
|
-
// newStr "REPLACED" is at line 4 in the file
|
|
398
|
-
const removeLines = result!.lines.filter((l) => l.type === 'remove');
|
|
399
|
-
expect(removeLines[0].lineNumber).toBe(4);
|
|
400
|
-
const addLines = result!.lines.filter((l) => l.type === 'add');
|
|
401
|
-
expect(addLines[0].lineNumber).toBe(4);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it('should fall back to line 1 when file not readable', () => {
|
|
405
|
-
const result = extractEditDiff('Edit', {
|
|
406
|
-
file_path: '/nonexistent/path/file.ts',
|
|
407
|
-
old_string: 'a',
|
|
408
|
-
new_string: 'b',
|
|
409
|
-
});
|
|
410
|
-
expect(result).not.toBeNull();
|
|
411
|
-
expect(result!.lines[0].lineNumber).toBe(1);
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it('should fall back to line 1 when newString not found in file', () => {
|
|
415
|
-
const content = 'line1\nline2\nline3';
|
|
416
|
-
const filePath = makeTempFile(content);
|
|
417
|
-
|
|
418
|
-
const result = extractEditDiff('Edit', {
|
|
419
|
-
file_path: filePath,
|
|
420
|
-
old_string: 'something',
|
|
421
|
-
new_string: 'NOT_IN_FILE_ANYWHERE',
|
|
422
|
-
});
|
|
423
|
-
expect(result).not.toBeNull();
|
|
424
|
-
expect(result!.lines[0].lineNumber).toBe(1);
|
|
425
|
-
});
|
|
426
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for paste detection and the full paste → label → expand pipeline.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import { filterPrintable } from '../../flows/cjk-text-input-flow.js';
|
|
7
|
-
import { expandPasteLabels } from '../paste-labels.js';
|
|
8
|
-
|
|
9
|
-
describe('filterPrintable and newlines', () => {
|
|
10
|
-
it('should strip newlines from input (they are control chars)', () => {
|
|
11
|
-
expect(filterPrintable('line1\nline2')).toBe('line1line2');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should strip carriage returns', () => {
|
|
15
|
-
expect(filterPrintable('line1\r\nline2')).toBe('line1line2');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should strip tabs', () => {
|
|
19
|
-
expect(filterPrintable('a\tb')).toBe('ab');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should preserve normal text', () => {
|
|
23
|
-
expect(filterPrintable('hello world')).toBe('hello world');
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('Full paste pipeline: detect → label → expand', () => {
|
|
28
|
-
it('should correctly round-trip multiline paste content', () => {
|
|
29
|
-
const pastedText = 'const a = 1;\nconst b = 2;\nconst c = 3;';
|
|
30
|
-
const store = new Map<number, string>();
|
|
31
|
-
|
|
32
|
-
// Simulate paste detection
|
|
33
|
-
expect(pastedText.length > 1 && pastedText.includes('\n')).toBe(true);
|
|
34
|
-
|
|
35
|
-
// Simulate store + label creation (as InputArea.handlePaste does)
|
|
36
|
-
const id = 1;
|
|
37
|
-
store.set(id, pastedText);
|
|
38
|
-
const lineCount = pastedText.split('\n').length;
|
|
39
|
-
const label = `[Pasted text #${id} +${lineCount} lines]`;
|
|
40
|
-
expect(label).toBe('[Pasted text #1 +3 lines]');
|
|
41
|
-
|
|
42
|
-
// Simulate submit → expand
|
|
43
|
-
const expanded = expandPasteLabels(label, store);
|
|
44
|
-
expect(expanded).toBe(pastedText);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should handle paste mixed with typed text', () => {
|
|
48
|
-
const pastedText = 'function hello() {\n return "world";\n}';
|
|
49
|
-
const store = new Map<number, string>();
|
|
50
|
-
store.set(1, pastedText);
|
|
51
|
-
|
|
52
|
-
const userInput = 'Review this code: [Pasted text #1 +3 lines] and fix any bugs';
|
|
53
|
-
const expanded = expandPasteLabels(userInput, store);
|
|
54
|
-
expect(expanded).toBe(
|
|
55
|
-
'Review this code: function hello() {\n return "world";\n} and fix any bugs',
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should handle multiple pastes in one message', () => {
|
|
60
|
-
const store = new Map<number, string>();
|
|
61
|
-
store.set(1, 'first\npaste');
|
|
62
|
-
store.set(2, 'second\npaste\nhere');
|
|
63
|
-
|
|
64
|
-
const input = '[Pasted text #1 +2 lines] compare with [Pasted text #2 +3 lines]';
|
|
65
|
-
const expanded = expandPasteLabels(input, store);
|
|
66
|
-
expect(expanded).toBe('first\npaste compare with second\npaste\nhere');
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('Paste at cursor position', () => {
|
|
71
|
-
it('label is inserted at cursor position, not end', () => {
|
|
72
|
-
const existingText = 'hello world';
|
|
73
|
-
const cursorPosition = 5; // between "hello" and " world"
|
|
74
|
-
const pastedText = 'line1\nline2';
|
|
75
|
-
const store = new Map<number, string>();
|
|
76
|
-
const id = 1;
|
|
77
|
-
store.set(id, pastedText);
|
|
78
|
-
const lineCount = pastedText.split('\n').length;
|
|
79
|
-
const label = `[Pasted text #${id} +${lineCount} lines]`;
|
|
80
|
-
|
|
81
|
-
// Simulate cursor-aware insertion (as handlePaste does)
|
|
82
|
-
const result =
|
|
83
|
-
existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
|
|
84
|
-
expect(result).toBe('hello[Pasted text #1 +2 lines] world');
|
|
85
|
-
|
|
86
|
-
// Expand should restore original text at correct position
|
|
87
|
-
const expanded = expandPasteLabels(result, store);
|
|
88
|
-
expect(expanded).toBe('helloline1\nline2 world');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('cursor hint equals cursorPosition + label.length', () => {
|
|
92
|
-
const cursorPosition = 5;
|
|
93
|
-
const label = '[Pasted text #1 +3 lines]';
|
|
94
|
-
const newCursorPos = cursorPosition + label.length;
|
|
95
|
-
expect(newCursorPos).toBe(5 + 25); // label is 25 chars
|
|
96
|
-
expect(newCursorPos).toBe(30);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('paste at start (cursor = 0)', () => {
|
|
100
|
-
const existingText = 'existing';
|
|
101
|
-
const cursorPosition = 0;
|
|
102
|
-
const label = '[Pasted text #1 +2 lines]';
|
|
103
|
-
const result =
|
|
104
|
-
existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
|
|
105
|
-
expect(result).toBe('[Pasted text #1 +2 lines]existing');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('paste at end (cursor = text.length)', () => {
|
|
109
|
-
const existingText = 'existing';
|
|
110
|
-
const cursorPosition = existingText.length;
|
|
111
|
-
const label = '[Pasted text #1 +2 lines]';
|
|
112
|
-
const result =
|
|
113
|
-
existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
|
|
114
|
-
expect(result).toBe('existing[Pasted text #1 +2 lines]');
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { expandPasteLabels } from '../paste-labels.js';
|
|
3
|
-
|
|
4
|
-
describe('expandPasteLabels', () => {
|
|
5
|
-
it('should expand single paste label', () => {
|
|
6
|
-
const store = new Map([[1, 'line1\nline2\nline3']]);
|
|
7
|
-
const result = expandPasteLabels('[Pasted text #1 +3 lines]', store);
|
|
8
|
-
expect(result).toBe('line1\nline2\nline3');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should expand multiple paste labels', () => {
|
|
12
|
-
const store = new Map([
|
|
13
|
-
[1, 'first paste'],
|
|
14
|
-
[2, 'second paste'],
|
|
15
|
-
]);
|
|
16
|
-
const result = expandPasteLabels(
|
|
17
|
-
'before [Pasted text #1 +1 lines] middle [Pasted text #2 +1 lines] after',
|
|
18
|
-
store,
|
|
19
|
-
);
|
|
20
|
-
expect(result).toBe('before first paste middle second paste after');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should return empty string for missing store entry', () => {
|
|
24
|
-
const store = new Map<number, string>();
|
|
25
|
-
const result = expandPasteLabels('[Pasted text #1 +3 lines]', store);
|
|
26
|
-
expect(result).toBe('');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should preserve text without paste labels', () => {
|
|
30
|
-
const store = new Map<number, string>();
|
|
31
|
-
const result = expandPasteLabels('regular text without labels', store);
|
|
32
|
-
expect(result).toBe('regular text without labels');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should handle label with different line counts', () => {
|
|
36
|
-
const store = new Map([[1, 'a\nb']]);
|
|
37
|
-
expect(expandPasteLabels('[Pasted text #1 +2 lines]', store)).toBe('a\nb');
|
|
38
|
-
expect(expandPasteLabels('[Pasted text #1 +99 lines]', store)).toBe('a\nb');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should expand label without line count (debounce pending)', () => {
|
|
42
|
-
const store = new Map([[1, 'partial paste']]);
|
|
43
|
-
const result = expandPasteLabels('[Pasted text #1]', store);
|
|
44
|
-
expect(result).toBe('partial paste');
|
|
45
|
-
});
|
|
46
|
-
});
|