mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
fix(repos): keep active clone dialog open on outside clicks
Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel. Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430 Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
This commit is contained in:
co-authored by
Neil
Nawapat Buakoet
parent
df9acd83d4
commit
752dbc9ef8
@@ -283,6 +283,7 @@ export default React.memo(function AddRepoDialog({
|
||||
isOpen={isOpen}
|
||||
step={step}
|
||||
isAdding={isAdding}
|
||||
isCloning={isCloning}
|
||||
onBack={handleBack}
|
||||
onCloseAutoFocus={hosted?.onCloseAutoFocus}
|
||||
onOpenChange={handleOpenChange}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useState } from 'react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { DialogDescription, DialogTitle } from '@/components/ui/dialog'
|
||||
import { AddRepoDialogChrome } from './AddRepoDialogChrome'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
function CloneDialog({
|
||||
isCloning,
|
||||
onOpenChange
|
||||
}: {
|
||||
isCloning: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
return (
|
||||
<AddRepoDialogChrome
|
||||
isCloning={isCloning}
|
||||
isAdding={false}
|
||||
isOpen={isOpen}
|
||||
step="clone"
|
||||
onBack={() => {}}
|
||||
onOpenChange={(open) => {
|
||||
onOpenChange(open)
|
||||
setIsOpen(open)
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Clone from URL</DialogTitle>
|
||||
<DialogDescription>Clone progress</DialogDescription>
|
||||
</AddRepoDialogChrome>
|
||||
)
|
||||
}
|
||||
|
||||
function backdrop(): Element {
|
||||
const overlay = document.querySelector('[data-slot="dialog-overlay"]')
|
||||
if (!overlay) {
|
||||
throw new Error('Missing dialog backdrop')
|
||||
}
|
||||
return overlay
|
||||
}
|
||||
|
||||
describe('AddRepoDialogChrome dismissal', () => {
|
||||
it('keeps an in-flight clone open after a backdrop click', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onOpenChange = vi.fn()
|
||||
render(<CloneDialog isCloning onOpenChange={onOpenChange} />)
|
||||
|
||||
await user.click(backdrop())
|
||||
|
||||
expect(onOpenChange).not.toHaveBeenCalled()
|
||||
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).not.toBeNull()
|
||||
})
|
||||
|
||||
it('allows backdrop dismissal when no clone is running', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onOpenChange = vi.fn()
|
||||
render(<CloneDialog isCloning={false} onOpenChange={onOpenChange} />)
|
||||
|
||||
await user.click(backdrop())
|
||||
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).toBeNull()
|
||||
})
|
||||
|
||||
it('allows backdrop dismissal after the clone settles', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onOpenChange = vi.fn()
|
||||
const view = render(<CloneDialog isCloning onOpenChange={onOpenChange} />)
|
||||
await user.click(backdrop())
|
||||
expect(onOpenChange).not.toHaveBeenCalled()
|
||||
|
||||
view.rerender(<CloneDialog isCloning={false} onOpenChange={onOpenChange} />)
|
||||
await user.click(backdrop())
|
||||
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).toBeNull()
|
||||
})
|
||||
|
||||
it.each(['Escape', 'Close'])(
|
||||
'keeps explicit %s dismissal available during a clone',
|
||||
async (action) => {
|
||||
const user = userEvent.setup()
|
||||
const onOpenChange = vi.fn()
|
||||
render(<CloneDialog isCloning onOpenChange={onOpenChange} />)
|
||||
|
||||
await (action === 'Escape'
|
||||
? user.keyboard('{Escape}')
|
||||
: user.click(screen.getByRole('button', { name: 'Close' })))
|
||||
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).toBeNull()
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog'
|
||||
import { preventOutsideDismissWhenDirty } from '@/lib/outside-dismiss-guard'
|
||||
import type { AddRepoDialogStep } from './add-repo-dialog-types'
|
||||
import { AddRepoStepIndicator } from './AddRepoStepIndicator'
|
||||
|
||||
export function AddRepoDialogChrome({
|
||||
children,
|
||||
isAdding,
|
||||
isCloning,
|
||||
isOpen,
|
||||
onBack,
|
||||
onCloseAutoFocus,
|
||||
@@ -14,6 +16,7 @@ export function AddRepoDialogChrome({
|
||||
}: {
|
||||
children: ReactNode
|
||||
isAdding: boolean
|
||||
isCloning: boolean
|
||||
isOpen: boolean
|
||||
onBack: () => void
|
||||
onCloseAutoFocus?: (event: Event) => void
|
||||
@@ -24,6 +27,8 @@ export function AddRepoDialogChrome({
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
onCloseAutoFocus={onCloseAutoFocus}
|
||||
// Closing the dialog aborts the clone; require an explicit close while it runs.
|
||||
onInteractOutside={preventOutsideDismissWhenDirty(() => isCloning)}
|
||||
className={`min-w-0 overflow-hidden sm:max-w-lg [&>*]:min-w-0 ${
|
||||
step === 'nested' ? 'max-h-[calc(100vh-2rem)] grid-rows-[auto_auto_minmax(0,1fr)]' : ''
|
||||
}`}
|
||||
|
||||
Reference in New Issue
Block a user