From 5fe1f6499b24a21e970d4857263d9aff5453d7b0 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 2 Jul 2026 22:03:52 -0500 Subject: [PATCH] test(qa): B-UI primitive behavior spec (dialog/select/disabled) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - e2e/b-ui.spec.js: functional checks axe can't assert — Add Bill dialog opens with a focus trap and Esc cancels with no bill created (Cancel = no side effect); the category Select opens by mouse and keyboard and lists options; the sort-direction button stays inert (disabled) in Custom order. Read-only, so safe in the parallel suite. Directly covers the B-UI batch. Co-Authored-By: Claude Opus 4.8 --- e2e/b-ui.spec.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 e2e/b-ui.spec.js diff --git a/e2e/b-ui.spec.js b/e2e/b-ui.spec.js new file mode 100644 index 0000000..a0ff316 --- /dev/null +++ b/e2e/b-ui.spec.js @@ -0,0 +1,62 @@ +// B-UI: functional behavior of shared primitives that axe/a11y can't assert — +// dialogs cancel without side effects, Selects open by mouse and keyboard, +// disabled controls stay inert. All checks are READ-ONLY (Esc/Cancel/reopen), so +// they're safe alongside the other UI specs in the parallel suite. +const { test, expect } = require('@playwright/test'); +const { STORAGE_STATE } = require('./constants'); + +test.use({ storageState: STORAGE_STATE }); + +test('dialog: Add Bill opens, Esc closes it, and creates nothing (Cancel = no side effect)', async ({ page }) => { + await page.goto('/'); + const badges = page.locator('button[title="Click to mark paid"]:visible, button[title="Click to mark unpaid"]:visible'); + await expect(badges.first()).toBeVisible(); // wait for bills to load before counting + const before = await badges.count(); + + await page.getByRole('button', { name: 'Add Bill' }).click(); + const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + // Focus moved into the dialog (focus trap). + await expect(dialog.locator(':focus')).toHaveCount(1); + + await page.keyboard.press('Escape'); + await expect(dialog).toBeHidden(); + // No bill was created and the page is still functional. + await expect(page.getByRole('button', { name: 'Add Bill' })).toBeVisible(); + await expect.poll(() => badges.count()).toBe(before); +}); + +test('select: category filter opens by mouse and by keyboard and lists options', async ({ page, isMobile }) => { + test.skip(isMobile, 'filter panel layout differs on mobile'); + await page.goto('/'); + + // Radix Select trigger exposes role="combobox". + const trigger = page.getByRole('combobox', { name: 'Filter by category' }); + await expect(trigger).toBeVisible(); + + // Mouse: opens a listbox with the "All categories" option. + await trigger.click(); + const listbox = page.getByRole('listbox'); + await expect(listbox).toBeVisible(); + await expect(page.getByRole('option', { name: 'All categories' })).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(listbox).toBeHidden(); + + // Keyboard: focus the trigger and open with Enter. + await trigger.focus(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('listbox')).toBeVisible(); + await page.keyboard.press('Escape'); +}); + +test('disabled control: sort-direction button is inert in Custom order', async ({ page, isMobile }) => { + test.skip(isMobile, 'filter panel layout differs on mobile'); + await page.goto('/'); + // Default sort is "Custom order", for which the asc/desc toggle is disabled. + const dir = page.getByRole('button', { name: 'Asc' }); + await expect(dir).toBeVisible(); + await expect(dir).toBeDisabled(); + // Clicking a disabled control must be a no-op (force past the actionability guard). + await dir.click({ force: true }); + await expect(dir).toBeDisabled(); +});