@opendata-ai/openchart-engine 8.0.0-rc.12 → 8.0.0-rc.3

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.
Files changed (271) hide show
  1. package/dist/index.d.ts +0 -7
  2. package/dist/index.js +161 -579
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -3
  5. package/src/__test-fixtures__/specs.ts +182 -0
  6. package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +2339 -0
  7. package/src/__tests__/annotation-behavior.test.ts +721 -0
  8. package/src/__tests__/axes.test.ts +1579 -0
  9. package/src/__tests__/compile-animation.test.ts +161 -0
  10. package/src/__tests__/compile-chart.test.ts +1303 -0
  11. package/src/__tests__/compile-footnotes.test.ts +277 -0
  12. package/src/__tests__/compile-layer.test.ts +969 -0
  13. package/src/__tests__/compile-snapshot.test.ts +156 -0
  14. package/src/__tests__/compound-labels.test.ts +147 -0
  15. package/src/__tests__/dimensions.test.ts +918 -0
  16. package/src/__tests__/encoding-sugar.test.ts +390 -0
  17. package/src/__tests__/facet.test.ts +215 -0
  18. package/src/__tests__/helpers/invariants.ts +299 -0
  19. package/src/__tests__/known-bugs.test.ts +566 -0
  20. package/src/__tests__/layout-fuzz.test.ts +358 -0
  21. package/src/__tests__/layout-invariants.test.ts +535 -0
  22. package/src/__tests__/legend.test.ts +721 -0
  23. package/src/__tests__/mark-keys.test.ts +294 -0
  24. package/src/__tests__/ranking-recipes.test.ts +260 -0
  25. package/src/__tests__/rotation.test.ts +83 -0
  26. package/src/__tests__/scales.test.ts +323 -0
  27. package/src/__tests__/series-search.test.ts +92 -0
  28. package/src/__tests__/spec-sugar.test.ts +801 -0
  29. package/src/__tests__/you-draw-it.test.ts +160 -0
  30. package/src/annotations/__tests__/compute.test.ts +1672 -0
  31. package/src/annotations/__tests__/geometry.test.ts +66 -0
  32. package/src/annotations/__tests__/placement-fuzz.test.ts +256 -0
  33. package/src/annotations/__tests__/placement-invariants.test.ts +345 -0
  34. package/src/annotations/__tests__/placement.test.ts +316 -0
  35. package/src/annotations/__tests__/resolve-text.test.ts +788 -0
  36. package/src/annotations/__tests__/rich-text.test.ts +91 -0
  37. package/src/annotations/__tests__/thinning.test.ts +285 -0
  38. package/src/annotations/collisions.ts +326 -0
  39. package/src/annotations/compute.ts +333 -0
  40. package/src/annotations/constants.ts +162 -0
  41. package/src/annotations/geometry.ts +370 -0
  42. package/src/annotations/move.ts +135 -0
  43. package/src/annotations/placement.ts +636 -0
  44. package/src/annotations/position.ts +131 -0
  45. package/src/annotations/resolve-range.ts +117 -0
  46. package/src/annotations/resolve-refline.ts +157 -0
  47. package/src/annotations/resolve-text.ts +539 -0
  48. package/src/annotations/rich-text.ts +65 -0
  49. package/src/annotations/thinning.ts +190 -0
  50. package/src/barlist/__tests__/compile-barlist.test.ts +200 -0
  51. package/src/barlist/compile-barlist.ts +391 -0
  52. package/src/barlist/types.ts +28 -0
  53. package/src/charts/__tests__/post-process.test.ts +237 -0
  54. package/src/charts/__tests__/registry.test.ts +116 -0
  55. package/src/charts/__tests__/utils.test.ts +195 -0
  56. package/src/charts/_shared/__tests__/density-filter.test.ts +32 -0
  57. package/src/charts/_shared/density-filter.ts +26 -0
  58. package/src/charts/_shared/format-label-value.ts +16 -0
  59. package/src/charts/bar/__tests__/compute.test.ts +934 -0
  60. package/src/charts/bar/__tests__/gradient-orient.test.ts +127 -0
  61. package/src/charts/bar/__tests__/labels.test.ts +292 -0
  62. package/src/charts/bar/compute.ts +539 -0
  63. package/src/charts/bar/index.ts +60 -0
  64. package/src/charts/bar/labels.ts +311 -0
  65. package/src/charts/beeswarm/__tests__/compute.test.ts +280 -0
  66. package/src/charts/beeswarm/__tests__/dodge.test.ts +148 -0
  67. package/src/charts/beeswarm/compute.ts +203 -0
  68. package/src/charts/beeswarm/dodge.ts +155 -0
  69. package/src/charts/beeswarm/index.ts +32 -0
  70. package/src/charts/builtin.ts +75 -0
  71. package/src/charts/calendar/__tests__/compute.test.ts +417 -0
  72. package/src/charts/calendar/compute.ts +319 -0
  73. package/src/charts/calendar/index.ts +32 -0
  74. package/src/charts/column/__tests__/compute.test.ts +540 -0
  75. package/src/charts/column/__tests__/labels.test.ts +179 -0
  76. package/src/charts/column/compute.ts +493 -0
  77. package/src/charts/column/index.ts +53 -0
  78. package/src/charts/column/labels.ts +229 -0
  79. package/src/charts/dot/__tests__/compute.test.ts +380 -0
  80. package/src/charts/dot/__tests__/labels.test.ts +98 -0
  81. package/src/charts/dot/compute.ts +285 -0
  82. package/src/charts/dot/index.ts +62 -0
  83. package/src/charts/dot/labels.ts +121 -0
  84. package/src/charts/line/__tests__/compute.test.ts +1194 -0
  85. package/src/charts/line/__tests__/curves.test.ts +51 -0
  86. package/src/charts/line/__tests__/labels.test.ts +94 -0
  87. package/src/charts/line/__tests__/mark-options.test.ts +175 -0
  88. package/src/charts/line/area.ts +484 -0
  89. package/src/charts/line/compute.ts +356 -0
  90. package/src/charts/line/curves.ts +85 -0
  91. package/src/charts/line/index.ts +176 -0
  92. package/src/charts/line/labels.ts +177 -0
  93. package/src/charts/parliament/__tests__/compute.test.ts +411 -0
  94. package/src/charts/parliament/compute.ts +313 -0
  95. package/src/charts/parliament/index.ts +32 -0
  96. package/src/charts/pie/__tests__/compute.test.ts +384 -0
  97. package/src/charts/pie/__tests__/labels.test.ts +132 -0
  98. package/src/charts/pie/compute.ts +324 -0
  99. package/src/charts/pie/index.ts +49 -0
  100. package/src/charts/pie/labels.ts +143 -0
  101. package/src/charts/post-process.ts +229 -0
  102. package/src/charts/range/__tests__/compute.test.ts +486 -0
  103. package/src/charts/range/compute.ts +273 -0
  104. package/src/charts/range/index.ts +82 -0
  105. package/src/charts/range/labels.ts +169 -0
  106. package/src/charts/rect/__tests__/compute.test.ts +120 -0
  107. package/src/charts/rect/index.ts +127 -0
  108. package/src/charts/registry.ts +72 -0
  109. package/src/charts/rule/index.ts +139 -0
  110. package/src/charts/scatter/__tests__/compute.test.ts +488 -0
  111. package/src/charts/scatter/__tests__/trendline.test.ts +191 -0
  112. package/src/charts/scatter/compute.ts +192 -0
  113. package/src/charts/scatter/index.ts +46 -0
  114. package/src/charts/scatter/trendline.ts +100 -0
  115. package/src/charts/text/__tests__/compute.test.ts +307 -0
  116. package/src/charts/text/index.ts +141 -0
  117. package/src/charts/tick/__tests__/compute.test.ts +75 -0
  118. package/src/charts/tick/index.ts +99 -0
  119. package/src/charts/utils.ts +170 -0
  120. package/src/charts/waffle/__tests__/compute.test.ts +305 -0
  121. package/src/charts/waffle/compute.ts +197 -0
  122. package/src/charts/waffle/index.ts +31 -0
  123. package/src/compile/__tests__/color-scale-range.test.ts +158 -0
  124. package/src/compile/__tests__/color-scale-reverse.test.ts +89 -0
  125. package/src/compile/__tests__/data-clip.test.ts +127 -0
  126. package/src/compile/__tests__/fill-patterns.test.ts +154 -0
  127. package/src/compile/__tests__/size-scale.test.ts +108 -0
  128. package/src/compile/__tests__/watermark-obstacle.test.ts +93 -0
  129. package/src/compile/color-scale-range.ts +101 -0
  130. package/src/compile/data-clip.ts +48 -0
  131. package/src/compile/fill-patterns.ts +74 -0
  132. package/src/compile/layer.ts +626 -0
  133. package/src/compile/size-scale.ts +141 -0
  134. package/src/compile/spec-sugar.ts +758 -0
  135. package/src/compile/watermark-obstacle.ts +54 -0
  136. package/src/compile/you-draw-it.ts +123 -0
  137. package/src/compile.ts +1450 -0
  138. package/src/compiler/__tests__/a11y-warnings.test.ts +148 -0
  139. package/src/compiler/__tests__/animation.test.ts +177 -0
  140. package/src/compiler/__tests__/compile.test.ts +87 -0
  141. package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +19 -0
  142. package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +20 -0
  143. package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +22 -0
  144. package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +19 -0
  145. package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +14 -0
  146. package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +34 -0
  147. package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +22 -0
  148. package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +41 -0
  149. package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +38 -0
  150. package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +28 -0
  151. package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +28 -0
  152. package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +30 -0
  153. package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +29 -0
  154. package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +32 -0
  155. package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +25 -0
  156. package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +19 -0
  157. package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +19 -0
  158. package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +21 -0
  159. package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +23 -0
  160. package/src/compiler/__tests__/keys.test.ts +83 -0
  161. package/src/compiler/__tests__/normalize-highlight.test.ts +103 -0
  162. package/src/compiler/__tests__/normalize.test.ts +399 -0
  163. package/src/compiler/__tests__/sparkline-defaults.test.ts +153 -0
  164. package/src/compiler/__tests__/validate-did-you-mean.test.ts +86 -0
  165. package/src/compiler/__tests__/validate-highlight.test.ts +112 -0
  166. package/src/compiler/__tests__/validate-near-miss.test.ts +89 -0
  167. package/src/compiler/__tests__/validate.test.ts +629 -0
  168. package/src/compiler/__tests__/vl-parity.test.ts +68 -0
  169. package/src/compiler/a11y-warnings.ts +110 -0
  170. package/src/compiler/animation.ts +142 -0
  171. package/src/compiler/index.ts +47 -0
  172. package/src/compiler/keys.ts +55 -0
  173. package/src/compiler/normalize.ts +734 -0
  174. package/src/compiler/sparkline-defaults.ts +138 -0
  175. package/src/compiler/types.ts +241 -0
  176. package/src/compiler/validate.ts +1871 -0
  177. package/src/endpoint-labels/__tests__/compute.test.ts +587 -0
  178. package/src/endpoint-labels/compute.ts +621 -0
  179. package/src/endpoint-labels/constants.ts +54 -0
  180. package/src/endpoint-labels/format.ts +60 -0
  181. package/src/endpoint-labels/predict.ts +139 -0
  182. package/src/format/__tests__/field-format.test.ts +164 -0
  183. package/src/format/field-format.ts +27 -0
  184. package/src/geo/__tests__/compile-map.test.ts +832 -0
  185. package/src/geo/compile-map.ts +1243 -0
  186. package/src/geo/join.ts +115 -0
  187. package/src/geo/projections.ts +45 -0
  188. package/src/geo/types.ts +26 -0
  189. package/src/graphs/__tests__/community.test.ts +228 -0
  190. package/src/graphs/__tests__/compile-graph.test.ts +396 -0
  191. package/src/graphs/__tests__/encoding.test.ts +415 -0
  192. package/src/graphs/community.ts +92 -0
  193. package/src/graphs/compile-graph.ts +329 -0
  194. package/src/graphs/encoding.ts +356 -0
  195. package/src/graphs/types.ts +106 -0
  196. package/src/index.ts +177 -0
  197. package/src/layout/__tests__/metrics.test.ts +97 -0
  198. package/src/layout/__tests__/scales-highlight.test.ts +192 -0
  199. package/src/layout/axes/rotation.ts +152 -0
  200. package/src/layout/axes/thinning.ts +98 -0
  201. package/src/layout/axes/ticks.ts +409 -0
  202. package/src/layout/axes.ts +757 -0
  203. package/src/layout/dimensions.ts +912 -0
  204. package/src/layout/facet.ts +91 -0
  205. package/src/layout/gridlines.ts +84 -0
  206. package/src/layout/metrics.ts +132 -0
  207. package/src/layout/plan.ts +631 -0
  208. package/src/layout/scales.ts +1053 -0
  209. package/src/layout/shared.ts +44 -0
  210. package/src/legend/__tests__/continuous.test.ts +318 -0
  211. package/src/legend/__tests__/redundancy.test.ts +108 -0
  212. package/src/legend/__tests__/size.test.ts +348 -0
  213. package/src/legend/__tests__/suppression.test.ts +382 -0
  214. package/src/legend/compute.ts +747 -0
  215. package/src/legend/continuous.ts +327 -0
  216. package/src/legend/size.ts +407 -0
  217. package/src/legend/suppression.ts +226 -0
  218. package/src/legend/wrap.ts +117 -0
  219. package/src/sankey/__tests__/compile-sankey.test.ts +458 -0
  220. package/src/sankey/__tests__/layout.test.ts +165 -0
  221. package/src/sankey/__tests__/node-label-wrap.test.ts +114 -0
  222. package/src/sankey/__tests__/node-sort.test.ts +45 -0
  223. package/src/sankey/compile-sankey.ts +706 -0
  224. package/src/sankey/layout.ts +184 -0
  225. package/src/sankey/types.ts +41 -0
  226. package/src/tables/__tests__/bar-column.test.ts +147 -0
  227. package/src/tables/__tests__/category-colors.test.ts +230 -0
  228. package/src/tables/__tests__/compile-table.test.ts +236 -0
  229. package/src/tables/__tests__/format-cells.test.ts +143 -0
  230. package/src/tables/__tests__/heatmap.test.ts +148 -0
  231. package/src/tables/__tests__/pagination.test.ts +78 -0
  232. package/src/tables/__tests__/search.test.ts +94 -0
  233. package/src/tables/__tests__/sort.test.ts +107 -0
  234. package/src/tables/__tests__/sparkline.test.ts +122 -0
  235. package/src/tables/bar-column.ts +94 -0
  236. package/src/tables/category-colors.ts +78 -0
  237. package/src/tables/compile-table.ts +427 -0
  238. package/src/tables/format-cells.ts +115 -0
  239. package/src/tables/heatmap.ts +141 -0
  240. package/src/tables/pagination.ts +46 -0
  241. package/src/tables/search.ts +66 -0
  242. package/src/tables/sort.ts +69 -0
  243. package/src/tables/sparkline.ts +113 -0
  244. package/src/tables/utils.ts +16 -0
  245. package/src/tilemap/__tests__/compile-tilemap.test.ts +576 -0
  246. package/src/tilemap/compile-tilemap.ts +755 -0
  247. package/src/tilemap/layout.ts +172 -0
  248. package/src/tilemap/types.ts +33 -0
  249. package/src/tooltips/__tests__/compute.test.ts +582 -0
  250. package/src/tooltips/compute.ts +700 -0
  251. package/src/transforms/__tests__/aggregate.test.ts +159 -0
  252. package/src/transforms/__tests__/bin.test.ts +88 -0
  253. package/src/transforms/__tests__/calculate.test.ts +146 -0
  254. package/src/transforms/__tests__/conditional.test.ts +127 -0
  255. package/src/transforms/__tests__/filter-relative.test.ts +202 -0
  256. package/src/transforms/__tests__/filter.test.ts +59 -0
  257. package/src/transforms/__tests__/fold.test.ts +79 -0
  258. package/src/transforms/__tests__/index.test.ts +93 -0
  259. package/src/transforms/__tests__/predicates.test.ts +193 -0
  260. package/src/transforms/__tests__/timeunit.test.ts +129 -0
  261. package/src/transforms/__tests__/window.test.ts +286 -0
  262. package/src/transforms/aggregate.ts +131 -0
  263. package/src/transforms/bin.ts +87 -0
  264. package/src/transforms/calculate.ts +60 -0
  265. package/src/transforms/conditional.ts +46 -0
  266. package/src/transforms/filter.ts +122 -0
  267. package/src/transforms/fold.ts +49 -0
  268. package/src/transforms/index.ts +60 -0
  269. package/src/transforms/predicates.ts +123 -0
  270. package/src/transforms/timeunit.ts +88 -0
  271. package/src/transforms/window.ts +185 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Integration snapshot test: the backstop for Step 7 refactor.
3
+ *
4
+ * Compiles three representative ChartSpecs that exercise the extracted paths
5
+ * (legend-heavy, clipped-domain, watermark + gradient) and snapshots the full
6
+ * ChartLayout. Pre-refactor snapshots are captured before helper extraction;
7
+ * post-refactor snapshots must match exactly without --update-snapshots.
8
+ *
9
+ * Non-serializable fields (Map, Function) are normalized into plain structures
10
+ * so the snapshot can round-trip through the default serializer.
11
+ */
12
+
13
+ import type { ChartLayout } from '@opendata-ai/openchart-core';
14
+ import { describe, expect, it } from 'vitest';
15
+ import { compileChart } from '../compile';
16
+
17
+ type AxisTick = { label?: string; value?: unknown; position?: unknown };
18
+ type AxisShape = Record<string, unknown> & { ticks?: AxisTick[] };
19
+
20
+ /**
21
+ * Whether an axis carries temporal (Date) ticks. D3 time scales produce
22
+ * midnight-local Date objects, so tick positions and values shift by hours
23
+ * between macOS (CDT/PDT) and Linux CI (UTC). We strip temporal axes from
24
+ * the main snapshot and assert their labels separately.
25
+ */
26
+ function isTemporalAxis(axis: AxisShape | undefined): boolean {
27
+ return Array.isArray(axis?.ticks) && axis.ticks.some((t) => t.value instanceof Date);
28
+ }
29
+
30
+ /** Convert ChartLayout into a fully serializable shape for snapshot comparison. */
31
+ function serializeLayout(
32
+ layout: ChartLayout,
33
+ { stripTemporalAxes = false } = {},
34
+ ): Record<string, unknown> {
35
+ const { tooltipDescriptors, measureText: _measure, ...rest } = layout;
36
+ const axes = rest.axes as { x?: AxisShape; y?: AxisShape } | undefined;
37
+
38
+ let serializedAxes = axes;
39
+ if (axes && stripTemporalAxes) {
40
+ serializedAxes = {
41
+ ...axes,
42
+ x: isTemporalAxis(axes.x) ? undefined : axes.x,
43
+ y: isTemporalAxis(axes.y) ? undefined : axes.y,
44
+ };
45
+ }
46
+
47
+ return {
48
+ ...rest,
49
+ axes: serializedAxes,
50
+ tooltipDescriptors: Array.from(tooltipDescriptors.entries()),
51
+ };
52
+ }
53
+
54
+ describe('compileChart snapshot (Step 7 oracle)', () => {
55
+ it('legend-heavy multi-series line chart', () => {
56
+ const spec = {
57
+ mark: 'line' as const,
58
+ data: [
59
+ { date: '2020-01-01', value: 10, country: 'US' },
60
+ { date: '2021-01-01', value: 40, country: 'US' },
61
+ { date: '2020-01-01', value: 15, country: 'UK' },
62
+ { date: '2021-01-01', value: 35, country: 'UK' },
63
+ { date: '2020-01-01', value: 8, country: 'FR' },
64
+ { date: '2021-01-01', value: 22, country: 'FR' },
65
+ { date: '2020-01-01', value: 12, country: 'DE' },
66
+ { date: '2021-01-01', value: 28, country: 'DE' },
67
+ ],
68
+ encoding: {
69
+ x: { field: 'date', type: 'temporal' as const },
70
+ y: { field: 'value', type: 'quantitative' as const },
71
+ color: { field: 'country', type: 'nominal' as const },
72
+ },
73
+ chrome: {
74
+ title: 'GDP Growth',
75
+ subtitle: 'Four-country comparison',
76
+ source: 'World Bank',
77
+ },
78
+ legend: { show: true },
79
+ watermark: false,
80
+ };
81
+
82
+ const layout = compileChart(spec, { width: 800, height: 500 });
83
+
84
+ // Temporal x-axis positions shift with timezone (macOS CDT vs Linux UTC),
85
+ // so we strip it from the structural snapshot and assert just the labels.
86
+ expect(serializeLayout(layout, { stripTemporalAxes: true })).toMatchSnapshot();
87
+
88
+ const xAxes = (layout.axes as { x?: AxisShape } | undefined)?.x;
89
+ const tickLabels = (xAxes?.ticks ?? []).map((t: AxisTick) => t.label);
90
+ expect(tickLabels.length).toBeGreaterThanOrEqual(3);
91
+ expect(tickLabels.length).toBeLessThanOrEqual(6);
92
+ expect(tickLabels.every((l) => typeof l === 'string' && l.length > 0)).toBe(true);
93
+ });
94
+
95
+ it('clipped-domain bar chart (data outside scale.domain filtered)', () => {
96
+ const spec = {
97
+ mark: 'bar' as const,
98
+ data: [
99
+ { name: 'A', value: 10 },
100
+ { name: 'B', value: 25 },
101
+ { name: 'C', value: 40 },
102
+ { name: 'D', value: 75 },
103
+ { name: 'E', value: 90 },
104
+ ],
105
+ encoding: {
106
+ x: {
107
+ field: 'value',
108
+ type: 'quantitative' as const,
109
+ scale: { domain: [0, 50], clip: true },
110
+ },
111
+ y: { field: 'name', type: 'nominal' as const },
112
+ },
113
+ chrome: {
114
+ title: 'Clipped Values',
115
+ },
116
+ watermark: false,
117
+ };
118
+
119
+ const layout = compileChart(spec, { width: 600, height: 400 });
120
+ expect(serializeLayout(layout)).toMatchSnapshot();
121
+ });
122
+
123
+ it('watermarked column chart with gradient fill', () => {
124
+ const spec = {
125
+ mark: {
126
+ type: 'bar' as const,
127
+ fill: {
128
+ type: 'linear' as const,
129
+ stops: [
130
+ { offset: 0, color: '#ff0000' },
131
+ { offset: 1, color: '#0000ff' },
132
+ ],
133
+ },
134
+ },
135
+ data: [
136
+ { category: 'A', score: 30 },
137
+ { category: 'B', score: 55 },
138
+ { category: 'C', score: 70 },
139
+ { category: 'D', score: 45 },
140
+ ],
141
+ encoding: {
142
+ x: { field: 'category', type: 'nominal' as const },
143
+ y: { field: 'score', type: 'quantitative' as const },
144
+ },
145
+ chrome: {
146
+ title: 'Gradient Columns',
147
+ source: 'Test data',
148
+ byline: 'By the engine',
149
+ },
150
+ watermark: true,
151
+ };
152
+
153
+ const layout = compileChart(spec, { width: 600, height: 400 });
154
+ expect(serializeLayout(layout)).toMatchSnapshot();
155
+ });
156
+ });
@@ -0,0 +1,147 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { compileChart } from '../compile';
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Test data
6
+ // ---------------------------------------------------------------------------
7
+
8
+ const albumData = [
9
+ { album: 'Abbey Road', artist: 'The Beatles', sales: 31 },
10
+ { album: 'Thriller', artist: 'Michael Jackson', sales: 66 },
11
+ { album: 'Back in Black', artist: 'AC/DC', sales: 50 },
12
+ { album: 'The Dark Side of the Moon', artist: 'Pink Floyd', sales: 45 },
13
+ { album: 'Rumours', artist: 'Fleetwood Mac', sales: 40 },
14
+ ];
15
+
16
+ function makeBarSpec(axisConfig?: Record<string, unknown>) {
17
+ return {
18
+ mark: 'bar' as const,
19
+ data: albumData,
20
+ encoding: {
21
+ x: { field: 'sales', type: 'quantitative' as const },
22
+ y: {
23
+ field: 'album',
24
+ type: 'nominal' as const,
25
+ axis: axisConfig,
26
+ },
27
+ },
28
+ };
29
+ }
30
+
31
+ const compileOpts = { width: 600, height: 400 };
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Tests
35
+ // ---------------------------------------------------------------------------
36
+
37
+ describe('compound axis labels (labelField)', () => {
38
+ it('populates subtitle on ticks when labelField is set', () => {
39
+ const spec = makeBarSpec({ labelField: 'artist' });
40
+ const layout = compileChart(spec, compileOpts);
41
+
42
+ const yTicks = layout.axes.y?.ticks ?? [];
43
+ expect(yTicks.length).toBeGreaterThan(0);
44
+
45
+ // Every tick should have a subtitle matching the artist for that album
46
+ for (const tick of yTicks) {
47
+ const row = albumData.find((r) => r.album === tick.label);
48
+ expect(row).toBeDefined();
49
+ expect(tick.subtitle).toBe(row!.artist);
50
+ }
51
+ });
52
+
53
+ it('does not add subtitle when labelField is omitted', () => {
54
+ const spec = makeBarSpec();
55
+ const layout = compileChart(spec, compileOpts);
56
+
57
+ const yTicks = layout.axes.y?.ticks ?? [];
58
+ expect(yTicks.length).toBeGreaterThan(0);
59
+
60
+ for (const tick of yTicks) {
61
+ expect(tick.subtitle).toBeUndefined();
62
+ }
63
+ });
64
+
65
+ it('handles missing labelField value gracefully', () => {
66
+ const dataWithMissing = [
67
+ { album: 'Abbey Road', artist: 'The Beatles', sales: 31 },
68
+ { album: 'Unknown Album', sales: 20 }, // no artist field
69
+ ];
70
+ const spec = {
71
+ mark: 'bar' as const,
72
+ data: dataWithMissing,
73
+ encoding: {
74
+ x: { field: 'sales', type: 'quantitative' as const },
75
+ y: {
76
+ field: 'album',
77
+ type: 'nominal' as const,
78
+ axis: { labelField: 'artist' },
79
+ },
80
+ },
81
+ };
82
+
83
+ const layout = compileChart(spec, compileOpts);
84
+ const yTicks = layout.axes.y?.ticks ?? [];
85
+
86
+ // Abbey Road should have a subtitle
87
+ const abbeyRoad = yTicks.find((t) => t.label === 'Abbey Road');
88
+ expect(abbeyRoad?.subtitle).toBe('The Beatles');
89
+
90
+ // Unknown Album has no artist field, so subtitle should be undefined
91
+ const unknown = yTicks.find((t) => t.label === 'Unknown Album');
92
+ expect(unknown?.subtitle).toBeUndefined();
93
+ });
94
+
95
+ it('maps subtitle correctly across multiple ticks', () => {
96
+ const spec = makeBarSpec({ labelField: 'artist' });
97
+ const layout = compileChart(spec, compileOpts);
98
+
99
+ const yTicks = layout.axes.y?.ticks ?? [];
100
+ expect(yTicks.length).toBe(5);
101
+
102
+ // Verify specific mappings
103
+ const thrillerTick = yTicks.find((t) => t.label === 'Thriller');
104
+ expect(thrillerTick?.subtitle).toBe('Michael Jackson');
105
+
106
+ const rumoursTick = yTicks.find((t) => t.label === 'Rumours');
107
+ expect(rumoursTick?.subtitle).toBe('Fleetwood Mac');
108
+ });
109
+
110
+ it('preserves subtitle mapping with sort: descending', () => {
111
+ const spec = {
112
+ mark: 'bar' as const,
113
+ data: albumData,
114
+ encoding: {
115
+ x: { field: 'sales', type: 'quantitative' as const },
116
+ y: {
117
+ field: 'album',
118
+ type: 'nominal' as const,
119
+ sort: 'descending' as const,
120
+ axis: { labelField: 'artist' },
121
+ },
122
+ },
123
+ };
124
+
125
+ const layout = compileChart(spec, compileOpts);
126
+ const yTicks = layout.axes.y?.ticks ?? [];
127
+
128
+ // Regardless of sort order, each tick should still map to the right artist
129
+ for (const tick of yTicks) {
130
+ const row = albumData.find((r) => r.album === tick.label);
131
+ expect(row).toBeDefined();
132
+ expect(tick.subtitle).toBe(row!.artist);
133
+ }
134
+ });
135
+
136
+ it('reserves wider dimension with labelField than without', () => {
137
+ const specWith = makeBarSpec({ labelField: 'artist' });
138
+ const specWithout = makeBarSpec();
139
+
140
+ const layoutWith = compileChart(specWith, compileOpts);
141
+ const layoutWithout = compileChart(specWithout, compileOpts);
142
+
143
+ // Chart area x (left edge) should be larger with labelField because more
144
+ // left margin is reserved for the wider compound labels
145
+ expect(layoutWith.area.x).toBeGreaterThanOrEqual(layoutWithout.area.x);
146
+ });
147
+ });