289 lines
7.8 KiB
TypeScript
289 lines
7.8 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen } from '@testing-library/react';
|
|
import { act } from 'react';
|
|
import { afterEach, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
|
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
|
type PlatformEdutainmentGalleryCard,
|
|
type PlatformPuzzleGalleryCard,
|
|
} from '../rpg-entry/rpgEntryWorldPresentation';
|
|
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
|
|
|
vi.mock('../ResolvedAssetImage', () => ({
|
|
ResolvedAssetImage: ({
|
|
src,
|
|
alt,
|
|
className,
|
|
'aria-hidden': ariaHidden,
|
|
}: {
|
|
src?: string | null;
|
|
alt?: string;
|
|
className?: string;
|
|
'aria-hidden'?: boolean | 'true' | 'false';
|
|
}) => (
|
|
<img
|
|
src={src ?? ''}
|
|
alt={alt ?? ''}
|
|
className={className}
|
|
aria-hidden={ariaHidden}
|
|
/>
|
|
),
|
|
}));
|
|
|
|
function createPuzzleEntry(): PlatformPuzzleGalleryCard {
|
|
return {
|
|
sourceType: 'puzzle',
|
|
workId: 'work-1',
|
|
profileId: 'profile-1',
|
|
publicWorkCode: 'PZ-001',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '137****6613',
|
|
worldName: '关键词:逍遥游拼图',
|
|
subtitle: '拼图关卡',
|
|
summaryText: '适合公开游玩的拼图作品。',
|
|
coverImageSrc: null,
|
|
coverSlides: [],
|
|
themeTags: ['拼图'],
|
|
playCount: 12,
|
|
remixCount: 3,
|
|
likeCount: 4,
|
|
recentPlayCount7d: 0,
|
|
visibility: 'published',
|
|
publishedAt: '2026-04-20T10:00:00.000Z',
|
|
updatedAt: '2026-04-25T12:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
function createBabyObjectMatchEntry(): PlatformEdutainmentGalleryCard {
|
|
return {
|
|
sourceType: 'edutainment',
|
|
templateId: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
|
templateName: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
|
workId: 'baby-object-match-work-1',
|
|
profileId: 'baby-object-match-profile-1',
|
|
publicWorkCode: 'EDU-BABY01',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '陶泥儿主',
|
|
worldName: '宝贝识物水果篮',
|
|
subtitle: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
|
summaryText: '将物品放入对应的篮子里。',
|
|
coverImageSrc: null,
|
|
themeTags: ['寓教于乐'],
|
|
playCount: 12,
|
|
remixCount: 0,
|
|
likeCount: 4,
|
|
recentPlayCount7d: 0,
|
|
visibility: 'published',
|
|
publishedAt: '2026-05-11T10:00:00.000Z',
|
|
updatedAt: '2026-05-11T12:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
test('PlatformWorkDetailView renders compact stats and date time', () => {
|
|
render(
|
|
<PlatformWorkDetailView
|
|
entry={createPuzzleEntry()}
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('改造')).toBeTruthy();
|
|
expect(screen.getByText('游玩')).toBeTruthy();
|
|
expect(screen.getAllByText('点赞').length).toBeGreaterThanOrEqual(2);
|
|
expect(screen.getByText('日期')).toBeTruthy();
|
|
expect(screen.queryByText('改造次数')).toBeNull();
|
|
expect(screen.queryByText('游玩次数')).toBeNull();
|
|
expect(screen.queryByText('上线日期')).toBeNull();
|
|
expect(screen.queryByText('最近更新')).toBeNull();
|
|
expect(screen.getByText('2026-04-25')).toBeTruthy();
|
|
expect(screen.getAllByText('次')).toHaveLength(2);
|
|
expect(screen.getByText('赞')).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '点赞 4赞' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '作品改造' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '启动' })).toBeTruthy();
|
|
});
|
|
|
|
test('PlatformWorkDetailView prefers resolved public user display name', () => {
|
|
render(
|
|
<PlatformWorkDetailView
|
|
entry={createPuzzleEntry()}
|
|
authorDisplayName="新的作者昵称"
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('新的作者昵称')).toBeTruthy();
|
|
expect(screen.queryByText('137****6613')).toBeNull();
|
|
});
|
|
|
|
test('PlatformWorkDetailView calls like handler', () => {
|
|
const onLike = vi.fn();
|
|
render(
|
|
<PlatformWorkDetailView
|
|
entry={createPuzzleEntry()}
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={onLike}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '点赞 4赞' }));
|
|
|
|
expect(onLike).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('PlatformWorkDetailView switches remix action label for owned work edit', () => {
|
|
render(
|
|
<PlatformWorkDetailView
|
|
entry={createPuzzleEntry()}
|
|
actionMode="edit"
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('button', { name: '作品编辑' })).toBeTruthy();
|
|
expect(screen.queryByRole('button', { name: '作品改造' })).toBeNull();
|
|
});
|
|
|
|
test('PlatformWorkDetailView labels baby object match works', () => {
|
|
render(
|
|
<PlatformWorkDetailView
|
|
entry={createBabyObjectMatchEntry()}
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('宝贝识物')).toBeTruthy();
|
|
expect(screen.getByText('EDU-BABY01')).toBeTruthy();
|
|
});
|
|
|
|
test('PlatformWorkDetailView cycles puzzle level cover slides', () => {
|
|
vi.useFakeTimers();
|
|
const { container } = render(
|
|
<PlatformWorkDetailView
|
|
entry={{
|
|
...createPuzzleEntry(),
|
|
coverImageSrc: '/fallback-cover.png',
|
|
coverSlides: [
|
|
{
|
|
id: 'level-1',
|
|
imageSrc: '/level-1.png',
|
|
label: '第一关',
|
|
},
|
|
{
|
|
id: 'level-2',
|
|
imageSrc: '/level-2.png',
|
|
label: '第二关',
|
|
},
|
|
],
|
|
}}
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getAllByAltText('关键词:逍遥游拼图')[0]?.getAttribute('src')).toBe(
|
|
'/level-1.png',
|
|
);
|
|
const appIconImage = container.querySelector(
|
|
'.platform-work-detail__app-icon img',
|
|
);
|
|
expect(appIconImage?.getAttribute('src')).toBe('/level-1.png');
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '下一张关卡图' }));
|
|
|
|
expect(screen.getAllByAltText('关键词:逍遥游拼图')[0]?.getAttribute('src')).toBe(
|
|
'/level-2.png',
|
|
);
|
|
expect(appIconImage?.getAttribute('src')).toBe('/level-1.png');
|
|
expect(
|
|
container.querySelector('.platform-work-detail__cover-image--locked'),
|
|
).toBeTruthy();
|
|
expect(
|
|
container.querySelector('.platform-work-detail__cover-lock-icon'),
|
|
).toBeTruthy();
|
|
|
|
act(() => {
|
|
vi.advanceTimersByTime(4200);
|
|
});
|
|
|
|
expect(screen.getAllByAltText('关键词:逍遥游拼图')[0]?.getAttribute('src')).toBe(
|
|
'/level-1.png',
|
|
);
|
|
});
|
|
|
|
test('PlatformWorkDetailView unlocks later puzzle covers by visible cover count', () => {
|
|
const { container } = render(
|
|
<PlatformWorkDetailView
|
|
entry={{
|
|
...createPuzzleEntry(),
|
|
coverSlides: [
|
|
{
|
|
id: 'level-1',
|
|
imageSrc: '/level-1.png',
|
|
label: '第一关',
|
|
},
|
|
{
|
|
id: 'level-2',
|
|
imageSrc: '/level-2.png',
|
|
label: '第二关',
|
|
},
|
|
],
|
|
}}
|
|
visibleCoverCount={2}
|
|
isBusy={false}
|
|
error={null}
|
|
onBack={vi.fn()}
|
|
onLike={vi.fn()}
|
|
onStart={vi.fn()}
|
|
onRemix={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '下一张关卡图' }));
|
|
|
|
expect(screen.getAllByAltText('关键词:逍遥游拼图')[0]?.getAttribute('src')).toBe(
|
|
'/level-2.png',
|
|
);
|
|
expect(
|
|
container.querySelector('.platform-work-detail__cover-image--locked'),
|
|
).toBeNull();
|
|
expect(
|
|
container.querySelector('.platform-work-detail__cover-lock-icon'),
|
|
).toBeNull();
|
|
});
|