/* @vitest-environment jsdom */ import { afterEach, describe, expect, test, vi } from 'vitest'; import { resetHostRuntimeCacheForTest, setHostRuntimeCacheForTest, } from './host-bridge/hostBridge'; import { resetNativeAppHostBridgeForTest } from './host-bridge/nativeAppHostBridge'; import { triggerRuntimeClickFeedback, triggerRuntimeImpactFeedback, } from './runtimeAudioFeedback'; function waitForNextTask() { return new Promise((resolve) => { window.setTimeout(resolve, 0); }); } function asTauriInvoke( invoke: (command: string, args?: Record) => Promise, ) { return async function tauriInvoke( command: string, args?: Record, ) { return (await invoke(command, args)) as Result; }; } afterEach(() => { vi.restoreAllMocks(); window.history.replaceState(null, '', '/'); delete window.ReactNativeWebView; delete window.__TAURI__; resetNativeAppHostBridgeForTest(); resetHostRuntimeCacheForTest(); }); describe('runtimeAudioFeedback', () => { test('普通浏览器点击反馈使用 vibration fallback', async () => { const vibrate = vi.fn(); Object.defineProperty(navigator, 'vibrate', { configurable: true, value: vibrate, }); triggerRuntimeImpactFeedback('light', 12); await waitForNextTask(); expect(vibrate).toHaveBeenCalledWith([12]); }); test('原生壳触觉反馈成功时不再触发浏览器 vibration fallback', async () => { const vibrate = vi.fn(); const invoke = vi.fn( async (_command: string, args?: Record) => { const request = (args as { request: { id: string } }).request; return { bridge: 'GenarrativeHostBridge', version: 1, id: request.id, ok: true, result: true, }; }, ); Object.defineProperty(navigator, 'vibrate', { configurable: true, value: vibrate, }); window.history.replaceState( null, '', '/?clientRuntime=native_app&hostCapabilities=haptics.impact', ); setHostRuntimeCacheForTest({ shell: 'tauri_desktop', platform: 'linux', hostVersion: '0.1.0', bridgeVersion: 1, capabilities: ['haptics.impact'], }); window.__TAURI__ = { core: { invoke: asTauriInvoke(invoke), }, }; triggerRuntimeClickFeedback(undefined, 0.6, { fallbackVibrationPatternMs: 12, hapticsStyle: 'medium', }); await waitForNextTask(); expect(invoke).toHaveBeenCalledWith('host_bridge_request', { request: expect.objectContaining({ method: 'haptics.impact', payload: { style: 'medium', }, }), }); expect(vibrate).not.toHaveBeenCalled(); }); test('原生壳不支持触觉反馈时回退到浏览器 vibration', async () => { const vibrate = vi.fn(); Object.defineProperty(navigator, 'vibrate', { configurable: true, value: vibrate, }); window.history.replaceState(null, '', '/?clientRuntime=native_app'); window.__TAURI__ = { core: { invoke: asTauriInvoke( vi.fn(async (_command: string, args?: Record) => { const request = (args as { request: { id: string } }).request; return { bridge: 'GenarrativeHostBridge', version: 1, id: request.id, ok: false, error: { code: 'unsupported_method', message: 'unsupported_method', }, }; }), ), }, }; triggerRuntimeImpactFeedback('light', 12); await waitForNextTask(); expect(vibrate).toHaveBeenCalledWith([12]); }); });