补齐移动壳触觉反馈单测
移动壳触觉反馈 helper 增加独立单测覆盖 原生壳结构门禁登记移动触觉反馈测试 宿主壳文档和共享决策同步触觉反馈测试边界
This commit is contained in:
@@ -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`,不得静默降级成真实设备触觉反馈。桌面壳不声明该 capability,H5 继续按 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 capability,H5 分享卡下载在 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 capability,H5 只传 `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
@@ -259,6 +259,7 @@ const expectedMobileHostBridgeFiles = [
|
||||
'filePayloads.test.ts',
|
||||
'filePayloads.ts',
|
||||
'files.ts',
|
||||
'haptics.test.ts',
|
||||
'haptics.ts',
|
||||
'navigation.ts',
|
||||
'network.ts',
|
||||
|
||||
Reference in New Issue
Block a user