Files
Genarrative/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx
T

237 lines
7.0 KiB
TypeScript

/* @vitest-environment jsdom */
import {fireEvent, render, screen, waitFor} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {beforeEach, expect, test, vi} from 'vitest';
import {
getAdminCreationEntryConfig,
upsertAdminCreationEntryBanners,
upsertAdminCreationEntryConfig,
} from '../api/adminApiClient';
import type {
AdminCreationEntryConfigResponse,
UnifiedCreationSpecPayload,
} from '../api/adminApiTypes';
import {AdminCreationEntrySwitchPage} from './AdminCreationEntrySwitchPage';
vi.mock('../api/adminApiClient', () => ({
formatAdminApiError: vi.fn((error: unknown) =>
error instanceof Error ? error.message : '请求失败',
),
getAdminCreationEntryConfig: vi.fn(),
isAdminApiError: vi.fn(() => false),
upsertAdminCreationEntryBanners: vi.fn(),
upsertAdminCreationEntryConfig: vi.fn(),
}));
const puzzleSpec: UnifiedCreationSpecPayload = {
playId: 'puzzle',
title: '想做个什么玩法?',
workspaceStage: 'puzzle-agent-workspace',
generationStage: 'puzzle-generating',
resultStage: 'puzzle-result',
fields: [
{
id: 'pictureDescription',
kind: 'text',
label: '画面描述',
required: true,
},
],
};
const configResponse: AdminCreationEntryConfigResponse = {
eventBanners: [
{
title: '创作公告',
description: '',
coverImageSrc: '',
prizePoolMudPoints: 0,
startsAtText: '',
endsAtText: '',
renderMode: 'html',
htmlCode: '<section>后台公告</section>',
},
],
entries: [
{
id: 'puzzle',
title: '拼图',
subtitle: '拼图关卡创作',
badge: '可创建',
imageSrc: '/creation-type-references/puzzle.webp',
visible: true,
open: true,
sortOrder: 30,
categoryId: 'recommended',
categoryLabel: '热门推荐',
categorySortOrder: 20,
updatedAtMicros: 1,
unifiedCreationSpec: puzzleSpec,
},
],
};
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getAdminCreationEntryConfig).mockResolvedValue(configResponse);
vi.mocked(upsertAdminCreationEntryBanners).mockResolvedValue(configResponse);
vi.mocked(upsertAdminCreationEntryConfig).mockResolvedValue(configResponse);
});
test('创作入口后台展示并保存统一创作契约', async () => {
const user = userEvent.setup();
const {container} = render(
<AdminCreationEntrySwitchPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await screen.findByText('pictureDescription');
expect(container.querySelector('.admin-subsection .admin-info-list')).not.toBeNull();
expect(container.querySelector('.admin-panel .admin-panel')).toBeNull();
expect(container.querySelector('.admin-muted')).toBeNull();
await user.click(screen.getByRole('button', {name: '保存入库'}));
await user.click(screen.getByRole('button', {name: '确认'}));
await waitFor(() => {
expect(upsertAdminCreationEntryConfig).toHaveBeenCalledWith(
'admin-token',
expect.objectContaining({
id: 'puzzle',
unifiedCreationSpec: puzzleSpec,
}),
);
});
});
test('创作入口后台拒绝 playId 不一致的统一创作契约', async () => {
const user = userEvent.setup();
render(
<AdminCreationEntrySwitchPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
const textarea = await screen.findByLabelText('契约 JSON');
fireEvent.change(textarea, {
target: {
value: JSON.stringify({
...puzzleSpec,
playId: 'match3d',
}),
},
});
await user.click(screen.getByRole('button', {name: '保存入库'}));
expect(await screen.findByText('统一创作契约 playId 必须与入口 ID 一致')).toBeTruthy();
expect(upsertAdminCreationEntryConfig).not.toHaveBeenCalled();
});
test('创作入口后台用表单保存公告配置', async () => {
const user = userEvent.setup();
render(
<AdminCreationEntrySwitchPage
mode="announcements"
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
expect(await screen.findAllByRole('heading', {name: '创作入口公告'})).toHaveLength(2);
expect(screen.queryByLabelText('公告代码 JSON')).toBeNull();
fireEvent.change(await screen.findByLabelText('公告 1 标题'), {
target: {value: '周末创作赛'},
});
fireEvent.change(screen.getByLabelText('公告 1 HTML'), {
target: {value: '<section>新的入口公告</section>'},
});
await user.click(screen.getByRole('button', {name: '新增公告'}));
fireEvent.change(screen.getByLabelText('公告 2 标题'), {
target: {value: '第二条公告'},
});
fireEvent.change(screen.getByLabelText('公告 2 HTML'), {
target: {value: '<section>轮播第二条</section>'},
});
await user.click(screen.getByRole('button', {name: '保存公告'}));
await user.click(screen.getByRole('button', {name: '确认'}));
await waitFor(() => {
expect(upsertAdminCreationEntryBanners).toHaveBeenCalled();
});
const [, payload] = vi.mocked(upsertAdminCreationEntryBanners).mock.calls[0]!;
expect(JSON.parse(payload.eventBannersJson)).toEqual([
{
title: '周末创作赛',
htmlCode: '<section>新的入口公告</section>',
},
{
title: '第二条公告',
htmlCode: '<section>轮播第二条</section>',
},
]);
expect(JSON.parse(payload.eventBannersJson)[0]).not.toHaveProperty(
'description',
);
expect(JSON.parse(payload.eventBannersJson)[0]).not.toHaveProperty(
'coverImageSrc',
);
});
test('创作入口后台把旧结构化公告回显成 HTML 表单', async () => {
vi.mocked(getAdminCreationEntryConfig).mockResolvedValueOnce({
...configResponse,
eventBanners: [
{
title: '旧公告 <标题>',
description: '旧描述 & 需要转义',
coverImageSrc: '/legacy.png',
prizePoolMudPoints: 120,
startsAtText: '2026-06-01',
endsAtText: '2026-06-30',
renderMode: 'structured',
},
],
});
render(
<AdminCreationEntrySwitchPage
mode="announcements"
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
expect(await screen.findByLabelText('公告 1 标题')).toHaveProperty(
'value',
'旧公告 <标题>',
);
expect(screen.getByLabelText('公告 1 HTML')).toHaveProperty(
'value',
'<section><h1>旧公告 &lt;标题&gt;</h1><p>旧描述 &amp; 需要转义</p></section>',
);
});
test('创作入口后台拒绝空公告表单', async () => {
const user = userEvent.setup();
render(
<AdminCreationEntrySwitchPage
mode="announcements"
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
fireEvent.change(await screen.findByLabelText('公告 1 标题'), {
target: {value: ''},
});
fireEvent.change(screen.getByLabelText('公告 1 HTML'), {
target: {value: ''},
});
await user.click(screen.getByRole('button', {name: '保存公告'}));
expect(await screen.findByText('公告 1 标题和 HTML 都不能为空')).toBeTruthy();
expect(upsertAdminCreationEntryBanners).not.toHaveBeenCalled();
});