Files
mudler--localai/core/http/react-ui/e2e/manage-action-menu-position.spec.js
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

51 lines
2.5 KiB
JavaScript

import { test, expect } from './coverage-fixtures.js'
// Regression: opening a row's kebab (ActionMenu) on /app/manage used to snap
// the page scroll to the top and render the menu detached from its trigger,
// making it impossible to operate. Two causes: the menu auto-focus scrolled
// the page (no preventScroll), and the position:fixed popover was rendered
// inside a row whose hover `transform` re-anchored it. Fix portals the popover
// to document.body, positions it before paint, and focuses without scrolling.
test.describe('Manage Page - Action menu positioning', () => {
test('opening a row menu keeps scroll stable and places the menu by its trigger', async ({ page }) => {
// Small viewport so the page is scrollable and a scroll jump is observable.
await page.setViewportSize({ width: 1024, height: 500 })
await page.goto('/app/manage')
await expect(page.locator('.table')).toBeVisible({ timeout: 10_000 })
const trigger = page.locator('button.action-menu__trigger').first()
await expect(trigger).toBeVisible()
// Bring the trigger into view ourselves first, so the only scroll we then
// measure is the one the menu would (wrongly) cause - not Playwright's own
// scroll-into-view before the click.
await trigger.scrollIntoViewIfNeeded()
const scrollBefore = await page.evaluate(() => window.scrollY)
await trigger.click()
const menu = page.locator('[role="menu"]')
await expect(menu).toBeVisible()
// Behavioural symptom 1: focusing the menu must not yank the page scroll.
const scrollAfter = await page.evaluate(() => window.scrollY)
expect(scrollAfter).toBe(scrollBefore)
// Behavioural symptom 2: the menu must sit next to its trigger, not float
// at the top of the window where it can't be operated.
const triggerBox = await trigger.boundingBox()
const menuBox = await menu.boundingBox()
expect(triggerBox).not.toBeNull()
expect(menuBox).not.toBeNull()
// Menu top is within ~24px of the trigger's bottom (below) or above it
// (flipped) — in all cases it tracks the trigger, never floating at y≈0.
const tracksTrigger =
Math.abs(menuBox.y - (triggerBox.y + triggerBox.height)) < 24 ||
Math.abs((menuBox.y + menuBox.height) - triggerBox.y) < 24
expect(tracksTrigger).toBe(true)
// Mechanism: the popover must be portaled to document.body so position:fixed
// resolves against the viewport, not a transformed ancestor row.
await expect(page.locator('body > .popover')).toHaveCount(1)
})
})