feat: add edutainment drawing and visual package flows

This commit is contained in:
2026-05-14 14:17:10 +08:00
parent 10e8beea80
commit e444266e1e
109 changed files with 8788 additions and 996 deletions

View File

@@ -142,6 +142,8 @@ import {
listSquareHoleGallery,
listSquareHoleWorks,
} from '../../services/square-hole-works';
import { listVisualNovelGallery } from '../../services/visual-novel-runtime';
import { listVisualNovelWorks } from '../../services/visual-novel-works';
import { type CustomWorldProfile, WorldType } from '../../types';
import {
AuthUiContext,
@@ -318,6 +320,17 @@ const testCreationEntryConfig = {
sortOrder: 80,
updatedAtMicros: 1,
},
{
id: 'baby-object-match',
title: '宝贝识物',
subtitle: '亲子识物分类',
badge: '可创建',
imageSrc: '/child-motion-demo/picture-book-grass-stage.png',
visible: true,
open: true,
sortOrder: 90,
updatedAtMicros: 1,
},
],
} satisfies CreationEntryConfig;
@@ -526,6 +539,28 @@ vi.mock('../../services/square-hole-works', () => ({
listSquareHoleWorks: vi.fn(),
}));
vi.mock('../../services/visual-novel-runtime', () => ({
listVisualNovelGallery: vi.fn(),
startVisualNovelRun: vi.fn(),
streamVisualNovelRuntimeAction: vi.fn(),
}));
vi.mock('../../services/visual-novel-works', () => ({
deleteVisualNovelWork: vi.fn(),
getVisualNovelWorkDetail: vi.fn(),
listVisualNovelWorks: vi.fn(),
publishVisualNovelWork: vi.fn(),
updateVisualNovelWork: vi.fn(),
}));
vi.mock('../../services/visual-novel-creation', () => ({
compileVisualNovelWorkProfile: vi.fn(),
createVisualNovelSession: vi.fn(),
executeVisualNovelAction: vi.fn(),
getVisualNovelSession: vi.fn(),
streamVisualNovelMessage: vi.fn(),
}));
vi.mock('../../services/creative-agent', () => ({
cancelCreativeAgentSession: vi.fn(),
confirmCreativePuzzleTemplate: vi.fn(),
@@ -1946,6 +1981,8 @@ beforeEach(() => {
vi.mocked(upsertProfileBrowseHistory).mockResolvedValue([]);
vi.mocked(clearProfileBrowseHistory).mockResolvedValue([]);
vi.mocked(deleteRpgEntryWorldProfile).mockResolvedValue([]);
vi.mocked(listVisualNovelGallery).mockResolvedValue({ works: [] });
vi.mocked(listVisualNovelWorks).mockResolvedValue({ works: [] });
vi.mocked(recordBigFishPlay).mockResolvedValue({ items: [] });
vi.mocked(recordRpgEntryWorldGalleryPlay).mockImplementation(
async (ownerUserId, profileId) => ({
@@ -2804,6 +2841,9 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(
screen.getByRole('tab', { name: '抓大鹅' }).querySelector('img')?.src,
).toContain('/creation-type-references/match3d.webp');
expect(
screen.getByRole('tab', { name: '宝贝识物' }).querySelector('img')?.src,
).toContain('/child-motion-demo/picture-book-grass-stage.png');
expect(
screen.getByRole('tab', { name: '拼图' }).querySelector('.text-white'),
).toBeTruthy();
@@ -2815,6 +2855,7 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByRole('tab', { name: //u })).toBeNull();
expect(screen.getByRole('tab', { name: //u })).toBeTruthy();
expect(screen.getByRole('tab', { name: //u })).toBeTruthy();
expect(createRpgCreationSession).not.toHaveBeenCalled();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
@@ -4705,6 +4746,30 @@ test('creation hub clears all private work shelves immediately after logout stat
});
});
test('creation draft hub skips visual novel shelves when entry is not open', async () => {
const user = userEvent.setup();
vi.mocked(fetchCreationEntryConfig).mockResolvedValue({
...testCreationEntryConfig,
creationTypes: testCreationEntryConfig.creationTypes.map((entry) =>
entry.id === 'visual-novel' ? { ...entry, open: false } : entry,
),
});
vi.mocked(listVisualNovelGallery).mockRejectedValue(
new Error('该玩法入口暂不可用'),
);
vi.mocked(listVisualNovelWorks).mockRejectedValue(
new Error('该玩法入口暂不可用'),
);
render(<TestWrapper withAuth />);
await openDraftHub(user);
expect(listVisualNovelGallery).not.toHaveBeenCalled();
expect(listVisualNovelWorks).not.toHaveBeenCalled();
expect(screen.queryByText('该玩法入口暂不可用')).toBeNull();
});
test('published puzzle works appear on home and mobile game category channel', async () => {
const user = userEvent.setup();
const publishedPuzzleWork = {