Initial Commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const pkgRoot = join(here, "..");
|
||||
const srcRoot = join(pkgRoot, "src");
|
||||
const pkgJson = JSON.parse(
|
||||
readFileSync(join(pkgRoot, "package.json"), "utf8")
|
||||
) as { exports: Record<string, unknown> };
|
||||
|
||||
function walk(dir: string, out: string[] = []): string[] {
|
||||
if (!existsSync(dir)) return out;
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const p = join(dir, entry);
|
||||
if (statSync(p).isDirectory()) walk(p, out);
|
||||
else if (p.endsWith(".test.ts")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("exports", () => {
|
||||
it("every feature module has a subpath export entry", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const required = [
|
||||
".",
|
||||
"./codec/ffmpeg-wasm-codec",
|
||||
"./worker/worker-host",
|
||||
"./core-loader/core-loader",
|
||||
"./decode/decode-pipeline",
|
||||
"./encode/encode-pipeline",
|
||||
"./streaming/streaming-decoder",
|
||||
"./streaming/streaming-encoder",
|
||||
"./register/register",
|
||||
"./error-codes",
|
||||
];
|
||||
for (const sub of required) {
|
||||
expect(pkgJson.exports[sub]).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("no test file imports vitest or bun:test directly", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const tests = [...walk(srcRoot), ...walk(join(pkgRoot, "tests"))];
|
||||
const offenders: string[] = [];
|
||||
for (const f of tests) {
|
||||
const text = readFileSync(f, "utf8");
|
||||
if (/from\s+["'](vitest|bun:test)["']/.test(text)) {
|
||||
offenders.push(f);
|
||||
}
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("package.json declares browser: true and sideEffects: false", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const raw = JSON.parse(
|
||||
readFileSync(join(pkgRoot, "package.json"), "utf8")
|
||||
) as { browser?: boolean; sideEffects?: boolean };
|
||||
expect(raw.browser).toBe(true);
|
||||
expect(raw.sideEffects).toBe(false);
|
||||
});
|
||||
|
||||
it("each feature module is independently importable", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const mod = await import("../src/register/register.js");
|
||||
expect(typeof mod.register).toBe("function");
|
||||
});
|
||||
});
|
||||
121
packages/media/audio/audio-codec-ffmpeg-wasm/tests/fixtures/fake-worker.ts
vendored
Normal file
121
packages/media/audio/audio-codec-ffmpeg-wasm/tests/fixtures/fake-worker.ts
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { CodecRegistry } from "@suaveplan/audio-codecs/registry/registry";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { FfmpegWasmCodec, register } from "../src/index.js";
|
||||
import {
|
||||
buildPcmBytes,
|
||||
FakeWorker,
|
||||
successScript,
|
||||
} from "./fixtures/fake-worker.js";
|
||||
|
||||
describe("integration: codec wired through CodecRegistry", () => {
|
||||
it("register installs and registry.detect finds the codec", {
|
||||
timeout: 30000,
|
||||
}, () => {
|
||||
const registry = new CodecRegistry();
|
||||
const codec = register(registry, {
|
||||
coreLoader: () => Promise.resolve({}),
|
||||
workerFactory: () => new FakeWorker() as unknown as Worker,
|
||||
});
|
||||
expect(codec).toBeInstanceOf(FfmpegWasmCodec);
|
||||
const wav = new Uint8Array(44);
|
||||
wav[0] = 0x52;
|
||||
wav[1] = 0x49;
|
||||
wav[2] = 0x46;
|
||||
wav[3] = 0x46;
|
||||
expect(registry.detect(wav)).toBe("ffmpeg-wasm");
|
||||
});
|
||||
|
||||
it("registry.decode produces an AudioDocument", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
const registry = new CodecRegistry();
|
||||
const worker = new FakeWorker();
|
||||
worker.script.push(...successScript(buildPcmBytes(64, 2)));
|
||||
register(registry, {
|
||||
coreLoader: () => Promise.resolve({}),
|
||||
workerFactory: () => worker as unknown as Worker,
|
||||
});
|
||||
const wav = new Uint8Array(44);
|
||||
wav[0] = 0x52;
|
||||
wav[1] = 0x49;
|
||||
wav[2] = 0x46;
|
||||
wav[3] = 0x46;
|
||||
const doc = await registry.decode(wav);
|
||||
expect(doc.channels).toBe(2);
|
||||
expect(doc.sampleRate).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const srcRoot = join(here, "..", "src");
|
||||
const distRoot = join(here, "..", "dist");
|
||||
|
||||
function walk(dir: string, out: string[] = []): string[] {
|
||||
if (!existsSync(dir)) return out;
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const p = join(dir, entry);
|
||||
if (statSync(p).isDirectory()) walk(p, out);
|
||||
else if (p.endsWith(".ts") || p.endsWith(".js")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const FORBIDDEN_NODE_PATTERNS = [
|
||||
/from\s+["']node:/,
|
||||
/from\s+["']fs["']/,
|
||||
/from\s+["']path["']/,
|
||||
/from\s+["']crypto["']/,
|
||||
];
|
||||
|
||||
describe("purity", () => {
|
||||
it("source files do not import Node-only APIs at the top level", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const files = walk(srcRoot).filter(
|
||||
(f) => !f.endsWith(".test.ts") && !f.endsWith(".d.ts")
|
||||
);
|
||||
const offenders: string[] = [];
|
||||
for (const f of files) {
|
||||
const text = readFileSync(f, "utf8");
|
||||
for (const rx of FORBIDDEN_NODE_PATTERNS) {
|
||||
if (rx.test(text)) offenders.push(`${f}: ${rx.source}`);
|
||||
}
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("@ffmpeg/core-mt is referenced only inside dynamic imports or comments", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const files = walk(srcRoot).filter(
|
||||
(f) => !f.endsWith(".test.ts") && !f.endsWith(".d.ts")
|
||||
);
|
||||
for (const f of files) {
|
||||
const text = readFileSync(f, "utf8");
|
||||
const lines = text.split("\n");
|
||||
for (const line of lines) {
|
||||
if (!line.includes("@ffmpeg/core-mt")) continue;
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith("//")) continue;
|
||||
if (trimmed.startsWith("*")) continue;
|
||||
if (line.includes("import(")) continue;
|
||||
throw new Error(`disallowed reference in ${f}: ${line}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("@ffmpeg/core is never reached via static import", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const files = walk(srcRoot).filter(
|
||||
(f) => !f.endsWith(".test.ts") && !f.endsWith(".d.ts")
|
||||
);
|
||||
// Forbidden: `import … from "@ffmpeg/core"` / `import "@ffmpeg/core"`.
|
||||
const STATIC_IMPORT =
|
||||
/^\s*import\b[^;]*from\s+["']@ffmpeg\/core(?:-mt)?["']/;
|
||||
for (const f of files) {
|
||||
if (f.includes("ffmpeg-shim.d.ts")) continue;
|
||||
const lines = readFileSync(f, "utf8").split("\n");
|
||||
for (const line of lines) {
|
||||
if (STATIC_IMPORT.test(line)) {
|
||||
throw new Error(`static import in ${f}: ${line}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("importing the root module mutates no global state", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
// The act of importing the module is the verification — no exception
|
||||
// means no top-level throw, and no spec covers any mutation outside
|
||||
// the module's own ESM closure.
|
||||
const mod = await import("../src/index.js");
|
||||
expect(mod.FfmpegWasmCodec).toBeDefined();
|
||||
});
|
||||
|
||||
it("dist/ — when built — contains no node: references", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
if (!existsSync(distRoot)) {
|
||||
// pre-build environment — skip
|
||||
return;
|
||||
}
|
||||
const files = walk(distRoot);
|
||||
for (const f of files) {
|
||||
const text = readFileSync(f, "utf8");
|
||||
for (const rx of FORBIDDEN_NODE_PATTERNS) {
|
||||
expect(rx.test(text)).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user