mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Chart builder: drop unparseable expressions mid-edit, polite glyphed inline feedback
This commit is contained in:
@@ -974,6 +974,29 @@ describe('buildTransforms (predicate coercion + shape)', () => {
|
||||
};
|
||||
expect(buildTransforms(config)).toEqual([]);
|
||||
});
|
||||
|
||||
it('drops a syntactically-invalid expression filter (mid-edit preview resilience)', () => {
|
||||
const t = buildTransforms(
|
||||
withFilters(
|
||||
filter({ mode: 'expression', expr: 'datum.value *' }), // half-typed → unparseable
|
||||
filter({ id: 'f2', mode: 'expression', expr: 'datum.value > 0' }), // valid stays
|
||||
),
|
||||
);
|
||||
expect(t).toEqual([{ filter: 'datum.value > 0' }]);
|
||||
});
|
||||
|
||||
it('drops a calculated field whose expression does not parse', () => {
|
||||
const config: BuilderConfig = {
|
||||
datasetName: 'D',
|
||||
mark: 'bar',
|
||||
encodings: {},
|
||||
calculates: [
|
||||
calc({ as: 'bad', expr: 'datum.a +' }),
|
||||
calc({ id: 'c2', as: 'ok', expr: 'datum.a' }),
|
||||
],
|
||||
};
|
||||
expect(buildTransforms(config)).toEqual([{ calculate: 'datum.a', as: 'ok' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildChartSpec — transform integration', () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import type { ColumnStats } from './profile';
|
||||
import { DISTINCT_CAP } from './profile';
|
||||
import type { ColumnType } from './type-inference';
|
||||
import { VEGA_LITE_SCHEMA_URL } from './snippet';
|
||||
import { validateExpression } from './expr-validate';
|
||||
|
||||
/** The five mark types the builder offers, in selector order (spec §06). */
|
||||
export const MARK_TYPES = ['bar', 'line', 'point', 'area', 'circle'] as const;
|
||||
@@ -835,25 +836,31 @@ function predicateObject(filter: BuilderFilter): Record<string, unknown> | null
|
||||
function filterTransformObject(filter: BuilderFilter): Record<string, unknown> | null {
|
||||
if (filter.mode === 'expression') {
|
||||
const expr = (filter.expr ?? '').trim();
|
||||
return expr === '' ? null : { filter: expr };
|
||||
// A syntactically-invalid expression is dropped like an empty one: a half-typed
|
||||
// `datum.x *` must not reach the renderer and blank the preview mid-edit — the
|
||||
// inline feedback already flags it. (Same mid-edit resilience as a partial predicate.)
|
||||
return expr === '' || !validateExpression(expr).valid ? null : { filter: expr };
|
||||
}
|
||||
const predicate = predicateObject(filter);
|
||||
return predicate ? { filter: predicate } : null;
|
||||
}
|
||||
|
||||
/** One calculate's `{ calculate, as }` transform entry, or `null` when incomplete. */
|
||||
/** One calculate's `{ calculate, as }` transform entry, or `null` when incomplete or unparseable. */
|
||||
function calculateTransformObject(calc: BuilderCalculate): Record<string, unknown> | null {
|
||||
const expr = calc.expr.trim();
|
||||
const as = calc.as.trim();
|
||||
return expr === '' || as === '' ? null : { calculate: expr, as };
|
||||
return expr === '' || as === '' || !validateExpression(expr).valid
|
||||
? null
|
||||
: { calculate: expr, as };
|
||||
}
|
||||
|
||||
/**
|
||||
* The complete top-level `transform` array for a configuration: every calculated
|
||||
* field first (so filters and encodings can reference the derived columns), then
|
||||
* every filter, each in the user's list order. Incomplete entries (a half-typed
|
||||
* filter, an unnamed calculate) are dropped so a configuration mid-edit still
|
||||
* produces a renderable spec. An empty result means no `transform` key is emitted.
|
||||
* every filter, each in the user's list order. Incomplete or unparseable entries (a
|
||||
* half-typed predicate, an unnamed calculate, an expression that doesn't parse) are
|
||||
* dropped so a configuration mid-edit still produces a renderable spec. An empty
|
||||
* result means no `transform` key is emitted.
|
||||
*/
|
||||
export function buildTransforms(config: BuilderConfig): Array<Record<string, unknown>> {
|
||||
const out: Array<Record<string, unknown>> = [];
|
||||
|
||||
Reference in New Issue
Block a user