1import { describe, expect, it, beforeEach, afterEach } from "vitest";2import { CHIRP_PAYLOAD_LENGTH } from "./sync-types";3import {4 __setGgwaveModuleForTest,5 createChirpCodec,6 GgwaveProtocol,7 loadGgwave,8} from "./ggwave";910function fakeGgwaveModule() {11 const initParams: unknown[] = [];12 const encodeCalls: Array<{ payload: Uint8Array; protocol: number; volume: number }> = [];13 const decodeCalls: Float32Array[] = [];14 let nextInstance = 1;15 const freed: number[] = [];1617 return {18 initParams,19 encodeCalls,20 decodeCalls,21 freed,22 mod: {23 ProtocolId: { GGWAVE_PROTOCOL_ULTRASOUND_FASTEST: 5 },24 GGWAVE_OPERATING_MODE_RX: 1,25 GGWAVE_OPERATING_MODE_TX: 2,26 GGWAVE_OPERATING_MODE_RX_AND_TX: 3,27 getDefaultParameters() {28 return {29 sampleRateInp: 48000,30 sampleRateOut: 48000,31 samplesPerFrame: 1024,32 soundMarkerThreshold: 3,33 payloadLength: -1,34 operatingMode: 0,35 };36 },37 init(p: unknown) {38 initParams.push(p);39 return nextInstance++;40 },41 free(instance: number) {42 freed.push(instance);43 },44 encode(_inst: number, data: Uint8Array | string, protocol: number, volume: number) {45 const payload = typeof data === "string" ? new TextEncoder().encode(data) : data;46 encodeCalls.push({ payload, protocol, volume });47 return new Float32Array(payload.length * 100); 48 },49 decode(_inst: number, samples: Float32Array | Int16Array) {50 decodeCalls.push(samples as Float32Array);51 52 if (samples.length === 999) return null;53 return new Uint8Array(CHIRP_PAYLOAD_LENGTH); 54 },55 },56 };57}5859describe("ggwave loader", () => {60 beforeEach(() => __setGgwaveModuleForTest(null));61 afterEach(() => __setGgwaveModuleForTest(null));6263 it("loadGgwave returns the test-injected module", async () => {64 const fake = fakeGgwaveModule();65 __setGgwaveModuleForTest(fake.mod);66 const mod = await loadGgwave();67 expect(mod).toBe(fake.mod);68 });6970 it("createChirpCodec configures sample rate, payload length, RX+TX mode", async () => {71 const fake = fakeGgwaveModule();72 __setGgwaveModuleForTest(fake.mod);73 await createChirpCodec({ sampleRate: 44100 });74 expect(fake.initParams).toHaveLength(1);75 const p = fake.initParams[0] as Record<string, number>;76 expect(p.sampleRateInp).toBe(44100);77 expect(p.sampleRateOut).toBe(44100);78 expect(p.payloadLength).toBe(CHIRP_PAYLOAD_LENGTH);79 expect(p.operatingMode).toBe(3); 80 });8182 it("encode forwards the payload to ggwave with ultrasonic-fastest protocol and 10% volume by default", async () => {83 const fake = fakeGgwaveModule();84 __setGgwaveModuleForTest(fake.mod);85 const codec = await createChirpCodec({ sampleRate: 48000 });86 const payload = new Uint8Array(CHIRP_PAYLOAD_LENGTH);87 payload[0] = 0x50;88 const wave = codec.encode(payload);89 expect(wave.length).toBeGreaterThan(0);90 expect(fake.encodeCalls).toHaveLength(1);91 expect(fake.encodeCalls[0].payload).toEqual(payload);92 expect(fake.encodeCalls[0].protocol).toBe(GgwaveProtocol.ULTRASOUND_FASTEST);93 expect(fake.encodeCalls[0].volume).toBe(10);94 });9596 it("encode rejects wrong-length payload", async () => {97 const fake = fakeGgwaveModule();98 __setGgwaveModuleForTest(fake.mod);99 const codec = await createChirpCodec({ sampleRate: 48000 });100 expect(() => codec.encode(new Uint8Array(63))).toThrow();101 });102103 it("decode forwards samples and returns the payload bytes", async () => {104 const fake = fakeGgwaveModule();105 __setGgwaveModuleForTest(fake.mod);106 const codec = await createChirpCodec({ sampleRate: 48000 });107 const samples = new Float32Array(2048);108 const decoded = codec.decode(samples);109 expect(decoded).toBeInstanceOf(Uint8Array);110 expect(decoded!.length).toBe(CHIRP_PAYLOAD_LENGTH);111 });112113 it("decode returns null when ggwave returns null (no chirp detected)", async () => {114 const fake = fakeGgwaveModule();115 __setGgwaveModuleForTest(fake.mod);116 const codec = await createChirpCodec({ sampleRate: 48000 });117 const samples = new Float32Array(999);118 expect(codec.decode(samples)).toBeNull();119 });120121 it("free is idempotent and prevents further encode/decode", async () => {122 const fake = fakeGgwaveModule();123 __setGgwaveModuleForTest(fake.mod);124 const codec = await createChirpCodec({ sampleRate: 48000 });125 codec.free();126 codec.free();127 expect(fake.freed).toEqual([1]);128 expect(() => codec.encode(new Uint8Array(CHIRP_PAYLOAD_LENGTH))).toThrow();129 expect(() => codec.decode(new Float32Array(0))).toThrow();130 });131});