Files
aiturk-hermes-ide/apps/desktop/src/app/shell/model-edit-submenu.test.tsx
T

147 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuSub,
DropdownMenuSubTrigger
} from '@/components/ui/dropdown-menu'
import { type FastControl, ModelEditSubmenu } from './model-edit-submenu'
// Radix calls these on open; jsdom doesn't implement them.
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn()
Element.prototype.hasPointerCapture = vi.fn(() => false)
Element.prototype.releasePointerCapture = vi.fn()
})
afterEach(() => {
cleanup()
vi.clearAllMocks()
})
// Render the submenu inside an open menu/sub so its content (switches) mounts.
function renderSubmenu(opts: {
defaultEffort?: string
effort?: string
fastControl: FastControl
isActive?: boolean
onSelectModel?: (model: string) => void
onOpenDetails?: () => void
onSetOptions: (patch: { effort?: string; fast?: boolean }) => void
reasoning: boolean
}) {
return render(
<DropdownMenu open>
<DropdownMenuContent>
<DropdownMenuSub open>
<DropdownMenuSubTrigger>edit</DropdownMenuSubTrigger>
<ModelEditSubmenu
defaultEffort={opts.defaultEffort ?? 'medium'}
effort={opts.effort ?? 'medium'}
fastControl={opts.fastControl}
isActive={opts.isActive ?? true}
model="m1"
onOpenDetails={opts.onOpenDetails}
onSelectModel={opts.onSelectModel ?? vi.fn()}
onSetOptions={opts.onSetOptions}
provider="p1"
reasoning={opts.reasoning}
/>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
)
}
// The submenu is PURE: it reports edits and never writes to a session, a
// preset store, or the gateway. That's the invariant that lets the same
// component drive a live chat session AND a detached per-task override — if it
// ever writes directly again, picking an effort for a kanban card would reach
// over and change the user's live chat.
describe('ModelEditSubmenu reports edits without performing them', () => {
it('opens read-only details without selecting a model or changing its options', () => {
const onOpenDetails = vi.fn()
const onSelectModel = vi.fn()
const onSetOptions = vi.fn()
renderSubmenu({ fastControl: { kind: 'none' }, reasoning: false, onOpenDetails, onSelectModel, onSetOptions })
fireEvent.click(screen.getByRole('menuitem', { name: /Model \/ combo (details|ayrıntıları)/ }))
expect(onOpenDetails).toHaveBeenCalledOnce()
expect(onSelectModel).not.toHaveBeenCalled()
expect(onSetOptions).not.toHaveBeenCalled()
})
it('param fast: reports the toggle', () => {
const onSetOptions = vi.fn()
renderSubmenu({ fastControl: { kind: 'param', on: true }, onSetOptions, reasoning: false })
fireEvent.click(screen.getByRole('switch'))
expect(onSetOptions).toHaveBeenCalledWith({ fast: false })
})
it('thinking: toggling off reports the none level', () => {
const onSetOptions = vi.fn()
renderSubmenu({ fastControl: { kind: 'none' }, onSetOptions, reasoning: true })
// Thinking starts on (medium); toggling it off reports 'none'.
fireEvent.click(screen.getByRole('switch'))
expect(onSetOptions).toHaveBeenCalledWith({ effort: 'none' })
})
it('thinking: toggling back on restores the row level, not the hardcoded default', () => {
const onSetOptions = vi.fn()
renderSubmenu({
defaultEffort: 'high',
effort: 'none',
fastControl: { kind: 'none' },
onSetOptions,
reasoning: true
})
fireEvent.click(screen.getByRole('switch'))
expect(onSetOptions).toHaveBeenCalledWith({ effort: 'high' })
})
it('variant fast: swaps the model only when the row is active', () => {
const onSelectModel = vi.fn()
const onSetOptions = vi.fn()
renderSubmenu({
fastControl: { baseId: 'm1', fastId: 'm1-fast', kind: 'variant', on: false },
isActive: false,
onSelectModel,
onSetOptions,
reasoning: false
})
fireEvent.click(screen.getByRole('switch'))
// Inactive rows stay preference-only — no model switch.
expect(onSetOptions).toHaveBeenCalledWith({ fast: true })
expect(onSelectModel).not.toHaveBeenCalled()
})
it('variant fast: active row swaps to the -fast sibling', () => {
const onSelectModel = vi.fn()
const onSetOptions = vi.fn()
renderSubmenu({
fastControl: { baseId: 'm1', fastId: 'm1-fast', kind: 'variant', on: false },
onSelectModel,
onSetOptions,
reasoning: false
})
fireEvent.click(screen.getByRole('switch'))
expect(onSelectModel).toHaveBeenCalledWith('m1-fast')
})
})