import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CharacterScene } from '@/pages/Makelore'; import { useAuthStore } from '@/stores/auth'; import { useOpencodeStore } from '@/stores/opencode'; import { usePartnerProfilesStore } from '@/stores/partner-profiles'; import { useProviderStore } from '@/stores/providers'; const hostApiFetchMock = vi.fn(); vi.mock('@/lib/host-api', () => ({ hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args), ensureHostApiToken: vi.fn().mockResolvedValue('test-token'), })); vi.mock('@/lib/api-client', () => ({ invokeIpc: vi.fn(), })); describe('Makelore character scene partner profiles', () => { beforeEach(() => { vi.clearAllMocks(); hostApiFetchMock.mockResolvedValue({ skills: [] }); useAuthStore.setState({ initialized: true, loading: false, error: null, accessToken: null, refreshToken: null, tokenType: null, expiresAt: null, user: null, }); useOpencodeStore.setState({ activeProject: null, projects: [], sessions: [], selectedSessionId: null }); usePartnerProfilesStore.setState({ subagents: [], projectRuntimeConfigsByProjectId: {}, profilesById: {} }); useProviderStore.setState({ statuses: [], accounts: [], vendors: [], defaultAccountId: null, loading: false, error: null, }); }); it('starts without partner presets and exposes manual creation', () => { render(); expect(screen.getByTestId('partner-empty-state')).toBeInTheDocument(); expect(screen.getByTestId('partner-create-open')).toBeInTheDocument(); expect(screen.queryByText(/角色预设/)).not.toBeInTheDocument(); }); it('creates and saves a custom partner without calling a default-agent route', async () => { render(); fireEvent.click(screen.getByTestId('partner-create-open')); const drawer = await screen.findByRole('dialog'); fireEvent.change(within(drawer).getByTestId('partner-create-name'), { target: { value: '研究伙伴' } }); fireEvent.change(within(drawer).getByTestId('partner-create-prompt'), { target: { value: '先分析资料,再给出可验证的结论。' } }); fireEvent.click(within(drawer).getByRole('button', { name: '创建伙伴' })); await waitFor(() => expect(usePartnerProfilesStore.getState().subagents).toHaveLength(1)); const [partner] = usePartnerProfilesStore.getState().subagents; expect(partner).toMatchObject({ displayName: '研究伙伴', prompt: '先分析资料,再给出可验证的结论。', templateRoleId: 'custom', }); expect(hostApiFetchMock.mock.calls.some(([path]) => String(path).includes('/api/opencode/agents'))).toBe(false); }); it('opens an installed Skill detail with its structure and main file', async () => { const project = { id: 'project-1', path: 'D:/projects/project-1', name: '详情测试项目', createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z', lastOpenedAt: '2026-01-01T00:00:00.000Z', }; useOpencodeStore.setState({ activeProject: project, projects: [project] }); hostApiFetchMock.mockResolvedValue({ skills: [{ name: 'pm-project-plan', description: 'E2E project planning skill.', location: 'D:/managed/skills/pm-project-plan/SKILL.md', content: '# Project planning', entries: [ { path: 'references', type: 'directory' }, { path: 'references/guide.md', type: 'file' }, { path: 'SKILL.md', type: 'file' }, ], }], }); render(); fireEvent.click(screen.getByTestId('resource-card-skills')); await screen.findByText('opencode 已安装'); fireEvent.click(screen.getByTestId('skill-library-card-pm-project-plan')); expect(await screen.findByTestId('skill-detail-view')).toBeInTheDocument(); expect(screen.getByTestId('skill-structure')).toHaveTextContent('references/guide.md'); expect(screen.getByTestId('skill-main-file')).toHaveTextContent('# Project planning'); fireEvent.click(screen.getByRole('button', { name: '返回技能列表' })); expect(await screen.findByTestId('skill-library-card-pm-project-plan')).toBeInTheDocument(); }); });