fix: DEL key deletes nodes and deletion is now undoable
- Add 'Delete' to deleteKeyCode so both Backspace and Delete remove nodes - Call snapshotHistory() in onBeforeDelete (keyboard) and in DetailPanel handleDelete (button) so deletions can be undone with Ctrl+Z
This commit is contained in:
@@ -15,7 +15,7 @@ type SvcForm = { port: string; protocol: 'tcp' | 'udp'; service_name: string }
|
||||
const EMPTY_FORM: SvcForm = { port: '', protocol: 'tcp', service_name: '' }
|
||||
|
||||
export function DetailPanel({ onEdit }: DetailPanelProps) {
|
||||
const { nodes, selectedNodeId, setSelectedNode, deleteNode, updateNode } = useCanvasStore()
|
||||
const { nodes, selectedNodeId, setSelectedNode, deleteNode, updateNode, snapshotHistory } = useCanvasStore()
|
||||
const node = nodes.find((n) => n.id === selectedNodeId)
|
||||
|
||||
const [addingForNode, setAddingForNode] = useState<string | null>(null)
|
||||
@@ -35,6 +35,7 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
||||
|
||||
const handleDelete = () => {
|
||||
if (confirm(`Delete "${data.label}"?`)) {
|
||||
snapshotHistory()
|
||||
deleteNode(node.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ function setupStore(nodeData: Partial<NodeData> = {}) {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
})
|
||||
|
||||
@@ -125,6 +127,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode,
|
||||
deleteNode: vi.fn(),
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
fireEvent.click(screen.getByLabelText('Close panel'))
|
||||
@@ -139,33 +142,39 @@ describe('DetailPanel', () => {
|
||||
expect(onEdit).toHaveBeenCalledWith('n1')
|
||||
})
|
||||
|
||||
it('calls deleteNode when delete confirmed', () => {
|
||||
it('calls snapshotHistory then deleteNode when delete confirmed', () => {
|
||||
const deleteNode = vi.fn()
|
||||
const snapshotHistory = vi.fn()
|
||||
vi.mocked(canvasStore.useCanvasStore).mockReturnValue({
|
||||
nodes: [makeNode({ label: 'My Server' })],
|
||||
selectedNodeId: 'n1',
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode,
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory,
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
fireEvent.click(screen.getByLabelText('Delete node'))
|
||||
expect(snapshotHistory).toHaveBeenCalledOnce()
|
||||
expect(deleteNode).toHaveBeenCalledWith('n1')
|
||||
})
|
||||
|
||||
it('does not call deleteNode when delete is cancelled', () => {
|
||||
it('does not call deleteNode or snapshotHistory when delete is cancelled', () => {
|
||||
const deleteNode = vi.fn()
|
||||
const snapshotHistory = vi.fn()
|
||||
vi.mocked(canvasStore.useCanvasStore).mockReturnValue({
|
||||
nodes: [makeNode({})],
|
||||
selectedNodeId: 'n1',
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode,
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory,
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(false)
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
fireEvent.click(screen.getByLabelText('Delete node'))
|
||||
expect(snapshotHistory).not.toHaveBeenCalled()
|
||||
expect(deleteNode).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -186,6 +195,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode,
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
fireEvent.click(screen.getByText('Add'))
|
||||
@@ -207,6 +217,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode,
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
fireEvent.click(screen.getByTitle('Remove service'))
|
||||
@@ -221,6 +232,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode: vi.fn(),
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
expect(() => render(<DetailPanel onEdit={vi.fn()} />)).not.toThrow()
|
||||
})
|
||||
@@ -249,6 +261,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode,
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
@@ -271,6 +284,7 @@ describe('DetailPanel', () => {
|
||||
setSelectedNode: vi.fn(),
|
||||
deleteNode: vi.fn(),
|
||||
updateNode,
|
||||
snapshotHistory: vi.fn(),
|
||||
} as unknown as ReturnType<typeof canvasStore.useCanvasStore>)
|
||||
|
||||
render(<DetailPanel onEdit={vi.fn()} />)
|
||||
|
||||
Reference in New Issue
Block a user