import { describe, it, expect, vi, beforeEach } from "vitest"; import { formatErrorForSender, synchronizeSegmentSinksOnObservation, updateCodecSupportInWorkerMode, extractExternalPlugins, } from "../utils"; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ const { mockFormatError, mockMediaSource_, mockCreateRepresentationFilter } = vi.hoisted( () => { return { mockFormatError: vi.fn(), mockMediaSource_: { isTypeSupported: vi.fn() }, mockCreateRepresentationFilter: vi.fn(), }; }, ); vi.mock("../../../errors", () => ({ formatError: mockFormatError, })); vi.mock("../../../compat/browser_compatibility_types", () => ({ default: { MediaSource_: mockMediaSource_, }, })); vi.mock("../../../manifest", () => ({ createRepresentationFilterFromFnString: mockCreateRepresentationFilter, })); beforeEach(() => { vi.resetAllMocks(); }); describe("formatErrorForSender", () => { it("should call formatError with the error and default options, then serialize", () => { const mockSerialize = vi .fn() .mockReturnValue({ code: "NONE", message: "An unknown error" }); mockFormatError.mockReturnValue({ serialize: mockSerialize }); const result = formatErrorForSender(new Error("test")); expect(mockFormatError).toHaveBeenCalledWith(new Error("test"), { defaultCode: "NONE", defaultReason: "An unknown error stopped content playback.", }); expect(mockSerialize).toHaveBeenCalled(); expect(result).toEqual({ code: "NONE", message: "An unknown error" }); }); }); describe("synchronizeSegmentSinksOnObservation", () => { it("should call synchronizeInventory for initialized segment sinks", () => { const syncInventory = vi.fn(); const segmentSinksStore = { getStatus: vi.fn((type: string) => { if (type === "video") { return { type: "initialized", value: { synchronizeInventory: syncInventory } }; } return { type: "not-initialized" }; }), } as any; const observation = { buffered: { video: [{ start: 0, end: 10 }], audio: [], text: [], }, } as any; synchronizeSegmentSinksOnObservation(observation, segmentSinksStore); expect(syncInventory).toHaveBeenCalledTimes(1); expect(syncInventory).toHaveBeenCalledWith([{ start: 0, end: 10 }]); }); it("should call synchronizeInventory for all initialized sinks with their buffered ranges", () => { const videoSync = vi.fn(); const audioSync = vi.fn(); const textSync = vi.fn(); const segmentSinksStore = { getStatus: vi.fn((type: string) => { if (type === "video") { return { type: "initialized", value: { synchronizeInventory: videoSync } }; } if (type === "audio") { return { type: "initialized", value: { synchronizeInventory: audioSync } }; } if (type === "text") { return { type: "initialized", value: { synchronizeInventory: textSync } }; } return { type: "not-initialized" }; }), } as any; const observation = { buffered: { video: [{ start: 0, end: 5 }], audio: [{ start: 1, end: 6 }], text: undefined, }, } as any; synchronizeSegmentSinksOnObservation(observation, segmentSinksStore); expect(videoSync).toHaveBeenCalledWith([{ start: 0, end: 5 }]); expect(audioSync).toHaveBeenCalledWith([{ start: 1, end: 6 }]); expect(textSync).toHaveBeenCalledWith([]); // undefined falls back to [] }); }); describe("updateCodecSupportInWorkerMode", () => { it("should do nothing if MediaSource_ is null", async () => { // Re-import with MediaSource_ as null - we test indirectly // by checking that isTypeSupported is never called when MediaSource_ is null // This test verifies the null-guard works via a manifest with no periods const manifest = { periods: [] } as any; // Since we can't easily change the module-level import, we verify normal flow updateCodecSupportInWorkerMode(manifest); expect(mockMediaSource_.isTypeSupported).not.toHaveBeenCalled(); }); it("should set isCodecSupportedInWebWorker based on isTypeSupported", () => { mockMediaSource_.isTypeSupported.mockImplementation((codec: string) => { // eslint-disable-next-line no-restricted-properties return codec.includes("avc1"); }); const videoRep = { mimeType: "video/mp4", codecs: ["avc1.42E01E"], isCodecSupportedInWebWorker: undefined, getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`, }; const audioRep = { mimeType: "audio/mp4", codecs: ["mp4a.40.2"], isCodecSupportedInWebWorker: undefined, getMimeTypeString: () => `audio/mp4;codecs="mp4a.40.2"`, }; const manifest = { periods: [ { adaptations: { video: [{ representations: [videoRep] }], audio: [{ representations: [audioRep] }], }, }, ], } as any; updateCodecSupportInWorkerMode(manifest); expect(videoRep.isCodecSupportedInWebWorker).toBe(true); expect(audioRep.isCodecSupportedInWebWorker).toBe(false); }); it("should cache codec support results", () => { mockMediaSource_.isTypeSupported.mockReturnValue(true); const rep1 = { mimeType: "video/mp4", codecs: ["avc1.42E01E"], isCodecSupportedInWebWorker: undefined, getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`, }; const rep2 = { mimeType: "video/mp4", codecs: ["avc1.42E01E"], isCodecSupportedInWebWorker: undefined, getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`, }; const manifest = { periods: [ { adaptations: { video: [{ representations: [rep1, rep2] }], audio: [], }, }, ], } as any; updateCodecSupportInWorkerMode(manifest); expect(mockMediaSource_.isTypeSupported).toHaveBeenCalledTimes(1); expect(rep1.isCodecSupportedInWebWorker).toBe(true); expect(rep2.isCodecSupportedInWebWorker).toBe(true); }); }); describe("extractExternalPlugins", () => { const emptyCorePlugins = { representationFilters: new Map(), segmentLoaders: new Map(), manifestLoaders: new Map(), }; it("should return undefined for all when input has no loaders", () => { const result = extractExternalPlugins( { manifestLoader: undefined, segmentLoader: undefined, representationFilter: undefined, }, emptyCorePlugins, ); expect(result).toEqual({ manifestLoader: undefined, segmentLoader: undefined, representationFilter: undefined, }); }); it("should use fn directly when provided", () => { const repFilter = vi.fn(); const manifestLoader = vi.fn(); const segmentLoader = vi.fn(); const result = extractExternalPlugins( { representationFilter: { fn: repFilter }, manifestLoader: { fn: manifestLoader }, segmentLoader: { fn: segmentLoader }, }, emptyCorePlugins, ); expect(result.representationFilter).toBe(repFilter); expect(result.manifestLoader).toBe(manifestLoader); expect(result.segmentLoader).toBe(segmentLoader); }); it("should use eval string for representationFilter", () => { const createdFilter = vi.fn(); mockCreateRepresentationFilter.mockReturnValue(createdFilter); const result = extractExternalPlugins( { representationFilter: { eval: "some code" }, manifestLoader: undefined, segmentLoader: undefined, }, emptyCorePlugins, ); expect(mockCreateRepresentationFilter).toHaveBeenCalledWith("some code"); expect(result.representationFilter).toBe(createdFilter); }); it("should look up workerId in corePlugins maps", () => { const workerRepFilter = vi.fn(); const workerManifestLoader = vi.fn(); const workerSegmentLoader = vi.fn(); const corePlugins = { representationFilters: new Map([["rf-id", workerRepFilter]]), segmentLoaders: new Map([["sl-id", workerSegmentLoader]]), manifestLoaders: new Map([["ml-id", workerManifestLoader]]), }; const result = extractExternalPlugins( { representationFilter: { workerId: "rf-id" }, manifestLoader: { workerId: "ml-id" }, segmentLoader: { workerId: "sl-id" }, }, corePlugins, ); expect(result.representationFilter).toBe(workerRepFilter); expect(result.manifestLoader).toBe(workerManifestLoader); expect(result.segmentLoader).toBe(workerSegmentLoader); }); it("should return undefined when workerId is not found in maps", () => { const result = extractExternalPlugins( { representationFilter: { workerId: "unknown" }, manifestLoader: { workerId: "unknown" }, segmentLoader: { workerId: "unknown" }, }, emptyCorePlugins, ); expect(result.representationFilter).toBeUndefined(); expect(result.manifestLoader).toBeUndefined(); expect(result.segmentLoader).toBeUndefined(); }); it("should prefer fn over eval and workerId for representationFilter", () => { const fnFilter = vi.fn(); mockCreateRepresentationFilter.mockReturnValue(vi.fn()); const result = extractExternalPlugins( { representationFilter: { fn: fnFilter, eval: "some code", workerId: "rf-id" }, manifestLoader: undefined, segmentLoader: undefined, }, emptyCorePlugins, ); expect(result.representationFilter).toBe(fnFilter); expect(mockCreateRepresentationFilter).not.toHaveBeenCalled(); }); });