Files
electric-cherry/tests/unit/command-palette.test.ts
T
2026-10-11 23:30:40 -04:00

166 lines
6.6 KiB
TypeScript

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('lets Super Viewers search administration pages without offering operations', () => {
const results = palettePageResults('super_viewer', '');
expect(results.filter((item) => item.group === 'Actions')).toEqual([]);
expect(results.map((item) => item.href)).toEqual(
palettePageResults('admin', '')
.filter((item) => item.group === 'Pages')
.map((item) => item.href)
);
});
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', '/provisioning']);
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']);
});
});