071faa482c
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
123 lines
3.4 KiB
TypeScript
123 lines
3.4 KiB
TypeScript
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('reports unavailable native feedback with a stable HostBridge error', async () => {
|
|
const warnSpy = vi
|
|
.spyOn(console, 'warn')
|
|
.mockImplementation(() => undefined);
|
|
const nativeError = new Error('native haptics failed');
|
|
vi.mocked(Haptics.impactAsync).mockRejectedValueOnce(nativeError);
|
|
|
|
try {
|
|
await expect(runMobileHapticsImpact('light')).rejects.toMatchObject({
|
|
code: 'host_error',
|
|
message: 'haptics impact unavailable',
|
|
});
|
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
'mobile haptics failed for impact.dispatch',
|
|
);
|
|
} finally {
|
|
warnSpy.mockRestore();
|
|
}
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|