fix(membership): add chronological sort key for custom :date fields
Custom :date values are real Date structs; sorting them by Erlang term order compares day, then month, then year, so the member list ordered them like day-first text instead of chronologically. Derive the sort key from a single shared helper that maps a date to its Gregorian day count, leaving the other value types at their already-correct natural order.
This commit is contained in:
parent
d6c322fd79
commit
1aaa0ece5d
3 changed files with 124 additions and 0 deletions
65
test/mv/membership/custom_field_sort_property_test.exs
Normal file
65
test/mv/membership/custom_field_sort_property_test.exs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
defmodule Mv.Membership.CustomFieldSortPropertyTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnitProperties
|
||||
|
||||
alias Mv.Membership.CustomFieldSort
|
||||
|
||||
defp date_generator do
|
||||
gen all(
|
||||
year <- integer(1..9999),
|
||||
month <- integer(1..12),
|
||||
day <- integer(1..28)
|
||||
) do
|
||||
Date.new!(year, month, day)
|
||||
end
|
||||
end
|
||||
|
||||
# The production load path always delivers a :date value as
|
||||
# %Ash.Union{type: :date, value: %Date{}}, so the property exercises both the
|
||||
# bare %Date{} form and the union-wrapped form to pin the union-dispatch clause.
|
||||
defp shape_generator do
|
||||
member_of([:bare, :union])
|
||||
end
|
||||
|
||||
defp wrap_date(date, :bare), do: date
|
||||
defp wrap_date(date, :union), do: %Ash.Union{type: :date, value: date}
|
||||
|
||||
property "sort_key/2 orders :date values chronologically in both directions" do
|
||||
check all(
|
||||
raw_dates <- list_of(date_generator(), min_length: 1),
|
||||
shape <- shape_generator()
|
||||
) do
|
||||
values = Enum.map(raw_dates, &wrap_date(&1, shape))
|
||||
|
||||
ascending = Enum.sort_by(values, &CustomFieldSort.sort_key(&1, :date))
|
||||
ascending_dates = Enum.map(ascending, &unwrap/1)
|
||||
descending_dates = Enum.reverse(ascending_dates)
|
||||
|
||||
assert non_decreasing?(ascending_dates)
|
||||
assert non_increasing?(descending_dates)
|
||||
|
||||
# The key must be an integer Gregorian-day count, not a stringified date:
|
||||
# this pins the dedicated :date branch and guards against a string-coerced
|
||||
# key whose chronological correctness would silently depend on zero-padded
|
||||
# ISO formatting.
|
||||
Enum.each(values, fn value ->
|
||||
assert CustomFieldSort.sort_key(value, :date) == Date.to_gregorian_days(unwrap(value))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp unwrap(%Ash.Union{value: value}), do: value
|
||||
defp unwrap(%Date{} = date), do: date
|
||||
|
||||
defp non_decreasing?(dates) do
|
||||
dates
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.all?(fn [a, b] -> Date.compare(a, b) != :gt end)
|
||||
end
|
||||
|
||||
defp non_increasing?(dates) do
|
||||
dates
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.all?(fn [a, b] -> Date.compare(a, b) != :lt end)
|
||||
end
|
||||
end
|
||||
29
test/mv/membership/custom_field_sort_test.exs
Normal file
29
test/mv/membership/custom_field_sort_test.exs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
defmodule Mv.Membership.CustomFieldSortTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Mv.Membership.CustomFieldSort
|
||||
|
||||
describe "sort_key/2" do
|
||||
test "keeps :integer values numerically comparable" do
|
||||
values = [10, 100, 2]
|
||||
|
||||
sorted = Enum.sort_by(values, &CustomFieldSort.sort_key(&1, :integer))
|
||||
|
||||
assert sorted == [2, 10, 100]
|
||||
end
|
||||
|
||||
test "passes :string values through to their natural term-order key" do
|
||||
assert CustomFieldSort.sort_key("Zebra", :string) == "Zebra"
|
||||
end
|
||||
|
||||
test "passes :email values through to their natural term-order key" do
|
||||
assert CustomFieldSort.sort_key("a@example.com", :email) == "a@example.com"
|
||||
end
|
||||
|
||||
test "unwraps an %Ash.Union{} value before deriving the key" do
|
||||
union = %Ash.Union{type: :integer, value: 42}
|
||||
|
||||
assert CustomFieldSort.sort_key(union, :integer) == 42
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue