@abreen/tada 1.5.1 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/globals.ts +1 -1
- package/package.json +3 -2
- package/src/code/index.ts +5 -4
- package/src/index.ts +1 -1
- package/src/navigate/index.ts +27 -21
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/style.scss +2 -2
- package/src/search/index.ts +19 -16
- package/src/toc/index.ts +1 -1
- package/src/top/index.ts +3 -3
- package/src/util.ts +7 -6
- package/templates/_nav.html +1 -0
- package/bin/validators.test.ts +0 -205
- package/build/build-manifest.test.ts +0 -369
- package/build/code.test.ts +0 -272
- package/build/colors.test.ts +0 -66
- package/build/globals.test.ts +0 -100
- package/build/json-schema.test.ts +0 -57
- package/build/log.test.ts +0 -117
- package/build/markdown-plugins.test.ts +0 -456
- package/build/pagefind.test.ts +0 -192
- package/build/pdf-text.test.ts +0 -52
- package/build/reachability.test.ts +0 -80
- package/build/serve.test.ts +0 -59
- package/build/toc-plugin.test.ts +0 -120
- package/build/utils/content-files.test.ts +0 -56
- package/build/utils/derive-theme.test.ts +0 -111
- package/build/utils/file-types.test.ts +0 -79
- package/build/utils/front-matter.test.ts +0 -77
- package/build/utils/include.test.ts +0 -117
- package/build/utils/paths.test.ts +0 -91
- package/build/utils/trace-layout.test.ts +0 -570
- package/build/utils/trace-svg.test.ts +0 -425
- package/build/utils/trace.test.ts +0 -169
- package/build/validate-config-links.test.ts +0 -239
- package/build/validate-internal-links-plugin.test.ts +0 -134
- package/build/watch-reachability-state.test.ts +0 -198
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable-Italic.ttf +0 -0
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable.ttf +0 -0
- package/fonts/inter/ttf/InterVariable-Italic.ttf +0 -0
- package/src/anchor/index.test.ts +0 -64
- package/src/code/index.test.ts +0 -71
- package/src/header/index.test.ts +0 -89
- package/src/navigate/eligible.test.ts +0 -101
- package/src/navigate/index.test.ts +0 -82
- package/src/navigate/lifecycle.test.ts +0 -26
- package/src/print/index.test.ts +0 -71
- package/src/question/index.test.ts +0 -82
- package/src/search/index.test.ts +0 -203
- package/src/timezone/index.test.ts +0 -400
- package/src/toc/index.test.ts +0 -237
- package/src/top/index.test.ts +0 -46
- package/src/trace/index.test.ts +0 -554
- package/src/util.test.ts +0 -135
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
generateStepSvg,
|
|
4
|
-
formatValue,
|
|
5
|
-
escapeXml,
|
|
6
|
-
filterStep,
|
|
7
|
-
} from './trace-svg';
|
|
8
|
-
import { computeLayout } from './trace-layout';
|
|
9
|
-
import type { TraceStep, TraceHeapObject, TraceStackFrame } from '../types';
|
|
10
|
-
|
|
11
|
-
function makeStep(
|
|
12
|
-
line: number,
|
|
13
|
-
stack: TraceStackFrame[],
|
|
14
|
-
heap: Record<string, TraceHeapObject>,
|
|
15
|
-
): TraceStep {
|
|
16
|
-
return { line, file: 'Test.java', stack, heap, stdout: '' };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('generateStepSvg', () => {
|
|
20
|
-
test('generates valid SVG with stack frames for a simple int variable', () => {
|
|
21
|
-
const step = makeStep(
|
|
22
|
-
5,
|
|
23
|
-
[
|
|
24
|
-
{
|
|
25
|
-
method: 'main',
|
|
26
|
-
class: 'Test',
|
|
27
|
-
line: 5,
|
|
28
|
-
locals: { x: { type: 'int', value: 42 } },
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
{},
|
|
32
|
-
);
|
|
33
|
-
const layout = computeLayout([step]);
|
|
34
|
-
const svg = generateStepSvg(step, layout);
|
|
35
|
-
|
|
36
|
-
expect(svg).toStartWith('<svg class="trace-memory"');
|
|
37
|
-
expect(svg).toEndWith('</svg>');
|
|
38
|
-
expect(svg).toContain('viewBox="0 0');
|
|
39
|
-
expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
|
|
40
|
-
// Should contain frame title with method:line
|
|
41
|
-
expect(svg).toContain('main:5');
|
|
42
|
-
expect(svg).toContain('class="trace-frame-title"');
|
|
43
|
-
// Should contain variable name
|
|
44
|
-
expect(svg).toContain('>x</text>');
|
|
45
|
-
// Should contain value
|
|
46
|
-
expect(svg).toContain('>42</text>');
|
|
47
|
-
// Should contain a var box rect
|
|
48
|
-
expect(svg).toContain('class="trace-var-box"');
|
|
49
|
-
// Should have the arrowhead defs
|
|
50
|
-
expect(svg).toContain('<defs>');
|
|
51
|
-
expect(svg).toContain('id="trace-arrowhead"');
|
|
52
|
-
// Should have stack separator line
|
|
53
|
-
expect(svg).toContain('class="trace-separator"');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('generates heap objects with arrows for ref variable', () => {
|
|
57
|
-
const step = makeStep(
|
|
58
|
-
10,
|
|
59
|
-
[
|
|
60
|
-
{
|
|
61
|
-
method: 'main',
|
|
62
|
-
class: 'Test',
|
|
63
|
-
line: 10,
|
|
64
|
-
locals: { node: { type: 'ref', id: '1' } },
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
{
|
|
68
|
-
'1': {
|
|
69
|
-
type: 'Node',
|
|
70
|
-
fields: { val: { type: 'int', value: 7 }, next: { type: 'null' } },
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
const layout = computeLayout([step]);
|
|
75
|
-
const svg = generateStepSvg(step, layout);
|
|
76
|
-
|
|
77
|
-
// Should have a heap object
|
|
78
|
-
expect(svg).toContain('class="trace-obj"');
|
|
79
|
-
expect(svg).toContain('data-id="1"');
|
|
80
|
-
// Should have object border
|
|
81
|
-
expect(svg).toContain('class="trace-obj-border"');
|
|
82
|
-
// Should have field names
|
|
83
|
-
expect(svg).toContain('>val</text>');
|
|
84
|
-
expect(svg).toContain('>next</text>');
|
|
85
|
-
// Should have field values
|
|
86
|
-
expect(svg).toContain('>7</text>');
|
|
87
|
-
expect(svg).toContain('>null</text>');
|
|
88
|
-
// Should have at least one arrow (stack -> heap)
|
|
89
|
-
expect(svg).toContain('class="trace-arrow"');
|
|
90
|
-
expect(svg).toContain('marker-end="url(#trace-arrowhead)"');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('generates BST with all three nodes and arrows', () => {
|
|
94
|
-
const step = makeStep(
|
|
95
|
-
15,
|
|
96
|
-
[
|
|
97
|
-
{
|
|
98
|
-
method: 'main',
|
|
99
|
-
class: 'Test',
|
|
100
|
-
line: 15,
|
|
101
|
-
locals: { root: { type: 'ref', id: '1' } },
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
{
|
|
105
|
-
'1': {
|
|
106
|
-
type: 'Node',
|
|
107
|
-
fields: {
|
|
108
|
-
val: { type: 'int', value: 10 },
|
|
109
|
-
left: { type: 'ref', id: '2' },
|
|
110
|
-
right: { type: 'ref', id: '3' },
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
'2': {
|
|
114
|
-
type: 'Node',
|
|
115
|
-
fields: {
|
|
116
|
-
val: { type: 'int', value: 5 },
|
|
117
|
-
left: { type: 'null' },
|
|
118
|
-
right: { type: 'null' },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
'3': {
|
|
122
|
-
type: 'Node',
|
|
123
|
-
fields: {
|
|
124
|
-
val: { type: 'int', value: 15 },
|
|
125
|
-
left: { type: 'null' },
|
|
126
|
-
right: { type: 'null' },
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
);
|
|
131
|
-
const layout = computeLayout([step]);
|
|
132
|
-
const svg = generateStepSvg(step, layout);
|
|
133
|
-
|
|
134
|
-
// All three nodes should be present
|
|
135
|
-
expect(svg).toContain('data-id="1"');
|
|
136
|
-
expect(svg).toContain('data-id="2"');
|
|
137
|
-
expect(svg).toContain('data-id="3"');
|
|
138
|
-
// At least 3 arrows: root->1 (stack, fg), 1->2 and 1->3 (heap)
|
|
139
|
-
const allArrows = (svg.match(/class="trace-arrow(?:-heap|-dim)?"/g) ?? [])
|
|
140
|
-
.length;
|
|
141
|
-
expect(allArrows).toBeGreaterThanOrEqual(3);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('BST node renders child-slot row with left/right labels', () => {
|
|
145
|
-
const step = makeStep(
|
|
146
|
-
15,
|
|
147
|
-
[
|
|
148
|
-
{
|
|
149
|
-
method: 'main',
|
|
150
|
-
class: 'Test',
|
|
151
|
-
line: 15,
|
|
152
|
-
locals: { root: { type: 'ref', id: '1' } },
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
{
|
|
156
|
-
'1': {
|
|
157
|
-
type: 'Node',
|
|
158
|
-
fields: {
|
|
159
|
-
val: { type: 'int', value: 10 },
|
|
160
|
-
left: { type: 'ref', id: '2' },
|
|
161
|
-
right: { type: 'ref', id: '3' },
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
'2': {
|
|
165
|
-
type: 'Node',
|
|
166
|
-
fields: {
|
|
167
|
-
val: { type: 'int', value: 5 },
|
|
168
|
-
left: { type: 'null' },
|
|
169
|
-
right: { type: 'null' },
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
'3': {
|
|
173
|
-
type: 'Node',
|
|
174
|
-
fields: {
|
|
175
|
-
val: { type: 'int', value: 15 },
|
|
176
|
-
left: { type: 'null' },
|
|
177
|
-
right: { type: 'null' },
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
const layout = computeLayout([step]);
|
|
183
|
-
const svg = generateStepSvg(step, layout);
|
|
184
|
-
|
|
185
|
-
// The root node should have child-slot labels (left, right) as field names
|
|
186
|
-
// "val" should be rendered as a data field row
|
|
187
|
-
expect(svg).toContain('>val</text>');
|
|
188
|
-
expect(svg).toContain('>left</text>');
|
|
189
|
-
expect(svg).toContain('>right</text>');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('handles empty step gracefully', () => {
|
|
193
|
-
const step = makeStep(
|
|
194
|
-
1,
|
|
195
|
-
[{ method: 'main', class: 'Test', locals: {} }],
|
|
196
|
-
{},
|
|
197
|
-
);
|
|
198
|
-
const layout = computeLayout([step]);
|
|
199
|
-
const svg = generateStepSvg(step, layout);
|
|
200
|
-
|
|
201
|
-
// Should still be valid SVG
|
|
202
|
-
expect(svg).toStartWith('<svg class="trace-memory"');
|
|
203
|
-
expect(svg).toEndWith('</svg>');
|
|
204
|
-
// Should have viewBox 0 0 0 0 for empty diagram
|
|
205
|
-
expect(svg).toContain('viewBox="0 0 0 0"');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('handles string heap objects', () => {
|
|
209
|
-
const step = makeStep(
|
|
210
|
-
5,
|
|
211
|
-
[
|
|
212
|
-
{
|
|
213
|
-
method: 'main',
|
|
214
|
-
class: 'Test',
|
|
215
|
-
locals: { s: { type: 'ref', id: '1' } },
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
{ '1': { type: 'String', value: 'hello' } },
|
|
219
|
-
);
|
|
220
|
-
const layout = computeLayout([step]);
|
|
221
|
-
const svg = generateStepSvg(step, layout);
|
|
222
|
-
|
|
223
|
-
// String object should show quoted text
|
|
224
|
-
expect(svg).toContain('"hello"');
|
|
225
|
-
// Should have an arrow to the string
|
|
226
|
-
expect(svg).toContain('class="trace-arrow"');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test('handles array heap objects', () => {
|
|
230
|
-
const step = makeStep(
|
|
231
|
-
5,
|
|
232
|
-
[
|
|
233
|
-
{
|
|
234
|
-
method: 'main',
|
|
235
|
-
class: 'Test',
|
|
236
|
-
locals: { arr: { type: 'ref', id: '1' } },
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
{
|
|
240
|
-
'1': {
|
|
241
|
-
type: 'int[]',
|
|
242
|
-
elements: [
|
|
243
|
-
{ type: 'int', value: 1 },
|
|
244
|
-
{ type: 'int', value: 2 },
|
|
245
|
-
{ type: 'int', value: 3 },
|
|
246
|
-
],
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
);
|
|
250
|
-
const layout = computeLayout([step]);
|
|
251
|
-
const svg = generateStepSvg(step, layout);
|
|
252
|
-
|
|
253
|
-
// Should show array values
|
|
254
|
-
expect(svg).toContain('>1</text>');
|
|
255
|
-
expect(svg).toContain('>2</text>');
|
|
256
|
-
expect(svg).toContain('>3</text>');
|
|
257
|
-
// Should have cell separators
|
|
258
|
-
expect(svg).toContain('class="trace-separator"');
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('multiple stack frames are rendered in reverse order', () => {
|
|
262
|
-
// In JDI traces, stack[0] is the bottom (main), stack[1] is the newest call.
|
|
263
|
-
// The renderer reverses the stack so the newest frame appears at the top
|
|
264
|
-
// visually and main appears at the bottom.
|
|
265
|
-
const step = makeStep(
|
|
266
|
-
20,
|
|
267
|
-
[
|
|
268
|
-
{
|
|
269
|
-
method: 'main',
|
|
270
|
-
class: 'Test',
|
|
271
|
-
line: 10,
|
|
272
|
-
locals: { x: { type: 'int', value: 1 } },
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
method: 'foo',
|
|
276
|
-
class: 'Test',
|
|
277
|
-
line: 20,
|
|
278
|
-
locals: { y: { type: 'int', value: 2 } },
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
{},
|
|
282
|
-
);
|
|
283
|
-
const layout = computeLayout([step]);
|
|
284
|
-
const svg = generateStepSvg(step, layout);
|
|
285
|
-
|
|
286
|
-
// Both frames should be present
|
|
287
|
-
expect(svg).toContain('main:10');
|
|
288
|
-
expect(svg).toContain('foo:20');
|
|
289
|
-
// foo (newest frame) should appear first in SVG output (top of diagram)
|
|
290
|
-
const mainPos = svg.indexOf('main:10');
|
|
291
|
-
const fooPos = svg.indexOf('foo:20');
|
|
292
|
-
expect(fooPos).toBeLessThan(mainPos);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test('non-tree field object has no child-slot row', () => {
|
|
296
|
-
const step = makeStep(
|
|
297
|
-
5,
|
|
298
|
-
[
|
|
299
|
-
{
|
|
300
|
-
method: 'main',
|
|
301
|
-
class: 'Test',
|
|
302
|
-
locals: { p: { type: 'ref', id: '1' } },
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
{
|
|
306
|
-
'1': {
|
|
307
|
-
type: 'Person',
|
|
308
|
-
fields: {
|
|
309
|
-
name: { type: 'String', value: 'Alice' },
|
|
310
|
-
age: { type: 'int', value: 30 },
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
);
|
|
315
|
-
const layout = computeLayout([step]);
|
|
316
|
-
const svg = generateStepSvg(step, layout);
|
|
317
|
-
|
|
318
|
-
// Should have field names as regular rows
|
|
319
|
-
expect(svg).toContain('>name</text>');
|
|
320
|
-
expect(svg).toContain('>age</text>');
|
|
321
|
-
// Should have values
|
|
322
|
-
expect(svg).toContain('>"Alice"</text>');
|
|
323
|
-
expect(svg).toContain('>30</text>');
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe('filterStep', () => {
|
|
328
|
-
test('removes args from main locals', () => {
|
|
329
|
-
const step = makeStep(
|
|
330
|
-
1,
|
|
331
|
-
[
|
|
332
|
-
{
|
|
333
|
-
method: 'main',
|
|
334
|
-
class: 'Test',
|
|
335
|
-
locals: {
|
|
336
|
-
args: { type: 'ref', id: 'args_1' },
|
|
337
|
-
x: { type: 'int', value: 5 },
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
],
|
|
341
|
-
{ args_1: { type: 'String[]', elements: [] } },
|
|
342
|
-
);
|
|
343
|
-
const { stack, heap } = filterStep(step);
|
|
344
|
-
expect(stack[0].locals).not.toHaveProperty('args');
|
|
345
|
-
expect(stack[0].locals).toHaveProperty('x');
|
|
346
|
-
// args_1 should be filtered out since it's only reachable via args
|
|
347
|
-
expect(heap).not.toHaveProperty('args_1');
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test('keeps objects reachable from non-args variables', () => {
|
|
351
|
-
const step = makeStep(
|
|
352
|
-
1,
|
|
353
|
-
[
|
|
354
|
-
{
|
|
355
|
-
method: 'main',
|
|
356
|
-
class: 'Test',
|
|
357
|
-
locals: {
|
|
358
|
-
args: { type: 'ref', id: 'args_1' },
|
|
359
|
-
node: { type: 'ref', id: '2' },
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
{
|
|
364
|
-
args_1: { type: 'String[]', elements: [] },
|
|
365
|
-
'2': { type: 'Node', fields: { val: { type: 'int', value: 10 } } },
|
|
366
|
-
},
|
|
367
|
-
);
|
|
368
|
-
const { heap } = filterStep(step);
|
|
369
|
-
expect(heap).toHaveProperty('2');
|
|
370
|
-
expect(heap).not.toHaveProperty('args_1');
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe('formatValue', () => {
|
|
375
|
-
test('null shows "null"', () => {
|
|
376
|
-
expect(formatValue({ type: 'null' })).toBe('null');
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
test('boolean shows true/false', () => {
|
|
380
|
-
expect(formatValue({ type: 'boolean', value: true })).toBe('true');
|
|
381
|
-
expect(formatValue({ type: 'boolean', value: false })).toBe('false');
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test('char shows quoted character', () => {
|
|
385
|
-
expect(formatValue({ type: 'char', value: 'A' })).toBe("'A'");
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
test('String shows double-quoted', () => {
|
|
389
|
-
expect(formatValue({ type: 'String', value: 'hi' })).toBe('"hi"');
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test('int shows number', () => {
|
|
393
|
-
expect(formatValue({ type: 'int', value: 42 })).toBe('42');
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
test('ref shows bullet', () => {
|
|
397
|
-
expect(formatValue({ type: 'ref', id: '1' })).toBe('\u2022');
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
test('unknown shows ?', () => {
|
|
401
|
-
expect(formatValue({ type: 'unknown' })).toBe('?');
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test('uninitialized shows empty', () => {
|
|
405
|
-
expect(formatValue({ type: 'uninitialized' })).toBe('');
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test('truncated shows ellipsis with count', () => {
|
|
409
|
-
expect(formatValue({ type: 'truncated', remaining: 5 })).toBe('\u2026(5)');
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
describe('escapeXml', () => {
|
|
414
|
-
test('escapes &, <, >, "', () => {
|
|
415
|
-
expect(escapeXml('a & b')).toBe('a & b');
|
|
416
|
-
expect(escapeXml('<tag>')).toBe('<tag>');
|
|
417
|
-
expect(escapeXml('say "hi"')).toBe('say "hi"');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test('handles combined special chars', () => {
|
|
421
|
-
expect(escapeXml('a & b < c > d "e"')).toBe(
|
|
422
|
-
'a & b < c > d "e"',
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { chunkTraceOutput, parseIgnoreFields } from './trace';
|
|
6
|
-
import type { TraceManifest } from '../types';
|
|
7
|
-
|
|
8
|
-
describe('chunkTraceOutput', () => {
|
|
9
|
-
let tempDir: string;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-trace-test-'));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('chunks JSONL output into files and writes manifest', () => {
|
|
20
|
-
const lines = [];
|
|
21
|
-
for (let i = 0; i < 5; i++) {
|
|
22
|
-
lines.push(
|
|
23
|
-
JSON.stringify({
|
|
24
|
-
line: i + 1,
|
|
25
|
-
file: 'Test.java',
|
|
26
|
-
stack: [{ method: 'main', class: 'Test', locals: {} }],
|
|
27
|
-
heap: {},
|
|
28
|
-
stdout: '',
|
|
29
|
-
}),
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
const output = lines.join('\n') + '\n';
|
|
33
|
-
const outputDir = path.join(tempDir, '_traces', 'Test');
|
|
34
|
-
|
|
35
|
-
const manifest = chunkTraceOutput(
|
|
36
|
-
output,
|
|
37
|
-
outputDir,
|
|
38
|
-
'Test.java',
|
|
39
|
-
'public class Test {}',
|
|
40
|
-
3,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Should create 2 chunks (3 + 2)
|
|
44
|
-
const chunk0 = JSON.parse(
|
|
45
|
-
fs.readFileSync(path.join(outputDir, 'chunk-0.json'), 'utf-8'),
|
|
46
|
-
);
|
|
47
|
-
const chunk1 = JSON.parse(
|
|
48
|
-
fs.readFileSync(path.join(outputDir, 'chunk-1.json'), 'utf-8'),
|
|
49
|
-
);
|
|
50
|
-
expect(chunk0).toHaveLength(3);
|
|
51
|
-
expect(chunk1).toHaveLength(2);
|
|
52
|
-
|
|
53
|
-
// Chunks now contain TraceChunkEntry objects with precomputed SVG
|
|
54
|
-
expect(chunk0[0].svg).toContain('<svg');
|
|
55
|
-
expect(chunk0[0]).toHaveProperty('line');
|
|
56
|
-
expect(chunk0[0]).toHaveProperty('stdout');
|
|
57
|
-
|
|
58
|
-
// Manifest (returned value and file on disk should both be correct)
|
|
59
|
-
const manifestFile = JSON.parse(
|
|
60
|
-
fs.readFileSync(path.join(outputDir, 'manifest.json'), 'utf-8'),
|
|
61
|
-
) as TraceManifest;
|
|
62
|
-
expect(manifest.totalSteps).toBe(5);
|
|
63
|
-
expect(manifest.chunkSize).toBe(3);
|
|
64
|
-
expect(manifestFile.sourceFile).toBe('Test.java');
|
|
65
|
-
expect(manifestFile.source).toBe('public class Test {}');
|
|
66
|
-
expect(manifest.lineToSteps[1]).toEqual([0]);
|
|
67
|
-
expect(manifest.lineToSteps[5]).toEqual([4]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('handles single chunk', () => {
|
|
71
|
-
const line = JSON.stringify({
|
|
72
|
-
line: 1,
|
|
73
|
-
file: 'Test.java',
|
|
74
|
-
stack: [],
|
|
75
|
-
heap: {},
|
|
76
|
-
stdout: 'hello\n',
|
|
77
|
-
});
|
|
78
|
-
const outputDir = path.join(tempDir, '_traces', 'Test');
|
|
79
|
-
|
|
80
|
-
const manifest = chunkTraceOutput(
|
|
81
|
-
line,
|
|
82
|
-
outputDir,
|
|
83
|
-
'Test.java',
|
|
84
|
-
'class Test {}',
|
|
85
|
-
50,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
expect(manifest.totalSteps).toBe(1);
|
|
89
|
-
const chunk0 = JSON.parse(
|
|
90
|
-
fs.readFileSync(path.join(outputDir, 'chunk-0.json'), 'utf-8'),
|
|
91
|
-
);
|
|
92
|
-
expect(chunk0).toHaveLength(1);
|
|
93
|
-
expect(chunk0[0].stdout).toBe('hello\n');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('passes ignoreFields from source to layout', () => {
|
|
97
|
-
const source = `class Node {
|
|
98
|
-
int value;
|
|
99
|
-
Node left;
|
|
100
|
-
Node right;
|
|
101
|
-
Node parent; // @trace-ignore
|
|
102
|
-
}
|
|
103
|
-
public class Test {
|
|
104
|
-
public static void main(String[] args) {}
|
|
105
|
-
}`;
|
|
106
|
-
const fields = parseIgnoreFields(source);
|
|
107
|
-
expect(fields).toEqual({ Node: ['parent'] });
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('parseIgnoreFields handles multiple classes', () => {
|
|
111
|
-
const source = `class A {
|
|
112
|
-
A prev; // @trace-ignore
|
|
113
|
-
A next;
|
|
114
|
-
}
|
|
115
|
-
class B {
|
|
116
|
-
B up; // @trace-ignore
|
|
117
|
-
B down; // @trace-ignore
|
|
118
|
-
}`;
|
|
119
|
-
const fields = parseIgnoreFields(source);
|
|
120
|
-
expect(fields).toEqual({ A: ['prev'], B: ['up', 'down'] });
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('parseIgnoreFields handles inner classes with $ separator', () => {
|
|
124
|
-
const source = `public class SearchTreeDemo {
|
|
125
|
-
static class Node {
|
|
126
|
-
String data;
|
|
127
|
-
Node left;
|
|
128
|
-
Node right;
|
|
129
|
-
Node parent; // @trace-ignore
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
public static void main(String[] args) {}
|
|
133
|
-
}`;
|
|
134
|
-
const fields = parseIgnoreFields(source);
|
|
135
|
-
expect(fields).toEqual({ SearchTreeDemo$Node: ['parent'] });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('parseIgnoreFields returns empty for no annotations', () => {
|
|
139
|
-
const source = `class Node { int x; }`;
|
|
140
|
-
expect(parseIgnoreFields(source)).toEqual({});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('maps repeated lines to multiple step indices', () => {
|
|
144
|
-
const lines = [];
|
|
145
|
-
// Simulate a loop: line 3 executes at steps 0, 1, 2
|
|
146
|
-
for (let i = 0; i < 3; i++) {
|
|
147
|
-
lines.push(
|
|
148
|
-
JSON.stringify({
|
|
149
|
-
line: 3,
|
|
150
|
-
file: 'Test.java',
|
|
151
|
-
stack: [],
|
|
152
|
-
heap: {},
|
|
153
|
-
stdout: '',
|
|
154
|
-
}),
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
const outputDir = path.join(tempDir, '_traces', 'Test');
|
|
158
|
-
|
|
159
|
-
const manifest = chunkTraceOutput(
|
|
160
|
-
lines.join('\n'),
|
|
161
|
-
outputDir,
|
|
162
|
-
'Test.java',
|
|
163
|
-
'',
|
|
164
|
-
50,
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
expect(manifest.lineToSteps[3]).toEqual([0, 1, 2]);
|
|
168
|
-
});
|
|
169
|
-
});
|