补齐移动壳角标单测

移动壳 badge helper 增加独立单测覆盖

原生壳结构门禁登记移动角标测试

宿主壳文档和共享决策同步角标测试边界
This commit is contained in:
2026-06-20 08:09:35 +08:00
parent 6bd24cc7f8
commit 1307357099
6 changed files with 152 additions and 4 deletions
@@ -25,6 +25,8 @@ const bridgeTestPath = new URL('../src/host-bridge/bridge.test.ts', import.meta.
const bridgeTestSource = fs.readFileSync(bridgeTestPath, 'utf8');
const badgePath = new URL('../src/host-bridge/badge.ts', import.meta.url);
const badgeSource = fs.readFileSync(badgePath, 'utf8');
const badgeTestPath = new URL('../src/host-bridge/badge.test.ts', import.meta.url);
const badgeTestSource = fs.readFileSync(badgeTestPath, 'utf8');
const clipboardPath = new URL('../src/host-bridge/clipboard.ts', import.meta.url);
const clipboardSource = fs.readFileSync(clipboardPath, 'utf8');
const dispatchPath = new URL('../src/host-bridge/dispatch.ts', import.meta.url);
@@ -2226,6 +2228,19 @@ for (const snippet of [
);
}
}
for (const snippet of [
'sets and clears the iOS app badge count through PushNotificationIOS',
'rejects invalid badge counts before touching the system badge',
'rejects missing badge payload before touching the system badge',
'returns unsupported on Android before validating payload or touching badge APIs',
'HOST_BRIDGE_BADGE_COUNT_MAX + 1',
"setPlatformOS('android')",
'not.toHaveBeenCalled()',
]) {
if (!badgeTestSource.includes(snippet)) {
throw new Error(`mobile shell badge tests missing ${snippet}`);
}
}
if (
!dispatchSource.includes('getMobileHostBridgeAppearanceColorScheme(request)') ||
@@ -0,0 +1,125 @@
import { Platform, PushNotificationIOS } from 'react-native';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
HOST_BRIDGE_BADGE_COUNT_MAX,
HOST_BRIDGE_PROTOCOL,
HOST_BRIDGE_VERSION,
type HostBridgeRequest,
} from '../../../../packages/shared/src/contracts/hostBridge';
import { setMobileAppBadgeCount } from './badge';
vi.mock('react-native', () => ({
Platform: {
OS: 'ios',
},
PushNotificationIOS: {
setApplicationIconBadgeNumber: vi.fn(),
},
}));
function request(payload?: unknown): HostBridgeRequest {
return {
bridge: HOST_BRIDGE_PROTOCOL,
version: HOST_BRIDGE_VERSION,
id: 'badge-request',
method: 'app.setBadgeCount',
payload,
};
}
function setPlatformOS(os: 'ios' | 'android') {
(Platform as { OS: 'ios' | 'android' }).OS = os;
}
beforeEach(() => {
setPlatformOS('ios');
vi.mocked(PushNotificationIOS.setApplicationIconBadgeNumber).mockReset();
});
describe('mobile app badge helper', () => {
test('sets and clears the iOS app badge count through PushNotificationIOS', () => {
expect(
setMobileAppBadgeCount(
request({
count: 12,
}),
),
).toEqual({
bridge: HOST_BRIDGE_PROTOCOL,
version: HOST_BRIDGE_VERSION,
id: 'badge-request',
ok: true,
result: true,
});
expect(
PushNotificationIOS.setApplicationIconBadgeNumber,
).toHaveBeenLastCalledWith(12);
expect(
setMobileAppBadgeCount(
request({
count: 0,
}),
),
).toMatchObject({
ok: true,
result: true,
});
expect(
PushNotificationIOS.setApplicationIconBadgeNumber,
).toHaveBeenLastCalledWith(0);
});
test('rejects invalid badge counts before touching the system badge', () => {
for (const count of [-1, 1.5, HOST_BRIDGE_BADGE_COUNT_MAX + 1]) {
expect(() =>
setMobileAppBadgeCount(
request({
count,
}),
),
).toThrowError(
`count must be an integer between 0 and ${HOST_BRIDGE_BADGE_COUNT_MAX}`,
);
}
expect(
PushNotificationIOS.setApplicationIconBadgeNumber,
).not.toHaveBeenCalled();
});
test('rejects missing badge payload before touching the system badge', () => {
expect(() => setMobileAppBadgeCount(request({}))).toThrowError(
`count must be an integer between 0 and ${HOST_BRIDGE_BADGE_COUNT_MAX}`,
);
expect(
PushNotificationIOS.setApplicationIconBadgeNumber,
).not.toHaveBeenCalled();
});
test('returns unsupported on Android before validating payload or touching badge APIs', () => {
setPlatformOS('android');
expect(() =>
setMobileAppBadgeCount(
request({
count: 1,
}),
),
).toThrowError('app badge count is only supported on iOS mobile shell');
expect(() =>
setMobileAppBadgeCount(
request({
count: HOST_BRIDGE_BADGE_COUNT_MAX + 1,
}),
),
).toThrowError('app badge count is only supported on iOS mobile shell');
expect(
PushNotificationIOS.setApplicationIconBadgeNumber,
).not.toHaveBeenCalled();
});
});
@@ -2941,3 +2941,10 @@
- 决策:新增 `apps/mobile-shell/src/host-bridge/appearance.test.ts`,直接覆盖 React Native `Appearance.getColorScheme()` 的 light / dark、空值 / 未知值归一为 `unknown`,以及 `appearance.getColorScheme` HostBridge 成功响应形状;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不改变 H5 主题策略,也不让壳层覆盖系统或用户偏好。
- 影响范围:`apps/mobile-shell/src/host-bridge/appearance.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/appearance.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动角标 HostBridge 单测边界
- 背景:Expo 移动壳 `app.setBadgeCount` 只在 iOS capability profile 中声明,Android 请求到达时必须明确返回 unsupported;此前 iOS 设置 / 清除、非法 payload 和 Android unsupported 主要压在巨型 bridge 测试中,缺少对 `badge.ts` helper 的直接覆盖。
- 决策:新增 `apps/mobile-shell/src/host-bridge/badge.test.ts`,直接覆盖 iOS `PushNotificationIOS.setApplicationIconBadgeNumber` 设置与清除、非法数量和缺少 payload 时不触碰系统角标,以及 Android 在 payload 校验前返回 `unsupported_capability` 的顺序;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不把 `app.setBadgeCount` 加入 Android base capability。
- 影响范围:`apps/mobile-shell/src/host-bridge/badge.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/badge.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -251,6 +251,7 @@ const expectedWechatPageFilesByRoute = {
const expectedMobileHostBridgeFiles = [
'appearance.test.ts',
'appearance.ts',
'badge.test.ts',
'badge.ts',
'bridge.test.ts',
'bridge.ts',