Files
NianToB/tests/unit/agent-system-documents-settings.test.tsx

212 lines
6.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { AgentSystemDocumentsSettings } from '@/components/settings/AgentSystemDocumentsSettings';
import i18n from '@/i18n';
const hostApiFetchMock = vi.fn();
const toastSuccessMock = vi.fn();
const toastErrorMock = vi.fn();
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
}));
vi.mock('sonner', () => ({
toast: {
success: (...args: unknown[]) => toastSuccessMock(...args),
error: (...args: unknown[]) => toastErrorMock(...args),
},
}));
type TestDocumentKind = 'soul' | 'identity' | 'user' | 'agent' | 'tool' | 'heartbeat' | 'boot';
function makeSnapshot(agentId = 'main', overrides?: Partial<Record<TestDocumentKind, string>>) {
const documents = [
{
kind: 'soul',
fileName: 'SOUL.md',
path: `/tmp/${agentId}/SOUL.md`,
exists: true,
source: 'workspace',
content: overrides?.soul ?? '# Soul\n',
size: 7,
updatedAt: 1,
templateAvailable: true,
templatePath: '/runtime/SOUL.md',
},
{
kind: 'identity',
fileName: 'IDENTITY.md',
path: `/tmp/${agentId}/IDENTITY.md`,
exists: true,
source: 'workspace',
content: overrides?.identity ?? '# Identity\n',
size: 11,
updatedAt: 1,
templateAvailable: true,
templatePath: '/runtime/IDENTITY.md',
},
{
kind: 'user',
fileName: 'USER.md',
path: `/tmp/${agentId}/USER.md`,
exists: true,
source: 'workspace',
content: overrides?.user ?? '# User\n',
size: 7,
updatedAt: 1,
templateAvailable: true,
templatePath: '/runtime/USER.md',
},
{
kind: 'agent',
fileName: 'AGENTS.md',
path: `/tmp/${agentId}/AGENTS.md`,
exists: true,
source: 'workspace',
content: overrides?.agent ?? '# Agent\n',
size: 8,
updatedAt: 1,
templateAvailable: true,
templatePath: '/runtime/AGENTS.md',
},
{
kind: 'tool',
fileName: 'TOOLS.md',
path: `/tmp/${agentId}/TOOLS.md`,
exists: false,
source: 'template',
content: overrides?.tool ?? '# Tool\n',
size: 0,
updatedAt: null,
templateAvailable: true,
templatePath: '/runtime/TOOLS.md',
},
{
kind: 'heartbeat',
fileName: 'HEARTBEAT.md',
path: `/tmp/${agentId}/HEARTBEAT.md`,
exists: false,
source: 'template',
content: overrides?.heartbeat ?? '# Heartbeat\n',
size: 0,
updatedAt: null,
templateAvailable: true,
templatePath: '/runtime/HEARTBEAT.md',
},
{
kind: 'boot',
fileName: 'BOOT.md',
path: `/tmp/${agentId}/BOOT.md`,
exists: false,
source: 'template',
content: overrides?.boot ?? '# Boot\n',
size: 0,
updatedAt: null,
templateAvailable: true,
templatePath: '/runtime/BOOT.md',
},
];
return {
success: true,
selectedAgentId: agentId,
defaultAgentId: 'main',
agents: [
{ id: 'main', name: 'Main', isDefault: true, workspace: '~/.openclaw/workspace' },
{ id: 'coder', name: 'Coder', isDefault: false, workspace: '~/.openclaw/workspace-coder' },
],
documents,
paths: {
workspace: `/tmp/${agentId}`,
templateDir: '/runtime',
},
};
}
describe('AgentSystemDocumentsSettings', () => {
beforeEach(async () => {
vi.clearAllMocks();
await i18n.changeLanguage('zh');
hostApiFetchMock.mockImplementation((path: string, init?: { method?: string; body?: string }) => {
if (path === '/api/agent-system-documents?agentId=coder') {
return Promise.resolve(makeSnapshot('coder', { soul: '# Coder soul\n' }));
}
if (path === '/api/agent-system-documents/soul' && init?.method === 'PUT') {
const body = JSON.parse(init.body || '{}') as { content?: string };
return Promise.resolve(makeSnapshot('main', { soul: body.content ?? '' }));
}
if (path === '/api/agent-system-documents/tool/reset' && init?.method === 'POST') {
return Promise.resolve(makeSnapshot('main', { tool: '# Tool template\n' }));
}
return Promise.resolve(makeSnapshot('main'));
});
});
it('loads system documents and switches selected agent', async () => {
render(<AgentSystemDocumentsSettings />);
expect(await screen.findByTestId('agent-system-document-editor')).toHaveValue('# Soul\n');
expect(screen.getByTestId('agent-system-document-tab-soul')).toHaveTextContent('SOUL.md');
fireEvent.change(screen.getByTestId('agent-system-document-agent-select'), {
target: { value: 'coder' },
});
await waitFor(() => {
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/agent-system-documents?agentId=coder');
});
expect(await screen.findByTestId('agent-system-document-editor')).toHaveValue('# Coder soul\n');
});
it('saves the edited selected document', async () => {
render(<AgentSystemDocumentsSettings />);
const editor = await screen.findByTestId('agent-system-document-editor');
fireEvent.change(editor, { target: { value: '# Updated soul\n' } });
fireEvent.click(screen.getByTestId('agent-system-document-save'));
await waitFor(() => {
expect(hostApiFetchMock).toHaveBeenCalledWith(
'/api/agent-system-documents/soul',
expect.objectContaining({
method: 'PUT',
body: JSON.stringify({ agentId: 'main', content: '# Updated soul\n' }),
}),
);
});
expect(toastSuccessMock).toHaveBeenCalledWith('SOUL.md 已保存');
});
it('restores the selected tool document from template', async () => {
render(<AgentSystemDocumentsSettings />);
await screen.findByTestId('agent-system-document-editor');
fireEvent.click(screen.getByTestId('agent-system-document-tab-tool'));
fireEvent.click(screen.getByTestId('agent-system-document-reset'));
await waitFor(() => {
expect(hostApiFetchMock).toHaveBeenCalledWith(
'/api/agent-system-documents/tool/reset',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ agentId: 'main' }),
}),
);
});
expect(await screen.findByTestId('agent-system-document-editor')).toHaveValue('# Tool template\n');
});
it('shows the OpenClaw workspace control documents', async () => {
render(<AgentSystemDocumentsSettings />);
await screen.findByTestId('agent-system-document-editor');
expect(screen.getByTestId('agent-system-document-tab-identity')).toHaveTextContent('IDENTITY.md');
expect(screen.getByTestId('agent-system-document-tab-user')).toHaveTextContent('USER.md');
expect(screen.getByTestId('agent-system-document-tab-heartbeat')).toHaveTextContent('HEARTBEAT.md');
expect(screen.getByTestId('agent-system-document-tab-boot')).toHaveTextContent('BOOT.md');
});
});