- Delete legacy .ts stores (auth, activity, assistantChat, datasetReview, environmentContext, health, sidebar, taskDrawer, translationRun) - Create new .svelte.ts runes-based stores using reactive primitives - Migrate i18n: /i18n → /i18n/index.svelte.js - Migrate toasts: /toasts → /toasts.svelte.js - Update all component imports across 180+ files: components, pages, routes, lib - Remove fromStore() wrappers — use store.value directly with Svelte 5 runes - Update test mocks for new import paths - Add @DEPRECATED annotation to legacy alias
191 lines
6.5 KiB
TypeScript
191 lines
6.5 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(),
|
|
connectWs: vi.fn(),
|
|
disconnectWs: 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.svelte.js', () => ({
|
|
addToast: vi.fn()
|
|
}));
|
|
|
|
// Mock i18n — $t is a derived store, needs subscribe method for Svelte $ prefix
|
|
vi.mock('$lib/i18n/index.svelte.js', () => {
|
|
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,
|
|
getT: () => ({}),
|
|
};
|
|
});
|
|
|
|
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
|