mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Chart builder: filter/calculate transforms, data preview, and inline expression validation
This commit is contained in:
@@ -162,4 +162,131 @@ describe('ChartBuilderModal', () => {
|
||||
});
|
||||
expect(container.textContent).toContain('No dataset loaded');
|
||||
});
|
||||
|
||||
test('a complete filter row reaches the renderer as a top-level transform (1C)', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { renderSpec } = await import('../services/chart-renderer');
|
||||
vi.mocked(renderSpec).mockClear();
|
||||
const ds = createDataset({
|
||||
name: 'Sales',
|
||||
data: [
|
||||
{ region: 'N', revenue: 100 },
|
||||
{ region: 'S', revenue: 50 },
|
||||
],
|
||||
format: 'json',
|
||||
source: 'inline',
|
||||
now: T,
|
||||
});
|
||||
useDatasetStore.getState().add(ds);
|
||||
const id = useDatasetStore.getState().datasets[0].id;
|
||||
const store = useChartBuilderStore.getState();
|
||||
store.init(id);
|
||||
store.addFilter();
|
||||
const fid = useChartBuilderStore.getState().config.filters![0].id;
|
||||
store.setFilterField(fid, 'revenue');
|
||||
store.updateFilter(fid, { op: 'gt', value: '60' });
|
||||
|
||||
await act(async () => {
|
||||
root.render(<ChartBuilderModal />);
|
||||
await Promise.resolve();
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(400); // drive the debounced preview render
|
||||
});
|
||||
|
||||
expect(container.querySelector('select[aria-label="Filter operator"]')).toBeTruthy();
|
||||
const calls = vi.mocked(renderSpec).mock.calls;
|
||||
const lastSpec = calls[calls.length - 1][1] as { transform?: unknown };
|
||||
expect(lastSpec.transform).toEqual([{ filter: { field: 'revenue', gt: 60 } }]);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('the data preview discloses the dataset rows on demand (1D)', async () => {
|
||||
const ds = createDataset({
|
||||
name: 'Sales',
|
||||
data: [{ region: 'North', revenue: 100 }],
|
||||
format: 'json',
|
||||
source: 'inline',
|
||||
now: T,
|
||||
});
|
||||
useDatasetStore.getState().add(ds);
|
||||
const id = useDatasetStore.getState().datasets[0].id;
|
||||
useChartBuilderStore.getState().init(id);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<ChartBuilderModal />);
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const toggle = Array.from(container.querySelectorAll('button')).find((b) =>
|
||||
b.textContent?.includes('Preview rows'),
|
||||
);
|
||||
expect(toggle).toBeTruthy();
|
||||
expect(container.querySelector('table')).toBeNull(); // collapsed by default
|
||||
|
||||
await act(async () => {
|
||||
toggle!.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(container.querySelector('table')).toBeTruthy();
|
||||
expect(container.textContent).toContain('region');
|
||||
expect(container.textContent).toContain('North');
|
||||
});
|
||||
|
||||
test('an invalid expression-mode filter surfaces an inline parser error (1E)', async () => {
|
||||
const ds = createDataset({
|
||||
name: 'S',
|
||||
data: [{ a: 1 }],
|
||||
format: 'json',
|
||||
source: 'inline',
|
||||
now: T,
|
||||
});
|
||||
useDatasetStore.getState().add(ds);
|
||||
const id = useDatasetStore.getState().datasets[0].id;
|
||||
const store = useChartBuilderStore.getState();
|
||||
store.init(id);
|
||||
store.addFilter();
|
||||
const fid = useChartBuilderStore.getState().config.filters![0].id;
|
||||
store.setFilterMode(fid, 'expression');
|
||||
store.updateFilter(fid, { expr: 'datum.a *' });
|
||||
|
||||
await act(async () => {
|
||||
root.render(<ChartBuilderModal />);
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const alert = container.querySelector('[role="alert"]');
|
||||
expect(alert?.textContent).toMatch(/nexpected|Invalid/);
|
||||
});
|
||||
|
||||
test('the Vega expression reference shows only when an expression is in play (1E)', async () => {
|
||||
const ds = createDataset({
|
||||
name: 'S',
|
||||
data: [{ a: 1 }],
|
||||
format: 'json',
|
||||
source: 'inline',
|
||||
now: T,
|
||||
});
|
||||
useDatasetStore.getState().add(ds);
|
||||
const id = useDatasetStore.getState().datasets[0].id;
|
||||
useChartBuilderStore.getState().init(id);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<ChartBuilderModal />);
|
||||
await Promise.resolve();
|
||||
});
|
||||
const refLink = () =>
|
||||
Array.from(container.querySelectorAll('a')).find((a) =>
|
||||
a.textContent?.includes('Vega expression'),
|
||||
);
|
||||
expect(refLink()).toBeUndefined(); // no expression yet → no reference link
|
||||
|
||||
await act(async () => {
|
||||
useChartBuilderStore.getState().addCalculate(); // a calculated field is an expression
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(refLink()).toBeDefined();
|
||||
expect(refLink()!.getAttribute('href')).toContain('vega.github.io');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user