@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.
Files changed (130) hide show
  1. package/.gitignore +115 -0
  2. package/.trace/progress.json +22 -0
  3. package/README.md +466 -0
  4. package/RELEASE-NOTES-1.5.0.md +410 -0
  5. package/STATUS.md +245 -0
  6. package/dist/auto-commit.d.ts +66 -0
  7. package/dist/auto-commit.d.ts.map +1 -0
  8. package/dist/auto-commit.js +180 -0
  9. package/dist/auto-commit.js.map +1 -0
  10. package/dist/cli.d.ts +7 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +246 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands.d.ts +46 -0
  15. package/dist/commands.d.ts.map +1 -0
  16. package/dist/commands.js +256 -0
  17. package/dist/commands.js.map +1 -0
  18. package/dist/diff.d.ts +23 -0
  19. package/dist/diff.d.ts.map +1 -0
  20. package/dist/diff.js +106 -0
  21. package/dist/diff.js.map +1 -0
  22. package/dist/github.d.ts.map +1 -0
  23. package/dist/github.js.map +1 -0
  24. package/dist/index-cache.d.ts +35 -0
  25. package/dist/index-cache.d.ts.map +1 -0
  26. package/dist/index-cache.js +114 -0
  27. package/dist/index-cache.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +25 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/storage.d.ts +45 -0
  33. package/dist/storage.d.ts.map +1 -0
  34. package/dist/storage.js +151 -0
  35. package/dist/storage.js.map +1 -0
  36. package/dist/sync.d.ts +60 -0
  37. package/dist/sync.js +184 -0
  38. package/dist/tags.d.ts +85 -0
  39. package/dist/tags.d.ts.map +1 -0
  40. package/dist/tags.js +219 -0
  41. package/dist/tags.js.map +1 -0
  42. package/dist/types.d.ts +102 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +6 -0
  45. package/dist/types.js.map +1 -0
  46. package/docs/.nojekyll +0 -0
  47. package/docs/README.md +73 -0
  48. package/docs/_config.yml +2 -0
  49. package/docs/index.html +960 -0
  50. package/docs-website/package.json +20 -0
  51. package/jest.config.js +21 -0
  52. package/package.json +50 -0
  53. package/scripts/init.ts +290 -0
  54. package/src/agent-audit.ts +270 -0
  55. package/src/agent-checkout.ts +227 -0
  56. package/src/agent-coordination.ts +318 -0
  57. package/src/async-queue.ts +203 -0
  58. package/src/auto-branching.ts +279 -0
  59. package/src/auto-commit.ts +166 -0
  60. package/src/cherry-pick.ts +252 -0
  61. package/src/chunked-upload.ts +224 -0
  62. package/src/cli-v2.ts +335 -0
  63. package/src/cli.ts +318 -0
  64. package/src/cliff-detection.ts +232 -0
  65. package/src/commands.ts +267 -0
  66. package/src/commit-hash-system.ts +351 -0
  67. package/src/compression.ts +176 -0
  68. package/src/conflict-resolution-ui.ts +277 -0
  69. package/src/conflict-visualization.ts +238 -0
  70. package/src/diff-formatter.ts +184 -0
  71. package/src/diff.ts +124 -0
  72. package/src/distributed-coordination.ts +273 -0
  73. package/src/git-interop.ts +316 -0
  74. package/src/index-cache.ts +88 -0
  75. package/src/index.ts +38 -0
  76. package/src/merge-engine.ts +143 -0
  77. package/src/message-search.ts +370 -0
  78. package/src/performance-monitoring.ts +236 -0
  79. package/src/rebase.ts +327 -0
  80. package/src/rollback.ts +215 -0
  81. package/src/semantic-grouping.ts +245 -0
  82. package/src/stage-area.ts +324 -0
  83. package/src/stash.ts +278 -0
  84. package/src/storage.ts +131 -0
  85. package/src/sync.ts +205 -0
  86. package/src/tags.ts +244 -0
  87. package/src/types.ts +119 -0
  88. package/src/webhooks.ts +119 -0
  89. package/src/workspace-isolation.ts +298 -0
  90. package/tests/auto-commit.test.ts +308 -0
  91. package/tests/checkout.test.ts +136 -0
  92. package/tests/commit.test.ts +118 -0
  93. package/tests/diff.test.ts +191 -0
  94. package/tests/github.test.ts +94 -0
  95. package/tests/integration.test.ts +267 -0
  96. package/tests/log.test.ts +125 -0
  97. package/tests/phase2-integration.test.ts +370 -0
  98. package/tests/storage.test.ts +167 -0
  99. package/tests/tags.test.ts +477 -0
  100. package/tests/types.test.ts +75 -0
  101. package/tests/v1.1/agent-audit.test.ts +472 -0
  102. package/tests/v1.1/agent-coordination.test.ts +308 -0
  103. package/tests/v1.1/async-queue.test.ts +253 -0
  104. package/tests/v1.1/comprehensive.test.ts +521 -0
  105. package/tests/v1.1/diff-formatter.test.ts +238 -0
  106. package/tests/v1.1/integration.test.ts +389 -0
  107. package/tests/v1.1/onboarding.test.ts +365 -0
  108. package/tests/v1.1/rollback.test.ts +370 -0
  109. package/tests/v1.1/semantic-grouping.test.ts +230 -0
  110. package/tests/v1.2/chunked-upload.test.ts +301 -0
  111. package/tests/v1.2/cliff-detection.test.ts +272 -0
  112. package/tests/v1.2/commit-hash-system.test.ts +288 -0
  113. package/tests/v1.2/compression.test.ts +220 -0
  114. package/tests/v1.2/conflict-visualization.test.ts +263 -0
  115. package/tests/v1.2/distributed.test.ts +261 -0
  116. package/tests/v1.2/performance-monitoring.test.ts +328 -0
  117. package/tests/v1.3/auto-branching.test.ts +270 -0
  118. package/tests/v1.3/message-search.test.ts +264 -0
  119. package/tests/v1.3/stage-area.test.ts +330 -0
  120. package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
  121. package/tests/v1.4/cli.test.ts +171 -0
  122. package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
  123. package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
  124. package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
  125. package/tests/v1.4/workspace-isolation.test.ts +268 -0
  126. package/tests/v1.5/agent-coordination.real.test.ts +401 -0
  127. package/tests/v1.5/cli-v2.test.ts +354 -0
  128. package/tests/v1.5/git-interop.real.test.ts +358 -0
  129. package/tests/v1.5/integration-testing.real.test.ts +440 -0
  130. 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('&lt;');
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('&lt;');
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
+ });