feat(01-08): install ts-ebml + webm-muxer; pin SW-compat via deps test
- Add ts-ebml ^3.0.2 (parse half) and webm-muxer ^5.1.4 (write half) per
CONTEXT.md amendment D-14-remux; both MIT, both verified SW-compatible
in the d13 debug-session library survey.
- tests/background/webm-remux-deps.test.ts pins two contracts:
(a) named exports surface (Muxer + ArrayBufferTarget + Decoder).
(b) both libraries import cleanly when window/document are absent on
globalThis — guards the published dist against accidentally
acquiring DOM globals on the hot path that would crash the
Chrome service-worker runtime.
- Note: webm-muxer 5.1.4 upstream-deprecated in favor of Mediabunny; the
pinned version still meets the d13 architectural requirement
(single-EBML output via addVideoChunkRaw). Migration to Mediabunny is
out of scope for Plan 01-08 and would require a new ADR.
- Baseline 53 GREEN + 2 new GREEN; tsc clean; 2 webm-playback duration
RED still pending (drive to GREEN in Tasks 3-5).
This commit is contained in:
137
tests/background/webm-remux-deps.test.ts
Normal file
137
tests/background/webm-remux-deps.test.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
// tests/background/webm-remux-deps.test.ts
|
||||
//
|
||||
// Plan 01-08 Task 1: SW-compatibility + presence contract for the two new
|
||||
// runtime dependencies that the WebM remux pipeline rests on. Pins the
|
||||
// architectural commitment that landed in CONTEXT.md amendment D-14-remux:
|
||||
// - `ts-ebml` ^3.0.2 (MIT, parses each VideoSegment's EBML structure)
|
||||
// - `webm-muxer` ^5.1.4 (MIT, writes the single-EBML-headered output)
|
||||
//
|
||||
// Both libraries were surveyed in `.planning/debug/d13-multi-ebml-concat-
|
||||
// unplayable.md` (Evidence/library-survey, lines 380-410). They are pure
|
||||
// JS, pure ESM/CJS, and were grep-verified at survey time to contain no
|
||||
// hard DOM-global references on the hot path. Chrome's service-worker
|
||||
// runtime (where `remuxSegments()` will execute) does not provide
|
||||
// `window` or `document`; this test pins that compat at the
|
||||
// devDependency-import surface so a future bump that accidentally adds
|
||||
// a DOM global is caught at test time rather than at runtime in a
|
||||
// production SW.
|
||||
//
|
||||
// Test 1 asserts named-export presence (RED until `npm install` lands).
|
||||
// Test 2 asserts both libraries load under default Node globals without
|
||||
// referencing `window` or `document` synchronously at import time.
|
||||
//
|
||||
// Skip discipline: none — these are pure import-shape tests, no external
|
||||
// binaries, no fixtures. Vitest's default Node environment is sufficient.
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
interface GlobalSnapshot {
|
||||
window: unknown;
|
||||
document: unknown;
|
||||
hadWindow: boolean;
|
||||
hadDocument: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the current values of `window` and `document` on `globalThis`
|
||||
* so they can be restored after a test that deletes them. Vitest's
|
||||
* `vi.stubGlobal` is not used here because we want to assert the
|
||||
* absence of the globals at import time, not just stub them.
|
||||
*
|
||||
* @returns Snapshot the caller passes back to {@link restoreGlobals}.
|
||||
*/
|
||||
function snapshotGlobals(): GlobalSnapshot {
|
||||
const g = globalThis as unknown as Record<string, unknown>;
|
||||
return {
|
||||
window: g.window,
|
||||
document: g.document,
|
||||
hadWindow: 'window' in g,
|
||||
hadDocument: 'document' in g,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the globals captured by {@link snapshotGlobals}. Idempotent.
|
||||
*
|
||||
* @param snap - Snapshot returned by {@link snapshotGlobals}.
|
||||
*/
|
||||
function restoreGlobals(snap: GlobalSnapshot): void {
|
||||
const g = globalThis as unknown as Record<string, unknown>;
|
||||
if (snap.hadWindow) {
|
||||
g.window = snap.window;
|
||||
} else {
|
||||
delete g.window;
|
||||
}
|
||||
if (snap.hadDocument) {
|
||||
g.document = snap.document;
|
||||
} else {
|
||||
delete g.document;
|
||||
}
|
||||
}
|
||||
|
||||
describe('webm-remux dependencies (Plan 01-08 Task 1)', () => {
|
||||
it('exports Muxer + ArrayBufferTarget + Decoder', async () => {
|
||||
// Dynamic import so a missing package surfaces as a precise test
|
||||
// failure ("Cannot find module 'webm-muxer'") rather than a Vitest
|
||||
// collection error that hides which dependency is the cause.
|
||||
const webmMuxer = await import('webm-muxer');
|
||||
expect(webmMuxer.Muxer).toBeDefined();
|
||||
expect(webmMuxer.ArrayBufferTarget).toBeDefined();
|
||||
|
||||
const tsEbml = await import('ts-ebml');
|
||||
expect(tsEbml.Decoder).toBeDefined();
|
||||
});
|
||||
|
||||
describe('loads under default Node globals without DOM-global ReferenceErrors', () => {
|
||||
let snap: GlobalSnapshot;
|
||||
|
||||
beforeEach(() => {
|
||||
snap = snapshotGlobals();
|
||||
const g = globalThis as unknown as Record<string, unknown>;
|
||||
delete g.window;
|
||||
delete g.document;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreGlobals(snap);
|
||||
});
|
||||
|
||||
it('webm-muxer + ts-ebml do not throw on import when window/document are absent', async () => {
|
||||
// The Chrome service-worker runtime provides neither `window` nor
|
||||
// `document`. If either library's published dist references one
|
||||
// of these synchronously at module evaluation time (e.g. a UMD
|
||||
// wrapper falling through to `window`), the import below would
|
||||
// throw a ReferenceError and this test would fail with a clear
|
||||
// signal.
|
||||
//
|
||||
// ts-ebml's UMD wrapper does contain a `typeof window` check
|
||||
// with a `self`/`global` fallback per the d13 library survey
|
||||
// — `typeof` does NOT throw on undeclared identifiers per
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
|
||||
// so the fallback path resolves cleanly.
|
||||
//
|
||||
// webm-muxer is documented zero-DOM-ref.
|
||||
let webmMuxerError: unknown = null;
|
||||
try {
|
||||
await import('webm-muxer');
|
||||
} catch (e) {
|
||||
webmMuxerError = e;
|
||||
}
|
||||
expect(
|
||||
webmMuxerError,
|
||||
`webm-muxer threw at import time without window/document: ${String(webmMuxerError)}`,
|
||||
).toBeNull();
|
||||
|
||||
let tsEbmlError: unknown = null;
|
||||
try {
|
||||
await import('ts-ebml');
|
||||
} catch (e) {
|
||||
tsEbmlError = e;
|
||||
}
|
||||
expect(
|
||||
tsEbmlError,
|
||||
`ts-ebml threw at import time without window/document: ${String(tsEbmlError)}`,
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user