import { describe, it, expect } from '@ohos/hypium'; import { BridgeController, FakeWebController, MessageCodec, Message, HandlerRegistry } from 'feature_bridge'; /** * 桥引擎单测(T-M1-08)。纯逻辑,用 FakeWebController 模拟 H5 收发,不依赖设备。 * 覆盖 MessageCodec(toJson/escape/toArray)、HandlerRegistry(兜底)、 * BridgeController(入站分发/出站下发/回执路由/启动队列/默认兜底)。 */ export default function bridgeTest() { describe('MessageCodec', () => { it('toJson_omits_undefined', 0, () => { const m = new Message(); m.handlerName = 'h'; m.data = 'd'; const json = MessageCodec.toJson(m); expect(json.includes('"handlerName":"h"')).assertTrue(); expect(json.includes('"data":"d"')).assertTrue(); expect(json.includes('callbackId')).assertFalse(); expect(json.includes('responseId')).assertFalse(); }); it('toJson_response', 0, () => { const json = MessageCodec.toJson(Message.response('cb1', 'ok')); expect(json.includes('"responseId":"cb1"')).assertTrue(); expect(json.includes('"responseData":"ok"')).assertTrue(); }); it('escape_plain_unchanged', 0, () => { // 无双引号/反斜杠的纯文本不应被第一步改变(仅可能被第二步处理引号,这里无引号) const s = MessageCodec.escape('1782349257911'); expect(s).assertEqual('1782349257911'); }); it('escape_quotes_backslash', 0, () => { // data='a"b' → toJson 得 {"data":"a\"b"}(内部引号被 JSON 转义为 \") const mm = new Message(); mm.data = 'a"b'; const json = MessageCodec.toJson(mm); // toJson 内部:a 与 b 之间是 反斜杠+引号 expect(json.includes('a\\"b')).assertTrue(); const escaped = MessageCodec.escape(json); // 第二步:结构引号(前面非反斜杠)被转义为 \" expect(escaped.includes('\\"data\\"')).assertTrue(); // 第一步(命门):内部已转义的 \" 的反斜杠被加倍 → a 与 b 之间出现 \\\"(反斜杠×2+引号) expect(escaped.includes('a\\\\\\"b')).assertTrue(); }); it('toArray_parses_queue', 0, () => { const list = MessageCodec.toArray('[{"handlerName":"echo","data":"hi","callbackId":"cb1"}]'); expect(list.length).assertEqual(1); expect(list[0].handlerName).assertEqual('echo'); expect(list[0].data).assertEqual('hi'); expect(list[0].callbackId).assertEqual('cb1'); }); }); describe('HandlerRegistry', () => { it('put_get_has', 0, () => { const r = new HandlerRegistry(); r.put('a', (_d, cb) => cb('A')); expect(r.has('a')).assertTrue(); expect(r.has('b')).assertFalse(); expect(r.size()).assertEqual(1); }); it('default_handler_no_throw', 0, () => { const r = new HandlerRegistry(); let resp = 'none'; // 默认兜底:未注册名不抛错,回空串 r.default()('x', (d) => { resp = d; }); expect(resp).assertEqual(''); }); it('set_default_and_remove', 0, () => { const r = new HandlerRegistry(); let v = ''; r.setDefault((d, cb) => cb('D:' + d)); r.default()('x', (s) => { v = s; }); expect(v).assertEqual('D:x'); r.put('a', (_d, cb) => cb('A')); expect(r.size()).assertEqual(1); r.remove('a'); expect(r.has('a')).assertFalse(); }); }); describe('FakeWebController', () => { it('records_and_clears', 0, () => { const f = new FakeWebController(); expect(f.lastScript() === undefined).assertTrue(); f.runJavaScript('s1'); f.loadUrl('u1'); expect(f.scripts.length).assertEqual(1); expect(f.loadedUrls.length).assertEqual(1); expect(f.lastScript()).assertEqual('s1'); f.dispose(); f.clear(); expect(f.scripts.length).assertEqual(0); expect(f.lastScript() === undefined).assertTrue(); }); }); describe('BridgeController', () => { it('inbound_dispatch_and_callback', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); let got = ''; bridge.registerHandler('echo', (data, cb) => { got = data; cb('R:' + data); }); bridge.onPageEnd(); // 清空启动队列,进入直发模式 // 模拟 H5 触发取队列 const intercepted = bridge.onLoadIntercept('yy://__QUEUE__/'); expect(intercepted).assertTrue(); // FakeWebController 应收到 _fetchQueue 触发脚本 expect(fake.scripts.some((s) => s.includes('_fetchQueue'))).assertTrue(); // 模拟 H5 交回队列:一条 echo 调用 fake.clear(); bridge.onLoadIntercept('yy://return/_fetchQueue/' + encodeURIComponent('[{"handlerName":"echo","data":"hi","callbackId":"cb1"}]')); expect(got).assertEqual('hi'); // 回执经 dispatch 下发,脚本含 _handleMessageFromNative 与 responseId cb1 const last = fake.lastScript() ?? ''; expect(last.includes('_handleMessageFromNative')).assertTrue(); expect(last.includes('cb1')).assertTrue(); }); it('outbound_callHandler', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.onPageEnd(); bridge.callHandler('getlocationinfo', '{"city":"x"}'); const last = fake.lastScript() ?? ''; expect(last.includes('_handleMessageFromNative')).assertTrue(); expect(last.includes('getlocationinfo')).assertTrue(); }); it('send_no_handlerName', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.onPageEnd(); bridge.send('hello'); const last = fake.lastScript() ?? ''; expect(last.includes('_handleMessageFromNative')).assertTrue(); expect(last.includes('hello')).assertTrue(); }); it('startup_queue_flush_after_onPageEnd', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); // onPageEnd 之前 callHandler → 入启动队列,暂不下发 bridge.callHandler('appservice', '1'); expect(fake.scripts.length).assertEqual(0); // onPageEnd → 补发 bridge.onPageEnd(); expect(fake.scripts.some((s) => s.includes('appservice'))).assertTrue(); }); it('response_route_by_responseId', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.onPageEnd(); let resp = ''; // 原生 callHandler 带回调 → 生成 callbackId bridge.callHandler('getTime', '', (r) => { resp = r; }); // H5 回执:responseId 对应刚才的 callbackId(从下发脚本里取) const sent = fake.lastScript() ?? ''; // 提取 callbackId(JAVA_CB_...) const m = sent.match(/JAVA_CB_[0-9]+_[0-9]+/); expect(m !== null).assertTrue(); const cbId = m![0]; // 先触发 flushMessageQueue 注册 _fetchQueue 回调,回执经队列机制路由 bridge.onLoadIntercept('yy://__queue__/'); bridge.onLoadIntercept('yy://return/_fetchQueue/' + encodeURIComponent('[{"responseId":"' + cbId + '","responseData":"1700"}]')); expect(resp).assertEqual('1700'); }); it('dispose_clears_registry_and_callbacks', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.registerHandler('a', (_d, cb) => cb('A')); expect(bridge.getRegistry().has('a')).assertTrue(); bridge.dispose(); // dispose 清空注册表(默认兜底保留) expect(bridge.getRegistry().has('a')).assertFalse(); }); it('inactive_gate_suppresses_outbound_and_inbound', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.onPageEnd(); let got = ''; bridge.registerHandler('echo', (data, cb) => { got = data; cb('R'); }); // 去激活:出站抑制 bridge.setActive(false); bridge.callHandler('appservice', '2'); expect(fake.scripts.length).assertEqual(0); // 去激活:入站忽略——yy:// 仍拦截(返回 true)但不 flush const intercepted = bridge.onLoadIntercept('yy://__queue__/'); expect(intercepted).assertTrue(); expect(fake.scripts.length).assertEqual(0); // 重新激活:恢复出站 bridge.setActive(true); bridge.callHandler('appservice', '1'); expect(fake.scripts.some((s) => s.includes('appservice'))).assertTrue(); }); it('default_handler_unregistered_no_throw', 0, () => { const fake = new FakeWebController(); const bridge = new BridgeController(fake); bridge.onPageEnd(); // 先触发 flushMessageQueue 注册 _fetchQueue 回调 bridge.onLoadIntercept('yy://__queue__/'); // 未注册的 handler 名 → 默认兜底,不抛错 bridge.onLoadIntercept('yy://return/_fetchQueue/' + encodeURIComponent('[{"handlerName":"finsh","data":"","callbackId":"cb9"}]')); // 兜底回空串回执 const last = fake.lastScript() ?? ''; expect(last.includes('cb9')).assertTrue(); }); }); }