feat(member): let members tailor overview density and visible columns
The View dropdown drives row density and whether the Member and Address fields render as composite cells or split into their underlying columns; the column manager toggles visibility and resets to the curated default. All choices persist per browser through the URL/session/cookie/global chain, so a saved layout survives reloads without a per-account store.
This commit is contained in:
parent
af2cc2e0d4
commit
dfc616257d
21 changed files with 1643 additions and 263 deletions
78
test/mv_web/member_live/index_a11y_hardening_test.exs
Normal file
78
test/mv_web/member_live/index_a11y_hardening_test.exs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
defmodule MvWeb.MemberLive.IndexA11yHardeningTest do
|
||||
@moduledoc """
|
||||
§3.5 — WCAG 2.2 AA posture: ≥24px interactive targets in both densities,
|
||||
scroll-margin on rows so a focused row is not obscured by the sticky header,
|
||||
select-all `indeterminate` for partial selection, and reflow markup keeping the
|
||||
table in its own focusable scroll region.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Mv.Helpers.SystemActor
|
||||
|
||||
@moduletag :ui
|
||||
|
||||
setup %{conn: conn} do
|
||||
actor = SystemActor.get_system_actor()
|
||||
|
||||
members =
|
||||
for i <- 1..3 do
|
||||
{:ok, m} =
|
||||
Mv.Membership.create_member(
|
||||
%{first_name: "Wcag#{i}", last_name: "Row", email: "wcag#{i}@example.com"},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
m
|
||||
end
|
||||
|
||||
%{conn: conn_with_oidc_user(conn), members: members}
|
||||
end
|
||||
|
||||
test "rows reserve scroll-margin under the sticky header", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/members")
|
||||
assert html =~ "scroll-mt-16"
|
||||
end
|
||||
|
||||
test "the table lives in its own focusable scroll region that reflows", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/members")
|
||||
|
||||
# Focusable region (1.4.10: table scrolls within its own focusable region).
|
||||
assert html =~ ~r/data-testid="members-table-scroll"[^>]*tabindex="0"/
|
||||
# The toolbar reflows to one column on narrow viewports.
|
||||
assert html =~ "flex-wrap"
|
||||
end
|
||||
|
||||
test "interactive row targets meet the >=24px minimum in both densities", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
# Compact (default) and comfortable both keep the >=24px checkbox target.
|
||||
assert has_element?(view, "input[type='checkbox'].min-h-6.min-w-6")
|
||||
|
||||
# Switch to comfortable via the view-settings dropdown.
|
||||
view |> element("[data-testid='view-settings-button']") |> render_click()
|
||||
view |> element("[data-testid='view-setting-density']") |> render_click()
|
||||
assert has_element?(view, "input[type='checkbox'].min-h-6.min-w-6")
|
||||
end
|
||||
|
||||
test "select-all reflects indeterminate for a partial selection", %{
|
||||
conn: conn,
|
||||
members: members
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
# No selection: not indeterminate.
|
||||
assert has_element?(view, "#select-all-checkbox[data-indeterminate='false']")
|
||||
|
||||
# Select one of several -> partial -> indeterminate.
|
||||
[m | _] = members
|
||||
render_click(view, "select_member", %{"id" => m.id})
|
||||
assert has_element?(view, "#select-all-checkbox[data-indeterminate='true']")
|
||||
|
||||
# Select all -> fully checked, not indeterminate.
|
||||
view |> element("[phx-click='select_all']") |> render_click()
|
||||
assert has_element?(view, "#select-all-checkbox[data-indeterminate='false']")
|
||||
assert has_element?(view, "#select-all-checkbox[checked]")
|
||||
end
|
||||
end
|
||||
76
test/mv_web/member_live/index_column_manager_test.exs
Normal file
76
test/mv_web/member_live/index_column_manager_test.exs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
defmodule MvWeb.MemberLive.IndexColumnManagerTest do
|
||||
@moduledoc """
|
||||
§1.6 — The column manager sits in the filter toolbar, offers a per-column
|
||||
visibility toggle plus All and None controls styled as buttons (not links),
|
||||
and applies changes while preserving the existing persistence chain.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Mv.Helpers.SystemActor
|
||||
|
||||
setup %{conn: conn} do
|
||||
{:ok, _} =
|
||||
Mv.Membership.create_member(
|
||||
%{first_name: "Manager", last_name: "Columns", email: "manager@example.com"},
|
||||
actor: SystemActor.get_system_actor()
|
||||
)
|
||||
|
||||
%{conn: conn_with_oidc_user(conn)}
|
||||
end
|
||||
|
||||
test "column manager trigger sits alongside the filter controls", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
# Both the filter panel trigger and the column-manager trigger render in the
|
||||
# same toolbar.
|
||||
assert has_element?(view, ~s(button[aria-label="Filter members"]))
|
||||
assert has_element?(view, "button[aria-controls='field-visibility-menu']")
|
||||
end
|
||||
|
||||
test "All and None are buttons, not links, and per-column toggles are checkboxes", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
view |> element("button[aria-controls='field-visibility-menu']") |> render_click()
|
||||
|
||||
# All / None render as buttons with button styling.
|
||||
assert has_element?(view, "button.btn[phx-click='select_all']", "All")
|
||||
assert has_element?(view, "button.btn[phx-click='select_none']", "None")
|
||||
refute has_element?(view, "a[phx-click='select_all']")
|
||||
refute has_element?(view, "a[phx-click='select_none']")
|
||||
|
||||
# Per-column visibility toggles are accessible checkbox menu items.
|
||||
assert has_element?(view, "button[role='menuitemcheckbox'][phx-value-item='name']")
|
||||
end
|
||||
|
||||
test "toggling a column applies and is reflected in the URL (persistence)", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
view |> element("button[aria-controls='field-visibility-menu']") |> render_click()
|
||||
view |> element("button[phx-value-item='join_date']") |> render_click()
|
||||
|
||||
# The selection is pushed to the URL fields param so it survives reloads.
|
||||
path = assert_patch(view)
|
||||
assert path =~ "fields="
|
||||
refute has_element?(view, "[data-testid='join_date']")
|
||||
end
|
||||
|
||||
test "a reset button restores the curated default column set", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
view |> element("button[aria-controls='field-visibility-menu']") |> render_click()
|
||||
|
||||
# Reset is a button styled like All/None, sitting next to them, with a tooltip.
|
||||
assert has_element?(view, "button.btn[phx-click='reset_fields']")
|
||||
|
||||
# Hide a curated-default column, then reset restores it.
|
||||
view |> element("button[phx-value-item='join_date']") |> render_click()
|
||||
assert_patch(view)
|
||||
refute has_element?(view, "[data-testid='join_date']")
|
||||
|
||||
view |> element("button[phx-click='reset_fields']") |> render_click()
|
||||
assert_patch(view)
|
||||
assert has_element?(view, "[data-testid='join_date']")
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
defmodule MvWeb.MemberLive.IndexDefaultColumnsTest do
|
||||
@moduledoc """
|
||||
§1.2 — With no persisted column selection, exactly the curated default columns
|
||||
are visible: selection checkbox, Name (name + email), Address (composite),
|
||||
fee type, fee status, groups, join date.
|
||||
are visible: selection checkbox, Name (composite, no in-cell email line),
|
||||
E-Mail (its own column while the compact Member field keeps the email line off),
|
||||
Address (composite), fee type, fee status, groups, join date.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
||||
|
|
@ -34,6 +35,8 @@ defmodule MvWeb.MemberLive.IndexDefaultColumnsTest do
|
|||
# Composite Name + Address cells.
|
||||
assert has_element?(view, "[data-testid='member-name']")
|
||||
assert has_element?(view, "[data-testid='member-address']")
|
||||
# E-Mail is its own column by default (compact Member field, email line off).
|
||||
assert has_element?(view, "[data-testid='email']")
|
||||
# Fee type, fee status, groups, join date headers.
|
||||
assert has_element?(view, "[data-testid='membership_fee_type']")
|
||||
assert has_element?(view, "[data-testid='join_date']")
|
||||
|
|
@ -44,7 +47,9 @@ defmodule MvWeb.MemberLive.IndexDefaultColumnsTest do
|
|||
test "the individual name/address sub-fields are hidden by default", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
for field <- ~w(first_name last_name email city street house_number postal_code country) do
|
||||
# The composite Name/Address cells replace their constituents by default.
|
||||
# E-Mail is excluded here: it is a default-visible column of its own.
|
||||
for field <- ~w(first_name last_name city street house_number postal_code country) do
|
||||
refute has_element?(view, "[data-testid='#{field}']")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
73
test/mv_web/member_live/index_density_test.exs
Normal file
73
test/mv_web/member_live/index_density_test.exs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
defmodule MvWeb.MemberLive.IndexDensityTest do
|
||||
@moduledoc """
|
||||
§1.4 — The density toggle switches the table row-spacing token and reflects the
|
||||
active density in the control state.
|
||||
§1.5 — Density persists across reload per browser (session/cookie), with the
|
||||
global default applying when nothing is stored.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Mv.Helpers.SystemActor
|
||||
|
||||
setup %{conn: conn} do
|
||||
{:ok, _} =
|
||||
Mv.Membership.create_member(
|
||||
%{first_name: "Dense", last_name: "Row", email: "dense@example.com"},
|
||||
actor: SystemActor.get_system_actor()
|
||||
)
|
||||
|
||||
%{conn: conn_with_oidc_user(conn)}
|
||||
end
|
||||
|
||||
defp open_view_settings(view) do
|
||||
view |> element("[data-testid='view-settings-button']") |> render_click()
|
||||
view
|
||||
end
|
||||
|
||||
test "defaults to compact when nothing is stored", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
assert has_element?(view, "[data-testid='members-table-scroll'][data-density='compact']")
|
||||
# The compact-mode toggle reflects the active (compact) state.
|
||||
open_view_settings(view)
|
||||
assert has_element?(view, "[data-testid='view-setting-density'][aria-checked='true']")
|
||||
end
|
||||
|
||||
test "toggle switches the density token and the control state", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
open_view_settings(view)
|
||||
view |> element("[data-testid='view-setting-density']") |> render_click()
|
||||
|
||||
assert has_element?(view, "[data-testid='members-table-scroll'][data-density='comfortable']")
|
||||
assert has_element?(view, "[data-testid='view-setting-density'][aria-checked='false']")
|
||||
|
||||
# Toggling again returns to compact.
|
||||
view |> element("[data-testid='view-setting-density']") |> render_click()
|
||||
assert has_element?(view, "[data-testid='members-table-scroll'][data-density='compact']")
|
||||
end
|
||||
|
||||
test "restores a persisted density from the session on reload", %{conn: conn} do
|
||||
conn =
|
||||
Plug.Test.init_test_session(conn, %{
|
||||
"member_view_settings" => ~s({"density":"comfortable"})
|
||||
})
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
assert has_element?(view, "[data-testid='members-table-scroll'][data-density='comfortable']")
|
||||
end
|
||||
|
||||
test "restores a persisted density on the connected mount via connect params", %{conn: conn} do
|
||||
# Faithful reload round-trip: the connected mount cannot read the cookie
|
||||
# (the live socket's connect-info map has no cookies), so the client echoes
|
||||
# the persisted value through connect params. This is the path that was
|
||||
# broken in the R1 accept (cookie written but not restored on mount).
|
||||
conn = put_connect_params(conn, %{"view_settings" => ~s({"density":"comfortable"})})
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
assert has_element?(view, "[data-testid='members-table-scroll'][data-density='comfortable']")
|
||||
end
|
||||
end
|
||||
|
|
@ -22,6 +22,17 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
|
||||
require Ash.Query
|
||||
|
||||
# Disables the compact Member/Address composites (per-browser view setting) so
|
||||
# the individual constituent columns (first_name/last_name/email, street/…) are
|
||||
# offered by the column manager and available via ?fields=.
|
||||
defp non_compact(conn) do
|
||||
Plug.Conn.put_session(
|
||||
conn,
|
||||
"member_view_settings",
|
||||
~s({"compact_member":false,"compact_address":false})
|
||||
)
|
||||
end
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
|
|
@ -112,7 +123,9 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
end
|
||||
|
||||
test "displays all member fields in dropdown", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
# The individual name/address constituents are only offered when the
|
||||
# compact Member/Address composites are off, so opt out of them first.
|
||||
conn = conn |> conn_with_oidc_user() |> non_compact()
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Open dropdown
|
||||
|
|
@ -148,9 +161,9 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# The curated Name column carries the email, so it is visible initially.
|
||||
# The curated Name column carries the member names, so they are visible.
|
||||
html = render(view)
|
||||
assert html =~ "alice@example.com"
|
||||
assert html =~ "Anderson"
|
||||
|
||||
# Open dropdown and hide the Name column
|
||||
view
|
||||
|
|
@ -161,10 +174,10 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
|> element("button[phx-click='select_item'][phx-value-item='name']")
|
||||
|> render_click()
|
||||
|
||||
# The Name column (and the email it carries) is no longer visible
|
||||
# The Name column (and the names it carries) is no longer visible
|
||||
html = render(view)
|
||||
refute html =~ "alice@example.com"
|
||||
refute html =~ "bob@example.com"
|
||||
refute html =~ "Anderson"
|
||||
refute html =~ "Brown"
|
||||
end
|
||||
|
||||
test "hiding custom field removes it from display", %{conn: conn, custom_field: custom_field} do
|
||||
|
|
@ -298,8 +311,8 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# All fields should be visible by default
|
||||
assert html =~ "alice@example.com"
|
||||
# The curated Name and Address columns are visible by default.
|
||||
assert html =~ "Anderson"
|
||||
assert html =~ "Main St"
|
||||
end
|
||||
|
||||
|
|
@ -309,7 +322,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Hide the curated Name column (which carries the email) via dropdown
|
||||
# Hide the curated Name column (which carries the names) via dropdown
|
||||
view
|
||||
|> element("button[aria-controls='field-visibility-menu']")
|
||||
|> render_click()
|
||||
|
|
@ -319,7 +332,7 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
|> render_click()
|
||||
|
||||
html = render(view)
|
||||
refute html =~ "alice@example.com"
|
||||
refute html =~ "Anderson"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -328,16 +341,16 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members?fields=")
|
||||
|
||||
# Should fall back to global settings
|
||||
assert html =~ "alice@example.com"
|
||||
# Should fall back to global settings (curated Name column visible)
|
||||
assert html =~ "Anderson"
|
||||
end
|
||||
|
||||
test "handles invalid field names in URL", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members?fields=invalid_field,another_invalid")
|
||||
|
||||
# Should ignore invalid fields and use defaults
|
||||
assert html =~ "alice@example.com"
|
||||
# Should ignore invalid fields and use defaults (curated Name column visible)
|
||||
assert html =~ "Anderson"
|
||||
end
|
||||
|
||||
test "handles custom field that doesn't exist", %{conn: conn} do
|
||||
|
|
@ -429,9 +442,9 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# The curated Name column carries the email, so it is visible initially.
|
||||
# The curated Name column carries the names, so they are visible initially.
|
||||
html = render(view)
|
||||
assert html =~ "alice@example.com"
|
||||
assert html =~ "Anderson"
|
||||
|
||||
# Open dropdown
|
||||
view
|
||||
|
|
@ -443,9 +456,9 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
|
|||
|> element("button[phx-click='select_item'][phx-value-item='name']")
|
||||
|> render_keydown(%{key: "Enter"})
|
||||
|
||||
# The Name column (and the email it carries) is no longer visible
|
||||
# The Name column (and the names it carries) is no longer visible
|
||||
html = render(view)
|
||||
refute html =~ "alice@example.com"
|
||||
refute html =~ "Anderson"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,6 +20,16 @@ defmodule MvWeb.MemberLive.IndexGroupsUrlParamsTest do
|
|||
|
||||
require Ash.Query
|
||||
|
||||
# Disables the compact Member composite (per-browser view setting) so the
|
||||
# individual first_name column is offered and selectable via ?fields=.
|
||||
defp non_compact(conn) do
|
||||
Plug.Conn.put_session(
|
||||
conn,
|
||||
"member_view_settings",
|
||||
~s({"compact_member":false,"compact_address":false})
|
||||
)
|
||||
end
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
|
|
@ -122,7 +132,7 @@ defmodule MvWeb.MemberLive.IndexGroupsUrlParamsTest do
|
|||
member1: member1,
|
||||
group1: group1
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
conn = conn |> conn_with_oidc_user() |> non_compact()
|
||||
|
||||
{:ok, view, html} =
|
||||
live(
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
|
|||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# The composite Member cell carries first + last name; the in-cell email
|
||||
# line stays off by default, but the email is surfaced in its own column
|
||||
# (compact Member field, include-email off).
|
||||
for m <- [m1, m2], field <- [m.first_name, m.last_name, m.email] do
|
||||
assert html =~ field
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,19 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
alias Mv.Membership.CustomFieldValue
|
||||
alias MvWeb.MemberLive.Index, as: MemberIndex
|
||||
|
||||
# Disables the compact Member/Address composites (per-browser view setting) so
|
||||
# the individual constituent columns (first_name/last_name, street/…) are
|
||||
# offered by the column manager and selectable via ?fields=. The composite
|
||||
# E-Mail column is offered even in compact mode, so email-only tests do not
|
||||
# need this.
|
||||
defp non_compact(conn) do
|
||||
Plug.Conn.put_session(
|
||||
conn,
|
||||
"member_view_settings",
|
||||
~s({"compact_member":false,"compact_address":false})
|
||||
)
|
||||
end
|
||||
|
||||
describe "desktop layout: scroll container and sticky table header" do
|
||||
@describetag :ui
|
||||
|
||||
|
|
@ -155,9 +168,11 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
describe "sorting integration" do
|
||||
@describetag :ui
|
||||
# The curated default columns no longer expose the individual name/address
|
||||
# sort headers, so these tests make the relevant column visible via ?fields=.
|
||||
# That puts a `fields` param on every push_patch, hence the relaxed
|
||||
# substring assertions instead of exact patch strings.
|
||||
# sort headers. The composite E-Mail column is offered even in compact mode
|
||||
# (so email-only tests just add it via ?fields=), but the name/address
|
||||
# constituents are only offered once their composite view setting is off —
|
||||
# those tests additionally opt out via `non_compact/1`. Either way a `fields`
|
||||
# param rides on every push_patch, hence the relaxed substring assertions.
|
||||
test "clicking a column header toggles sort order and updates the URL", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?fields=email")
|
||||
|
|
@ -183,7 +198,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "clicking different column header resets order to ascending", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
conn = conn |> conn_with_oidc_user() |> non_compact()
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(conn, "/members?fields=first_name,email&sort_field=email&sort_order=desc")
|
||||
|
|
@ -199,7 +214,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "all sortable columns work correctly", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
conn = conn |> conn_with_oidc_user() |> non_compact()
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(
|
||||
|
|
@ -275,7 +290,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
|
||||
test "handle_params handles invalid sort field gracefully", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
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")
|
||||
|
|
|
|||
102
test/mv_web/member_live/index_view_settings_test.exs
Normal file
102
test/mv_web/member_live/index_view_settings_test.exs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
defmodule MvWeb.MemberLive.IndexViewSettingsTest do
|
||||
@moduledoc """
|
||||
§1.20 — the view-settings dropdown toggles: the composite "Member" and address
|
||||
fields, the "include email" sub-toggle and their effect on the rendered
|
||||
columns. Turning a composite off surfaces the underlying separate columns.
|
||||
§1.2 — email is not in the Member cell by default.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Mv.Helpers.SystemActor
|
||||
|
||||
setup %{conn: conn} do
|
||||
{:ok, _} =
|
||||
Mv.Membership.create_member(
|
||||
%{
|
||||
first_name: "Ada",
|
||||
last_name: "Lovelace",
|
||||
email: "ada@example.com",
|
||||
street: "Baker Street",
|
||||
house_number: "221",
|
||||
postal_code: "10115",
|
||||
city: "Berlin"
|
||||
},
|
||||
actor: SystemActor.get_system_actor()
|
||||
)
|
||||
|
||||
%{conn: conn_with_oidc_user(conn)}
|
||||
end
|
||||
|
||||
defp open_view_settings(view) do
|
||||
view |> element("[data-testid='view-settings-button']") |> render_click()
|
||||
view
|
||||
end
|
||||
|
||||
test "member cell hides the email line by default", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
assert has_element?(view, "[data-testid='member-name']")
|
||||
refute has_element?(view, "[data-testid='member-name-email']")
|
||||
end
|
||||
|
||||
test "the include-email sub-toggle surfaces the email line in the member cell", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
open_view_settings(view)
|
||||
view |> element("[data-testid='view-setting-member-include-email']") |> render_click()
|
||||
|
||||
assert has_element?(view, "[data-testid='member-name-email']", "ada@example.com")
|
||||
end
|
||||
|
||||
test "turning off the compact member field shows separate name columns", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
open_view_settings(view)
|
||||
view |> element("[data-testid='view-setting-compact-member']") |> render_click()
|
||||
|
||||
refute has_element?(view, "[data-testid='member-name']")
|
||||
assert has_element?(view, "[data-testid='first_name']")
|
||||
assert has_element?(view, "[data-testid='last_name']")
|
||||
# With the composite off, the email is surfaced as its own column.
|
||||
assert has_element?(view, "[data-testid='email']")
|
||||
end
|
||||
|
||||
test "with the compact member field off, the include-email sub-toggle is hidden",
|
||||
%{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
open_view_settings(view)
|
||||
# While the composite Member field is on, the sub-toggle is available.
|
||||
assert has_element?(view, "[data-testid='view-setting-member-include-email']")
|
||||
|
||||
view |> element("[data-testid='view-setting-compact-member']") |> render_click()
|
||||
|
||||
# Once the composite is off, the email is a separate column, so the
|
||||
# in-cell "include email" sub-toggle no longer applies and is hidden.
|
||||
refute has_element?(view, "[data-testid='view-setting-member-include-email']")
|
||||
assert has_element?(view, "[data-testid='email']")
|
||||
end
|
||||
|
||||
test "the compact address field renders a composite address cell by default", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
assert has_element?(view, "[data-testid='member-address']")
|
||||
refute has_element?(view, "[data-testid='street']")
|
||||
refute has_element?(view, "[data-testid='postal_code']")
|
||||
refute has_element?(view, "[data-testid='city']")
|
||||
end
|
||||
|
||||
test "turning off the compact address field shows separate address columns", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/members")
|
||||
|
||||
open_view_settings(view)
|
||||
view |> element("[data-testid='view-setting-compact-address']") |> render_click()
|
||||
|
||||
refute has_element?(view, "[data-testid='member-address']")
|
||||
assert has_element?(view, "[data-testid='street']")
|
||||
assert has_element?(view, "[data-testid='postal_code']")
|
||||
assert has_element?(view, "[data-testid='city']")
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue