Files
tts-workshop/packages/bgm/src/variants.test.ts
T
hypercross 9b5223686e feat(bgm): allow $variants to take multiple csv sources
Expand $variants to accept an array of csv paths, concatenating their
rows. Detect a path by a .csv suffix on the first line instead of a
newline, so inline csv and paths are self-documenting and the rule
applies uniformly to single values and array elements.
2026-08-10 14:06:36 +08:00

90 lines
3.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { parseCsvData, expandVariants } from './variants.js';
import type { DefFile } from './types.js';
function defFile(name: string, text: string): DefFile {
return { name, text, source: name, kind: 'csv' };
}
describe('parseCsvData', () => {
it('parses the spec example with an empty array', () => {
const csv = [
'name,parents',
'string,string[]',
'clark,[jonathan;martha]',
'bruce,[]',
].join('\n');
const { rows, header } = parseCsvData(csv, 'test');
expect(header).toEqual(['name', 'parents']);
expect(rows).toEqual([
{ name: 'clark', parents: ['jonathan', 'martha'] },
{ name: 'bruce', parents: [] },
]);
});
it('parses a crop tuple', () => {
const csv = [
'id,faceCrop',
'string,[number;number;number;number]',
'fish,[0;0;5;2]',
'grain,[1;0;5;2]',
].join('\n');
const { rows } = parseCsvData(csv, 'test');
expect(rows).toEqual([
{ id: 'fish', faceCrop: [0, 0, 5, 2] },
{ id: 'grain', faceCrop: [1, 0, 5, 2] },
]);
});
it('throws a BgmError on a type mismatch', () => {
const csv = ['n', 'number', 'not-a-number'].join('\n');
expect(() => parseCsvData(csv, 'test')).toThrow(/Invalid CSV/);
});
it('allows a header and schema with no data rows', () => {
const { rows } = parseCsvData('a\nstring', 'test');
expect(rows).toEqual([]);
});
});
describe('expandVariants', () => {
it('parses inline CSV when the first line does not end in .csv', () => {
const rows = expandVariants('a,b\nstring,number\nx,1', 'pkg/def.yaml', new Map(), 'src');
expect(rows).toEqual([{ a: 'x', b: 1 }]);
});
it('resolves a path against the def file directory', () => {
const defs = new Map<string, DefFile[]>([
['pkg/parts/seats.csv', [defFile('pkg/parts/seats.csv', 'seat\nnumber\n0\n1')]],
]);
const rows = expandVariants('./seats.csv', 'pkg/parts/board.yaml', defs, 'src');
expect(rows).toEqual([{ seat: 0 }, { seat: 1 }]);
});
it('concatenates rows from an array of csv paths', () => {
const defs = new Map<string, DefFile[]>([
['pkg/parts/a.csv', [defFile('pkg/parts/a.csv', 'id\nstring\nred')]],
['pkg/parts/b.csv', [defFile('pkg/parts/b.csv', 'id\nstring\nblack')]],
]);
const rows = expandVariants(['./a.csv', './b.csv'], 'pkg/parts/board.yaml', defs, 'src');
expect(rows).toEqual([{ id: 'red' }, { id: 'black' }]);
});
it('throws when the referenced csv is missing', () => {
expect(() => expandVariants('./nope.csv', 'pkg/def.yaml', new Map(), 'src')).toThrow(
/CSV not found/,
);
});
it('throws when $variants is not a string or string array', () => {
expect(() => expandVariants(42, 'pkg/def.yaml', new Map(), 'src')).toThrow(
/must be a path or inline CSV string, or an array of them/,
);
});
it('throws on an empty $variants array', () => {
expect(() => expandVariants([], 'pkg/def.yaml', new Map(), 'src')).toThrow(
/must not be empty/,
);
});
});