Files
astrolabe/src/core/chart-export.test.ts
T

133 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
chartExportFilename,
inlineReferencedDatasets,
MAX_BASENAME_LEN,
referencedDatasetNames,
snippetFileBasename,
} from './chart-export';
import { DatasetNotFoundError, type ResolvableDataset } from './rendering';
describe('snippetFileBasename', () => {
it('keeps the users words and case', () => {
expect(snippetFileBasename('Sales by Region')).toBe('Sales-by-Region');
});
it('swaps whitespace runs for a single dash', () => {
expect(snippetFileBasename(' a b c ')).toBe('a-b-c');
});
it('folds dots into the separator so the extension stays unambiguous', () => {
expect(snippetFileBasename('data.v1.final')).toBe('data-v1-final');
});
it('strips characters illegal on Windows / awkward in URLs', () => {
expect(snippetFileBasename('a/b\\c:d*e?f"g<h>i|j')).toBe('abcdefghij');
});
it('keeps non-latin letters (no ASCII folding — full-script support)', () => {
expect(snippetFileBasename('Продажи по региону')).toBe('Продажи-по-региону');
expect(snippetFileBasename('売上 グラフ')).toBe('売上-グラフ');
});
it('collapses and trims dashes', () => {
expect(snippetFileBasename('--a -- b--')).toBe('a-b');
});
it('falls back to "chart" when nothing usable remains', () => {
expect(snippetFileBasename('')).toBe('chart');
expect(snippetFileBasename(' ')).toBe('chart');
expect(snippetFileBasename('/// \\\\\\')).toBe('chart');
expect(snippetFileBasename('...')).toBe('chart');
});
it('caps the length and never leaves a trailing dash from the cut', () => {
const long = snippetFileBasename('x'.repeat(200));
expect(long.length).toBe(MAX_BASENAME_LEN);
// A name whose cap boundary lands on a dash must not end in one.
const dashy = snippetFileBasename('a'.repeat(MAX_BASENAME_LEN - 1) + ' bbbb');
expect(dashy.endsWith('-')).toBe(false);
});
it('drops control characters', () => {
// Build the control byte from a code point so no literal control char is in source.
expect(snippetFileBasename(`a${String.fromCharCode(1)}bcd`)).toBe('abcd');
});
});
describe('chartExportFilename', () => {
it('appends the format as the extension', () => {
expect(chartExportFilename('Sales by Region', 'png')).toBe('Sales-by-Region.png');
expect(chartExportFilename('Sales by Region', 'svg')).toBe('Sales-by-Region.svg');
expect(chartExportFilename('Sales by Region', 'vl.json')).toBe('Sales-by-Region.vl.json');
});
it('uses the "chart" fallback for an unusable name', () => {
expect(chartExportFilename('', 'png')).toBe('chart.png');
});
});
describe('referencedDatasetNames', () => {
it('returns the saved-dataset names a spec references, deduped', () => {
const spec = JSON.stringify({
layer: [
{ data: { name: 'sales' } },
{ data: { name: 'sales' } },
{ data: { name: 'costs' } },
],
});
expect(referencedDatasetNames(spec).sort()).toEqual(['costs', 'sales']);
});
it('excludes names the spec defines for itself via top-level datasets', () => {
const spec = JSON.stringify({
datasets: { local: [{ a: 1 }] },
data: { name: 'local' },
});
expect(referencedDatasetNames(spec)).toEqual([]);
});
it('returns [] for an inline-data spec (no references)', () => {
const spec = JSON.stringify({ data: { values: [{ a: 1 }] }, mark: 'bar' });
expect(referencedDatasetNames(spec)).toEqual([]);
});
it('returns [] for unparseable text rather than throwing', () => {
expect(referencedDatasetNames('{ not json')).toEqual([]);
});
});
describe('inlineReferencedDatasets', () => {
const sales: ResolvableDataset = {
name: 'sales',
data: [{ region: 'N', value: 10 }],
format: 'json',
source: 'inline',
};
const parse = (text: string) => JSON.parse(text) as { data?: unknown; width?: unknown };
it('replaces a named reference with the datasets inline values', () => {
const spec = JSON.stringify({ data: { name: 'sales' }, mark: 'bar' });
const out = parse(inlineReferencedDatasets(spec, [sales]));
expect(out.data).toEqual({ values: [{ region: 'N', value: 10 }] });
});
it('resolves the name case-insensitively (mirrors reference resolution)', () => {
const spec = JSON.stringify({ data: { name: 'Sales' }, mark: 'bar' });
const out = parse(inlineReferencedDatasets(spec, [sales]));
expect(out.data).toEqual({ values: [{ region: 'N', value: 10 }] });
});
it('leaves sizing as authored — no fit-mode applied', () => {
const spec = JSON.stringify({ data: { name: 'sales' }, width: 300, mark: 'bar' });
const out = parse(inlineReferencedDatasets(spec, [sales]));
expect(out.width).toBe(300);
});
it('throws DatasetNotFoundError when a referenced dataset is missing', () => {
const spec = JSON.stringify({ data: { name: 'missing' }, mark: 'bar' });
expect(() => inlineReferencedDatasets(spec, [sales])).toThrow(DatasetNotFoundError);
});
});