wip
fix: docs
This commit is contained in:
160 files changed
+2521
-1135
No files matched your search
@@ -13,6 +13,7 @@ import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SlashSource } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
import type { CommandServiceContract } from '../src/client/contract.ts'
|
||||
import type { PopupSelectInjected } from '../src/client/PopupSelectView.tsx'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { apply, CommandService, inject } from '../src/client/index.ts'
|
||||
|
||||
const sid = (k: string): SessionId => k as SessionId
|
||||
@@ -41,6 +42,7 @@ async function bench() {
|
||||
},
|
||||
})
|
||||
ctx.provide('conversation', {})
|
||||
ctx.provide('locale', new LocaleService(ctx))
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
const mint = (key: string) => {
|
||||
@@ -53,7 +55,7 @@ async function bench() {
|
||||
|
||||
describe('apply', () => {
|
||||
it('declares the services it binds', () => {
|
||||
expect(inject).toEqual(['slash', 'sessions', 'connection'])
|
||||
expect(inject).toEqual(['slash', 'sessions', 'connection', 'locale'])
|
||||
})
|
||||
|
||||
it('mounts ctx.command, registers the source and the overlay entry, and folds up on disposal', async () => {
|
||||
|
||||
@@ -14,6 +14,12 @@ import type { SelectOption } from '../src/client/contract.ts'
|
||||
import type { PopupSpec, TokenSegment } from '../src/client/popup.ts'
|
||||
import { PopupSelectController } from '../src/client/popup.ts'
|
||||
import { PopupSelectView } from '../src/client/PopupSelectView.tsx'
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import { zh } from '../src/client/locales.ts'
|
||||
|
||||
// The framework-injected t seat, stubbed over the zh dictionaries (the default locale).
|
||||
const t: Parameters<typeof PopupSelectView>[0]['t'] = makeTranslate(zh, commonZh)
|
||||
|
||||
// jsdom has no scrollIntoView; the view calls it on the highlighted row.
|
||||
const scrollIntoView = vi.fn()
|
||||
@@ -47,12 +53,12 @@ async function mountOpen(overrides: Partial<PopupSpec<string>> = {}, consumeResu
|
||||
const consume = vi.fn((_segment: TokenSegment) => consumeResult)
|
||||
const focusComposer = vi.fn()
|
||||
const popup = new PopupSelectController<string>({ consume, focusComposer })
|
||||
const view = render(<PopupSelectView popup={popup} />)
|
||||
const view = render(<PopupSelectView popup={popup} t={t} />)
|
||||
await act(async () => {
|
||||
popup.open('theme', spec(overrides), 'ctx-A', SEGMENT)
|
||||
await Promise.resolve()
|
||||
})
|
||||
return { popup, view, consume, focusComposer, search: screen.getByRole('textbox', { name: 'Filter options' }) }
|
||||
return { popup, view, consume, focusComposer, search: screen.getByRole('textbox', { name: '筛选选项' }) }
|
||||
}
|
||||
|
||||
function rowLabels(): string[] {
|
||||
@@ -62,13 +68,13 @@ function rowLabels(): string[] {
|
||||
describe('PopupSelectView', () => {
|
||||
it('renders null while closed, opens with focus in the search input', async () => {
|
||||
const popup = new PopupSelectController<string>({ consume: () => true, focusComposer: () => {} })
|
||||
const view = render(<PopupSelectView popup={popup} />)
|
||||
const view = render(<PopupSelectView popup={popup} t={t} />)
|
||||
expect(view.container.childElementCount).toBe(0)
|
||||
await act(async () => {
|
||||
popup.open('theme', spec(), 'ctx-A', SEGMENT)
|
||||
await Promise.resolve()
|
||||
})
|
||||
const search = screen.getByRole('textbox', { name: 'Filter options' })
|
||||
const search = screen.getByRole('textbox', { name: '筛选选项' })
|
||||
expect(document.activeElement).toBe(search)
|
||||
expect(rowLabels()).toEqual(['Dark', 'Light', 'Sepia'])
|
||||
})
|
||||
@@ -82,7 +88,7 @@ describe('PopupSelectView', () => {
|
||||
expect(options).toHaveBeenCalledTimes(1)
|
||||
act(() => { fireEvent.change(search, { target: { value: 'zzz' } }) })
|
||||
expect(screen.queryByRole('option')).toBeNull()
|
||||
expect(screen.queryByText('No options')).not.toBeNull()
|
||||
expect(screen.queryByText('无选项')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('ArrowUp/Down move the filtered highlight; ArrowLeft/Right are left to the native caret', async () => {
|
||||
@@ -110,13 +116,13 @@ describe('PopupSelectView', () => {
|
||||
it('caps the card height at the design maximum when the composer sits low enough', async () => {
|
||||
vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({ bottom: 800 } as DOMRect)
|
||||
await mountOpen()
|
||||
expect(screen.getByLabelText('/theme options').style.maxHeight).toBe('320px')
|
||||
expect(screen.getByLabelText('/theme 选项').style.maxHeight).toBe('320px')
|
||||
})
|
||||
|
||||
it('clamps the card height to the space above the composer minus the safe margin', async () => {
|
||||
vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({ bottom: 200 } as DOMRect)
|
||||
await mountOpen()
|
||||
expect(screen.getByLabelText('/theme options').style.maxHeight).toBe('188px')
|
||||
expect(screen.getByLabelText('/theme 选项').style.maxHeight).toBe('188px')
|
||||
})
|
||||
|
||||
it('Enter selects the highlighted row: onSelect, consume, close, focusComposer', async () => {
|
||||
@@ -148,7 +154,7 @@ describe('PopupSelectView', () => {
|
||||
const onSelect = vi.fn(() => new Promise<void>((resolve) => { release = resolve }))
|
||||
const { search, consume } = await mountOpen({ onSelect })
|
||||
await act(async () => { fireEvent.keyDown(search, { key: 'Enter' }) })
|
||||
expect(screen.queryByText('Applying…')).not.toBeNull()
|
||||
expect(screen.queryByText('正在应用…')).not.toBeNull()
|
||||
expect((search as HTMLInputElement).readOnly).toBe(true)
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(search, { key: 'Enter' })
|
||||
@@ -162,7 +168,7 @@ describe('PopupSelectView', () => {
|
||||
expect(consume).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('a failed options load shows the error with a Retry button that reloads', async () => {
|
||||
it('a failed options load shows the error with a retry button that reloads', async () => {
|
||||
let attempts = 0
|
||||
await mountOpen({
|
||||
options: () => {
|
||||
@@ -172,7 +178,7 @@ describe('PopupSelectView', () => {
|
||||
})
|
||||
expect(screen.getByRole('alert').textContent).toContain('directory down')
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Retry' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试' }))
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(attempts).toBe(2)
|
||||
@@ -183,7 +189,7 @@ describe('PopupSelectView', () => {
|
||||
const { search, consume } = await mountOpen({ onSelect: () => Promise.reject(new Error('host rejected')) })
|
||||
await act(async () => { fireEvent.keyDown(search, { key: 'Enter' }) })
|
||||
expect(screen.getByRole('alert').textContent).toContain('host rejected')
|
||||
expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull()
|
||||
expect(screen.queryByRole('button', { name: '重试' })).toBeNull()
|
||||
expect(consume).not.toHaveBeenCalled()
|
||||
expect(screen.getAllByRole('option').length).toBe(3)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user