Merge remote-tracking branch 'origin/master' into web-permission-sandbox-merge-master

This commit is contained in:
imccyu
2026-07-29 11:16:49 +08:00
222 changed files with 8347 additions and 1465 deletions

View File

@@ -1,7 +1,7 @@
/** Test-owned workspaces face: the renderer standard-kit observable plus recorded actions. */
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type {
IWorkspaces, SessionId, SnapshotStore, WorkspaceId, WorkspaceListState, WorkspaceView,
DirectoryListing, IWorkspaces, SessionId, SnapshotStore, WorkspaceId, WorkspaceListState, WorkspaceView,
} from '@deepseek-ai/dsh-client-runtime/client'
import { workspaceListState } from './fixtures.ts'
import type { Stabilizer } from './fixtures.ts'
@@ -109,6 +109,48 @@ export class TestWorkspaces implements IWorkspaces {
return null
}
/**
* Browse listing (recorded). The default serves an empty home level; stub
* to shape a tree.
* @param path - absolute directory to list; absent lists the home level.
* @returns the level's listing.
*/
async listDirectory(path?: string, signal?: AbortSignal): Promise<DirectoryListing> {
// The signal is recorded and forwarded like the production face passes
// it to the wire, so cancellation integration tests can observe or
// reject on a superseded scan.
this.calls.push({ method: 'listDirectory', args: [path, signal] })
const stub = this.stubs.get('listDirectory')
if (stub !== undefined) return await (stub(path, signal) as Promise<DirectoryListing>)
// The chain runs root-to-target inclusive, per the DirectoryListing
// contract — a bare root crumb would mislabel the level in browsers
// driven by this double.
return {
path: '/home/test',
home: '/home/test',
crumbs: [
{ name: '/', path: '/', hidden: false },
{ name: 'home', path: '/home', hidden: false },
{ name: 'test', path: '/home/test', hidden: false },
],
entries: [],
truncated: false,
}
}
/**
* Browse child creation (recorded). The default joins parent and name.
* @param path - absolute existing parent directory.
* @param name - single path segment.
* @returns the created directory's absolute path.
*/
async createDirectory(path: string, name: string): Promise<string> {
this.calls.push({ method: 'createDirectory', args: [path, name] })
const stub = this.stubs.get('createDirectory')
if (stub !== undefined) return await (stub(path, name) as Promise<string>)
return `${path}/${name}`
}
/**
* Rename a Workspace (recorded). The default echoes a minimal view.
* @param workspaceId - target workspace.

View File

@@ -322,6 +322,32 @@ describe('workspaces', () => {
expect(stub).toHaveBeenCalledOnce()
await runtime.dispose()
})
it('records the browse calls: listDirectory serves an empty home, createDirectory joins, stubs override', async () => {
const runtime = await runtimeWithFrame()
// Defaults: an empty home level and parent/name joining.
await expect(runtime.workspaces.listDirectory()).resolves.toMatchObject({ path: '/home/test', entries: [] })
await expect(runtime.workspaces.listDirectory('/home/test')).resolves.toMatchObject({ path: '/home/test' })
await expect(runtime.workspaces.createDirectory('/home/test', 'fresh')).resolves.toBe('/home/test/fresh')
// The recorded signal seat mirrors the production face (undefined here;
// cancellation tests pass and observe a real one).
expect(runtime.workspaces.calls).toEqual([
{ method: 'listDirectory', args: [undefined, undefined] },
{ method: 'listDirectory', args: ['/home/test', undefined] },
{ method: 'createDirectory', args: ['/home/test', 'fresh'] },
])
// Stubs replace the defaults like every sibling method.
const listing = { path: '/x', home: '/x', crumbs: [], entries: [] }
const listStub = vi.fn(() => Promise.resolve(listing as never))
runtime.workspaces.stub('listDirectory', listStub)
runtime.workspaces.stub('createDirectory', vi.fn(() => Promise.resolve('/x/made' as never)))
const scan = new AbortController()
await expect(runtime.workspaces.listDirectory('/x', scan.signal)).resolves.toBe(listing)
// The stub receives the signal too, like the production face gives the wire.
expect(listStub).toHaveBeenLastCalledWith('/x', scan.signal)
await expect(runtime.workspaces.createDirectory('/x', 'made')).resolves.toBe('/x/made')
await runtime.dispose()
})
})
describe('feature mount and disposal', () => {