import { describe, expect, it } from 'vitest'; import { strFromU8, strToU8, unzipSync, zipSync } from 'fflate'; import { useDocument } from '../../store/document'; import { serializeDocument } from '../json'; import { importJsonReplace, readNumFile } from '../operations'; import { NUM_MIMETYPE, NumFileError, packNum, unpackNum } from '../num'; describe('.num files', () => { it('round-trip the document JSON exactly', async () => { const s = useDocument(); s.resetToSeed(); const json = serializeDocument(s.snapshot()); const bytes = packNum(json); expect(unpackNum(bytes)).toBe(json); // and import back into the store like a .json file s.newDocument(); importJsonReplace(s, await readNumFile(bytes.slice().buffer)); expect(serializeDocument(s.snapshot())).toBe(json); }); it('are smaller than the JSON they carry', () => { const s = useDocument(); s.resetToSeed(); const json = serializeDocument(s.snapshot()); expect(packNum(json).length).toBeLessThan(json.length); }); it('start with an uncompressed mimetype entry, plus meta.json with an ISO timestamp', () => { const bytes = packNum('{}', new Date('2026-09-28T10:00:00Z')); // local file header (30 bytes) + "mimetype" (8) -> the stored content expect(strFromU8(bytes.subarray(30, 38))).toBe('mimetype'); expect(strFromU8(bytes.subarray(38, 38 + NUM_MIMETYPE.length))).toBe(NUM_MIMETYPE); const meta = JSON.parse(strFromU8(unzipSync(bytes)['meta.json'])); expect(meta).toEqual({ format: 'numolus', formatVersion: 1, created: '2026-09-28T10:00:00.000Z' }); }); it('reject what is not a numolus file', () => { expect(() => unpackNum(strToU8('{"version":2}'))).toThrow(NumFileError); expect(() => unpackNum(zipSync({ mimetype: strToU8('application/zip') }))).toThrow(/Keine numolus-Datei/); expect(() => unpackNum(zipSync({ mimetype: strToU8(NUM_MIMETYPE) }))).toThrow(/kein document.json/); const newer = zipSync({ mimetype: strToU8(NUM_MIMETYPE), 'meta.json': strToU8('{"formatVersion": 99}'), 'document.json': strToU8('{}'), }); expect(() => unpackNum(newer)).toThrow(/neueren numolus-Version/); }); });