Files
suaveplan/packages/media/audio/audio-codec-ffmpeg-wasm/tests/fixtures/fake-worker.ts
2026-05-27 23:47:53 +01:00

122 lines
3.9 KiB
TypeScript

import type {
WorkerLike,
WorkerRequest,
WorkerResponse,
} from "../../src/worker/worker-host.types.js";
type MessageListener = (ev: MessageEvent) => void;
type ErrorListener = (ev: ErrorEvent) => void;
/**
* Deterministic `Worker` shim. Tests script the response sequence via the
* `script` array; each posted request consumes the next handler. Out-of-script
* requests are echoed back as `ok: false` with a fixed stderr so the host
* surfaces the matching error code.
*/
export class FakeWorker implements WorkerLike {
public posted: WorkerRequest[] = [];
public terminated = 0;
public messageListeners: MessageListener[] = [];
public errorListeners: ErrorListener[] = [];
public script: Array<(req: WorkerRequest) => WorkerResponse | "drop"> = [];
public postMessage(message: unknown): void {
const req = message as WorkerRequest;
this.posted.push(req);
const handler = this.script.shift();
if (!handler) {
queueMicrotask(() => {
this.emitMessage({
id: req.id,
ok: false,
stderr: "no-handler",
});
});
return;
}
const result = handler(req);
if (result === "drop") return;
queueMicrotask(() => this.emitMessage(result));
}
public emitMessage(data: unknown): void {
const ev = { data } as MessageEvent;
for (const l of this.messageListeners) l(ev);
}
public emitError(message: string, error?: Error): void {
const ev = { message, error } as unknown as ErrorEvent;
for (const l of this.errorListeners) l(ev);
}
public addEventListener(
type: "message" | "error",
listener: MessageListener | ErrorListener
): void {
if (type === "message")
this.messageListeners.push(listener as MessageListener);
else this.errorListeners.push(listener as ErrorListener);
}
public removeEventListener(
type: "message" | "error",
listener: MessageListener | ErrorListener
): void {
if (type === "message")
this.messageListeners = this.messageListeners.filter(
(l) => l !== listener
);
else
this.errorListeners = this.errorListeners.filter(
(l) => l !== listener
);
}
public terminate(): void {
this.terminated++;
}
}
/**
* Standard success script: `load` resolves, `writeFile` resolves, `run`
* resolves with the provided output bytes and stderr, `readFile` resolves
* with those bytes, both `deleteFile` calls resolve. Drop into the worker's
* `script` array.
*/
export function successScript(
outputBytes: Uint8Array,
stderr = ""
): Array<(req: WorkerRequest) => WorkerResponse> {
return [
(req) => ({ id: req.id, ok: true }), // load
(req) => ({ id: req.id, ok: true }), // writeFile
(req) => ({ id: req.id, ok: true, value: { stderr } }), // run
(req) => ({ id: req.id, ok: true, value: outputBytes }), // readFile
(req) => ({ id: req.id, ok: true }), // deleteFile input
(req) => ({ id: req.id, ok: true }), // deleteFile output
];
}
/**
* Build a deterministic `Float32Array` interleaved into `Uint8Array` form
* for use as the `readFile` payload.
*/
export function buildPcmBytes(
frames: number,
channels: number,
pattern: (frame: number, channel: number) => number = (f, c) =>
Math.sin((f * (c + 1)) / 100) * 0.5
): Uint8Array {
const interleaved = new Float32Array(frames * channels);
for (let f = 0; f < frames; f++) {
for (let c = 0; c < channels; c++) {
interleaved[f * channels + c] = pattern(f, c);
}
}
return new Uint8Array(
interleaved.buffer,
interleaved.byteOffset,
interleaved.byteLength
);
}