feat(member-live): wire date filters into LiveView lifecycle

This commit is contained in:
Moritz 2026-05-20 16:28:17 +02:00
parent ddd4a9a878
commit e3295ab4b5
10 changed files with 1037 additions and 140 deletions

View file

@ -0,0 +1,89 @@
defmodule MvWeb.MemberLive.Index.CustomFieldValueLookupTest do
@moduledoc """
Unit tests for the shared custom-field-value lookup helper.
The lookup must handle both shapes a CFV entry can take on a loaded member:
* `%{custom_field_id: id, value: ...}` id present directly
* `%{custom_field: %{id: id, ...}, value: ...}` id nested under loaded relation
"""
use ExUnit.Case, async: true
alias MvWeb.MemberLive.Index.CustomFieldValueLookup
defp uuid, do: "11111111-2222-3333-4444-555555555555"
describe "find_by_id/2" do
test "matches when custom_field_id key is present" do
id = uuid()
cfv = %{custom_field_id: id, value: :anything}
member = %{custom_field_values: [cfv]}
assert CustomFieldValueLookup.find_by_id(member, id) == cfv
end
test "matches when nested custom_field relation is loaded" do
id = uuid()
cfv = %{custom_field: %{id: id, value_type: :date}, value: :anything}
member = %{custom_field_values: [cfv]}
assert CustomFieldValueLookup.find_by_id(member, id) == cfv
end
test "compares stringified ids — accepts atom or binary ids on the cfv side" do
id = uuid()
cfv = %{custom_field_id: id, value: :v}
member = %{custom_field_values: [cfv]}
# Same id, passed as binary
assert CustomFieldValueLookup.find_by_id(member, id) == cfv
end
test "returns nil when no entry has a matching id" do
member = %{
custom_field_values: [
%{custom_field_id: "11111111-1111-1111-1111-111111111111", value: 1}
]
}
assert CustomFieldValueLookup.find_by_id(member, uuid()) == nil
end
test "returns nil when custom_field_values is nil" do
assert CustomFieldValueLookup.find_by_id(%{custom_field_values: nil}, uuid()) == nil
end
test "returns nil when custom_field_values is not loaded (Ash.NotLoaded)" do
member = %{custom_field_values: %Ash.NotLoaded{type: :relationship}}
assert CustomFieldValueLookup.find_by_id(member, uuid()) == nil
end
test "returns nil when custom_field_values is empty" do
assert CustomFieldValueLookup.find_by_id(%{custom_field_values: []}, uuid()) == nil
end
end
describe "find_by_field/2" do
test "matches a custom_field struct via its :id" do
id = uuid()
cfv = %{custom_field_id: id, value: :v}
member = %{custom_field_values: [cfv]}
custom_field = %{id: id, value_type: :boolean}
assert CustomFieldValueLookup.find_by_field(member, custom_field) == cfv
end
test "matches when only the nested custom_field is present" do
id = uuid()
cfv = %{custom_field: %{id: id}, value: :v}
member = %{custom_field_values: [cfv]}
assert CustomFieldValueLookup.find_by_field(member, %{id: id}) == cfv
end
test "returns nil when no entry matches" do
member = %{custom_field_values: [%{custom_field_id: "other", value: :v}]}
assert CustomFieldValueLookup.find_by_field(member, %{id: uuid()}) == nil
end
end
end

View file

@ -0,0 +1,85 @@
defmodule MvWeb.MemberLive.Index.FilterParamsTest do
@moduledoc """
Unit tests for the shared filter-param parsers.
"""
use ExUnit.Case, async: true
alias MvWeb.MemberLive.Index.FilterParams
describe "parse_prefix_filters/3" do
test "extracts only entries whose key starts with the prefix" do
params = %{
"group_abc" => "in",
"group_def" => "not_in",
"fee_type_xyz" => "in",
"unrelated" => "in",
"query" => "alice"
}
result = FilterParams.parse_prefix_filters(params, "group_", & &1)
assert result == %{"abc" => "in", "def" => "not_in"}
end
test "strips exactly one occurrence of the prefix, even when the rest starts with the prefix again" do
# Quirky but legal: a key like "p_p_abc" with prefix "p_" must produce id "p_abc".
params = %{"p_p_abc" => "v"}
result = FilterParams.parse_prefix_filters(params, "p_", & &1)
assert result == %{"p_abc" => "v"}
end
test "applies parse_value_fn to every value" do
params = %{"x_one" => "in", "x_two" => "not_in", "x_three" => "garbage"}
result =
FilterParams.parse_prefix_filters(
params,
"x_",
&FilterParams.parse_in_not_in_value/1
)
assert result == %{"one" => :in, "two" => :not_in, "three" => nil}
end
test "returns empty map when no key matches the prefix" do
params = %{"a" => "1", "b" => "2"}
assert FilterParams.parse_prefix_filters(params, "z_", & &1) == %{}
end
test "ignores non-binary keys" do
params = %{"x_a" => "1", :atom_key => "2", 123 => "3"}
result = FilterParams.parse_prefix_filters(params, "x_", & &1)
assert result == %{"a" => "1"}
end
test "returns empty map for empty input" do
assert FilterParams.parse_prefix_filters(%{}, "x_", & &1) == %{}
end
end
describe "parse_in_not_in_value/1" do
test "maps 'in' to :in" do
assert FilterParams.parse_in_not_in_value("in") == :in
end
test "maps 'not_in' to :not_in" do
assert FilterParams.parse_in_not_in_value("not_in") == :not_in
end
test "trims whitespace around recognized values" do
assert FilterParams.parse_in_not_in_value(" in ") == :in
assert FilterParams.parse_in_not_in_value("\tnot_in\n") == :not_in
end
test "returns nil for unrecognized strings" do
assert FilterParams.parse_in_not_in_value("yes") == nil
assert FilterParams.parse_in_not_in_value("") == nil
end
test "returns nil for non-binary input" do
assert FilterParams.parse_in_not_in_value(nil) == nil
assert FilterParams.parse_in_not_in_value(:in) == nil
assert FilterParams.parse_in_not_in_value(123) == nil
end
end
end