diff --git a/entry/src/test/Bridge.test.ets b/entry/src/test/Bridge.test.ets new file mode 100644 index 0000000..525a705 --- /dev/null +++ b/entry/src/test/Bridge.test.ets @@ -0,0 +1,148 @@ +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, () => { + // JSON {"data":"a\"b"} → 结构引号转义、内部 \" 加倍 + const mm = new Message(); + mm.data = 'a"b'; + const escaped = MessageCodec.escape(MessageCodec.toJson(mm)); + // 结构引号被转义为 \"(前面非反斜杠) + expect(escaped.includes('\\"data\\"')).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(''); + }); + }); + + 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('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]; + bridge.onLoadIntercept('yy://return/_fetchQueue/' + + encodeURIComponent('[{"responseId":"' + cbId + '","responseData":"1700"}]')); + expect(resp).assertEqual('1700'); + }); + + it('default_handler_unregistered_no_throw', 0, () => { + const fake = new FakeWebController(); + const bridge = new BridgeController(fake); + bridge.onPageEnd(); + // 未注册的 handler 名 → 默认兜底,不抛错 + bridge.onLoadIntercept('yy://return/_fetchQueue/' + + encodeURIComponent('[{"handlerName":"finsh","data":"","callbackId":"cb9"}]')); + // 兜底回空串回执 + const last = fake.lastScript() ?? ''; + expect(last.includes('cb9')).assertTrue(); + }); + }); +} diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets index bb5b5c3..f58041f 100644 --- a/entry/src/test/List.test.ets +++ b/entry/src/test/List.test.ets @@ -1,5 +1,7 @@ import localUnitTest from './LocalUnit.test'; +import bridgeTest from './Bridge.test'; export default function testsuite() { localUnitTest(); -} \ No newline at end of file + bridgeTest(); +}