@objectstack/cli 9.0.0 → 9.0.1

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.
@@ -0,0 +1,304 @@
1
+ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2
+ /**
3
+ * Build-time dashboard widget binding diagnostics (issues #1719, #1721).
4
+ *
5
+ * Runs at `objectstack validate`/`compile`/`build` AFTER the stack has been
6
+ * schema-parsed, so every widget's `dataset` reference can be linked to its
7
+ * `defineDataset` and each entry in `dimensions`/`values` resolved to a
8
+ * declared dimension/measure. This is the semantic/cross-reference phase —
9
+ * the rules here cannot run during plain Zod parsing of the raw widget
10
+ * literal (the dataset may even live in another package of the stack).
11
+ *
12
+ * Reference-integrity rules (#1721) — severity `error`, the page is broken:
13
+ *
14
+ * - `widget-dataset-unknown` — `dataset` does not resolve to a declared
15
+ * `Dataset`.
16
+ * - `widget-dimension-unknown` — a `dimensions[]` entry is not a dimension
17
+ * name on the bound dataset.
18
+ * - `widget-measure-unknown` — a `values[]` entry is not a measure name on
19
+ * the bound dataset.
20
+ * - `chart-field-unknown` — a `chartConfig` binding names a field the query
21
+ * result will not contain: `xAxis.field` must be one of the widget's
22
+ * dimensions (or a dataset dimension), and each `yAxis[].field` /
23
+ * `series[].name` must be one of the widget's selected measures
24
+ * (`values`). Post-cutover (ADR-0021) the result rows are keyed by
25
+ * measure NAME (e.g. `sum_amount`), not the base column (`amount`) — a
26
+ * stale base-column reference renders the axis but an empty series.
27
+ *
28
+ * Advisory rules — severity `warning`, build stays green:
29
+ *
30
+ * - `chart-config-missing` — a chart-type widget (bar/line/pie/…) has no
31
+ * `chartConfig`, so the renderer cannot tell which measure to plot.
32
+ * - `table-count-only` (#1719) — a `table`/`pivot` widget whose selected
33
+ * measures are ALL `aggregate: 'count'` and which declares no
34
+ * `dimensions` asks the analytics service for a single summary row. That
35
+ * is the shape a `metric` widget wants — for a table it almost always
36
+ * means the author wanted a per-record listing, which is not an
37
+ * analytics dataset at all (model it as an object-bound ListView,
38
+ * ADR-0017). Evaluated on the WIDGET's binding, not the dataset.
39
+ *
40
+ * Warnings can be deliberately suppressed per widget via
41
+ * `suppressWarnings: ['<rule-id>']`; errors cannot — they describe a
42
+ * binding the analytics service cannot satisfy.
43
+ */
44
+ export const WIDGET_DATASET_UNKNOWN = 'widget-dataset-unknown';
45
+ export const WIDGET_DIMENSION_UNKNOWN = 'widget-dimension-unknown';
46
+ export const WIDGET_MEASURE_UNKNOWN = 'widget-measure-unknown';
47
+ export const CHART_FIELD_UNKNOWN = 'chart-field-unknown';
48
+ export const CHART_CONFIG_MISSING = 'chart-config-missing';
49
+ export const TABLE_COUNT_ONLY = 'table-count-only';
50
+ /** Coerce a collection (array or name-keyed map) to an array. */
51
+ function asArray(v) {
52
+ if (Array.isArray(v))
53
+ return v;
54
+ if (v && typeof v === 'object') {
55
+ return Object.entries(v).map(([name, def]) => ({ name, ...def }));
56
+ }
57
+ return [];
58
+ }
59
+ function asStrings(v) {
60
+ return Array.isArray(v) ? v.filter((s) => typeof s === 'string') : [];
61
+ }
62
+ /**
63
+ * Chart families whose renderer needs a `chartConfig` measure mapping.
64
+ * Single-value types (metric/kpi/gauge/…) plot their lone value and tabular
65
+ * types (table/pivot) render every column, so they are exempt.
66
+ */
67
+ const CHART_TYPES = new Set([
68
+ 'bar', 'horizontal-bar', 'column',
69
+ 'line', 'area',
70
+ 'pie', 'donut', 'funnel',
71
+ 'scatter', 'treemap', 'sankey', 'radar',
72
+ ]);
73
+ function levenshtein(a, b) {
74
+ const m = a.length, n = b.length;
75
+ let prev = Array.from({ length: n + 1 }, (_, j) => j);
76
+ for (let i = 1; i <= m; i++) {
77
+ const cur = [i];
78
+ for (let j = 1; j <= n; j++) {
79
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
80
+ }
81
+ prev = cur;
82
+ }
83
+ return prev[n];
84
+ }
85
+ /**
86
+ * Nearest declared name for a typo'd/stale reference, or undefined when
87
+ * nothing is close. Containment is checked first because the cutover's
88
+ * canonical drift is base column → prefixed measure name (`amount` →
89
+ * `sum_amount`), which is far in edit distance but obvious to a human.
90
+ */
91
+ function didYouMean(input, candidates) {
92
+ let best;
93
+ let bestScore = Infinity;
94
+ for (const c of candidates) {
95
+ let score;
96
+ if (input.length >= 3 && (c.includes(input) || input.includes(c))) {
97
+ score = Math.abs(c.length - input.length);
98
+ }
99
+ else {
100
+ const d = levenshtein(input, c);
101
+ if (d > Math.max(2, Math.floor(input.length / 3)))
102
+ continue;
103
+ score = 100 + d;
104
+ }
105
+ if (score < bestScore) {
106
+ bestScore = score;
107
+ best = c;
108
+ }
109
+ }
110
+ return best;
111
+ }
112
+ function suggest(input, candidates) {
113
+ const s = didYouMean(input, candidates);
114
+ return s ? ` Did you mean "${s}"?` : '';
115
+ }
116
+ function list(names) {
117
+ const arr = [...names];
118
+ return arr.length > 0 ? arr.join(', ') : '(none)';
119
+ }
120
+ /**
121
+ * Validate every dashboard widget's dataset binding. Returns the list of
122
+ * findings (empty = clean). Caller decides how to surface them: `error`
123
+ * findings describe bindings the analytics service cannot satisfy and
124
+ * should fail validate/build; `warning` findings are advisory and must
125
+ * never fail the build on their own.
126
+ */
127
+ export function validateWidgetBindings(stack) {
128
+ const findings = [];
129
+ const datasets = new Map();
130
+ for (const ds of asArray(stack.datasets)) {
131
+ if (typeof ds.name === 'string')
132
+ datasets.set(ds.name, ds);
133
+ }
134
+ const dashboards = asArray(stack.dashboards);
135
+ for (let i = 0; i < dashboards.length; i++) {
136
+ const dash = dashboards[i];
137
+ const dashName = typeof dash.name === 'string' ? dash.name : `(dashboard ${i})`;
138
+ const widgets = Array.isArray(dash.widgets) ? dash.widgets : [];
139
+ for (let j = 0; j < widgets.length; j++) {
140
+ const w = widgets[j];
141
+ const widgetId = typeof w.id === 'string' ? w.id : `(widget ${j})`;
142
+ const where = `dashboard "${dashName}" › widget "${widgetId}"`;
143
+ const path = `dashboards[${i}].widgets[${j}]`;
144
+ const suppressed = (rule) => Array.isArray(w.suppressWarnings) && w.suppressWarnings.includes(rule);
145
+ const push = (f) => {
146
+ if (f.severity === 'warning' && suppressed(f.rule))
147
+ return;
148
+ findings.push({ ...f, where, path });
149
+ };
150
+ // ── (a) dataset reference resolves ──
151
+ const dsName = typeof w.dataset === 'string' ? w.dataset : undefined;
152
+ const dataset = dsName ? datasets.get(dsName) : undefined;
153
+ if (dsName && !dataset) {
154
+ push({
155
+ severity: 'error',
156
+ rule: WIDGET_DATASET_UNKNOWN,
157
+ message: `dataset "${dsName}" does not resolve to a declared dataset.`,
158
+ hint: `Declared datasets: ${list(datasets.keys())}.${suggest(dsName, datasets.keys())} ` +
159
+ `Define the dataset with defineDataset() or fix the reference (ADR-0021).`,
160
+ });
161
+ }
162
+ // Without a resolved dataset there is nothing to check names against.
163
+ if (!dataset)
164
+ continue;
165
+ const dimensionNames = new Set();
166
+ for (const d of asArray(dataset.dimensions)) {
167
+ if (typeof d.name === 'string')
168
+ dimensionNames.add(d.name);
169
+ }
170
+ const measures = new Map();
171
+ for (const m of asArray(dataset.measures)) {
172
+ if (typeof m.name === 'string')
173
+ measures.set(m.name, m);
174
+ }
175
+ // ── (b) every dimensions[] entry is a dataset dimension ──
176
+ const dims = asStrings(w.dimensions);
177
+ for (let k = 0; k < dims.length; k++) {
178
+ if (dimensionNames.has(dims[k]))
179
+ continue;
180
+ push({
181
+ severity: 'error',
182
+ rule: WIDGET_DIMENSION_UNKNOWN,
183
+ message: `dimensions[${k}] "${dims[k]}" is not a dimension of dataset ` +
184
+ `"${dsName}" (declared dimensions: ${list(dimensionNames)}).`,
185
+ hint: `Widgets select dataset dimensions BY NAME.${suggest(dims[k], dimensionNames)} ` +
186
+ `Add the dimension to the dataset or fix the reference.`,
187
+ });
188
+ }
189
+ // ── (c) every values[] entry is a dataset measure ──
190
+ const values = asStrings(w.values);
191
+ for (let k = 0; k < values.length; k++) {
192
+ if (measures.has(values[k]))
193
+ continue;
194
+ push({
195
+ severity: 'error',
196
+ rule: WIDGET_MEASURE_UNKNOWN,
197
+ message: `values[${k}] "${values[k]}" is not a measure of dataset ` +
198
+ `"${dsName}" (declared measures: ${list(measures.keys())}).`,
199
+ hint: `Widgets select dataset measures BY NAME, not by base column.` +
200
+ `${suggest(values[k], measures.keys())} ` +
201
+ `Add the measure to the dataset or fix the reference.`,
202
+ });
203
+ }
204
+ // ── (d) chartConfig bindings resolve against the widget's selection ──
205
+ const chartConfig = (w.chartConfig && typeof w.chartConfig === 'object')
206
+ ? w.chartConfig
207
+ : undefined;
208
+ const isChartType = typeof w.type === 'string' && CHART_TYPES.has(w.type);
209
+ if (chartConfig) {
210
+ // The query result carries the widget's selected dimensions and
211
+ // measures; resolve every chartConfig field against that shape.
212
+ const selectedValues = new Set(values.filter((v) => measures.has(v)));
213
+ const xAxis = (chartConfig.xAxis && typeof chartConfig.xAxis === 'object')
214
+ ? chartConfig.xAxis
215
+ : undefined;
216
+ // A field naming an entry of the widget's own (already-validated)
217
+ // selection is not re-reported here — rules (b)/(c) own that error.
218
+ if (xAxis && typeof xAxis.field === 'string'
219
+ && !dimensionNames.has(xAxis.field) && !dims.includes(xAxis.field)) {
220
+ push({
221
+ severity: 'error',
222
+ rule: CHART_FIELD_UNKNOWN,
223
+ message: `chartConfig.xAxis.field "${xAxis.field}" does not resolve to a ` +
224
+ `dimension of dataset "${dsName}" (declared dimensions: ${list(dimensionNames)}).`,
225
+ hint: `Point xAxis.field at a dataset dimension name.${suggest(xAxis.field, dimensionNames)}`,
226
+ });
227
+ }
228
+ const measureField = (label, field) => {
229
+ if (values.includes(field))
230
+ return; // resolvable, or already errored via rule (c)
231
+ const declaredButUnselected = measures.has(field);
232
+ push({
233
+ severity: 'error',
234
+ rule: CHART_FIELD_UNKNOWN,
235
+ message: declaredButUnselected
236
+ ? `chartConfig.${label} "${field}" is a measure of dataset "${dsName}" ` +
237
+ `but is not selected in the widget's values (${list(values)}), so the ` +
238
+ `query result will not contain it.`
239
+ : `chartConfig.${label} "${field}" does not resolve to a measure of ` +
240
+ `dataset "${dsName}" (declared measures: ${list(measures.keys())}).`,
241
+ hint: declaredButUnselected
242
+ ? `Add "${field}" to the widget's values, or bind the chart to a selected measure.`
243
+ : `Post-cutover data is keyed by the dataset's measure NAME, not the ` +
244
+ `base column.${suggest(field, selectedValues.size > 0 ? selectedValues : measures.keys())}`,
245
+ });
246
+ };
247
+ const yAxes = Array.isArray(chartConfig.yAxis) ? chartConfig.yAxis : [];
248
+ for (let k = 0; k < yAxes.length; k++) {
249
+ const field = yAxes[k]?.field;
250
+ if (typeof field === 'string')
251
+ measureField(`yAxis[${k}].field`, field);
252
+ }
253
+ const series = Array.isArray(chartConfig.series) ? chartConfig.series : [];
254
+ for (let k = 0; k < series.length; k++) {
255
+ const name = series[k]?.name;
256
+ if (typeof name === 'string')
257
+ measureField(`series[${k}].name`, name);
258
+ }
259
+ }
260
+ else if (isChartType) {
261
+ push({
262
+ severity: 'warning',
263
+ rule: CHART_CONFIG_MISSING,
264
+ message: `chart-type widget ('${w.type}') has no chartConfig — the renderer ` +
265
+ `cannot determine which measure to plot, so the series renders empty.`,
266
+ hint: `Add chartConfig with xAxis.field set to a dimension (${list(dims)}) and ` +
267
+ `yAxis[].field set to a measure name (${list(values)}). If the default ` +
268
+ `rendering is intentional, suppress with: suppressWarnings: ['${CHART_CONFIG_MISSING}']`,
269
+ });
270
+ }
271
+ // ── (e) table/pivot bound to a count-only, dimensionless selection ──
272
+ if (w.type !== 'table' && w.type !== 'pivot')
273
+ continue;
274
+ // Grouped by at least one dimension → genuinely aggregated rows.
275
+ if (dims.length > 0)
276
+ continue;
277
+ if (values.length === 0)
278
+ continue;
279
+ const resolved = values.map((v) => measures.get(v));
280
+ // An unresolvable measure name already errored above — don't guess here.
281
+ if (resolved.some((m) => !m))
282
+ continue;
283
+ // Derived measures combine other measures; treat them as non-count even
284
+ // when their (ignored) `aggregate` says otherwise.
285
+ const countOnly = resolved.every((m) => m.aggregate === 'count' && !m.derived);
286
+ if (!countOnly)
287
+ continue;
288
+ push({
289
+ severity: 'warning',
290
+ rule: TABLE_COUNT_ONLY,
291
+ message: `a '${w.type}' widget bound to dataset "${dsName}" selects only count ` +
292
+ `measure(s) (${values.join(', ')}) and no dimensions, so it renders a ` +
293
+ `single summary row — not a per-record list.`,
294
+ hint: `A flat record listing is not an analytics dataset. Model it as an ` +
295
+ `object-bound ListView (ADR-0017) surfaced through app navigation, and ` +
296
+ `use a 'metric' widget here if you only need the count. If a single-row ` +
297
+ `table is intentional, add an explicit dimension or suppress with: ` +
298
+ `suppressWarnings: ['${TABLE_COUNT_ONLY}']`,
299
+ });
300
+ }
301
+ }
302
+ return findings;
303
+ }
304
+ //# sourceMappingURL=validate-widget-bindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-widget-bindings.js","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validate-widget-bindings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-widget-bindings.test.d.ts","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,285 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateWidgetBindings, TABLE_COUNT_ONLY, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, CHART_FIELD_UNKNOWN, CHART_CONFIG_MISSING, } from './validate-widget-bindings.js';
3
+ /** The downstream repro from issue #1719 — dataset with a count AND a sum
4
+ * measure plus a dimension; the widget selects only the count, no dims. */
5
+ function reproStack(widgetOverrides = {}) {
6
+ return {
7
+ datasets: [{
8
+ name: 'expense_report_metrics',
9
+ label: 'Expense report metrics',
10
+ object: 'expense_report',
11
+ measures: [
12
+ { name: 'report_count', label: 'report_count', aggregate: 'count' },
13
+ { name: 'total_amount', label: 'total_amount', aggregate: 'sum', field: 'total_amount' },
14
+ ],
15
+ dimensions: [{ name: 'cost_center', field: 'cost_center' }],
16
+ }],
17
+ dashboards: [{
18
+ name: 'expenses_overview_dashboard',
19
+ label: 'Expenses Overview',
20
+ widgets: [{
21
+ id: 'pending_reports_table',
22
+ type: 'table',
23
+ dataset: 'expense_report_metrics',
24
+ values: ['report_count'],
25
+ filter: { status: 'submitted' },
26
+ layout: { x: 0, y: 0, w: 6, h: 4 },
27
+ ...widgetOverrides,
28
+ }],
29
+ }],
30
+ };
31
+ }
32
+ /** The minimal repro from issue #1721 — dataset measure is `sum_amount`, but
33
+ * the chart's yAxis still names the old base column `amount`. */
34
+ function chartStack(widgetOverrides = {}) {
35
+ return {
36
+ datasets: [{
37
+ name: 'expense_line_metrics',
38
+ label: 'Expense line metrics',
39
+ object: 'expense_line',
40
+ dimensions: [{ name: 'category', field: 'category' }],
41
+ measures: [
42
+ { name: 'sum_amount', label: 'sum_amount', aggregate: 'sum', field: 'amount', certified: true },
43
+ { name: 'ticket_count', label: 'ticket_count', aggregate: 'count' },
44
+ ],
45
+ }],
46
+ dashboards: [{
47
+ name: 'spend_dashboard',
48
+ label: 'Spend',
49
+ widgets: [{
50
+ id: 'spend_by_category',
51
+ type: 'bar',
52
+ dataset: 'expense_line_metrics',
53
+ dimensions: ['category'],
54
+ values: ['sum_amount'],
55
+ chartConfig: {
56
+ type: 'bar',
57
+ xAxis: { field: 'category' },
58
+ yAxis: [{ field: 'sum_amount' }],
59
+ },
60
+ layout: { x: 0, y: 0, w: 6, h: 4 },
61
+ ...widgetOverrides,
62
+ }],
63
+ }],
64
+ };
65
+ }
66
+ describe('validateWidgetBindings (reference integrity, issue #1721)', () => {
67
+ it('a fully resolved chart widget is clean', () => {
68
+ expect(validateWidgetBindings(chartStack())).toHaveLength(0);
69
+ });
70
+ it('(a) errors on a dataset reference that does not resolve', () => {
71
+ const findings = validateWidgetBindings(chartStack({ dataset: 'expense_line_metric' }));
72
+ expect(findings).toHaveLength(1);
73
+ expect(findings[0].severity).toBe('error');
74
+ expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
75
+ expect(findings[0].message).toContain('expense_line_metric');
76
+ expect(findings[0].hint).toContain('Did you mean "expense_line_metrics"?');
77
+ });
78
+ it('(b) errors on a dimension name the dataset does not declare', () => {
79
+ const findings = validateWidgetBindings(chartStack({ dimensions: ['categry'] }));
80
+ expect(findings).toHaveLength(1);
81
+ expect(findings[0].severity).toBe('error');
82
+ expect(findings[0].rule).toBe(WIDGET_DIMENSION_UNKNOWN);
83
+ expect(findings[0].message).toContain('"categry"');
84
+ expect(findings[0].message).toContain('declared dimensions: category');
85
+ expect(findings[0].hint).toContain('Did you mean "category"?');
86
+ });
87
+ it('(c) errors on a measure name the dataset does not declare', () => {
88
+ const findings = validateWidgetBindings(chartStack({
89
+ type: 'metric',
90
+ values: ['amount'],
91
+ chartConfig: undefined,
92
+ }));
93
+ expect(findings).toHaveLength(1);
94
+ expect(findings[0].severity).toBe('error');
95
+ expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
96
+ expect(findings[0].message).toContain('declared measures: sum_amount, ticket_count');
97
+ expect(findings[0].hint).toContain('Did you mean "sum_amount"?');
98
+ });
99
+ it('(d) errors on the issue repro: yAxis.field naming the stale base column', () => {
100
+ const findings = validateWidgetBindings(chartStack({
101
+ chartConfig: {
102
+ type: 'bar',
103
+ xAxis: { field: 'category' },
104
+ yAxis: [{ field: 'amount' }],
105
+ },
106
+ }));
107
+ expect(findings).toHaveLength(1);
108
+ expect(findings[0].severity).toBe('error');
109
+ expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
110
+ expect(findings[0].where).toContain('spend_by_category');
111
+ expect(findings[0].message).toContain('chartConfig.yAxis[0].field "amount"');
112
+ expect(findings[0].message).toContain('declared measures: sum_amount, ticket_count');
113
+ expect(findings[0].hint).toContain('Did you mean "sum_amount"?');
114
+ });
115
+ it('(d) errors on xAxis.field that is not a dataset dimension', () => {
116
+ const findings = validateWidgetBindings(chartStack({
117
+ chartConfig: {
118
+ type: 'bar',
119
+ xAxis: { field: 'categories' },
120
+ yAxis: [{ field: 'sum_amount' }],
121
+ },
122
+ }));
123
+ expect(findings).toHaveLength(1);
124
+ expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
125
+ expect(findings[0].message).toContain('chartConfig.xAxis.field "categories"');
126
+ expect(findings[0].hint).toContain('Did you mean "category"?');
127
+ });
128
+ it('(d) errors on series[].name that resolves to no selected measure', () => {
129
+ const findings = validateWidgetBindings(chartStack({
130
+ chartConfig: {
131
+ type: 'bar',
132
+ series: [{ name: 'value' }],
133
+ },
134
+ }));
135
+ expect(findings).toHaveLength(1);
136
+ expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
137
+ expect(findings[0].message).toContain('chartConfig.series[0].name "value"');
138
+ });
139
+ it('(d) a declared-but-unselected measure gets the targeted message', () => {
140
+ const findings = validateWidgetBindings(chartStack({
141
+ chartConfig: {
142
+ type: 'bar',
143
+ xAxis: { field: 'category' },
144
+ yAxis: [{ field: 'ticket_count' }],
145
+ },
146
+ }));
147
+ expect(findings).toHaveLength(1);
148
+ expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
149
+ expect(findings[0].message).toContain('not selected in the widget\'s values');
150
+ expect(findings[0].hint).toContain('Add "ticket_count" to the widget\'s values');
151
+ });
152
+ it('(d) warns when a chart-type widget has no chartConfig at all', () => {
153
+ const findings = validateWidgetBindings(chartStack({ chartConfig: undefined }));
154
+ expect(findings).toHaveLength(1);
155
+ expect(findings[0].severity).toBe('warning');
156
+ expect(findings[0].rule).toBe(CHART_CONFIG_MISSING);
157
+ expect(findings[0].message).toContain("'bar'");
158
+ expect(findings[0].hint).toContain(`suppressWarnings: ['${CHART_CONFIG_MISSING}']`);
159
+ });
160
+ it('(d) missing chartConfig is suppressible per widget', () => {
161
+ expect(validateWidgetBindings(chartStack({
162
+ chartConfig: undefined,
163
+ suppressWarnings: [CHART_CONFIG_MISSING],
164
+ }))).toHaveLength(0);
165
+ });
166
+ it('(d) non-chart types do not warn on missing chartConfig', () => {
167
+ for (const type of ['metric', 'kpi', 'gauge', 'table']) {
168
+ expect(validateWidgetBindings(chartStack({ type, chartConfig: undefined }))).toHaveLength(0);
169
+ }
170
+ });
171
+ it('errors are NOT suppressible via suppressWarnings', () => {
172
+ const findings = validateWidgetBindings(chartStack({
173
+ dataset: 'no_such_dataset',
174
+ suppressWarnings: [WIDGET_DATASET_UNKNOWN],
175
+ }));
176
+ expect(findings).toHaveLength(1);
177
+ expect(findings[0].severity).toBe('error');
178
+ });
179
+ it('does not double-report a chartConfig field that names an already-errored selection entry', () => {
180
+ const findings = validateWidgetBindings(chartStack({
181
+ values: ['amount'],
182
+ chartConfig: {
183
+ type: 'bar',
184
+ xAxis: { field: 'category' },
185
+ yAxis: [{ field: 'amount' }],
186
+ },
187
+ }));
188
+ expect(findings).toHaveLength(1);
189
+ expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
190
+ });
191
+ it('a dangling dataset reports once and skips the name checks', () => {
192
+ const findings = validateWidgetBindings(chartStack({
193
+ dataset: 'nope',
194
+ dimensions: ['whatever'],
195
+ values: ['also_whatever'],
196
+ }));
197
+ expect(findings).toHaveLength(1);
198
+ expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
199
+ });
200
+ });
201
+ describe('validateWidgetBindings (table-count-only, issue #1719)', () => {
202
+ it('warns on the issue repro: count-only table widget without dimensions', () => {
203
+ const warnings = validateWidgetBindings(reproStack());
204
+ expect(warnings).toHaveLength(1);
205
+ expect(warnings[0].severity).toBe('warning');
206
+ expect(warnings[0].rule).toBe(TABLE_COUNT_ONLY);
207
+ expect(warnings[0].where).toContain('expenses_overview_dashboard');
208
+ expect(warnings[0].where).toContain('pending_reports_table');
209
+ expect(warnings[0].path).toBe('dashboards[0].widgets[0]');
210
+ expect(warnings[0].message).toContain('report_count');
211
+ expect(warnings[0].message).toContain('single summary row');
212
+ expect(warnings[0].hint).toContain('ListView (ADR-0017)');
213
+ expect(warnings[0].hint).toContain(`suppressWarnings: ['${TABLE_COUNT_ONLY}']`);
214
+ });
215
+ it('warns for pivot widgets too', () => {
216
+ const warnings = validateWidgetBindings(reproStack({ type: 'pivot' }));
217
+ expect(warnings).toHaveLength(1);
218
+ expect(warnings[0].message).toContain("'pivot' widget");
219
+ });
220
+ it('is keyed on the WIDGET binding — selecting the sum measure is clean', () => {
221
+ expect(validateWidgetBindings(reproStack({ values: ['total_amount'] }))).toHaveLength(0);
222
+ });
223
+ it('mixed count + non-count selection is clean', () => {
224
+ expect(validateWidgetBindings(reproStack({ values: ['report_count', 'total_amount'] }))).toHaveLength(0);
225
+ });
226
+ it('declaring a dimension on the widget is clean', () => {
227
+ expect(validateWidgetBindings(reproStack({ dimensions: ['cost_center'] }))).toHaveLength(0);
228
+ });
229
+ it('metric widgets are exactly what a count-only binding wants — clean', () => {
230
+ expect(validateWidgetBindings(reproStack({ type: 'metric' }))).toHaveLength(0);
231
+ });
232
+ it('suppressWarnings opts a deliberate single-row table out', () => {
233
+ expect(validateWidgetBindings(reproStack({ suppressWarnings: [TABLE_COUNT_ONLY] }))).toHaveLength(0);
234
+ });
235
+ it('unrelated suppressWarnings entries do not suppress', () => {
236
+ expect(validateWidgetBindings(reproStack({ suppressWarnings: ['some-other-rule'] }))).toHaveLength(1);
237
+ });
238
+ it('a dangling dataset reference is the cross-reference error, not this rule', () => {
239
+ const findings = validateWidgetBindings(reproStack({ dataset: 'no_such_dataset' }));
240
+ expect(findings).toHaveLength(1);
241
+ expect(findings[0].severity).toBe('error');
242
+ expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
243
+ });
244
+ it('an unresolvable measure name is the cross-reference error, not this rule', () => {
245
+ const findings = validateWidgetBindings(reproStack({ values: ['no_such_measure'] }));
246
+ expect(findings).toHaveLength(1);
247
+ expect(findings[0].severity).toBe('error');
248
+ expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
249
+ });
250
+ it('treats derived measures as non-count even when aggregate says count', () => {
251
+ const stack = reproStack({ values: ['count_ratio'] });
252
+ stack.datasets[0].measures.push({
253
+ name: 'count_ratio',
254
+ aggregate: 'count',
255
+ derived: { op: 'ratio', of: ['report_count', 'report_count'] },
256
+ });
257
+ expect(validateWidgetBindings(stack)).toHaveLength(0);
258
+ });
259
+ it('count_distinct is a deliberate analytic — clean', () => {
260
+ const stack = reproStack({ values: ['unique_requesters'] });
261
+ stack.datasets[0].measures.push({
262
+ name: 'unique_requesters',
263
+ aggregate: 'count_distinct',
264
+ field: 'requester',
265
+ });
266
+ expect(validateWidgetBindings(stack)).toHaveLength(0);
267
+ });
268
+ it('handles map-keyed datasets/dashboards collections', () => {
269
+ const arrayForm = reproStack();
270
+ const { name: _dsName, ...dsRest } = arrayForm.datasets[0];
271
+ const { name: _dashName, ...dashRest } = arrayForm.dashboards[0];
272
+ const stack = {
273
+ datasets: { expense_report_metrics: dsRest },
274
+ dashboards: { expenses_overview_dashboard: dashRest },
275
+ };
276
+ const warnings = validateWidgetBindings(stack);
277
+ expect(warnings).toHaveLength(1);
278
+ expect(warnings[0].where).toContain('expenses_overview_dashboard');
279
+ });
280
+ it('is silent on stacks without dashboards or datasets', () => {
281
+ expect(validateWidgetBindings({})).toHaveLength(0);
282
+ expect(validateWidgetBindings({ dashboards: [], datasets: [] })).toHaveLength(0);
283
+ });
284
+ });
285
+ //# sourceMappingURL=validate-widget-bindings.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-widget-bindings.test.js","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.test.ts"],"names":[],"mappings":"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"}