This commit is contained in:
2026-05-09 17:15:23 +08:00
parent 80a4183b45
commit a0ed128bde
43 changed files with 2573 additions and 381 deletions

View File

@@ -310,16 +310,6 @@ const originalMatchMedia = window.matchMedia;
const originalRequestAnimationFrame = window.requestAnimationFrame;
const originalCancelAnimationFrame = window.cancelAnimationFrame;
function dispatchClientYPointerEvent(
target: HTMLElement,
type: string,
clientY: number,
) {
const event = new Event(type, { bubbles: true, cancelable: true });
Object.assign(event, { clientY });
target.dispatchEvent(event);
}
const puzzlePublicEntry = {
sourceType: 'puzzle',
workId: 'puzzle-work-public-1',
@@ -534,6 +524,7 @@ function renderLoggedOutHomeView(
| 'onSelectPreviousRecommendEntry'
>
> = {},
activeTab: RpgEntryHomeViewProps['activeTab'] = 'home',
) {
return render(
<AuthUiContext.Provider
@@ -556,7 +547,7 @@ function renderLoggedOutHomeView(
}}
>
<RpgEntryHomeView
activeTab="home"
activeTab={activeTab}
onTabChange={vi.fn()}
hasSavedGame={false}
savedSnapshot={null}
@@ -602,19 +593,27 @@ function renderStatefulLoggedOutHomeView(
| 'latestEntries'
| 'onOpenGalleryDetail'
| 'onSearchPublicCode'
| 'recommendRuntimeContent'
| 'activeRecommendEntryKey'
| 'onSelectNextRecommendEntry'
| 'onSelectPreviousRecommendEntry'
>
> = {},
) {
const authSpies = {
openLoginModal: vi.fn(),
};
function StatefulLoggedOutHomeView() {
const [activeTab, setActiveTab] =
useState<RpgEntryHomeViewProps['activeTab']>('home');
useState<RpgEntryHomeViewProps['activeTab']>('category');
return (
<AuthUiContext.Provider
value={{
user: null,
canAccessProtectedData: false,
openLoginModal: vi.fn(),
openLoginModal: authSpies.openLoginModal,
requireAuth: vi.fn(),
openSettingsModal: vi.fn(),
openAccountModal: vi.fn(),
@@ -651,7 +650,14 @@ function renderStatefulLoggedOutHomeView(
onOpenCreateWorld={vi.fn()}
onOpenCreateTypePicker={vi.fn()}
onOpenGalleryDetail={overrides.onOpenGalleryDetail ?? vi.fn()}
recommendRuntimeContent={<div data-testid="recommend-runtime" />}
recommendRuntimeContent={
overrides.recommendRuntimeContent ?? (
<div data-testid="recommend-runtime" />
)
}
activeRecommendEntryKey={overrides.activeRecommendEntryKey}
onSelectNextRecommendEntry={overrides.onSelectNextRecommendEntry}
onSelectPreviousRecommendEntry={overrides.onSelectPreviousRecommendEntry}
onOpenLibraryDetail={vi.fn()}
onSearchPublicCode={overrides.onSearchPublicCode ?? vi.fn()}
/>
@@ -659,7 +665,10 @@ function renderStatefulLoggedOutHomeView(
);
}
return render(<StatefulLoggedOutHomeView />);
return {
...render(<StatefulLoggedOutHomeView />),
openLoginModal: authSpies.openLoginModal,
};
}
afterEach(() => {
@@ -1111,35 +1120,80 @@ test('public gallery cards hide work code until detail is opened', async () => {
expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry);
});
test('mobile recommend page renders runtime viewport without bottom work cards', () => {
test('logged out mobile shell defaults to discover tab', () => {
const { container } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
});
const activePanel = container.querySelector('.platform-tab-panel--active');
expect(activePanel?.id).toBe('platform-tab-panel-category');
expect(screen.getByPlaceholderText('搜索作品号、名称、作者、描述')).toBeTruthy();
});
test('logged out recommend tab opens login modal and shows cover only', async () => {
const user = userEvent.setup();
const { container, openLoginModal } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
});
const bottomNav = container.querySelector('.platform-bottom-nav');
if (!bottomNav) {
throw new Error('缺少底部导航');
}
await user.click(
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
);
expect(openLoginModal).toHaveBeenCalledTimes(1);
expect(container.querySelector('.platform-recommend-cover-only')).toBeTruthy();
expect(screen.queryByTestId('recommend-runtime')).toBeNull();
expect(screen.queryByLabelText('奇幻拼图 作品信息')).toBeNull();
expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0);
});
test('logged out recommend cover opens login modal again', async () => {
const user = userEvent.setup();
const onOpenGalleryDetail = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
const { openLoginModal } = renderStatefulLoggedOutHomeView({
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onOpenGalleryDetail,
});
const bottomNav = document.querySelector('.platform-bottom-nav');
if (!bottomNav) {
throw new Error('缺少底部导航');
}
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
const runtimePanel = document.querySelector('.platform-recommend-runtime-panel');
expect(runtimePanel).toBeTruthy();
expect(runtimePanel?.className).not.toContain('bg-black');
expect(screen.queryByText('一张用于公开分享的拼图作品。')).toBeNull();
await user.click(
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
);
await user.click(screen.getByRole('button', { name: / /u }));
expect(openLoginModal).toHaveBeenCalledTimes(2);
expect(openLoginModal).toHaveBeenLastCalledWith(expect.any(Function));
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
});
test('logged out mobile recommend page renders cover instead of runtime', () => {
const onOpenGalleryDetail = vi.fn();
renderLoggedOutHomeView(
vi.fn(),
{
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onOpenGalleryDetail,
},
'home',
);
expect(screen.queryByTestId('recommend-runtime')).toBeNull();
expect(document.querySelector('.platform-recommend-cover-only')).toBeTruthy();
expect(
document.querySelector('.platform-public-work-card__cover'),
).toBeNull();
expect(screen.getByText('拼图玩家')).toBeTruthy();
expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0);
expect(screen.getAllByText('20').length).toBeGreaterThan(0);
expect(screen.getAllByText('12').length).toBeGreaterThan(0);
expect(screen.queryByRole('button', { name: '切换到 奇幻拼图' })).toBeNull();
expect(
screen.queryByRole('button', { name: '查看 奇幻拼图 详情' }),
).toBeNull();
expect(
screen.queryByRole('button', { name: '打开 奇幻拼图 详情' }),
).toBeNull();
expect(document.querySelector('.platform-recommend-switcher')).toBeNull();
fireEvent.click(screen.getByLabelText('奇幻拼图 作品信息'));
fireEvent.click(screen.getByRole('button', { name: / /u }));
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
});
@@ -1151,38 +1205,15 @@ test('mobile recommend loading state is themed instead of hardcoded black', () =
recommendRuntimeContent: null,
});
const loadingState = screen.getByText('加载中...');
expect(loadingState.className).toContain('platform-recommend-runtime-state');
expect(loadingState.className).not.toContain('bg-black');
expect(document.querySelector('.platform-recommend-cover-only')).toBeTruthy();
});
test('mobile recommend meta swipes between public works', () => {
const onSelectNextRecommendEntry = vi.fn();
const onSelectPreviousRecommendEntry = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onSelectNextRecommendEntry,
onSelectPreviousRecommendEntry,
});
const meta = screen.getByLabelText('奇幻拼图 作品信息');
dispatchClientYPointerEvent(meta, 'pointerdown', 240);
dispatchClientYPointerEvent(meta, 'pointerup', 180);
expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1);
expect(onSelectPreviousRecommendEntry).not.toHaveBeenCalled();
dispatchClientYPointerEvent(meta, 'pointerdown', 180);
dispatchClientYPointerEvent(meta, 'pointerup', 240);
expect(onSelectPreviousRecommendEntry).toHaveBeenCalledTimes(1);
});
test('active recommend bottom tab selects next work instead of navigating', async () => {
test('logged out active recommend bottom tab selects next work without login', async () => {
const user = userEvent.setup();
const onSelectNextRecommendEntry = vi.fn();
const openLoginModal = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
renderLoggedOutHomeView(openLoginModal, {
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onSelectNextRecommendEntry,
@@ -1191,6 +1222,7 @@ test('active recommend bottom tab selects next work instead of navigating', asyn
await user.click(screen.getByRole('button', { name: '下一个' }));
expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1);
expect(openLoginModal).not.toHaveBeenCalled();
});
test('mobile recommend meta loads real author avatar from public user summary', async () => {
@@ -1210,7 +1242,7 @@ test('mobile recommend meta loads real author avatar from public user summary',
await waitFor(() => {
expect(
document
.querySelector('.platform-recommend-work-meta__avatar img')
.querySelector('.platform-recommend-cover-only__author img')
?.getAttribute('src'),
).toBe('data:image/png;base64,AUTHOR');
});