Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser
# Conflicts: # .agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.i18n.yaml # packages/host/directory-picker-browse/README.i18n.yaml # packages/host/directory-picker-browse/package.json # pnpm-lock.yaml
This commit is contained in:
@@ -130,6 +130,29 @@ describe('directory-picker-browse client half', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('rolls back the zh dictionary when a rival already owns the namespace en slot', async () => {
|
||||
const b = await bench()
|
||||
b.declare()
|
||||
const locale = b.ctx.get('locale') as LocaleService
|
||||
const disposeRival = locale.register('directory-browser', 'en', { 'browser.title': 'rival' })
|
||||
const rejections: unknown[] = []
|
||||
const onUnhandled = (reason: unknown): void => { rejections.push(reason) }
|
||||
// cordis re-raises the apply throw as a late rejection (installFailLoud's contract).
|
||||
process.on('unhandledRejection', onUnhandled)
|
||||
try {
|
||||
const fiber = b.ctx.plugin({ inject: [...inject], apply })
|
||||
await expect(fiber.await()).rejects.toThrow(/already has locale/)
|
||||
// The zh registration rolled back with the failure: once the rival
|
||||
// leaves, a fresh registrant owns the whole namespace again.
|
||||
disposeRival()
|
||||
const disposeZh = locale.register('directory-browser', 'zh', { 'browser.title': '空闲' })
|
||||
disposeZh()
|
||||
} finally {
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
process.off('unhandledRejection', onUnhandled)
|
||||
}
|
||||
})
|
||||
|
||||
it('registers the dialog dictionaries and binds this package namespace', async () => {
|
||||
const b = await bench()
|
||||
b.declare()
|
||||
|
||||
@@ -7,7 +7,8 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { DirectoryPickerError } from '@deepseek-ai/dsh-host-directory-picker'
|
||||
import type { DirectoryPickerBrowseCapability } from '@deepseek-ai/dsh-host-directory-picker'
|
||||
import BrowseDirectoryPicker, { fullyQualified } from '../src/index.ts'
|
||||
import BrowseDirectoryPicker, { boundedInsert, fullyQualified } from '../src/index.ts'
|
||||
import type { ListingCandidate } from '../src/index.ts'
|
||||
|
||||
let root: string
|
||||
let capability: DirectoryPickerBrowseCapability
|
||||
@@ -21,6 +22,13 @@ beforeAll(async () => {
|
||||
await writeFile(join(root, 'notes.txt'), 'not a directory')
|
||||
await symlink(join(root, 'projects'), join(root, 'linked'), 'junction')
|
||||
await symlink(join(root, 'gone'), join(root, 'broken'), 'junction')
|
||||
try {
|
||||
await symlink(join(root, 'notes.txt'), join(root, 'file-link'))
|
||||
} catch {
|
||||
// Windows denies unprivileged file symlinks; the file-link row only
|
||||
// feeds the POSIX lanes' coverage of the symlink-to-file arm, and every
|
||||
// assertion below expects it to be filtered out anyway.
|
||||
}
|
||||
|
||||
const ctx = new Context()
|
||||
const fiber = ctx.plugin(BrowseDirectoryPicker)
|
||||
@@ -63,11 +71,31 @@ describe('BrowseDirectoryPicker', () => {
|
||||
const exact = await bounded.list(join(root, 'projects'))
|
||||
expect(exact.entries.map(entry => entry.name)).toEqual(['harness'])
|
||||
expect(exact.truncated).toBe(false)
|
||||
// A level that fits the window but exceeds the bound (two rows, bound
|
||||
// one): the in-window extra row proves the cut without any eviction.
|
||||
await mkdir(join(root, 'projects', 'harness', 'a'))
|
||||
await mkdir(join(root, 'projects', 'harness', 'b'))
|
||||
const inWindow = await bounded.list(join(root, 'projects', 'harness'))
|
||||
expect(inWindow.entries.map(entry => entry.name)).toEqual(['a'])
|
||||
expect(inWindow.truncated).toBe(true)
|
||||
} finally {
|
||||
await fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('boundedInsert keeps the window name-sorted and bounded, reporting evictions', () => {
|
||||
const candidate = (name: string): ListingCandidate => ({ name, isDirectory: true, isSymbolicLink: false })
|
||||
const window: ListingCandidate[] = []
|
||||
expect(boundedInsert(window, candidate('m'), 2)).toBe(false)
|
||||
expect(boundedInsert(window, candidate('z'), 2)).toBe(false)
|
||||
// A smaller name lands in place and pushes the current largest out.
|
||||
expect(boundedInsert(window, candidate('a'), 2)).toBe(true)
|
||||
expect(window.map(entry => entry.name)).toEqual(['a', 'm'])
|
||||
// A name beyond the window's tail enters last and leaves immediately.
|
||||
expect(boundedInsert(window, candidate('t'), 2)).toBe(true)
|
||||
expect(window.map(entry => entry.name)).toEqual(['a', 'm'])
|
||||
})
|
||||
|
||||
it('reports the ancestry as jump-target crumbs ending at the listed directory', async () => {
|
||||
const listing = await capability.list(join(root, 'projects'))
|
||||
const tail = listing.crumbs.at(-1)!
|
||||
|
||||
Reference in New Issue
Block a user