@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-vanilla",
3
- "version": "6.4.1",
3
+ "version": "6.5.0",
4
4
  "description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@floating-ui/dom": "^1.7.6",
53
- "@opendata-ai/openchart-core": "6.4.1",
54
- "@opendata-ai/openchart-engine": "6.4.1",
53
+ "@opendata-ai/openchart-core": "6.5.0",
54
+ "@opendata-ai/openchart-engine": "6.5.0",
55
55
  "d3-force": "^3.0.0",
56
56
  "d3-quadtree": "^3.0.1"
57
57
  },
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Animation integration tests for the vanilla renderer.
3
+ *
4
+ * Tests the full pipeline from spec -> compile -> render for animation-related
5
+ * behavior. Verifies that the compiled layout carries animation config and that
6
+ * the SVG output is correctly structured for CSS animations to work.
7
+ *
8
+ * Note: CSS animation keyframes and class-based triggers are defined in the
9
+ * CSS partials under packages/core/src/styles/. These tests verify the DOM
10
+ * structure that CSS hooks into, not the visual animation itself (which
11
+ * requires a real browser).
12
+ */
13
+
14
+ import type { ChartSpec } from '@opendata-ai/openchart-engine';
15
+ import { compileChart } from '@opendata-ai/openchart-engine';
16
+ import { afterEach, describe, expect, it } from 'vitest';
17
+ import { createContainer } from '../__test-fixtures__/dom';
18
+ import { barSpec, columnSpec, lineSpec } from '../__test-fixtures__/specs';
19
+ import { createChart } from '../mount';
20
+ import { renderChartSVG } from '../svg-renderer';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function compileAndRender(spec: ChartSpec, width = 600, height = 400) {
27
+ const container = createContainer(width, height);
28
+ const layout = compileChart(spec, { width, height });
29
+ const svg = renderChartSVG(layout, container);
30
+ return { svg, container, layout };
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Cleanup
35
+ // ---------------------------------------------------------------------------
36
+
37
+ afterEach(() => {
38
+ document.body.innerHTML = '';
39
+ });
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Layout carries animation config through to render
43
+ // ---------------------------------------------------------------------------
44
+
45
+ describe('animation in compiled layout', () => {
46
+ it('layout has animation config when spec enables animation', () => {
47
+ const spec = { ...barSpec, animation: true } as ChartSpec;
48
+ const layout = compileChart(spec, { width: 600, height: 400 });
49
+ expect(layout.animation).toBeDefined();
50
+ expect(layout.animation!.enabled).toBe(true);
51
+ });
52
+
53
+ it('layout has no animation config when spec omits animation', () => {
54
+ const layout = compileChart(barSpec, { width: 600, height: 400 });
55
+ expect(layout.animation).toBeUndefined();
56
+ });
57
+ });
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // SVG mark structure supports CSS animation selectors
61
+ // ---------------------------------------------------------------------------
62
+
63
+ describe('SVG structure for animation CSS hooks', () => {
64
+ it('bar chart renders rect elements inside oc-mark groups', () => {
65
+ const spec = { ...barSpec, animation: true } as ChartSpec;
66
+ const { svg } = compileAndRender(spec);
67
+
68
+ // CSS animations target .oc-mark-rect rect and .oc-mark-bar rect
69
+ const rects = svg.querySelectorAll('.oc-marks rect');
70
+ expect(rects.length).toBeGreaterThan(0);
71
+ });
72
+
73
+ it('column chart renders rect elements for vertical bars', () => {
74
+ const spec = { ...columnSpec, animation: true } as ChartSpec;
75
+ const { svg } = compileAndRender(spec);
76
+
77
+ const rects = svg.querySelectorAll('.oc-marks rect');
78
+ expect(rects.length).toBeGreaterThan(0);
79
+ });
80
+
81
+ it('line chart renders path elements inside oc-mark-line groups', () => {
82
+ const spec = { ...lineSpec, animation: true } as ChartSpec;
83
+ const { svg } = compileAndRender(spec);
84
+
85
+ const paths = svg.querySelectorAll('.oc-mark-line path');
86
+ expect(paths.length).toBeGreaterThan(0);
87
+ });
88
+
89
+ it('mark groups have data-mark-id attributes for targeting', () => {
90
+ const spec = { ...barSpec, animation: true } as ChartSpec;
91
+ const { svg } = compileAndRender(spec);
92
+
93
+ const markGroups = svg.querySelectorAll('[data-mark-id]');
94
+ expect(markGroups.length).toBeGreaterThan(0);
95
+ });
96
+
97
+ it('SVG root has oc-chart class (oc-animate is added by mount, not renderer)', () => {
98
+ const spec = { ...barSpec, animation: true } as ChartSpec;
99
+ const { svg } = compileAndRender(spec);
100
+ expect(svg.getAttribute('class')).toContain('oc-chart');
101
+ // oc-animate is added by mount.ts on first render only, not by the renderer
102
+ expect(svg.getAttribute('class')).not.toContain('oc-animate');
103
+ });
104
+
105
+ it('SVG root does not have oc-animate class when animation is disabled', () => {
106
+ const { svg } = compileAndRender(barSpec);
107
+ expect(svg.getAttribute('class')).toContain('oc-chart');
108
+ expect(svg.getAttribute('class')).not.toContain('oc-animate');
109
+ });
110
+ });
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Animation CSS custom properties on SVG root
114
+ // ---------------------------------------------------------------------------
115
+
116
+ describe('animation CSS custom properties', () => {
117
+ it('sets --oc-animation-duration on SVG root', () => {
118
+ const spec = {
119
+ ...barSpec,
120
+ animation: { enter: { duration: 800 } },
121
+ } as ChartSpec;
122
+ const { svg } = compileAndRender(spec);
123
+ expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('800ms');
124
+ });
125
+
126
+ it('sets --oc-animation-stagger on SVG root', () => {
127
+ const spec = { ...barSpec, animation: true } as ChartSpec;
128
+ const { svg } = compileAndRender(spec);
129
+ const stagger = svg.style.getPropertyValue('--oc-animation-stagger');
130
+ expect(stagger).toMatch(/^\d+ms$/);
131
+ });
132
+
133
+ it('sets --oc-annotation-delay on SVG root', () => {
134
+ const spec = {
135
+ ...barSpec,
136
+ animation: { enter: true, annotationDelay: 500 },
137
+ } as ChartSpec;
138
+ const { svg } = compileAndRender(spec);
139
+ expect(svg.style.getPropertyValue('--oc-annotation-delay')).toBe('500ms');
140
+ });
141
+
142
+ it('does not set animation custom properties when animation is disabled', () => {
143
+ const { svg } = compileAndRender(barSpec);
144
+ expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('');
145
+ expect(svg.style.getPropertyValue('--oc-animation-stagger')).toBe('');
146
+ });
147
+ });
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // --oc-mark-index stamped on mark elements
151
+ // ---------------------------------------------------------------------------
152
+
153
+ describe('mark animation index attributes', () => {
154
+ it('stamps --oc-mark-index on rect mark groups', () => {
155
+ const spec = { ...columnSpec, animation: true } as ChartSpec;
156
+ const { svg } = compileAndRender(spec);
157
+
158
+ const markGroups = svg.querySelectorAll('.oc-mark-rect');
159
+ expect(markGroups.length).toBeGreaterThan(0);
160
+
161
+ for (const group of markGroups) {
162
+ const el = group as SVGElement & ElementCSSInlineStyle;
163
+ const markIndex = el.style.getPropertyValue('--oc-mark-index');
164
+ expect(markIndex).not.toBe('');
165
+ }
166
+ });
167
+
168
+ it('stamps data-animation-index attribute on mark groups', () => {
169
+ const spec = { ...columnSpec, animation: true } as ChartSpec;
170
+ const { svg } = compileAndRender(spec);
171
+
172
+ const markGroups = svg.querySelectorAll('[data-animation-index]');
173
+ expect(markGroups.length).toBeGreaterThan(0);
174
+
175
+ // Each should have a numeric index
176
+ for (const group of markGroups) {
177
+ const idx = group.getAttribute('data-animation-index');
178
+ expect(idx).toMatch(/^\d+$/);
179
+ }
180
+ });
181
+
182
+ it('does not stamp animation attributes when animation is disabled', () => {
183
+ const { svg } = compileAndRender(columnSpec);
184
+
185
+ const withAnimIdx = svg.querySelectorAll('[data-animation-index]');
186
+ expect(withAnimIdx.length).toBe(0);
187
+ });
188
+
189
+ it('stamps --oc-mark-index on line mark groups', () => {
190
+ const spec = { ...lineSpec, animation: true } as ChartSpec;
191
+ const { svg } = compileAndRender(spec);
192
+
193
+ const lineGroups = svg.querySelectorAll('.oc-mark-line');
194
+ expect(lineGroups.length).toBeGreaterThan(0);
195
+
196
+ for (const group of lineGroups) {
197
+ const el = group as SVGElement & ElementCSSInlineStyle;
198
+ const markIndex = el.style.getPropertyValue('--oc-mark-index');
199
+ expect(markIndex).not.toBe('');
200
+ }
201
+ });
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // data-orient attribute for horizontal bars
206
+ // ---------------------------------------------------------------------------
207
+
208
+ describe('data-orient for horizontal bars', () => {
209
+ it('sets data-orient=horizontal on mark groups for horizontal bar charts', () => {
210
+ // barSpec has x=quantitative, y=nominal which is a horizontal bar chart
211
+ const spec = { ...barSpec, animation: true } as ChartSpec;
212
+ const { svg } = compileAndRender(spec);
213
+
214
+ const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
215
+ expect(horizontalGroups.length).toBeGreaterThan(0);
216
+ });
217
+
218
+ it('does not set data-orient=horizontal on vertical column charts', () => {
219
+ // columnSpec has x=nominal, y=quantitative which is a vertical column chart
220
+ const spec = { ...columnSpec, animation: true } as ChartSpec;
221
+ const { svg } = compileAndRender(spec);
222
+
223
+ const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
224
+ expect(horizontalGroups.length).toBe(0);
225
+ });
226
+ });
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Value-based stagger ordering assigns animationIndex on marks
230
+ // ---------------------------------------------------------------------------
231
+
232
+ describe('animationIndex for value-based stagger', () => {
233
+ it('assigns animationIndex to rect marks when stagger order is value', () => {
234
+ const spec = {
235
+ ...columnSpec,
236
+ animation: {
237
+ enter: { stagger: { order: 'value' as const } },
238
+ },
239
+ } as ChartSpec;
240
+ const layout = compileChart(spec, { width: 600, height: 400 });
241
+
242
+ const rectMarks = layout.marks.filter((m) => m.type === 'rect');
243
+ expect(rectMarks.length).toBe(3);
244
+
245
+ // Each rect mark should have animationIndex
246
+ for (const mark of rectMarks) {
247
+ const idx = (mark as unknown as { animationIndex?: number }).animationIndex;
248
+ expect(idx).toBeDefined();
249
+ }
250
+ });
251
+
252
+ it('orders marks by their primary quantitative value', () => {
253
+ // columnSpec has revenues: Q1=100, Q2=200, Q3=150
254
+ // Value ordering should sort by bar height (revenue)
255
+ const spec = {
256
+ ...columnSpec,
257
+ animation: {
258
+ enter: { stagger: { order: 'value' as const } },
259
+ },
260
+ } as ChartSpec;
261
+ const layout = compileChart(spec, { width: 600, height: 400 });
262
+
263
+ const rectMarks = layout.marks.filter((m) => m.type === 'rect');
264
+ const indexed = rectMarks.map((m) => ({
265
+ index: (m as unknown as { animationIndex: number }).animationIndex,
266
+ }));
267
+
268
+ // All indices should be unique
269
+ const indices = indexed.map((m) => m.index);
270
+ const uniqueIndices = new Set(indices);
271
+ expect(uniqueIndices.size).toBe(rectMarks.length);
272
+ });
273
+ });
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // createChart mount lifecycle with animation
277
+ // ---------------------------------------------------------------------------
278
+
279
+ describe('createChart animation lifecycle', () => {
280
+ it('produces valid SVG on mount with animation enabled', () => {
281
+ const container = createContainer();
282
+ const spec = { ...barSpec, animation: true } as ChartSpec;
283
+ const chart = createChart(container, spec);
284
+
285
+ const svg = container.querySelector('svg');
286
+ expect(svg).not.toBeNull();
287
+ expect(svg?.getAttribute('class')).toContain('oc-chart');
288
+ expect(svg?.getAttribute('class')).toContain('oc-animate');
289
+
290
+ // Marks are rendered
291
+ const rects = container.querySelectorAll('.oc-marks rect');
292
+ expect(rects.length).toBeGreaterThan(0);
293
+
294
+ chart.destroy();
295
+ });
296
+
297
+ it('update re-renders chart correctly with animation', () => {
298
+ const container = createContainer();
299
+ const spec = { ...barSpec, animation: true } as ChartSpec;
300
+ const chart = createChart(container, spec);
301
+
302
+ // Verify initial render
303
+ let rects = container.querySelectorAll('.oc-marks rect');
304
+ const initialCount = rects.length;
305
+ expect(initialCount).toBeGreaterThan(0);
306
+
307
+ // Update with different data but same animation setting
308
+ const updatedSpec = {
309
+ ...barSpec,
310
+ data: [
311
+ { name: 'X', value: 50 },
312
+ { name: 'Y', value: 70 },
313
+ ],
314
+ animation: true,
315
+ } as ChartSpec;
316
+ chart.update(updatedSpec);
317
+
318
+ // SVG is re-rendered
319
+ const svg = container.querySelector('svg');
320
+ expect(svg).not.toBeNull();
321
+
322
+ // Marks reflect updated data
323
+ rects = container.querySelectorAll('.oc-marks rect');
324
+ expect(rects.length).toBeGreaterThan(0);
325
+
326
+ chart.destroy();
327
+ });
328
+
329
+ it('works correctly when animation is toggled off on update', () => {
330
+ const container = createContainer();
331
+ const specWithAnim = { ...barSpec, animation: true } as ChartSpec;
332
+ const chart = createChart(container, specWithAnim);
333
+
334
+ // Update with animation disabled
335
+ const specNoAnim = { ...barSpec, animation: false } as ChartSpec;
336
+ chart.update(specNoAnim);
337
+
338
+ // Chart should still render correctly
339
+ const svg = container.querySelector('svg');
340
+ expect(svg).not.toBeNull();
341
+ const rects = container.querySelectorAll('.oc-marks rect');
342
+ expect(rects.length).toBeGreaterThan(0);
343
+
344
+ chart.destroy();
345
+ });
346
+
347
+ it('destroy cleans up the container', () => {
348
+ const container = createContainer();
349
+ const spec = { ...barSpec, animation: true } as ChartSpec;
350
+ const chart = createChart(container, spec);
351
+
352
+ expect(container.querySelector('svg')).not.toBeNull();
353
+
354
+ chart.destroy();
355
+
356
+ expect(container.querySelector('svg')).toBeNull();
357
+ });
358
+ });
@@ -105,7 +105,7 @@ describe('edit events', () => {
105
105
  const onEdit = vi.fn();
106
106
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
107
107
 
108
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
108
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
109
109
  if (!annotation) {
110
110
  chart.destroy();
111
111
  return;
@@ -119,7 +119,7 @@ describe('edit events', () => {
119
119
  const onEdit = vi.fn();
120
120
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
121
121
 
122
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
122
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
123
123
  if (!annotation) {
124
124
  chart.destroy();
125
125
  return;
@@ -143,7 +143,7 @@ describe('edit events', () => {
143
143
  const onEdit = vi.fn();
144
144
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
145
145
 
146
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
146
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
147
147
  if (!annotation) {
148
148
  chart.destroy();
149
149
  return;
@@ -161,7 +161,7 @@ describe('edit events', () => {
161
161
  const onEdit = vi.fn();
162
162
  const chart = createChart(container, textAnnotatedSpec, { onAnnotationClick, onEdit });
163
163
 
164
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
164
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
165
165
  if (!annotation) {
166
166
  chart.destroy();
167
167
  return;
@@ -184,7 +184,7 @@ describe('edit events', () => {
184
184
  const onEdit = vi.fn();
185
185
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
186
186
 
187
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
187
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
188
188
  if (!annotation) {
189
189
  chart.destroy();
190
190
  return;
@@ -227,19 +227,19 @@ describe('edit events', () => {
227
227
  const chart = createChart(container, fullEditSpec, {});
228
228
 
229
229
  // Chrome elements should not have grab cursor
230
- const chromeTexts = container.querySelectorAll('.viz-chrome text[data-chrome-key]');
230
+ const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
231
231
  for (const el of chromeTexts) {
232
232
  expect((el as HTMLElement).style.cursor).not.toBe('grab');
233
233
  }
234
234
 
235
235
  // Legend should not have grab cursor
236
- const legendG = container.querySelector('.viz-legend') as SVGGElement | null;
236
+ const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
237
237
  if (legendG) {
238
238
  expect(legendG.style.cursor).not.toBe('grab');
239
239
  }
240
240
 
241
241
  // Series labels should not have grab cursor
242
- const seriesLabels = container.querySelectorAll('.viz-mark-label');
242
+ const seriesLabels = container.querySelectorAll('.oc-mark-label');
243
243
  for (const el of seriesLabels) {
244
244
  expect((el as HTMLElement).style.cursor).not.toBe('grab');
245
245
  }
@@ -252,19 +252,19 @@ describe('edit events', () => {
252
252
  const chart = createChart(container, fullEditSpec, { onEdit });
253
253
 
254
254
  // Chrome elements should have grab cursor
255
- const chromeTexts = container.querySelectorAll('.viz-chrome text[data-chrome-key]');
255
+ const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
256
256
  for (const el of chromeTexts) {
257
257
  expect((el as HTMLElement).style.cursor).toBe('grab');
258
258
  }
259
259
 
260
260
  // Legend should have grab cursor (if present)
261
- const legendG = container.querySelector('.viz-legend') as SVGGElement | null;
261
+ const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
262
262
  if (legendG) {
263
263
  expect(legendG.style.cursor).toBe('grab');
264
264
  }
265
265
 
266
266
  // Series labels should have grab cursor (if any rendered with data-series)
267
- const seriesLabels = container.querySelectorAll('.viz-mark-label[data-series]');
267
+ const seriesLabels = container.querySelectorAll('.oc-mark-label[data-series]');
268
268
  for (const el of seriesLabels) {
269
269
  expect((el as HTMLElement).style.cursor).toBe('grab');
270
270
  }
@@ -277,7 +277,7 @@ describe('edit events', () => {
277
277
  const onEdit = vi.fn();
278
278
  const chart = createChart(container, textAnnotatedSpec, { onAnnotationEdit, onEdit });
279
279
 
280
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
280
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
281
281
  if (!annotation) {
282
282
  chart.destroy();
283
283
  return;
@@ -311,7 +311,7 @@ describe('edit events', () => {
311
311
  const onEdit = vi.fn();
312
312
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
313
313
 
314
- const annotation = container.querySelector('.viz-annotation-text') as SVGGElement | null;
314
+ const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
315
315
  if (!annotation) {
316
316
  chart.destroy();
317
317
  return;
@@ -336,7 +336,7 @@ describe('edit events', () => {
336
336
  const chart = createChart(container, rangeAnnotatedSpec, { onEdit });
337
337
 
338
338
  const rangeLabel = container.querySelector(
339
- '.viz-annotation-range .viz-annotation-label',
339
+ '.oc-annotation-range .oc-annotation-label',
340
340
  ) as SVGTextElement | null;
341
341
  if (!rangeLabel) {
342
342
  // Range may not render a visible label in test env
@@ -353,7 +353,7 @@ describe('edit events', () => {
353
353
  const chart = createChart(container, rangeAnnotatedSpec, { onEdit });
354
354
 
355
355
  const rangeLabel = container.querySelector(
356
- '.viz-annotation-range .viz-annotation-label',
356
+ '.oc-annotation-range .oc-annotation-label',
357
357
  ) as SVGTextElement | null;
358
358
  if (!rangeLabel) {
359
359
  chart.destroy();
@@ -380,7 +380,7 @@ describe('edit events', () => {
380
380
  const chart = createChart(container, reflineAnnotatedSpec, { onEdit });
381
381
 
382
382
  const reflineLabel = container.querySelector(
383
- '.viz-annotation-refline .viz-annotation-label',
383
+ '.oc-annotation-refline .oc-annotation-label',
384
384
  ) as SVGTextElement | null;
385
385
  if (!reflineLabel) {
386
386
  chart.destroy();
@@ -396,7 +396,7 @@ describe('edit events', () => {
396
396
  const chart = createChart(container, reflineAnnotatedSpec, { onEdit });
397
397
 
398
398
  const reflineLabel = container.querySelector(
399
- '.viz-annotation-refline .viz-annotation-label',
399
+ '.oc-annotation-refline .oc-annotation-label',
400
400
  ) as SVGTextElement | null;
401
401
  if (!reflineLabel) {
402
402
  chart.destroy();
@@ -427,7 +427,7 @@ describe('edit events', () => {
427
427
  const onEdit = vi.fn();
428
428
  const chart = createChart(container, fullEditSpec, { onEdit });
429
429
 
430
- const chromeTexts = container.querySelectorAll('.viz-chrome text[data-chrome-key]');
430
+ const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
431
431
  expect(chromeTexts.length).toBeGreaterThan(0);
432
432
 
433
433
  for (const el of chromeTexts) {
@@ -442,7 +442,7 @@ describe('edit events', () => {
442
442
  const chart = createChart(container, fullEditSpec, { onEdit });
443
443
 
444
444
  const titleEl = container.querySelector(
445
- '.viz-chrome text[data-chrome-key="title"]',
445
+ '.oc-chrome text[data-chrome-key="title"]',
446
446
  ) as SVGTextElement | null;
447
447
  if (!titleEl) {
448
448
  chart.destroy();
@@ -474,7 +474,7 @@ describe('edit events', () => {
474
474
  const onEdit = vi.fn();
475
475
  const chart = createChart(container, fullEditSpec, { onEdit });
476
476
 
477
- const legendG = container.querySelector('.viz-legend') as SVGGElement | null;
477
+ const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
478
478
  if (!legendG) {
479
479
  chart.destroy();
480
480
  return;
@@ -488,7 +488,7 @@ describe('edit events', () => {
488
488
  const onEdit = vi.fn();
489
489
  const chart = createChart(container, fullEditSpec, { onEdit });
490
490
 
491
- const legendG = container.querySelector('.viz-legend') as SVGGElement | null;
491
+ const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
492
492
  if (!legendG) {
493
493
  chart.destroy();
494
494
  return;
@@ -512,7 +512,7 @@ describe('edit events', () => {
512
512
  const onEdit = vi.fn();
513
513
  const chart = createChart(container, fullEditSpec, { onLegendToggle, onEdit });
514
514
 
515
- const legendG = container.querySelector('.viz-legend') as SVGGElement | null;
515
+ const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
516
516
  if (!legendG) {
517
517
  chart.destroy();
518
518
  return;
@@ -540,16 +540,16 @@ describe('edit events', () => {
540
540
  const onEdit = vi.fn();
541
541
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
542
542
 
543
- const annotationG = container.querySelector('.viz-annotation-text') as SVGGElement | null;
543
+ const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
544
544
  if (!annotationG) {
545
545
  chart.destroy();
546
546
  return;
547
547
  }
548
548
 
549
549
  // Check that connector handles exist within the annotation group
550
- const handles = annotationG.querySelectorAll('.viz-connector-handle');
550
+ const handles = annotationG.querySelectorAll('.oc-connector-handle');
551
551
  // There should be 2 handles (from and to) if a connector is present
552
- const connector = annotationG.querySelector('.viz-annotation-connector');
552
+ const connector = annotationG.querySelector('.oc-annotation-connector');
553
553
  if (connector) {
554
554
  expect(handles.length).toBe(2);
555
555
  // Verify they have the correct data-endpoint attributes
@@ -565,13 +565,13 @@ describe('edit events', () => {
565
565
  const onEdit = vi.fn();
566
566
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
567
567
 
568
- const annotationG = container.querySelector('.viz-annotation-text') as SVGGElement | null;
568
+ const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
569
569
  if (!annotationG) {
570
570
  chart.destroy();
571
571
  return;
572
572
  }
573
573
 
574
- const handles = annotationG.querySelectorAll('.viz-connector-handle');
574
+ const handles = annotationG.querySelectorAll('.oc-connector-handle');
575
575
  if (handles.length === 0) {
576
576
  chart.destroy();
577
577
  return;
@@ -597,13 +597,13 @@ describe('edit events', () => {
597
597
  const onEdit = vi.fn();
598
598
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
599
599
 
600
- const annotationG = container.querySelector('.viz-annotation-text') as SVGGElement | null;
600
+ const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
601
601
  if (!annotationG) {
602
602
  chart.destroy();
603
603
  return;
604
604
  }
605
605
 
606
- const handles = annotationG.querySelectorAll('.viz-connector-handle');
606
+ const handles = annotationG.querySelectorAll('.oc-connector-handle');
607
607
  if (handles.length === 0) {
608
608
  chart.destroy();
609
609
  return;
@@ -625,7 +625,7 @@ describe('edit events', () => {
625
625
  const onEdit = vi.fn();
626
626
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
627
627
 
628
- const annotationG = container.querySelector('.viz-annotation-text') as SVGGElement | null;
628
+ const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
629
629
  if (!annotationG) {
630
630
  chart.destroy();
631
631
  return;
@@ -633,7 +633,7 @@ describe('edit events', () => {
633
633
 
634
634
  // Find the "from" handle
635
635
  const fromHandle = annotationG.querySelector(
636
- '.viz-connector-handle[data-endpoint="from"]',
636
+ '.oc-connector-handle[data-endpoint="from"]',
637
637
  ) as SVGCircleElement | null;
638
638
 
639
639
  if (!fromHandle) {
@@ -661,14 +661,14 @@ describe('edit events', () => {
661
661
  const onEdit = vi.fn();
662
662
  const chart = createChart(container, textAnnotatedSpec, { onEdit });
663
663
 
664
- const annotationG = container.querySelector('.viz-annotation-text') as SVGGElement | null;
664
+ const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
665
665
  if (!annotationG) {
666
666
  chart.destroy();
667
667
  return;
668
668
  }
669
669
 
670
670
  const toHandle = annotationG.querySelector(
671
- '.viz-connector-handle[data-endpoint="to"]',
671
+ '.oc-connector-handle[data-endpoint="to"]',
672
672
  ) as SVGCircleElement | null;
673
673
 
674
674
  if (!toHandle) {
@@ -697,7 +697,7 @@ describe('edit events', () => {
697
697
  const onEdit = vi.fn();
698
698
  const chart = createChart(container, fullEditSpec, { onEdit });
699
699
 
700
- const seriesLabels = container.querySelectorAll('.viz-mark-label[data-series]');
700
+ const seriesLabels = container.querySelectorAll('.oc-mark-label[data-series]');
701
701
  if (seriesLabels.length === 0) {
702
702
  // Labels may not render in the test env depending on layout
703
703
  chart.destroy();
@@ -716,7 +716,7 @@ describe('edit events', () => {
716
716
  const chart = createChart(container, fullEditSpec, { onEdit });
717
717
 
718
718
  const seriesLabel = container.querySelector(
719
- '.viz-mark-label[data-series]',
719
+ '.oc-mark-label[data-series]',
720
720
  ) as SVGTextElement | null;
721
721
  if (!seriesLabel) {
722
722
  chart.destroy();