test: dedup sonner mocks and close Toolbar seam-mock gaps
- Replace 10 inline sonner toast mocks with the shared mockSonner() builder. - Toolbar: add tests for the store-driven undo/redo disabled state and the unsaved-changes dot — behaviour the full-store mock previously left unasserted. ha-relevant: yes
This commit is contained in:
@@ -4,7 +4,7 @@ import { CustomStyleModal } from '../CustomStyleModal'
|
|||||||
import { useThemeStore } from '@/stores/themeStore'
|
import { useThemeStore } from '@/stores/themeStore'
|
||||||
import { useCanvasStore } from '@/stores/canvasStore'
|
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'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
describe('CustomStyleModal', () => {
|
describe('CustomStyleModal', () => {
|
||||||
|
|||||||
@@ -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', () => ({
|
vi.mock('@/components/modals/PendingDeviceModal', () => ({
|
||||||
PendingDeviceModal: ({ device, onApprove }: { device: unknown; onApprove: (d: unknown) => void }) =>
|
PendingDeviceModal: ({ device, onApprove }: { device: unknown; onApprove: (d: unknown) => void }) =>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|||||||
import { ScanHistoryModal } from '../ScanHistoryModal'
|
import { ScanHistoryModal } from '../ScanHistoryModal'
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
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', () => ({
|
vi.mock('@/stores/canvasStore', () => ({
|
||||||
useCanvasStore: { getState: () => ({ notifyScanDeviceFound: vi.fn() }) },
|
useCanvasStore: { getState: () => ({ notifyScanDeviceFound: vi.fn() }) },
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||||
import { SettingsModal } from '../SettingsModal'
|
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', () => ({
|
vi.mock('@/api/client', () => ({
|
||||||
settingsApi: {
|
settingsApi: {
|
||||||
get: vi.fn(),
|
get: vi.fn(),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useThemeStore } from '@/stores/themeStore'
|
|||||||
import { useCanvasStore } from '@/stores/canvasStore'
|
import { useCanvasStore } from '@/stores/canvasStore'
|
||||||
import { THEME_ORDER } from '@/utils/themes'
|
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'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
describe('ThemeModal', () => {
|
describe('ThemeModal', () => {
|
||||||
|
|||||||
@@ -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', () => ({
|
vi.mock('@/components/ui/Logo', () => ({
|
||||||
Logo: ({ showText }: { showText: boolean }) => (
|
Logo: ({ showText }: { showText: boolean }) => (
|
||||||
|
|||||||
@@ -61,6 +61,39 @@ describe('Toolbar', () => {
|
|||||||
render(<Toolbar {...defaultProps} />)
|
render(<Toolbar {...defaultProps} />)
|
||||||
expect(screen.getByText('View')).toBeInTheDocument()
|
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(<Toolbar {...defaultProps} />)
|
||||||
|
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<ReturnType<typeof useCanvasStore>>)
|
||||||
|
render(<Toolbar {...defaultProps} />)
|
||||||
|
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(<Toolbar {...defaultProps} />)
|
||||||
|
// 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(<Toolbar {...defaultProps} />)
|
||||||
|
expect(document.querySelector('.bg-\\[\\#e3b341\\]')).not.toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// ── Standalone mode ────────────────────────────────────────────────────────────
|
// ── Standalone mode ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -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/Logo', () => ({ Logo: () => null }))
|
||||||
vi.mock('@/components/ui/tooltip', () => ({
|
vi.mock('@/components/ui/tooltip', () => ({
|
||||||
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({
|
|||||||
importToPending: vi.fn(),
|
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 { proxmoxApi } from '@/api/client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({
|
|||||||
importToPending: vi.fn(),
|
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 { zigbeeApi } from '@/api/client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ vi.mock('@/api/client', () => ({
|
|||||||
importToPending: vi.fn(),
|
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 { zwaveApi } from '@/api/client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|||||||
Reference in New Issue
Block a user