Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,69 @@ describe('ViewsMenu', () => {
container.remove()
})

it('confirms before deleting a saved view', () => {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
const onDelete = vi.fn()

act(() => {
root.render(
<ViewsMenu
views={[PRIMARY_VIEW, SECOND_VIEW]}
activeViewId={PRIMARY_VIEW.id}
onSelect={vi.fn()}
onRename={vi.fn()}
onSetDefault={vi.fn()}
onDelete={onDelete}
onNewView={vi.fn()}
canEdit
/>
)
})
act(() => container.querySelector<HTMLButtonElement>('button[aria-label="Views"]')?.click())

const getDeleteButton = () =>
[...document.body.querySelectorAll<HTMLButtonElement>('button[aria-label="Delete"]')].find(
(button) => button.getAttribute('aria-disabled') !== 'true'
)
const getConfirmationDialog = () =>
[...document.body.querySelectorAll<HTMLElement>('[role="dialog"]')].find((dialog) =>
dialog.textContent?.includes('This action cannot be undone.')
)

act(() => getDeleteButton()?.click())

expect(onDelete).not.toHaveBeenCalled()
const firstDialog = getConfirmationDialog()
expect(firstDialog).toHaveTextContent('Delete View')
expect(firstDialog).toHaveTextContent('Second view')
expect(firstDialog).toHaveTextContent('This action cannot be undone.')

const cancelButton = [
...(firstDialog?.querySelectorAll<HTMLButtonElement>('button') ?? []),
].find((button) => button.textContent === 'Cancel')
act(() => cancelButton?.click())

expect(getConfirmationDialog()).toBeUndefined()
expect(onDelete).not.toHaveBeenCalled()

act(() => container.querySelector<HTMLButtonElement>('button[aria-label="Views"]')?.click())
act(() => getDeleteButton()?.click())

const dialog = getConfirmationDialog()
const confirmButton = [...(dialog?.querySelectorAll<HTMLButtonElement>('button') ?? [])].find(
(button) => button.textContent === 'Delete'
)
act(() => confirmButton?.click())

expect(onDelete).toHaveBeenCalledOnce()
expect(onDelete).toHaveBeenCalledWith(SECOND_VIEW.id)

act(() => root.unmount())
container.remove()
})

it('keeps the menu open when keyboard focus moves from the trigger to the default pin', () => {
vi.useFakeTimers()
const container = document.createElement('div')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { memo, useEffect, useRef, useState } from 'react'
import {
Button,
ChipChevronDown,
ChipConfirmModal,
chipContentLabelClass,
chipVariants,
cn,
Expand Down Expand Up @@ -62,9 +63,11 @@ export const ViewsMenu = memo(function ViewsMenu({
canEdit,
}: ViewsMenuProps) {
const [open, setOpen] = useState(false)
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)

const { activeView, defaultView } = resolveTableViewSelection(views, activeViewId)
const deleteTarget = views.find((view) => view.id === deleteTargetId)
const hasDefaultView = defaultView !== null
const label = activeView?.name ?? ALL_ROWS_VIEW_LABEL

Expand Down Expand Up @@ -176,7 +179,7 @@ export const ViewsMenu = memo(function ViewsMenu({
disabledReason: view.isDefault
? 'Default view cannot be deleted'
: undefined,
onClick: () => runAndClose(() => onDelete(view.id)),
onClick: () => runAndClose(() => setDeleteTargetId(view.id)),
},
]
: undefined
Expand All @@ -199,6 +202,28 @@ export const ViewsMenu = memo(function ViewsMenu({
</>
)}
</PopoverContent>
<ChipConfirmModal
open={deleteTargetId !== null}
onOpenChange={(nextOpen) => {
if (!nextOpen) setDeleteTargetId(null)
}}
srTitle='Delete View'
title='Delete View'
text={[
'Are you sure you want to delete ',
{ text: deleteTarget?.name ?? 'this view', bold: true },
'? ',
{ text: 'This action cannot be undone.', error: true },
]}
confirm={{
label: 'Delete',
onClick: () => {
if (!deleteTargetId) return
onDelete(deleteTargetId)
setDeleteTargetId(null)
},
}}
/>
</Popover>
)
})
Expand Down
Loading