merge master and address permission settings review
This commit is contained in:
@@ -14,8 +14,11 @@ import { SlotsService, type SessionId } from '@deepseek-ai/dsh-client-runtime/cl
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { CommandDecoration } from '@deepseek-ai/dsh-client-ui-command/client'
|
||||
import type { PermissionSelect } from '@deepseek-ai/dsh-permission/client'
|
||||
import { PermissionRow } from '../src/client/PermissionRow.tsx'
|
||||
import {
|
||||
PermissionRow, type PermissionRowInjected,
|
||||
} from '../src/client/PermissionRow.tsx'
|
||||
import { apply, inject } from '../src/client/index.ts'
|
||||
import { accessEn } from '../src/client/locales.ts'
|
||||
|
||||
const sid = (k: string): SessionId => k as SessionId
|
||||
|
||||
@@ -32,6 +35,7 @@ async function bench() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SlotsService)
|
||||
const locale = new LocaleService(ctx)
|
||||
locale.setLocale('en')
|
||||
ctx.provide('locale', locale)
|
||||
ctx.slots.register({
|
||||
name: 'root',
|
||||
@@ -96,9 +100,10 @@ describe('ui-permission browser plugin', () => {
|
||||
expect(c.ui.kind).toBe('popupSelect')
|
||||
const row = b.permissionRow()!
|
||||
expect(row.options).toEqual({ id: 'permission', order: -20 })
|
||||
const injected = row.inject?.()
|
||||
expect(injected?.controller).toBeDefined()
|
||||
expect(typeof injected?.useSnapshot).toBe('function')
|
||||
const injected = row.inject?.() as PermissionRowInjected | undefined
|
||||
expect(injected?.hooks.permission).toBeDefined()
|
||||
expect(typeof injected?.load).toBe('function')
|
||||
expect(typeof injected?.select).toBe('function')
|
||||
})
|
||||
|
||||
it('availability follows the projection key; options mark the current value active and exclude custom', async () => {
|
||||
@@ -116,7 +121,14 @@ describe('ui-permission browser plugin', () => {
|
||||
expect(again.find(option => option.id === 'workspace-write')?.active).toBe(true)
|
||||
expect(again.find(option => option.id === 'read-only')?.detail).toBe('Reads only.')
|
||||
// Kebab-case names title-case; non-kebab host-configured names pass through.
|
||||
expect(again.map(option => option.label)).toEqual(['Read Only', 'Workspace Write', 'Danger Full Access'])
|
||||
expect(again.map(option => option.label)).toEqual(['Read Only', 'Workspace Write', 'Full access'])
|
||||
expect(again.find(option => option.id === 'danger-full-access')?.confirmation).toEqual({
|
||||
title: 'Enable Full access?',
|
||||
description: accessEn['confirm.description'],
|
||||
acknowledgeLabel: 'I understand the risks and want to continue',
|
||||
cancelLabel: 'Cancel',
|
||||
confirmLabel: 'Enable Full access',
|
||||
})
|
||||
b.values.set(sid('s1'), { ...SELECT, options: [{ value: 'plain', name: 'Ask Every Time' }] })
|
||||
const passthrough = await c.ui.options(proj, new AbortController().signal)
|
||||
expect(passthrough[0]?.label).toBe('Ask Every Time')
|
||||
|
||||
@@ -10,12 +10,13 @@ import { PermissionSettingsController } from '../src/client/settings-store.ts'
|
||||
afterEach(cleanup)
|
||||
|
||||
const SCHEMA = {
|
||||
uid: 4,
|
||||
uid: 5,
|
||||
refs: {
|
||||
1: { type: 'const', value: 'read-only' },
|
||||
2: { type: 'const', value: 'workspace-write' },
|
||||
3: { type: 'union', list: [1, 2] },
|
||||
4: { type: 'object', dict: { defaultPreset: 3 } },
|
||||
3: { type: 'const', value: 'danger-full-access' },
|
||||
4: { type: 'union', list: [1, 2, 3] },
|
||||
5: { type: 'object', dict: { defaultPreset: 4 } },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,8 +47,9 @@ function mount(controller: PermissionSettingsController) {
|
||||
return render(
|
||||
<PermissionRow
|
||||
{...runtime}
|
||||
controller={controller}
|
||||
useSnapshot={bindSnapshotSelector(controller.store)}
|
||||
load={() => controller.load()}
|
||||
select={preset => controller.select(preset)}
|
||||
usePermission={bindSnapshotSelector(controller.store)}
|
||||
t={t}
|
||||
/>,
|
||||
)
|
||||
@@ -78,6 +80,27 @@ describe('PermissionRow', () => {
|
||||
expect(mutate).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('requires explicit acknowledgement before saving Full access', async () => {
|
||||
const mutate = vi.fn(() => Promise.resolve(ok(view('danger-full-access', 1))))
|
||||
const controller = new PermissionSettingsController({
|
||||
settings: {
|
||||
describe: () => Promise.resolve(ok({ writable: true, namespaces: [view('read-only')] })),
|
||||
mutate,
|
||||
} as never,
|
||||
})
|
||||
mount(controller)
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Read Only' }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Full access' }))
|
||||
expect(mutate).not.toHaveBeenCalled()
|
||||
const dialog = screen.getByRole('dialog', { name: 'Enable Full access?' })
|
||||
const enable = screen.getByRole('button', { name: 'Enable Full access' })
|
||||
expect((enable as HTMLButtonElement).disabled).toBe(true)
|
||||
fireEvent.click(screen.getByRole('checkbox'))
|
||||
fireEvent.click(enable)
|
||||
await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
|
||||
expect(dialog.isConnected).toBe(false)
|
||||
})
|
||||
|
||||
it('hides an unavailable namespace and disables a read-only provider', async () => {
|
||||
const absent = new PermissionSettingsController({
|
||||
settings: {
|
||||
|
||||
Reference in New Issue
Block a user