import { describe, expect, it } from "@suaveplan/testing/runner"; import * as fc from "fast-check"; import { AudioDocument } from "../src/document/document.js"; import { BLOCK_SIZE } from "../src/page/page.js"; import { PlanEngine } from "../src/plan/plan-engine.js"; import { OpRegistry } from "../src/registry/op-registry.js"; function makeDoc( sourceLength: number, channels: number, sampleRate: number ): AudioDocument { const pageSize = 48000; const pages = []; for (let s = 0; s < sourceLength; s += pageSize) { const len = Math.min(pageSize, sourceLength - s); pages.push({ startSample: s, channelData: Array.from( { length: channels }, () => new Float32Array(len) ), }); } return new AudioDocument({ sampleRate, channels, sourceLength, pages, edits: [], }); } describe("Property: identity edit preserves plan length", () => { it("passes with random source lengths and sample rates", { timeout: 30000, }, () => { fc.assert( fc.property( fc.integer({ min: 1000, max: 500_000 }), fc.integer({ min: 1, max: 8 }), fc.integer({ min: 8000, max: 192_000 }), (sourceLength, channels, sampleRate) => { const doc = makeDoc(sourceLength, channels, sampleRate); const registry = OpRegistry.createWithDefaults(); const engine = new PlanEngine(registry); const before = engine.compile(doc, true); doc.run({ type: "speed", opts: { rate: 1 } }); const after = engine.compile(doc, true); expect(after.totalLen).toBe(before.totalLen); } ) ); }); }); describe("Property: plan compilation is deterministic", () => { it("plan compilation is deterministic", { timeout: 30000 }, () => { fc.assert( fc.property( fc.integer({ min: 1000, max: 200_000 }), fc.integer({ min: 1, max: 4 }), fc.integer({ min: 8000, max: 96_000 }), fc.integer({ min: 1, max: 4 }), (sourceLength, channels, sampleRate, repeatTimes) => { const doc = makeDoc(sourceLength, channels, sampleRate); doc.run({ type: "repeat", opts: { times: repeatTimes } }); doc.run({ type: "gain", opts: { gain: 0.5 } }); const registry = OpRegistry.createWithDefaults(); const engine = new PlanEngine(registry); const first = engine.compile(doc, true); const second = engine.compile(doc, true); expect(second).toEqual(first); } ) ); }); }); describe("Property: stream yields exact total sample count", () => { it("stream yields exact total sample count", { timeout: 30000, }, async () => { await fc.assert( fc.asyncProperty( fc.integer({ min: 1000, max: 200_000 }), fc.integer({ min: 1, max: 4 }), fc.integer({ min: 1, max: BLOCK_SIZE }), async (sourceLength, channels, blockSize) => { const doc = makeDoc(sourceLength, channels, 48000); const registry = OpRegistry.createWithDefaults(); const engine = new PlanEngine(registry); const plan = engine.compile(doc, false); let totalSamples = 0; for await (const block of doc.stream({ blockSize })) { totalSamples += block[0]?.length ?? 0; } expect(totalSamples).toBe(plan.totalLen); } ) ); }); }); describe("Property: clone preserves structure with new id", () => { it("clone toJSON equals original toJSON", { timeout: 30000 }, () => { // The property under test is structural — `clone().toJSON()` equals // the original's `toJSON()` modulo `id`. The doc's *size* is // irrelevant to that invariant, so cap inputs small to keep the // fast-check sweep inside the 30 s budget. The other three // properties in this file deliberately exercise large source // lengths because they probe size-dependent code paths (plan // compilation, streaming); this one does not. fc.assert( fc.property( fc.integer({ min: 1000, max: 10_000 }), fc.integer({ min: 1, max: 4 }), fc.integer({ min: 8000, max: 96_000 }), (sourceLength, channels, sampleRate) => { const doc = makeDoc(sourceLength, channels, sampleRate); doc.run({ type: "trim", opts: { start: 0, end: sourceLength }, }); const cloned = doc.clone(); const { id: originalId, ...originalRest } = doc.toJSON(); const { id: clonedId, ...clonedRest } = cloned.toJSON(); // The clone gets a fresh id but is otherwise an exact // structural copy — toJSON matches on every other field. expect(clonedId).not.toBe(originalId); expect(clonedRest).toEqual(originalRest); } ), { numRuns: 25 } ); }); });