test: added tests
This commit is contained in:
parent
b509dc4ea3
commit
4313703538
5 changed files with 1068 additions and 0 deletions
|
|
@ -0,0 +1,109 @@
|
|||
defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
||||
@moduledoc """
|
||||
Accessibility tests for custom field columns in the member overview.
|
||||
|
||||
Tests cover:
|
||||
- SortHeaderComponent for custom fields has correct ARIA labels
|
||||
- Tab navigation works for custom field columns
|
||||
- Screen reader announcements for sorting
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
require Ash.Query
|
||||
|
||||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field with show_in_overview: true
|
||||
{:ok, field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "membership_number",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field value
|
||||
{:ok, _cfv} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "A001"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
%{member: member, field: field}
|
||||
end
|
||||
|
||||
test "sort header component for custom fields has correct ARIA labels", %{
|
||||
conn: conn,
|
||||
field: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the sort button has aria-label
|
||||
assert html =~ ~r/aria-label=["']Click to sort["']/i or
|
||||
html =~ ~r/aria-label=["'].*sort.*["']/i
|
||||
|
||||
# Check that data-testid is present for testing
|
||||
assert html =~ ~r/data-testid=["']custom_field_#{field.id}["']/
|
||||
end
|
||||
|
||||
test "sort header component shows correct ARIA label when sorted ascending", %{
|
||||
conn: conn,
|
||||
field: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Check that aria-label indicates ascending sort
|
||||
assert html =~ ~r/aria-label=["'].*ascending.*["']/i
|
||||
end
|
||||
|
||||
test "sort header component shows correct ARIA label when sorted descending", %{
|
||||
conn: conn,
|
||||
field: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Check that aria-label indicates descending sort
|
||||
assert html =~ ~r/aria-label=["'].*descending.*["']/i
|
||||
end
|
||||
|
||||
test "custom field column header is keyboard accessible", %{conn: conn, field: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the sort button is a button element (keyboard accessible)
|
||||
assert html =~ ~r/<button[^>]*data-testid=["']custom_field_#{field.id}["']/
|
||||
|
||||
# Button should not have tabindex="-1" (which would remove from tab order)
|
||||
refute html =~ ~r/tabindex=["']-1["']/
|
||||
end
|
||||
|
||||
test "custom field column header has proper semantic structure", %{conn: conn, field: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that custom field name is displayed in the header
|
||||
assert html =~ field.name
|
||||
end
|
||||
end
|
||||
262
test/mv_web/member_live/index_custom_fields_display_test.exs
Normal file
262
test/mv_web/member_live/index_custom_fields_display_test.exs
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
||||
@moduledoc """
|
||||
Tests for displaying custom fields in the member overview.
|
||||
|
||||
Tests cover:
|
||||
- Custom fields with show_in_overview: true are displayed
|
||||
- Custom fields with show_in_overview: false are not displayed
|
||||
- Multiple custom fields with show_in_overview: true are all displayed
|
||||
- Custom field values are correctly formatted for different types
|
||||
- Members without custom field values show empty cell or "-"
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
require Ash.Query
|
||||
|
||||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Bob",
|
||||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom fields
|
||||
{:ok, field_show_string} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "phone_mobile",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_hide} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "internal_note",
|
||||
value_type: :string,
|
||||
show_in_overview: false
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_show_integer} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "membership_number",
|
||||
value_type: :integer,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_show_boolean} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "newsletter",
|
||||
value_type: :boolean,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_show_date} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "birthday",
|
||||
value_type: :date,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_show_email} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "secondary_email",
|
||||
value_type: :email,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field values for member1
|
||||
{:ok, _cfv1} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_show_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "+49123456789"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_show_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 12345}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_show_boolean.id,
|
||||
value: %{"_union_type" => "boolean", "_union_value" => true}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv4} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_show_date.id,
|
||||
value: %{"_union_type" => "date", "_union_value" => ~D[1990-05-15]}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv5} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_show_email.id,
|
||||
value: %{"_union_type" => "email", "_union_value" => "alice.private@example.com"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create hidden custom field value (should not be displayed)
|
||||
{:ok, _cfv_hidden} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_hide.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Internal note"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
member2: member2,
|
||||
field_show_string: field_show_string,
|
||||
field_hide: field_hide,
|
||||
field_show_integer: field_show_integer,
|
||||
field_show_boolean: field_show_boolean,
|
||||
field_show_date: field_show_date,
|
||||
field_show_email: field_show_email
|
||||
}
|
||||
end
|
||||
|
||||
test "displays custom field with show_in_overview: true", %{
|
||||
conn: conn,
|
||||
member1: _member1,
|
||||
field_show_string: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the custom field column header is displayed
|
||||
assert html =~ field.name
|
||||
|
||||
# Check that the value is displayed
|
||||
assert html =~ "+49123456789"
|
||||
end
|
||||
|
||||
test "does not display custom field with show_in_overview: false", %{
|
||||
conn: conn,
|
||||
member1: _member1,
|
||||
field_hide: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the hidden custom field column header is NOT displayed
|
||||
refute html =~ field.name
|
||||
|
||||
# Check that the value is NOT displayed
|
||||
refute html =~ "Internal note"
|
||||
end
|
||||
|
||||
test "displays multiple custom fields with show_in_overview: true", %{
|
||||
conn: conn,
|
||||
field_show_string: field_string,
|
||||
field_show_integer: field_integer,
|
||||
field_show_boolean: field_boolean
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that all visible custom field column headers are displayed
|
||||
assert html =~ field_string.name
|
||||
assert html =~ field_integer.name
|
||||
assert html =~ field_boolean.name
|
||||
end
|
||||
|
||||
test "formats string custom field values correctly", %{conn: conn, member1: _member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
assert html =~ "+49123456789"
|
||||
end
|
||||
|
||||
test "formats integer custom field values correctly", %{conn: conn, member1: _member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
assert html =~ "12345"
|
||||
end
|
||||
|
||||
test "formats boolean custom field values correctly", %{conn: conn, member1: _member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Boolean should be displayed as "Yes" or "No" or similar
|
||||
# Check for true representation
|
||||
assert html =~ "true" or html =~ "Yes" or html =~ "Ja"
|
||||
end
|
||||
|
||||
test "formats date custom field values correctly", %{conn: conn, member1: _member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Date should be displayed in readable format
|
||||
assert html =~ "1990" or html =~ "1990-05-15" or html =~ "15.05.1990"
|
||||
end
|
||||
|
||||
test "formats email custom field values correctly", %{conn: conn, member1: _member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
assert html =~ "alice.private@example.com"
|
||||
end
|
||||
|
||||
test "shows empty cell or placeholder for members without custom field values", %{
|
||||
conn: conn,
|
||||
member2: _member2,
|
||||
field_show_string: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# The custom field column should exist
|
||||
assert html =~ field.name
|
||||
|
||||
# Member2 should have an empty cell for this field
|
||||
# We check that member2's row exists but doesn't have the value
|
||||
assert html =~ "Bob Brown"
|
||||
# The value should not appear for member2 (only for member1)
|
||||
# We check that the value appears somewhere (for member1) but member2 row should have "-"
|
||||
assert html =~ "+49123456789"
|
||||
end
|
||||
end
|
||||
174
test/mv_web/member_live/index_custom_fields_edge_cases_test.exs
Normal file
174
test/mv_web/member_live/index_custom_fields_edge_cases_test.exs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
||||
@moduledoc """
|
||||
Edge case tests for custom fields in the member overview.
|
||||
|
||||
Tests cover:
|
||||
- Custom field without values (all members have no value)
|
||||
- Very long custom field values are correctly displayed
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
require Ash.Query
|
||||
|
||||
alias Mv.Membership.{CustomField, Member}
|
||||
|
||||
test "displays custom field column even when no members have values", %{conn: conn} do
|
||||
# Create test members without custom field values
|
||||
{:ok, _member1} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _member2} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Bob",
|
||||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field with show_in_overview: true but no values
|
||||
{:ok, field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "membership_number",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the custom field column header is still displayed
|
||||
assert html =~ field.name
|
||||
end
|
||||
|
||||
test "displays very long custom field values correctly", %{conn: conn} do
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "long_note",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create very long value (but within limits)
|
||||
long_value = String.duplicate("A", 500)
|
||||
|
||||
{:ok, _cfv} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => long_value}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that the value is displayed (may be truncated in UI, but should be present)
|
||||
# We check for at least part of the value
|
||||
assert html =~ "A" or html =~ long_value
|
||||
end
|
||||
|
||||
test "handles multiple custom fields with show_in_overview correctly", %{conn: conn} do
|
||||
# Create test member
|
||||
{:ok, member} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create multiple custom fields with show_in_overview: true
|
||||
{:ok, field1} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "field1",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field2} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "field2",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field3} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "field3",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create values for all fields
|
||||
{:ok, _cfv1} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: field1.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value1"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv2} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: field2.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value2"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv3} =
|
||||
Mv.Membership.CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member.id,
|
||||
custom_field_id: field3.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Value3"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Check that all custom field columns are displayed
|
||||
assert html =~ field1.name
|
||||
assert html =~ field2.name
|
||||
assert html =~ field3.name
|
||||
|
||||
# Check that all values are displayed
|
||||
assert html =~ "Value1"
|
||||
assert html =~ "Value2"
|
||||
assert html =~ "Value3"
|
||||
end
|
||||
end
|
||||
|
||||
446
test/mv_web/member_live/index_custom_fields_sorting_test.exs
Normal file
446
test/mv_web/member_live/index_custom_fields_sorting_test.exs
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
||||
@moduledoc """
|
||||
Tests for sorting by custom fields in the member overview.
|
||||
|
||||
Tests cover:
|
||||
- Sorting by custom field (ascending)
|
||||
- Sorting by custom field (descending)
|
||||
- Sorting by custom field works with search
|
||||
- Sorting by custom field works with URL parameters
|
||||
- Sorting by custom field works with other columns
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
require Ash.Query
|
||||
|
||||
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||
|
||||
setup do
|
||||
# Create test members
|
||||
{:ok, member1} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Alice",
|
||||
last_name: "Anderson",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member2} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Bob",
|
||||
last_name: "Brown",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member3} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "Charlie",
|
||||
last_name: "Clark",
|
||||
email: "charlie@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field with show_in_overview: true
|
||||
{:ok, field_string} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "membership_number",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, field_integer} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "priority",
|
||||
value_type: :integer,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field values
|
||||
{:ok, _cfv1} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "A001"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member2.id,
|
||||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "C003"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member3.id,
|
||||
custom_field_id: field_string.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "B002"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv4} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member1.id,
|
||||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 10}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv5} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member2.id,
|
||||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 30}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv6} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member3.id,
|
||||
custom_field_id: field_integer.id,
|
||||
value: %{"_union_type" => "integer", "_union_value" => 20}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
%{
|
||||
member1: member1,
|
||||
member2: member2,
|
||||
member3: member3,
|
||||
field_string: field_string,
|
||||
field_integer: field_integer
|
||||
}
|
||||
end
|
||||
|
||||
test "sorts by custom field ascending", %{conn: conn, field_string: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Click on custom field column header to sort
|
||||
view
|
||||
|> element("[data-testid='custom_field_#{field.id}']")
|
||||
|> render_click()
|
||||
|
||||
# Check URL was updated
|
||||
assert_patch(view, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
|
||||
# Verify sort state
|
||||
assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='ascending']")
|
||||
end
|
||||
|
||||
test "sorts by custom field descending", %{conn: conn, field_string: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
|
||||
# Click again to toggle to descending
|
||||
view
|
||||
|> element("[data-testid='custom_field_#{field.id}']")
|
||||
|> render_click()
|
||||
|
||||
# Check URL was updated
|
||||
assert_patch(view, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")
|
||||
|
||||
# Verify sort state
|
||||
assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='descending']")
|
||||
end
|
||||
|
||||
test "sorting by custom field works with search", %{conn: conn, field_string: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=Alice")
|
||||
|
||||
# Click on custom field column header to sort
|
||||
view
|
||||
|> element("[data-testid='custom_field_#{field.id}']")
|
||||
|> render_click()
|
||||
|
||||
# Check URL maintains search query
|
||||
assert_patch(view, "/members?query=Alice&sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
end
|
||||
|
||||
test "sorting by custom field works with URL parameters", %{conn: conn, field_string: field} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")
|
||||
|
||||
# Check that the sort state is correctly applied
|
||||
assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='descending']")
|
||||
end
|
||||
|
||||
test "clicking different custom field column resets order to ascending", %{
|
||||
conn: conn,
|
||||
field_string: field_string,
|
||||
field_integer: field_integer
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field_string.id}&sort_order=desc")
|
||||
|
||||
# Click on a different custom field column
|
||||
view
|
||||
|> element("[data-testid='custom_field_#{field_integer.id}']")
|
||||
|> render_click()
|
||||
|
||||
assert_patch(view, "/members?query=&sort_field=custom_field_#{field_integer.id}&sort_order=asc")
|
||||
end
|
||||
|
||||
test "clicking regular column after custom field column works", %{
|
||||
conn: conn,
|
||||
field_string: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")
|
||||
|
||||
# Click on email column
|
||||
view
|
||||
|> element("[data-testid='email']")
|
||||
|> render_click()
|
||||
|
||||
assert_patch(view, "/members?query=&sort_field=email&sort_order=asc")
|
||||
end
|
||||
|
||||
test "clicking custom field column after regular column works", %{
|
||||
conn: conn,
|
||||
field_string: field
|
||||
} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")
|
||||
|
||||
# Click on custom field column
|
||||
view
|
||||
|> element("[data-testid='custom_field_#{field.id}']")
|
||||
|> render_click()
|
||||
|
||||
assert_patch(view, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
end
|
||||
|
||||
test "NULL values and empty strings are always sorted last (ASC)", %{conn: conn} do
|
||||
# Create additional members with NULL and empty string values
|
||||
{:ok, member_with_value} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithValue",
|
||||
last_name: "Test",
|
||||
email: "withvalue@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_empty} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithEmpty",
|
||||
last_name: "Test",
|
||||
email: "withempty@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_null} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithNull",
|
||||
last_name: "Test",
|
||||
email: "withnull@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_another_value} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "AnotherValue",
|
||||
last_name: "Test",
|
||||
email: "another@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "test_field",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create values: one with actual value, one with empty string, one with NULL (no value), another with value
|
||||
{:ok, _cfv1} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_value.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Zebra"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_empty.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => ""}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# member_with_null has no custom field value (NULL)
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_another_value.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Apple"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc")
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Find positions of member first names in the HTML to verify sort order
|
||||
apple_pos = :binary.match(html, member_with_another_value.first_name)
|
||||
zebra_pos = :binary.match(html, member_with_value.first_name)
|
||||
empty_pos = :binary.match(html, member_with_empty.first_name)
|
||||
null_pos = :binary.match(html, member_with_null.first_name)
|
||||
|
||||
assert apple_pos != :nomatch, "AnotherValue (Apple) should be in HTML"
|
||||
assert zebra_pos != :nomatch, "WithValue (Zebra) should be in HTML"
|
||||
assert empty_pos != :nomatch, "WithEmpty should be in HTML"
|
||||
assert null_pos != :nomatch, "WithNull should be in HTML"
|
||||
|
||||
{apple_idx, _} = apple_pos
|
||||
{zebra_idx, _} = zebra_pos
|
||||
{empty_idx, _} = empty_pos
|
||||
{null_idx, _} = null_pos
|
||||
|
||||
# In ASC order: Apple should come before Zebra
|
||||
assert apple_idx < zebra_idx, "Apple should come before Zebra in ASC order"
|
||||
|
||||
# NULL and empty should come after all values
|
||||
assert apple_idx < empty_idx, "Apple should come before empty value"
|
||||
assert apple_idx < null_idx, "Apple should come before NULL value"
|
||||
assert zebra_idx < empty_idx, "Zebra should come before empty value"
|
||||
assert zebra_idx < null_idx, "Zebra should come before NULL value"
|
||||
end
|
||||
|
||||
test "NULL values and empty strings are always sorted last (DESC)", %{conn: conn} do
|
||||
# Create additional members with NULL and empty string values
|
||||
{:ok, member_with_value} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithValue",
|
||||
last_name: "Test",
|
||||
email: "withvalue@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_empty} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithEmpty",
|
||||
last_name: "Test",
|
||||
email: "withempty@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_null} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "WithNull",
|
||||
last_name: "Test",
|
||||
email: "withnull@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, member_with_another_value} =
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, %{
|
||||
first_name: "AnotherValue",
|
||||
last_name: "Test",
|
||||
email: "another@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create custom field
|
||||
{:ok, field} =
|
||||
CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "test_field",
|
||||
value_type: :string,
|
||||
show_in_overview: true
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Create values: one with actual value, one with empty string, one with NULL (no value), another with value
|
||||
{:ok, _cfv1} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_value.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Apple"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
{:ok, _cfv2} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_empty.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => ""}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# member_with_null has no custom field value (NULL)
|
||||
|
||||
{:ok, _cfv3} =
|
||||
CustomFieldValue
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
member_id: member_with_another_value.id,
|
||||
custom_field_id: field.id,
|
||||
value: %{"_union_type" => "string", "_union_value" => "Zebra"}
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc")
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Find positions of member first names in the HTML to verify sort order
|
||||
apple_pos = :binary.match(html, member_with_value.first_name)
|
||||
zebra_pos = :binary.match(html, member_with_another_value.first_name)
|
||||
empty_pos = :binary.match(html, member_with_empty.first_name)
|
||||
null_pos = :binary.match(html, member_with_null.first_name)
|
||||
|
||||
assert apple_pos != :nomatch, "WithValue (Apple) should be in HTML"
|
||||
assert zebra_pos != :nomatch, "AnotherValue (Zebra) should be in HTML"
|
||||
assert empty_pos != :nomatch, "WithEmpty should be in HTML"
|
||||
assert null_pos != :nomatch, "WithNull should be in HTML"
|
||||
|
||||
{apple_idx, _} = apple_pos
|
||||
{zebra_idx, _} = zebra_pos
|
||||
{empty_idx, _} = empty_pos
|
||||
{null_idx, _} = null_pos
|
||||
|
||||
# In DESC order: Zebra should come before Apple
|
||||
assert zebra_idx < apple_idx, "Zebra should come before Apple in DESC order"
|
||||
|
||||
# NULL and empty should come after all values
|
||||
assert zebra_idx < empty_idx, "Zebra should come before empty value"
|
||||
assert zebra_idx < null_idx, "Zebra should come before NULL value"
|
||||
assert apple_idx < empty_idx, "Apple should come before empty value"
|
||||
assert apple_idx < null_idx, "Apple should come before NULL value"
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue