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>) { 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(); 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(); 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(); 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(); 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'); }); });