This commit is contained in:
2026-04-21 18:27:46 +08:00
parent 04bff9617d
commit 4372ab5be1
358 changed files with 30788 additions and 14737 deletions

View File

@@ -12,10 +12,11 @@ import type {
} from '../types';
import { CustomWorldEntityCatalog } from './CustomWorldEntityCatalog';
import {
type CustomWorldEditorTarget,
CustomWorldEntityEditorModal,
} from './CustomWorldEntityEditorModal';
type RpgCreationEditorTarget,
RpgCreationEntityEditorModal,
} from './rpg-creation-editor/RpgCreationEntityEditorModal';
import * as customWorldCoverAssetService from '../services/customWorldCoverAssetService';
import * as rpgCreationAssetClient from '../services/rpg-creation/rpgCreationAssetClient';
vi.mock('../data/characterPresets', async () => {
const actual = await vi.importActual<typeof import('../data/characterPresets')>(
@@ -37,12 +38,23 @@ vi.mock('./CharacterAnimator', () => ({
CharacterAnimator: () => <div></div>,
}));
vi.mock('../services/aiService', () => ({
generateCustomWorldSceneImage: vi.fn(),
generateCustomWorldSceneNpc: vi.fn(),
generateInitialStory: vi.fn(),
generateNextStep: vi.fn(),
}));
vi.mock('../services/rpg-creation/rpgCreationAssetClient', () => {
const generateSceneImage = vi.fn();
const generateSceneNpc = vi.fn();
return {
rpgCreationAssetClient: {
generateSceneImage,
generateSceneNpc,
},
generateCustomWorldSceneImage: generateSceneImage,
generateCustomWorldSceneNpc: generateSceneNpc,
};
});
const mockedRpgCreationAssetClient = vi.mocked(
rpgCreationAssetClient.rpgCreationAssetClient,
);
vi.mock('./CustomWorldNpcVisualEditor', () => ({
CustomWorldNpcPortrait: ({ npc }: { npc: { name: string } }) => (
@@ -51,8 +63,8 @@ vi.mock('./CustomWorldNpcVisualEditor', () => ({
CustomWorldNpcVisualEditor: () => <div></div>,
}));
vi.mock('./game-shell/GameShellRuntime', () => ({
GameShellRuntime: ({
vi.mock('./rpg-runtime-shell', () => ({
RpgRuntimeShell: ({
session,
}: {
session: { gameState: { currentScenePreset?: { name?: string } | null } };
@@ -215,7 +227,7 @@ function createProfileWithLandmark(): CustomWorldProfile {
function LandmarkEditorFlowHarness() {
const [profile, setProfile] = useState(createProfileWithLandmark());
const [target, setTarget] = useState<CustomWorldEditorTarget | null>({
const [target, setTarget] = useState<RpgCreationEditorTarget | null>({
kind: 'landmark',
mode: 'edit',
id: 'landmark-1',
@@ -236,7 +248,7 @@ function LandmarkEditorFlowHarness() {
onDeleteStoryNpcs={() => {}}
onDeleteLandmarks={() => {}}
/>
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={profile}
target={target}
onClose={() => setTarget(null)}
@@ -278,7 +290,7 @@ function CampEditorFlowHarness() {
],
},
});
const [target, setTarget] = useState<CustomWorldEditorTarget | null>({
const [target, setTarget] = useState<RpgCreationEditorTarget | null>({
kind: 'camp',
});
@@ -297,7 +309,7 @@ function CampEditorFlowHarness() {
onDeleteStoryNpcs={() => {}}
onDeleteLandmarks={() => {}}
/>
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={profile}
target={target}
onClose={() => setTarget(null)}
@@ -316,7 +328,7 @@ function CoverEditorFlowHarness() {
characterRoleIds: ['playable-1'],
},
});
const [target, setTarget] = useState<CustomWorldEditorTarget | null>({
const [target, setTarget] = useState<RpgCreationEditorTarget | null>({
kind: 'cover',
});
@@ -325,7 +337,7 @@ function CoverEditorFlowHarness() {
<pre data-testid="cover-profile-json" className="hidden">
{JSON.stringify(profile)}
</pre>
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={profile}
target={target}
onClose={() => setTarget(null)}
@@ -350,7 +362,7 @@ test('playable角色打开AI工坊后不会自动关闭', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'playable', mode: 'edit', id: 'playable-1' }}
onClose={handleClose}
@@ -372,7 +384,7 @@ test('场景角色打开AI工坊后不会自动关闭', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'story', mode: 'edit', id: 'story-1' }}
onClose={handleClose}
@@ -394,7 +406,7 @@ test('可扮演角色未修改时右上角关闭不会弹确认', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'playable', mode: 'edit', id: 'playable-1' }}
onClose={handleClose}
@@ -413,7 +425,7 @@ test('可扮演角色修改后右上角关闭才弹确认', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'playable', mode: 'edit', id: 'playable-1' }}
onClose={handleClose}
@@ -435,7 +447,7 @@ test('场景角色未修改时右上角关闭不会弹确认', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'story', mode: 'edit', id: 'story-1' }}
onClose={handleClose}
@@ -454,7 +466,7 @@ test('场景角色修改后右上角关闭才弹确认', async () => {
const handleClose = vi.fn();
render(
<CustomWorldEntityEditorModal
<RpgCreationEntityEditorModal
profile={createProfile()}
target={{ kind: 'story', mode: 'edit', id: 'story-1' }}
onClose={handleClose}
@@ -538,9 +550,8 @@ test('实体目录在空 id 列表项下不会触发重复 key 警告', () => {
});
test('场景图片保存后会同步更新编辑页和场景列表', async () => {
const aiService = await import('../services/aiService');
vi.mocked(aiService.generateCustomWorldSceneImage).mockClear();
vi.mocked(aiService.generateCustomWorldSceneImage).mockResolvedValue({
mockedRpgCreationAssetClient.generateSceneImage.mockClear();
mockedRpgCreationAssetClient.generateSceneImage.mockResolvedValue({
imageSrc: '/generated-custom-world-scenes/updated-scene.png',
assetId: 'asset-1',
model: 'wan2.2-t2i-flash',
@@ -573,7 +584,7 @@ test('场景图片保存后会同步更新编辑页和场景列表', async () =>
await user.click(screen.getByRole('button', { name: '开始生成' }));
await waitFor(() => {
expect(aiService.generateCustomWorldSceneImage).toHaveBeenCalledTimes(1);
expect(mockedRpgCreationAssetClient.generateSceneImage).toHaveBeenCalledTimes(1);
});
await user.click(screen.getByRole('button', { name: '保存' }));
@@ -609,9 +620,8 @@ test('场景图片保存后会同步更新编辑页和场景列表', async () =>
});
test('开局场景图片保存后会同步更新编辑页和场景列表', async () => {
const aiService = await import('../services/aiService');
vi.mocked(aiService.generateCustomWorldSceneImage).mockClear();
vi.mocked(aiService.generateCustomWorldSceneImage).mockResolvedValue({
mockedRpgCreationAssetClient.generateSceneImage.mockClear();
mockedRpgCreationAssetClient.generateSceneImage.mockResolvedValue({
imageSrc: '/generated-custom-world-scenes/updated-camp.png',
assetId: 'asset-camp-1',
model: 'wan2.2-t2i-flash',
@@ -644,7 +654,7 @@ test('开局场景图片保存后会同步更新编辑页和场景列表', async
await user.click(screen.getByRole('button', { name: '开始生成' }));
await waitFor(() => {
expect(aiService.generateCustomWorldSceneImage).toHaveBeenCalledTimes(1);
expect(mockedRpgCreationAssetClient.generateSceneImage).toHaveBeenCalledTimes(1);
});
await user.click(screen.getByRole('button', { name: '保存' }));