diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx index b9ed6c06..6027709c 100644 --- a/desktop/src/__tests__/generalSettings.test.tsx +++ b/desktop/src/__tests__/generalSettings.test.tsx @@ -68,7 +68,7 @@ vi.mock('../api/providers', () => ({ })) vi.mock('../lib/desktopNotifications', () => desktopNotificationsMock) -vi.mock('../components/chat/clipboard', () => clipboardMock) +vi.mock('@/lib/clipboard', () => clipboardMock) vi.mock('@tauri-apps/api/core', () => tauriCoreMock) vi.mock('@tauri-apps/plugin-dialog', () => tauriDialogMock) vi.mock('@tauri-apps/plugin-process', () => tauriProcessMock) @@ -883,7 +883,7 @@ describe('Settings > General tab', () => { const trigger = screen.getByRole('button', { name: 'Response Language' }) expect(trigger).toHaveTextContent('Default (English)') fireEvent.click(trigger) - fireEvent.click(screen.getByRole('button', { name: '中文 (Chinese)' })) + fireEvent.click(screen.getByRole('option', { name: '中文 (Chinese)' })) expect(useSettingsStore.getState().setResponseLanguage).toHaveBeenCalledWith('chinese') }) @@ -1724,8 +1724,9 @@ describe('Settings > Providers tab', () => { expect(within(dialog).queryByRole('combobox')).not.toBeInTheDocument() fireEvent.click(within(dialog).getByRole('button', { name: /Anthropic Messages \(native\)/i })) - fireEvent.click(within(dialog).getByRole('button', { name: /OpenAI Responses API \(proxy\)/i })) + fireEvent.click(within(dialog).getByRole('option', { name: /OpenAI Responses API \(proxy\)/i })) + // The panel is closed now; this finds the trigger, which reflects the pick. expect(within(dialog).getByRole('button', { name: /OpenAI Responses API \(proxy\)/i })).toBeInTheDocument() expect(within(dialog).getByText('Requests will be translated via the local proxy')).toBeInTheDocument() }) diff --git a/desktop/src/__tests__/mockPaths.test.ts b/desktop/src/__tests__/mockPaths.test.ts new file mode 100644 index 00000000..a00cb8d9 --- /dev/null +++ b/desktop/src/__tests__/mockPaths.test.ts @@ -0,0 +1,57 @@ +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' + +import { describe, expect, it } from 'vitest' + +/** + * A `vi.mock` whose path does not resolve is not an error — Vitest registers + * the factory against a specifier nothing imports, the real module loads, and + * the test keeps passing with its isolation quietly gone. + * + * That is exactly what happened when components moved out of `shared/`: + * `RepositoryLaunchControls.test.tsx` kept mocking `./DirectoryPicker` while + * the component had moved to `@/components/composite/DirectoryPicker`, so the + * real picker (and its API calls) rendered in a test that believed it was + * stubbed. + */ + +const SRC = join(process.cwd(), 'src') + +function collectTests(dir: string, out: string[] = []): string[] { + for (const entry of readdirSync(dir)) { + if (entry === 'node_modules' || entry.startsWith('.')) continue + const path = join(dir, entry) + if (statSync(path).isDirectory()) collectTests(path, out) + else if (/\.test\.tsx?$/.test(entry)) out.push(path) + } + return out +} + +/** Mirrors the resolution in vite.config.ts: `@/` maps to `src/`. */ +function resolvesToFile(specifier: string, fromFile: string): boolean { + let base: string + if (specifier.startsWith('@/')) base = join(SRC, specifier.slice(2)) + else if (specifier.startsWith('.')) base = resolve(dirname(fromFile), specifier) + else return true // bare package specifier — node_modules, not our concern + + return ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', ''] + .some((suffix) => existsSync(base + suffix)) +} + +describe('vi.mock specifiers', () => { + it('all resolve to a real module', () => { + const broken: string[] = [] + + for (const file of collectTests(SRC)) { + readFileSync(file, 'utf8').split('\n').forEach((line, index) => { + const match = line.match(/vi\.mock\(\s*['"]([^'"]+)['"]/) + if (!match) return + if (!resolvesToFile(match[1]!, file)) { + broken.push(`${file.replace(SRC, 'src')}:${index + 1} vi.mock('${match[1]}')`) + } + }) + } + + expect(broken).toEqual([]) + }) +}) diff --git a/desktop/src/__tests__/pages.test.tsx b/desktop/src/__tests__/pages.test.tsx index 97a50bf9..d31ff054 100644 --- a/desktop/src/__tests__/pages.test.tsx +++ b/desktop/src/__tests__/pages.test.tsx @@ -75,9 +75,7 @@ vi.mock('../api/sessions', async (importOriginal) => { // Import all pages import { EmptySession } from '../pages/EmptySession' import { ActiveSession } from '../pages/ActiveSession' -import { AgentTeams } from '../pages/AgentTeams' import { ScheduledTasks } from '../pages/ScheduledTasks' -import { ToolInspection } from '../pages/ToolInspection' // Layout components (chrome is now here, not in pages) import { Sidebar } from '../components/layout/Sidebar' @@ -1243,25 +1241,11 @@ describe('Content-only pages render without errors', () => { useSessionRuntimeStore.setState({ selections: {} }) }) - it('AgentTeams renders team strip and members', () => { - const { container } = render() - expect(container.innerHTML).toContain('Architect') - expect(container.innerHTML).toContain('session-dev') - expect(container.innerHTML).toContain('groups') - }) - it('ScheduledTasks renders (store-connected)', async () => { const { container } = render() await screen.findByText('Scheduled tasks') expect(container.innerHTML).toContain('Scheduled tasks') }) - - it('ToolInspection renders diff viewer', () => { - const { container } = render() - expect(container.innerHTML).toContain('edit_file') - expect(container.innerHTML).toContain('Split') - expect(container.innerHTML).toContain('Unified') - }) }) describe('Chat attachments', () => { @@ -1301,7 +1285,7 @@ describe('AppShell layout renders chrome', () => { describe('Design system compliance', () => { it('Pages use Material Symbols Outlined icons', () => { - const pages = [EmptySession, AgentTeams, ToolInspection] + const pages = [EmptySession] for (const Page of pages) { const { container, unmount } = render() const icons = container.querySelectorAll('.material-symbols-outlined') @@ -1326,13 +1310,3 @@ describe('Design system compliance', () => { } }) }) - -describe('Mock data integration', () => { - it('AgentTeams shows team members from mock data', () => { - const { container } = render() - expect(container.innerHTML).toContain('Architect') - expect(container.innerHTML).toContain('Frontend Dev') - expect(container.innerHTML).toContain('Backend Dev') - expect(container.innerHTML).toContain('Tester') - }) -}) diff --git a/desktop/src/components/markdown/MarkdownRenderer.tsx b/desktop/src/components/markdown/MarkdownRenderer.tsx index 37de1b82..5d67ce76 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.tsx @@ -6,7 +6,7 @@ import 'katex/dist/katex.min.css' import { marked, type Tokens } from 'marked' import { CodeViewer } from '../chat/CodeViewer' import { MermaidRenderer } from '../chat/MermaidRenderer' -import { copyTextToClipboard } from '../chat/clipboard' +import { copyTextToClipboard } from '@/lib/clipboard' type Props = { content: string