diff --git a/docs/requirements/e2e-test-guidelines.md b/docs/requirements/e2e-test-guidelines.md index efbdfcc3d6..79b1a067a6 100644 --- a/docs/requirements/e2e-test-guidelines.md +++ b/docs/requirements/e2e-test-guidelines.md @@ -67,6 +67,16 @@ page.locator(".mx-name-myForm").getByRole("button", { name: "Save" }); page.locator(".mx-name-myWidget").getByLabel("Start date"); ``` +## Keyboard Interaction + +| Don't | Do Instead | Why | +| ---------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `press("Escape")` on a page opened as a popup | Reach the state another way, or test Escape on a plain page | The Mendix client closes the popup on Escape — widgets that only `preventDefault()` still let it bubble, so the widget unmounts mid-test | +| `input.click()` to focus a field that may be empty | Click a stable sibling that focuses it, or `locator.focus()` | Widgets collapse empty unfocused inputs (e.g. `max-width: 0`), and Playwright cannot click a zero-sized box | +| `container.click()` when children own click handlers | Click a child with no handler of its own | Clicks land on the element's centre; a child under that point receives the event instead | + +A widget losing focus to `document.body` mid-test looks identical to a locator bug — assert `toBeFocused()` on the expected element after each keystroke so the step that actually broke is named. + ## Screenshot Testing - No per-test `{ threshold: N }` or `{ maxDiffPixels: N }` overrides — use global config (`threshold: 0.1`) diff --git a/packages/pluggableWidgets/combobox-web/CHANGELOG.md b/packages/pluggableWidgets/combobox-web/CHANGELOG.md index f648423ef0..fdca9b58cb 100644 --- a/packages/pluggableWidgets/combobox-web/CHANGELOG.md +++ b/packages/pluggableWidgets/combobox-web/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed an issue where selecting all of the typed text in a multi-select combobox and pressing Backspace did not permanently clear it, so the text reappeared after clicking away and back into the combobox. + +- We fixed an issue where removing a selected item of a multi-select combobox with Backspace or Delete moved keyboard focus out of the combobox, unless the removed item was the last one. Focus now stays on the selected items so you can keep navigating and removing them with the keyboard. + ## [2.9.0] - 2026-07-24 ### Fixed diff --git a/packages/pluggableWidgets/combobox-web/e2e/ComboboxMultiSelectionKeys.spec.js b/packages/pluggableWidgets/combobox-web/e2e/ComboboxMultiSelectionKeys.spec.js new file mode 100644 index 0000000000..c5759241d9 --- /dev/null +++ b/packages/pluggableWidgets/combobox-web/e2e/ComboboxMultiSelectionKeys.spec.js @@ -0,0 +1,213 @@ +import { expect, test } from "@mendix/run-e2e/fixtures"; + +// NOTE for anyone extending this file: do not press Escape. These tests run on a Mendix popup +// page (opened by .mx-name-actionButton1) and the client closes that popup on Escape. The +// combobox's own Escape handler only calls preventDefault(), so the keydown still bubbles and +// the whole widget leaves the DOM mid-test, which shows up as chips silently dropping to 0. +// Everything below therefore leaves the menu open, which is also where the filter input already +// holds focus after an option is picked. + +/** + * Leaves the combobox with exactly `count` chips, the menu open and the filter input focused. + * + * The test page ships this combobox with options already selected, so tests that address chips + * by index clear it first and re-select from the top. Nothing is persisted — the popup's Save + * button is never pressed — so this stays local to the test. In row click selection mode + * selected options drop out of the menu, hence clicking the first option each pass picks a new + * one. + */ +async function selectChips(comboBox, count) { + const chips = comboBox.locator(".widget-combobox-selected-item"); + const options = comboBox.locator("[role=listbox] [role=option]"); + + // Only the widget-level clear button is a