Files
youle_app_ohos/entry/src/test/Bridge.test.ets
T
lanterngamescn a865ac766b M5: BridgeController 激活闸门 setActive(双 Web 基础件)
- BridgeController 加 active 状态 + setActive/isActive:
  - 非激活:onLoadIntercept 拦截 yy:// 但不分发(入站忽略)、dispatch 直接返回(出站抑制)
  - 保证双 Web 下"同时只有一个 Web 执行桥接口",且去激活端不会收到 native 推送
- 去激活顺序约定:先 callHandler(appservice,'2')(彼时仍激活),再 setActive(false)
- 单测新增 inactive_gate_suppresses_outbound_and_inbound;scripts/test.sh 通过
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 23:25:49 +08:00

226 lines
9.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from '@ohos/hypium';
import {
BridgeController, FakeWebController, MessageCodec, Message, HandlerRegistry
} from 'feature_bridge';
/**
* 桥引擎单测(T-M1-08)。纯逻辑,用 FakeWebController 模拟 H5 收发,不依赖设备。
* 覆盖 MessageCodectoJson/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() ?? '';
// 提取 callbackIdJAVA_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();
});
});
}