@opendata-ai/openchart-core 8.0.0-rc.9 → 8.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/README.md +1 -1
- package/dist/index.d.ts +403 -45
- package/dist/index.js +50 -18
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -2
- package/schema/chart.schema.json +375 -69
- package/schema/table.schema.json +35 -1
- package/schema/vizspec.schema.json +992 -171
- package/src/accessibility/__tests__/alt-text.test.ts +0 -110
- package/src/accessibility/__tests__/aria.test.ts +0 -125
- package/src/accessibility/alt-text.ts +0 -155
- package/src/accessibility/aria.ts +0 -75
- package/src/accessibility/index.ts +0 -6
- package/src/colors/__tests__/colorblind.test.ts +0 -75
- package/src/colors/__tests__/contrast.test.ts +0 -119
- package/src/colors/__tests__/palettes.test.ts +0 -74
- package/src/colors/colorblind.ts +0 -131
- package/src/colors/contrast.ts +0 -143
- package/src/colors/index.ts +0 -36
- package/src/colors/palettes.ts +0 -206
- package/src/helpers/__tests__/deep-merge.test.ts +0 -54
- package/src/helpers/__tests__/spec-builders.test.ts +0 -338
- package/src/helpers/deep-merge.ts +0 -29
- package/src/helpers/spec-builders.ts +0 -479
- package/src/index.ts +0 -215
- package/src/labels/__tests__/collision.test.ts +0 -310
- package/src/labels/collision.ts +0 -249
- package/src/labels/index.ts +0 -13
- package/src/layout/__tests__/chrome.test.ts +0 -208
- package/src/layout/__tests__/text-measure.test.ts +0 -57
- package/src/layout/chrome.ts +0 -470
- package/src/layout/index.ts +0 -18
- package/src/layout/text-measure.ts +0 -404
- package/src/layout/text-wrap.ts +0 -93
- package/src/locale/__tests__/format.test.ts +0 -606
- package/src/locale/format.ts +0 -459
- package/src/locale/index.ts +0 -21
- package/src/responsive/__tests__/breakpoints.test.ts +0 -146
- package/src/responsive/__tests__/metrics.test.ts +0 -52
- package/src/responsive/breakpoints.ts +0 -177
- package/src/responsive/index.ts +0 -53
- package/src/responsive/metrics.ts +0 -299
- package/src/schema/__tests__/generated-artifacts.test.ts +0 -42
- package/src/schema/__tests__/schema.test.ts +0 -179
- package/src/styles/__tests__/reduced-motion-selectors.test.ts +0 -28
- package/src/styles/animation.css +0 -213
- package/src/styles/base.css +0 -102
- package/src/styles/chrome.css +0 -174
- package/src/styles/dark.css +0 -44
- package/src/styles/graph.css +0 -135
- package/src/styles/index.css +0 -28
- package/src/styles/keyframes.css +0 -151
- package/src/styles/legend.css +0 -18
- package/src/styles/reduced-motion.css +0 -67
- package/src/styles/series-search.css +0 -153
- package/src/styles/sparkline.css +0 -16
- package/src/styles/table-animation.css +0 -93
- package/src/styles/table.css +0 -398
- package/src/styles/token-definitions.ts +0 -199
- package/src/styles/tokens.css +0 -156
- package/src/styles/tooltip.css +0 -126
- package/src/styles/you-draw-it.css +0 -126
- package/src/theme/__tests__/dark-mode.test.ts +0 -232
- package/src/theme/__tests__/defaults.test.ts +0 -73
- package/src/theme/__tests__/resolve.test.ts +0 -277
- package/src/theme/dark-mode.ts +0 -258
- package/src/theme/defaults.ts +0 -110
- package/src/theme/index.ts +0 -8
- package/src/theme/presets.ts +0 -64
- package/src/theme/resolve.ts +0 -312
- package/src/types/__tests__/encoding.test.ts +0 -368
- package/src/types/__tests__/gradient.test.ts +0 -95
- package/src/types/__tests__/spec.test.ts +0 -499
- package/src/types/encoding.ts +0 -280
- package/src/types/events.ts +0 -236
- package/src/types/index.ts +0 -290
- package/src/types/layout.ts +0 -2024
- package/src/types/schema-roots.ts +0 -32
- package/src/types/spec.ts +0 -2925
- package/src/types/table.ts +0 -108
- package/src/types/theme.ts +0 -209
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
Annotation,
|
|
4
|
-
ChartSpec,
|
|
5
|
-
GraphSpec,
|
|
6
|
-
RangeAnnotation,
|
|
7
|
-
TableSpec,
|
|
8
|
-
VizSpec,
|
|
9
|
-
} from '../spec';
|
|
10
|
-
import {
|
|
11
|
-
isChartSpec,
|
|
12
|
-
isGraphSpec,
|
|
13
|
-
isRangeAnnotation,
|
|
14
|
-
isRefLineAnnotation,
|
|
15
|
-
isTableSpec,
|
|
16
|
-
isTextAnnotation,
|
|
17
|
-
MARK_TYPES,
|
|
18
|
-
} from '../spec';
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Test data factories
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
function makeChartSpec(): ChartSpec {
|
|
25
|
-
return {
|
|
26
|
-
mark: 'line',
|
|
27
|
-
data: [
|
|
28
|
-
{ date: '2020-01', value: 42 },
|
|
29
|
-
{ date: '2020-02', value: 45 },
|
|
30
|
-
],
|
|
31
|
-
encoding: {
|
|
32
|
-
x: { field: 'date', type: 'temporal' },
|
|
33
|
-
y: { field: 'value', type: 'quantitative' },
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function makeTableSpec(overrides?: Partial<TableSpec>): TableSpec {
|
|
39
|
-
return {
|
|
40
|
-
type: 'table',
|
|
41
|
-
data: [
|
|
42
|
-
{ name: 'US', gdp: 21000 },
|
|
43
|
-
{ name: 'China', gdp: 14700 },
|
|
44
|
-
],
|
|
45
|
-
columns: [
|
|
46
|
-
{ key: 'name', label: 'Country' },
|
|
47
|
-
{ key: 'gdp', label: 'GDP (B$)', format: ',.0f' },
|
|
48
|
-
],
|
|
49
|
-
...overrides,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function makeGraphSpec(overrides?: Partial<GraphSpec>): GraphSpec {
|
|
54
|
-
return {
|
|
55
|
-
type: 'graph',
|
|
56
|
-
nodes: [
|
|
57
|
-
{ id: 'a', label: 'Node A' },
|
|
58
|
-
{ id: 'b', label: 'Node B' },
|
|
59
|
-
],
|
|
60
|
-
edges: [{ source: 'a', target: 'b', weight: 1 }],
|
|
61
|
-
...overrides,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Spec type guard tests
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
describe('isChartSpec', () => {
|
|
70
|
-
it('returns true for all mark types', () => {
|
|
71
|
-
// isChartSpec is a runtime guard that only checks for the presence of `mark`.
|
|
72
|
-
// Use `as ChartSpec` here since we're testing the guard, not encoding validity.
|
|
73
|
-
for (const markType of MARK_TYPES) {
|
|
74
|
-
const spec = { mark: markType, data: [], encoding: {} } as ChartSpec;
|
|
75
|
-
expect(isChartSpec(spec)).toBe(true);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('returns false for table specs', () => {
|
|
80
|
-
const spec: VizSpec = makeTableSpec();
|
|
81
|
-
expect(isChartSpec(spec)).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns false for graph specs', () => {
|
|
85
|
-
const spec: VizSpec = makeGraphSpec();
|
|
86
|
-
expect(isChartSpec(spec)).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('isTableSpec', () => {
|
|
91
|
-
it('returns true for table specs', () => {
|
|
92
|
-
const spec: VizSpec = makeTableSpec();
|
|
93
|
-
expect(isTableSpec(spec)).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('returns false for chart specs', () => {
|
|
97
|
-
const spec: VizSpec = makeChartSpec();
|
|
98
|
-
expect(isTableSpec(spec)).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('returns false for graph specs', () => {
|
|
102
|
-
const spec: VizSpec = makeGraphSpec();
|
|
103
|
-
expect(isTableSpec(spec)).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('isGraphSpec', () => {
|
|
108
|
-
it('returns true for graph specs', () => {
|
|
109
|
-
const spec: VizSpec = makeGraphSpec();
|
|
110
|
-
expect(isGraphSpec(spec)).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('returns false for chart specs', () => {
|
|
114
|
-
const spec: VizSpec = makeChartSpec();
|
|
115
|
-
expect(isGraphSpec(spec)).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('returns false for table specs', () => {
|
|
119
|
-
const spec: VizSpec = makeTableSpec();
|
|
120
|
-
expect(isGraphSpec(spec)).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('type guard mutual exclusivity', () => {
|
|
125
|
-
it('exactly one guard returns true for each spec type', () => {
|
|
126
|
-
const specs: VizSpec[] = [makeChartSpec(), makeTableSpec(), makeGraphSpec()];
|
|
127
|
-
|
|
128
|
-
for (const spec of specs) {
|
|
129
|
-
const guards = [isChartSpec(spec), isTableSpec(spec), isGraphSpec(spec)];
|
|
130
|
-
const trueCount = guards.filter(Boolean).length;
|
|
131
|
-
expect(trueCount).toBe(1);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// MARK_TYPES constant tests
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
describe('MARK_TYPES', () => {
|
|
141
|
-
it('contains all 16 mark types', () => {
|
|
142
|
-
expect(MARK_TYPES.size).toBe(16);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('contains expected types', () => {
|
|
146
|
-
const expected = [
|
|
147
|
-
'line',
|
|
148
|
-
'area',
|
|
149
|
-
'bar',
|
|
150
|
-
'point',
|
|
151
|
-
'circle',
|
|
152
|
-
'arc',
|
|
153
|
-
'text',
|
|
154
|
-
'rule',
|
|
155
|
-
'tick',
|
|
156
|
-
'rect',
|
|
157
|
-
'lollipop',
|
|
158
|
-
'beeswarm',
|
|
159
|
-
'range',
|
|
160
|
-
'waffle',
|
|
161
|
-
'calendar',
|
|
162
|
-
'parliament',
|
|
163
|
-
];
|
|
164
|
-
for (const t of expected) {
|
|
165
|
-
expect(MARK_TYPES.has(t)).toBe(true);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('does not contain non-mark types', () => {
|
|
170
|
-
expect(MARK_TYPES.has('table')).toBe(false);
|
|
171
|
-
expect(MARK_TYPES.has('graph')).toBe(false);
|
|
172
|
-
expect(MARK_TYPES.has('map')).toBe(false);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
// Annotation type guard tests
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
describe('isTextAnnotation', () => {
|
|
181
|
-
it('returns true for text annotations', () => {
|
|
182
|
-
const annotation: Annotation = {
|
|
183
|
-
type: 'text',
|
|
184
|
-
x: '2020-06',
|
|
185
|
-
y: 42,
|
|
186
|
-
text: 'Peak value',
|
|
187
|
-
};
|
|
188
|
-
expect(isTextAnnotation(annotation)).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('returns false for range annotations', () => {
|
|
192
|
-
const annotation: Annotation = {
|
|
193
|
-
type: 'range',
|
|
194
|
-
x1: '2020-03',
|
|
195
|
-
x2: '2020-09',
|
|
196
|
-
label: 'Recession',
|
|
197
|
-
};
|
|
198
|
-
expect(isTextAnnotation(annotation)).toBe(false);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('returns false for refline annotations', () => {
|
|
202
|
-
const annotation: Annotation = {
|
|
203
|
-
type: 'refline',
|
|
204
|
-
y: 0,
|
|
205
|
-
label: 'Zero',
|
|
206
|
-
};
|
|
207
|
-
expect(isTextAnnotation(annotation)).toBe(false);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe('isRangeAnnotation', () => {
|
|
212
|
-
it('returns true for range annotations', () => {
|
|
213
|
-
const annotation: Annotation = {
|
|
214
|
-
type: 'range',
|
|
215
|
-
x1: '2020-03',
|
|
216
|
-
x2: '2020-09',
|
|
217
|
-
fill: '#fee2e2',
|
|
218
|
-
};
|
|
219
|
-
expect(isRangeAnnotation(annotation)).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('returns false for text annotations', () => {
|
|
223
|
-
const annotation: Annotation = {
|
|
224
|
-
type: 'text',
|
|
225
|
-
x: 10,
|
|
226
|
-
y: 20,
|
|
227
|
-
text: 'Hello',
|
|
228
|
-
};
|
|
229
|
-
expect(isRangeAnnotation(annotation)).toBe(false);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('isRefLineAnnotation', () => {
|
|
234
|
-
it('returns true for refline annotations', () => {
|
|
235
|
-
const annotation: Annotation = {
|
|
236
|
-
type: 'refline',
|
|
237
|
-
y: 0,
|
|
238
|
-
label: 'Baseline',
|
|
239
|
-
style: 'dashed',
|
|
240
|
-
};
|
|
241
|
-
expect(isRefLineAnnotation(annotation)).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('returns false for text annotations', () => {
|
|
245
|
-
const annotation: Annotation = {
|
|
246
|
-
type: 'text',
|
|
247
|
-
x: 10,
|
|
248
|
-
y: 20,
|
|
249
|
-
text: 'Hello',
|
|
250
|
-
};
|
|
251
|
-
expect(isRefLineAnnotation(annotation)).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('returns false for range annotations', () => {
|
|
255
|
-
const annotation: RangeAnnotation = {
|
|
256
|
-
type: 'range',
|
|
257
|
-
y1: 0,
|
|
258
|
-
y2: 100,
|
|
259
|
-
};
|
|
260
|
-
expect(isRefLineAnnotation(annotation)).toBe(false);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
describe('annotation type guard mutual exclusivity', () => {
|
|
265
|
-
it('exactly one annotation guard returns true for each annotation type', () => {
|
|
266
|
-
const annotations: Annotation[] = [
|
|
267
|
-
{ type: 'text', x: 0, y: 0, text: 'test' },
|
|
268
|
-
{ type: 'range', x1: 0, x2: 10 },
|
|
269
|
-
{ type: 'refline', y: 0 },
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
for (const annotation of annotations) {
|
|
273
|
-
const guards = [
|
|
274
|
-
isTextAnnotation(annotation),
|
|
275
|
-
isRangeAnnotation(annotation),
|
|
276
|
-
isRefLineAnnotation(annotation),
|
|
277
|
-
];
|
|
278
|
-
const trueCount = guards.filter(Boolean).length;
|
|
279
|
-
expect(trueCount).toBe(1);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
// Type-level tests (compile-time verification)
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
|
|
288
|
-
describe('type-level spec construction', () => {
|
|
289
|
-
it('allows a fully featured chart spec', () => {
|
|
290
|
-
const spec: ChartSpec = {
|
|
291
|
-
mark: 'line',
|
|
292
|
-
data: [{ date: '2020-01', value: 42, series: 'US' }],
|
|
293
|
-
encoding: {
|
|
294
|
-
x: { field: 'date', type: 'temporal' },
|
|
295
|
-
y: {
|
|
296
|
-
field: 'value',
|
|
297
|
-
type: 'quantitative',
|
|
298
|
-
axis: { title: 'GDP Growth (%)' },
|
|
299
|
-
scale: { zero: true, nice: true },
|
|
300
|
-
},
|
|
301
|
-
color: { field: 'series', type: 'nominal' },
|
|
302
|
-
},
|
|
303
|
-
chrome: {
|
|
304
|
-
title: 'GDP Growth Rate',
|
|
305
|
-
subtitle: { text: 'Quarterly, seasonally adjusted', style: { fontSize: 14 } },
|
|
306
|
-
source: 'World Bank',
|
|
307
|
-
byline: 'OpenData',
|
|
308
|
-
footer: 'Last updated: Jan 2024',
|
|
309
|
-
},
|
|
310
|
-
annotations: [
|
|
311
|
-
{
|
|
312
|
-
type: 'range',
|
|
313
|
-
x1: '2020-03',
|
|
314
|
-
x2: '2020-09',
|
|
315
|
-
label: 'COVID-19',
|
|
316
|
-
fill: '#fee2e2',
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
type: 'text',
|
|
320
|
-
x: '2021-06',
|
|
321
|
-
y: 45,
|
|
322
|
-
text: 'Recovery begins',
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
type: 'refline',
|
|
326
|
-
y: 0,
|
|
327
|
-
label: 'Zero growth',
|
|
328
|
-
style: 'dashed',
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
responsive: true,
|
|
332
|
-
theme: {
|
|
333
|
-
colors: { categorical: ['#2563eb', '#dc2626'] },
|
|
334
|
-
fonts: { family: 'Inter' },
|
|
335
|
-
},
|
|
336
|
-
darkMode: 'auto',
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
// If this compiles and type-checks, the types are correct.
|
|
340
|
-
expect(spec.mark).toBe('line');
|
|
341
|
-
expect(spec.data).toHaveLength(1);
|
|
342
|
-
expect(spec.encoding.x?.field).toBe('date');
|
|
343
|
-
expect(spec.annotations).toHaveLength(3);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('allows a chart spec with mark object', () => {
|
|
347
|
-
const spec: ChartSpec = {
|
|
348
|
-
mark: { type: 'line', interpolate: 'step', point: true },
|
|
349
|
-
data: [{ x: 1, y: 2 }],
|
|
350
|
-
encoding: {
|
|
351
|
-
x: { field: 'x', type: 'quantitative' },
|
|
352
|
-
y: { field: 'y', type: 'quantitative' },
|
|
353
|
-
},
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
expect(typeof spec.mark).toBe('object');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('allows a table spec with column configs', () => {
|
|
360
|
-
const spec: TableSpec = {
|
|
361
|
-
type: 'table',
|
|
362
|
-
data: [{ country: 'US', gdp: 21000, trend: [20000, 20500, 21000] }],
|
|
363
|
-
columns: [
|
|
364
|
-
{ key: 'country', label: 'Country', sortable: true },
|
|
365
|
-
{ key: 'gdp', label: 'GDP (B$)', format: ',.0f', align: 'right' },
|
|
366
|
-
{
|
|
367
|
-
key: 'trend',
|
|
368
|
-
label: 'Trend',
|
|
369
|
-
sparkline: { type: 'line', valuesField: 'trend', color: '#2563eb' },
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
chrome: { title: 'GDP by Country' },
|
|
373
|
-
search: true,
|
|
374
|
-
pagination: { pageSize: 25 },
|
|
375
|
-
stickyFirstColumn: true,
|
|
376
|
-
compact: false,
|
|
377
|
-
darkMode: 'off',
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
expect(spec.type).toBe('table');
|
|
381
|
-
expect(spec.columns).toHaveLength(3);
|
|
382
|
-
expect(spec.search).toBe(true);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('allows a graph spec with encoding and layout', () => {
|
|
386
|
-
const spec: GraphSpec = {
|
|
387
|
-
type: 'graph',
|
|
388
|
-
nodes: [
|
|
389
|
-
{ id: 'a', name: 'Alice', dept: 'Engineering' },
|
|
390
|
-
{ id: 'b', name: 'Bob', dept: 'Design' },
|
|
391
|
-
],
|
|
392
|
-
edges: [{ source: 'a', target: 'b', weight: 5, type: 'collaboration' }],
|
|
393
|
-
encoding: {
|
|
394
|
-
nodeColor: { field: 'dept', type: 'nominal' },
|
|
395
|
-
nodeLabel: { field: 'name' },
|
|
396
|
-
edgeWidth: { field: 'weight', type: 'quantitative' },
|
|
397
|
-
},
|
|
398
|
-
layout: {
|
|
399
|
-
type: 'force',
|
|
400
|
-
clustering: { field: 'dept' },
|
|
401
|
-
chargeStrength: -100,
|
|
402
|
-
linkDistance: 50,
|
|
403
|
-
},
|
|
404
|
-
chrome: {
|
|
405
|
-
title: 'Team Collaboration Network',
|
|
406
|
-
source: 'HR Data',
|
|
407
|
-
},
|
|
408
|
-
darkMode: 'auto',
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
expect(spec.type).toBe('graph');
|
|
412
|
-
expect(spec.nodes).toHaveLength(2);
|
|
413
|
-
expect(spec.edges).toHaveLength(1);
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
// ---------------------------------------------------------------------------
|
|
418
|
-
// Negative type tests (compile-time rejection verification)
|
|
419
|
-
//
|
|
420
|
-
// Each variable below is annotated with @ts-expect-error on the line that
|
|
421
|
-
// should be rejected. If any @ts-expect-error becomes "unused" (no error),
|
|
422
|
-
// TypeScript will fail the build — meaning the type guarantee regressed.
|
|
423
|
-
// ---------------------------------------------------------------------------
|
|
424
|
-
|
|
425
|
-
describe('type-level rejection', () => {
|
|
426
|
-
it('compiles with @ts-expect-error annotations intact (runtime no-op)', () => {
|
|
427
|
-
// Arc requires theta + color. Missing color must error.
|
|
428
|
-
const _arcMissingColor: ChartSpec = {
|
|
429
|
-
mark: 'arc',
|
|
430
|
-
data: [],
|
|
431
|
-
// @ts-expect-error ArcEncoding requires color channel
|
|
432
|
-
encoding: {
|
|
433
|
-
theta: { field: 'value', type: 'quantitative' },
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
// Text mark requires text channel. Missing text must error.
|
|
438
|
-
const _textMissingText: ChartSpec = {
|
|
439
|
-
mark: 'text',
|
|
440
|
-
data: [],
|
|
441
|
-
// @ts-expect-error TextEncoding requires text channel
|
|
442
|
-
encoding: {},
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
// Typed spec: field typo must error when TData is provided.
|
|
446
|
-
type SalesRow = { date: string; revenue: number };
|
|
447
|
-
const _typoField: ChartSpec<SalesRow> = {
|
|
448
|
-
mark: 'line',
|
|
449
|
-
data: [],
|
|
450
|
-
encoding: {
|
|
451
|
-
// @ts-expect-error 'dat' is not a key of SalesRow — did you mean 'date'?
|
|
452
|
-
x: { field: 'dat', type: 'temporal' },
|
|
453
|
-
y: { field: 'revenue', type: 'quantitative' },
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
// Valid typed spec must compile without errors.
|
|
458
|
-
const _validTyped: ChartSpec<SalesRow> = {
|
|
459
|
-
mark: 'line',
|
|
460
|
-
data: [{ date: '2024-01', revenue: 100 }],
|
|
461
|
-
encoding: {
|
|
462
|
-
x: { field: 'date', type: 'temporal' },
|
|
463
|
-
y: { field: 'revenue', type: 'quantitative' },
|
|
464
|
-
},
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
// Arc with only the deprecated y alias (no theta) must error: ArcEncoding
|
|
468
|
-
// requires theta, the canonical value channel in v8. y is still accepted
|
|
469
|
-
// at runtime (spec-sugar expands it into theta with a warning), but the
|
|
470
|
-
// type no longer allows authoring it directly.
|
|
471
|
-
const _arcDeprecatedYOnly: ChartSpec = {
|
|
472
|
-
mark: 'arc',
|
|
473
|
-
data: [],
|
|
474
|
-
// @ts-expect-error ArcEncoding requires theta; y alone is not enough
|
|
475
|
-
encoding: {
|
|
476
|
-
y: { field: 'value', type: 'quantitative' },
|
|
477
|
-
color: { field: 'category', type: 'nominal' },
|
|
478
|
-
},
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
// Untyped spec (no TData param) must compile — no migration cost.
|
|
482
|
-
const _untypedSpec: ChartSpec = {
|
|
483
|
-
mark: 'line',
|
|
484
|
-
data: [],
|
|
485
|
-
encoding: {
|
|
486
|
-
x: { field: 'anything', type: 'temporal' },
|
|
487
|
-
y: { field: 'anything', type: 'quantitative' },
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
void _arcMissingColor;
|
|
492
|
-
void _textMissingText;
|
|
493
|
-
void _typoField;
|
|
494
|
-
void _validTyped;
|
|
495
|
-
void _arcDeprecatedYOnly;
|
|
496
|
-
void _untypedSpec;
|
|
497
|
-
expect(true).toBe(true);
|
|
498
|
-
});
|
|
499
|
-
});
|