Add clickable facet pills for mobile-friendly DSL filter composition
All checks were successful
Deploy / deploy (push) Successful in 1m50s
All checks were successful
Deploy / deploy (push) Successful in 1m50s
- Create reusable dsl_facets.py component with clickable pills that compose DSL filter expressions by appending field:value to the filter input - Add /api/facets endpoint for dynamic facet count refresh via HTMX - Fix select dropdown dark mode styling with color-scheme: dark in SelectStyles - Integrate facet pills into all DSL filter screens: registry, move, and all action forms (tag-add, tag-end, attrs, outcome, status-correct) - Update routes to fetch and pass facet counts, locations, and species - Add comprehensive unit tests for component and API endpoint - Add E2E tests for facet pill click behavior and dark mode select visibility This enables tap-based filter composition on mobile without requiring typing. Facet counts update dynamically as filters are applied via HTMX. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
192
tests/e2e/test_facet_pills.py
Normal file
192
tests/e2e/test_facet_pills.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# 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 updates to selection container
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
# Selection container should show filtered animals
|
||||
selection_container = page.locator("#selection-container")
|
||||
expect(selection_container).to_be_visible()
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user