import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createBridge, getNativeSetting, type NativeHandler, type NativeResponseCallback, type NativeBridgeChannel, } 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 channel: NativeBridgeChannel = { registerHandler(name, h) { handlers.set(name, h); }, callHandler(name, data, cb) { calls.push({ handler: name, data }); if (cb) responses.set(name, cb); }, }; const g = globalThis as any; const oldWVJB = g.WVJBCallbacks; g.WVJBCallbacks = []; // mock 原生侧 WebView bridge 就绪:遍历 WVJBCallbacks 全调用 const triggerReady = () => { for (const cb of [...g.WVJBCallbacks]) cb(channel); }; return { channel, restore: () => { g.WVJBCallbacks = oldWVJB; }, calls, handlers, responses, triggerReady, }; } test('createBridge: 通道就绪时 onReady 触发', () => { const env = makeEnv(); let got: any = null; const bridge = createBridge({ onReady(c) { got = c; }, }); env.triggerReady(); assert.equal(got, env.channel, 'onReady 应收到 mock channel'); env.restore(); }); test('registerHandler: H5 注册供原生调用', () => { const env = makeEnv(); let received: any = null; const bridge = createBridge({ onReady(c) { (globalThis as any).__activeChannel = c; bridge.registerHandler('getVideoinfo', (data) => { received = data; return { ok: true }; }); }, }); env.triggerReady(); // 原生侧调用 const handler = env.handlers.get('getVideoinfo'); assert.ok(handler, 'handler 应已注册'); handler!({ vid: 'abc' }, (() => {}) as NativeResponseCallback); assert.deepEqual(received, { vid: 'abc' }); env.restore(); }); test('callHandler: H5 调用原生,响应通过 cb 接收', () => { const env = makeEnv(); const bridge = createBridge({ onReady(c) { (globalThis as any).__activeChannel = c; }, }); env.triggerReady(); let resp: unknown = null; bridge.callHandler('getBattery', {}, (r) => { resp = r; }); // 模拟原生侧响应 const cb = env.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 bridge = createBridge({ onReady: () => {} }); env.triggerReady(); assert.throws( () => bridge.callHandler('unknown_handler' as any, {}, () => {}), /unknown_handler/, ); env.restore(); }); test('registerHandler: handler 名不在白名单 → 显式抛错', () => { const env = makeEnv(); const bridge = createBridge({ onReady: () => {} }); env.triggerReady(); assert.throws( () => bridge.registerHandler('unknown_handler' as any, () => undefined), /unknown_handler/, ); env.restore(); }); test('bridge 未就绪时 callHandler/registerHandler 显式抛错', () => { const g = globalThis as any; const old = g.WVJBCallbacks; g.WVJBCallbacks = []; // 空队列,triggerReady 不被调用 const bridge = createBridge({ onReady: () => {} }); assert.throws(() => bridge.callHandler('getBattery', {}, () => {}), /not ready/i); assert.throws(() => bridge.registerHandler('getVideoinfo', (() => undefined) as any), /not ready/i); g.WVJBCallbacks = old; }); test('重复 createBridge: 多个 instance 各自注册独立回调', () => { const g = globalThis as any; const old = g.WVJBCallbacks; g.WVJBCallbacks = []; createBridge({ onReady: () => {} }); createBridge({ onReady: () => {} }); assert.equal(g.WVJBCallbacks.length, 2, '每个 instance 独立注册一个 cb'); g.WVJBCallbacks = old; }); test('getNativeSetting: 同步取 window.settings.getothername', () => { const g = globalThis as any; const oldWindow = g.window; g.window = { settings: { getothername: (n: string) => n === 'agentid' ? 'A' : '' } }; assert.equal(getNativeSetting('agentid'), 'A'); assert.equal(getNativeSetting('missing'), ''); g.window = oldWindow; }); test('getNativeSetting: window.settings 未注入 → 显式抛错(不静默兜底)', () => { const g = globalThis as any; const oldWindow = g.window; g.window = {}; // 无 settings assert.throws(() => getNativeSetting('agentid'), /window\.settings 未注入/); g.window = oldWindow; });