@opendata-ai/openchart-vanilla 6.4.1 → 6.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.
@@ -53,7 +53,7 @@ afterEach(() => {
53
53
  describe('line chart SVG rendering', () => {
54
54
  it('renders <path> elements with valid d attribute for each series', () => {
55
55
  const { svg } = renderSpec(lineSpec);
56
- const paths = svg.querySelectorAll('.viz-mark-line path');
56
+ const paths = svg.querySelectorAll('.oc-mark-line path');
57
57
  expect(paths.length).toBeGreaterThan(0);
58
58
 
59
59
  for (const path of paths) {
@@ -67,14 +67,14 @@ describe('line chart SVG rendering', () => {
67
67
 
68
68
  it('creates a mark group per series in multi-series line chart', () => {
69
69
  const { svg } = renderSpec(lineSpec);
70
- const lineGroups = svg.querySelectorAll('.viz-mark-line');
70
+ const lineGroups = svg.querySelectorAll('.oc-mark-line');
71
71
  // lineSpec has 2 series (US and UK)
72
72
  expect(lineGroups.length).toBe(2);
73
73
  });
74
74
 
75
75
  it('each line mark group has a data-mark-id attribute', () => {
76
76
  const { svg } = renderSpec(lineSpec);
77
- const lineGroups = svg.querySelectorAll('.viz-mark-line');
77
+ const lineGroups = svg.querySelectorAll('.oc-mark-line');
78
78
  for (const group of lineGroups) {
79
79
  const markId = group.getAttribute('data-mark-id');
80
80
  expect(markId).not.toBeNull();
@@ -84,7 +84,7 @@ describe('line chart SVG rendering', () => {
84
84
 
85
85
  it('each line mark group has a data-series attribute', () => {
86
86
  const { svg } = renderSpec(lineSpec);
87
- const lineGroups = svg.querySelectorAll('.viz-mark-line');
87
+ const lineGroups = svg.querySelectorAll('.oc-mark-line');
88
88
  const seriesNames = new Set<string>();
89
89
  for (const group of lineGroups) {
90
90
  const series = group.getAttribute('data-series');
@@ -97,7 +97,7 @@ describe('line chart SVG rendering', () => {
97
97
 
98
98
  it('paths have stroke color and non-zero stroke width', () => {
99
99
  const { svg } = renderSpec(lineSpec);
100
- const paths = svg.querySelectorAll('.viz-mark-line path');
100
+ const paths = svg.querySelectorAll('.oc-mark-line path');
101
101
  for (const path of paths) {
102
102
  const stroke = path.getAttribute('stroke');
103
103
  expect(stroke).not.toBeNull();
@@ -115,14 +115,14 @@ describe('line chart SVG rendering', () => {
115
115
  describe('bar chart SVG rendering', () => {
116
116
  it('renders <rect> elements for each data point', () => {
117
117
  const { svg } = renderSpec(barSpec);
118
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
118
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
119
119
  // barSpec has 3 data points
120
120
  expect(rects.length).toBe(3);
121
121
  });
122
122
 
123
123
  it('rect elements have width and height > 0', () => {
124
124
  const { svg } = renderSpec(barSpec);
125
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
125
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
126
126
  for (const rect of rects) {
127
127
  const width = Number(rect.getAttribute('width'));
128
128
  const height = Number(rect.getAttribute('height'));
@@ -133,7 +133,7 @@ describe('bar chart SVG rendering', () => {
133
133
 
134
134
  it('rect marks have data-mark-id attributes', () => {
135
135
  const { svg } = renderSpec(barSpec);
136
- const markGroups = svg.querySelectorAll('.viz-mark-rect');
136
+ const markGroups = svg.querySelectorAll('.oc-mark-rect');
137
137
  for (const group of markGroups) {
138
138
  const markId = group.getAttribute('data-mark-id');
139
139
  expect(markId).not.toBeNull();
@@ -143,7 +143,7 @@ describe('bar chart SVG rendering', () => {
143
143
 
144
144
  it('bar rects are oriented horizontally (width varies, y is categorical)', () => {
145
145
  const { svg } = renderSpec(barSpec);
146
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
146
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
147
147
  const widths = Array.from(rects).map((r) => Number(r.getAttribute('width')));
148
148
  // Different data values should produce different widths
149
149
  const uniqueWidths = new Set(widths);
@@ -158,13 +158,13 @@ describe('bar chart SVG rendering', () => {
158
158
  describe('column chart SVG rendering', () => {
159
159
  it('renders <rect> elements oriented vertically', () => {
160
160
  const { svg } = renderSpec(columnSpec);
161
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
161
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
162
162
  expect(rects.length).toBe(3);
163
163
  });
164
164
 
165
165
  it('column rects have varying heights (vertical orientation)', () => {
166
166
  const { svg } = renderSpec(columnSpec);
167
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
167
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
168
168
  const heights = Array.from(rects).map((r) => Number(r.getAttribute('height')));
169
169
  // Different revenue values should produce different heights
170
170
  const uniqueHeights = new Set(heights);
@@ -177,7 +177,7 @@ describe('column chart SVG rendering', () => {
177
177
 
178
178
  it('column rects have positive width', () => {
179
179
  const { svg } = renderSpec(columnSpec);
180
- const rects = svg.querySelectorAll('.viz-mark-rect rect');
180
+ const rects = svg.querySelectorAll('.oc-mark-rect rect');
181
181
  for (const rect of rects) {
182
182
  const width = Number(rect.getAttribute('width'));
183
183
  expect(width).toBeGreaterThan(0);
@@ -192,14 +192,14 @@ describe('column chart SVG rendering', () => {
192
192
  describe('scatter chart SVG rendering', () => {
193
193
  it('renders <circle> elements for each data point', () => {
194
194
  const { svg } = renderSpec(scatterSpec);
195
- const circles = svg.querySelectorAll('.viz-mark-point');
195
+ const circles = svg.querySelectorAll('.oc-mark-point');
196
196
  // scatterSpec has 4 data points
197
197
  expect(circles.length).toBe(4);
198
198
  });
199
199
 
200
200
  it('circles have valid cx, cy, and r attributes', () => {
201
201
  const { svg } = renderSpec(scatterSpec);
202
- const circles = svg.querySelectorAll('.viz-mark-point');
202
+ const circles = svg.querySelectorAll('.oc-mark-point');
203
203
  for (const circle of circles) {
204
204
  const cx = Number(circle.getAttribute('cx'));
205
205
  const cy = Number(circle.getAttribute('cy'));
@@ -215,7 +215,7 @@ describe('scatter chart SVG rendering', () => {
215
215
 
216
216
  it('scatter marks have data-mark-id attributes', () => {
217
217
  const { svg } = renderSpec(scatterSpec);
218
- const circles = svg.querySelectorAll('.viz-mark-point');
218
+ const circles = svg.querySelectorAll('.oc-mark-point');
219
219
  for (const circle of circles) {
220
220
  const markId = circle.getAttribute('data-mark-id');
221
221
  expect(markId).not.toBeNull();
@@ -231,14 +231,14 @@ describe('scatter chart SVG rendering', () => {
231
231
  describe('pie chart SVG rendering', () => {
232
232
  it('renders <path> arc segments for each slice', () => {
233
233
  const { svg } = renderSpec(pieSpec);
234
- const arcGroups = svg.querySelectorAll('.viz-mark-arc');
234
+ const arcGroups = svg.querySelectorAll('.oc-mark-arc');
235
235
  // pieSpec has 3 categories
236
236
  expect(arcGroups.length).toBe(3);
237
237
  });
238
238
 
239
239
  it('arc paths have valid d attribute with arc commands', () => {
240
240
  const { svg } = renderSpec(pieSpec);
241
- const paths = svg.querySelectorAll('.viz-mark-arc path');
241
+ const paths = svg.querySelectorAll('.oc-mark-arc path');
242
242
  for (const path of paths) {
243
243
  const d = path.getAttribute('d');
244
244
  expect(d).not.toBeNull();
@@ -250,7 +250,7 @@ describe('pie chart SVG rendering', () => {
250
250
 
251
251
  it('arc groups are translated to the pie center', () => {
252
252
  const { svg } = renderSpec(pieSpec);
253
- const arcGroups = svg.querySelectorAll('.viz-mark-arc');
253
+ const arcGroups = svg.querySelectorAll('.oc-mark-arc');
254
254
  for (const group of arcGroups) {
255
255
  const transform = group.getAttribute('transform');
256
256
  expect(transform).not.toBeNull();
@@ -260,7 +260,7 @@ describe('pie chart SVG rendering', () => {
260
260
 
261
261
  it('arc marks have fill colors', () => {
262
262
  const { svg } = renderSpec(pieSpec);
263
- const paths = svg.querySelectorAll('.viz-mark-arc path');
263
+ const paths = svg.querySelectorAll('.oc-mark-arc path');
264
264
  for (const path of paths) {
265
265
  const fill = path.getAttribute('fill');
266
266
  expect(fill).not.toBeNull();
@@ -276,7 +276,7 @@ describe('pie chart SVG rendering', () => {
276
276
  describe('multi-series rendering', () => {
277
277
  it('multi-series line chart has distinct stroke colors per series', () => {
278
278
  const { svg } = renderSpec(lineSpec);
279
- const paths = svg.querySelectorAll('.viz-mark-line path');
279
+ const paths = svg.querySelectorAll('.oc-mark-line path');
280
280
  const strokes = new Set<string>();
281
281
  for (const path of paths) {
282
282
  const stroke = path.getAttribute('stroke');
@@ -288,7 +288,7 @@ describe('multi-series rendering', () => {
288
288
 
289
289
  it('multi-series scatter chart has distinct fill colors per group', () => {
290
290
  const { svg } = renderSpec(scatterSpec);
291
- const circles = svg.querySelectorAll('.viz-mark-point');
291
+ const circles = svg.querySelectorAll('.oc-mark-point');
292
292
  const fills = new Set<string>();
293
293
  for (const circle of circles) {
294
294
  const fill = circle.getAttribute('fill');
@@ -300,7 +300,7 @@ describe('multi-series rendering', () => {
300
300
 
301
301
  it('multi-series bar chart renders data-series attributes on rect marks', () => {
302
302
  const { svg } = renderSpec(multiSeriesBarSpec);
303
- const marks = svg.querySelectorAll('.viz-mark-rect[data-series]');
303
+ const marks = svg.querySelectorAll('.oc-mark-rect[data-series]');
304
304
  const seriesNames = new Set<string>();
305
305
  for (const mark of marks) {
306
306
  const s = mark.getAttribute('data-series');
@@ -318,35 +318,35 @@ describe('multi-series rendering', () => {
318
318
  describe('chart chrome rendering', () => {
319
319
  it('renders title text with correct content', () => {
320
320
  const { svg } = renderSpec(lineSpec);
321
- const title = svg.querySelector('.viz-title');
321
+ const title = svg.querySelector('.oc-title');
322
322
  expect(title).not.toBeNull();
323
323
  expect(title!.textContent).toBe('GDP Growth');
324
324
  });
325
325
 
326
326
  it('renders subtitle text with correct content', () => {
327
327
  const { svg } = renderSpec(lineSpec);
328
- const subtitle = svg.querySelector('.viz-subtitle');
328
+ const subtitle = svg.querySelector('.oc-subtitle');
329
329
  expect(subtitle).not.toBeNull();
330
330
  expect(subtitle!.textContent).toBe('US vs UK over time');
331
331
  });
332
332
 
333
333
  it('renders source text with correct content', () => {
334
334
  const { svg } = renderSpec(lineSpec);
335
- const source = svg.querySelector('.viz-source');
335
+ const source = svg.querySelector('.oc-source');
336
336
  expect(source).not.toBeNull();
337
337
  expect(source!.textContent).toBe('World Bank');
338
338
  });
339
339
 
340
- it('chrome elements are inside a .viz-chrome group', () => {
340
+ it('chrome elements are inside a .oc-chrome group', () => {
341
341
  const { svg } = renderSpec(lineSpec);
342
- const chromeGroup = svg.querySelector('.viz-chrome');
342
+ const chromeGroup = svg.querySelector('.oc-chrome');
343
343
  expect(chromeGroup).not.toBeNull();
344
- expect(chromeGroup!.querySelector('.viz-title')).not.toBeNull();
344
+ expect(chromeGroup!.querySelector('.oc-title')).not.toBeNull();
345
345
  });
346
346
 
347
347
  it('title has font styling applied', () => {
348
348
  const { svg } = renderSpec(lineSpec);
349
- const title = svg.querySelector('.viz-title');
349
+ const title = svg.querySelector('.oc-title');
350
350
  expect(title).not.toBeNull();
351
351
  const fontFamily = title!.getAttribute('font-family');
352
352
  const fontSize = Number(title!.getAttribute('font-size'));
@@ -371,7 +371,7 @@ describe('chart chrome rendering', () => {
371
371
  };
372
372
  // Render at a very narrow width to force wrapping
373
373
  const { svg } = renderSpec(longTitleSpec, { width: 250, height: 300 });
374
- const title = svg.querySelector('.viz-title');
374
+ const title = svg.querySelector('.oc-title');
375
375
  expect(title).not.toBeNull();
376
376
  const tspans = title!.querySelectorAll('tspan');
377
377
  expect(tspans.length).toBeGreaterThan(1);
@@ -386,7 +386,7 @@ describe('chart chrome rendering', () => {
386
386
 
387
387
  it('does not wrap short title text', () => {
388
388
  const { svg } = renderSpec(lineSpec);
389
- const title = svg.querySelector('.viz-title');
389
+ const title = svg.querySelector('.oc-title');
390
390
  expect(title).not.toBeNull();
391
391
  // Short title should have no tspan children, just direct textContent
392
392
  const tspans = title!.querySelectorAll('tspan');
@@ -404,12 +404,12 @@ describe('chart chrome rendering', () => {
404
404
  },
405
405
  };
406
406
  const { svg } = renderSpec(noChrome);
407
- const chromeGroup = svg.querySelector('.viz-chrome');
407
+ const chromeGroup = svg.querySelector('.oc-chrome');
408
408
  expect(chromeGroup).not.toBeNull();
409
409
  // No title/subtitle/source should be in the chrome group
410
- expect(chromeGroup!.querySelector('.viz-title')).toBeNull();
411
- expect(chromeGroup!.querySelector('.viz-subtitle')).toBeNull();
412
- expect(chromeGroup!.querySelector('.viz-source')).toBeNull();
410
+ expect(chromeGroup!.querySelector('.oc-title')).toBeNull();
411
+ expect(chromeGroup!.querySelector('.oc-subtitle')).toBeNull();
412
+ expect(chromeGroup!.querySelector('.oc-source')).toBeNull();
413
413
  });
414
414
  });
415
415
 
@@ -420,15 +420,15 @@ describe('chart chrome rendering', () => {
420
420
  describe('axis rendering', () => {
421
421
  it('renders x-axis and y-axis groups', () => {
422
422
  const { svg } = renderSpec(lineSpec);
423
- const xAxis = svg.querySelector('.viz-axis-x');
424
- const yAxis = svg.querySelector('.viz-axis-y');
423
+ const xAxis = svg.querySelector('.oc-axis-x');
424
+ const yAxis = svg.querySelector('.oc-axis-y');
425
425
  expect(xAxis).not.toBeNull();
426
426
  expect(yAxis).not.toBeNull();
427
427
  });
428
428
 
429
429
  it('x-axis has tick labels as text elements', () => {
430
430
  const { svg } = renderSpec(lineSpec);
431
- const xAxis = svg.querySelector('.viz-axis-x');
431
+ const xAxis = svg.querySelector('.oc-axis-x');
432
432
  const labels = xAxis!.querySelectorAll('text');
433
433
  expect(labels.length).toBeGreaterThan(0);
434
434
  // Each label should have text content
@@ -439,14 +439,14 @@ describe('axis rendering', () => {
439
439
 
440
440
  it('y-axis has tick labels as text elements', () => {
441
441
  const { svg } = renderSpec(barSpec);
442
- const yAxis = svg.querySelector('.viz-axis-y');
442
+ const yAxis = svg.querySelector('.oc-axis-y');
443
443
  const labels = yAxis!.querySelectorAll('text');
444
444
  expect(labels.length).toBeGreaterThan(0);
445
445
  });
446
446
 
447
447
  it('x-axis has a baseline line element', () => {
448
448
  const { svg } = renderSpec(lineSpec);
449
- const xAxis = svg.querySelector('.viz-axis-x');
449
+ const xAxis = svg.querySelector('.oc-axis-x');
450
450
  const line = xAxis!.querySelector('line');
451
451
  // The renderer draws an axis line for x-axis
452
452
  expect(line).not.toBeNull();
@@ -461,14 +461,14 @@ describe('gridline rendering', () => {
461
461
  it('renders gridlines as line elements within axis groups', () => {
462
462
  const { svg } = renderSpec(lineSpec);
463
463
  // y-axis gridlines are horizontal lines
464
- const yAxis = svg.querySelector('.viz-axis-y');
464
+ const yAxis = svg.querySelector('.oc-axis-y');
465
465
  const gridlines = yAxis!.querySelectorAll('line');
466
466
  expect(gridlines.length).toBeGreaterThan(0);
467
467
  });
468
468
 
469
469
  it('gridlines have stroke-opacity for subtlety', () => {
470
470
  const { svg } = renderSpec(lineSpec);
471
- const yAxis = svg.querySelector('.viz-axis-y');
471
+ const yAxis = svg.querySelector('.oc-axis-y');
472
472
  const gridlines = yAxis!.querySelectorAll('line');
473
473
  for (const gl of gridlines) {
474
474
  const opacity = gl.getAttribute('stroke-opacity');
@@ -487,16 +487,16 @@ describe('gridline rendering', () => {
487
487
  describe('legend rendering', () => {
488
488
  it('multi-series chart renders legend entries', () => {
489
489
  const { svg } = renderSpec(lineSpec);
490
- const legend = svg.querySelector('.viz-legend');
490
+ const legend = svg.querySelector('.oc-legend');
491
491
  expect(legend).not.toBeNull();
492
- const entries = legend!.querySelectorAll('.viz-legend-entry');
492
+ const entries = legend!.querySelectorAll('.oc-legend-entry');
493
493
  // lineSpec has US and UK series
494
494
  expect(entries.length).toBe(2);
495
495
  });
496
496
 
497
497
  it('legend entries have labels with series names', () => {
498
498
  const { svg } = renderSpec(lineSpec);
499
- const entries = svg.querySelectorAll('.viz-legend-entry');
499
+ const entries = svg.querySelectorAll('.oc-legend-entry');
500
500
  const labels: string[] = [];
501
501
  for (const entry of entries) {
502
502
  const text = entry.querySelector('text');
@@ -508,7 +508,7 @@ describe('legend rendering', () => {
508
508
 
509
509
  it('legend entries have data-legend-label attribute', () => {
510
510
  const { svg } = renderSpec(lineSpec);
511
- const entries = svg.querySelectorAll('.viz-legend-entry');
511
+ const entries = svg.querySelectorAll('.oc-legend-entry');
512
512
  for (const entry of entries) {
513
513
  expect(entry.getAttribute('data-legend-label')).not.toBeNull();
514
514
  }
@@ -516,10 +516,10 @@ describe('legend rendering', () => {
516
516
 
517
517
  it('legend has ARIA attributes for accessibility', () => {
518
518
  const { svg } = renderSpec(lineSpec);
519
- const legend = svg.querySelector('.viz-legend');
519
+ const legend = svg.querySelector('.oc-legend');
520
520
  expect(legend!.getAttribute('role')).toBe('list');
521
521
  expect(legend!.getAttribute('aria-label')).toBe('Chart legend');
522
- const entries = legend!.querySelectorAll('.viz-legend-entry');
522
+ const entries = legend!.querySelectorAll('.oc-legend-entry');
523
523
  for (const entry of entries) {
524
524
  expect(entry.getAttribute('role')).toBe('listitem');
525
525
  }
@@ -527,13 +527,13 @@ describe('legend rendering', () => {
527
527
 
528
528
  it('single-series chart has no legend entries', () => {
529
529
  const { svg } = renderSpec(singleSeriesLineSpec);
530
- const entries = svg.querySelectorAll('.viz-legend-entry');
530
+ const entries = svg.querySelectorAll('.oc-legend-entry');
531
531
  expect(entries.length).toBe(0);
532
532
  });
533
533
 
534
534
  it('pie chart renders legend entries for each slice', () => {
535
535
  const { svg } = renderSpec(pieSpec);
536
- const entries = svg.querySelectorAll('.viz-legend-entry');
536
+ const entries = svg.querySelectorAll('.oc-legend-entry');
537
537
  // pieSpec has 3 slices
538
538
  expect(entries.length).toBe(3);
539
539
  });
@@ -558,9 +558,9 @@ describe('SVG root structure', () => {
558
558
  expect(ariaLabel!.length).toBeGreaterThan(0);
559
559
  });
560
560
 
561
- it('SVG has viz-chart class', () => {
561
+ it('SVG has oc-chart class', () => {
562
562
  const { svg } = renderSpec(lineSpec);
563
- expect(svg.getAttribute('class')).toBe('viz-chart');
563
+ expect(svg.getAttribute('class')).toBe('oc-chart');
564
564
  });
565
565
 
566
566
  it('SVG has a background rect as first child', () => {
@@ -577,14 +577,14 @@ describe('SVG root structure', () => {
577
577
  expect(defs).not.toBeNull();
578
578
  const clipPath = defs!.querySelector('clipPath');
579
579
  expect(clipPath).not.toBeNull();
580
- expect(clipPath!.getAttribute('id')).toMatch(/^viz-clip-/);
580
+ expect(clipPath!.getAttribute('id')).toMatch(/^oc-clip-/);
581
581
  });
582
582
 
583
583
  it('marks group is clipped via clip-path attribute', () => {
584
584
  const { svg } = renderSpec(lineSpec);
585
585
  const clippedGroup = svg.querySelector('[clip-path]');
586
586
  expect(clippedGroup).not.toBeNull();
587
- expect(clippedGroup!.getAttribute('clip-path')).toMatch(/url\(#viz-clip-/);
587
+ expect(clippedGroup!.getAttribute('clip-path')).toMatch(/url\(#oc-clip-/);
588
588
  });
589
589
  });
590
590
 
@@ -595,9 +595,9 @@ describe('SVG root structure', () => {
595
595
  describe('targeted mark snapshots', () => {
596
596
  it('line mark group has expected structure', () => {
597
597
  const { svg } = renderSpec(singleSeriesLineSpec);
598
- const lineGroup = svg.querySelector('.viz-mark-line');
598
+ const lineGroup = svg.querySelector('.oc-mark-line');
599
599
  expect(lineGroup).not.toBeNull();
600
- expect(lineGroup!.getAttribute('class')).toBe('viz-mark viz-mark-line');
600
+ expect(lineGroup!.getAttribute('class')).toBe('oc-mark oc-mark-line');
601
601
  expect(lineGroup!.getAttribute('data-mark-id')).toMatch(/^line-/);
602
602
 
603
603
  const path = lineGroup!.querySelector('path');
@@ -610,9 +610,9 @@ describe('targeted mark snapshots', () => {
610
610
 
611
611
  it('rect mark group has expected structure', () => {
612
612
  const { svg } = renderSpec(barSpec);
613
- const rectGroup = svg.querySelector('.viz-mark-rect');
613
+ const rectGroup = svg.querySelector('.oc-mark-rect');
614
614
  expect(rectGroup).not.toBeNull();
615
- expect(rectGroup!.getAttribute('class')).toBe('viz-mark viz-mark-rect');
615
+ expect(rectGroup!.getAttribute('class')).toBe('oc-mark oc-mark-rect');
616
616
  expect(rectGroup!.getAttribute('data-mark-id')).toMatch(/^rect-/);
617
617
 
618
618
  const rect = rectGroup!.querySelector('rect');
@@ -624,10 +624,10 @@ describe('targeted mark snapshots', () => {
624
624
 
625
625
  it('point mark has expected attributes', () => {
626
626
  const { svg } = renderSpec(scatterSpec);
627
- const point = svg.querySelector('.viz-mark-point');
627
+ const point = svg.querySelector('.oc-mark-point');
628
628
  expect(point).not.toBeNull();
629
629
  expect(point!.tagName.toLowerCase()).toBe('circle');
630
- expect(point!.getAttribute('class')).toBe('viz-mark viz-mark-point');
630
+ expect(point!.getAttribute('class')).toBe('oc-mark oc-mark-point');
631
631
  expect(point!.getAttribute('data-mark-id')).toMatch(/^point-/);
632
632
  expect(Number(point!.getAttribute('r'))).toBeGreaterThan(0);
633
633
  expect(point!.getAttribute('fill')).not.toBeNull();
@@ -635,9 +635,9 @@ describe('targeted mark snapshots', () => {
635
635
 
636
636
  it('arc mark group has expected structure', () => {
637
637
  const { svg } = renderSpec(pieSpec);
638
- const arcGroup = svg.querySelector('.viz-mark-arc');
638
+ const arcGroup = svg.querySelector('.oc-mark-arc');
639
639
  expect(arcGroup).not.toBeNull();
640
- expect(arcGroup!.getAttribute('class')).toBe('viz-mark viz-mark-arc');
640
+ expect(arcGroup!.getAttribute('class')).toBe('oc-mark oc-mark-arc');
641
641
  expect(arcGroup!.getAttribute('data-mark-id')).toMatch(/^arc-/);
642
642
  expect(arcGroup!.getAttribute('transform')).toMatch(/translate\(/);
643
643
 
@@ -655,7 +655,7 @@ describe('targeted mark snapshots', () => {
655
655
  describe('brand watermark', () => {
656
656
  it('renders "OpenData" as a single text element with two tspans', () => {
657
657
  const { svg } = renderSpec(lineSpec);
658
- const brandLink = svg.querySelector('.viz-chrome-ref');
658
+ const brandLink = svg.querySelector('.oc-chrome-ref');
659
659
  expect(brandLink).not.toBeNull();
660
660
  const text = brandLink!.querySelector('text')!;
661
661
  expect(text.textContent).toBe('OpenData');
@@ -673,21 +673,21 @@ describe('brand watermark', () => {
673
673
 
674
674
  it('is a direct child of SVG root', () => {
675
675
  const { svg } = renderSpec(lineSpec);
676
- const brandLink = svg.querySelector('.viz-chrome-ref');
676
+ const brandLink = svg.querySelector('.oc-chrome-ref');
677
677
  expect(brandLink!.parentElement).toBe(svg);
678
678
  });
679
679
 
680
680
  it('renders after chrome (in the footer row)', () => {
681
681
  const { svg } = renderSpec(lineSpec);
682
682
  const children = Array.from(svg.children);
683
- const chromeIdx = children.findIndex((el) => el.classList.contains('viz-chrome'));
684
- const brandIdx = children.findIndex((el) => el.classList.contains('viz-chrome-ref'));
683
+ const chromeIdx = children.findIndex((el) => el.classList.contains('oc-chrome'));
684
+ const brandIdx = children.findIndex((el) => el.classList.contains('oc-chrome-ref'));
685
685
  expect(brandIdx).toBeGreaterThan(chromeIdx);
686
686
  });
687
687
 
688
688
  it('skips watermark on very small charts', () => {
689
689
  const { svg } = renderSpec(lineSpec, { width: 100, height: 80 });
690
- const brandLink = svg.querySelector('.viz-chrome-ref');
690
+ const brandLink = svg.querySelector('.oc-chrome-ref');
691
691
  expect(brandLink).toBeNull();
692
692
  });
693
693
  });
@@ -41,7 +41,7 @@ function createTableDOM(rows: number, cols: number, opts?: { search?: boolean })
41
41
  // Search input
42
42
  if (opts?.search) {
43
43
  const searchDiv = document.createElement('div');
44
- searchDiv.className = 'viz-table-search';
44
+ searchDiv.className = 'oc-table-search';
45
45
  const input = document.createElement('input');
46
46
  searchDiv.appendChild(input);
47
47
  wrapper.appendChild(searchDiv);
@@ -147,8 +147,8 @@ describe('tbody arrow key navigation', () => {
147
147
 
148
148
  it('focus highlights first cell and sets aria-activedescendant', () => {
149
149
  const tbody = focusTbody(wrapper);
150
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-0');
151
- const cell = wrapper.querySelector('.viz-table-cell-focus');
150
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-0');
151
+ const cell = wrapper.querySelector('.oc-table-cell-focus');
152
152
  expect(cell).not.toBeNull();
153
153
  expect(cell?.textContent).toBe('R0C0');
154
154
  });
@@ -156,25 +156,25 @@ describe('tbody arrow key navigation', () => {
156
156
  it('ArrowDown moves focus down one row', () => {
157
157
  const tbody = focusTbody(wrapper);
158
158
  keydown(tbody, 'ArrowDown');
159
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-1-0');
159
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-1-0');
160
160
  });
161
161
 
162
162
  it('ArrowDown does not go past last row', () => {
163
163
  const tbody = focusTbody(wrapper);
164
164
  for (let i = 0; i < 10; i++) keydown(tbody, 'ArrowDown');
165
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-4-0');
165
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-4-0');
166
166
  });
167
167
 
168
168
  it('ArrowRight moves focus right one column', () => {
169
169
  const tbody = focusTbody(wrapper);
170
170
  keydown(tbody, 'ArrowRight');
171
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-1');
171
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-1');
172
172
  });
173
173
 
174
174
  it('ArrowRight does not go past last column', () => {
175
175
  const tbody = focusTbody(wrapper);
176
176
  for (let i = 0; i < 10; i++) keydown(tbody, 'ArrowRight');
177
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-2');
177
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-2');
178
178
  });
179
179
 
180
180
  it('ArrowLeft moves focus left', () => {
@@ -182,13 +182,13 @@ describe('tbody arrow key navigation', () => {
182
182
  keydown(tbody, 'ArrowRight');
183
183
  keydown(tbody, 'ArrowRight');
184
184
  keydown(tbody, 'ArrowLeft');
185
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-1');
185
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-1');
186
186
  });
187
187
 
188
188
  it('ArrowLeft does not go past first column', () => {
189
189
  const tbody = focusTbody(wrapper);
190
190
  keydown(tbody, 'ArrowLeft');
191
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-0');
191
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-0');
192
192
  });
193
193
 
194
194
  it('Home moves to first column in current row', () => {
@@ -196,19 +196,19 @@ describe('tbody arrow key navigation', () => {
196
196
  keydown(tbody, 'ArrowRight');
197
197
  keydown(tbody, 'ArrowRight');
198
198
  keydown(tbody, 'Home');
199
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-0');
199
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-0');
200
200
  });
201
201
 
202
202
  it('End moves to last column in current row', () => {
203
203
  const tbody = focusTbody(wrapper);
204
204
  keydown(tbody, 'End');
205
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-2');
205
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-2');
206
206
  });
207
207
 
208
208
  it('navigation clears previous focus highlight', () => {
209
209
  const tbody = focusTbody(wrapper);
210
210
  keydown(tbody, 'ArrowDown');
211
- const focused = wrapper.querySelectorAll('.viz-table-cell-focus');
211
+ const focused = wrapper.querySelectorAll('.oc-table-cell-focus');
212
212
  expect(focused.length).toBe(1);
213
213
  expect(focused[0].textContent).toBe('R1C0');
214
214
  });
@@ -274,7 +274,7 @@ describe('header keyboard navigation', () => {
274
274
  keydown(headers[1] as HTMLElement, 'ArrowDown');
275
275
  const tbody = wrapper.querySelector('tbody')!;
276
276
  // The tbody should have activedescendant set to first row, column 1
277
- expect(tbody.getAttribute('aria-activedescendant')).toBe('viz-cell-0-1');
277
+ expect(tbody.getAttribute('aria-activedescendant')).toBe('oc-cell-0-1');
278
278
  });
279
279
  });
280
280
 
@@ -305,19 +305,19 @@ describe('search escape handling', () => {
305
305
  });
306
306
 
307
307
  it('Escape in search input calls onClearSearch', () => {
308
- const input = wrapper.querySelector('.viz-table-search input')!;
308
+ const input = wrapper.querySelector('.oc-table-search input')!;
309
309
  keydown(input as HTMLElement, 'Escape');
310
310
  expect(onClearSearch).toHaveBeenCalledTimes(1);
311
311
  });
312
312
 
313
313
  it('Escape in search announces "Search cleared"', () => {
314
- const input = wrapper.querySelector('.viz-table-search input')!;
314
+ const input = wrapper.querySelector('.oc-table-search input')!;
315
315
  keydown(input as HTMLElement, 'Escape');
316
316
  expect(onAnnounce).toHaveBeenCalledWith('Search cleared');
317
317
  });
318
318
 
319
319
  it('non-Escape keys in search do not trigger clear', () => {
320
- const input = wrapper.querySelector('.viz-table-search input')!;
320
+ const input = wrapper.querySelector('.oc-table-search input')!;
321
321
  keydown(input as HTMLElement, 'a');
322
322
  keydown(input as HTMLElement, 'Enter');
323
323
  expect(onClearSearch).not.toHaveBeenCalled();
@@ -347,13 +347,13 @@ describe('cleanup', () => {
347
347
  tbody.dispatchEvent(new Event('focus'));
348
348
  keydown(tbody, 'ArrowDown');
349
349
 
350
- const input = wrapper.querySelector('.viz-table-search input')!;
350
+ const input = wrapper.querySelector('.oc-table-search input')!;
351
351
  keydown(input as HTMLElement, 'Escape');
352
352
 
353
353
  expect(onClearSearch).not.toHaveBeenCalled();
354
354
 
355
355
  // Focus highlight should be cleared
356
- const focused = wrapper.querySelectorAll('.viz-table-cell-focus');
356
+ const focused = wrapper.querySelectorAll('.oc-table-cell-focus');
357
357
  expect(focused.length).toBe(0);
358
358
 
359
359
  document.body.innerHTML = '';