1
This commit is contained in:
@@ -508,6 +508,11 @@ function renderLoggedOutHomeView(
|
||||
| 'latestEntries'
|
||||
| 'onOpenGalleryDetail'
|
||||
| 'onSearchPublicCode'
|
||||
| 'recommendRuntimeContent'
|
||||
| 'activeRecommendEntryKey'
|
||||
| 'isStartingRecommendEntry'
|
||||
| 'recommendRuntimeError'
|
||||
| 'onSelectRecommendEntry'
|
||||
>
|
||||
> = {},
|
||||
) {
|
||||
@@ -553,6 +558,15 @@ function renderLoggedOutHomeView(
|
||||
onOpenCreateWorld={vi.fn()}
|
||||
onOpenCreateTypePicker={vi.fn()}
|
||||
onOpenGalleryDetail={overrides.onOpenGalleryDetail ?? vi.fn()}
|
||||
recommendRuntimeContent={
|
||||
overrides.recommendRuntimeContent ?? (
|
||||
<div data-testid="recommend-runtime">运行内容</div>
|
||||
)
|
||||
}
|
||||
activeRecommendEntryKey={overrides.activeRecommendEntryKey}
|
||||
isStartingRecommendEntry={overrides.isStartingRecommendEntry}
|
||||
recommendRuntimeError={overrides.recommendRuntimeError}
|
||||
onSelectRecommendEntry={overrides.onSelectRecommendEntry}
|
||||
onOpenLibraryDetail={vi.fn()}
|
||||
onSearchPublicCode={overrides.onSearchPublicCode ?? vi.fn()}
|
||||
/>
|
||||
@@ -562,7 +576,13 @@ function renderLoggedOutHomeView(
|
||||
|
||||
function renderStatefulLoggedOutHomeView(
|
||||
overrides: Partial<
|
||||
Pick<RpgEntryHomeViewProps, 'featuredEntries' | 'latestEntries'>
|
||||
Pick<
|
||||
RpgEntryHomeViewProps,
|
||||
| 'featuredEntries'
|
||||
| 'latestEntries'
|
||||
| 'onOpenGalleryDetail'
|
||||
| 'onSearchPublicCode'
|
||||
>
|
||||
> = {},
|
||||
) {
|
||||
function StatefulLoggedOutHomeView() {
|
||||
@@ -610,9 +630,10 @@ function renderStatefulLoggedOutHomeView(
|
||||
onResumeSave={vi.fn()}
|
||||
onOpenCreateWorld={vi.fn()}
|
||||
onOpenCreateTypePicker={vi.fn()}
|
||||
onOpenGalleryDetail={vi.fn()}
|
||||
onOpenGalleryDetail={overrides.onOpenGalleryDetail ?? vi.fn()}
|
||||
recommendRuntimeContent={<div data-testid="recommend-runtime" />}
|
||||
onOpenLibraryDetail={vi.fn()}
|
||||
onSearchPublicCode={vi.fn()}
|
||||
onSearchPublicCode={overrides.onSearchPublicCode ?? vi.fn()}
|
||||
/>
|
||||
</AuthUiContext.Provider>
|
||||
);
|
||||
@@ -956,57 +977,12 @@ test('logged out bottom nav keeps creation centered with recommend icon', () =>
|
||||
expect(buttons[2]?.querySelector('.lucide-compass')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('mobile home search submits public work code', async () => {
|
||||
test('mobile discover search submits public work code', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSearchPublicCode = vi.fn();
|
||||
|
||||
render(
|
||||
<AuthUiContext.Provider
|
||||
value={{
|
||||
user: null,
|
||||
canAccessProtectedData: false,
|
||||
openLoginModal: vi.fn(),
|
||||
requireAuth: vi.fn(),
|
||||
openSettingsModal: vi.fn(),
|
||||
openAccountModal: vi.fn(),
|
||||
setCurrentUser: vi.fn(),
|
||||
logout: vi.fn(async () => undefined),
|
||||
musicVolume: 0.42,
|
||||
setMusicVolume: vi.fn(),
|
||||
platformTheme: 'light',
|
||||
setPlatformTheme: vi.fn(),
|
||||
isHydratingSettings: false,
|
||||
isPersistingSettings: false,
|
||||
settingsError: null,
|
||||
}}
|
||||
>
|
||||
<RpgEntryHomeView
|
||||
activeTab="home"
|
||||
onTabChange={vi.fn()}
|
||||
hasSavedGame={false}
|
||||
savedSnapshot={null}
|
||||
saveEntries={[]}
|
||||
saveError={null}
|
||||
featuredEntries={[]}
|
||||
latestEntries={[]}
|
||||
myEntries={[]}
|
||||
historyEntries={[]}
|
||||
profileDashboard={null}
|
||||
isLoadingPlatform={false}
|
||||
isLoadingDashboard={false}
|
||||
isResumingSaveWorldKey={null}
|
||||
platformError={null}
|
||||
dashboardError={null}
|
||||
onContinueGame={vi.fn()}
|
||||
onResumeSave={vi.fn()}
|
||||
onOpenCreateWorld={vi.fn()}
|
||||
onOpenCreateTypePicker={vi.fn()}
|
||||
onOpenGalleryDetail={vi.fn()}
|
||||
onOpenLibraryDetail={vi.fn()}
|
||||
onSearchPublicCode={onSearchPublicCode}
|
||||
/>
|
||||
</AuthUiContext.Provider>,
|
||||
);
|
||||
renderStatefulLoggedOutHomeView({ onSearchPublicCode });
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
|
||||
const searchInput = screen.getByPlaceholderText(
|
||||
'搜索作品号、名称、作者、描述',
|
||||
@@ -1016,7 +992,7 @@ test('mobile home search submits public work code', async () => {
|
||||
expect(onSearchPublicCode).toHaveBeenCalledWith('PZ-PROFILE1');
|
||||
});
|
||||
|
||||
test('home search fuzzy matches public work id, name, author and description', async () => {
|
||||
test('discover search fuzzy matches public work id, name, author and description', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenGalleryDetail = vi.fn();
|
||||
const onSearchPublicCode = vi.fn();
|
||||
@@ -1041,46 +1017,52 @@ test('home search fuzzy matches public work id, name, author and description', a
|
||||
},
|
||||
] satisfies PlatformPublicGalleryCard[];
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: entries,
|
||||
onOpenGalleryDetail,
|
||||
onSearchPublicCode,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
const discoverPanel = document.getElementById('platform-tab-panel-category');
|
||||
if (!discoverPanel) {
|
||||
throw new Error('缺少发现面板');
|
||||
}
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('搜索作品号、名称、作者、描述');
|
||||
await user.type(searchInput, 'MOON01{enter}');
|
||||
expect(await screen.findByText('搜索结果')).toBeTruthy();
|
||||
expect(screen.getByText('月井机关')).toBeTruthy();
|
||||
expect(screen.queryByText('火桥谜图')).toBeNull();
|
||||
expect(await within(discoverPanel).findByText('搜索结果')).toBeTruthy();
|
||||
expect(within(discoverPanel).getByText('月井机关')).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('火桥谜图')).toBeNull();
|
||||
expect(onSearchPublicCode).not.toHaveBeenCalled();
|
||||
|
||||
await user.clear(searchInput);
|
||||
await user.type(searchInput, '火桥{enter}');
|
||||
expect(await screen.findByText('火桥谜图')).toBeTruthy();
|
||||
expect(screen.queryByText('月井机关')).toBeNull();
|
||||
expect(await within(discoverPanel).findByText('火桥谜图')).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('月井机关')).toBeNull();
|
||||
|
||||
await user.clear(searchInput);
|
||||
await user.type(searchInput, '月井守望{enter}');
|
||||
expect(await screen.findByText('月井机关')).toBeTruthy();
|
||||
expect(screen.queryByText('火桥谜图')).toBeNull();
|
||||
expect(await within(discoverPanel).findByText('月井机关')).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('火桥谜图')).toBeNull();
|
||||
|
||||
await user.clear(searchInput);
|
||||
await user.type(searchInput, '熔岩断桥{enter}');
|
||||
expect(await screen.findByText('火桥谜图')).toBeTruthy();
|
||||
expect(screen.queryByText('月井机关')).toBeNull();
|
||||
expect(await within(discoverPanel).findByText('火桥谜图')).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('月井机关')).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /火桥谜图/u }));
|
||||
expect(onOpenGalleryDetail).toHaveBeenCalledWith(entries[1]);
|
||||
});
|
||||
|
||||
test('home search keeps public code fallback when local works do not match', async () => {
|
||||
test('discover search keeps public code fallback when local works do not match', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSearchPublicCode = vi.fn();
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
onSearchPublicCode,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('搜索作品号、名称、作者、描述');
|
||||
await user.type(searchInput, 'CW-REMOTE-ONLY{enter}');
|
||||
@@ -1093,10 +1075,11 @@ test('public gallery cards hide work code until detail is opened', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenGalleryDetail = vi.fn();
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
onOpenGalleryDetail,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
|
||||
expect(screen.queryByText('PZ-EPUBLIC1')).toBeNull();
|
||||
expect(
|
||||
@@ -1108,47 +1091,54 @@ test('public gallery cards hide work code until detail is opened', async () => {
|
||||
expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry);
|
||||
});
|
||||
|
||||
test('mobile public work cards render cover, author, kind and cover stats', () => {
|
||||
const { container } = renderLoggedOutHomeView(vi.fn(), {
|
||||
test('mobile recommend page renders runtime viewport and bottom switcher', () => {
|
||||
const onSelectRecommendEntry = vi.fn();
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
|
||||
onSelectRecommendEntry,
|
||||
});
|
||||
|
||||
const card = screen.getByRole('button', {
|
||||
name: /奇幻拼图,拼图,20游玩,5改造,12点赞/u,
|
||||
});
|
||||
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
|
||||
expect(screen.queryByText('一张用于公开分享的拼图作品。')).toBeNull();
|
||||
expect(
|
||||
card.querySelector('.platform-public-work-card__cover.aspect-video'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
card.querySelector('.platform-public-work-card__cover-stats'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
card.querySelectorAll('.platform-public-work-card__cover-stat'),
|
||||
).toHaveLength(3);
|
||||
expect(
|
||||
card.querySelector('.platform-public-work-card__kind')?.textContent,
|
||||
).toBe('拼图');
|
||||
expect(
|
||||
card.querySelector('.platform-public-work-card__author-avatar')
|
||||
?.textContent,
|
||||
).toBe('拼');
|
||||
expect(screen.getByText('奇幻拼图')).toBeTruthy();
|
||||
document.querySelector('.platform-public-work-card__cover'),
|
||||
).toBeNull();
|
||||
expect(screen.getByText('拼图玩家')).toBeTruthy();
|
||||
expect(screen.getByText('一张用于公开分享的拼图作品。')).toBeTruthy();
|
||||
expect(screen.getByText('奇幻')).toBeTruthy();
|
||||
expect(screen.getByText('20')).toBeTruthy();
|
||||
expect(screen.getByText('5')).toBeTruthy();
|
||||
expect(screen.getByText('12')).toBeTruthy();
|
||||
expect(card.querySelector('.platform-pill--warm')?.textContent).not.toBe(
|
||||
'推荐',
|
||||
);
|
||||
expect(
|
||||
container.querySelector('.platform-mobile-home-channel--active')
|
||||
?.textContent,
|
||||
).toBe('推荐');
|
||||
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');
|
||||
});
|
||||
|
||||
test('public work cards load real author avatar from public user summary', async () => {
|
||||
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;
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
latestEntries: [puzzlePublicEntry, secondEntry],
|
||||
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
|
||||
onSelectRecommendEntry,
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '切换到 第二拼图' }));
|
||||
|
||||
expect(onSelectRecommendEntry).toHaveBeenCalledWith(secondEntry);
|
||||
});
|
||||
|
||||
test('mobile recommend meta loads real author avatar from public user summary', async () => {
|
||||
mockGetPublicAuthUserById.mockResolvedValueOnce({
|
||||
id: 'user-2',
|
||||
publicUserCode: 'SY-00000002',
|
||||
@@ -1159,16 +1149,13 @@ test('public work cards load real author avatar from public user summary', async
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
featuredEntries: [puzzlePublicEntry],
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
});
|
||||
|
||||
const card = screen.getByRole('button', {
|
||||
name: /奇幻拼图,拼图,20游玩,5改造,12点赞/u,
|
||||
activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
card
|
||||
.querySelector('.platform-public-work-card__author-avatar-image')
|
||||
document
|
||||
.querySelector('.platform-recommend-work-meta__avatar img')
|
||||
?.getAttribute('src'),
|
||||
).toBe('data:image/png;base64,AUTHOR');
|
||||
});
|
||||
@@ -1177,7 +1164,7 @@ test('public work cards load real author avatar from public user summary', async
|
||||
expect(mockGetPublicAuthUserByCode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('mobile home feed only rotates the card closest to screen center', () => {
|
||||
test('mobile discover recommend feed only rotates the card closest to screen center', async () => {
|
||||
vi.useFakeTimers();
|
||||
Object.defineProperty(window, 'requestAnimationFrame', {
|
||||
configurable: true,
|
||||
@@ -1199,9 +1186,12 @@ test('mobile home feed only rotates the card closest to screen center', () => {
|
||||
);
|
||||
const cardRects = new Map<string, DOMRect>();
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [firstEntry, secondEntry],
|
||||
});
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: '发现' }).click();
|
||||
});
|
||||
|
||||
const tabPanel = document.querySelector('.platform-tab-panel--active');
|
||||
const firstCard = screen.getByRole('button', { name: /中心拼图一/u });
|
||||
@@ -1340,15 +1330,22 @@ test('mobile today channel only shows newly published works from today', async (
|
||||
updatedAt: todayPublishedAt,
|
||||
} satisfies PlatformPublicGalleryCard;
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [yesterdayEntry, updatedTodayEntry, todayEntry],
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '今日游戏' }));
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '今日' }));
|
||||
const discoverPanel = document.getElementById('platform-tab-panel-category');
|
||||
if (!discoverPanel) {
|
||||
throw new Error('缺少发现面板');
|
||||
}
|
||||
|
||||
expect(screen.getByRole('button', { name: /今日新游/u })).toBeTruthy();
|
||||
expect(screen.queryByText('昨日旧作')).toBeNull();
|
||||
expect(screen.queryByText('今日更新旧作')).toBeNull();
|
||||
expect(
|
||||
within(discoverPanel).getByRole('button', { name: /今日新游/u }),
|
||||
).toBeTruthy();
|
||||
expect(within(discoverPanel).queryByText('昨日旧作')).toBeNull();
|
||||
expect(within(discoverPanel).queryByText('今日更新旧作')).toBeNull();
|
||||
});
|
||||
|
||||
test('desktop home syncs mobile home modules without square or latest labels', () => {
|
||||
@@ -1369,7 +1366,7 @@ test('desktop home syncs mobile home modules without square or latest labels', (
|
||||
});
|
||||
|
||||
expect(screen.getByText('今日游戏')).toBeTruthy();
|
||||
expect(screen.getByText('推荐')).toBeTruthy();
|
||||
expect(screen.getAllByText('推荐').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('作品分类')).toBeTruthy();
|
||||
expect(screen.getAllByText('桌面今日新游').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('趋势关注')).toBeNull();
|
||||
@@ -1383,16 +1380,17 @@ test('desktop home syncs mobile home modules without square or latest labels', (
|
||||
|
||||
test('mobile home moves category shelf into game category channel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = renderLoggedOutHomeView(vi.fn(), {
|
||||
const { container } = renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'PC游戏' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '即点即玩' })).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '游戏分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
|
||||
expect(screen.getAllByText('游戏分类').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('分类').length).toBeGreaterThan(0);
|
||||
expect(screen.getByRole('button', { name: /筛选/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '奇幻' })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /奇幻拼图,试玩/u })).toBeTruthy();
|
||||
@@ -1407,11 +1405,12 @@ test('mobile home moves category shelf into game category channel', async () =>
|
||||
test('mobile game category list orders works by composite public metric', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderLoggedOutHomeView(vi.fn(), {
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry, hotRankEntry],
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '游戏分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '奇幻' }));
|
||||
|
||||
const gameItems = Array.from(
|
||||
@@ -1427,8 +1426,7 @@ test('bottom category tab becomes ranking and switches ranking metrics', async (
|
||||
latestEntries: [remixRankEntry, hotRankEntry, newRankEntry],
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('button', { name: '分类' })).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '排行' }));
|
||||
|
||||
expect(await screen.findByRole('tab', { name: '热门榜' })).toBeTruthy();
|
||||
@@ -1462,6 +1460,7 @@ test('ranking rows limit displayed work name and show two short tags on the thir
|
||||
latestEntries: [longTextRankEntry],
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '排行' }));
|
||||
|
||||
const rankingPanel = document.getElementById('platform-tab-panel-category');
|
||||
|
||||
Reference in New Issue
Block a user