ggwave.test.ts

4.7 KB · raw

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); // synthetic waveform48      },49      decode(_inst: number, samples: Float32Array | Int16Array) {50        decodeCalls.push(samples as Float32Array);51        // half the time, "succeed"; tests inject deterministic samples52        if (samples.length === 999) return null;53        return new Uint8Array(CHIRP_PAYLOAD_LENGTH); // empty-but-correct-length54      },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); // RX_AND_TX80  });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});