feat: 支持创作入口公告配置
This commit is contained in:
@@ -3,6 +3,7 @@ import { expect, test } from 'vitest';
|
||||
|
||||
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
|
||||
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
|
||||
import { buildCreationWorkShelfItems } from './creationWorkShelf';
|
||||
import { CustomWorldCreationHub } from './CustomWorldCreationHub';
|
||||
|
||||
const noopCreateType = () => {};
|
||||
@@ -26,6 +27,26 @@ const testEntryConfig = {
|
||||
startsAtText: '2026-05-01',
|
||||
endsAtText: '2026-05-31',
|
||||
},
|
||||
eventBanners: [
|
||||
{
|
||||
title: '后台拼图赛',
|
||||
description: '后台配置的拼图横幅。',
|
||||
coverImageSrc: '/creation-type-references/puzzle.webp',
|
||||
prizePoolMudPoints: 1000,
|
||||
startsAtText: '2026-05-01',
|
||||
endsAtText: '2026-05-31',
|
||||
renderMode: 'structured',
|
||||
},
|
||||
{
|
||||
title: '后台抓大鹅赛',
|
||||
description: '后台配置的抓大鹅横幅。',
|
||||
coverImageSrc: '/creation-type-references/match3d.webp',
|
||||
prizePoolMudPoints: 1200,
|
||||
startsAtText: '2026-06-01',
|
||||
endsAtText: '2026-06-30',
|
||||
renderMode: 'structured',
|
||||
},
|
||||
],
|
||||
creationTypes: [
|
||||
{
|
||||
id: 'rpg',
|
||||
@@ -36,9 +57,9 @@ const testEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 10,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -50,9 +71,9 @@ const testEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 30,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -64,9 +85,9 @@ const testEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 40,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -78,9 +99,9 @@ const testEntryConfig = {
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 50,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -92,9 +113,9 @@ const testEntryConfig = {
|
||||
visible: false,
|
||||
open: false,
|
||||
sortOrder: 60,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -106,9 +127,9 @@ const testEntryConfig = {
|
||||
visible: true,
|
||||
open: false,
|
||||
sortOrder: 70,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
],
|
||||
@@ -186,11 +207,15 @@ test('creation start card renders reference-aligned banner and template metadata
|
||||
expect(html).toContain('creation-event-banner__track');
|
||||
expect(html).toContain('creation-event-banner__slide');
|
||||
expect(html).toContain('creation-event-banner__timebar');
|
||||
expect(html).toContain('拼图主题创作赛');
|
||||
expect(html).toContain('抓大鹅主题创作赛');
|
||||
expect(html).toContain('后台拼图赛');
|
||||
expect(html).toContain('后台抓大鹅赛');
|
||||
expect(html).toContain('1,000');
|
||||
expect(html).toContain('1,200');
|
||||
expect(html).toContain('泥点数');
|
||||
expect(html).not.toContain('泥点挑战');
|
||||
expect(html).not.toContain('拼图主题创作赛');
|
||||
expect(html).not.toContain('抓大鹅主题创作赛');
|
||||
expect(html).not.toContain('最近创作');
|
||||
expect(html).toMatch(
|
||||
/creation-event-banner__timebar[\s\S]*creation-event-banner__pager[\s\S]*creation-template-card/u,
|
||||
);
|
||||
@@ -206,6 +231,305 @@ test('creation start card renders reference-aligned banner and template metadata
|
||||
expect(html).not.toContain('platform-creation-reference-card');
|
||||
});
|
||||
|
||||
test('creation start card falls back to legacy single banner when eventBanners is empty', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={{
|
||||
...testEntryConfig,
|
||||
eventBanners: [],
|
||||
}}
|
||||
creationTypes={testCreationTypes}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('泥点挑战');
|
||||
expect(html).not.toContain('后台拼图赛');
|
||||
});
|
||||
|
||||
test('creation start card renders html banner in an empty-permission sandbox', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={{
|
||||
...testEntryConfig,
|
||||
eventBanners: [
|
||||
{
|
||||
...testEntryConfig.eventBanner,
|
||||
title: 'HTML 后台横幅',
|
||||
renderMode: 'html',
|
||||
htmlCode: '<section><h1>自定义横幅</h1></section>',
|
||||
},
|
||||
],
|
||||
}}
|
||||
creationTypes={testCreationTypes}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('title="HTML 后台横幅"');
|
||||
expect(html).toContain('sandbox=""');
|
||||
expect(html).toContain('<section><h1>自定义横幅</h1></section>');
|
||||
});
|
||||
|
||||
test('creation start card renders recent tab from real shelf summaries', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[
|
||||
{
|
||||
workId: 'draft:recent-session',
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '后端返回的最近草稿',
|
||||
subtitle: '待完善草稿',
|
||||
summary: '这条内容来自作品架摘要。',
|
||||
coverImageSrc: null,
|
||||
updatedAt: new Date('2026-06-01T12:00:00.000Z').toISOString(),
|
||||
publishedAt: null,
|
||||
stage: 'clarifying',
|
||||
stageLabel: '待完善草稿',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
sessionId: 'recent-session',
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
},
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
getWorkState={() => ({ isGenerating: true })}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('aria-label="创作入口页签"');
|
||||
expect(html).toContain('role="tab"');
|
||||
expect(html).toContain('aria-selected="true"');
|
||||
expect(html).toContain('creation-recent-work-grid');
|
||||
expect(html).toContain('aria-label="打开最近创作 1"');
|
||||
expect(html).toContain('最近创作');
|
||||
expect(html).toContain('后端返回的最近草稿');
|
||||
expect(html).toContain('这条内容来自作品架摘要');
|
||||
expect(html).toContain('生成中');
|
||||
});
|
||||
|
||||
test('creation start card prefers backend recent summaries over local pending placeholders', () => {
|
||||
const recentWorkItems = buildCreationWorkShelfItems({
|
||||
rpgItems: [
|
||||
{
|
||||
workId: 'draft:backend-session',
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '后端最近草稿',
|
||||
subtitle: '真实作品架摘要',
|
||||
summary: '最近创作应该只读取后端摘要。',
|
||||
coverImageSrc: null,
|
||||
updatedAt: new Date('2026-06-03T12:00:00.000Z').toISOString(),
|
||||
publishedAt: null,
|
||||
stage: 'failed',
|
||||
stageLabel: '生成失败',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
sessionId: 'backend-session',
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
},
|
||||
],
|
||||
bigFishItems: [],
|
||||
puzzleItems: [],
|
||||
});
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[
|
||||
{
|
||||
workId: 'draft:local-pending-session',
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '本地生成中占位',
|
||||
subtitle: '本地占位',
|
||||
summary: '这条占位不应该进入最近创作。',
|
||||
coverImageSrc: null,
|
||||
updatedAt: new Date('2026-06-04T12:00:00.000Z').toISOString(),
|
||||
publishedAt: null,
|
||||
stage: 'generating',
|
||||
stageLabel: '生成中',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
sessionId: 'local-pending-session',
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
},
|
||||
]}
|
||||
recentWorkItems={recentWorkItems}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('最近创作');
|
||||
expect(html).toContain('后端最近草稿');
|
||||
expect(html).toContain('最近创作应该只读取后端摘要');
|
||||
expect(html).not.toContain('本地生成中占位');
|
||||
});
|
||||
|
||||
test('creation start card marks backend jump-hop generating draft in recent tab', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[]}
|
||||
jumpHopItems={[
|
||||
{
|
||||
runtimeKind: 'jump-hop',
|
||||
workId: 'jump-hop-work-1',
|
||||
profileId: 'jump-hop-profile-1',
|
||||
ownerUserId: 'user-1',
|
||||
sourceSessionId: 'jump-hop-session-1',
|
||||
workTitle: '跳一跳生成草稿',
|
||||
workDescription: '后端仍在生成跳一跳玩法。',
|
||||
themeTags: ['跳一跳'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'minimal-blocks',
|
||||
coverImageSrc: null,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: new Date('2026-06-03T13:00:00.000Z').toISOString(),
|
||||
publishedAt: null,
|
||||
publishReady: false,
|
||||
generationStatus: 'generating',
|
||||
},
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
onOpenJumpHopDetail={() => {}}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('最近创作');
|
||||
expect(html).toContain('跳一跳生成草稿');
|
||||
expect(html).toContain('后端仍在生成跳一跳玩法');
|
||||
expect(html).toContain('生成中');
|
||||
});
|
||||
|
||||
test('creation start card includes failed drafts in the recent tab', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[
|
||||
{
|
||||
workId: 'draft:failed-session',
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '失败但仍可恢复的草稿',
|
||||
subtitle: '生成失败',
|
||||
summary: '失败草稿也来自真实作品架摘要。',
|
||||
coverImageSrc: null,
|
||||
updatedAt: new Date('2026-06-02T12:00:00.000Z').toISOString(),
|
||||
publishedAt: null,
|
||||
stage: 'failed',
|
||||
stageLabel: '生成失败',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
sessionId: 'failed-session',
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
},
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('最近创作');
|
||||
expect(html).toContain('creation-recent-work-grid');
|
||||
expect(html).toContain('失败但仍可恢复的草稿');
|
||||
expect(html).toContain('失败草稿也来自真实作品架摘要');
|
||||
expect(html).toContain('生成失败');
|
||||
});
|
||||
|
||||
test('creation start card maps failed mini-game drafts into recent status labels', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
items={[]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onRetry={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
entryConfig={testEntryConfig}
|
||||
creationTypes={testCreationTypes}
|
||||
match3dItems={[
|
||||
{
|
||||
workId: 'match3d-failed-work',
|
||||
profileId: 'match3d-failed-profile',
|
||||
ownerUserId: 'user-1',
|
||||
gameName: '失败抓大鹅草稿',
|
||||
themeText: '水果',
|
||||
summary: '失败的小玩法草稿也应该进入最近创作。',
|
||||
tags: [],
|
||||
coverImageSrc: null,
|
||||
clearCount: 0,
|
||||
difficulty: 1,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-06-02T13:00:00.000Z',
|
||||
publishedAt: null,
|
||||
publishReady: false,
|
||||
generationStatus: 'failed',
|
||||
},
|
||||
]}
|
||||
mode="start-only"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('最近创作');
|
||||
expect(html).toContain('失败抓大鹅草稿');
|
||||
expect(html).toContain('失败的小玩法草稿也应该进入最近创作。');
|
||||
expect(html).toContain('生成失败');
|
||||
});
|
||||
|
||||
test('creation start card keeps typography in compact UI scale', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
|
||||
Reference in New Issue
Block a user