feat(member-live): sort the composite Member column by first or last name

This commit is contained in:
Simon 2026-07-16 09:39:07 +02:00
parent ca2aaa069c
commit f7005f395f
11 changed files with 443 additions and 34 deletions

View file

@ -52,13 +52,13 @@ defmodule MvWeb.MemberLive.IndexSortNameAddressTest do
end
test "name sort orders by last name then first name, ignoring email", %{actor: actor} do
asc = OverviewQuery.build(%{sort_field: :name, sort_order: :asc}) |> read_names(actor)
asc = OverviewQuery.build(%{sort_field: :last_name, sort_order: :asc}) |> read_names(actor)
# Both share last name "Meyer"; the first-name tie-breaker orders Anna before
# Bruno, even though Bruno's email sorts first alphabetically.
assert asc == [{"Meyer", "Anna"}, {"Meyer", "Bruno"}]
desc = OverviewQuery.build(%{sort_field: :name, sort_order: :desc}) |> read_names(actor)
desc = OverviewQuery.build(%{sort_field: :last_name, sort_order: :desc}) |> read_names(actor)
assert desc == [{"Meyer", "Bruno"}, {"Meyer", "Anna"}]
end
@ -81,15 +81,48 @@ defmodule MvWeb.MemberLive.IndexSortNameAddressTest do
test "clicking the Name header sorts and reflects state in URL and aria-sort", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/members")
# The composite header sorts by its active sub-field (last name by default).
view |> element("button[phx-click='sort'][data-testid='name']") |> render_click()
path = assert_patch(view)
assert path =~ "sort_field=name"
# Exactly one column carries aria-sort (§1.11).
assert has_element?(view, "th[aria-sort='ascending']")
assert path =~ "sort_field=last_name"
# Last name is already the default sort (ascending), so the click toggles it
# to descending. Exactly one column carries aria-sort (§1.11).
assert has_element?(view, "th[aria-sort='descending']")
assert render(view) |> aria_sort_count() == 1
end
test "the sub-field menu sorts ascending by the chosen field without toggling", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/members")
# Pick "first name" from the composite sub-field menu.
view |> element("[data-testid='member-sort-first_name']") |> render_click()
path = assert_patch(view)
assert path =~ "sort_field=first_name"
assert path =~ "sort_order=asc"
assert has_element?(view, "th[aria-sort='ascending']")
# Selecting the same sub-field again keeps it ascending: the menu switches the
# sort key, it does not toggle direction (that is what a header click does).
view |> element("[data-testid='member-sort-first_name']") |> render_click()
path2 = assert_patch(view)
assert path2 =~ "sort_field=first_name"
assert path2 =~ "sort_order=asc"
end
test "after choosing a sub-field, a header click toggles that field's direction", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/members")
view |> element("[data-testid='member-sort-first_name']") |> render_click()
assert_patch(view)
# Now sorting by first name ascending; a click on the header toggles to desc.
view |> element("button[phx-click='sort'][data-testid='name']") |> render_click()
path = assert_patch(view)
assert path =~ "sort_field=first_name"
assert path =~ "sort_order=desc"
end
test "clicking the Address header sorts and reflects state in URL", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/members")

View file

@ -221,11 +221,11 @@ defmodule MvWeb.MemberLive.IndexTest do
{:ok, view, _html} =
live(
conn,
"/members?fields=first_name,email,street,house_number,postal_code,city,country,join_date"
"/members?fields=last_name,first_name,email,street,house_number,postal_code,city,country,join_date"
)
# default ascending sorting with first name
assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")
# default ascending sorting on the default sort field (last name)
assert has_element?(view, "[data-testid='last_name'][aria-label='ascending']")
sortable_fields = [
:email,
@ -295,10 +295,10 @@ defmodule MvWeb.MemberLive.IndexTest do
conn = conn |> conn_with_oidc_user() |> non_compact()
{:ok, view, _html} =
live(conn, "/members?fields=first_name&query=&sort_field=invalid_field&sort_order=asc")
live(conn, "/members?fields=last_name&query=&sort_field=invalid_field&sort_order=asc")
# Should not crash and should show default first name order
assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")
# Should not crash and should fall back to the default sort (last name)
assert has_element?(view, "[data-testid='last_name'][aria-label='ascending']")
end
test "handle_params preserves search query with sort params", %{conn: conn} do