Files
Genarrative/src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx
T
2026-05-08 11:44:42 +08:00

124 lines
4.3 KiB
TypeScript

/* @vitest-environment jsdom */
import type { ComponentProps } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test, vi } from 'vitest';
import { buildVisualNovelForbiddenCopyPattern } from '../visual-novel-runtime/visualNovelForbiddenCopy';
import { mockVisualNovelSession } from '../visual-novel-runtime/visualNovelMockData';
import { VisualNovelAgentWorkspace } from './VisualNovelAgentWorkspace';
import { AuthUiContext } from '../auth/AuthUiContext';
vi.mock('../../services/creation-agent/creationAgentDocumentInput', () => ({
parseCreationAgentDocumentInput: vi.fn(),
}));
vi.mock('../../services/visual-novel-creation', () => ({
uploadVisualNovelAsset: vi.fn(),
}));
const mockedParseCreationAgentDocumentInput = vi.mocked(
await import('../../services/creation-agent/creationAgentDocumentInput'),
);
const mockedVisualNovelAssetClient = vi.mocked(
await import('../../services/visual-novel-creation'),
);
function renderWorkspace(ui?: Partial<ComponentProps<typeof VisualNovelAgentWorkspace>>) {
return render(
<AuthUiContext.Provider
value={
{
user: { id: 'user-1' },
platformTheme: 'light',
} as never
}
>
<VisualNovelAgentWorkspace
session={mockVisualNovelSession}
onBack={() => {}}
{...ui}
/>
</AuthUiContext.Provider>,
);
}
test('visual novel workspace renders mock creation shell without forbidden entry', async () => {
const user = userEvent.setup();
const onOpenResult = vi.fn();
renderWorkspace({ onOpenResult });
expect(screen.getByRole('heading', { name: '视觉小说' })).toBeTruthy();
expect(screen.getByRole('button', { name: '一句话' })).toBeTruthy();
expect(screen.queryByText(buildVisualNovelForbiddenCopyPattern())).toBeNull();
await user.click(screen.getByRole('button', { name: '进入结果页' }));
expect(onOpenResult).toHaveBeenCalledWith(mockVisualNovelSession);
});
test('visual novel workspace opens editable blank draft from blank source', async () => {
const user = userEvent.setup();
const onOpenResult = vi.fn();
renderWorkspace({ session: null, onOpenResult });
await user.click(screen.getByRole('button', { name: '空白' }));
const openResultButtons = screen.getAllByRole('button', {
name: '进入结果页',
});
await user.click(openResultButtons[0]!);
expect(onOpenResult).toHaveBeenCalledTimes(1);
const session = onOpenResult.mock.calls[0]?.[0];
expect(session?.sourceMode).toBe('blank');
expect(session?.draft?.sourceMode).toBe('blank');
expect(session?.draft?.runtimeConfig.textModeEnabled).toBe(true);
});
test('visual novel workspace uploads document asset and passes asset id to session', async () => {
const user = userEvent.setup();
const onCreateSession = vi.fn();
const parseMock = mockedParseCreationAgentDocumentInput.parseCreationAgentDocumentInput;
const uploadMock = mockedVisualNovelAssetClient.uploadVisualNovelAsset;
parseMock.mockResolvedValue({
document: {
fileName: '世界设定.docx',
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
sizeBytes: 128,
text: '第一章\n雨夜书店\n第二章\n失踪乘客',
sourceAssetId: 'asset-doc-source',
},
});
uploadMock.mockResolvedValue({
assetObjectId: 'asset-doc-1',
assetKind: 'visual_novel_document',
objectKey: 'generated-character-drafts/visual-novel/draft/document/1.docx',
imageSrc: '/generated-character-drafts/visual-novel/draft/document/1.docx',
});
renderWorkspace({ session: null, onCreateSession });
await user.click(screen.getByRole('button', { name: '文档' }));
const fileInput = screen.getByLabelText('上传文档') as HTMLInputElement;
const file = new File(['first chapter'], '世界设定.docx', {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
await user.upload(fileInput, file);
await user.click(screen.getByRole('button', { name: '生成底稿' }));
expect(parseMock).toHaveBeenCalledTimes(1);
expect(uploadMock).toHaveBeenCalledTimes(1);
expect(onCreateSession).toHaveBeenCalledWith(
expect.objectContaining({
sourceMode: 'document',
sourceAssetIds: ['asset-doc-1'],
seedText: expect.stringContaining('第一章'),
}),
);
});