/* @vitest-environment jsdom */
import { act, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
import { afterEach, expect, test, vi } from 'vitest';
import type { AuthUser } from '../../services/authService';
import { AuthUiContext } from '../auth/AuthUiContext';
import {
RpgEntryHomeView,
type RpgEntryHomeViewProps,
} from './RpgEntryHomeView';
import type { PlatformPublicGalleryCard } from './rpgEntryWorldPresentation';
const { mockGetRpgProfileWalletLedger } = vi.hoisted(() => ({
mockGetRpgProfileWalletLedger: vi.fn(async () => ({
entries: [
{
id: 'ledger-1',
amountDelta: -1,
balanceAfter: 29,
sourceType: 'asset_operation_consume',
createdAt: '2026-04-28T10:00:00Z',
},
{
id: 'ledger-2',
amountDelta: 30,
balanceAfter: 30,
sourceType: 'invite_invitee_reward',
createdAt: '2026-04-28T09:00:00Z',
},
],
})),
}));
vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
getRpgProfileWalletLedger: mockGetRpgProfileWalletLedger,
getRpgProfileRechargeCenter: vi.fn(async () => ({
walletBalance: 0,
membership: {
status: 'normal',
tier: 'normal',
startedAt: null,
expiresAt: null,
updatedAt: null,
},
pointProducts: [
{
productId: 'points_60',
title: '60陶泥币',
priceCents: 600,
kind: 'points',
pointsAmount: 60,
bonusPoints: 60,
durationDays: 0,
badgeLabel: '首充双倍',
description: '首充送60陶泥币',
tier: 'normal',
},
],
membershipProducts: [
{
productId: 'member_month',
title: '月卡',
priceCents: 2800,
kind: 'membership',
pointsAmount: 0,
bonusPoints: 0,
durationDays: 30,
badgeLabel: '',
description: '30天会员',
tier: 'month',
},
],
benefits: [
{
benefitName: '免陶泥币回合数',
normalValue: '30',
monthValue: '100',
seasonValue: '100',
yearValue: '100',
},
],
latestOrder: null,
hasPointsRecharged: false,
})),
createRpgProfileRechargeOrder: vi.fn(async () => ({
order: {
orderId: 'order-1',
productId: 'points_60',
productTitle: '60陶泥币',
kind: 'points',
amountCents: 600,
status: 'paid',
paymentChannel: 'mock',
paidAt: '2026-04-25T10:00:00Z',
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
center: {
walletBalance: 120,
membership: {
status: 'normal',
tier: 'normal',
startedAt: null,
expiresAt: null,
updatedAt: null,
},
pointProducts: [],
membershipProducts: [],
benefits: [],
latestOrder: null,
hasPointsRecharged: true,
},
})),
}));
vi.mock('../ResolvedAssetImage', () => ({
ResolvedAssetImage: ({
src,
alt,
className,
...rest
}: {
src?: string | null;
alt?: string;
className?: string;
}) =>
src ? (
) : null,
}));
const originalMatchMedia = window.matchMedia;
const originalRequestAnimationFrame = window.requestAnimationFrame;
const originalCancelAnimationFrame = window.cancelAnimationFrame;
const puzzlePublicEntry = {
sourceType: 'puzzle',
workId: 'puzzle-work-public-1',
profileId: 'puzzle-profile-public-1',
publicWorkCode: 'PZ-EPUBLIC1',
ownerUserId: 'user-2',
authorDisplayName: '拼图玩家',
worldName: '奇幻拼图',
subtitle: '拼图关卡',
summaryText: '一张用于公开分享的拼图作品。',
coverImageSrc: null,
themeTags: ['奇幻'],
playCount: 20,
remixCount: 5,
likeCount: 12,
visibility: 'published',
publishedAt: '1777110165.990127Z',
updatedAt: '2026-04-25T10:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
const remixRankEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-remix-rank',
profileId: 'puzzle-profile-remix-rank',
publicWorkCode: 'PZ-REMIX1',
worldName: '改造高分拼图',
playCount: 2,
remixCount: 18,
likeCount: 1,
recentPlayCount7d: 0,
publishedAt: '2026-04-25T11:00:00.000Z',
updatedAt: '2026-04-25T11:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
function buildCarouselPuzzleEntry(
id: string,
worldName: string,
coverPrefix: string,
) {
return {
...puzzlePublicEntry,
workId: `puzzle-work-${id}`,
profileId: `puzzle-profile-${id}`,
publicWorkCode: `PZ-${id.toUpperCase()}`,
worldName,
coverImageSrc: `${coverPrefix}-fallback.png`,
coverSlides: [
{
id: `${id}-cover-1`,
imageSrc: `${coverPrefix}-1.png`,
label: `${worldName} 1`,
},
{
id: `${id}-cover-2`,
imageSrc: `${coverPrefix}-2.png`,
label: `${worldName} 2`,
},
],
} satisfies PlatformPublicGalleryCard;
}
const hotRankEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-hot-rank',
profileId: 'puzzle-profile-hot-rank',
publicWorkCode: 'PZ-HOT001',
worldName: '热门高分拼图',
themeTags: ['奇幻', '机关'],
playCount: 40,
remixCount: 1,
likeCount: 4,
recentPlayCount7d: 0,
publishedAt: '2026-04-24T10:00:00.000Z',
updatedAt: '2026-04-24T10:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
const newRankEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-new-rank',
profileId: 'puzzle-profile-new-rank',
publicWorkCode: 'PZ-NEW001',
worldName: '新品增长拼图',
playCount: 1,
remixCount: 0,
likeCount: 0,
recentPlayCount7d: 9,
publishedAt: '2026-04-20T10:00:00.000Z',
updatedAt: '2026-04-20T10:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
const longTextRankEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-long-text-rank',
profileId: 'puzzle-profile-long-text-rank',
publicWorkCode: 'PZ-LONG01',
worldName: '关键词逍遥游拼图关卡',
themeTags: ['逍遥游拼图', '古风机关'],
playCount: 88,
remixCount: 0,
likeCount: 0,
recentPlayCount7d: 0,
publishedAt: '2026-04-29T10:00:00.000Z',
updatedAt: '2026-04-29T10:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
function mockDesktopLayout() {
Object.defineProperty(window, 'matchMedia', {
configurable: true,
writable: true,
value: vi.fn().mockImplementation(() => ({
matches: true,
media: '(min-width: 1024px)',
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
}
function renderProfileView(
onRechargeSuccess = vi.fn(),
profileDashboardOverrides: Partial<
NonNullable
> = {},
userOverrides: Partial = {},
) {
return render(
action(),
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,
}}
>
,
);
}
function renderLoggedOutHomeView(
openLoginModal = vi.fn(),
overrides: Partial<
Pick<
RpgEntryHomeViewProps,
| 'featuredEntries'
| 'latestEntries'
| 'onOpenGalleryDetail'
| 'onSearchPublicCode'
>
> = {},
) {
return render(
undefined),
musicVolume: 0.42,
setMusicVolume: vi.fn(),
platformTheme: 'light',
setPlatformTheme: vi.fn(),
isHydratingSettings: false,
isPersistingSettings: false,
settingsError: null,
}}
>
,
);
}
function renderStatefulLoggedOutHomeView(
overrides: Partial<
Pick
> = {},
) {
function StatefulLoggedOutHomeView() {
const [activeTab, setActiveTab] =
useState('home');
return (
undefined),
musicVolume: 0.42,
setMusicVolume: vi.fn(),
platformTheme: 'light',
setPlatformTheme: vi.fn(),
isHydratingSettings: false,
isPersistingSettings: false,
settingsError: null,
}}
>
);
}
return render();
}
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
Object.defineProperty(window, 'matchMedia', {
configurable: true,
writable: true,
value: originalMatchMedia,
});
Object.defineProperty(window, 'requestAnimationFrame', {
configurable: true,
writable: true,
value: originalRequestAnimationFrame,
});
Object.defineProperty(window, 'cancelAnimationFrame', {
configurable: true,
writable: true,
value: originalCancelAnimationFrame,
});
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
value: undefined,
});
});
test('opens wallet ledger modal from narrative coin card', async () => {
const user = userEvent.setup();
renderProfileView();
await user.click(screen.getByRole('button', { name: /陶泥币\s*0/u }));
expect(await screen.findByText('陶泥币账单')).toBeTruthy();
expect(mockGetRpgProfileWalletLedger).toHaveBeenCalledTimes(1);
expect(screen.getByText('资产操作消耗')).toBeTruthy();
expect(screen.getByText('-1')).toBeTruthy();
expect(screen.getByText('填写邀请码奖励')).toBeTruthy();
expect(screen.getByText('+30')).toBeTruthy();
});
test('profile total play time card always uses hours', () => {
renderProfileView(vi.fn(), {
totalPlayTimeMs: 90 * 60 * 1000,
});
const playTimeCard = screen.getByRole('button', {
name: /游戏时长/u,
});
expect(within(playTimeCard).getByText('1.5小时')).toBeTruthy();
expect(within(playTimeCard).queryByText('90分')).toBeNull();
});
test('profile played works card shows count unit', () => {
renderProfileView(vi.fn(), {
playedWorldCount: 1,
});
const playedCard = screen.getByRole('button', {
name: /玩过\s*1个/u,
});
expect(within(playedCard).getByText('1个')).toBeTruthy();
});
test('desktop account entry uses saved avatar image when available', () => {
mockDesktopLayout();
const avatarUrl = 'data:image/png;base64,AAAA';
renderProfileView(vi.fn(), {}, { avatarUrl });
const accountEntry = screen.getByRole('button', { name: /测试玩家/u });
const avatarImage = accountEntry.querySelector('img');
expect(avatarImage?.getAttribute('src')).toBe(avatarUrl);
expect(within(accountEntry).queryByText('测')).toBeNull();
});
test('wallet ledger modal shows empty and error states', async () => {
const user = userEvent.setup();
mockGetRpgProfileWalletLedger.mockResolvedValueOnce({ entries: [] });
renderProfileView();
await user.click(screen.getByRole('button', { name: /陶泥币\s*0/u }));
expect(await screen.findByText('暂无账单记录')).toBeTruthy();
await user.click(screen.getByLabelText('关闭陶泥币账单'));
mockGetRpgProfileWalletLedger.mockRejectedValueOnce(new Error('加载失败'));
await user.click(screen.getByRole('button', { name: /陶泥币\s*0/u }));
expect(await screen.findByText('加载失败')).toBeTruthy();
expect(screen.getByText('重新加载')).toBeTruthy();
});
test('opens reward code modal from profile action on mobile', async () => {
const user = userEvent.setup();
renderProfileView();
await user.click(screen.getByRole('button', { name: /兑换码/u }));
const modal = await screen.findByPlaceholderText('输入兑换码');
expect(modal).toBeTruthy();
expect(screen.getByRole('button', { name: '兑换' })).toBeTruthy();
expect(screen.getByLabelText('关闭兑换码')).toBeTruthy();
});
test('shows a reachable login entry in logged out mobile shell', async () => {
const user = userEvent.setup();
const openLoginModal = vi.fn();
renderLoggedOutHomeView(openLoginModal);
await user.click(screen.getByRole('button', { name: '登录' }));
expect(openLoginModal).toHaveBeenCalledTimes(1);
});
test('mobile home search submits public work code', async () => {
const user = userEvent.setup();
const onSearchPublicCode = vi.fn();
render(
undefined),
musicVolume: 0.42,
setMusicVolume: vi.fn(),
platformTheme: 'light',
setPlatformTheme: vi.fn(),
isHydratingSettings: false,
isPersistingSettings: false,
settingsError: null,
}}
>
,
);
const searchInput = screen.getByPlaceholderText(
'输入 SY / CW / BF / M3 / PZ 编号',
);
await user.type(searchInput, 'PZ-PROFILE1{enter}');
expect(onSearchPublicCode).toHaveBeenCalledWith('PZ-PROFILE1');
});
test('public gallery cards hide work code until detail is opened', async () => {
const user = userEvent.setup();
const onOpenGalleryDetail = vi.fn();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
onOpenGalleryDetail,
});
expect(screen.queryByText('PZ-EPUBLIC1')).toBeNull();
expect(
screen.queryByRole('button', { name: '复制作品号 PZ-EPUBLIC1' }),
).toBeNull();
await user.click(screen.getByRole('button', { name: /奇幻拼图/u }));
expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry);
});
test('mobile public work cards render cover, author, kind and cover stats', () => {
const { container } = renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
});
const card = screen.getByRole('button', {
name: /奇幻拼图,拼图,20游玩,5改造,12点赞/u,
});
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();
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('推荐');
});
test('mobile home feed only rotates the card closest to screen center', () => {
vi.useFakeTimers();
Object.defineProperty(window, 'requestAnimationFrame', {
configurable: true,
writable: true,
value: (callback: FrameRequestCallback) =>
window.setTimeout(() => callback(0), 0),
});
Object.defineProperty(window, 'cancelAnimationFrame', {
configurable: true,
writable: true,
value: (handle: number) => window.clearTimeout(handle),
});
const firstEntry = buildCarouselPuzzleEntry('center1', '中心拼图一', 'center-one');
const secondEntry = buildCarouselPuzzleEntry(
'center2',
'中心拼图二',
'center-two',
);
const cardRects = new Map();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [firstEntry, secondEntry],
});
const tabPanel = document.querySelector('.platform-tab-panel--active');
const firstCard = screen.getByRole('button', { name: /中心拼图一/u });
const secondCard = screen.getByRole('button', { name: /中心拼图二/u });
if (!tabPanel) {
throw new Error('缺少移动端首页滚动面板');
}
tabPanel.getBoundingClientRect = vi.fn(
() =>
({
top: 0,
bottom: 600,
height: 600,
left: 0,
right: 360,
width: 360,
}) as DOMRect,
);
firstCard.getBoundingClientRect = vi.fn(() => cardRects.get('first')!);
secondCard.getBoundingClientRect = vi.fn(() => cardRects.get('second')!);
cardRects.set('first', {
top: 170,
bottom: 370,
height: 200,
left: 0,
right: 320,
width: 320,
} as DOMRect);
cardRects.set('second', {
top: 420,
bottom: 620,
height: 200,
left: 0,
right: 320,
width: 320,
} as DOMRect);
act(() => {
vi.runOnlyPendingTimers();
});
expect(within(firstCard).getByRole('img').getAttribute('src')).toBe(
'center-one-1.png',
);
expect(within(secondCard).getByRole('img').getAttribute('src')).toBe(
'center-two-1.png',
);
act(() => {
vi.advanceTimersByTime(4200);
});
expect(within(firstCard).getByRole('img').getAttribute('src')).toBe(
'center-one-2.png',
);
expect(within(secondCard).getByRole('img').getAttribute('src')).toBe(
'center-two-1.png',
);
cardRects.set('first', {
top: -120,
bottom: 80,
height: 200,
left: 0,
right: 320,
width: 320,
} as DOMRect);
cardRects.set('second', {
top: 200,
bottom: 400,
height: 200,
left: 0,
right: 320,
width: 320,
} as DOMRect);
act(() => {
tabPanel.dispatchEvent(new Event('scroll'));
vi.runOnlyPendingTimers();
});
expect(within(firstCard).getByRole('img').getAttribute('src')).toBe(
'center-one-1.png',
);
act(() => {
vi.advanceTimersByTime(4200);
});
expect(within(secondCard).getByRole('img').getAttribute('src')).toBe(
'center-two-2.png',
);
});
test('mobile today channel only shows newly published works from today', async () => {
const user = userEvent.setup();
const now = new Date();
const todayPublishedAt = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
10,
).toISOString();
const yesterdayPublishedAt = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate() - 1,
10,
).toISOString();
const todayEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-today',
profileId: 'puzzle-profile-today',
publicWorkCode: 'PZ-TODAY1',
worldName: '今日新游',
publishedAt: todayPublishedAt,
updatedAt: todayPublishedAt,
} satisfies PlatformPublicGalleryCard;
const yesterdayEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-yesterday',
profileId: 'puzzle-profile-yesterday',
publicWorkCode: 'PZ-YDAY01',
worldName: '昨日旧作',
publishedAt: yesterdayPublishedAt,
updatedAt: yesterdayPublishedAt,
} satisfies PlatformPublicGalleryCard;
const updatedTodayEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-updated-today',
profileId: 'puzzle-profile-updated-today',
publicWorkCode: 'PZ-UPDAY1',
worldName: '今日更新旧作',
publishedAt: yesterdayPublishedAt,
updatedAt: todayPublishedAt,
} satisfies PlatformPublicGalleryCard;
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [yesterdayEntry, updatedTodayEntry, todayEntry],
});
await user.click(screen.getByRole('button', { name: '今日游戏' }));
expect(screen.getByRole('button', { name: /今日新游/u })).toBeTruthy();
expect(screen.queryByText('昨日旧作')).toBeNull();
expect(screen.queryByText('今日更新旧作')).toBeNull();
});
test('desktop home syncs mobile home modules without square or latest labels', () => {
mockDesktopLayout();
const todayPublishedAt = new Date().toISOString();
const todayEntry = {
...puzzlePublicEntry,
workId: 'puzzle-work-desktop-today',
profileId: 'puzzle-profile-desktop-today',
publicWorkCode: 'PZ-DTODAY',
worldName: '桌面今日新游',
publishedAt: todayPublishedAt,
updatedAt: todayPublishedAt,
} satisfies PlatformPublicGalleryCard;
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry, todayEntry],
});
expect(screen.getByText('今日游戏')).toBeTruthy();
expect(screen.getByText('推荐')).toBeTruthy();
expect(screen.getByText('作品分类')).toBeTruthy();
expect(screen.getAllByText('桌面今日新游').length).toBeGreaterThan(0);
expect(screen.queryByText('趋势关注')).toBeNull();
expect(screen.queryByText('最新发布')).toBeNull();
expect(screen.queryByText('作品广场')).toBeNull();
expect(screen.queryByText('公开作品')).toBeNull();
expect(screen.queryByText('PZ-EPUBLIC1')).toBeNull();
expect(screen.getAllByText('拼图').length).toBeGreaterThan(0);
expect(screen.queryByText('1777110165.990127Z')).toBeNull();
});
test('mobile home moves category shelf into game category channel', async () => {
const user = userEvent.setup();
const { container } = renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
});
expect(screen.queryByRole('button', { name: 'PC游戏' })).toBeNull();
expect(screen.queryByRole('button', { name: '即点即玩' })).toBeNull();
await user.click(screen.getByRole('button', { name: '游戏分类' }));
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();
expect(container.querySelector('.platform-category-game-list')).toBeTruthy();
expect(container.querySelector('.platform-category-game-item')).toBeTruthy();
expect(
container.querySelector('.platform-category-game-item__action')
?.textContent,
).toBe('试玩');
});
test('mobile game category list orders works by composite public metric', async () => {
const user = userEvent.setup();
renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry, hotRankEntry],
});
await user.click(screen.getByRole('button', { name: '游戏分类' }));
await user.click(screen.getByRole('button', { name: '奇幻' }));
const gameItems = Array.from(
document.querySelectorAll('.platform-category-game-item__title'),
).map((element) => element.textContent);
expect(gameItems).toEqual(['热门高分拼图', '奇幻拼图']);
});
test('bottom category tab becomes ranking and switches ranking metrics', async () => {
const user = userEvent.setup();
renderStatefulLoggedOutHomeView({
latestEntries: [remixRankEntry, hotRankEntry, newRankEntry],
});
expect(screen.queryByRole('button', { name: '分类' })).toBeNull();
await user.click(screen.getByRole('button', { name: '排行' }));
expect(await screen.findByRole('tab', { name: '热门榜' })).toBeTruthy();
expect(screen.getByRole('tab', { name: '改造榜' })).toBeTruthy();
expect(screen.getByRole('tab', { name: '新品榜' })).toBeTruthy();
expect(screen.getByRole('tab', { name: '点赞榜' })).toBeTruthy();
const rankingPanel = document.getElementById('platform-tab-panel-category');
expect(rankingPanel?.getAttribute('aria-hidden')).toBe('false');
expect(within(rankingPanel!).getByText('热门高分拼图')).toBeTruthy();
expect(within(rankingPanel!).getByText('40')).toBeTruthy();
expect(within(rankingPanel!).getAllByText('游玩').length).toBeGreaterThan(0);
await user.click(screen.getByRole('tab', { name: '改造榜' }));
expect(within(rankingPanel!).getByText('改造高分拼图')).toBeTruthy();
expect(within(rankingPanel!).getByText('18')).toBeTruthy();
expect(within(rankingPanel!).getAllByText('改造').length).toBeGreaterThan(0);
await user.click(screen.getByRole('tab', { name: '新品榜' }));
expect(within(rankingPanel!).getByText('新品增长拼图')).toBeTruthy();
expect(within(rankingPanel!).getByText('9')).toBeTruthy();
expect(within(rankingPanel!).getAllByText('近7日').length).toBeGreaterThan(0);
});
test('ranking rows limit displayed work name and show two short tags on the third line', async () => {
const user = userEvent.setup();
renderStatefulLoggedOutHomeView({
latestEntries: [longTextRankEntry],
});
await user.click(screen.getByRole('button', { name: '排行' }));
const rankingPanel = document.getElementById('platform-tab-panel-category');
expect(rankingPanel).toBeTruthy();
expect(within(rankingPanel!).getByText('关键词逍遥游拼图')).toBeTruthy();
expect(within(rankingPanel!).queryByText('关键词逍遥游拼图关卡')).toBeNull();
expect(within(rankingPanel!).getByText('逍遥游拼')).toBeTruthy();
expect(within(rankingPanel!).getByText('古风机关')).toBeTruthy();
expect(within(rankingPanel!).queryByText(/2026-04-29/u)).toBeNull();
expect(within(rankingPanel!).queryByText('拼图玩家')).toBeNull();
});