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

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

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

215 lines
5.9 KiB
TypeScript

/* @vitest-environment jsdom */
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, test, vi } from 'vitest';
import App from './App';
import { AuthUiContext } from './components/auth/AuthUiContext';
import type { PlatformEntryFlowShellProps } from './components/platform-entry';
import {
canUseNativeHostCapability,
resetHostRuntimeCacheForTest,
} from './services/host-bridge/hostBridge';
import { resetNativeAppHostBridgeForTest } from './services/host-bridge/nativeAppHostBridge';
const appTitleMock = vi.hoisted(() => ({
syncAppTitle: vi.fn(),
}));
vi.mock('./services/appTitle', async (importOriginal) => {
const actual = await importOriginal<typeof import('./services/appTitle')>();
return {
...actual,
syncAppTitle: appTitleMock.syncAppTitle,
};
});
vi.mock('./components/platform-entry/PlatformEntryFlowShell', () => ({
PlatformEntryFlowShell: ({
handleCustomWorldSelect,
setSelectionStage,
selectionStage,
}: PlatformEntryFlowShellProps) => (
<div>
<div data-testid="selection-stage">{selectionStage}</div>
<div data-testid="share-capability">
{canUseNativeHostCapability('share.open') ? 'enabled' : 'disabled'}
</div>
<button
type="button"
onClick={() => setSelectionStage('puzzle-agent-workspace')}
>
打开拼图创作
</button>
<button
type="button"
onClick={() =>
handleCustomWorldSelect(
{
id: 'profile-1',
name: '潮雾列岛',
} as Parameters<
PlatformEntryFlowShellProps['handleCustomWorldSelect']
>[0],
)
}
>
进入 RPG
</button>
</div>
),
}));
vi.mock('./RpgRuntimeApp', () => ({
RpgRuntimeApp: ({ onExitRuntime }: { onExitRuntime: () => void }) => (
<button type="button" onClick={onExitRuntime}>
退出 RPG
</button>
),
}));
function renderApp() {
return render(
<AuthUiContext.Provider
value={{
canAccessProtectedData: false,
isHydratingSettings: false,
isPersistingSettings: false,
logout: vi.fn(async () => undefined),
musicVolume: 0.6,
openAccountModal: vi.fn(),
openLoginModal: vi.fn(),
openSettingsModal: vi.fn(),
platformTheme: 'light',
requireAuth: vi.fn(),
setCurrentUser: vi.fn(),
setMusicVolume: vi.fn(),
setPlatformTheme: vi.fn(),
settingsError: null,
user: null,
}}
>
<App />
</AuthUiContext.Provider>,
);
}
afterEach(() => {
appTitleMock.syncAppTitle.mockReset();
window.history.replaceState(null, '', '/');
delete window.__TAURI__;
delete window.ReactNativeWebView;
resetHostRuntimeCacheForTest();
resetNativeAppHostBridgeForTest();
vi.restoreAllMocks();
});
describe('App title sync', () => {
test('主站阶段变化会同步浏览器与宿主标题', () => {
renderApp();
expect(appTitleMock.syncAppTitle).toHaveBeenLastCalledWith('陶泥儿');
act(() => {
fireEvent.click(screen.getByRole('button', { name: '打开拼图创作' }));
});
expect(appTitleMock.syncAppTitle).toHaveBeenLastCalledWith(
'拼图创作 - 陶泥儿',
);
});
test('RPG runtime 进入和退出时同步窗口标题', async () => {
renderApp();
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '进入 RPG' }));
});
expect(appTitleMock.syncAppTitle).toHaveBeenLastCalledWith(
'RPG 运行中 - 陶泥儿',
);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '退出 RPG' }));
});
expect(appTitleMock.syncAppTitle).toHaveBeenLastCalledWith('陶泥儿');
});
test('启动时回读宿主 runtime 后刷新壳能力 UI', async () => {
window.history.replaceState(
null,
'',
'/?clientRuntime=native_app&hostShell=tauri_desktop',
);
window.__TAURI__ = {
core: {
invoke: vi.fn(async (_command, args) => {
const request = (args as { request: { id: string } }).request;
return {
bridge: 'GenarrativeHostBridge',
version: 1,
id: request.id,
ok: true,
result: {
shell: 'tauri_desktop',
platform: 'linux',
hostVersion: '0.1.0',
bridgeVersion: 1,
capabilities: ['host.getRuntime', 'share.open'],
},
};
}),
},
};
renderApp();
expect(screen.getByTestId('share-capability').textContent).toBe(
'disabled',
);
await screen.findByText('enabled');
expect(screen.getByTestId('share-capability').textContent).toBe(
'enabled',
);
});
test('原生壳直达二级页面时补齐 H5 返回锚点', async () => {
window.history.replaceState(
null,
'',
'/creation/puzzle?clientRuntime=native_app&hostShell=tauri_desktop&hostCapabilities=host.events,navigation.canGoBack',
);
window.__TAURI__ = {
core: {
invoke: vi.fn(async () => {
throw new Error('unsupported');
}),
},
};
renderApp();
expect(screen.getByTestId('selection-stage').textContent).toBe(
'puzzle-agent-workspace',
);
expect(window.location.pathname).toBe('/creation/puzzle');
expect(window.location.search).toBe(
'?clientRuntime=native_app&hostShell=tauri_desktop&hostCapabilities=host.events%2Cnavigation.canGoBack',
);
await act(async () => {
window.history.back();
});
await waitFor(() => {
expect(window.location.pathname).toBe('/');
});
expect(window.location.search).toBe(
'?clientRuntime=native_app&hostShell=tauri_desktop&hostCapabilities=host.events%2Cnavigation.canGoBack',
);
expect(screen.getByTestId('selection-stage').textContent).toBe('platform');
});
});