补齐移动壳触觉反馈单测

移动壳触觉反馈 helper 增加独立单测覆盖

原生壳结构门禁登记移动触觉反馈测试

宿主壳文档和共享决策同步触觉反馈测试边界
This commit is contained in:
2026-06-20 07:40:47 +08:00
parent 6a1b188bcf
commit 97c3971111
5 changed files with 108 additions and 4 deletions
@@ -0,0 +1,102 @@
import * as Haptics from 'expo-haptics';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
HOST_BRIDGE_PROTOCOL,
HOST_BRIDGE_VERSION,
type HostBridgeRequest,
} from '../../../../packages/shared/src/contracts/hostBridge';
import {
runMobileHapticsImpact,
runMobileHostBridgeHapticsImpact,
} from './haptics';
vi.mock('expo-haptics', () => ({
ImpactFeedbackStyle: {
Heavy: 'heavy',
Light: 'light',
Medium: 'medium',
},
impactAsync: vi.fn(),
}));
function request(payload?: unknown): HostBridgeRequest {
return {
bridge: HOST_BRIDGE_PROTOCOL,
version: HOST_BRIDGE_VERSION,
id: 'haptics-request',
method: 'haptics.impact',
payload,
};
}
beforeEach(() => {
vi.mocked(Haptics.impactAsync).mockReset();
});
describe('mobile haptics helpers', () => {
test('maps allowed impact styles to Expo Haptics styles', async () => {
await expect(runMobileHapticsImpact('light')).resolves.toBe(true);
expect(Haptics.impactAsync).toHaveBeenLastCalledWith(
Haptics.ImpactFeedbackStyle.Light,
);
await expect(runMobileHapticsImpact('medium')).resolves.toBe(true);
expect(Haptics.impactAsync).toHaveBeenLastCalledWith(
Haptics.ImpactFeedbackStyle.Medium,
);
await expect(runMobileHapticsImpact('heavy')).resolves.toBe(true);
expect(Haptics.impactAsync).toHaveBeenLastCalledWith(
Haptics.ImpactFeedbackStyle.Heavy,
);
});
test('defaults missing style to light impact', async () => {
await expect(runMobileHapticsImpact(undefined)).resolves.toBe(true);
expect(Haptics.impactAsync).toHaveBeenCalledWith(
Haptics.ImpactFeedbackStyle.Light,
);
});
test('rejects unknown styles without triggering device feedback', async () => {
await expect(runMobileHapticsImpact('rigid')).resolves.toBe(false);
expect(Haptics.impactAsync).not.toHaveBeenCalled();
});
test('wraps HostBridge success response after real impact dispatch', async () => {
const response = await runMobileHostBridgeHapticsImpact(
request({
style: 'medium',
}),
);
expect(response).toEqual({
bridge: HOST_BRIDGE_PROTOCOL,
version: HOST_BRIDGE_VERSION,
id: 'haptics-request',
ok: true,
result: true,
});
expect(Haptics.impactAsync).toHaveBeenCalledWith(
Haptics.ImpactFeedbackStyle.Medium,
);
});
test('wraps HostBridge invalid request before device feedback', async () => {
await expect(
runMobileHostBridgeHapticsImpact(
request({
style: 'rigid',
}),
),
).rejects.toMatchObject({
code: 'invalid_request',
message: 'haptics impact style must be light, medium, or heavy',
});
expect(Haptics.impactAsync).not.toHaveBeenCalled();
});
});
@@ -61,6 +61,7 @@
- 2026-06-20 桌面壳未声明 method 禁伪成功:Tauri 桌面壳只声明真实可用 capability;共享 HostBridge method 白名单中未进入桌面 capability profile 的 method,例如 `auth.requestLogin``payment.request``file.captureImage``scanner.scanQrCode``haptics.impact`,请求实际到达桌面壳时必须统一返回明确 `unsupported_method`,不得伪造成功或半接入。桌面 Rust 测试必须从 `HOST_BRIDGE_METHODS``capabilities()` 差集派生 unsupported 覆盖清单,桌面单端配置检查会反查该派生路径,后续共享契约新增 method 时必须同步声明真实桌面能力或补进 unsupported 语义。
- 2026-06-18 移动壳触觉反馈边界:`haptics.impact` 只接受 `light``medium``heavy` 三档 impact style,缺省为 `light`;未知值必须返回 `invalid_request`,不得静默降级成真实设备触觉反馈。桌面壳不声明该 capabilityH5 继续按 HostBridge fallback 处理。
- 2026-06-19 移动壳触觉反馈模块边界:Expo `haptics.impact` 的 HostBridge payload 解析、共享 style 归一、`light` / `medium` / `heavy``Haptics.ImpactFeedbackStyle` 的映射、真实 `Haptics.impactAsync(...)` 调用和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/haptics.ts``dispatch.ts` 只负责把完整 request 委托给 `runMobileHostBridgeHapticsImpact(...)`,不得直接导入 `expo-haptics`、读取 `HapticsImpactPayload`、调用 `Haptics.impactAsync` 或包装触觉反馈成功响应。移动壳配置检查会覆盖该模块结构、共享 style 边界和 dispatch 委托关系,避免触觉反馈能力散落到分发层。
- 2026-06-20 移动触觉反馈单测边界:`apps/mobile-shell/src/host-bridge/haptics.test.ts` 直接覆盖 `haptics.impact``light` / `medium` / `heavy` 到 Expo Haptics style 映射、缺省 `light`、未知 style 不触发设备反馈、HostBridge 成功响应和 `invalid_request` 失败包装;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免触觉反馈边界只靠完整 HostBridge bridge 流程间接覆盖。
- 2026-06-18 分享卡图片导出:新增 `file.exportImage` HostBridge capabilityH5 分享卡下载在 native app 中优先把 canvas 生成的 base64 图片交给宿主导出;Expo 壳写缓存图片后交给系统分享 / 保存面板,Tauri 壳通过系统保存对话框写入图片字节。该能力只接受 `image/png` / `image/jpeg` / `image/webp`、单次 5 MiB 内图片数据,成功只返回文件名和字节数,不暴露本机绝对路径;宿主未声明时保留浏览器下载。Expo 图片导出的 payload 校验、缓存写入、系统分享 / 保存面板和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-18 应用角标能力:新增 `app.setBadgeCount` HostBridge capabilityH5 只传 `0` 到共享契约 `HOST_BRIDGE_BADGE_COUNT_MAX` 之间的整数并在宿主未声明时静默 fallback;Expo 壳只在 iOS 声明并通过 React Native `PushNotificationIOS` 设置应用图标角标,Android 不声明、不伪造成功;Tauri 壳通过主窗口 `set_badge_count` 设置任务栏角标,底层平台不支持时返回真实错误。
- 2026-06-18 草稿生成未读角标:平台壳层把“可见作品架里未读的草稿生成完成更新”同步到 `app.setBadgeCount`;同一草稿的 work/profile/session 等多个恢复 ID 只计 1,已读、失败、生成中和不可见草稿不计入。该角标只消费已有 HostBridge 能力,宿主不支持或设置失败不影响 H5 红点、作品架或后端状态。
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
@@ -259,6 +259,7 @@ const expectedMobileHostBridgeFiles = [
'filePayloads.test.ts',
'filePayloads.ts',
'files.ts',
'haptics.test.ts',
'haptics.ts',
'navigation.ts',
'network.ts',