mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-31 16:33:34 +08:00
test(desktop): guard the mock paths and update suites for the new markup
`mockPaths.test.ts` asserts every `vi.mock` specifier resolves. A mock pointing at a moved module 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 what happened to `RepositoryLaunchControls.test.tsx`, which kept mocking `./DirectoryPicker` after the component moved to `composite/`, so the real picker and its API calls rendered in a test that believed they were stubbed. Suite updates for markup that legitimately changed shape: - Dropdown entries are `role="option"` now, not buttons. `<button>` is invalid inside a `role="listbox"` and left the dropdown without arrow keys; the assertions follow the corrected semantics. - `PetSettings`' toggles report `role="switch"`, the ARIA role for a toggle. They were bare checkboxes while `McpSettings`' equivalent was already a switch — the two are now consistent. - `pages.test.tsx` drops its references to the two deleted mock pages.
This commit is contained in:
parent
f64745e569
commit
59b93f9bca
@ -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()
|
||||
})
|
||||
|
||||
57
desktop/src/__tests__/mockPaths.test.ts
Normal file
57
desktop/src/__tests__/mockPaths.test.ts
Normal file
@ -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([])
|
||||
})
|
||||
})
|
||||
@ -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(<AgentTeams />)
|
||||
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(<ScheduledTasks />)
|
||||
await screen.findByText('Scheduled tasks')
|
||||
expect(container.innerHTML).toContain('Scheduled tasks')
|
||||
})
|
||||
|
||||
it('ToolInspection renders diff viewer', () => {
|
||||
const { container } = render(<ToolInspection />)
|
||||
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(<Page />)
|
||||
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(<AgentTeams />)
|
||||
expect(container.innerHTML).toContain('Architect')
|
||||
expect(container.innerHTML).toContain('Frontend Dev')
|
||||
expect(container.innerHTML).toContain('Backend Dev')
|
||||
expect(container.innerHTML).toContain('Tester')
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user