fix: scan no longer triggers before user confirms in ScanConfigModal

This commit is contained in:
Pouzor
2026-04-05 14:35:10 +02:00
parent 07da498d18
commit 4d8bb246f1
5 changed files with 19 additions and 32 deletions
+6 -2
View File
@@ -44,7 +44,7 @@ export default function App() {
const [themeModalOpen, setThemeModalOpen] = useState(false)
const [searchOpen, setSearchOpen] = useState(false)
const [sidebarForceView, setSidebarForceView] = useState<'pending' | undefined>(undefined)
const [sidebarForceView, setSidebarForceView] = useState<'pending' | 'history' | undefined>(undefined)
const [highlightPendingId, setHighlightPendingId] = useState<string | undefined>(undefined)
const [shortcutsOpen, setShortcutsOpen] = useState(false)
const [addNodeOpen, setAddNodeOpen] = useState(false)
@@ -454,7 +454,11 @@ export default function App() {
<ScanConfigModal
open={scanConfigOpen}
onClose={() => setScanConfigOpen(false)}
onScanNow={() => toast.success('Scan triggered')}
onScanNow={() => {
toast.success('Network scan started — check Scan History for results')
setSidebarForceView(undefined)
setTimeout(() => setSidebarForceView('history'), 0)
}}
/>
)}
@@ -24,29 +24,22 @@ export function ScanConfigModal({ open, onClose, onScanNow }: ScanConfigModalPro
.catch(() => {/* use defaults */})
}, [open])
const handleSave = async () => {
const handleScanNow = async () => {
const cleaned = ranges.map((r) => r.trim()).filter(Boolean)
if (cleaned.length === 0) { toast.error('Add at least one IP range'); return }
setSaving(true)
try {
await scanApi.saveConfig({ ranges: cleaned })
toast.success('Scan config saved')
await scanApi.trigger()
onScanNow()
onClose()
} catch {
toast.error('Failed to save config')
toast.error('Failed to start scan')
} finally {
setSaving(false)
}
}
const handleScanNow = async () => {
const cleaned = ranges.map((r) => r.trim()).filter(Boolean)
if (cleaned.length === 0) { toast.error('Add at least one IP range'); return }
await handleSave()
onScanNow()
onClose()
}
return (
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="bg-[#161b22] border-border max-w-md">
@@ -6,6 +6,7 @@ vi.mock('@/api/client', () => ({
scanApi: {
getConfig: vi.fn(),
saveConfig: vi.fn(),
trigger: vi.fn(),
},
}))
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } }))
@@ -20,6 +21,8 @@ describe('ScanConfigModal', () => {
vi.mocked(scanApi.getConfig).mockResolvedValue(defaultConfig as never)
vi.mocked(scanApi.saveConfig).mockReset()
vi.mocked(scanApi.saveConfig).mockResolvedValue({} as never)
vi.mocked(scanApi.trigger).mockReset()
vi.mocked(scanApi.trigger).mockResolvedValue({} as never)
vi.mocked(toast.success).mockReset()
vi.mocked(toast.error).mockReset()
})
@@ -72,7 +75,7 @@ describe('ScanConfigModal', () => {
expect(scanApi.saveConfig).not.toHaveBeenCalled()
})
it('calls onScanNow after saving on "Scan Now" click', async () => {
it('saves config, triggers scan, calls onScanNow and closes on "Scan Now" click', async () => {
const onScanNow = vi.fn()
const onClose = vi.fn()
render(<ScanConfigModal open onClose={onClose} onScanNow={onScanNow} />)
@@ -80,7 +83,9 @@ describe('ScanConfigModal', () => {
fireEvent.click(screen.getByRole('button', { name: 'Scan Now' }))
await waitFor(() => {
expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['192.168.1.0/24'] })
expect(scanApi.trigger).toHaveBeenCalledOnce()
expect(onScanNow).toHaveBeenCalledOnce()
expect(onClose).toHaveBeenCalledOnce()
})
})
+2 -9
View File
@@ -53,15 +53,8 @@ export function Sidebar({ onAddNode, onAddGroupRect, onScan, onSave, onNodeAppro
const onlineCount = networkNodes.filter((n) => n.data.status === 'online').length
const offlineCount = networkNodes.filter((n) => n.data.status === 'offline').length
const handleScan = useCallback(async () => {
try {
await scanApi.trigger()
toast.success('Network scan started — check Scan History for results')
setActiveView('history')
onScan()
} catch {
toast.error('Failed to trigger scan')
}
const handleScan = useCallback(() => {
onScan()
}, [onScan])
return (
@@ -222,20 +222,12 @@ describe('Sidebar', () => {
// ── Scan action ────────────────────────────────────────────────────────────
it('calls scanApi.trigger and onScan prop when Scan Network is clicked', async () => {
const { scanApi } = await import('@/api/client')
it('calls onScan prop when Scan Network is clicked (scan trigger moved to ScanConfigModal)', () => {
render(<Sidebar {...defaultProps} />)
fireEvent.click(screen.getByText('Scan Network'))
await waitFor(() => expect(scanApi.trigger).toHaveBeenCalledOnce())
expect(defaultProps.onScan).toHaveBeenCalledOnce()
})
it('switches to Scan History view after scan is triggered', async () => {
render(<Sidebar {...defaultProps} />)
fireEvent.click(screen.getByText('Scan Network'))
await waitFor(() => expect(screen.getByText('History')).toBeInTheDocument())
})
// ── Navigation ─────────────────────────────────────────────────────────────
it('shows Pending panel when Pending Devices nav item is clicked', async () => {