73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildCybersigil, GLYPHS } from './cybersigil';
|
|
|
|
/** Deterministic LCG so a given seed reproduces a given sigil. */
|
|
function seeded(seed: number): () => number {
|
|
let s = seed >>> 0;
|
|
return () => {
|
|
s = (s * 1664525 + 1013904223) >>> 0;
|
|
return s / 4294967296;
|
|
};
|
|
}
|
|
|
|
describe('buildCybersigil', () => {
|
|
it('is deterministic for a fixed RNG', () => {
|
|
const a = buildCybersigil({ rng: seeded(42) });
|
|
const b = buildCybersigil({ rng: seeded(42) });
|
|
expect(a).toBe(b);
|
|
});
|
|
|
|
it('varies with the seed', () => {
|
|
expect(buildCybersigil({ rng: seeded(1) })).not.toBe(buildCybersigil({ rng: seeded(2) }));
|
|
});
|
|
|
|
it('emits a single well-formed root svg', () => {
|
|
const svg = buildCybersigil({ rng: seeded(7) });
|
|
expect(svg.startsWith('<svg')).toBe(true);
|
|
expect(svg.endsWith('</svg>')).toBe(true);
|
|
expect(svg.match(/<svg/g)).toHaveLength(1);
|
|
// no formatting holes leaked into the path data
|
|
expect(svg).not.toMatch(/NaN|undefined|Infinity/);
|
|
});
|
|
|
|
it('is vertically mirrored (two halves, one flipped about x=0)', () => {
|
|
const svg = buildCybersigil({ rng: seeded(7) });
|
|
expect(svg.match(/class="cs-sig-half"/g)).toHaveLength(2);
|
|
expect(svg).toContain('transform="scale(-1 1)"');
|
|
});
|
|
|
|
it('every stroke is pathLength-normalised for the carve animation', () => {
|
|
const svg = buildCybersigil({ rng: seeded(99) });
|
|
const paths = svg.match(/<path\b[^>]*>/g) ?? [];
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
for (const p of paths) expect(p).toContain('pathLength="1"');
|
|
});
|
|
|
|
it('exposes a numeric, positive viewBox', () => {
|
|
const svg = buildCybersigil({ rng: seeded(7) });
|
|
const vb = svg.match(/viewBox="([^"]+)"/)?.[1] ?? '';
|
|
const nums = vb.split(/\s+/).map(Number);
|
|
expect(nums).toHaveLength(4);
|
|
expect(nums.every(Number.isFinite)).toBe(true);
|
|
expect(nums[2]).toBeGreaterThan(0); // width
|
|
expect(nums[3]).toBeGreaterThan(0); // height
|
|
});
|
|
|
|
it('honours the count option and stays bounded', () => {
|
|
const sparse = buildCybersigil({ count: 1, rng: seeded(5) });
|
|
const dense = buildCybersigil({ count: 9, rng: seeded(5) });
|
|
const n = (s: string) => (s.match(/<path/g) ?? []).length;
|
|
expect(n(dense)).toBeGreaterThan(n(sparse));
|
|
expect(n(dense)).toBeLessThanOrEqual(260); // MAX_PATHS(110) mirrored + ornaments
|
|
});
|
|
|
|
it('ships a non-empty glyph library with valid path data', () => {
|
|
expect(GLYPHS.length).toBeGreaterThan(0);
|
|
for (const g of GLYPHS) {
|
|
expect(g.w).toBeGreaterThan(0);
|
|
expect(g.h).toBeGreaterThan(0);
|
|
expect(g.d).toMatch(/^M/);
|
|
}
|
|
});
|
|
});
|