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[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 | undefined; beforeEach(() => { const storage = new Map(); 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); });