Files
Genarrative/src/hooks/useHostNavigationCanGoBack.test.tsx
T
kdletters ce0765b298 接入原生返回栈状态消费
H5 新增 navigation.canGoBack hook 并在直达二级页时补齐返回锚点

路由导航保留完整原生宿主上下文并标记应用历史状态

补齐 HostBridge 返回栈消费测试、门禁和文档
2026-06-19 10:59:47 +08:00

122 lines
3.4 KiB
TypeScript

/* @vitest-environment jsdom */
import { act, renderHook } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import {
canUseNativeHostCapability,
subscribeHostNavigationCanGoBack,
subscribeHostRuntimeChange,
} from '../services/host-bridge/hostBridge';
import { useHostNavigationCanGoBack } from './useHostNavigationCanGoBack';
vi.mock('../services/host-bridge/hostBridge', () => ({
canUseNativeHostCapability: vi.fn(() => false),
subscribeHostNavigationCanGoBack: vi.fn(() => () => undefined),
subscribeHostRuntimeChange: vi.fn(() => () => undefined),
}));
const canUseNativeHostCapabilityMock = vi.mocked(canUseNativeHostCapability);
const subscribeHostNavigationCanGoBackMock = vi.mocked(
subscribeHostNavigationCanGoBack,
);
const subscribeHostRuntimeChangeMock = vi.mocked(subscribeHostRuntimeChange);
function setNavigationCapability(enabled: boolean) {
canUseNativeHostCapabilityMock.mockImplementation((capability) =>
enabled
? capability === 'host.events' || capability === 'navigation.canGoBack'
: false,
);
}
afterEach(() => {
vi.clearAllMocks();
});
test('host navigation can-go-back stays unsupported without both event capabilities', () => {
setNavigationCapability(false);
const { result } = renderHook(() => useHostNavigationCanGoBack());
expect(result.current).toEqual({
canGoBack: false,
isSupported: false,
});
expect(subscribeHostNavigationCanGoBackMock).not.toHaveBeenCalled();
});
test('host navigation can-go-back follows native app events', () => {
setNavigationCapability(true);
const unsubscribe = vi.fn();
let navigationListener:
| Parameters<typeof subscribeHostNavigationCanGoBack>[0]
| null = null;
subscribeHostNavigationCanGoBackMock.mockImplementation((listener) => {
navigationListener = listener;
return unsubscribe;
});
const { result, unmount } = renderHook(() =>
useHostNavigationCanGoBack(),
);
expect(result.current).toEqual({
canGoBack: false,
isSupported: true,
});
act(() => {
navigationListener?.({ canGoBack: true });
});
expect(result.current).toEqual({
canGoBack: true,
isSupported: true,
});
act(() => {
navigationListener?.({ canGoBack: false });
});
expect(result.current).toEqual({
canGoBack: false,
isSupported: true,
});
unmount();
expect(unsubscribe).toHaveBeenCalledTimes(1);
});
test('host navigation can-go-back subscribes after runtime refresh exposes capabilities', () => {
setNavigationCapability(false);
let runtimeListener: Parameters<typeof subscribeHostRuntimeChange>[0] | null =
null;
let navigationListener:
| Parameters<typeof subscribeHostNavigationCanGoBack>[0]
| null = null;
subscribeHostRuntimeChangeMock.mockImplementation((listener) => {
runtimeListener = listener;
return () => undefined;
});
subscribeHostNavigationCanGoBackMock.mockImplementation((listener) => {
navigationListener = listener;
return () => undefined;
});
const { result } = renderHook(() => useHostNavigationCanGoBack());
expect(result.current.isSupported).toBe(false);
setNavigationCapability(true);
act(() => {
runtimeListener?.();
});
expect(result.current.isSupported).toBe(true);
expect(subscribeHostNavigationCanGoBackMock).toHaveBeenCalledTimes(1);
act(() => {
navigationListener?.({ canGoBack: true });
});
expect(result.current.canGoBack).toBe(true);
});