Files
ss-tools/frontend/tests/maintenance.test.ts
2026-05-26 09:30:41 +03:00

190 lines
6.4 KiB
TypeScript

// #region MaintenanceComponentTests [C:2] [TYPE Module] [SEMANTICS test, component, maintenance, events-table, badge]
// @BRIEF Component tests for MaintenanceEventsTable and DashboardMaintenanceBadge.
// @RELATION DEPENDS_ON -> [EXT:frontend:MaintenanceEventsTable]
// @RELATION DEPENDS_ON -> [EXT:frontend:DashboardMaintenanceBadge]
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Create mutable mock data using vi.hoisted
const { mockStoreState, mockStore } = vi.hoisted(() => {
const sharedState = {
activeEvents: [],
completedEvents: [],
settings: null,
dashboardBanners: [],
isLoading: false,
error: null,
};
const store = {
get activeEvents() { return sharedState.activeEvents; },
get completedEvents() { return sharedState.completedEvents; },
get settings() { return sharedState.settings; },
get dashboardBanners() { return sharedState.dashboardBanners; },
get isLoading() { return sharedState.isLoading; },
get error() { return sharedState.error; },
loadEvents: vi.fn(),
loadSettings: vi.fn(),
loadDashboardBanners: vi.fn(),
endEvent: vi.fn(),
endAllEvents: vi.fn(),
updateSettings: vi.fn(),
init: vi.fn(),
startPolling: vi.fn(),
stopPolling: vi.fn(),
};
return { mockStoreState: sharedState, mockStore: store };
});
function resetMockStore() {
mockStoreState.activeEvents = [];
mockStoreState.completedEvents = [];
mockStoreState.settings = null;
mockStoreState.dashboardBanners = [];
mockStoreState.isLoading = false;
mockStoreState.error = null;
}
// Mock toasts
vi.mock('$lib/toasts.js', () => ({
addToast: vi.fn()
}));
// Mock i18n — $t is a derived store, needs subscribe method for Svelte $ prefix
vi.mock('$lib/i18n', () => {
const subscribers = new Set();
const tData = {
maintenance: {
active_events: 'Active Events',
completed_events: 'Completed Events',
no_active_events: 'No active maintenance events',
no_completed_events: 'No completed maintenance events',
remove_banners: 'Remove',
remove_all: 'Remove All',
maintenance_badge: 'Maintenance',
maintenance_tooltip: 'Maintenance active: {start} — {end}',
event_id: 'Event ID',
tables: 'Tables',
affected_dashboards: 'Dashboards',
start_time: 'Start Time',
end_time: 'End Time',
status: 'Status',
actions: 'Actions',
},
common: {}
};
return {
t: {
subscribe(run) {
subscribers.add(run);
run(tData);
return () => subscribers.delete(run);
}
},
_: (key) => key,
};
});
vi.mock('$lib/stores/maintenance.svelte.js', () => ({
maintenanceStore: mockStore,
createMaintenanceStore: () => mockStore,
}));
// Mock the API module
vi.mock('$lib/api.js', () => ({
api: {
getEnvironments: vi.fn(() => Promise.resolve([])),
getDashboards: vi.fn(() => Promise.resolve({ dashboards: [] })),
},
requestApi: vi.fn(),
}));
// Mock $app/navigation
vi.mock('$app/navigation', () => ({
goto: vi.fn(),
}));
// Mock $app/stores
vi.mock('$app/stores', () => ({
page: { subscribe: vi.fn() },
navigating: { subscribe: vi.fn() },
updated: { subscribe: vi.fn() },
}));
import { render, screen } from '@testing-library/svelte';
import MaintenanceEventsTable from '$lib/components/MaintenanceEventsTable.svelte';
import DashboardMaintenanceBadge from '$lib/components/DashboardMaintenanceBadge.svelte';
describe('MaintenanceEventsTable', () => {
beforeEach(() => {
vi.clearAllMocks();
resetMockStore();
});
it('renders empty state when no active events', async () => {
mockStoreState.activeEvents = [];
mockStoreState.completedEvents = [];
render(MaintenanceEventsTable);
expect(screen.getByText('No active maintenance events')).toBeTruthy();
});
it('renders active events in the table', async () => {
mockStoreState.activeEvents = [
{ id: 'ev-1', tables: ['raw.sales'], affected_count: 3, start_time: '2026-05-21T22:00:00Z', end_time: null, status: 'active' }
];
mockStoreState.completedEvents = [];
render(MaintenanceEventsTable);
expect(screen.getByText('ev-1')).toBeTruthy();
});
it('renders Remove All button when events exist', async () => {
mockStoreState.activeEvents = [
{ id: 'ev-1', tables: ['raw.sales'], affected_count: 3, start_time: '2026-05-21T22:00:00Z', end_time: null, status: 'active' }
];
mockStoreState.completedEvents = [];
render(MaintenanceEventsTable);
expect(screen.getByText('Remove All')).toBeTruthy();
});
});
describe('DashboardMaintenanceBadge', () => {
beforeEach(() => {
vi.clearAllMocks();
resetMockStore();
});
it('renders nothing when no banner state for dashboard', async () => {
mockStoreState.dashboardBanners = [];
const { container } = render(DashboardMaintenanceBadge, {
props: { dashboardId: 42 }
});
const badgeElements = container.querySelectorAll('.bg-orange-100');
expect(badgeElements.length).toBe(0);
});
it('renders badge when banner is active for dashboard', async () => {
mockStoreState.dashboardBanners = [
{ dashboard_id: 42, active: true, events: ['ev-1'], start_time: '2026-05-21T22:00:00Z', end_time: null }
];
const { container } = render(DashboardMaintenanceBadge, {
props: { dashboardId: 42 }
});
const badge = container.querySelector('.bg-orange-100');
expect(badge).toBeTruthy();
expect(badge?.textContent).toContain('Maintenance');
});
it('renders nothing when banner exists but not active for this dashboard', async () => {
mockStoreState.dashboardBanners = [
{ dashboard_id: 57, active: true, events: ['ev-1'], start_time: '2026-05-21T22:00:00Z', end_time: null }
];
const { container } = render(DashboardMaintenanceBadge, {
props: { dashboardId: 42 }
});
const badge = container.querySelector('.bg-orange-100');
expect(badge).toBeNull();
});
});
// #endregion MaintenanceComponentTests