mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fd48169219
Add and extend tests across 32 files (+10 595 lines) covering Admin panels (AuditLog, Backup, DevNotifications, GitHub), Collab (Chat, Notes, Panel, Polls), Planner (DayDetailPanel, DayPlanSidebar), Settings (DisplaySettings, Integrations, MapSettings), Files (FileManager, FilesPage), Map, Layout (DemoBanner, InAppNotificationBell), shared pickers (CustomDateTimePicker, CustomTimePicker), Vacay holidays, pages (Dashboard, Login), unit stores (authStore, inAppNotificationStore), API (authUrl, client integration), and i18n. Also updates sonar-project.properties and MSW trip handlers to support the new cases.
314 lines
12 KiB
TypeScript
314 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { render, screen, waitFor, within, fireEvent } from '../../../tests/helpers/render'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { resetAllStores, seedStore } from '../../../tests/helpers/store'
|
|
import { useSettingsStore } from '../../store/settingsStore'
|
|
import { server } from '../../../tests/helpers/msw/server'
|
|
import { http, HttpResponse } from 'msw'
|
|
import BackupPanel from './BackupPanel'
|
|
import { ToastContainer } from '../shared/Toast'
|
|
|
|
const manualBackup = {
|
|
filename: 'backup-2025-01-15.zip',
|
|
created_at: '2025-01-15T10:00:00Z',
|
|
size: 2048000,
|
|
}
|
|
const autoBackup = {
|
|
filename: 'auto-backup-2025-02-01.zip',
|
|
created_at: '2025-02-01T02:00:00Z',
|
|
size: 1024000,
|
|
}
|
|
|
|
function defaultBackupHandlers() {
|
|
return [
|
|
http.get('/api/backup/list', () => HttpResponse.json({ backups: [manualBackup] })),
|
|
http.get('/api/backup/auto-settings', () =>
|
|
HttpResponse.json({
|
|
settings: { enabled: false, interval: 'daily', keep_days: 7, hour: 2, day_of_week: 0, day_of_month: 1 },
|
|
timezone: 'UTC',
|
|
}),
|
|
),
|
|
]
|
|
}
|
|
|
|
function getToggleButton() {
|
|
// The enable toggle is a <button> inside a <label> that contains "Enable auto-backup"
|
|
const label = screen.getByText('Enable auto-backup').closest('label') as HTMLElement
|
|
return label.querySelector('button') as HTMLElement
|
|
}
|
|
|
|
describe('BackupPanel', () => {
|
|
beforeEach(() => {
|
|
resetAllStores()
|
|
seedStore(useSettingsStore, { settings: { time_format: '24h' } } as any)
|
|
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
|
server.use(...defaultBackupHandlers())
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.useRealTimers()
|
|
server.resetHandlers()
|
|
})
|
|
|
|
// BKP-001: Loading state
|
|
it('FE-ADMIN-BKP-001: shows loading spinner while fetching backups', async () => {
|
|
server.use(
|
|
http.get('/api/backup/list', async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 300))
|
|
return HttpResponse.json({ backups: [] })
|
|
}),
|
|
)
|
|
render(<BackupPanel />)
|
|
expect(document.querySelector('.animate-spin')).toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-002: Empty state
|
|
it('FE-ADMIN-BKP-002: shows empty state when no backups exist', async () => {
|
|
server.use(
|
|
http.get('/api/backup/list', () => HttpResponse.json({ backups: [] })),
|
|
)
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('No backups yet')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getByText('Create first backup')).toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-003: Backup list renders filename, size, and date
|
|
it('FE-ADMIN-BKP-003: renders filename, formatted size, and date for a backup', async () => {
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getByText('2.0 MB')).toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-004: Auto-backup badge shown for auto-backup filenames
|
|
it('FE-ADMIN-BKP-004: shows Auto badge for auto-backup filenames', async () => {
|
|
server.use(
|
|
http.get('/api/backup/list', () => HttpResponse.json({ backups: [autoBackup] })),
|
|
)
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('auto-backup-2025-02-01.zip')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getByText('Auto')).toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-005: Create backup success
|
|
it('FE-ADMIN-BKP-005: creates backup and shows success toast', async () => {
|
|
const user = userEvent.setup()
|
|
server.use(
|
|
http.post('/api/backup/create', () => HttpResponse.json({ success: true })),
|
|
http.get('/api/backup/list', () => HttpResponse.json({ backups: [manualBackup] })),
|
|
)
|
|
render(<><ToastContainer /><BackupPanel /></>)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getByTitle('Create Backup'))
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Backup created successfully')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// BKP-006: Restore opens confirmation modal
|
|
it('FE-ADMIN-BKP-006: clicking Restore opens confirmation modal', async () => {
|
|
const user = userEvent.setup()
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getAllByText('Restore')[0])
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Restore Backup?')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getAllByText('backup-2025-01-15.zip').length).toBeGreaterThanOrEqual(1)
|
|
expect(screen.getByText('Yes, restore')).toBeInTheDocument()
|
|
expect(screen.getByText('Cancel')).toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-007: Cancel dismisses modal without calling restore API
|
|
it('FE-ADMIN-BKP-007: cancel dismisses the restore modal without calling the API', async () => {
|
|
const user = userEvent.setup()
|
|
let restoreCalled = false
|
|
server.use(
|
|
http.post('/api/backup/restore/:filename', () => {
|
|
restoreCalled = true
|
|
return HttpResponse.json({ success: true })
|
|
}),
|
|
)
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getAllByText('Restore')[0])
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Restore Backup?')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getByText('Cancel'))
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('Restore Backup?')).not.toBeInTheDocument()
|
|
})
|
|
expect(restoreCalled).toBe(false)
|
|
})
|
|
|
|
// BKP-008: Backdrop click dismisses modal
|
|
it('FE-ADMIN-BKP-008: clicking the backdrop dismisses the restore modal', async () => {
|
|
const user = userEvent.setup()
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getAllByText('Restore')[0])
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Restore Backup?')).toBeInTheDocument()
|
|
})
|
|
// Click the backdrop overlay (the fixed-position div)
|
|
const backdrop = document.querySelector('[style*="position: fixed"]') as HTMLElement
|
|
expect(backdrop).toBeTruthy()
|
|
fireEvent.click(backdrop!)
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('Restore Backup?')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// BKP-009: Successful restore calls API and reloads after 1500ms
|
|
it('FE-ADMIN-BKP-009: successful restore shows toast and reloads after 1500ms', async () => {
|
|
const user = userEvent.setup()
|
|
server.use(
|
|
http.post('/api/backup/restore/:filename', () => HttpResponse.json({ success: true })),
|
|
)
|
|
render(<><ToastContainer /><BackupPanel /></>)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
|
|
// Stub reload AFTER initial data load so we don't corrupt window.location during setup
|
|
const reloadMock = vi.fn()
|
|
vi.stubGlobal('location', { ...window.location, reload: reloadMock })
|
|
|
|
await user.click(screen.getAllByText('Restore')[0])
|
|
await waitFor(() => expect(screen.getByText('Restore Backup?')).toBeInTheDocument())
|
|
await user.click(screen.getByText('Yes, restore'))
|
|
await waitFor(() => expect(screen.getByText('Backup restored. Page will reload…')).toBeInTheDocument())
|
|
|
|
// Wait for the 1500ms reload timer to fire
|
|
await new Promise(resolve => setTimeout(resolve, 1600))
|
|
expect(reloadMock).toHaveBeenCalled()
|
|
vi.unstubAllGlobals()
|
|
}, 20000)
|
|
|
|
// BKP-010: Delete backup with confirm dialog
|
|
it('FE-ADMIN-BKP-010: deletes backup after confirm and shows success toast', async () => {
|
|
const user = userEvent.setup()
|
|
server.use(
|
|
http.delete('/api/backup/:filename', () => HttpResponse.json({ success: true })),
|
|
)
|
|
render(<><ToastContainer /><BackupPanel /></>)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('backup-2025-01-15.zip')).toBeInTheDocument()
|
|
})
|
|
const trashBtn = Array.from(document.querySelectorAll('button')).find(
|
|
b => b.querySelector('svg.lucide-trash2'),
|
|
) as HTMLElement
|
|
expect(trashBtn).toBeTruthy()
|
|
await user.click(trashBtn!)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Backup deleted')).toBeInTheDocument()
|
|
})
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('backup-2025-01-15.zip')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// BKP-011: Auto-backup enable toggle shows interval controls
|
|
it('FE-ADMIN-BKP-011: enabling auto-backup shows interval controls', async () => {
|
|
const user = userEvent.setup()
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Enable auto-backup')).toBeInTheDocument()
|
|
})
|
|
expect(screen.queryByText('Hourly')).not.toBeInTheDocument()
|
|
await user.click(getToggleButton())
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Hourly')).toBeInTheDocument()
|
|
expect(screen.getByText('Daily')).toBeInTheDocument()
|
|
expect(screen.getByText('Weekly')).toBeInTheDocument()
|
|
expect(screen.getByText('Monthly')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// BKP-012: Weekly interval shows day-of-week picker
|
|
it('FE-ADMIN-BKP-012: weekly interval shows day-of-week picker', async () => {
|
|
const user = userEvent.setup()
|
|
server.use(
|
|
http.get('/api/backup/auto-settings', () =>
|
|
HttpResponse.json({
|
|
settings: { enabled: true, interval: 'daily', keep_days: 7, hour: 2, day_of_week: 0, day_of_month: 1 },
|
|
timezone: 'UTC',
|
|
}),
|
|
),
|
|
)
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Weekly')).toBeInTheDocument()
|
|
})
|
|
expect(screen.queryByText('Sun')).not.toBeInTheDocument()
|
|
await user.click(screen.getByText('Weekly'))
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Sun')).toBeInTheDocument()
|
|
expect(screen.getByText('Mon')).toBeInTheDocument()
|
|
expect(screen.getByText('Sat')).toBeInTheDocument()
|
|
})
|
|
expect(screen.queryByText('Day of month')).not.toBeInTheDocument()
|
|
})
|
|
|
|
// BKP-013: Save auto-settings calls API and shows toast
|
|
it('FE-ADMIN-BKP-013: saving auto-settings calls API and shows success toast', async () => {
|
|
const user = userEvent.setup()
|
|
server.use(
|
|
http.get('/api/backup/auto-settings', () =>
|
|
HttpResponse.json({
|
|
settings: { enabled: true, interval: 'daily', keep_days: 7, hour: 2, day_of_week: 0, day_of_month: 1 },
|
|
timezone: 'UTC',
|
|
}),
|
|
),
|
|
http.put('/api/backup/auto-settings', () =>
|
|
HttpResponse.json({
|
|
settings: { enabled: true, interval: 'weekly', keep_days: 7, hour: 2, day_of_week: 0, day_of_month: 1 },
|
|
}),
|
|
),
|
|
)
|
|
render(<><ToastContainer /><BackupPanel /></>)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Weekly')).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getByText('Weekly'))
|
|
await waitFor(() => {
|
|
const saveBtn = screen.getByRole('button', { name: /^save$/i })
|
|
expect(saveBtn).not.toBeDisabled()
|
|
})
|
|
await user.click(screen.getByRole('button', { name: /^save$/i }))
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Auto-backup settings saved')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// BKP-014: Save button disabled until settings changed
|
|
it('FE-ADMIN-BKP-014: save button is disabled until settings are changed', async () => {
|
|
const user = userEvent.setup()
|
|
render(<BackupPanel />)
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Enable auto-backup')).toBeInTheDocument()
|
|
})
|
|
const saveBtn = screen.getByRole('button', { name: /^save$/i })
|
|
expect(saveBtn).toBeDisabled()
|
|
await user.click(getToggleButton())
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /^save$/i })).not.toBeDisabled()
|
|
})
|
|
})
|
|
})
|