diff --git a/frontend/src/components/modals/__tests__/CustomStyleModal.test.tsx b/frontend/src/components/modals/__tests__/CustomStyleModal.test.tsx index 513ddd6..4f7a5f3 100644 --- a/frontend/src/components/modals/__tests__/CustomStyleModal.test.tsx +++ b/frontend/src/components/modals/__tests__/CustomStyleModal.test.tsx @@ -4,7 +4,7 @@ import { CustomStyleModal } from '../CustomStyleModal' import { useThemeStore } from '@/stores/themeStore' import { useCanvasStore } from '@/stores/canvasStore' -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) import { toast } from 'sonner' describe('CustomStyleModal', () => { diff --git a/frontend/src/components/modals/__tests__/PendingDevicesModal.test.tsx b/frontend/src/components/modals/__tests__/PendingDevicesModal.test.tsx index d4d527f..cb0bbe7 100644 --- a/frontend/src/components/modals/__tests__/PendingDevicesModal.test.tsx +++ b/frontend/src/components/modals/__tests__/PendingDevicesModal.test.tsx @@ -31,7 +31,7 @@ vi.mock('@/api/client', () => ({ }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) vi.mock('@/components/modals/PendingDeviceModal', () => ({ PendingDeviceModal: ({ device, onApprove }: { device: unknown; onApprove: (d: unknown) => void }) => diff --git a/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx b/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx index 72e1a67..03b01cf 100644 --- a/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx +++ b/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { ScanHistoryModal } from '../ScanHistoryModal' import { TooltipProvider } from '@/components/ui/tooltip' -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), warning: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) vi.mock('@/stores/canvasStore', () => ({ useCanvasStore: { getState: () => ({ notifyScanDeviceFound: vi.fn() }) }, })) diff --git a/frontend/src/components/modals/__tests__/SettingsModal.test.tsx b/frontend/src/components/modals/__tests__/SettingsModal.test.tsx index c33d9da..c4d7082 100644 --- a/frontend/src/components/modals/__tests__/SettingsModal.test.tsx +++ b/frontend/src/components/modals/__tests__/SettingsModal.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { SettingsModal } from '../SettingsModal' -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) vi.mock('@/api/client', () => ({ settingsApi: { get: vi.fn(), diff --git a/frontend/src/components/modals/__tests__/ThemeModal.test.tsx b/frontend/src/components/modals/__tests__/ThemeModal.test.tsx index 1e5b9c3..b5df59c 100644 --- a/frontend/src/components/modals/__tests__/ThemeModal.test.tsx +++ b/frontend/src/components/modals/__tests__/ThemeModal.test.tsx @@ -5,7 +5,7 @@ import { useThemeStore } from '@/stores/themeStore' import { useCanvasStore } from '@/stores/canvasStore' import { THEME_ORDER } from '@/utils/themes' -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) import { toast } from 'sonner' describe('ThemeModal', () => { diff --git a/frontend/src/components/panels/__tests__/Sidebar.test.tsx b/frontend/src/components/panels/__tests__/Sidebar.test.tsx index 55266ff..c74e73d 100644 --- a/frontend/src/components/panels/__tests__/Sidebar.test.tsx +++ b/frontend/src/components/panels/__tests__/Sidebar.test.tsx @@ -24,7 +24,7 @@ vi.mock('@/api/client', () => ({ }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) vi.mock('@/components/ui/Logo', () => ({ Logo: ({ showText }: { showText: boolean }) => ( diff --git a/frontend/src/components/panels/__tests__/Toolbar.test.tsx b/frontend/src/components/panels/__tests__/Toolbar.test.tsx index e06cc60..8fcc1a9 100644 --- a/frontend/src/components/panels/__tests__/Toolbar.test.tsx +++ b/frontend/src/components/panels/__tests__/Toolbar.test.tsx @@ -61,6 +61,39 @@ describe('Toolbar', () => { render() expect(screen.getByText('View')).toBeInTheDocument() }) + + // These cover the store→UI wiring the seam mock would otherwise hide: the + // undo/redo disabled state and the unsaved dot are driven purely by store + // state, so assert them against the mocked slices. + it('disables Undo when the history stack is empty and Redo when the future is empty', () => { + mockStore({ past: [], future: [] }) + render() + expect(screen.getByTitle('Undo (Ctrl+Z)')).toBeDisabled() + expect(screen.getByTitle('Redo (Ctrl+Y)')).toBeDisabled() + }) + + it('enables Undo/Redo when history/future have entries and forwards the clicks', () => { + mockStore({ past: [{}], future: [{}] } as Partial>) + render() + const undo = screen.getByTitle('Undo (Ctrl+Z)') + const redo = screen.getByTitle('Redo (Ctrl+Y)') + expect(undo).toBeEnabled() + expect(redo).toBeEnabled() + fireEvent.click(undo) + fireEvent.click(redo) + expect(defaultProps.onUndo).toHaveBeenCalledOnce() + expect(defaultProps.onRedo).toHaveBeenCalledOnce() + }) + + it('renders the unsaved-changes dot only when hasUnsavedChanges is true', () => { + const { rerender } = render() + // Save button is the last button; the dot is a sibling span with bg-[#e3b341]. + expect(document.querySelector('.bg-\\[\\#e3b341\\]')).toBeNull() + + mockStore({ hasUnsavedChanges: true }) + rerender() + expect(document.querySelector('.bg-\\[\\#e3b341\\]')).not.toBeNull() + }) }) // ── Standalone mode ──────────────────────────────────────────────────────────── diff --git a/frontend/src/components/panels/__tests__/VersionBadge.test.tsx b/frontend/src/components/panels/__tests__/VersionBadge.test.tsx index 7aad9de..104fc41 100644 --- a/frontend/src/components/panels/__tests__/VersionBadge.test.tsx +++ b/frontend/src/components/panels/__tests__/VersionBadge.test.tsx @@ -22,7 +22,7 @@ vi.mock('@/api/client', () => ({ }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) vi.mock('@/components/ui/Logo', () => ({ Logo: () => null })) vi.mock('@/components/ui/tooltip', () => ({ Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}, diff --git a/frontend/src/components/proxmox/__tests__/ProxmoxImportModal.test.tsx b/frontend/src/components/proxmox/__tests__/ProxmoxImportModal.test.tsx index dd35d39..4384c2b 100644 --- a/frontend/src/components/proxmox/__tests__/ProxmoxImportModal.test.tsx +++ b/frontend/src/components/proxmox/__tests__/ProxmoxImportModal.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({ importToPending: vi.fn(), }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) import { proxmoxApi } from '@/api/client' import { toast } from 'sonner' diff --git a/frontend/src/components/zigbee/__tests__/ZigbeeImportModal.test.tsx b/frontend/src/components/zigbee/__tests__/ZigbeeImportModal.test.tsx index c7f2088..f74b11c 100644 --- a/frontend/src/components/zigbee/__tests__/ZigbeeImportModal.test.tsx +++ b/frontend/src/components/zigbee/__tests__/ZigbeeImportModal.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({ importToPending: vi.fn(), }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) import { zigbeeApi } from '@/api/client' import { toast } from 'sonner' diff --git a/frontend/src/components/zwave/__tests__/ZwaveImportModal.test.tsx b/frontend/src/components/zwave/__tests__/ZwaveImportModal.test.tsx index 7c8f54c..3d5fcf8 100644 --- a/frontend/src/components/zwave/__tests__/ZwaveImportModal.test.tsx +++ b/frontend/src/components/zwave/__tests__/ZwaveImportModal.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({ importToPending: vi.fn(), }, })) -vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) +vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner()) import { zwaveApi } from '@/api/client' import { toast } from 'sonner'