// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react' import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client' import { PermissionRow, type PermissionRowProps } from '../src/client/PermissionRow.tsx' import { en } from '../src/client/locales.ts' import { PermissionSettingsController } from '../src/client/settings-store.ts' afterEach(cleanup) const SCHEMA = { uid: 5, refs: { 1: { type: 'const', value: 'read-only' }, 2: { type: 'const', value: 'workspace-write' }, 3: { type: 'const', value: 'danger-full-access' }, 4: { type: 'union', list: [1, 2, 3] }, 5: { type: 'object', dict: { defaultPreset: 4 } }, }, } function view(defaultPreset: string, revision = 0): SettingsNamespaceView { return { ns: 'permission', schema: SCHEMA, value: { defaultPreset }, base: { defaultPreset: 'read-only' }, applies: 'live', secrets: [], revision, } } function ok(value: T) { return { rpcId: 'test', result: { ok: true as const, value } } } const dictionary: Record = en const t: PermissionRowProps['t'] = key => dictionary[key] ?? key const runtime = { useSessions: (() => { throw new Error('unused') }) as never, useWorkspaces: (() => { throw new Error('unused') }) as never, } function mount(controller: PermissionSettingsController) { return render( controller.load()} select={preset => controller.select(preset)} usePermission={bindSnapshotSelector(controller.store)} t={t} />, ) } describe('PermissionRow', () => { it('loads the descriptor, opens the menu, and selects a new default', async () => { const mutate = vi.fn(() => Promise.resolve(ok(view('workspace-write', 1)))) const controller = new PermissionSettingsController({ settings: { describe: () => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [view('read-only')] })), mutate, } as never, }) mount(controller) const button = await screen.findByRole('button', { name: 'Read Only' }) expect(button.getAttribute('aria-expanded')).toBe('false') fireEvent.click(button) expect(button.getAttribute('aria-expanded')).toBe('true') fireEvent.keyDown(document, { key: 'Escape' }) await waitFor(() => { expect(button.getAttribute('aria-expanded')).toBe('false') }) fireEvent.click(button) fireEvent.click(button) expect(button.getAttribute('aria-expanded')).toBe('false') fireEvent.click(button) fireEvent.click(screen.getByRole('menuitem', { name: 'Read Only' })) expect(mutate).not.toHaveBeenCalled() fireEvent.click(button) fireEvent.click(screen.getByRole('menuitem', { name: 'Workspace Write' })) await screen.findByRole('button', { name: 'Workspace Write' }) 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, hasDocument: false, 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() fireEvent.click(screen.getByRole('button', { name: 'Cancel' })) expect(screen.queryByRole('dialog', { name: 'Enable Full access?' })).toBeNull() fireEvent.click(screen.getByRole('button', { name: 'Read Only' })) fireEvent.click(screen.getByRole('menuitem', { name: 'Full access' })) 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: { describe: () => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [] })), mutate: vi.fn(), } as never, }) const rendered = mount(absent) await waitFor(() => { expect(rendered.container.textContent).toBe('') }) rendered.unmount() const readonly = new PermissionSettingsController({ settings: { describe: () => Promise.resolve(ok({ writable: false, hasDocument: false, namespaces: [view('read-only')] })), mutate: vi.fn(), } as never, }) mount(readonly) expect((await screen.findByRole('button', { name: 'Read Only' })).hasAttribute('disabled')).toBe(true) }) it('shows loading and a contained write error', async () => { const describe = Promise.withResolvers>>() const controller = new PermissionSettingsController({ settings: { describe: () => describe.promise, mutate: () => Promise.resolve({ rpcId: 'test', result: { ok: false as const, error: { code: 'settings-conflict', message: 'changed elsewhere', details: {} }, }, }), } as never, }) mount(controller) expect((await screen.findByRole('button', { name: 'Loading' })).hasAttribute('disabled')).toBe(true) describe.resolve(ok({ writable: true, hasDocument: false, namespaces: [view('read-only')] })) const button = await screen.findByRole('button', { name: 'Read Only' }) fireEvent.click(button) fireEvent.click(screen.getByRole('menuitem', { name: 'Workspace Write' })) expect((await screen.findByRole('alert')).textContent).toBe('changed elsewhere') }) })