d3eeaf979d
移动端底部导航仅保留我的入口 移动端创作、项目与画布直达统一展示桌面端提示 补充移动端入口与画布阻断回归测试 纠正旧模板退役时越权扩大的移动端产品口径
361 lines
11 KiB
TypeScript
361 lines
11 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
import { useState } from 'react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { PlatformEntryFlowShellImpl } from './PlatformEntryActiveFlowShell';
|
|
import type { SelectionStage } from './platformEntryActiveTypes';
|
|
|
|
const authUiMock = vi.hoisted(() => ({
|
|
value: {
|
|
user: null,
|
|
canAccessProtectedData: false,
|
|
openLoginModal: vi.fn(),
|
|
openAccountModal: vi.fn(),
|
|
openSettingsModal: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
const responsiveMock = vi.hoisted(() => ({
|
|
isDesktopLayout: true,
|
|
}));
|
|
|
|
vi.mock('../auth/AuthUiContext', () => ({
|
|
useAuthUi: () => authUiMock.value,
|
|
}));
|
|
|
|
vi.mock('./platformEntryResponsive', () => ({
|
|
usePlatformDesktopLayout: () => responsiveMock.isDesktopLayout,
|
|
}));
|
|
|
|
vi.mock('../creation-home/CreationLandingView', () => ({
|
|
CreationLandingView: ({
|
|
onOpenProject,
|
|
onOpenProjects,
|
|
searchKeyword,
|
|
}: {
|
|
onOpenProject: (
|
|
projectId: string,
|
|
options?: { guide?: boolean; tool?: string },
|
|
) => void;
|
|
onOpenProjects: () => void;
|
|
searchKeyword?: string;
|
|
}) => (
|
|
<main aria-label="陶泥儿创作主页" data-search={searchKeyword}>
|
|
创作主页
|
|
<button type="button" onClick={onOpenProjects}>
|
|
查看项目
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
onOpenProject('tool-project', { tool: 'background-music' })
|
|
}
|
|
>
|
|
打开音乐工具
|
|
</button>
|
|
</main>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../project/ProjectGalleryView', () => ({
|
|
ProjectGalleryView: ({
|
|
onOpenProject,
|
|
searchKeyword,
|
|
}: {
|
|
onOpenProject: (projectId: string, options?: { guide?: boolean }) => void;
|
|
searchKeyword?: string;
|
|
}) => (
|
|
<main aria-label="项目" data-search={searchKeyword}>
|
|
项目
|
|
<button
|
|
type="button"
|
|
onClick={() => onOpenProject('guide-project', { guide: true })}
|
|
>
|
|
打开引导项目
|
|
</button>
|
|
</main>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../image-editor/ImageCanvasEditorView', () => ({
|
|
ImageCanvasEditorView: () => <main aria-label="图片画布编辑器" />,
|
|
}));
|
|
|
|
vi.mock('../../services/platform-entry/platformProfileClient', () => ({
|
|
getPlatformProfileDashboard: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('./usePlatformProfileCenterController', () => ({
|
|
usePlatformProfileCenterController: () => ({
|
|
buyRechargeProduct: vi.fn(),
|
|
closeNativeWechatPayment: vi.fn(),
|
|
confirmNativeWechatPayment: vi.fn(),
|
|
isLoadingRechargeCenter: false,
|
|
isLoadingWalletLedger: false,
|
|
isRechargeOpen: false,
|
|
isWalletLedgerOpen: false,
|
|
loadRechargeCenter: vi.fn(),
|
|
nativeWechatPayment: null,
|
|
openWalletLedgerPanel: vi.fn(),
|
|
rechargeCenter: null,
|
|
rechargeError: null,
|
|
rechargePaymentResult: null,
|
|
setIsRechargeOpen: vi.fn(),
|
|
setIsWalletLedgerOpen: vi.fn(),
|
|
setRechargePaymentResult: vi.fn(),
|
|
submittingRechargeProductId: null,
|
|
walletLedger: null,
|
|
walletLedgerError: null,
|
|
wechatRechargeOrderConfirmationState: null,
|
|
}),
|
|
}));
|
|
|
|
function StatefulPlatformEntryFlowShell({
|
|
initialStage,
|
|
}: {
|
|
initialStage: SelectionStage;
|
|
}) {
|
|
const [selectionStage, setSelectionStage] = useState(initialStage);
|
|
|
|
return (
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage={selectionStage}
|
|
setSelectionStage={(nextStage) => setSelectionStage(nextStage)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
describe('PlatformEntryActiveFlowShell', () => {
|
|
beforeEach(() => {
|
|
window.history.replaceState(null, '', '/creation');
|
|
authUiMock.value.openLoginModal.mockReset();
|
|
responsiveMock.isDesktopLayout = true;
|
|
});
|
|
|
|
it('keeps the active desktop rail and the shared account capsule', async () => {
|
|
const setSelectionStage = vi.fn();
|
|
const { container, rerender } = render(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="creation-home"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
await screen.findByRole('main', { name: '陶泥儿创作主页' }),
|
|
).toBeTruthy();
|
|
const navigation = screen.getByRole('navigation', { name: '平台导航' });
|
|
expect(
|
|
within(navigation)
|
|
.getAllByRole('button')
|
|
.map((button) => button.getAttribute('aria-label')),
|
|
).toEqual(['创作', '项目', '我的']);
|
|
expect(
|
|
within(navigation)
|
|
.getByRole('button', { name: '创作' })
|
|
.getAttribute('aria-current'),
|
|
).toBe('page');
|
|
|
|
const topbar = container.querySelector('.platform-desktop-topbar');
|
|
expect(topbar).toBeTruthy();
|
|
expect(
|
|
topbar?.querySelectorAll('button.platform-desktop-search'),
|
|
).toHaveLength(1);
|
|
expect(
|
|
within(topbar as HTMLElement).queryByRole('button', { name: '设置' }),
|
|
).toBeNull();
|
|
|
|
fireEvent.change(
|
|
screen.getByRole('searchbox', { name: '搜索项目和素材' }),
|
|
{ target: { value: '角色' } },
|
|
);
|
|
fireEvent.click(screen.getByRole('button', { name: '搜索' }));
|
|
expect(
|
|
screen
|
|
.getByRole('main', { name: '陶泥儿创作主页' })
|
|
.getAttribute('data-search'),
|
|
).toBe('角色');
|
|
|
|
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
|
|
|
|
expect(window.location.pathname).toBe('/profile');
|
|
expect(setSelectionStage).toHaveBeenCalledWith('profile', {
|
|
path: '/profile',
|
|
});
|
|
|
|
rerender(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="profile"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
|
|
expect(
|
|
within(navigation)
|
|
.getByRole('button', { name: '我的' })
|
|
.getAttribute('aria-current'),
|
|
).toBe('page');
|
|
});
|
|
|
|
it('keeps only profile reachable from the mobile dock', async () => {
|
|
responsiveMock.isDesktopLayout = false;
|
|
const setSelectionStage = vi.fn();
|
|
render(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="platform"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
const navigation = await screen.findByRole('navigation', {
|
|
name: '移动平台导航',
|
|
});
|
|
expect(
|
|
within(navigation)
|
|
.getAllByRole('button')
|
|
.map((button) => button.getAttribute('aria-label')),
|
|
).toEqual(['我的']);
|
|
|
|
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
|
|
expect(window.location.pathname).toBe('/profile');
|
|
expect(setSelectionStage).toHaveBeenCalledWith('profile', {
|
|
path: '/profile',
|
|
});
|
|
});
|
|
|
|
it.each(['creation-home', 'project', 'image-editor'] as const)(
|
|
'shows the desktop guide instead of mounting the %s page on mobile',
|
|
async (selectionStage) => {
|
|
responsiveMock.isDesktopLayout = false;
|
|
|
|
render(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage={selectionStage}
|
|
setSelectionStage={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
await screen.findByRole('main', { name: '桌面端创作提示' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
screen.queryByRole('main', { name: '陶泥儿创作主页' }),
|
|
).toBeNull();
|
|
expect(screen.queryByRole('main', { name: '项目' })).toBeNull();
|
|
expect(
|
|
screen.queryByRole('main', { name: '图片画布编辑器' }),
|
|
).toBeNull();
|
|
},
|
|
);
|
|
|
|
it('shows the desktop guide instead of the editor after opening a canvas tool from the mobile root', async () => {
|
|
responsiveMock.isDesktopLayout = false;
|
|
|
|
render(<StatefulPlatformEntryFlowShell initialStage="platform" />);
|
|
|
|
fireEvent.click(
|
|
await screen.findByRole('button', { name: '打开音乐工具' }),
|
|
);
|
|
|
|
expect(
|
|
await screen.findByRole('main', { name: '桌面端创作提示' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
screen.queryByRole('main', { name: '图片画布编辑器' }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('switches between creation and projects and passes search to the active page', async () => {
|
|
const setSelectionStage = vi.fn();
|
|
const { rerender } = render(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="creation-home"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
const navigation = await screen.findByRole('navigation', {
|
|
name: '平台导航',
|
|
});
|
|
fireEvent.click(within(navigation).getByRole('button', { name: '项目' }));
|
|
|
|
expect(window.location.pathname).toBe('/project');
|
|
expect(setSelectionStage).toHaveBeenCalledWith('project', {
|
|
path: '/project',
|
|
});
|
|
|
|
rerender(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="project"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
const projectPage = await screen.findByRole('main', { name: '项目' });
|
|
expect(
|
|
within(navigation)
|
|
.getByRole('button', { name: '项目' })
|
|
.getAttribute('aria-current'),
|
|
).toBe('page');
|
|
expect(
|
|
within(navigation)
|
|
.getByRole('button', { name: '创作' })
|
|
.getAttribute('aria-current'),
|
|
).toBeNull();
|
|
|
|
fireEvent.change(
|
|
screen.getByRole('searchbox', { name: '搜索项目和素材' }),
|
|
{ target: { value: '场景' } },
|
|
);
|
|
fireEvent.click(screen.getByRole('button', { name: '搜索' }));
|
|
expect(projectPage.getAttribute('data-search')).toBe('场景');
|
|
|
|
fireEvent.click(within(navigation).getByRole('button', { name: '创作' }));
|
|
expect(window.location.pathname).toBe('/creation');
|
|
expect(setSelectionStage).toHaveBeenCalledWith('creation-home', {
|
|
path: '/creation',
|
|
});
|
|
});
|
|
|
|
it('keeps guide and tool intent in editor navigation URLs', async () => {
|
|
const setSelectionStage = vi.fn();
|
|
const { rerender } = render(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="project"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(
|
|
await screen.findByRole('button', { name: '打开引导项目' }),
|
|
);
|
|
expect(`${window.location.pathname}${window.location.search}`).toBe(
|
|
'/editor/canvas?projectid=guide-project&guide=toolbar',
|
|
);
|
|
expect(setSelectionStage).toHaveBeenCalledWith('image-editor', {
|
|
path: '/editor/canvas?projectid=guide-project&guide=toolbar',
|
|
});
|
|
|
|
window.history.replaceState(null, '', '/creation');
|
|
setSelectionStage.mockClear();
|
|
rerender(
|
|
<PlatformEntryFlowShellImpl
|
|
selectionStage="creation-home"
|
|
setSelectionStage={setSelectionStage}
|
|
/>,
|
|
);
|
|
fireEvent.click(
|
|
await screen.findByRole('button', { name: '打开音乐工具' }),
|
|
);
|
|
expect(`${window.location.pathname}${window.location.search}`).toBe(
|
|
'/editor/canvas?projectid=tool-project&tool=background-music',
|
|
);
|
|
expect(setSelectionStage).toHaveBeenCalledWith('image-editor', {
|
|
path: '/editor/canvas?projectid=tool-project&tool=background-music',
|
|
});
|
|
});
|
|
});
|