164 lines
5.6 KiB
TypeScript
164 lines
5.6 KiB
TypeScript
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
|
import { flushSync, mount, unmount } from 'svelte';
|
|
import { writable } from 'svelte/store';
|
|
import BetaSetupPage from '../../src/routes/betas/new/+page.svelte';
|
|
|
|
const currentPage = writable({
|
|
url: new URL('http://localhost/betas/new'),
|
|
data: { user: { role: 'operator' } }
|
|
});
|
|
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() }));
|
|
vi.mock('../../src/lib/ui/stores/fleet.js', () => ({
|
|
fleetServers: writable([]),
|
|
fleetError: writable(''),
|
|
refreshFleet: vi.fn().mockResolvedValue(undefined)
|
|
}));
|
|
|
|
let component: ReturnType<typeof mount> | undefined;
|
|
beforeEach(() => {
|
|
const storage = new Map<string, string>();
|
|
vi.stubGlobal('localStorage', {
|
|
getItem: (key: string) => storage.get(key) ?? null,
|
|
setItem: (key: string, value: string) => storage.set(key, value),
|
|
removeItem: (key: string) => storage.delete(key)
|
|
});
|
|
window.history.replaceState({}, '', '/betas/new');
|
|
});
|
|
afterEach(async () => {
|
|
if (component) await unmount(component);
|
|
component = undefined;
|
|
document.body.replaceChildren();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
function file(
|
|
id: number,
|
|
options: {
|
|
currentVersionId?: number;
|
|
latestVersionId?: number | null;
|
|
runningBetaId?: number;
|
|
} = {}
|
|
) {
|
|
const latestVersionId =
|
|
options.latestVersionId === undefined ? id * 10 + 2 : options.latestVersionId;
|
|
return {
|
|
id,
|
|
name: `File ${id}`,
|
|
kind: 'script',
|
|
target_rel_path: `file-${id}.gsc`,
|
|
scope_type: null,
|
|
scope_value: null,
|
|
current_version_id: options.currentVersionId ?? id * 10,
|
|
latestVersion: latestVersionId === null ? null : { id: latestVersionId },
|
|
runningBetaId: options.runningBetaId ?? null
|
|
};
|
|
}
|
|
const files = [
|
|
file(1),
|
|
file(2),
|
|
file(3, { currentVersionId: 32 }),
|
|
file(4, { runningBetaId: 7 }),
|
|
file(5, { latestVersionId: null })
|
|
];
|
|
const button = (label: string) =>
|
|
[...document.querySelectorAll('button')].find((node) => node.textContent?.trim() === label)!;
|
|
const selections = () =>
|
|
[...document.querySelectorAll('.pick-row')].map((row) =>
|
|
[...row.querySelectorAll('select')].map((select) => select.value)
|
|
);
|
|
|
|
async function show({ query = '', failingFile = 0 } = {}) {
|
|
window.history.replaceState({}, '', `/betas/new${query}`);
|
|
currentPage.set({
|
|
url: new URL(window.location.href),
|
|
data: { user: { role: 'operator' } }
|
|
});
|
|
const fetch = vi.fn(async (url: string) => {
|
|
if (url === '/api/scripts') return Response.json({ scripts: files });
|
|
const script = files.find((entry) => url === `/api/scripts/${entry.id}`);
|
|
if (!script) throw new Error(`Unexpected request: ${url}`);
|
|
if (script.id === failingFile) return new Response('', { status: 503 });
|
|
return Response.json({
|
|
script,
|
|
versions: [2, 1, 0].map((version) => ({
|
|
id: script.id * 10 + version,
|
|
version: version + 1,
|
|
release_version: `1.0.${version}`,
|
|
sha256: `hash-${script.id}-${version}`,
|
|
state: 'untested'
|
|
})),
|
|
matchedServers: []
|
|
});
|
|
});
|
|
vi.stubGlobal('fetch', fetch);
|
|
component = mount(BetaSetupPage, { target: document.body });
|
|
flushSync();
|
|
await vi.waitFor(() => {
|
|
flushSync();
|
|
expect(button('Add all eligible files')).toBeDefined();
|
|
});
|
|
return fetch;
|
|
}
|
|
|
|
it('adds every eligible file with its latest unpromoted version and replaces empty rows', async () => {
|
|
const fetch = await show();
|
|
button('Add file').click();
|
|
flushSync();
|
|
expect(document.querySelectorAll('.pick-row')).toHaveLength(2);
|
|
button('Add all eligible files').click();
|
|
await vi.waitFor(() => {
|
|
flushSync();
|
|
expect(selections()).toEqual([
|
|
['1', '12'],
|
|
['2', '22']
|
|
]);
|
|
});
|
|
expect(button('Continue').disabled).toBe(false);
|
|
expect(button('Add all eligible files').disabled).toBe(true);
|
|
button('Add all eligible files').click();
|
|
flushSync();
|
|
expect(document.querySelectorAll('.pick-row')).toHaveLength(2);
|
|
expect(fetch.mock.calls.map(([url]) => url)).toEqual([
|
|
'/api/scripts',
|
|
'/api/scripts/1',
|
|
'/api/scripts/2'
|
|
]);
|
|
});
|
|
|
|
it('preserves exact linked versions, including an older upload of a promoted file', async () => {
|
|
await show({ query: '?artifact=1:11&artifact=3:31' });
|
|
button('Add file').click();
|
|
flushSync();
|
|
button('Add all eligible files').click();
|
|
await vi.waitFor(() => {
|
|
flushSync();
|
|
expect(selections()).toEqual([
|
|
['1', '11'],
|
|
['3', '31'],
|
|
['2', '22']
|
|
]);
|
|
});
|
|
expect(button('Continue').disabled).toBe(false);
|
|
});
|
|
|
|
it('retains successful selections and blocks continuing when one file cannot load versions', async () => {
|
|
await show({ failingFile: 2 });
|
|
button('Add all eligible files').click();
|
|
await vi.waitFor(() => {
|
|
flushSync();
|
|
expect(document.querySelector('[role="alert"]')?.textContent).toContain(
|
|
'Could not load versions'
|
|
);
|
|
});
|
|
expect(selections()[0]).toEqual(['1', '12']);
|
|
expect(selections()[1][0]).toBe('2');
|
|
const failedVersion = document.querySelectorAll('.pick-row')[1].querySelectorAll('select')[1];
|
|
expect(failedVersion.selectedOptions[0].textContent).toBe('Choose a version');
|
|
expect(button('Continue').disabled).toBe(true);
|
|
});
|