fix(workspace): remove transient duplicate warning

This commit is contained in:
NI0317
2026-07-27 15:52:35 +08:00
parent 7bd96af5eb
commit 4701373fc2
9 changed files with 155 additions and 17 deletions
@@ -461,7 +461,7 @@ describe('WorkspaceBrowser', () => {
it('confirms Workspace deletion, explains retention, and blocks duplicate submission', async () => {
let resolveDelete!: () => void
const deleteWorkspace = vi.fn(() => new Promise<void>((resolve) => { resolveDelete = resolve }))
mount({
const browser = mount({
useWorkspaces: hook(workspaceState([workspace('alpha', ['session'], 'Alpha')])),
deleteWorkspace,
})
@@ -484,6 +484,11 @@ describe('WorkspaceBrowser', () => {
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
expect(screen.getByRole('dialog', { name: 'Delete workspace' })).toBeTruthy()
await act(async () => { resolveDelete() })
// RPC success alone does not close: the component waits until its
// useWorkspaces projection has committed the removal, preventing a stale
// duplicate-name frame from leaking into the next create gesture.
expect(screen.getByRole('dialog', { name: 'Delete workspace' })).toBeTruthy()
rerender(browser, { useWorkspaces: hook(workspaceState([])) })
expect(screen.queryByRole('dialog', { name: 'Delete workspace' })).toBeNull()
})
@@ -35,18 +35,25 @@ function anchor(): { current: HTMLElement } {
function mount(items: readonly WorkspaceView[] = [workspace('alpha', 'Alpha')], createWorkspace = vi.fn()) {
const onPick = vi.fn()
const onClose = vi.fn()
const view = render(
const anchorRef = anchor()
const renderPicker = (nextItems: readonly WorkspaceView[]) => (
<WorkspacePicker
open
anchorRef={anchor()}
anchorRef={anchorRef}
useSessions={hook(sessions)}
useWorkspaces={hook(workspaceState(items))}
useWorkspaces={hook(workspaceState(nextItems))}
onPick={onPick}
onClose={onClose}
createWorkspace={createWorkspace}
/>,
/>
)
return { view, onPick, onClose, createWorkspace }
const view = render(
renderPicker(items),
)
return {
view, onPick, onClose, createWorkspace,
rerenderItems: (nextItems: readonly WorkspaceView[]) => { view.rerender(renderPicker(nextItems)) },
}
}
function chooseCreateItem(name: 'Use an existing folder' | 'Create a new workspace'): void {
@@ -106,6 +113,22 @@ describe('WorkspacePicker', () => {
expect(b.createWorkspace).not.toHaveBeenCalled()
})
it('does not flash a duplicate alert when the successful create frame arrives before its unary response', async () => {
let resolve!: (workspace: WorkspaceView) => void
const pending = new Promise<WorkspaceView>((settle) => { resolve = settle })
const created = workspace('fresh', 'same-name')
const b = mount([], vi.fn(() => pending))
chooseCreateItem('Create a new workspace')
fireEvent.change(screen.getByLabelText('New workspace name'), { target: { value: 'same-name' } })
fireEvent.click(screen.getByRole('button', { name: 'Create workspace' }))
b.rerenderItems([created])
expect(screen.getByRole('status').textContent).toBe('Creating workspace…')
expect(screen.queryByRole('alert')).toBeNull()
await act(async () => { resolve(created); await pending })
expect(b.onPick).toHaveBeenCalledWith(created.workspaceId)
})
it('exposes creation phase and error text while retaining the modal for retry', async () => {
let reject!: (reason: unknown) => void
const pending = new Promise<WorkspaceView>((_resolve, rejectPromise) => { reject = rejectPromise })