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
|
|
@ -201,6 +201,56 @@ defmodule MvWeb.MemberLive.Index.FieldVisibilityTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "offered_member_fields/3 ordering (name group, then email, then address group, then rest)" do
|
||||
test "compact member + compact address: Name, E-Mail, Adresse lead" do
|
||||
result = FieldVisibility.offered_member_fields(true, false, true)
|
||||
|
||||
assert Enum.take(result, 3) == [:name, :email, :address]
|
||||
# the composite constituents are not offered while their composite is on
|
||||
refute :first_name in result
|
||||
refute :street in result
|
||||
# the Datenfelder-ordered rest trails the identity block
|
||||
assert index_of(result, :address) < index_of(result, :join_date)
|
||||
end
|
||||
|
||||
test "compact member with email folded in: no separate E-Mail column" do
|
||||
result = FieldVisibility.offered_member_fields(true, true, true)
|
||||
|
||||
assert Enum.take(result, 2) == [:name, :address]
|
||||
refute :email in result
|
||||
end
|
||||
|
||||
test "non-compact member + compact address: Vorname/Nachname/E-Mail before Adresse" do
|
||||
result = FieldVisibility.offered_member_fields(false, false, true)
|
||||
|
||||
# the reported bug: the composite address must not jump ahead of the
|
||||
# expanded name constituents
|
||||
assert Enum.take(result, 4) == [:first_name, :last_name, :email, :address]
|
||||
assert index_of(result, :address) < index_of(result, :join_date)
|
||||
end
|
||||
|
||||
test "non-compact member + non-compact address: name, email, then the address block, then rest" do
|
||||
result = FieldVisibility.offered_member_fields(false, false, false)
|
||||
|
||||
assert Enum.take(result, 7) == [
|
||||
:first_name,
|
||||
:last_name,
|
||||
:email,
|
||||
:street,
|
||||
:house_number,
|
||||
:postal_code,
|
||||
:city
|
||||
]
|
||||
|
||||
# join_date and later Datenfelder columns never precede the address block
|
||||
assert index_of(result, :city) < index_of(result, :join_date)
|
||||
refute :name in result
|
||||
refute :address in result
|
||||
end
|
||||
end
|
||||
|
||||
defp index_of(list, elem), do: Enum.find_index(list, &(&1 == elem))
|
||||
|
||||
describe "get_visible_fields/1" do
|
||||
test "returns only fields with true visibility" do
|
||||
selection = %{
|
||||
|
|
|
|||
109
test/mv_web/live/member_live/index/view_settings_test.exs
Normal file
109
test/mv_web/live/member_live/index/view_settings_test.exs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
defmodule MvWeb.MemberLive.Index.ViewSettingsTest do
|
||||
@moduledoc """
|
||||
§1.20 / §1.5 — the member-overview view settings (density + the compact
|
||||
member/address field toggles) and their per-browser persistence chain
|
||||
(connect params > session > cookie > global default).
|
||||
"""
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias MvWeb.MemberLive.Index.ViewSettings
|
||||
|
||||
test "defaults are all-compact with the email line off" do
|
||||
assert ViewSettings.defaults() == %{
|
||||
density: :compact,
|
||||
compact_member: true,
|
||||
member_include_email: false,
|
||||
compact_address: true
|
||||
}
|
||||
end
|
||||
|
||||
describe "parse/1" do
|
||||
test "reads a partial map with string keys and string/bool values" do
|
||||
assert ViewSettings.parse(%{
|
||||
"density" => "comfortable",
|
||||
"compact_member" => false,
|
||||
"member_include_email" => true
|
||||
}) == %{
|
||||
density: :comfortable,
|
||||
compact_member: false,
|
||||
member_include_email: true
|
||||
}
|
||||
end
|
||||
|
||||
test "ignores unknown keys and invalid values" do
|
||||
assert ViewSettings.parse(%{"density" => "huge", "bogus" => true}) == %{}
|
||||
end
|
||||
|
||||
test "returns empty map for non-maps" do
|
||||
assert ViewSettings.parse(nil) == %{}
|
||||
assert ViewSettings.parse("x") == %{}
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_from_* sources" do
|
||||
test "session JSON string" do
|
||||
session = %{"member_view_settings" => ~s({"density":"comfortable"})}
|
||||
assert ViewSettings.get_from_session(session) == %{density: :comfortable}
|
||||
assert ViewSettings.get_from_session(%{}) == %{}
|
||||
end
|
||||
|
||||
test "cookie header" do
|
||||
json = ~s({"compact_address":false})
|
||||
conn = %Plug.Conn{req_headers: [{"cookie", "member_view_settings=#{URI.encode(json)}"}]}
|
||||
assert ViewSettings.get_from_cookie(conn) == %{compact_address: false}
|
||||
assert ViewSettings.get_from_cookie(%Plug.Conn{req_headers: []}) == %{}
|
||||
assert ViewSettings.get_from_cookie(nil) == %{}
|
||||
end
|
||||
|
||||
test "connect params (raw JSON string under view_settings)" do
|
||||
params = %{"view_settings" => ~s({"density":"comfortable","compact_member":false})}
|
||||
|
||||
assert ViewSettings.get_from_connect_params(params) == %{
|
||||
density: :comfortable,
|
||||
compact_member: false
|
||||
}
|
||||
|
||||
assert ViewSettings.get_from_connect_params(nil) == %{}
|
||||
end
|
||||
end
|
||||
|
||||
describe "resolve/3 priority connect_params > session > cookie > default" do
|
||||
test "falls back to defaults when nothing is stored" do
|
||||
assert ViewSettings.resolve(%{}, nil, nil) == ViewSettings.defaults()
|
||||
end
|
||||
|
||||
test "cookie overrides default, session overrides cookie, connect params win" do
|
||||
cookie_json = ~s({"density":"comfortable","compact_member":false,"compact_address":false})
|
||||
|
||||
conn = %Plug.Conn{
|
||||
req_headers: [{"cookie", "member_view_settings=#{URI.encode(cookie_json)}"}]
|
||||
}
|
||||
|
||||
session = %{"member_view_settings" => ~s({"compact_member":true})}
|
||||
connect_params = %{"view_settings" => ~s({"density":"compact"})}
|
||||
|
||||
assert ViewSettings.resolve(session, conn, connect_params) == %{
|
||||
# from connect params (highest)
|
||||
density: :compact,
|
||||
# from session (overrides cookie)
|
||||
compact_member: true,
|
||||
# from cookie
|
||||
compact_address: false,
|
||||
# default
|
||||
member_include_email: false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
test "to_json/1 round-trips through parse/1" do
|
||||
settings = %{
|
||||
density: :comfortable,
|
||||
compact_member: false,
|
||||
member_include_email: true,
|
||||
compact_address: false
|
||||
}
|
||||
|
||||
assert settings |> ViewSettings.to_json() |> Jason.decode!() |> ViewSettings.parse() ==
|
||||
settings
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue