This commit is contained in:
2026-05-08 21:46:11 +08:00
parent 94975e4735
commit e410f7974e
13 changed files with 757 additions and 426 deletions

View File

@@ -1,6 +1,13 @@
/* @vitest-environment jsdom */
import { act, render, screen, waitFor, within } from '@testing-library/react';
import {
act,
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
import { afterEach, expect, test, vi } from 'vitest';
@@ -302,6 +309,17 @@ vi.mock('../ResolvedAssetImage', () => ({
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',
@@ -512,7 +530,8 @@ function renderLoggedOutHomeView(
| 'activeRecommendEntryKey'
| 'isStartingRecommendEntry'
| 'recommendRuntimeError'
| 'onSelectRecommendEntry'
| 'onSelectNextRecommendEntry'
| 'onSelectPreviousRecommendEntry'
>
> = {},
) {
@@ -566,7 +585,8 @@ function renderLoggedOutHomeView(
activeRecommendEntryKey={overrides.activeRecommendEntryKey}
isStartingRecommendEntry={overrides.isStartingRecommendEntry}
recommendRuntimeError={overrides.recommendRuntimeError}
onSelectRecommendEntry={overrides.onSelectRecommendEntry}
onSelectNextRecommendEntry={overrides.onSelectNextRecommendEntry}
onSelectPreviousRecommendEntry={overrides.onSelectPreviousRecommendEntry}
onOpenLibraryDetail={vi.fn()}
onSearchPublicCode={overrides.onSearchPublicCode ?? vi.fn()}
/>
@@ -960,7 +980,7 @@ test('shows a reachable login entry in logged out mobile shell', async () => {
expect(openLoginModal).toHaveBeenCalledTimes(1);
});
test('logged out bottom nav keeps creation centered with recommend icon', () => {
test('logged out bottom nav turns active recommend tab into next action', () => {
const { container } = renderLoggedOutHomeView(vi.fn());
const nav = container.querySelector('.platform-bottom-nav');
@@ -968,11 +988,11 @@ test('logged out bottom nav keeps creation centered with recommend icon', () =>
const buttons = within(nav as HTMLElement).getAllByRole('button');
expect(buttons.map((button) => button.textContent)).toEqual([
'推荐',
'下一个',
'创作',
'发现',
]);
expect(buttons[0]?.querySelector('.lucide-gamepad-2')).toBeTruthy();
expect(buttons[0]?.querySelector('.lucide-chevron-down')).toBeTruthy();
expect(buttons[1]?.querySelector('.lucide-sparkles')).toBeTruthy();
expect(buttons[2]?.querySelector('.lucide-compass')).toBeTruthy();
});
@@ -1091,16 +1111,18 @@ test('public gallery cards hide work code until detail is opened', async () => {
expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry);
});
test('mobile recommend page renders runtime viewport and bottom switcher', () => {
const onSelectRecommendEntry = vi.fn();
test('mobile recommend page renders runtime viewport without bottom work cards', () => {
const onOpenGalleryDetail = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onSelectRecommendEntry,
onOpenGalleryDetail,
});
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();
expect(
document.querySelector('.platform-public-work-card__cover'),
@@ -1109,33 +1131,66 @@ test('mobile recommend page renders runtime viewport and bottom switcher', () =>
expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0);
expect(screen.getAllByText('20').length).toBeGreaterThan(0);
expect(screen.getAllByText('12').length).toBeGreaterThan(0);
const switchButton = screen.getByRole('button', {
name: '切换到 奇幻拼图',
});
expect(switchButton.getAttribute('aria-pressed')).toBe('true');
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('奇幻拼图 作品信息'));
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
});
test('mobile recommend switcher selects a different public work', async () => {
const user = userEvent.setup();
const onSelectRecommendEntry = vi.fn();
const secondEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-second',
profileId: 'puzzle-profile-second',
publicWorkCode: 'PZ-SECOND',
worldName: '第二拼图',
} satisfies PlatformPublicGalleryCard;
test('mobile recommend loading state is themed instead of hardcoded black', () => {
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry, secondEntry],
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onSelectRecommendEntry,
isStartingRecommendEntry: true,
recommendRuntimeContent: null,
});
await user.click(screen.getByRole('button', { name: '切换到 第二拼图' }));
const loadingState = screen.getByText('加载中...');
expect(loadingState.className).toContain('platform-recommend-runtime-state');
expect(loadingState.className).not.toContain('bg-black');
});
expect(onSelectRecommendEntry).toHaveBeenCalledWith(secondEntry);
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 () => {
const user = userEvent.setup();
const onSelectNextRecommendEntry = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
onSelectNextRecommendEntry,
});
await user.click(screen.getByRole('button', { name: '下一个' }));
expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1);
});
test('mobile recommend meta loads real author avatar from public user summary', async () => {