156 lines
6.1 KiB
TypeScript
156 lines
6.1 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('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']);
|
|
});
|
|
});
|