feat: 支持创作入口公告配置

This commit is contained in:
2026-06-03 03:31:45 +08:00
parent 1cb11bc1dd
commit 70ff18ad90
52 changed files with 3045 additions and 504 deletions

View File

@@ -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('&lt;section&gt;&lt;h1&gt;自定义横幅&lt;/h1&gt;&lt;/section&gt;');
});
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