f73e710e38
Build site and push to gh-pages / Build site (push) Waiting to run
Build / build (push) Waiting to run
Linter / lint (push) Waiting to run
Security / dependency-review (push) Waiting to run
Security / npm-audit (push) Waiting to run
Security / codeql (push) Waiting to run
Tests / test (push) Waiting to run
145 lines
4.0 KiB
TypeScript
145 lines
4.0 KiB
TypeScript
import {
|
|
CreateServiceWorkerMLCEngine,
|
|
ServiceWorkerMLCEngine,
|
|
ServiceWorkerMLCEngineHandler,
|
|
} from "../src/extension_service_worker";
|
|
import {
|
|
jest,
|
|
test,
|
|
expect,
|
|
describe,
|
|
beforeEach,
|
|
afterEach,
|
|
} from "@jest/globals";
|
|
|
|
jest.mock("@mlc-ai/web-runtime", () => ({
|
|
detectGPUDevice: jest.fn(async () => ({
|
|
adapterInfo: { description: "MockGPU", vendor: "MockVendor" },
|
|
device: { features: new Set() },
|
|
})),
|
|
}));
|
|
|
|
const reloadMock = jest.fn();
|
|
const initCallback = jest.fn();
|
|
|
|
jest.mock("../src/engine", () => {
|
|
return {
|
|
MLCEngine: jest.fn(() => ({
|
|
reload: reloadMock,
|
|
getInitProgressCallback: jest.fn(() => initCallback),
|
|
setInitProgressCallback: jest.fn(),
|
|
})),
|
|
};
|
|
});
|
|
|
|
type MockPort = chrome.runtime.Port & {
|
|
triggerDisconnect: () => void;
|
|
emitMessage: (msg: any) => void;
|
|
};
|
|
|
|
function createPort(): MockPort {
|
|
const disconnectListeners: Array<() => void> = [];
|
|
const messageListeners: Array<(msg: any) => void> = [];
|
|
return {
|
|
postMessage: jest.fn(),
|
|
onDisconnect: {
|
|
addListener: (cb: () => void) => disconnectListeners.push(cb),
|
|
},
|
|
onMessage: {
|
|
addListener: (cb: (msg: any) => void) => messageListeners.push(cb),
|
|
},
|
|
triggerDisconnect: () => disconnectListeners.forEach((cb) => cb()),
|
|
emitMessage: (msg: any) => messageListeners.forEach((cb) => cb(msg)),
|
|
} as unknown as MockPort;
|
|
}
|
|
|
|
function createHandler() {
|
|
const handler = new ServiceWorkerMLCEngineHandler(createPort());
|
|
(handler as any).handleTask = jest.fn(async (_uuid: string, task: any) =>
|
|
task(),
|
|
);
|
|
(handler as any).engine = {
|
|
reload: reloadMock,
|
|
getInitProgressCallback: jest.fn(() => initCallback),
|
|
};
|
|
reloadMock.mockClear();
|
|
initCallback.mockClear();
|
|
return handler;
|
|
}
|
|
|
|
test("reload message with same model skips loading and triggers init callback", async () => {
|
|
const handler = createHandler();
|
|
handler.modelId = ["demo"];
|
|
handler.chatOpts = [];
|
|
await handler.onmessage({
|
|
type: "message",
|
|
kind: "reload",
|
|
uuid: "task",
|
|
content: { modelId: ["demo"], chatOpts: [] },
|
|
} as any);
|
|
expect(reloadMock).not.toHaveBeenCalled();
|
|
expect(initCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
test("reload with new model calls engine reload", async () => {
|
|
const handler = createHandler();
|
|
handler.modelId = ["demo"];
|
|
handler.chatOpts = [];
|
|
await handler.onmessage({
|
|
kind: "reload",
|
|
uuid: "task",
|
|
content: { modelId: ["new"], chatOpts: [] },
|
|
} as any);
|
|
expect(reloadMock).toHaveBeenCalledWith(["new"], []);
|
|
});
|
|
|
|
function mockChromeRuntime(port: MockPort = createPort()) {
|
|
const connect = jest.fn<(...args: any[]) => MockPort>(() => port);
|
|
(globalThis as any).chrome = {
|
|
runtime: {
|
|
connect,
|
|
},
|
|
};
|
|
return { port, connect };
|
|
}
|
|
|
|
describe("ServiceWorkerMLCEngine integration", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
jest.clearAllTimers();
|
|
delete (globalThis as any).chrome;
|
|
});
|
|
|
|
test("keepAlive pings and onDisconnect callback fires", () => {
|
|
const { port, connect } = mockChromeRuntime();
|
|
const onDisconnect = jest.fn();
|
|
const engine = new ServiceWorkerMLCEngine({ onDisconnect }, 500);
|
|
expect(connect).toHaveBeenCalledWith({ name: "web_llm_service_worker" });
|
|
jest.advanceTimersByTime(500);
|
|
expect(port.postMessage).toHaveBeenCalledWith({ kind: "keepAlive" });
|
|
port.triggerDisconnect();
|
|
expect(onDisconnect).toHaveBeenCalled();
|
|
expect(engine).toBeTruthy();
|
|
});
|
|
|
|
test("CreateServiceWorkerMLCEngine reloads requested model", async () => {
|
|
const { connect } = mockChromeRuntime();
|
|
const reloadSpy = jest
|
|
.spyOn(ServiceWorkerMLCEngine.prototype, "reload")
|
|
.mockResolvedValue(undefined);
|
|
const engine = await CreateServiceWorkerMLCEngine("demo-model", {
|
|
extensionId: "abc",
|
|
});
|
|
expect(connect).toHaveBeenCalledWith("abc", {
|
|
name: "web_llm_service_worker",
|
|
});
|
|
expect(reloadSpy).toHaveBeenCalledWith("demo-model", undefined);
|
|
reloadSpy.mockRestore();
|
|
expect(engine).toBeInstanceOf(ServiceWorkerMLCEngine);
|
|
});
|
|
});
|