1
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { buildVisualNovelForbiddenCopyPattern } from './visualNovelForbiddenCopy';
|
||||
import { mockVisualNovelDraft, mockVisualNovelRun } from './visualNovelMockData';
|
||||
import { VisualNovelRuntimeShell } from './VisualNovelRuntimeShell';
|
||||
|
||||
test('visual novel runtime renders mock play surface and opens panels as dialogs', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={mockVisualNovelRun}
|
||||
onBack={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('风雪站台')).toBeTruthy();
|
||||
expect(screen.getAllByText('林遥').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText(buildVisualNovelForbiddenCopyPattern())).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '历史' }));
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '历史' });
|
||||
expect(within(dialog).getByText('#1')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('visual novel runtime submits free text action with client event id', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubmitAction = vi.fn();
|
||||
|
||||
render(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={mockVisualNovelRun}
|
||||
onBack={() => {}}
|
||||
onSubmitAction={onSubmitAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.type(screen.getByLabelText('输入行动'), '检查广播柜');
|
||||
await user.click(screen.getByRole('button', { name: '发送行动' }));
|
||||
|
||||
expect(onSubmitAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actionKind: 'free_text',
|
||||
text: '检查广播柜',
|
||||
}),
|
||||
);
|
||||
expect(onSubmitAction.mock.calls[0]?.[0].clientEventId).toMatch(
|
||||
/^vn-free-text-/u,
|
||||
);
|
||||
});
|
||||
|
||||
test('visual novel runtime submits choice and continue actions', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubmitAction = vi.fn();
|
||||
const onContinue = vi.fn();
|
||||
const runWithoutChoices = {
|
||||
...mockVisualNovelRun,
|
||||
availableChoices: [],
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={mockVisualNovelRun}
|
||||
onBack={() => {}}
|
||||
onSubmitAction={onSubmitAction}
|
||||
onContinue={onContinue}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '靠近广播柜,确认频段来源。' }));
|
||||
expect(onSubmitAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actionKind: 'choice',
|
||||
choiceId: 'vn-choice-radio',
|
||||
}),
|
||||
);
|
||||
expect(onSubmitAction.mock.calls[0]?.[0].clientEventId).toMatch(
|
||||
/^vn-choice-/u,
|
||||
);
|
||||
|
||||
rerender(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={runWithoutChoices}
|
||||
onBack={() => {}}
|
||||
onSubmitAction={onSubmitAction}
|
||||
onContinue={onContinue}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '继续' }));
|
||||
expect(onContinue).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('visual novel runtime panels call regeneration and platform archive actions', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onRegenerateHistoryEntry = vi.fn();
|
||||
const onSaveRun = vi.fn();
|
||||
const onResumeSaveArchive = vi.fn();
|
||||
|
||||
render(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={mockVisualNovelRun}
|
||||
onBack={() => {}}
|
||||
onRegenerateHistoryEntry={onRegenerateHistoryEntry}
|
||||
onSaveRun={onSaveRun}
|
||||
onResumeSaveArchive={onResumeSaveArchive}
|
||||
saveArchives={[
|
||||
{
|
||||
worldKey: 'visual-novel:archive-1',
|
||||
ownerUserId: 'mock-user',
|
||||
profileId: 'vn-profile-mock-1',
|
||||
worldType: 'visual-novel',
|
||||
worldName: '雪线电台',
|
||||
subtitle: '风雪站台',
|
||||
summaryText: '第 2 回合',
|
||||
coverImageSrc: null,
|
||||
lastPlayedAt: '2026-05-05T12:00:00.000Z',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '历史' }));
|
||||
await user.click(screen.getByRole('button', { name: '重生成' }));
|
||||
expect(onRegenerateHistoryEntry).toHaveBeenCalledWith('vn-history-1');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '关闭' }));
|
||||
await user.click(screen.getByRole('button', { name: '存档' }));
|
||||
await user.click(screen.getByRole('button', { name: '保存' }));
|
||||
expect(onSaveRun).toHaveBeenCalledTimes(1);
|
||||
await user.click(screen.getByText('雪线电台'));
|
||||
expect(onResumeSaveArchive).toHaveBeenCalledWith('visual-novel:archive-1');
|
||||
});
|
||||
|
||||
test('visual novel runtime shows raw text only as transient stream text', () => {
|
||||
const transientText = '这是临时流式文本';
|
||||
|
||||
render(
|
||||
<VisualNovelRuntimeShell
|
||||
draft={mockVisualNovelDraft}
|
||||
run={mockVisualNovelRun}
|
||||
streamingText={transientText}
|
||||
onBack={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(transientText)).toBeTruthy();
|
||||
const textModeBlocks = screen.queryAllByText((content, element) => {
|
||||
return Boolean(
|
||||
element?.className.toString().includes('whitespace-pre-line') &&
|
||||
content.includes(transientText),
|
||||
);
|
||||
});
|
||||
expect(textModeBlocks).toHaveLength(0);
|
||||
});
|
||||
Reference in New Issue
Block a user