A test file under packages/client now says which face it covers:
`*.client.spec.{ts,tsx}` and its `*.client.{ts,tsx}` helpers belong to the
Client aggregate, `*.host.spec.ts` to the host aggregate. The carrier's four
node-half specs take the Host suffix.
The two suffixes are mutually exclusive, so each aggregate excludes the
other's and both keep one broad test glob: `exclude` wins over `include`, and
`packages/client/**` no longer has to be excluded wholesale from the host
program with per-file `files` entries carved back out of it. A Host-face spec
that reaches only Host source therefore needs no cross-face project
reference, which the split-project rule rejects.
vitest still discovers every file through `**/*.spec.{ts,tsx}`.
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
/** Trajectory toolbar export button: click dispatch, in-flight disable, and error surfacing. */
|
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
import type { LocaleKeysOf } from '@deepseek-ai/dsh-client-ui-slots'
|
|
import { TrajectoryToolbar, type TrajectoryToolbarProps } from '../src/client/TrajectoryToolbar.tsx'
|
|
import { zh, type TrajectoryKey } from '../src/client/locales.ts'
|
|
|
|
/** Test translator pinned to the Simplified Chinese dictionary. */
|
|
const zhT = (key: LocaleKeysOf<'trajectory'>): string => zh[key as TrajectoryKey] ?? key
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
function baseProps(overrides: Partial<TrajectoryToolbarProps> = {}): TrajectoryToolbarProps {
|
|
return {
|
|
actualDuration: false,
|
|
onActualDurationChange: vi.fn(),
|
|
actualTime: false,
|
|
onActualTimeChange: vi.fn(),
|
|
allTurnsCollapsed: false,
|
|
onToggleAllTurns: vi.fn(),
|
|
allAssistantsCollapsed: false,
|
|
onToggleAllAssistants: vi.fn(),
|
|
searchQuery: '',
|
|
onSearchQueryChange: vi.fn(),
|
|
exporting: false,
|
|
onExport: vi.fn(),
|
|
exportError: null,
|
|
t: zhT,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('TrajectoryToolbar export', () => {
|
|
it('renders the export button and dispatches the export callback on click', () => {
|
|
const onExport = vi.fn()
|
|
render(<TrajectoryToolbar {...baseProps({ onExport })} />)
|
|
const button = screen.getByRole('button', { name: 'Export session log' })
|
|
fireEvent.click(button)
|
|
expect(onExport).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('disables the button while an export is in flight and blocks dispatch', () => {
|
|
const onExport = vi.fn()
|
|
render(<TrajectoryToolbar {...baseProps({ exporting: true, onExport })} />)
|
|
const button = screen.getByRole('button', { name: 'Export session log' }) as HTMLButtonElement
|
|
expect(button.disabled).toBe(true)
|
|
fireEvent.click(button)
|
|
expect(onExport).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('surfaces an export failure as the button title', () => {
|
|
render(<TrajectoryToolbar {...baseProps({ exportError: 'Export failed: internal boom' })} />)
|
|
const button = screen.getByRole('button', { name: 'Export session log' })
|
|
expect(button.title).toBe('Export failed: internal boom')
|
|
})
|
|
})
|