mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import { beforeEach, describe, expect, test } from 'vitest';
|
||
import { createDataset } from '@core/dataset';
|
||
import { useDatasetStore } from '../stores/DatasetStore';
|
||
import { dataInfoAt } from './active-dataset';
|
||
|
||
/** Seed a library dataset, profiled from inline rows. */
|
||
function seed(name: string, rows: Record<string, unknown>[]): void {
|
||
useDatasetStore
|
||
.getState()
|
||
.add(createDataset({ name, data: rows, format: 'json', source: 'inline' }));
|
||
}
|
||
|
||
/** An offset inside the first occurrence of `marker` in `text`. */
|
||
function at(text: string, marker: string): number {
|
||
return text.indexOf(marker) + 1;
|
||
}
|
||
|
||
const names = (cols: ReadonlyArray<{ name: string }>) => cols.map((c) => c.name).sort();
|
||
|
||
beforeEach(() => {
|
||
useDatasetStore.getState().reset();
|
||
});
|
||
|
||
describe('dataInfoAt — view-scoped data context', () => {
|
||
test('resolves a library binding case-insensitively', () => {
|
||
seed('Sales', [{ region: 'W', revenue: 10 }]);
|
||
const text = '{ "data": { "name": "sales" }, "encoding": { "x": { "field": "MK" } } }';
|
||
const info = dataInfoAt(text, at(text, 'MK'));
|
||
expect(info.name).toBe('Sales');
|
||
expect(names(info.columnTypes)).toEqual(['region', 'revenue']);
|
||
});
|
||
|
||
test('each layer sees its own dataset, not a sibling view’s', () => {
|
||
seed('Sales', [{ region: 'W', revenue: 10 }]);
|
||
seed('Regions', [{ region: 'W', population: 100 }]);
|
||
const text = JSON.stringify(
|
||
{
|
||
layer: [
|
||
{ data: { name: 'Sales' }, encoding: { x: { field: 'sales_mk' } } },
|
||
{ data: { name: 'Regions' }, encoding: { y: { field: 'regions_mk' } } },
|
||
],
|
||
},
|
||
null,
|
||
2,
|
||
);
|
||
const inLayer0 = dataInfoAt(text, at(text, 'sales_mk'));
|
||
const inLayer1 = dataInfoAt(text, at(text, 'regions_mk'));
|
||
expect(inLayer0.name).toBe('Sales');
|
||
expect(names(inLayer0.columnTypes)).toEqual(['region', 'revenue']);
|
||
expect(inLayer1.name).toBe('Regions');
|
||
expect(names(inLayer1.columnTypes)).toEqual(['population', 'region']);
|
||
});
|
||
|
||
test('a child without its own data inherits the parent binding', () => {
|
||
seed('Sales', [{ region: 'W', revenue: 10 }]);
|
||
const text = JSON.stringify(
|
||
{ data: { name: 'Sales' }, layer: [{ encoding: { x: { field: 'mk' } } }] },
|
||
null,
|
||
2,
|
||
);
|
||
const info = dataInfoAt(text, at(text, '"mk"'));
|
||
expect(info.name).toBe('Sales');
|
||
expect(names(info.columnTypes)).toEqual(['region', 'revenue']);
|
||
});
|
||
|
||
test('profiles an inline binding (ghost dataset, no name)', () => {
|
||
const text =
|
||
'{ "data": { "values": [{ "a": 1, "b": 2 }] }, "encoding": { "x": { "field": "MK" } } }';
|
||
const info = dataInfoAt(text, at(text, 'MK'));
|
||
expect(info.name).toBeNull();
|
||
expect(names(info.columnTypes)).toEqual(['a', 'b']);
|
||
});
|
||
|
||
test('profiles a self-defined top-level datasets binding', () => {
|
||
const text = JSON.stringify(
|
||
{
|
||
datasets: { local: [{ c: 1, d: 2 }] },
|
||
data: { name: 'local' },
|
||
encoding: { x: { field: 'mk' } },
|
||
},
|
||
null,
|
||
2,
|
||
);
|
||
const info = dataInfoAt(text, at(text, '"mk"'));
|
||
expect(info.name).toBeNull();
|
||
expect(names(info.columnTypes)).toEqual(['c', 'd']);
|
||
});
|
||
|
||
test('offers ancestor-derived fields, not a sibling view’s', () => {
|
||
seed('Sales', [{ region: 'W', revenue: 10 }]);
|
||
const text = JSON.stringify(
|
||
{
|
||
data: { name: 'Sales' },
|
||
transform: [{ calculate: 'datum.revenue * 2', as: 'shared' }],
|
||
layer: [
|
||
{ transform: [{ calculate: 'x', as: 'inner' }], encoding: { x: { field: 'mk0' } } },
|
||
{ transform: [{ calculate: 'y', as: 'sibling' }], encoding: { y: { field: 'mk1' } } },
|
||
],
|
||
},
|
||
null,
|
||
2,
|
||
);
|
||
const fields = dataInfoAt(text, at(text, 'mk0')).fields.map((f) => f.name);
|
||
expect(fields).toContain('shared'); // top-level (inherited)
|
||
expect(fields).toContain('inner'); // this view
|
||
expect(fields).not.toContain('sibling'); // the other layer
|
||
});
|
||
|
||
test('a url/generator binding yields no static columns', () => {
|
||
const url = '{ "data": { "url": "x.csv" }, "encoding": { "x": { "field": "MK" } } }';
|
||
expect(dataInfoAt(url, at(url, 'MK')).columnTypes).toEqual([]);
|
||
});
|
||
});
|