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>
179 lines
6.0 KiB
Python
179 lines
6.0 KiB
Python
"""E2E: Mappings page with flat-row list, sorting, multi-CODMAT modal."""
|
|
import re
|
|
|
|
import pytest
|
|
from playwright.sync_api import Page, expect
|
|
|
|
pytestmark = pytest.mark.e2e
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def navigate_to_mappings(page: Page, app_url: str):
|
|
page.goto(f"{app_url}/mappings")
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
|
|
def test_mappings_page_loads(page: Page):
|
|
"""Verify mappings page renders."""
|
|
expect(page.locator("h4")).to_contain_text("Mapari SKU")
|
|
|
|
|
|
def test_flat_list_container_exists(page: Page):
|
|
"""Verify the flat-row list container is rendered."""
|
|
container = page.locator("#mappingsFlatList")
|
|
expect(container).to_be_visible()
|
|
# Should have at least one flat-row (data or empty message)
|
|
rows = container.locator(".flat-row")
|
|
assert rows.count() >= 1, "Expected at least one flat-row in the list"
|
|
|
|
|
|
def test_show_inactive_toggle_exists(page: Page):
|
|
"""R5: Verify 'Arata inactive' toggle is present."""
|
|
toggle = page.locator("#showInactive")
|
|
expect(toggle).to_be_visible()
|
|
label = page.locator("label[for='showInactive']")
|
|
expect(label).to_contain_text("Arata inactive")
|
|
|
|
|
|
def test_show_deleted_toggle_exists(page: Page):
|
|
"""Verify 'Arata sterse' toggle is present."""
|
|
toggle = page.locator("#showDeleted")
|
|
expect(toggle).to_be_visible()
|
|
label = page.locator("label[for='showDeleted']")
|
|
expect(label).to_contain_text("Arata sterse")
|
|
|
|
|
|
def test_add_modal_multi_codmat(page: Page):
|
|
"""R11: Verify the add mapping modal supports multiple CODMAT lines."""
|
|
# "Formular complet" opens the full modal
|
|
page.locator("button[data-bs-target='#addModal']").first.click()
|
|
page.wait_for_timeout(500)
|
|
|
|
codmat_lines = page.locator("#codmatLines .codmat-line")
|
|
assert codmat_lines.count() >= 1, "Expected at least one CODMAT line in modal"
|
|
|
|
# Click "+ CODMAT" button to add another line
|
|
page.locator("#addModal button", has_text="CODMAT").click()
|
|
page.wait_for_timeout(300)
|
|
assert codmat_lines.count() >= 2, "Expected a second CODMAT line after clicking + CODMAT"
|
|
|
|
# Second line must have a remove button
|
|
remove_btns = page.locator("#codmatLines .codmat-line:nth-child(2) .qm-rm-btn")
|
|
assert remove_btns.count() >= 1, "Second CODMAT line is missing remove button"
|
|
|
|
|
|
def test_search_input_exists(page: Page):
|
|
"""Verify search input is present with the correct placeholder."""
|
|
search = page.locator("#searchInput")
|
|
expect(search).to_be_visible()
|
|
expect(search).to_have_attribute("placeholder", "Cauta SKU, CODMAT sau denumire...")
|
|
|
|
|
|
def test_pagination_exists(page: Page):
|
|
"""Verify pagination containers are in DOM."""
|
|
expect(page.locator("#mappingsPagTop")).to_be_attached()
|
|
expect(page.locator("#mappingsPagBottom")).to_be_attached()
|
|
|
|
|
|
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()
|