Files
animaltrack/tests/e2e/test_facet_pills.py
Petru Paler 034aa6e0bf
All checks were successful
Deploy / deploy (push) Successful in 1m48s
Fix facet pills replacing body instead of self on HTMX update
Add hx_target="this" to the dsl_facet_pills container to prevent HTMX
from inheriting hx_target="body" from the parent wrapper. Without this,
clicking a facet pill would cause the facet refresh to replace the entire
body with just the pills HTML, breaking forms on pages like /actions/outcome.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 18:53:01 +00:00

232 lines
9.5 KiB
Python

# ABOUTME: E2E tests for DSL facet pills component.
# ABOUTME: Tests click-to-filter, dynamic count updates, and dark mode visibility.
import pytest
from playwright.sync_api import Page, expect
pytestmark = pytest.mark.e2e
class TestFacetPillsOnMoveForm:
"""Test facet pills functionality on the move form."""
def test_facet_pills_visible_on_move_page(self, page: Page, live_server):
"""Verify facet pills section is visible on move page."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Should see facet pills container
facet_container = page.locator("#dsl-facet-pills")
expect(facet_container).to_be_visible()
def test_click_species_facet_updates_filter(self, page: Page, live_server):
"""Clicking a species facet pill updates the filter input."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Click on a species facet pill (e.g., duck)
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Filter input should now contain species:duck
filter_input = page.locator("#filter")
expect(filter_input).to_have_value("species:duck")
def test_click_multiple_facets_composes_filter(self, page: Page, live_server):
"""Clicking multiple facet pills composes the filter."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Click species facet
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Click sex facet
female_pill = page.locator('[data-facet-field="sex"][data-facet-value="female"]')
expect(female_pill).to_be_visible()
female_pill.click()
# Filter should contain both
filter_input = page.locator("#filter")
filter_value = filter_input.input_value()
assert "species:duck" in filter_value
assert "sex:female" in filter_value
def test_facet_counts_update_after_filter(self, page: Page, live_server):
"""Facet counts update dynamically when filter changes."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Get initial species counts
facet_container = page.locator("#dsl-facet-pills")
expect(facet_container).to_be_visible()
# Click species:duck to filter
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
duck_pill.click()
# Wait for HTMX updates
page.wait_for_timeout(1000)
# Facet counts should have updated - only alive duck-related counts shown
# The sex facet should now show counts for ducks only
sex_section = page.locator("#dsl-facet-pills").locator("text=Sex").locator("..")
expect(sex_section).to_be_visible()
def test_selection_preview_updates_after_facet_click(self, page: Page, live_server):
"""Selection preview updates after clicking a facet pill."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Click species facet
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Wait for HTMX to complete the network request
page.wait_for_load_state("networkidle")
# Selection container should have content after filter is applied
# The container always exists, but content is added via HTMX
selection_container = page.locator("#selection-container")
# Verify container has some text content (animal names or count)
content = selection_container.text_content() or ""
assert len(content) > 0, "Selection container should have content after facet click"
class TestFacetPillsOnOutcomeForm:
"""Test facet pills functionality on the outcome form."""
def test_facet_pills_visible_on_outcome_page(self, page: Page, live_server):
"""Verify facet pills section is visible on outcome page."""
page.goto(f"{live_server.url}/actions/outcome")
expect(page.locator("body")).to_be_visible()
# Should see facet pills container
facet_container = page.locator("#dsl-facet-pills")
expect(facet_container).to_be_visible()
def test_click_facet_on_outcome_form(self, page: Page, live_server):
"""Clicking a facet pill on outcome form updates filter."""
page.goto(f"{live_server.url}/actions/outcome")
expect(page.locator("body")).to_be_visible()
# Click on a species facet pill
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Filter input should now contain species:duck
filter_input = page.locator("#filter")
expect(filter_input).to_have_value("species:duck")
def test_facet_click_preserves_form_structure(self, page: Page, live_server):
"""Clicking a facet pill should not replace the form with just pills.
Regression test: Without hx_target="this" on the facet pills container,
HTMX inherits hx_target="body" from the parent and replaces the entire
page body with just the facet pills HTML.
"""
page.goto(f"{live_server.url}/actions/outcome")
expect(page.locator("body")).to_be_visible()
# Verify form elements are visible before clicking facet
outcome_select = page.locator("#outcome")
expect(outcome_select).to_be_visible()
filter_input = page.locator("#filter")
expect(filter_input).to_be_visible()
# Click a facet pill
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Wait for HTMX to complete the facet refresh (600ms delay + network time)
# The facet pills use hx_trigger="change delay:600ms" so we must wait
page.wait_for_timeout(1000)
page.wait_for_load_state("networkidle")
# Form elements should still be visible after facet pills refresh
# If this fails, the body was replaced with just the facet pills
expect(outcome_select).to_be_visible()
expect(filter_input).to_be_visible()
# Verify the form can still be submitted (submit button visible)
submit_button = page.locator('button[type="submit"]')
expect(submit_button).to_be_visible()
class TestFacetPillsOnTagAddForm:
"""Test facet pills functionality on the tag add form."""
def test_facet_pills_visible_on_tag_add_page(self, page: Page, live_server):
"""Verify facet pills section is visible on tag add page."""
page.goto(f"{live_server.url}/actions/tag-add")
expect(page.locator("body")).to_be_visible()
# Should see facet pills container
facet_container = page.locator("#dsl-facet-pills")
expect(facet_container).to_be_visible()
class TestFacetPillsOnRegistry:
"""Test facet pills on registry replace existing facets."""
def test_registry_facet_pills_visible(self, page: Page, live_server):
"""Verify facet pills appear in registry sidebar."""
page.goto(f"{live_server.url}/registry")
expect(page.locator("body")).to_be_visible()
# Should see facet pills in sidebar
facet_container = page.locator("#dsl-facet-pills")
expect(facet_container).to_be_visible()
def test_registry_facet_click_updates_filter(self, page: Page, live_server):
"""Clicking a facet in registry updates the filter."""
page.goto(f"{live_server.url}/registry")
expect(page.locator("body")).to_be_visible()
# Click on species facet
duck_pill = page.locator('[data-facet-field="species"][data-facet-value="duck"]')
expect(duck_pill).to_be_visible()
duck_pill.click()
# Filter input should be updated
filter_input = page.locator("#filter")
expect(filter_input).to_have_value("species:duck")
class TestSelectDarkMode:
"""Test select dropdown visibility in dark mode."""
def test_select_options_visible_on_move_form(self, page: Page, live_server):
"""Verify select dropdown options are readable in dark mode."""
page.goto(f"{live_server.url}/move")
expect(page.locator("body")).to_be_visible()
# Click to open destination dropdown
select = page.locator("#to_location_id")
expect(select).to_be_visible()
# Check the select has proper dark mode styling
# Note: We check computed styles to verify color-scheme is set
color_scheme = select.evaluate("el => window.getComputedStyle(el).colorScheme")
# Should have dark color scheme for native dark mode option styling
assert "dark" in color_scheme.lower() or color_scheme == "auto"
def test_outcome_select_options_visible(self, page: Page, live_server):
"""Verify outcome dropdown options are readable."""
page.goto(f"{live_server.url}/actions/outcome")
expect(page.locator("body")).to_be_visible()
# Check outcome dropdown has proper styling
select = page.locator("#outcome")
expect(select).to_be_visible()
# Verify the select can be interacted with
select.click()
expect(select).to_be_focused()