Files
electric-cherry/tests/unit/ui-navigation.test.ts
T

74 lines
4.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { render } from 'svelte/server';
import { writable } from 'svelte/store';
import { createRawSnippet } from 'svelte';
import Navigation from '../../src/lib/ui/components/Navigation.svelte';
import Layout from '../../src/routes/+layout.svelte';
import { fleet } from '../../src/lib/ui/stores/fleet.js';
import type { AuthUser } from '../../src/lib/auth.js';
const currentPage = writable({ url: new URL('http://localhost/campaigns/42'), data: {} });
vi.mock('$app/stores', () => ({ page: { subscribe: (run: Parameters<typeof currentPage.subscribe>[0]) => currentPage.subscribe(run) } }));
vi.mock('$app/navigation', () => ({ goto: vi.fn(), beforeNavigate: vi.fn(), afterNavigate: vi.fn(), onNavigate: vi.fn() }));
afterEach(() => { fleet.set({ servers: [], operations: [], receivedAt: 0 }); currentPage.set({ url: new URL('http://localhost/campaigns/42'), data: {} }); });
describe('sidebar navigation', () => {
it('exposes every allowed page in one navigation and marks the current page', () => {
const body = render(Navigation, { props: { role: 'operator' } }).body;
expect(body.match(/<nav\b/g)).toHaveLength(1);
expect(body).toContain('Main navigation');
expect(body).toMatch(/<a[^>]*href="\/campaigns"[^>]*aria-current="page"/);
expect(body).toContain('href="/betas"');
expect(body).toContain('href="/activity"');
expect(body).toContain('href="/metrics"');
expect(body).not.toContain('href="/settings"');
expect(body).not.toContain('href="/server-keys"');
});
it('does not show invented attention counts before the fleet snapshot loads', () => {
expect(render(Navigation, { props: { role: 'admin' } }).body).not.toMatch(/needs? attention/);
fleet.set({ receivedAt: Date.now(), operations: [], servers: [{
name: 't5_zombie_theater_1', game: 't5', map: 'zombie_theater', last_players: null,
last_map: null, last_gametype: null, last_seen_at: null, last_error: null, roster: [],
health: 'offline', held: [], active_since: null, inactive_since: null, game_started_at: null, game_ended_at: null
}] });
expect(render(Navigation, { props: { role: 'admin' } }).body).toContain('aria-label="Servers, 1 needs attention"');
});
it('counts hosts without servers and clears the badge when the next host summary is healthy', () => {
fleet.set({ receivedAt: Date.now(), operations: [], servers: [], hostAttention: 2 });
expect(render(Navigation, { props: { role: 'admin' } }).body).toContain('aria-label="Hosts, 2 need attention"');
fleet.update(value => ({ ...value, hostAttention: 0 }));
expect(render(Navigation, { props: { role: 'admin' } }).body).not.toMatch(/aria-label="Hosts, \d/);
});
});
const user: AuthUser = { id: '1', username: 'operator', displayName: 'Operator', role: 'operator', disabled: false,
mustChangePassword: false, createdAt: '', updatedAt: '', lastLoginAt: null };
const children = createRawSnippet(() => ({ render: () => '<h1>Page content</h1>' }));
const shell = (overrides: Partial<AuthUser> = {}) => render(Layout, { props: { children, data: { user: { ...user, ...overrides }, setupRequired: false } } }).body;
describe('application shell', () => {
it('offers collapsible desktop navigation, a labelled mobile drawer, search and help', () => {
const body = shell();
expect(body).toContain('id="desktop-navigation"');
expect(body).toContain('aria-label="Collapse navigation"');
expect(body).toContain('aria-label="Open navigation"');
expect(body).toContain('id="mobile-navigation"');
expect(body).toContain('aria-keyshortcuts="Meta+k Control+k"');
expect(body).toContain('aria-label="Keyboard shortcuts"');
expect(body).not.toContain('class="primary-navigation"');
expect(body).not.toContain('variant="secondary"');
});
it('preserves the viewer notice and hides navigation while a password change is required', () => {
const viewer = shell({ role: 'viewer' });
expect(viewer).toContain('Viewer access');
expect(viewer).not.toContain('href="/server-keys"');
const password = shell({ mustChangePassword: true });
expect(password).not.toContain('id="desktop-navigation"');
expect(password).not.toContain('id="mobile-navigation"');
expect(password).not.toContain('aria-keyshortcuts=');
});
});