diff --git a/core/http/react-ui/e2e/backends-installed-node-removal.spec.js b/core/http/react-ui/e2e/backends-installed-node-removal.spec.js index b6bc50a79..bb04dcd84 100644 --- a/core/http/react-ui/e2e/backends-installed-node-removal.spec.js +++ b/core/http/react-ui/e2e/backends-installed-node-removal.spec.js @@ -407,15 +407,19 @@ for (const beforeConfirm of [false, true]) test(`generic deletion blocks targete if (beforeConfirm) { await clearTarget(page); await genericRemove(page).click() await expect(page.getByRole('alertdialog')).toBeVisible() - // Start the targeted request through its captured React handler while the - // existing generic confirmation remains open. + // Restore the target while the existing generic confirmation remains open. await page.evaluate(id => { const url = new URL(location.href); url.searchParams.set('target', id) history.pushState({}, '', url); dispatchEvent(new PopStateEvent('popstate')) }, ids[0]) } + // The generic and targeted buttons share a name; the URL can update before + // React replaces the generic control. Wait for the exact targeted handler. + await expect(reinstall(page)).toHaveAttribute('title', `Reinstall on ${ids[0]}`) + await expect(reinstall(page)).toBeEnabled() await reinstall(page).evaluate(el => el.click()) await expect.poll(() => s.installs.length).toBe(1) + expect(s.installs).toEqual([{ id: ids[0], method: 'POST', body: { backend: BUILD } }]) if (beforeConfirm) { await confirm(page).click() await expect(page.getByRole('alertdialog')).toBeHidden() diff --git a/core/http/react-ui/e2e/canonical-resource-navigation.spec.js b/core/http/react-ui/e2e/canonical-resource-navigation.spec.js index aae3749d3..0342fa03e 100644 --- a/core/http/react-ui/e2e/canonical-resource-navigation.spec.js +++ b/core/http/react-ui/e2e/canonical-resource-navigation.spec.js @@ -1,5 +1,13 @@ import { test, expect } from './coverage-fixtures.js' +async function backendInventory(page, installed) { + await page.route('**/api/backends?*', route => route.fulfill({ json: { + backends: [{ id: 'llama-cpp', name: 'llama-cpp', installed: true }], + } })) + await page.route('**/api/backends/upgrades', route => route.fulfill({ json: {} })) + await page.route('**/backends', route => route.fulfill({ json: installed })) +} + function urlState(page) { const url = new URL(page.url()) return { path: url.pathname, params: url.searchParams } @@ -22,9 +30,14 @@ test.describe('Canonical resource navigation', () => { }) test('redirects legacy backend management state including visibility flags', async ({ page }) => { + // The installed view clears selections absent from its inventory. + await backendInventory(page, [{ Name: 'llama-cpp', IsSystem: false }]) await page.goto('/app/manage?tab=backends&sel=llama-cpp&bq=llama&bf=user&bv=1&bd=1') - await expect.poll(() => urlState(page).path).toBe('/app/backends') + const row = page.locator('[data-entity="llama-cpp"]') + await expect(row).toBeVisible() + await expect(row).toHaveAttribute('data-selected', 'true') + await expect(page).toHaveURL('/app/backends?view=installed&backend=llama-cpp&q=llama&state=user&show_all=1&development=1') const state = urlState(page) expect(state.params.get('view')).toBe('installed') expect(state.params.get('backend')).toBe('llama-cpp') @@ -34,6 +47,14 @@ test.describe('Canonical resource navigation', () => { expect(state.params.get('development')).toBe('1') }) + test('clears a missing installed backend selection without losing URL refinements', async ({ page }) => { + await backendInventory(page, []) + await page.goto('/app/backends?view=installed&backend=llama-cpp&q=llama&state=user&show_all=1&development=1&target=worker-alpha') + + await expect(page).toHaveURL('/app/backends?view=installed&q=llama&state=user&show_all=1&development=1&target=worker-alpha') + await expect(page.locator('[data-entity="llama-cpp"]')).toHaveCount(0) + }) + test('defaults legacy management links to Installed Models', async ({ page }) => { await page.goto('/app/manage')