import { getNodeChildCount } from '@flighthq/node/contract'; import { registerLottieScene2DDocumentImporter, registerRiveScene2DDocumentImporter, registerSvgScene2DDocumentImporter, } from './builtInScene2DDocumentImporters'; import { createScene2DDocumentFromBytes, createScene2DDocumentImporterRegistry, } from './scene2DDocumentImporterRegistry'; const encode = (source: string) => new TextEncoder().encode(source); describe('registerLottieScene2DDocumentImporter', () => { it('adds an opt-in Bodymovin codec without global registration', () => { const registry = createScene2DDocumentImporterRegistry(); registerLottieScene2DDocumentImporter(registry); const document = createScene2DDocumentFromBytes( encode('{"v":"5.7.0","fr":30,"ip":0,"op":30,"w":100,"h":100,"layers":[]}'), registry, ); expect(document?.sourceKind).toBe('lottie'); }); it('preserves the null sentinel when the format importer rejects a document', () => { const registry = createScene2DDocumentImporterRegistry(); registerLottieScene2DDocumentImporter(registry); expect( createScene2DDocumentFromBytes(encode('{invalid'), registry, { mimeType: 'application/lottie+json', url: null, }), ).toBeNull(); }); }); describe('registerRiveScene2DDocumentImporter', () => { it('adds an opt-in Rive codec that produces a document root', () => { const registry = createScene2DDocumentImporterRegistry(); registerRiveScene2DDocumentImporter(registry); const document = createScene2DDocumentFromBytes(riveBytes(), registry); expect(document?.sourceKind).toBe('rive'); expect(getNodeChildCount(document!.root)).toBe(1); }); // A .riv embeds its images, so the document hands them over as unresolved references rather than // decoding anything — resolveScene2DResources is what turns them into pixels. it('carries an embedded image out as an unresolved resource reference', () => { const registry = createScene2DDocumentImporterRegistry(); registerRiveScene2DDocumentImporter(registry); const document = createScene2DDocumentFromBytes(riveBytes(true), registry); expect(document!.imageResources).toHaveLength(1); expect(document!.imageResources[0].mimeType).toBe('image/png'); }); it('preserves the null sentinel for bytes that are not a Rive file', () => { const registry = createScene2DDocumentImporterRegistry(); registerRiveScene2DDocumentImporter(registry); expect(createScene2DDocumentFromBytes(encode('not a riv'), registry)).toBeNull(); }); }); describe('registerSvgScene2DDocumentImporter', () => { it('adds an opt-in SVG codec that produces a document root', () => { const registry = createScene2DDocumentImporterRegistry(); registerSvgScene2DDocumentImporter(registry); const document = createScene2DDocumentFromBytes( encode(''), registry, ); expect(document?.sourceKind).toBe('svg'); expect(getNodeChildCount(document!.root)).toBe(1); }); it('preserves the null sentinel when the format importer rejects a document', () => { const registry = createScene2DDocumentImporterRegistry(); registerSvgScene2DDocumentImporter(registry); expect( createScene2DDocumentFromBytes(encode(''), registry, { mimeType: 'image/svg+xml', url: null, }), ).toBeNull(); }); }); // A minimal well-formed .riv: the fingerprint, version 7, an empty table of contents, one named // artboard, and optionally an embedded PNG asset. function riveBytes(withImage = false): Uint8Array { const varUint = (value: number): number[] => { const out: number[] = []; let remaining = value; do { const group = remaining % 128; remaining = Math.floor(remaining / 128); out.push(remaining > 0 ? group + 128 : group); } while (remaining > 0); return out; }; const text = (key: number, value: string): number[] => { const encoded = Array.from(new TextEncoder().encode(value)); return [...varUint(key), ...varUint(encoded.length), ...encoded]; }; const out: number[] = [0x52, 0x49, 0x56, 0x45, ...varUint(7), ...varUint(0), ...varUint(0), 0]; if (withImage) { out.push(...varUint(105), ...text(203, 'sky'), 0); const png = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; out.push(...varUint(106), ...varUint(212), ...varUint(png.length), ...png, 0); } out.push(...varUint(1), ...text(4, 'Board'), 0); return new Uint8Array(out); }