Implement fit-mode rendering contract with container sizing and pane re-fit

This commit is contained in:
2026-06-05 10:45:41 +03:00
parent f4253f50ca
commit 411bfbc6c2
13 changed files with 552 additions and 73 deletions
+70 -5
View File
@@ -14,19 +14,84 @@ describe('prepareSpecForRender', () => {
const spec = {
data: { values: [{ a: 1 }] },
mark: 'bar',
width: 200,
height: 100,
encoding: { x: { field: 'a', type: 'quantitative' } },
};
const before = structuredClone(spec);
const out = prepareSpecForRender(spec, { fitMode: 'width' });
// Mutating the output must not touch the input.
(out as { mark: string }).mark = 'point';
// A mode that both sets and removes sizing — the most invasive transform.
prepareSpecForRender(spec, { fitMode: 'width' });
expect(spec).toEqual(before);
});
test('M1 is a faithful pass-through of the spec content', () => {
test('Original (default) leaves sizing untouched', () => {
const spec = { $schema: 'x', mark: 'line', width: 200, height: 100 };
expect(prepareSpecForRender(spec)).toEqual(spec);
expect(prepareSpecForRender(spec, { fitMode: 'default' })).toEqual(spec);
});
});
describe('prepareSpecForRender — fit modes (spec §04 Rendering Contract step 2)', () => {
const base = { mark: 'bar', width: 200, height: 100 };
test('Width: width→container, height removed', () => {
const out = prepareSpecForRender(base, { fitMode: 'width' }) as Record<string, unknown>;
expect(out.width).toBe('container');
expect('height' in out).toBe(false);
});
test('Height: height→container, width removed', () => {
const out = prepareSpecForRender(base, { fitMode: 'height' }) as Record<string, unknown>;
expect(out.height).toBe('container');
expect('width' in out).toBe(false);
});
test('Full: both dimensions→container', () => {
const out = prepareSpecForRender(base, { fitMode: 'full' }) as Record<string, unknown>;
expect(out.width).toBe('container');
expect(out.height).toBe('container');
});
test('adds container sizing even when the spec declares no width/height', () => {
const out = prepareSpecForRender({ mark: 'point' }, { fitMode: 'full' }) as Record<
string,
unknown
>;
expect(out).toEqual({ mark: 'point', width: 'container', height: 'container' });
});
test('recurses into layered sub-specs', () => {
const spec = {
layer: [
{ mark: 'bar', width: 50, height: 50 },
{ mark: 'line', height: 50 },
],
};
const out = prepareSpecForRender(spec, { fitMode: 'full' }) as unknown as {
width: string;
height: string;
layer: Array<Record<string, unknown>>;
};
expect(out.width).toBe('container');
expect(out.height).toBe('container');
expect(out.layer[0]).toMatchObject({ width: 'container', height: 'container' });
expect(out.layer[1]).toMatchObject({ width: 'container', height: 'container' });
});
test('recurses into concat arrays and a child spec (facet/repeat)', () => {
const spec = {
facet: { field: 'g', type: 'nominal' },
spec: {
hconcat: [{ mark: 'bar', height: 80 }, { mark: 'point' }],
},
};
const out = prepareSpecForRender(spec, { fitMode: 'width' }) as unknown as {
spec: { hconcat: Array<Record<string, unknown>> };
};
for (const child of out.spec.hconcat) {
expect(child.width).toBe('container');
expect('height' in child).toBe(false);
}
});
});