fix(autocomplete): add keyboard navigation and fix scroll/blur in all CODMAT dropdowns

Extract shared setupAutocomplete() into shared.js so all three autocomplete
instances (mappings modal, inline add, quick-map modal) get keyboard nav
(ArrowDown/Up/Enter/Escape), scroll-safe blur handling, and capture-phase
keydown to prevent browser interception. Remove old onmousedown inline
handlers, use data-codmat/data-label attributes instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-09 16:07:49 +00:00
parent 84e5d55592
commit 520f0836bf
6 changed files with 242 additions and 62 deletions

View File

@@ -1,4 +1,6 @@
"""E2E: Mappings page with flat-row list, sorting, multi-CODMAT modal."""
import re
import pytest
from playwright.sync_api import Page, expect
@@ -77,3 +79,100 @@ def test_inline_add_button_exists(page: Page):
"""Verify 'Adauga Mapare' button is present."""
btn = page.locator("button", has_text="Adauga Mapare")
expect(btn).to_be_visible()
# ── Autocomplete keyboard & scroll tests ─────────
MOCK_ARTICLES = [
{"codmat": f"ART{i:03}", "denumire": f"Articol Test {i}", "um": "BUC"}
for i in range(1, 20)
]
@pytest.fixture
def mock_articles(page: Page):
"""Mock /api/articles/search to return test data without Oracle."""
def handle(route):
route.fulfill(json={"results": MOCK_ARTICLES})
page.route("**/api/articles/search*", handle)
yield
page.unroute("**/api/articles/search*")
def _open_modal_and_type(page: Page, query: str = "ART"):
"""Open add-modal, type in CODMAT input, wait for dropdown."""
page.locator("button[data-bs-target='#addModal']").first.click()
page.wait_for_timeout(400)
codmat_input = page.locator("#codmatLines .cl-codmat").first
codmat_input.fill(query)
# Wait for debounce + render
page.wait_for_timeout(400)
return codmat_input
def test_autocomplete_keyboard_navigation(page: Page, mock_articles):
"""ArrowDown/Up moves .active class, Enter selects."""
codmat_input = _open_modal_and_type(page)
dropdown = page.locator("#codmatLines .cl-ac-dropdown").first
expect(dropdown).to_be_visible()
# ArrowDown → first item active
codmat_input.press("ArrowDown")
first_item = dropdown.locator(".autocomplete-item").first
expect(first_item).to_have_class(re.compile("active"))
# ArrowDown again → second item active
codmat_input.press("ArrowDown")
second_item = dropdown.locator(".autocomplete-item").nth(1)
expect(second_item).to_have_class(re.compile("active"))
expect(first_item).not_to_have_class(re.compile("active"))
# ArrowUp → back to first
codmat_input.press("ArrowUp")
expect(first_item).to_have_class(re.compile("active"))
# Enter → selects the item
codmat_input.press("Enter")
expect(dropdown).to_be_hidden()
assert codmat_input.input_value() == "ART001"
def test_autocomplete_escape_closes(page: Page, mock_articles):
"""Escape closes dropdown."""
codmat_input = _open_modal_and_type(page)
dropdown = page.locator("#codmatLines .cl-ac-dropdown").first
expect(dropdown).to_be_visible()
codmat_input.press("Escape")
expect(dropdown).to_be_hidden()
def test_autocomplete_scroll_keeps_open(page: Page, mock_articles):
"""Mouse wheel on dropdown doesn't close it (blur fix)."""
codmat_input = _open_modal_and_type(page)
dropdown = page.locator("#codmatLines .cl-ac-dropdown").first
expect(dropdown).to_be_visible()
# Scroll inside the dropdown via mouse wheel
dropdown.evaluate("el => el.scrollTop = 100")
page.wait_for_timeout(300)
# Dropdown should still be visible
expect(dropdown).to_be_visible()
def test_autocomplete_click_outside_closes(page: Page, mock_articles):
"""Click outside closes dropdown (Tab away moves focus)."""
codmat_input = _open_modal_and_type(page)
dropdown = page.locator("#codmatLines .cl-ac-dropdown").first
expect(dropdown).to_be_visible()
# Tab away from the input to trigger blur
codmat_input.press("Tab")
page.wait_for_timeout(300)
expect(dropdown).to_be_hidden()