import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createNativeBridge, type WVJBHandler, type WVJBCallback, // type-only, see below } from '../../YouleNexus/assets/framework/platform/native-bridge.ts'; /** * Mock 浏览器侧 globals。Node 单测无 window,需 stub。 */ function makeEnv() { const calls: { handler: string; data: unknown }[] = []; const responses: Map void> = new Map(); const handlers: Map = new Map(); const bridge = { registerHandler(name: string, h: WVJBHandler) { handlers.set(name, h); }, callHandler(name: string, data: unknown, cb?: (resp: unknown) => void) { calls.push({ handler: name, data }); if (cb) responses.set(name, cb); }, _handlers: handlers, _calls: calls, _responses: responses, }; // 模拟 WebView:bridge 已就绪,setup cb 立即触发 const g = globalThis as any; const oldWVJB = g.WVJBCallbacks; // mock:WVJBCallbacks 是空数组,createNativeBridge 调用时 push 自己的 cb // 测试需要主动触发 onReady 时,调用 triggerReady() g.WVJBCallbacks = []; const triggerReady = () => { // 模拟原生侧 WebView bridge 就绪,遍历 WVJBCallbacks 调用每个 cb for (const cb of [...g.WVJBCallbacks]) cb(bridge); }; return { bridge, restore: () => { g.WVJBCallbacks = oldWVJB; }, calls, handlers, triggerReady }; } test('createNativeBridge: bridge 就绪时 setup cb 触发', () => { const env = makeEnv(); let got: any = null; const nb = createNativeBridge({ onReady(bridge) { got = bridge; }, }); env.triggerReady(); assert.equal(got, env.bridge, 'onReady 应收到 mock bridge'); env.restore(); }); test('registerHandler: H5 注册供原生调用', () => { const env = makeEnv(); let received: any = null; const nb = createNativeBridge({ onReady(b) { (globalThis as any).__nbBridge = b; nb.registerHandler('getVideoinfo', (data) => { received = data; return { ok: true }; }); }, }); env.triggerReady(); // 原生侧调用 const handler = env.handlers.get('getVideoinfo'); assert.ok(handler, 'handler 应已注册'); const ret = handler!({ vid: 'abc' }, () => {}); assert.deepEqual(received, { vid: 'abc' }); env.restore(); }); test('callHandler: H5 调用原生,响应通过 cb 接收', () => { const env = makeEnv(); // onReady 必须把 bridge 存到 __nbBridge,callHandler 才能找到 const nb = createNativeBridge({ onReady(b) { (globalThis as any).__nbBridge = b; }, }); env.triggerReady(); let resp: unknown = null; nb.callHandler('getBattery', {}, (r) => { resp = r; }); // 模拟原生侧响应 const cb = env.bridge._responses.get('getBattery'); assert.ok(cb, 'callHandler 应记录 cb'); cb!({ level: 80, charging: false }); assert.deepEqual(resp, { level: 80, charging: false }); env.restore(); }); test('callHandler: handler 名不在白名单 → 显式抛错(第二准则:不静默吞包)', () => { const env = makeEnv(); const nb = createNativeBridge({ onReady: () => {} }); assert.throws( () => nb.callHandler('unknown_handler' as any, {}, () => {}), /unknown_handler/, ); env.restore(); }); test('registerHandler: handler 名不在白名单 → 显式抛错', () => { const env = makeEnv(); const nb = createNativeBridge({ onReady: () => {} }); assert.throws( () => nb.registerHandler('unknown_handler' as any, () => undefined), /unknown_handler/, ); env.restore(); }); test('bridge 未就绪时 callHandler/registerHandler 显式抛错(不在初始化就调用)', () => { // 不调用 onReady,bridge 永不触发 const g = globalThis as any; const old = g.WVJBCallbacks; g.WVJBCallbacks = []; // 空队列,setup 永不触发 const nb = createNativeBridge({ onReady: () => {} }); assert.throws(() => nb.callHandler('getBattery', {}, () => {}), /not ready/i); assert.throws(() => nb.registerHandler('getVideoinfo', (() => ({})) as any), /not ready/i); g.WVJBCallbacks = old; }); test('重复 createNativeBridge: 多个 instance 各自注册独立回调到 WVJBCallbacks', () => { const g = globalThis as any; const old = g.WVJBCallbacks; g.WVJBCallbacks = []; const nb1 = createNativeBridge({ onReady: () => {} }); const nb2 = createNativeBridge({ onReady: () => {} }); // 每个 createNativeBridge push 一个 cb(原生侧 bridgeReady 时全触发,业务侧通过闭包区分) assert.equal(g.WVJBCallbacks.length, 2, '每个 instance 独立注册一个 cb'); g.WVJBCallbacks = old; }); test('window.settings: getSetting 同步取值', () => { const g = globalThis as any; const old = g.window; g.window = { settings: { getothername: (n: string) => n === 'agentid' ? 'A' : '' } }; // dynamic import to mutate after module load? 这里简单测: window injected already // 用 require 方式不可行,直接验证 window injection 模式 assert.equal(g.window.settings.getothername('agentid'), 'A'); g.window = old; });