124 lines
4.1 KiB
Elixir
124 lines
4.1 KiB
Elixir
defmodule Mv.Membership.MembersCSVTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Mv.Membership.MembersCSV
|
|
|
|
describe "export/3" do
|
|
test "returns CSV with header and one data row (member fields only)" do
|
|
member = %{first_name: "Jane", email: "jane@example.com"}
|
|
member_fields = ["first_name", "email"]
|
|
custom_fields_by_id = %{}
|
|
|
|
iodata = MembersCSV.export([member], member_fields, custom_fields_by_id)
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
assert csv =~ "first_name"
|
|
assert csv =~ "email"
|
|
assert csv =~ "Jane"
|
|
assert csv =~ "jane@example.com"
|
|
# One header line, one data line
|
|
lines = String.split(csv, "\n", trim: true)
|
|
assert length(lines) == 2
|
|
end
|
|
|
|
test "escapes cell containing comma (RFC 4180 quoted)" do
|
|
member = %{first_name: "Doe, John", email: "john@example.com"}
|
|
member_fields = ["first_name", "email"]
|
|
custom_fields_by_id = %{}
|
|
|
|
iodata = MembersCSV.export([member], member_fields, custom_fields_by_id)
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
# Comma inside value must be quoted so the cell is one field
|
|
assert csv =~ ~s("Doe, John")
|
|
assert csv =~ "john@example.com"
|
|
end
|
|
|
|
test "escapes cell containing double-quote (RFC 4180 doubled and quoted)" do
|
|
member = %{first_name: ~s(He said "Hi"), email: "a@b.com"}
|
|
member_fields = ["first_name", "email"]
|
|
custom_fields_by_id = %{}
|
|
|
|
iodata = MembersCSV.export([member], member_fields, custom_fields_by_id)
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
# Double-quote inside value must be doubled and cell quoted
|
|
assert csv =~ ~s("He said ""Hi""")
|
|
assert csv =~ "a@b.com"
|
|
end
|
|
|
|
test "formats date as ISO8601 for member fields" do
|
|
member = %{first_name: "D", email: "d@d.com", join_date: ~D[2024-03-15]}
|
|
iodata = MembersCSV.export([member], ["first_name", "email", "join_date"], %{})
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
assert csv =~ "2024-03-15"
|
|
assert csv =~ "join_date"
|
|
end
|
|
|
|
test "formats nil as empty string" do
|
|
member = %{first_name: "Only", last_name: nil, email: "x@y.com"}
|
|
member_fields = ["first_name", "last_name", "email"]
|
|
custom_fields_by_id = %{}
|
|
|
|
iodata = MembersCSV.export([member], member_fields, custom_fields_by_id)
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
assert csv =~ "first_name"
|
|
assert csv =~ "Only"
|
|
assert csv =~ "x@y.com"
|
|
# Nil becomes empty; between Only and x@y we have empty (e.g. Only,,x@y.com)
|
|
assert csv =~ "Only,,x@y"
|
|
end
|
|
|
|
test "formats boolean as true/false" do
|
|
# Use a field we can set to boolean via a custom-like struct - member has no boolean field.
|
|
# So we test via custom field instead.
|
|
custom_cf = %{id: "cf-1", name: "Active", value_type: :boolean}
|
|
custom_fields_by_id = %{"cf-1" => custom_cf}
|
|
|
|
member_with_cfv = %{
|
|
first_name: "Test",
|
|
email: "e@e.com",
|
|
custom_field_values: [
|
|
%{custom_field_id: "cf-1", value: true, custom_field: custom_cf}
|
|
]
|
|
}
|
|
|
|
iodata =
|
|
MembersCSV.export(
|
|
[member_with_cfv],
|
|
["first_name", "email"],
|
|
custom_fields_by_id
|
|
)
|
|
|
|
csv = IO.iodata_to_binary(iodata)
|
|
assert csv =~ "Active"
|
|
# Formatter yields "Yes" for true (gettext)
|
|
assert csv =~ "Yes"
|
|
end
|
|
|
|
test "includes custom field columns in header and rows (order from map)" do
|
|
cf1 = %{id: "a", name: "Custom1", value_type: :string}
|
|
cf2 = %{id: "b", name: "Custom2", value_type: :string}
|
|
# Map order: a then b
|
|
custom_fields_by_id = %{"a" => cf1, "b" => cf2}
|
|
|
|
member = %{
|
|
first_name: "M",
|
|
email: "m@m.com",
|
|
custom_field_values: [
|
|
%{custom_field_id: "a", value: "v1", custom_field: cf1},
|
|
%{custom_field_id: "b", value: "v2", custom_field: cf2}
|
|
]
|
|
}
|
|
|
|
iodata = MembersCSV.export([member], ["first_name", "email"], custom_fields_by_id)
|
|
csv = IO.iodata_to_binary(iodata)
|
|
|
|
assert csv =~ "first_name,email,Custom1,Custom2"
|
|
assert csv =~ "v1"
|
|
assert csv =~ "v2"
|
|
end
|
|
end
|
|
end
|