git-trace 0.1.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/.tracerc.example +38 -0
- package/README.md +136 -0
- package/bun.lock +511 -0
- package/bunchee.config.ts +11 -0
- package/cli/index.ts +251 -0
- package/cli/parser.ts +76 -0
- package/cli/tsconfig.json +6 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +858 -0
- package/dist/config.cjs +66 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +63 -0
- package/dist/highlight/index.cjs +770 -0
- package/dist/highlight/index.d.ts +26 -0
- package/dist/highlight/index.js +766 -0
- package/dist/index.cjs +849 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +845 -0
- package/examples/demo/App.tsx +78 -0
- package/examples/demo/index.html +12 -0
- package/examples/demo/main.tsx +10 -0
- package/examples/demo/mockData.ts +170 -0
- package/examples/demo/styles.css +103 -0
- package/examples/demo/tsconfig.json +21 -0
- package/examples/demo/tsconfig.node.json +10 -0
- package/examples/demo/vite.config.ts +20 -0
- package/package.json +58 -0
- package/src/Trace.tsx +717 -0
- package/src/cache.ts +118 -0
- package/src/config.ts +51 -0
- package/src/entries/config.ts +7 -0
- package/src/entries/gitea.ts +4 -0
- package/src/entries/github.ts +5 -0
- package/src/entries/gitlab.ts +4 -0
- package/src/gitea.ts +58 -0
- package/src/github.ts +100 -0
- package/src/gitlab.ts +65 -0
- package/src/highlight/highlight.ts +119 -0
- package/src/highlight/index.ts +4 -0
- package/src/host.ts +32 -0
- package/src/index.ts +6 -0
- package/src/patterns.ts +6 -0
- package/src/shared.ts +108 -0
- package/src/themes.ts +98 -0
- package/src/types.ts +72 -0
- package/test/e2e.html +424 -0
- package/tsconfig.json +18 -0
- package/vercel.json +4 -0
package/test/e2e.html
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Trace E2E Test</title>
|
|
7
|
+
<script src="https://unpkg.com/react@19/umd/react.development.js" crossorigin></script>
|
|
8
|
+
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js" crossorigin></script>
|
|
9
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
13
|
+
background: #0d1117;
|
|
14
|
+
color: #e2e8f0;
|
|
15
|
+
padding: 20px;
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
h1 { font-size: 24px; margin-bottom: 8px; }
|
|
19
|
+
.subtitle { opacity: 0.6; margin-bottom: 24px; }
|
|
20
|
+
.test-section { margin-bottom: 40px; }
|
|
21
|
+
.test-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; opacity: 0.8; }
|
|
22
|
+
.status {
|
|
23
|
+
display: inline-block;
|
|
24
|
+
padding: 4px 8px;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
font-size: 12px;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
margin-left: 8px;
|
|
29
|
+
}
|
|
30
|
+
.status.pass { background: rgba(52, 211, 153, 0.2); color: #34d399; }
|
|
31
|
+
.status.fail { background: rgba(248, 113, 113, 0.2); color: #f87171; }
|
|
32
|
+
.status.pending { background: rgba(148, 163, 184, 0.2); color: #94a3b8; }
|
|
33
|
+
.controls {
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 8px;
|
|
36
|
+
margin-bottom: 16px;
|
|
37
|
+
flex-wrap: wrap;
|
|
38
|
+
}
|
|
39
|
+
button {
|
|
40
|
+
padding: 8px 16px;
|
|
41
|
+
border: 1px solid #30363d;
|
|
42
|
+
border-radius: 6px;
|
|
43
|
+
background: #21262d;
|
|
44
|
+
color: #e2e8f0;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
font-size: 13px;
|
|
47
|
+
}
|
|
48
|
+
button:hover { background: #30363d; }
|
|
49
|
+
.log {
|
|
50
|
+
background: #0d1117;
|
|
51
|
+
border: 1px solid #30363d;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
padding: 12px;
|
|
54
|
+
font-family: 'JetBrains Mono', monospace;
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
max-height: 200px;
|
|
57
|
+
overflow-y: auto;
|
|
58
|
+
}
|
|
59
|
+
.log-entry { margin-bottom: 4px; }
|
|
60
|
+
.log-entry.pass { color: #34d399; }
|
|
61
|
+
.log-entry.fail { color: #f87171; }
|
|
62
|
+
.log-entry.info { color: #94a3b8; }
|
|
63
|
+
</style>
|
|
64
|
+
</head>
|
|
65
|
+
<body>
|
|
66
|
+
<h1>Trace E2E Test <span id="overall-status" class="status pending">Running...</span></h1>
|
|
67
|
+
<p class="subtitle">End-to-end test for trace v0.1</p>
|
|
68
|
+
|
|
69
|
+
<div class="test-section">
|
|
70
|
+
<div class="test-title">Test Controls</div>
|
|
71
|
+
<div class="controls">
|
|
72
|
+
<button onclick="runAllTests()">Run All Tests</button>
|
|
73
|
+
<button onclick="testRender()">Test: Render</button>
|
|
74
|
+
<button onclick="testNavigation()">Test: Navigation</button>
|
|
75
|
+
<button onclick="testAutoPlay()">Test: AutoPlay</button>
|
|
76
|
+
<button onclick="testOnCommit()">Test: onCommit Callback</button>
|
|
77
|
+
<button onclick="testEmptyState()">Test: Empty State</button>
|
|
78
|
+
<button onclick="clearLog()">Clear Log</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div class="test-section">
|
|
83
|
+
<div class="test-title">Test Log</div>
|
|
84
|
+
<div class="log" id="log"></div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="test-section">
|
|
88
|
+
<div class="test-title">Component Preview</div>
|
|
89
|
+
<div id="root"></div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<script type="text/babel" data-presets="react">
|
|
93
|
+
// Mock Trace component (simplified for testing)
|
|
94
|
+
function Trace({ commits = [], autoPlay = false, interval = 2000, onCommit, className = '' }) {
|
|
95
|
+
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
96
|
+
const [isPlaying, setIsPlaying] = React.useState(autoPlay);
|
|
97
|
+
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
if (isPlaying && activeIndex < commits.length - 1) {
|
|
100
|
+
const timer = setInterval(() => {
|
|
101
|
+
setActiveIndex(i => {
|
|
102
|
+
const next = i + 1;
|
|
103
|
+
if (next >= commits.length - 1) {
|
|
104
|
+
setIsPlaying(false);
|
|
105
|
+
return commits.length - 1;
|
|
106
|
+
}
|
|
107
|
+
return next;
|
|
108
|
+
});
|
|
109
|
+
}, interval);
|
|
110
|
+
return () => clearInterval(timer);
|
|
111
|
+
}
|
|
112
|
+
}, [isPlaying, activeIndex, commits.length, interval]);
|
|
113
|
+
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (commits[activeIndex]) {
|
|
116
|
+
onCommit?.(commits[activeIndex]);
|
|
117
|
+
}
|
|
118
|
+
}, [activeIndex, commits, onCommit]);
|
|
119
|
+
|
|
120
|
+
if (commits.length === 0) {
|
|
121
|
+
return React.createElement('div', { className: `trace-root ${className}` },
|
|
122
|
+
React.createElement('div', { className: 'trace-empty' }, 'No commits to display')
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const activeCommit = commits[activeIndex];
|
|
127
|
+
const getColor = (type) => type === 'ai' ? '#a78bfa' : '#34d399';
|
|
128
|
+
|
|
129
|
+
return React.createElement('div', { className: `trace-root ${className}`, style: {
|
|
130
|
+
fontFamily: 'monospace',
|
|
131
|
+
background: '#07090f',
|
|
132
|
+
color: '#e2e8f0',
|
|
133
|
+
borderRadius: '8px',
|
|
134
|
+
overflow: 'hidden',
|
|
135
|
+
border: '1px solid #1e293b'
|
|
136
|
+
}},
|
|
137
|
+
React.createElement('div', { style: { display: 'flex', gap: '8px', padding: '12px', borderBottom: '1px solid #1e293b' } },
|
|
138
|
+
React.createElement('button', {
|
|
139
|
+
onClick: () => setActiveIndex(i => Math.max(0, i - 1)),
|
|
140
|
+
disabled: activeIndex === 0,
|
|
141
|
+
style: { padding: '6px 12px', borderRadius: '4px', border: '1px solid #1e293b', background: 'transparent', color: '#e2e8f0', cursor: 'pointer' }
|
|
142
|
+
}, '←'),
|
|
143
|
+
React.createElement('button', {
|
|
144
|
+
onClick: () => setIsPlaying(v => !v),
|
|
145
|
+
style: { padding: '6px 12px', borderRadius: '4px', border: '1px solid #1e293b', background: 'transparent', color: '#e2e8f0', cursor: 'pointer' }
|
|
146
|
+
}, isPlaying ? '⏸' : '▶'),
|
|
147
|
+
React.createElement('button', {
|
|
148
|
+
onClick: () => setActiveIndex(i => Math.min(commits.length - 1, i + 1)),
|
|
149
|
+
disabled: activeIndex >= commits.length - 1,
|
|
150
|
+
style: { padding: '6px 12px', borderRadius: '4px', border: '1px solid #1e293b', background: 'transparent', color: '#e2e8f0', cursor: 'pointer' }
|
|
151
|
+
}, '→'),
|
|
152
|
+
React.createElement('span', { style: { fontSize: '11px', opacity: 0.6, marginLeft: 'auto' } },
|
|
153
|
+
`${activeIndex + 1} / ${commits.length}`
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
React.createElement('div', { style: { display: 'flex', height: '400px' } },
|
|
157
|
+
React.createElement('div', { style: { width: '200px', borderRight: '1px solid #1e293b', padding: '16px 0', overflowY: 'auto' } },
|
|
158
|
+
commits.map((commit, index) =>
|
|
159
|
+
React.createElement('div', {
|
|
160
|
+
key: commit.hash,
|
|
161
|
+
onClick: () => setActiveIndex(index),
|
|
162
|
+
style: {
|
|
163
|
+
padding: '8px 16px',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
borderLeft: `2px solid ${index === activeIndex ? getColor(commit.authorType) : 'transparent'}`,
|
|
166
|
+
background: index === activeIndex ? 'rgba(255,255,255,0.05)' : 'transparent'
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', marginBottom: '4px' } },
|
|
170
|
+
React.createElement('span', { style: { width: '8px', height: '8px', borderRadius: '50%', background: getColor(commit.authorType), marginRight: '8px' } }),
|
|
171
|
+
React.createElement('span', { style: { fontSize: '12px' } }, commit.message),
|
|
172
|
+
React.createElement('span', { style: { fontSize: '9px', padding: '2px 6px', borderRadius: '4px', marginLeft: '6px', background: commit.authorType === 'ai' ? 'rgba(167,139,250,0.15)' : 'rgba(52,211,153,0.15)', color: getColor(commit.authorType) } }, commit.authorType)
|
|
173
|
+
),
|
|
174
|
+
React.createElement('div', { style: { fontSize: '11px', opacity: 0.6 } }, `${commit.author} · ${commit.time}`)
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
),
|
|
178
|
+
React.createElement('div', { style: { flex: 1, padding: '16px', overflowY: 'auto', fontSize: '13px', lineHeight: 1.6 } },
|
|
179
|
+
React.createElement('div', { style: { marginBottom: '16px', paddingBottom: '16px', borderBottom: '1px solid #1e293b' } },
|
|
180
|
+
React.createElement('h3', { style: { fontSize: '16px', margin: '0 0 4px' } }, activeCommit.message),
|
|
181
|
+
React.createElement('div', { style: { fontSize: '12px', opacity: 0.6 } }, `${activeCommit.hash} · ${activeCommit.author}`)
|
|
182
|
+
),
|
|
183
|
+
activeCommit.lines.map((line, index) =>
|
|
184
|
+
React.createElement('div', {
|
|
185
|
+
key: index,
|
|
186
|
+
style: {
|
|
187
|
+
padding: '2px 8px',
|
|
188
|
+
animation: `fadeIn 0.22s ease ${index * 0.04}s both`,
|
|
189
|
+
background: line.type === 'add' ? 'rgba(52,211,153,0.07)' : line.type === 'remove' ? 'rgba(248,113,113,0.07)' : 'transparent',
|
|
190
|
+
color: line.type === 'add' ? '#34d399' : line.type === 'remove' ? '#f87171' : 'inherit',
|
|
191
|
+
opacity: line.type === 'ctx' ? 0.5 : 1
|
|
192
|
+
}
|
|
193
|
+
}, line.content || ' ')
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Test data
|
|
201
|
+
const mockCommits = [
|
|
202
|
+
{
|
|
203
|
+
hash: 'a1b2c3d',
|
|
204
|
+
message: 'feat: add bundle optimization',
|
|
205
|
+
author: 'doanbactam',
|
|
206
|
+
authorType: 'human',
|
|
207
|
+
time: '2h ago',
|
|
208
|
+
lines: [
|
|
209
|
+
{ type: 'ctx', content: 'function optimize(data) {' },
|
|
210
|
+
{ type: 'add', content: ' const minified = minify(data)' },
|
|
211
|
+
{ type: 'add', content: ' return compress(minified)' },
|
|
212
|
+
{ type: 'ctx', content: '}' }
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
hash: 'e4f5g6h',
|
|
217
|
+
message: 'fix: resolve path issue',
|
|
218
|
+
author: 'claude',
|
|
219
|
+
authorType: 'ai',
|
|
220
|
+
time: '4h ago',
|
|
221
|
+
lines: [
|
|
222
|
+
{ type: 'remove', content: 'import path from "path"' },
|
|
223
|
+
{ type: 'add', content: 'import { resolve } from "path"' },
|
|
224
|
+
{ type: 'ctx', content: '' },
|
|
225
|
+
{ type: 'add', content: 'const fullPath = resolve(cwd, filePath)' }
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
hash: 'i7j8k9l',
|
|
230
|
+
message: 'refactor: clean up types',
|
|
231
|
+
author: 'doanbactam',
|
|
232
|
+
authorType: 'human',
|
|
233
|
+
time: '1d ago',
|
|
234
|
+
lines: [
|
|
235
|
+
{ type: 'remove', content: 'type AnyType = unknown' },
|
|
236
|
+
{ type: 'add', content: 'type Config = {' },
|
|
237
|
+
{ type: 'add', content: ' entry: string' },
|
|
238
|
+
{ type: 'add', content: ' format: Array<"esm" | "cjs">' },
|
|
239
|
+
{ type: 'add', content: '}' }
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
// Global test state
|
|
245
|
+
window.testResults = { pass: 0, fail: 0 };
|
|
246
|
+
window.testComponent = null;
|
|
247
|
+
|
|
248
|
+
// Helper functions
|
|
249
|
+
function log(message, type = 'info') {
|
|
250
|
+
const logDiv = document.getElementById('log');
|
|
251
|
+
const entry = document.createElement('div');
|
|
252
|
+
entry.className = `log-entry ${type}`;
|
|
253
|
+
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
254
|
+
logDiv.appendChild(entry);
|
|
255
|
+
logDiv.scrollTop = logDiv.scrollHeight;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function clearLog() {
|
|
259
|
+
document.getElementById('log').innerHTML = '';
|
|
260
|
+
window.testResults = { pass: 0, fail: 0 };
|
|
261
|
+
updateStatus();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function updateStatus() {
|
|
265
|
+
const statusEl = document.getElementById('overall-status');
|
|
266
|
+
if (window.testResults.pass > 0 && window.testResults.fail === 0) {
|
|
267
|
+
statusEl.className = 'status pass';
|
|
268
|
+
statusEl.textContent = 'PASS';
|
|
269
|
+
} else if (window.testResults.fail > 0) {
|
|
270
|
+
statusEl.className = 'status fail';
|
|
271
|
+
statusEl.textContent = 'FAIL';
|
|
272
|
+
} else {
|
|
273
|
+
statusEl.className = 'status pending';
|
|
274
|
+
statusEl.textContent = 'Pending';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function assert(condition, testName) {
|
|
279
|
+
if (condition) {
|
|
280
|
+
log(`✓ ${testName}`, 'pass');
|
|
281
|
+
window.testResults.pass++;
|
|
282
|
+
} else {
|
|
283
|
+
log(`✗ ${testName}`, 'fail');
|
|
284
|
+
window.testResults.fail++;
|
|
285
|
+
}
|
|
286
|
+
updateStatus();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Test functions
|
|
290
|
+
window.testRender = function() {
|
|
291
|
+
log('--- Test: Render ---', 'info');
|
|
292
|
+
const root = document.getElementById('root');
|
|
293
|
+
root.innerHTML = '';
|
|
294
|
+
|
|
295
|
+
const element = React.createElement(Trace, { commits: mockCommits });
|
|
296
|
+
window.testComponent = ReactDOM.createRoot(root).render(element);
|
|
297
|
+
|
|
298
|
+
setTimeout(() => {
|
|
299
|
+
const traceRoot = document.querySelector('.trace-root');
|
|
300
|
+
assert(traceRoot !== null, 'Component renders');
|
|
301
|
+
assert(document.querySelectorAll('.trace-commit').length === 3, 'Renders 3 commits');
|
|
302
|
+
assert(document.querySelector('.trace-badge.ai') !== null, 'AI badge rendered');
|
|
303
|
+
assert(document.querySelector('.trace-badge.human') !== null, 'Human badge rendered');
|
|
304
|
+
}, 100);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
window.testNavigation = function() {
|
|
308
|
+
log('--- Test: Navigation ---', 'info');
|
|
309
|
+
const root = document.getElementById('root');
|
|
310
|
+
root.innerHTML = '';
|
|
311
|
+
|
|
312
|
+
const element = React.createElement(Trace, { commits: mockCommits });
|
|
313
|
+
window.testComponent = ReactDOM.createRoot(root).render(element);
|
|
314
|
+
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
const buttons = document.querySelectorAll('.trace-btn');
|
|
317
|
+
const prevBtn = buttons[0];
|
|
318
|
+
const nextBtn = buttons[2];
|
|
319
|
+
|
|
320
|
+
assert(prevBtn.disabled === true, 'Prev button disabled at start');
|
|
321
|
+
assert(nextBtn.disabled === false, 'Next button enabled at start');
|
|
322
|
+
|
|
323
|
+
nextBtn.click();
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
assert(document.querySelector('.trace-commit.active').textContent.includes('fix: resolve'), 'Navigation to next commit works');
|
|
326
|
+
assert(prevBtn.disabled === false, 'Prev button enabled after navigation');
|
|
327
|
+
|
|
328
|
+
prevBtn.click();
|
|
329
|
+
setTimeout(() => {
|
|
330
|
+
assert(document.querySelector('.trace-commit.active').textContent.includes('feat: add'), 'Navigation to previous commit works');
|
|
331
|
+
}, 50);
|
|
332
|
+
}, 50);
|
|
333
|
+
}, 100);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
window.testAutoPlay = function() {
|
|
337
|
+
log('--- Test: AutoPlay ---', 'info');
|
|
338
|
+
const root = document.getElementById('root');
|
|
339
|
+
root.innerHTML = '';
|
|
340
|
+
|
|
341
|
+
const element = React.createElement(Trace, {
|
|
342
|
+
commits: mockCommits,
|
|
343
|
+
autoPlay: true,
|
|
344
|
+
interval: 500
|
|
345
|
+
});
|
|
346
|
+
window.testComponent = ReactDOM.createRoot(root).render(element);
|
|
347
|
+
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
const initialIndex = document.querySelector('.trace-info').textContent;
|
|
350
|
+
assert(initialIndex === '1 / 3', 'Starts at first commit');
|
|
351
|
+
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
const laterIndex = document.querySelector('.trace-info').textContent;
|
|
354
|
+
assert(laterIndex === '2 / 3', 'AutoPlay advances to next commit');
|
|
355
|
+
}, 600);
|
|
356
|
+
}, 100);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
window.testOnCommit = function() {
|
|
360
|
+
log('--- Test: onCommit Callback ---', 'info');
|
|
361
|
+
const root = document.getElementById('root');
|
|
362
|
+
root.innerHTML = '';
|
|
363
|
+
|
|
364
|
+
let callbackInvoked = false;
|
|
365
|
+
let lastCommit = null;
|
|
366
|
+
|
|
367
|
+
const element = React.createElement(Trace, {
|
|
368
|
+
commits: mockCommits,
|
|
369
|
+
onCommit: (commit) => {
|
|
370
|
+
callbackInvoked = true;
|
|
371
|
+
lastCommit = commit;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
window.testComponent = ReactDOM.createRoot(root).render(element);
|
|
375
|
+
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
assert(callbackInvoked === true, 'onCommit callback invoked on mount');
|
|
378
|
+
assert(lastCommit !== null && lastCommit.hash === 'a1b2c3d', 'onCommit receives correct commit');
|
|
379
|
+
|
|
380
|
+
const nextBtn = document.querySelectorAll('.trace-btn')[2];
|
|
381
|
+
nextBtn.click();
|
|
382
|
+
setTimeout(() => {
|
|
383
|
+
assert(lastCommit !== null && lastCommit.hash === 'e4f5g6h', 'onCommit called on navigation');
|
|
384
|
+
}, 50);
|
|
385
|
+
}, 100);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
window.testEmptyState = function() {
|
|
389
|
+
log('--- Test: Empty State ---', 'info');
|
|
390
|
+
const root = document.getElementById('root');
|
|
391
|
+
root.innerHTML = '';
|
|
392
|
+
|
|
393
|
+
const element = React.createElement(Trace, { commits: [] });
|
|
394
|
+
window.testComponent = ReactDOM.createRoot(root).render(element);
|
|
395
|
+
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
const traceRoot = document.querySelector('.trace-root');
|
|
398
|
+
assert(traceRoot !== null, 'Component renders with empty commits');
|
|
399
|
+
assert(traceRoot.textContent.includes('No commits to display'), 'Shows empty state message');
|
|
400
|
+
}, 100);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
window.runAllTests = function() {
|
|
404
|
+
clearLog();
|
|
405
|
+
log('=== Running All Tests ===', 'info');
|
|
406
|
+
|
|
407
|
+
setTimeout(() => testRender(), 100);
|
|
408
|
+
setTimeout(() => testNavigation(), 1000);
|
|
409
|
+
setTimeout(() => testAutoPlay(), 2000);
|
|
410
|
+
setTimeout(() => testOnCommit(), 3000);
|
|
411
|
+
setTimeout(() => testEmptyState(), 4000);
|
|
412
|
+
|
|
413
|
+
setTimeout(() => {
|
|
414
|
+
log(`=== Tests Complete: ${window.testResults.pass} passed, ${window.testResults.fail} failed ===`,
|
|
415
|
+
window.testResults.fail === 0 ? 'pass' : 'fail');
|
|
416
|
+
}, 5000);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Render initial component
|
|
420
|
+
const root = document.getElementById('root');
|
|
421
|
+
ReactDOM.createRoot(root).render(React.createElement(Trace, { commits: mockCommits }));
|
|
422
|
+
</script>
|
|
423
|
+
</body>
|
|
424
|
+
</html>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"rootDir": "src",
|
|
14
|
+
"verbatimModuleSyntax": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"],
|
|
17
|
+
"exclude": ["node_modules", "dist", "site"]
|
|
18
|
+
}
|
package/vercel.json
ADDED