@zoebuildsai/trace 1.5.0
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/.gitignore +115 -0
- package/.trace/progress.json +22 -0
- package/README.md +466 -0
- package/RELEASE-NOTES-1.5.0.md +410 -0
- package/STATUS.md +245 -0
- package/dist/auto-commit.d.ts +66 -0
- package/dist/auto-commit.d.ts.map +1 -0
- package/dist/auto-commit.js +180 -0
- package/dist/auto-commit.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +46 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +256 -0
- package/dist/commands.js.map +1 -0
- package/dist/diff.d.ts +23 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +106 -0
- package/dist/diff.js.map +1 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js.map +1 -0
- package/dist/index-cache.d.ts +35 -0
- package/dist/index-cache.d.ts.map +1 -0
- package/dist/index-cache.js +114 -0
- package/dist/index-cache.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +45 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +151 -0
- package/dist/storage.js.map +1 -0
- package/dist/sync.d.ts +60 -0
- package/dist/sync.js +184 -0
- package/dist/tags.d.ts +85 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +219 -0
- package/dist/tags.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +73 -0
- package/docs/_config.yml +2 -0
- package/docs/index.html +960 -0
- package/docs-website/package.json +20 -0
- package/jest.config.js +21 -0
- package/package.json +50 -0
- package/scripts/init.ts +290 -0
- package/src/agent-audit.ts +270 -0
- package/src/agent-checkout.ts +227 -0
- package/src/agent-coordination.ts +318 -0
- package/src/async-queue.ts +203 -0
- package/src/auto-branching.ts +279 -0
- package/src/auto-commit.ts +166 -0
- package/src/cherry-pick.ts +252 -0
- package/src/chunked-upload.ts +224 -0
- package/src/cli-v2.ts +335 -0
- package/src/cli.ts +318 -0
- package/src/cliff-detection.ts +232 -0
- package/src/commands.ts +267 -0
- package/src/commit-hash-system.ts +351 -0
- package/src/compression.ts +176 -0
- package/src/conflict-resolution-ui.ts +277 -0
- package/src/conflict-visualization.ts +238 -0
- package/src/diff-formatter.ts +184 -0
- package/src/diff.ts +124 -0
- package/src/distributed-coordination.ts +273 -0
- package/src/git-interop.ts +316 -0
- package/src/index-cache.ts +88 -0
- package/src/index.ts +38 -0
- package/src/merge-engine.ts +143 -0
- package/src/message-search.ts +370 -0
- package/src/performance-monitoring.ts +236 -0
- package/src/rebase.ts +327 -0
- package/src/rollback.ts +215 -0
- package/src/semantic-grouping.ts +245 -0
- package/src/stage-area.ts +324 -0
- package/src/stash.ts +278 -0
- package/src/storage.ts +131 -0
- package/src/sync.ts +205 -0
- package/src/tags.ts +244 -0
- package/src/types.ts +119 -0
- package/src/webhooks.ts +119 -0
- package/src/workspace-isolation.ts +298 -0
- package/tests/auto-commit.test.ts +308 -0
- package/tests/checkout.test.ts +136 -0
- package/tests/commit.test.ts +118 -0
- package/tests/diff.test.ts +191 -0
- package/tests/github.test.ts +94 -0
- package/tests/integration.test.ts +267 -0
- package/tests/log.test.ts +125 -0
- package/tests/phase2-integration.test.ts +370 -0
- package/tests/storage.test.ts +167 -0
- package/tests/tags.test.ts +477 -0
- package/tests/types.test.ts +75 -0
- package/tests/v1.1/agent-audit.test.ts +472 -0
- package/tests/v1.1/agent-coordination.test.ts +308 -0
- package/tests/v1.1/async-queue.test.ts +253 -0
- package/tests/v1.1/comprehensive.test.ts +521 -0
- package/tests/v1.1/diff-formatter.test.ts +238 -0
- package/tests/v1.1/integration.test.ts +389 -0
- package/tests/v1.1/onboarding.test.ts +365 -0
- package/tests/v1.1/rollback.test.ts +370 -0
- package/tests/v1.1/semantic-grouping.test.ts +230 -0
- package/tests/v1.2/chunked-upload.test.ts +301 -0
- package/tests/v1.2/cliff-detection.test.ts +272 -0
- package/tests/v1.2/commit-hash-system.test.ts +288 -0
- package/tests/v1.2/compression.test.ts +220 -0
- package/tests/v1.2/conflict-visualization.test.ts +263 -0
- package/tests/v1.2/distributed.test.ts +261 -0
- package/tests/v1.2/performance-monitoring.test.ts +328 -0
- package/tests/v1.3/auto-branching.test.ts +270 -0
- package/tests/v1.3/message-search.test.ts +264 -0
- package/tests/v1.3/stage-area.test.ts +330 -0
- package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
- package/tests/v1.4/cli.test.ts +171 -0
- package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
- package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
- package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
- package/tests/v1.4/workspace-isolation.test.ts +268 -0
- package/tests/v1.5/agent-coordination.real.test.ts +401 -0
- package/tests/v1.5/cli-v2.test.ts +354 -0
- package/tests/v1.5/git-interop.real.test.ts +358 -0
- package/tests/v1.5/integration-testing.real.test.ts +440 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Conflict Resolution Tests
|
|
3
|
+
* Complex merge scenarios and edge cases
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ConflictResolutionUI } from '../../src/conflict-resolution-ui';
|
|
7
|
+
|
|
8
|
+
describe('ConflictResolutionUI - Advanced Scenarios', () => {
|
|
9
|
+
describe('Complex Code Conflicts', () => {
|
|
10
|
+
test('handles import statement conflicts', () => {
|
|
11
|
+
const ours = `import React from 'react';
|
|
12
|
+
import { Component } from 'react';
|
|
13
|
+
import styles from './App.css';`;
|
|
14
|
+
|
|
15
|
+
const theirs = `import React, { Component } from 'react';
|
|
16
|
+
import { useEffect } from 'react';
|
|
17
|
+
import './App.css';`;
|
|
18
|
+
|
|
19
|
+
const base = `import React from 'react';`;
|
|
20
|
+
|
|
21
|
+
const html = ConflictResolutionUI.renderHTML('App.tsx', ours, theirs, base);
|
|
22
|
+
expect(html).toContain('import');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('handles function signature conflicts', () => {
|
|
26
|
+
const ours = `function processData(items: Item[], options?: ProcessOptions): Result {
|
|
27
|
+
return new Result(items, options);
|
|
28
|
+
}`;
|
|
29
|
+
|
|
30
|
+
const theirs = `async function processData(items: Item[]): Promise<Result> {
|
|
31
|
+
return await new Result(items).process();
|
|
32
|
+
}`;
|
|
33
|
+
|
|
34
|
+
const base = `function processData(items: Item[]): Result {
|
|
35
|
+
return new Result(items);
|
|
36
|
+
}`;
|
|
37
|
+
|
|
38
|
+
const result = ConflictResolutionUI.suggestResolution(ours, theirs, base);
|
|
39
|
+
expect(result.suggestion).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('handles class method conflicts', () => {
|
|
43
|
+
const ours = `class UserService {
|
|
44
|
+
getUser(id: string) {
|
|
45
|
+
return db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
46
|
+
}
|
|
47
|
+
}`;
|
|
48
|
+
|
|
49
|
+
const theirs = `class UserService {
|
|
50
|
+
async getUser(id: string): Promise<User> {
|
|
51
|
+
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
52
|
+
return user[0];
|
|
53
|
+
}
|
|
54
|
+
}`;
|
|
55
|
+
|
|
56
|
+
const base = `class UserService {
|
|
57
|
+
getUser(id: string) {
|
|
58
|
+
return db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
59
|
+
}
|
|
60
|
+
}`;
|
|
61
|
+
|
|
62
|
+
const html = ConflictResolutionUI.renderHTML('UserService.ts', ours, theirs, base);
|
|
63
|
+
expect(html).toContain('async');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('handles JSON/config conflicts', () => {
|
|
67
|
+
const ours = `{
|
|
68
|
+
"name": "app",
|
|
69
|
+
"version": "1.0.0",
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"react": "^18.0.0",
|
|
72
|
+
"lodash": "^4.17.0"
|
|
73
|
+
}
|
|
74
|
+
}`;
|
|
75
|
+
|
|
76
|
+
const theirs = `{
|
|
77
|
+
"name": "app",
|
|
78
|
+
"version": "1.1.0",
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"react": "^18.2.0",
|
|
81
|
+
"typescript": "^5.0.0"
|
|
82
|
+
}
|
|
83
|
+
}`;
|
|
84
|
+
|
|
85
|
+
const base = `{
|
|
86
|
+
"name": "app",
|
|
87
|
+
"version": "1.0.0",
|
|
88
|
+
"dependencies": {}
|
|
89
|
+
}`;
|
|
90
|
+
|
|
91
|
+
const html = ConflictResolutionUI.renderHTML('package.json', ours, theirs, base);
|
|
92
|
+
expect(html).toContain('version');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Multi-Conflict Files', () => {
|
|
97
|
+
test('parses multiple conflicts in one file', () => {
|
|
98
|
+
const content = `export const config1 = {
|
|
99
|
+
<<<<<<< HEAD
|
|
100
|
+
apiUrl: 'prod',
|
|
101
|
+
timeout: 5000,
|
|
102
|
+
=======
|
|
103
|
+
apiUrl: 'dev',
|
|
104
|
+
timeout: 10000,
|
|
105
|
+
>>>>>>> feature
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const config2 = {
|
|
109
|
+
<<<<<<< HEAD
|
|
110
|
+
debug: false,
|
|
111
|
+
=======
|
|
112
|
+
debug: true,
|
|
113
|
+
>>>>>>> feature
|
|
114
|
+
};`;
|
|
115
|
+
|
|
116
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
117
|
+
expect(result.hasConflicts).toBe(true);
|
|
118
|
+
expect(result.markers.length).toBeGreaterThanOrEqual(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('handles nested conflicts', () => {
|
|
122
|
+
const content = `function outer() {
|
|
123
|
+
<<<<<<< HEAD
|
|
124
|
+
const inner = function() {
|
|
125
|
+
return 'ours';
|
|
126
|
+
};
|
|
127
|
+
=======
|
|
128
|
+
const inner = () => 'theirs';
|
|
129
|
+
>>>>>>> feature
|
|
130
|
+
return inner();
|
|
131
|
+
}`;
|
|
132
|
+
|
|
133
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
134
|
+
expect(result.hasConflicts).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('identifies correct conflict boundaries', () => {
|
|
138
|
+
const content = `start
|
|
139
|
+
<<<<<<< ours
|
|
140
|
+
content1
|
|
141
|
+
content2
|
|
142
|
+
content3
|
|
143
|
+
=======
|
|
144
|
+
different1
|
|
145
|
+
different2
|
|
146
|
+
>>>>>>> theirs
|
|
147
|
+
end`;
|
|
148
|
+
|
|
149
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
150
|
+
const marker = result.markers[0];
|
|
151
|
+
expect(marker).toBeDefined();
|
|
152
|
+
expect(marker.oursStart < marker.oursEnd).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('Large File Conflicts', () => {
|
|
157
|
+
test('handles large conflict efficiently', () => {
|
|
158
|
+
const largeOurs = Array(1000)
|
|
159
|
+
.fill('line')
|
|
160
|
+
.map((l, i) => `${l} ${i}`)
|
|
161
|
+
.join('\n');
|
|
162
|
+
const largeTheirs = Array(1000)
|
|
163
|
+
.fill('other')
|
|
164
|
+
.map((l, i) => `${l} ${i}`)
|
|
165
|
+
.join('\n');
|
|
166
|
+
|
|
167
|
+
const start = Date.now();
|
|
168
|
+
const html = ConflictResolutionUI.renderHTML('large.ts', largeOurs, largeTheirs, 'base');
|
|
169
|
+
const elapsed = Date.now() - start;
|
|
170
|
+
|
|
171
|
+
expect(elapsed).toBeLessThan(500);
|
|
172
|
+
expect(html).toContain('1000 lines');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('terminal rendering truncates large files', () => {
|
|
176
|
+
const largeLines = Array(100).fill('line');
|
|
177
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
178
|
+
'file.ts',
|
|
179
|
+
largeLines,
|
|
180
|
+
largeLines,
|
|
181
|
+
largeLines
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(output).toContain('... +');
|
|
185
|
+
expect(output).not.toContain('line 99'); // Should truncate
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Binary/Non-Text Conflicts', () => {
|
|
190
|
+
test('detects binary file conflicts', () => {
|
|
191
|
+
const binaryMarker = Buffer.from([0xff, 0xd8, 0xff, 0xe0]).toString('utf8');
|
|
192
|
+
const content = `<<<<<<< HEAD
|
|
193
|
+
${binaryMarker}
|
|
194
|
+
=======
|
|
195
|
+
different binary
|
|
196
|
+
>>>>>>> theirs`;
|
|
197
|
+
|
|
198
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
199
|
+
expect(result.hasConflicts).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('Special Characters & Encoding', () => {
|
|
204
|
+
test('handles Unicode characters', () => {
|
|
205
|
+
const ours = 'export const msg = "Hello 世界";';
|
|
206
|
+
const theirs = 'export const msg = "Hola 世界";';
|
|
207
|
+
const base = 'export const msg = "Hi";';
|
|
208
|
+
|
|
209
|
+
const html = ConflictResolutionUI.renderHTML('i18n.ts', ours, theirs, base);
|
|
210
|
+
expect(html).toContain('&');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('escapes HTML special characters', () => {
|
|
214
|
+
const ours = '<script>alert("xss")</script>';
|
|
215
|
+
const theirs = '<img src=x onerror="alert(1)">';
|
|
216
|
+
const base = 'console.log("safe");';
|
|
217
|
+
|
|
218
|
+
const html = ConflictResolutionUI.renderHTML('xss.ts', ours, theirs, base);
|
|
219
|
+
expect(html).not.toContain('<script>');
|
|
220
|
+
expect(html).not.toContain('onerror=');
|
|
221
|
+
expect(html).toContain('<');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('handles emoji and special symbols', () => {
|
|
225
|
+
const ours = '// TODO: Implement feature 🚀';
|
|
226
|
+
const theirs = '// FIXME: Bug here ⚠️';
|
|
227
|
+
|
|
228
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', ours, theirs, 'base');
|
|
229
|
+
expect(html.length).toBeGreaterThan(0);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('preserves whitespace in conflicts', () => {
|
|
233
|
+
const ours = 'const x = 1; // extra spaces';
|
|
234
|
+
const theirs = 'const x = 1; // triple spaces';
|
|
235
|
+
|
|
236
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', ours, theirs, 'base');
|
|
237
|
+
expect(html).toContain('pre-wrap'); // Should preserve whitespace
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('Suggestion Algorithm', () => {
|
|
242
|
+
test('prefers shorter for obvious cases', () => {
|
|
243
|
+
const result = ConflictResolutionUI.suggestResolution(
|
|
244
|
+
'x = 1;',
|
|
245
|
+
'const veryLongVariableNameWithComplexLogic = functionCall(param1, param2, param3);',
|
|
246
|
+
'base'
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expect(result.suggestion).toBe('ours');
|
|
250
|
+
expect(result.confidence).toBeGreaterThan(0.5);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('provides confidence levels', () => {
|
|
254
|
+
const confident = ConflictResolutionUI.suggestResolution('short', 'very long content', 'base');
|
|
255
|
+
const uncertain = ConflictResolutionUI.suggestResolution('medium', 'medium length', 'base');
|
|
256
|
+
|
|
257
|
+
expect(confident.confidence).toBeGreaterThan(uncertain.confidence);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('explains reasoning', () => {
|
|
261
|
+
const result = ConflictResolutionUI.suggestResolution('a', 'longer', 'b');
|
|
262
|
+
expect(result.reason).toContain('shorter');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Git Format Variations', () => {
|
|
267
|
+
test('handles custom conflict marker names', () => {
|
|
268
|
+
const content = `<<<<<<< feature/my-branch
|
|
269
|
+
our code
|
|
270
|
+
=======
|
|
271
|
+
their code
|
|
272
|
+
>>>>>>> main`;
|
|
273
|
+
|
|
274
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
275
|
+
expect(result.hasConflicts).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('handles three-way merge markers (merge base)', () => {
|
|
279
|
+
const content = `<<<<<<< current
|
|
280
|
+
our change
|
|
281
|
+
||||||| base
|
|
282
|
+
original
|
|
283
|
+
=======
|
|
284
|
+
their change
|
|
285
|
+
>>>>>>> other`;
|
|
286
|
+
|
|
287
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
288
|
+
// Should handle various conflict formats
|
|
289
|
+
expect(Array.isArray(result.markers)).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('handles edge case: marker at EOF', () => {
|
|
293
|
+
const content = `line 1
|
|
294
|
+
line 2
|
|
295
|
+
<<<<<<< HEAD
|
|
296
|
+
our final line`;
|
|
297
|
+
|
|
298
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
299
|
+
// Should handle incomplete conflicts gracefully
|
|
300
|
+
expect(result.hasConflicts).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('HTML UI Features', () => {
|
|
305
|
+
test('HTML includes copy-to-clipboard function', () => {
|
|
306
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'ours', 'theirs', 'base');
|
|
307
|
+
// Should be safe to copy code from rendered HTML
|
|
308
|
+
expect(html).toContain('line-content');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('HTML includes keyboard shortcuts hints', () => {
|
|
312
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'ours', 'theirs', 'base');
|
|
313
|
+
expect(html).toContain('onclick');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('terminal includes clear instructions', () => {
|
|
317
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
318
|
+
'file.ts',
|
|
319
|
+
['our'],
|
|
320
|
+
['their'],
|
|
321
|
+
['base']
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(output).toContain('[o]');
|
|
325
|
+
expect(output).toContain('[t]');
|
|
326
|
+
expect(output).toContain('[m]');
|
|
327
|
+
expect(output).toContain('[s]');
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('Error Scenarios', () => {
|
|
332
|
+
test('handles empty inputs', () => {
|
|
333
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', '', '', '');
|
|
334
|
+
expect(html).toContain('Conflict Resolution');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('handles very long lines', () => {
|
|
338
|
+
const longLine = 'a'.repeat(1000);
|
|
339
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', longLine, 'short', 'base');
|
|
340
|
+
expect(html).toContain('white-space: pre-wrap');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('handles mixed line endings', () => {
|
|
344
|
+
const ours = 'line1\r\nline2\nline3\r';
|
|
345
|
+
const theirs = 'line1\nline2\r\nline3';
|
|
346
|
+
|
|
347
|
+
const result = ConflictResolutionUI.parseConflictMarkers(
|
|
348
|
+
`<<<<<<< HEAD\n${ours}\n=======\n${theirs}\n>>>>>>> other`
|
|
349
|
+
);
|
|
350
|
+
expect(result.hasConflicts).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('Performance & Scalability', () => {
|
|
355
|
+
test('renders 10000-line file conflict', () => {
|
|
356
|
+
const huge = Array(5000)
|
|
357
|
+
.fill('line')
|
|
358
|
+
.map((l, i) => `${l} ${i}`)
|
|
359
|
+
.join('\n');
|
|
360
|
+
|
|
361
|
+
const start = Date.now();
|
|
362
|
+
const html = ConflictResolutionUI.renderHTML('huge.ts', huge, huge, 'base');
|
|
363
|
+
const elapsed = Date.now() - start;
|
|
364
|
+
|
|
365
|
+
expect(elapsed).toBeLessThan(1000);
|
|
366
|
+
expect(html).toContain('5000 lines');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('parses 100 conflicts efficiently', () => {
|
|
370
|
+
let content = '';
|
|
371
|
+
for (let i = 0; i < 100; i++) {
|
|
372
|
+
content += `<<<<<<< HEAD\nours${i}\n=======\ntheirs${i}\n>>>>>>> other\n`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const start = Date.now();
|
|
376
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
377
|
+
const elapsed = Date.now() - start;
|
|
378
|
+
|
|
379
|
+
expect(elapsed).toBeLessThan(500);
|
|
380
|
+
expect(result.markers.length).toBeGreaterThanOrEqual(1);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('Real Repository Scenarios', () => {
|
|
385
|
+
test('handles merge conflict from git rebase', () => {
|
|
386
|
+
const conflicted = `CONFLICT (content): Merge conflict in src/main.ts
|
|
387
|
+
<<<<<<< HEAD
|
|
388
|
+
const version = '1.1.0';
|
|
389
|
+
console.log(\`App version: \${version}\`);
|
|
390
|
+
=======
|
|
391
|
+
const version = '1.2.0';
|
|
392
|
+
console.log('Starting app...');
|
|
393
|
+
console.log(\`Version: \${version}\`);
|
|
394
|
+
>>>>>>> feature/update-version`;
|
|
395
|
+
|
|
396
|
+
const result = ConflictResolutionUI.parseConflictMarkers(conflicted);
|
|
397
|
+
expect(result.hasConflicts).toBe(true);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('handles dependency version conflicts', () => {
|
|
401
|
+
const ours = `"react": "^18.0.0",
|
|
402
|
+
"react-dom": "^18.0.0",`;
|
|
403
|
+
|
|
404
|
+
const theirs = `"react": "^18.2.0",
|
|
405
|
+
"react-dom": "^18.2.0",`;
|
|
406
|
+
|
|
407
|
+
const suggestion = ConflictResolutionUI.suggestResolution(ours, theirs, 'base');
|
|
408
|
+
expect(suggestion.confidence).toBeGreaterThan(0);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('handles database migration conflicts', () => {
|
|
412
|
+
const ours = `CREATE TABLE users (
|
|
413
|
+
id INT PRIMARY KEY,
|
|
414
|
+
name VARCHAR(255),
|
|
415
|
+
email VARCHAR(255) UNIQUE
|
|
416
|
+
);`;
|
|
417
|
+
|
|
418
|
+
const theirs = `CREATE TABLE users (
|
|
419
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
420
|
+
username VARCHAR(255) UNIQUE,
|
|
421
|
+
email VARCHAR(255) UNIQUE,
|
|
422
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
423
|
+
);`;
|
|
424
|
+
|
|
425
|
+
const html = ConflictResolutionUI.renderHTML('migration.sql', ours, theirs, 'base');
|
|
426
|
+
expect(html).toContain('CREATE TABLE');
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict Resolution UI Tests
|
|
3
|
+
* HTML/Terminal rendering for conflict resolution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ConflictResolutionUI } from '../../src/conflict-resolution-ui';
|
|
7
|
+
|
|
8
|
+
describe('ConflictResolutionUI', () => {
|
|
9
|
+
describe('HTML Rendering', () => {
|
|
10
|
+
test('renders HTML for conflict', () => {
|
|
11
|
+
const html = ConflictResolutionUI.renderHTML(
|
|
12
|
+
'main.ts',
|
|
13
|
+
'export function myFunc() {}',
|
|
14
|
+
'export async function myFunc() {}',
|
|
15
|
+
'function myFunc() {}'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
19
|
+
expect(html).toContain('Conflict Resolution');
|
|
20
|
+
expect(html).toContain('main.ts');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('includes both versions in HTML', () => {
|
|
24
|
+
const html = ConflictResolutionUI.renderHTML(
|
|
25
|
+
'file.ts',
|
|
26
|
+
'version a',
|
|
27
|
+
'version b',
|
|
28
|
+
'base'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(html).toContain('Your Changes (Ours)');
|
|
32
|
+
expect(html).toContain('Incoming Changes (Theirs)');
|
|
33
|
+
expect(html).toContain('version a');
|
|
34
|
+
expect(html).toContain('version b');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('includes action buttons in HTML', () => {
|
|
38
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'a', 'b', 'c');
|
|
39
|
+
expect(html).toContain('Use Ours');
|
|
40
|
+
expect(html).toContain('Use Theirs');
|
|
41
|
+
expect(html).toContain('Manual Merge');
|
|
42
|
+
expect(html).toContain('Skip');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('includes line numbers in HTML', () => {
|
|
46
|
+
const html = ConflictResolutionUI.renderHTML(
|
|
47
|
+
'file.ts',
|
|
48
|
+
'line1\nline2\nline3',
|
|
49
|
+
'line1\nlineB\nlineC',
|
|
50
|
+
'line1'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(html).toContain('line-num');
|
|
54
|
+
expect(html).toContain('3 lines');
|
|
55
|
+
expect(html).toContain('2 lines');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('escapes HTML special characters', () => {
|
|
59
|
+
const html = ConflictResolutionUI.renderHTML(
|
|
60
|
+
'file.ts',
|
|
61
|
+
'<script>alert("xss")</script>',
|
|
62
|
+
'const x = 5;',
|
|
63
|
+
'base'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(html).toContain('<');
|
|
67
|
+
expect(html).not.toContain('<script>');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Terminal Rendering', () => {
|
|
72
|
+
test('renders terminal conflict', () => {
|
|
73
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
74
|
+
'main.ts',
|
|
75
|
+
['export function myFunc() {}'],
|
|
76
|
+
['export async function myFunc() {}'],
|
|
77
|
+
['function myFunc() {}']
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(output).toContain('CONFLICT RESOLUTION');
|
|
81
|
+
expect(output).toContain('main.ts');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('includes both versions in terminal', () => {
|
|
85
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
86
|
+
'file.ts',
|
|
87
|
+
['version a'],
|
|
88
|
+
['version b'],
|
|
89
|
+
['base']
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(output).toContain('YOURS (Current Branch)');
|
|
93
|
+
expect(output).toContain('THEIRS (Incoming Changes)');
|
|
94
|
+
expect(output).toContain('BASE (Common Ancestor)');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('includes resolution options in terminal', () => {
|
|
98
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
99
|
+
'file.ts',
|
|
100
|
+
['a'],
|
|
101
|
+
['b'],
|
|
102
|
+
['c']
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(output).toContain('RESOLUTION OPTIONS');
|
|
106
|
+
expect(output).toContain('[o]');
|
|
107
|
+
expect(output).toContain('[t]');
|
|
108
|
+
expect(output).toContain('[m]');
|
|
109
|
+
expect(output).toContain('[s]');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('handles large diffs gracefully', () => {
|
|
113
|
+
const largeOurs = Array(50).fill('line').map((l, i) => `${l} ${i}`);
|
|
114
|
+
const largeTheirs = Array(50).fill('other').map((l, i) => `${l} ${i}`);
|
|
115
|
+
const output = ConflictResolutionUI.renderTerminal(
|
|
116
|
+
'large.ts',
|
|
117
|
+
largeOurs,
|
|
118
|
+
largeTheirs,
|
|
119
|
+
['base']
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(output).toContain('... +40 more lines');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Resolution Suggestions', () => {
|
|
127
|
+
test('suggests shorter version', () => {
|
|
128
|
+
const result = ConflictResolutionUI.suggestResolution(
|
|
129
|
+
'short',
|
|
130
|
+
'this is much longer content',
|
|
131
|
+
'base'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.suggestion).toBe('ours');
|
|
135
|
+
expect(result.confidence).toBeGreaterThan(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('suggests incoming if shorter', () => {
|
|
139
|
+
const result = ConflictResolutionUI.suggestResolution(
|
|
140
|
+
'this is much longer content',
|
|
141
|
+
'short',
|
|
142
|
+
'base'
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(result.suggestion).toBe('theirs');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('suggests manual if equal', () => {
|
|
149
|
+
const result = ConflictResolutionUI.suggestResolution(
|
|
150
|
+
'version a',
|
|
151
|
+
'version b',
|
|
152
|
+
'base'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result.suggestion).toBe('manual');
|
|
156
|
+
expect(result.confidence).toBeLessThan(0.7);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('includes reasoning', () => {
|
|
160
|
+
const result = ConflictResolutionUI.suggestResolution(
|
|
161
|
+
'short',
|
|
162
|
+
'longer content',
|
|
163
|
+
'base'
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(result.reason).toBeDefined();
|
|
167
|
+
expect(result.reason.length).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Conflict Marker Parsing', () => {
|
|
172
|
+
test('parses git conflict markers', () => {
|
|
173
|
+
const content = `line 1
|
|
174
|
+
<<<<<<< ours
|
|
175
|
+
our change
|
|
176
|
+
=======
|
|
177
|
+
their change
|
|
178
|
+
>>>>>>> theirs
|
|
179
|
+
line 2`;
|
|
180
|
+
|
|
181
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
182
|
+
expect(result.hasConflicts).toBe(true);
|
|
183
|
+
expect(result.markers).toHaveLength(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('identifies multiple conflicts', () => {
|
|
187
|
+
const content = `<<<<<<< ours
|
|
188
|
+
a1
|
|
189
|
+
=======
|
|
190
|
+
b1
|
|
191
|
+
>>>>>>> theirs
|
|
192
|
+
<<<<<<< ours
|
|
193
|
+
a2
|
|
194
|
+
=======
|
|
195
|
+
b2
|
|
196
|
+
>>>>>>> theirs`;
|
|
197
|
+
|
|
198
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
199
|
+
expect(result.markers.length).toBeGreaterThanOrEqual(1);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('reports no conflicts when none exist', () => {
|
|
203
|
+
const content = `clean file
|
|
204
|
+
with no conflicts
|
|
205
|
+
just code`;
|
|
206
|
+
|
|
207
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
208
|
+
expect(result.hasConflicts).toBe(false);
|
|
209
|
+
expect(result.markers).toHaveLength(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('locates conflict markers accurately', () => {
|
|
213
|
+
const content = `line 0
|
|
214
|
+
line 1
|
|
215
|
+
<<<<<<< ours
|
|
216
|
+
our
|
|
217
|
+
=======
|
|
218
|
+
their
|
|
219
|
+
>>>>>>> theirs
|
|
220
|
+
line 2`;
|
|
221
|
+
|
|
222
|
+
const result = ConflictResolutionUI.parseConflictMarkers(content);
|
|
223
|
+
expect(result.markers[0].startLine).toBe(2);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Real-World Scenarios', () => {
|
|
228
|
+
test('handles code conflict', () => {
|
|
229
|
+
const ours = `function calculateTotal(items) {
|
|
230
|
+
let sum = 0;
|
|
231
|
+
for (let item of items) {
|
|
232
|
+
sum += item.price;
|
|
233
|
+
}
|
|
234
|
+
return sum;
|
|
235
|
+
}`;
|
|
236
|
+
|
|
237
|
+
const theirs = `function calculateTotal(items) {
|
|
238
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
239
|
+
}`;
|
|
240
|
+
|
|
241
|
+
const base = `function calculateTotal(items) {
|
|
242
|
+
let sum = 0;
|
|
243
|
+
items.forEach(item => sum += item.price);
|
|
244
|
+
return sum;
|
|
245
|
+
}`;
|
|
246
|
+
|
|
247
|
+
const html = ConflictResolutionUI.renderHTML('calc.ts', ours, theirs, base);
|
|
248
|
+
expect(html).toContain('calculateTotal');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('handles merge conflict markers', () => {
|
|
252
|
+
const conflicted = `const config = {
|
|
253
|
+
<<<<<<< HEAD
|
|
254
|
+
apiUrl: 'https://api.prod.com',
|
|
255
|
+
timeout: 5000,
|
|
256
|
+
=======
|
|
257
|
+
apiUrl: 'https://api.dev.com',
|
|
258
|
+
timeout: 10000,
|
|
259
|
+
>>>>>>> feature/dev-config
|
|
260
|
+
retries: 3
|
|
261
|
+
};`;
|
|
262
|
+
|
|
263
|
+
const result = ConflictResolutionUI.parseConflictMarkers(conflicted);
|
|
264
|
+
expect(result.hasConflicts).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('UI Accessibility', () => {
|
|
269
|
+
test('includes proper semantic HTML', () => {
|
|
270
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'a', 'b', 'c');
|
|
271
|
+
expect(html).toContain('<button');
|
|
272
|
+
expect(html).toContain('</html>');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('includes styling', () => {
|
|
276
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'a', 'b', 'c');
|
|
277
|
+
expect(html).toContain('<style>');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('includes JavaScript handlers', () => {
|
|
281
|
+
const html = ConflictResolutionUI.renderHTML('file.ts', 'a', 'b', 'c');
|
|
282
|
+
expect(html).toContain('<script>');
|
|
283
|
+
expect(html).toContain('resolve');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|