import { describe, expect, it, vi } from 'vitest'; import { loadPaletteDirectory, paletteDirectoryResults, paletteMatchJump, palettePageResults, searchPaletteRecords } from '../../src/lib/ui/command-palette.js'; describe('command palette destinations', () => { it('keeps administrator destinations and operations out of inappropriate roles', () => { const viewer = palettePageResults('viewer', ''); expect(viewer.some(item => item.group === 'Actions' || item.href.startsWith('/settings') || item.href === '/server-keys')).toBe(false); expect(palettePageResults('operator', '').filter(item => item.group === 'Actions').map(item => item.href)).toEqual(['/?action=create']); expect(palettePageResults('admin', 'enroll')).toContainEqual(expect.objectContaining({ href: '/hosts?action=add' })); expect(palettePageResults('admin', 'campaigns')).toContainEqual(expect.objectContaining({ label: 'Campaigns', href: '/campaigns' })); }); it('searches friendly server names and hosts while encoding identifiers and bounding groups', () => { const directory = { servers: Array.from({ length: 10 }, (_, i) => ({ name: `t5_zombie_theater_${i + 1}`, host: { name: 'Dallas 1' } })), hosts: [{ id: 'host / 1', name: 'Dallas 1', hostname: 'dallas.example', connectionState: 'offline' }], unavailable: [] }; expect(paletteDirectoryResults('', directory)).toEqual([]); expect(paletteDirectoryResults('kino lobby 2', directory)).toEqual([{ key: 'server:t5_zombie_theater_2', group: 'Servers', label: 'Kino Der Toten ยท Lobby 2', detail: 'Dallas 1', href: '/servers/t5_zombie_theater_2' }]); const found = paletteDirectoryResults('Dallas', directory); expect(found.filter(item => item.group === 'Servers')).toHaveLength(6); expect(found).toContainEqual(expect.objectContaining({ href: '/hosts/host%20%2F%201', detail: 'Host agent offline' })); expect(new Set(found.map(item => item.key)).size).toBe(found.length); }); it.each(['42', '#42', 'match 42', 'Match #42'])('opens a positive match ID directly: %s', query => { expect(paletteMatchJump(query)).toEqual([expect.objectContaining({ key: 'match:42', href: '/matches/42' })]); }); it.each(['0', '-1', '1.5', 'match one', '9007199254740993', 'https://example.com'])('does not invent an invalid match destination: %s', query => { expect(paletteMatchJump(query)).toEqual([]); }); }); describe('bounded palette requests', () => { it('does not fetch histories for an empty or one-character query', async () => { const fetcher = vi.fn(); expect(await searchPaletteRecords('x', new AbortController().signal, fetcher)).toEqual({ items: [], unavailable: [] }); expect(fetcher).not.toHaveBeenCalled(); }); it('queries at most five players and completed matches, and propagates cancellation', async () => { const controller = new AbortController(); const fetcher = vi.fn(async (url: string) => Response.json(url.startsWith('/api/leaderboard') ? { entries: [{ guid: 'id / 1', name: 'Cherry' }] } : { matches: [{ id: 2, map: 'Kino Der Toten', server: 't5_zombie_theater_1', status: 'completed' }, { id: 3, map: 'Town', server: 'server', status: 'active' }] })); const result = await searchPaletteRecords('Cherry & Co', controller.signal, fetcher); expect(fetcher).toHaveBeenCalledTimes(2); for (const call of fetcher.mock.calls) { const url = new URL(call[0], 'http://localhost'); expect(url.searchParams.get('limit')).toBe('5'); expect(url.searchParams.get('q')).toBe('Cherry & Co'); } expect(fetcher).toHaveBeenCalledWith(expect.stringContaining('/api/matches?'), { signal: controller.signal, cache: 'no-store' }); expect(result.items.map(item => item.href)).toEqual(['/leaderboard/players/id%20%2F%201', '/matches/2']); expect(result.unavailable).toEqual([]); }); it('preserves results from a successful source when another source fails', async () => { const fetcher = vi.fn(async (url: string) => url === '/api/hosts' ? new Response('', { status: 503 }) : Response.json({ servers: [{ name: 'server' }] })); expect(await loadPaletteDirectory(new AbortController().signal, fetcher)).toEqual({ servers: [{ name: 'server' }], hosts: [], unavailable: ['Host'] }); const records = await searchPaletteRecords('Cherry', new AbortController().signal, async url => url.startsWith('/api/leaderboard') ? new Response('', { status: 500 }) : Response.json({ matches: [{ id: 5, status: 'completed', map: 'Town', server: 'server' }] })); expect(records.items.map(item => item.href)).toEqual(['/matches/5']); expect(records.unavailable).toEqual(['Player']); }); });