feat(member): condense the overview into composite name and address cells

This commit is contained in:
Simon 2026-07-03 11:35:46 +02:00
parent b79d7ac9ea
commit af2cc2e0d4
17 changed files with 716 additions and 93 deletions

View file

@ -2,10 +2,14 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
use MvWeb.ConnCase, async: true
import Phoenix.LiveViewTest
# The curated default columns hide the individual name/address sort headers,
# so these component tests make every sortable field visible via ?fields=.
@cols "first_name,email,street,house_number,postal_code,city,country,join_date"
describe "rendering" do
test "renders with correct attributes", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Test that the component renders with correct attributes
assert has_element?(view, "[data-testid='first_name']")
@ -15,7 +19,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "renders all sortable headers", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
sortable_fields = [
:first_name,
@ -35,7 +39,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "renders correct labels", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Test specific labels
assert has_element?(view, "button[phx-value-field='first_name']", "First name")
@ -47,7 +51,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
describe "sort icons" do
test "shows neutral icon for specific field when not sorted", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# The neutral icon has the opcity class we can test for
# Test that EMAIL field specifically shows neutral icon
@ -59,7 +63,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "shows ascending icon for specific field when sorted ascending", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?query=&sort_field=city&sort_order=asc")
{:ok, view, html} =
live(conn, "/members?fields=#{@cols}&query=&sort_field=city&sort_order=asc")
# Test that FIRST_NAME field specifically shows ascending icon
# Test CSS classes - no opacity for active state
@ -80,7 +86,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "shows descending icon for specific field when sorted descending", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")
{:ok, _view, html} =
live(conn, "/members?fields=#{@cols}&query=&sort_field=email&sort_order=desc")
# Count occurrences to ensure only one descending sort icon. Dropdown
# triggers carry their own trailing "hero-chevron-down size-4" chevron, so
@ -92,7 +100,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "multiple fields can have different icon states", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=&sort_field=city&sort_order=asc")
{:ok, view, _html} =
live(conn, "/members?fields=#{@cols}&query=&sort_field=city&sort_order=asc")
# CITY field should be active (ascending)
refute has_element?(view, "[data-testid='city'] .opacity-40")
@ -109,7 +119,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "icon state changes correctly when clicking different fields", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Start: all fields neutral except first name as default
assert has_element?(view, "[data-testid='city'] .opacity-40")
@ -139,15 +149,19 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
conn = conn_with_oidc_user(conn)
# Test EMAIL field specifically
{:ok, view, html_asc} = live(conn, "/members?sort_field=email&sort_order=asc")
{:ok, view, html_asc} =
live(conn, "/members?fields=#{@cols}&sort_field=email&sort_order=asc")
assert html_asc =~ "hero-chevron-up"
refute has_element?(view, "[data-testid='email'] .opacity-40")
{:ok, view, html_desc} = live(conn, "/members?sort_field=email&sort_order=desc")
{:ok, view, html_desc} =
live(conn, "/members?fields=#{@cols}&sort_field=email&sort_order=desc")
assert html_desc =~ "hero-chevron-down"
refute has_element?(view, "[data-testid='email'] .opacity-40")
{:ok, view, html_neutral} = live(conn, "/members")
{:ok, view, html_neutral} = live(conn, "/members?fields=#{@cols}")
assert html_neutral =~ "hero-chevron-up-down"
assert has_element?(view, "[data-testid='email'] .opacity-40")
end
@ -156,7 +170,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
conn = conn_with_oidc_user(conn)
# Test neutral state - only one field should have active sort icon
{:ok, _view, html_neutral} = live(conn, "/members")
{:ok, _view, html_neutral} = live(conn, "/members?fields=#{@cols}")
# Count active icons (should be exactly 1 - ascending for default sort field)
up_count = html_neutral |> String.split("hero-chevron-up ") |> length() |> Kernel.-(1)
@ -166,7 +180,8 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
assert down_count == 0, "Expected 0 descending icons, got #{down_count}"
# Test descending state
{:ok, _view, html_desc} = live(conn, "/members?sort_field=first_name&sort_order=desc")
{:ok, _view, html_desc} =
live(conn, "/members?fields=#{@cols}&sort_field=first_name&sort_order=desc")
up_count = html_desc |> String.split("hero-chevron-up ") |> length() |> Kernel.-(1)
down_count = active_sort_down_count(html_desc)
@ -190,7 +205,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
describe "accessibility" do
test "sets aria-label correctly for unsorted state", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Check aria-label for unsorted state
assert has_element?(view, "button[phx-value-field='city'][aria-label='Click to sort']")
@ -198,7 +213,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "sets aria-label correctly for ascending sort", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=asc")
{:ok, view, _html} =
live(conn, "/members?fields=#{@cols}&sort_field=first_name&sort_order=asc")
# Check aria-label for ascending sort
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='ascending']")
@ -206,7 +223,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "sets aria-label correctly for descending sort", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=desc")
{:ok, view, _html} =
live(conn, "/members?fields=#{@cols}&sort_field=first_name&sort_order=desc")
# Check aria-label for descending sort
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='descending']")
@ -214,7 +233,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "includes tooltip with correct aria-label", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=asc")
{:ok, view, _html} =
live(conn, "/members?fields=#{@cols}&sort_field=first_name&sort_order=asc")
# Check that tooltip div exists with correct data-tip
assert has_element?(view, "[data-testid='first_name']")
@ -223,7 +244,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "aria-labels work for all sortable fields", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=email&sort_order=desc")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}&sort_field=email&sort_order=desc")
# Test aria-labels for different fields
assert has_element?(view, "button[phx-value-field='email'][aria-label='descending']")
@ -240,7 +261,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
describe "component behavior" do
test "clicking triggers sort event on parent LiveView", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Click on the first name sort header
view
@ -253,7 +274,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "component handles different field types correctly", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Test that different field types render correctly
assert has_element?(view, "button[phx-value-field='first_name']")
@ -265,7 +286,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
describe "edge cases" do
test "handles invalid sort field gracefully", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=invalid_field&sort_order=asc")
{:ok, view, html} =
live(conn, "/members?fields=#{@cols}&sort_field=invalid_field&sort_order=asc")
# Should not crash and should default sorting for first name
assert html =~ "hero-chevron-up-down"
@ -274,7 +297,9 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "handles invalid sort order gracefully", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=first_name&sort_order=invalid")
{:ok, view, html} =
live(conn, "/members?fields=#{@cols}&sort_field=first_name&sort_order=invalid")
# Should default to ascending
assert html =~ "hero-chevron-up"
@ -283,7 +308,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "handles empty sort parameters", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=&sort_order=")
{:ok, view, html} = live(conn, "/members?fields=#{@cols}&sort_field=&sort_order=")
# Should show neutral icons
assert html =~ "hero-chevron-up-down"
@ -294,7 +319,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
describe "icon state transitions" do
test "icon changes when sorting state changes", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
{:ok, view, _html} = live(conn, "/members?fields=#{@cols}")
# Start with neutral state
assert has_element?(view, "[data-testid='city'] .opacity-40")
@ -310,7 +335,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
test "multiple fields can be tested for icon states", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=email&sort_order=desc")
{:ok, view, html} = live(conn, "/members?fields=#{@cols}&sort_field=email&sort_order=desc")
# Email should be active (descending)
assert html =~ "hero-chevron-down"