|
|
|
|
@@ -43,6 +43,8 @@ interface BenchOptions {
|
|
|
|
|
variant?: 'hero' | 'composer'
|
|
|
|
|
placeholder?: string
|
|
|
|
|
translateHint?: (key: string) => string
|
|
|
|
|
translateAccess?: (key: string) => string
|
|
|
|
|
command?: (line: string) => Promise<boolean>
|
|
|
|
|
accessory?: React.ReactNode
|
|
|
|
|
overlay?: React.ReactNode
|
|
|
|
|
leftItems?: React.ReactNode
|
|
|
|
|
@@ -100,12 +102,19 @@ function bench(over?: BenchOptions) {
|
|
|
|
|
useNotices: bindSnapshotSelector(shell.notices),
|
|
|
|
|
useLexicon: bindSnapshotSelector(shell.lexicon),
|
|
|
|
|
stop,
|
|
|
|
|
command: () => Promise.resolve(true),
|
|
|
|
|
command: over?.command ?? (() => Promise.resolve(true)),
|
|
|
|
|
// Mirrors the en 'command.hint' locale entries the production apply wires in.
|
|
|
|
|
translateHint: over?.translateHint ?? ((key: string) => ({
|
|
|
|
|
'placeholder.default': 'Message the agent',
|
|
|
|
|
'placeholder.plan': 'describe your task to generate plan',
|
|
|
|
|
} as Record<string, string>)[key] ?? key),
|
|
|
|
|
translateAccess: over?.translateAccess ?? ((key: string) => ({
|
|
|
|
|
'confirm.title': 'Enable Full access?',
|
|
|
|
|
'confirm.description': 'Full access can perform sensitive operations.',
|
|
|
|
|
'confirm.acknowledge': 'I understand the risks and want to continue',
|
|
|
|
|
'confirm.cancel': 'Cancel',
|
|
|
|
|
'confirm.enable': 'Enable Full access',
|
|
|
|
|
} as Record<string, string>)[key] ?? key),
|
|
|
|
|
renderSlot,
|
|
|
|
|
variant: over?.variant ?? 'composer',
|
|
|
|
|
...(over?.placeholder !== undefined ? { placeholder: over.placeholder } : {}),
|
|
|
|
|
@@ -450,7 +459,35 @@ describe('placeholder chrome and control seats', () => {
|
|
|
|
|
expect(view.queryByLabelText('Model')).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('the Access chip renders the projection value and submits /permission on pick', async () => {
|
|
|
|
|
it('the Access chip renders the projection value and submits a non-Full-access pick directly', async () => {
|
|
|
|
|
const command = vi.fn(() => Promise.resolve(true))
|
|
|
|
|
const permissions = {
|
|
|
|
|
options: [
|
|
|
|
|
{ value: 'read-only', name: 'read-only' },
|
|
|
|
|
{ value: 'workspace-write', name: 'workspace-write' },
|
|
|
|
|
{ value: 'danger-full-access', name: 'danger-full-access' },
|
|
|
|
|
],
|
|
|
|
|
currentValue: 'read-only',
|
|
|
|
|
}
|
|
|
|
|
const { view } = bench({ permissions, command })
|
|
|
|
|
const trigger = view.getByLabelText(/^Access mode/) as HTMLButtonElement
|
|
|
|
|
// Title-case display is presentation only; the menu ids stay machine names.
|
|
|
|
|
expect(trigger.textContent).toBe('Read Only')
|
|
|
|
|
fireEvent.click(trigger)
|
|
|
|
|
const items = view.getAllByRole('menuitem')
|
|
|
|
|
expect(items.map(o => o.textContent)).toEqual(['Read Only', 'Workspace Write', 'Full access'])
|
|
|
|
|
fireEvent.click(items[1]!)
|
|
|
|
|
// Optimistic pick + disable until admission resolves (command stub resolves true).
|
|
|
|
|
const busy = view.getByLabelText(/^Access mode/) as HTMLButtonElement
|
|
|
|
|
expect(busy.textContent).toBe('Workspace Write')
|
|
|
|
|
expect(busy.disabled).toBe(true)
|
|
|
|
|
expect(command).toHaveBeenCalledWith('/permission workspace-write')
|
|
|
|
|
await act(async () => {})
|
|
|
|
|
expect((view.getByLabelText(/^Access mode/) as HTMLButtonElement).disabled).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('requires explicit risk acknowledgement before submitting Full access', async () => {
|
|
|
|
|
const command = vi.fn(() => Promise.resolve(true))
|
|
|
|
|
const permissions = {
|
|
|
|
|
options: [
|
|
|
|
|
{ value: 'workspace-write', name: 'workspace-write' },
|
|
|
|
|
@@ -458,20 +495,50 @@ describe('placeholder chrome and control seats', () => {
|
|
|
|
|
],
|
|
|
|
|
currentValue: 'workspace-write',
|
|
|
|
|
}
|
|
|
|
|
const { view } = bench({ permissions })
|
|
|
|
|
const trigger = view.getByLabelText(/^Access mode/) as HTMLButtonElement
|
|
|
|
|
// Title-case display is presentation only; the menu ids stay machine names.
|
|
|
|
|
expect(trigger.textContent).toBe('Workspace Write')
|
|
|
|
|
fireEvent.click(trigger)
|
|
|
|
|
const items = view.getAllByRole('menuitem')
|
|
|
|
|
expect(items.map(o => o.textContent)).toEqual(['Workspace Write', 'Danger Full Access'])
|
|
|
|
|
fireEvent.click(items[1]!)
|
|
|
|
|
// Optimistic pick + disable until admission resolves (command stub resolves true).
|
|
|
|
|
const busy = view.getByLabelText(/^Access mode/) as HTMLButtonElement
|
|
|
|
|
expect(busy.textContent).toBe('Danger Full Access')
|
|
|
|
|
expect(busy.disabled).toBe(true)
|
|
|
|
|
const { view } = bench({ permissions, command })
|
|
|
|
|
fireEvent.click(view.getByLabelText(/^Access mode/))
|
|
|
|
|
fireEvent.click(view.getByRole('menuitem', { name: 'Full access' }))
|
|
|
|
|
|
|
|
|
|
expect(command).not.toHaveBeenCalled()
|
|
|
|
|
expect(view.getByRole('dialog', { name: 'Enable Full access?' })).toBeTruthy()
|
|
|
|
|
const enable = view.getByRole('button', { name: 'Enable Full access' }) as HTMLButtonElement
|
|
|
|
|
expect(enable.disabled).toBe(true)
|
|
|
|
|
|
|
|
|
|
fireEvent.click(view.getByRole('checkbox', { name: 'I understand the risks and want to continue' }))
|
|
|
|
|
expect(enable.disabled).toBe(false)
|
|
|
|
|
fireEvent.click(enable)
|
|
|
|
|
|
|
|
|
|
expect(command).toHaveBeenCalledOnce()
|
|
|
|
|
expect(command).toHaveBeenCalledWith('/permission danger-full-access')
|
|
|
|
|
expect(view.queryByRole('dialog')).toBeNull()
|
|
|
|
|
expect((view.getByLabelText(/^Access mode/) as HTMLButtonElement).textContent).toBe('Full access')
|
|
|
|
|
await act(async () => {})
|
|
|
|
|
expect((view.getByLabelText(/^Access mode/) as HTMLButtonElement).disabled).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('cancels a Full access selection without changing permission and resets acknowledgement', () => {
|
|
|
|
|
const command = vi.fn(() => Promise.resolve(true))
|
|
|
|
|
const permissions = {
|
|
|
|
|
options: [
|
|
|
|
|
{ value: 'workspace-write', name: 'workspace-write' },
|
|
|
|
|
{ value: 'danger-full-access', name: 'danger-full-access' },
|
|
|
|
|
],
|
|
|
|
|
currentValue: 'workspace-write',
|
|
|
|
|
}
|
|
|
|
|
const { view } = bench({ permissions, command })
|
|
|
|
|
const openConfirmation = () => {
|
|
|
|
|
fireEvent.click(view.getByLabelText(/^Access mode/))
|
|
|
|
|
fireEvent.click(view.getByRole('menuitem', { name: 'Full access' }))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
openConfirmation()
|
|
|
|
|
fireEvent.click(view.getByRole('checkbox'))
|
|
|
|
|
fireEvent.click(view.getByRole('button', { name: 'Cancel' }))
|
|
|
|
|
expect(command).not.toHaveBeenCalled()
|
|
|
|
|
expect((view.getByLabelText(/^Access mode/) as HTMLButtonElement).textContent).toBe('Workspace Write')
|
|
|
|
|
|
|
|
|
|
openConfirmation()
|
|
|
|
|
expect((view.getByRole('checkbox') as HTMLInputElement).checked).toBe(false)
|
|
|
|
|
expect((view.getByRole('button', { name: 'Enable Full access' }) as HTMLButtonElement).disabled).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('a registered entry fills its seat and receives the locked owner prop', () => {
|
|
|
|
|
|