diff --git a/lib/mv_web/helpers/member_helpers.ex b/lib/mv_web/helpers/member_helpers.ex new file mode 100644 index 0000000..047bd12 --- /dev/null +++ b/lib/mv_web/helpers/member_helpers.ex @@ -0,0 +1,64 @@ +defmodule MvWeb.Helpers.MemberHelpers do + @moduledoc """ + Helper functions for member-related operations in the web layer. + + Provides utilities for formatting and displaying member information. + """ + + alias Mv.Membership.Member + + @doc """ + Returns a display name for a member. + + Combines first_name and last_name if available, otherwise falls back to email. + This ensures that members without names still have a meaningful display name. + + ## Examples + + iex> member = %Member{first_name: "John", last_name: "Doe", email: "john@example.com"} + iex> MvWeb.Helpers.MemberHelpers.display_name(member) + "John Doe" + + iex> member = %Member{first_name: nil, last_name: nil, email: "john@example.com"} + iex> MvWeb.Helpers.MemberHelpers.display_name(member) + "john@example.com" + + iex> member = %Member{first_name: "John", last_name: nil, email: "john@example.com"} + iex> MvWeb.Helpers.MemberHelpers.display_name(member) + "John" + """ + def display_name(%Member{} = member) do + name_parts = + [member.first_name, member.last_name] + |> Enum.reject(&blank?/1) + |> Enum.map_join(" ", &String.trim/1) + + if name_parts == "" do + member.email + else + name_parts + end + end + + @doc """ + Checks if a value is blank (nil, empty string, or only whitespace). + + ## Examples + + iex> MvWeb.Helpers.MemberHelpers.blank?(nil) + true + + iex> MvWeb.Helpers.MemberHelpers.blank?("") + true + + iex> MvWeb.Helpers.MemberHelpers.blank?(" ") + true + + iex> MvWeb.Helpers.MemberHelpers.blank?("John") + false + """ + def blank?(nil), do: true + def blank?(""), do: true + def blank?(value) when is_binary(value), do: String.trim(value) == "" + def blank?(_), do: false +end diff --git a/lib/mv_web/live/contribution_period_live/show.ex b/lib/mv_web/live/contribution_period_live/show.ex index f297bf2..b6a2574 100644 --- a/lib/mv_web/live/contribution_period_live/show.ex +++ b/lib/mv_web/live/contribution_period_live/show.ex @@ -36,7 +36,7 @@ defmodule MvWeb.ContributionPeriodLive.Show do <.mockup_warning /> <.header> - {gettext("Contributions for %{name}", name: MvWeb.MemberLive.Index.display_name(@member))} + {gettext("Contributions for %{name}", name: MvWeb.Helpers.MemberHelpers.display_name(@member))} <:subtitle> {gettext("Contribution type")}: {@member.contribution_type} diff --git a/lib/mv_web/live/custom_field_value_live/form.ex b/lib/mv_web/live/custom_field_value_live/form.ex index 4ed1a23..6ff6432 100644 --- a/lib/mv_web/live/custom_field_value_live/form.ex +++ b/lib/mv_web/live/custom_field_value_live/form.ex @@ -52,7 +52,7 @@ defmodule MvWeb.CustomFieldValueLive.Form do options={custom_field_options(@custom_fields)} prompt={gettext("Choose a custom field")} /> - + <.input field={@form[:member_id]} @@ -61,7 +61,7 @@ defmodule MvWeb.CustomFieldValueLive.Form do options={member_options(@members)} prompt={gettext("Choose a member")} /> - + <%= if @selected_custom_field do %> <.union_value_input form={@form} custom_field={@selected_custom_field} /> @@ -289,6 +289,6 @@ defmodule MvWeb.CustomFieldValueLive.Form do end defp member_options(members) do - Enum.map(members, &{MvWeb.MemberLive.Index.display_name(&1), &1.id}) + Enum.map(members, &{MvWeb.Helpers.MemberHelpers.display_name(&1), &1.id}) end end diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index 16ad195..0a05e1f 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -43,7 +43,7 @@ defmodule MvWeb.MemberLive.Form do

<%= if @member do %> - {MvWeb.MemberLive.Index.display_name(@member)} + {MvWeb.Helpers.MemberHelpers.display_name(@member)} <% else %> {gettext("New Member")} <% end %> diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index f1ec177..fff5517 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -1165,61 +1165,6 @@ defmodule MvWeb.MemberLive.Index do end end - @doc """ - Returns a display name for a member. - - Combines first_name and last_name if available, otherwise falls back to email. - This ensures that members without names still have a meaningful display name. - - ## Examples - - iex> member = %Member{first_name: "John", last_name: "Doe", email: "john@example.com"} - iex> display_name(member) - "John Doe" - - iex> member = %Member{first_name: nil, last_name: nil, email: "john@example.com"} - iex> display_name(member) - "john@example.com" - - iex> member = %Member{first_name: "John", last_name: nil, email: "john@example.com"} - iex> display_name(member) - "John" - """ - def display_name(member) do - name_parts = - [member.first_name, member.last_name] - |> Enum.reject(&blank?/1) - |> Enum.map_join(" ", &String.trim/1) - - if name_parts == "" do - member.email - else - name_parts - end - end - - @doc """ - Checks if a value is blank (nil, empty string, or only whitespace). - - ## Examples - - iex> blank?(nil) - true - - iex> blank?("") - true - - iex> blank?(" ") - true - - iex> blank?("John") - false - """ - def blank?(nil), do: true - def blank?(""), do: true - def blank?(value) when is_binary(value), do: String.trim(value) == "" - def blank?(_), do: false - # Public helper function to format dates for use in templates def format_date(date), do: DateFormatter.format_date(date) diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index e9236fd..997cb1a 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -35,7 +35,7 @@ defmodule MvWeb.MemberLive.Show do

- {MvWeb.MemberLive.Index.display_name(@member)} + {MvWeb.Helpers.MemberHelpers.display_name(@member)}

<.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}> diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 85e5bbb..c6749d8 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -44,7 +44,7 @@ defmodule MvWeb.UserLive.Form do <.form class="max-w-xl" for={@form} id="user-form" phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label={gettext("Email")} required type="email" /> - +
- +

{gettext("Linked Member")}

@@ -131,7 +131,7 @@ defmodule MvWeb.UserLive.Form do

- {MvWeb.MemberLive.Index.display_name(@user.member)} + {MvWeb.Helpers.MemberHelpers.display_name(@user.member)}

{@user.member.email}

@@ -210,7 +210,7 @@ defmodule MvWeb.UserLive.Form do ) ]} > -

{MvWeb.MemberLive.Index.display_name(member)}

+

{MvWeb.Helpers.MemberHelpers.display_name(member)}

{member.email}

<% end %> @@ -438,7 +438,7 @@ defmodule MvWeb.UserLive.Form do member_name = if selected_member, - do: MvWeb.MemberLive.Index.display_name(selected_member), + do: MvWeb.Helpers.MemberHelpers.display_name(selected_member), else: "" # Store the selected member ID and name in socket state and clear unlink flag diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index f05a763..9eaa4fa 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -57,7 +57,7 @@ defmodule MvWeb.UserLive.Show do class="text-blue-600 underline hover:text-blue-800" > <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> - {MvWeb.MemberLive.Index.display_name(@user.member)} + {MvWeb.Helpers.MemberHelpers.display_name(@user.member)} <% else %> {gettext("No member linked")} diff --git a/test/mv_web/helpers/member_helpers_test.exs b/test/mv_web/helpers/member_helpers_test.exs new file mode 100644 index 0000000..7a11235 --- /dev/null +++ b/test/mv_web/helpers/member_helpers_test.exs @@ -0,0 +1,141 @@ +defmodule MvWeb.Helpers.MemberHelpersTest do + @moduledoc """ + Tests for the display_name/1 helper function in MemberHelpers. + """ + use Mv.DataCase, async: true + + alias Mv.Membership.Member + alias MvWeb.Helpers.MemberHelpers + + describe "display_name/1" do + test "returns full name when both first_name and last_name are present" do + member = %Member{ + first_name: "John", + last_name: "Doe", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "John Doe" + end + + test "returns email when both first_name and last_name are nil" do + member = %Member{ + first_name: nil, + last_name: nil, + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "returns first_name only when last_name is nil" do + member = %Member{ + first_name: "John", + last_name: nil, + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "John" + end + + test "returns last_name only when first_name is nil" do + member = %Member{ + first_name: nil, + last_name: "Doe", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "Doe" + end + + test "returns email when first_name and last_name are empty strings" do + member = %Member{ + first_name: "", + last_name: "", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "returns email when first_name and last_name are whitespace only" do + member = %Member{ + first_name: " ", + last_name: " \t ", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "trims whitespace from name parts" do + member = %Member{ + first_name: " John ", + last_name: " Doe ", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "John Doe" + end + + test "handles one empty string and one nil" do + member = %Member{ + first_name: "", + last_name: nil, + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "handles one nil and one empty string" do + member = %Member{ + first_name: nil, + last_name: "", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "handles one whitespace and one nil" do + member = %Member{ + first_name: " ", + last_name: nil, + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "john@example.com" + end + + test "handles one valid name and one whitespace" do + member = %Member{ + first_name: "John", + last_name: " ", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "John" + end + + test "handles member with only first_name containing whitespace" do + member = %Member{ + first_name: " John ", + last_name: nil, + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "John" + end + + test "handles member with only last_name containing whitespace" do + member = %Member{ + first_name: nil, + last_name: " Doe ", + email: "john@example.com" + } + + assert MemberHelpers.display_name(member) == "Doe" + end + end +end diff --git a/test/mv_web/member_live/index_display_name_test.exs b/test/mv_web/member_live/index_display_name_test.exs index 86758c9..7a11235 100644 --- a/test/mv_web/member_live/index_display_name_test.exs +++ b/test/mv_web/member_live/index_display_name_test.exs @@ -1,11 +1,11 @@ -defmodule MvWeb.MemberLive.Index.DisplayNameTest do +defmodule MvWeb.Helpers.MemberHelpersTest do @moduledoc """ - Tests for the display_name/1 helper function in MemberLive.Index. + Tests for the display_name/1 helper function in MemberHelpers. """ use Mv.DataCase, async: true alias Mv.Membership.Member - alias MvWeb.MemberLive.Index + alias MvWeb.Helpers.MemberHelpers describe "display_name/1" do test "returns full name when both first_name and last_name are present" do @@ -15,7 +15,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "John Doe" + assert MemberHelpers.display_name(member) == "John Doe" end test "returns email when both first_name and last_name are nil" do @@ -25,7 +25,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "returns first_name only when last_name is nil" do @@ -35,7 +35,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "John" + assert MemberHelpers.display_name(member) == "John" end test "returns last_name only when first_name is nil" do @@ -45,7 +45,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "Doe" + assert MemberHelpers.display_name(member) == "Doe" end test "returns email when first_name and last_name are empty strings" do @@ -55,7 +55,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "returns email when first_name and last_name are whitespace only" do @@ -65,7 +65,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "trims whitespace from name parts" do @@ -75,7 +75,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "John Doe" + assert MemberHelpers.display_name(member) == "John Doe" end test "handles one empty string and one nil" do @@ -85,7 +85,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "handles one nil and one empty string" do @@ -95,7 +95,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "handles one whitespace and one nil" do @@ -105,7 +105,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "john@example.com" + assert MemberHelpers.display_name(member) == "john@example.com" end test "handles one valid name and one whitespace" do @@ -115,7 +115,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "John" + assert MemberHelpers.display_name(member) == "John" end test "handles member with only first_name containing whitespace" do @@ -125,7 +125,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "John" + assert MemberHelpers.display_name(member) == "John" end test "handles member with only last_name containing whitespace" do @@ -135,7 +135,7 @@ defmodule MvWeb.MemberLive.Index.DisplayNameTest do email: "john@example.com" } - assert Index.display_name(member) == "Doe" + assert MemberHelpers.display_name(member) == "Doe" end end end