test: updated tests

This commit is contained in:
carla 2025-10-24 10:55:11 +02:00 committed by moritz
parent b71df98ba2
commit 41e3a52482
6 changed files with 412 additions and 16 deletions

View file

@ -44,6 +44,7 @@ defmodule MvWeb.Components.SearchBarComponent do
placeholder={@placeholder}
value={@query}
name="query"
data-testid="search-input"
phx-change="search"
phx-target={@myself}
phx-debounce="300"

View file

@ -56,6 +56,7 @@ defmodule MvWeb.Components.SortHeaderComponent do
case dir do
:asc -> gettext("ascending")
:desc -> gettext("descending")
nil -> gettext("Click to sort")
end
end

View file

@ -88,6 +88,18 @@ for member_attrs <- [
city: "Berlin",
street: "Kastanienallee",
house_number: "8"
},
%{
first_name: "Marianne",
last_name: "Wagner",
email: "marianne.wagner@example.de",
birth_date: ~D[1978-11-08],
join_date: ~D[2022-11-10],
paid: true,
phone_number: "+49301122334",
city: "Berlin",
street: "Kastanienallee",
house_number: "8"
}
] do
# Use upsert to prevent duplicates based on email

View file

@ -18,14 +18,14 @@ defmodule MvWeb.Components.SearchBarComponentTest do
html =
view
|> element("form[role=search]")
|> render_change(%{"query" => "Friedrich"})
|> render_submit(%{"query" => "Friedrich"})
refute html =~ "Greta"
html =
view
|> element("form[role=search]")
|> render_change(%{"query" => "Greta"})
|> render_submit(%{"query" => "Greta"})
refute html =~ "Friedrich"
end

View file

@ -1,12 +1,301 @@
defmodule MvWeb.Components.SortHeaderComponentTest do
use MvWeb.ConnCase, async: true
use Phoenix.Component
import Phoenix.LiveViewTest
test "renders sort header with correct attributes", %{conn: conn} do
describe "rendering" do
test "renders with correct attributes", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
assert view |> element("[data-testid='first_name']")
# Test that the component renders with correct attributes
assert has_element?(view, "[data-testid='first_name']")
assert has_element?(view, "button[phx-value-field='city']")
assert has_element?(view, "button[phx-value-field='first_name']", "First name")
end
test "renders all sortable headers", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
sortable_fields = [:first_name, :email, :street, :house_number, :postal_code, :city, :phone_number, :join_date]
for field <- sortable_fields do
assert has_element?(view, "button[phx-value-field='#{field}']")
end
end
test "renders correct labels", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Test specific labels
assert has_element?(view, "button[phx-value-field='first_name']", "First name")
assert has_element?(view, "button[phx-value-field='email']", "Email")
assert has_element?(view, "button[phx-value-field='city']", "City")
end
end
describe "sort icons" do
test "shows neutral icon for specific field when not sorted", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members")
# The neutral icon has the opcity class we can test for
# Test that EMAIL field specifically shows neutral icon
assert has_element?(view, "[data-testid='email'] .opacity-40")
# Test that CITY field specifically shows neutral icon
assert has_element?(view, "[data-testid='city'] .opacity-40")
end
test "shows ascending icon for specific field when sorted ascending", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?query=&sort_field=city&sort_order=asc")
# Test that FIRST_NAME field specifically shows ascending icon
# Test CSS classes - no opacity for active state
refute has_element?(view, "[data-testid='city'] .opacity-40")
# Test that OTHER fields still show neutral icons
assert has_element?(view, "[data-testid='first_name'] .opacity-40")
# Test HTML content - should contain chevron-up AND chevron-up-down
assert html =~ "hero-chevron-up"
assert html =~ "hero-chevron-up-down"
# Count occurrences to ensure only one ascending icon
up_count = html |> String.split("hero-chevron-up ") |> length() |> Kernel.-(1)
assert up_count == 1 # Should be exactly one chevron-up icon
end
test "shows descending icon for specific field when sorted descending", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")
# Count occurrences to ensure only one descending icon
down_count = html |> String.split("hero-chevron-down ") |> length() |> Kernel.-(1)
assert down_count == 1 # Should be exactly one chevron-down icon
end
test "multiple fields can have different icon states", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=&sort_field=city&sort_order=asc")
# CITY field should be active (ascending)
refute has_element?(view, "[data-testid='city'] .opacity-40")
# All other fields should be neutral
assert has_element?(view, "[data-testid='first_name'] .opacity-40")
assert has_element?(view, "[data-testid='email'] .opacity-40")
assert has_element?(view, "[data-testid='street'] .opacity-40")
assert has_element?(view, "[data-testid='house_number'] .opacity-40")
assert has_element?(view, "[data-testid='postal_code'] .opacity-40")
assert has_element?(view, "[data-testid='phone_number'] .opacity-40")
assert has_element?(view, "[data-testid='join_date'] .opacity-40")
end
test "icon state changes correctly when clicking different fields", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Start: all fields neutral except first name as default
assert has_element?(view, "[data-testid='city'] .opacity-40")
refute has_element?(view, "[data-testid='first_name'] .opacity-40")
assert has_element?(view, "[data-testid='email'] .opacity-40")
# Click city - should become active
view
|> element("button[phx-value-field='city']")
|> render_click()
# city should be active, email should still be neutral
refute has_element?(view, "[data-testid='city'] .opacity-40")
assert has_element?(view, "[data-testid='email'] .opacity-40")
# Click email - should switch active field
view
|> element("button[phx-value-field='email']")
|> render_click()
# email should be active, city should be neutral again
refute has_element?(view, "[data-testid='email'] .opacity-40")
assert has_element?(view, "[data-testid='city'] .opacity-40")
end
test "specific field shows correct icon for each sort state", %{conn: conn} do
conn = conn_with_oidc_user(conn)
# Test EMAIL field specifically
{:ok, view, html_asc} = live(conn, "/members?sort_field=email&sort_order=asc")
assert html_asc =~ "hero-chevron-up"
refute has_element?(view, "[data-testid='email'] .opacity-40")
{:ok, view, html_desc} = live(conn, "/members?sort_field=email&sort_order=desc")
assert html_desc =~ "hero-chevron-down"
refute has_element?(view, "[data-testid='email'] .opacity-40")
{:ok, view, html_neutral} = live(conn, "/members")
assert html_neutral =~ "hero-chevron-up-down"
assert has_element?(view, "[data-testid='email'] .opacity-40")
end
test "icon distribution is correct for all fields", %{conn: conn} do
conn = conn_with_oidc_user(conn)
# Test neutral state - all fields except first name (default) should show neutral icons
{:ok, _view, html_neutral} = live(conn, "/members")
# Count neutral icons (should be 7 - one for each field)
neutral_count = html_neutral |> String.split("hero-chevron-up-down") |> length() |> Kernel.-(1)
assert neutral_count == 7
# Count active icons (should be 1)
up_count = html_neutral |> String.split("hero-chevron-up ") |> length() |> Kernel.-(1)
down_count = html_neutral |> String.split("hero-chevron-down ") |> length() |> Kernel.-(1)
assert up_count == 1
assert down_count == 0
# Test ascending state - one field active, others neutral
{:ok, _view, html_asc} = live(conn, "/members?sort_field=first_name&sort_order=asc")
# Should have exactly 1 ascending icon and 7 neutral icons
up_count = html_asc |> String.split("hero-chevron-up ") |> length() |> Kernel.-(1)
neutral_count = html_asc |> String.split("hero-chevron-up-down") |> length() |> Kernel.-(1)
down_count = html_asc |> String.split("hero-chevron-down ") |> length() |> Kernel.-(1)
assert up_count == 1
assert neutral_count == 7
assert down_count == 0
end
end
describe "accessibility" do
test "sets aria-label correctly for unsorted state", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Check aria-label for unsorted state
assert has_element?(view, "button[phx-value-field='city'][aria-label='Click to sort']")
end
test "sets aria-label correctly for ascending sort", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=asc")
# Check aria-label for ascending sort
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='ascending']")
end
test "sets aria-label correctly for descending sort", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=desc")
# Check aria-label for descending sort
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='descending']")
end
test "includes tooltip with correct aria-label", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=first_name&sort_order=asc")
# Check that tooltip div exists with correct data-tip
assert has_element?(view, "[data-testid='first_name']")
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='ascending']")
end
test "aria-labels work for all sortable fields", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=email&sort_order=desc")
# Test aria-labels for different fields
assert has_element?(view, "button[phx-value-field='email'][aria-label='descending']")
assert has_element?(view, "button[phx-value-field='first_name'][aria-label='Click to sort']")
assert has_element?(view, "button[phx-value-field='city'][aria-label='Click to sort']")
end
end
describe "component behavior" do
test "clicking sends sort message to parent", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Click on the first_name sort header
view
|> element("button[phx-value-field='first_name']")
|> render_click()
# The component should send a message to the parent LiveView
# This is tested indirectly through the URL change in integration tests
end
test "component handles different field types correctly", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Test that different field types render correctly
assert has_element?(view, "button[phx-value-field='first_name']")
assert has_element?(view, "button[phx-value-field='email']")
assert has_element?(view, "button[phx-value-field='join_date']")
end
end
describe "edge cases" do
test "handles invalid sort field gracefully", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=invalid_field&sort_order=asc")
# Should not crash and should default sorting for first name
assert html =~ "hero-chevron-up-down"
refute has_element?(view, "[data-testid='first_name'] .opacity-40")
end
test "handles invalid sort order gracefully", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=first_name&sort_order=invalid")
# Should default to ascending
assert html =~ "hero-chevron-up"
refute has_element?(view, "[data-testid='first_name'] [aria-label='ascending']")
end
test "handles empty sort parameters", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=&sort_order=")
# Should show neutral icons
assert html =~ "hero-chevron-up-down"
assert has_element?(view, "[data-testid='city'] .opacity-40")
end
end
describe "icon state transitions" do
test "icon changes when sorting state changes", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Start with neutral state
assert has_element?(view, "[data-testid='city'] .opacity-40")
# Click to sort ascending
view
|> element("button[phx-value-field='city']")
|> render_click()
# Should now be ascending (no opacity class)
refute has_element?(view, "[data-testid='city'] .opacity-40")
end
test "multiple fields can be tested for icon states", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, html} = live(conn, "/members?sort_field=email&sort_order=desc")
# Email should be active (descending)
assert html =~ "hero-chevron-down"
refute has_element?(view, "[data-testid='email'] .opacity-40")
# Other fields should be neutral
assert has_element?(view, "[data-testid='first_name'] .opacity-40")
assert has_element?(view, "[data-testid='city'] .opacity-40")
end
end
end

View file

@ -74,39 +74,132 @@ defmodule MvWeb.MemberLive.IndexTest do
assert has_element?(index_view, "#flash-group", "Member create successfully")
end
describe "sorting interaction" do
describe "sorting integration" do
test "clicking a column header toggles sort order and updates the URL", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# The component data test ids are built as "<field>"
# The component data test ids are built with the name of the field
# First click should sort ASC
view
|> element("[data-testid='email']")
|> render_click()
# The LiveView pushes a patch with the new query params
assert_patch(view, "/members?sort_field=email&sort_order=asc")
assert_patch(view, "/members?query=&sort_field=email&sort_order=asc")
# Second click toggles to DESC
view
|> element("[data-testid='email']")
|> render_click()
assert_patch(view, "/members?sort_field=email&sort_order=desc")
assert_patch(view, "/members?query=&sort_field=email&sort_order=desc")
end
test "clicking different column header resets order to ascending", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?sort_field=email&sort_order=desc")
# Click on a different column
view
|> element("[data-testid='first_name']")
|> render_click()
assert_patch(view, "/members?query=&sort_field=first_name&sort_order=asc")
end
test "all sortable columns work correctly", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# default ascending sorting with first name
assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")
sortable_fields = [:email, :street, :house_number, :postal_code, :city, :phone_number, :join_date]
for field <- sortable_fields do
view
|> element("[data-testid='#{field}']")
|> render_click()
assert_patch(view, "/members?query=&sort_field=#{field}&sort_order=asc")
end
end
test "sorting works with search query", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=test")
view
|> element("[data-testid='email']")
|> render_click()
assert_patch(view, "/members?query=test&sort_field=email&sort_order=asc")
end
test "sorting maintains search query when toggling order", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc")
view
|> element("[data-testid='email']")
|> render_click()
assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc")
end
end
describe "URL param handling" do
test "handle_params reads sort query and applies it", %{conn: conn} do
conn = conn_with_oidc_user(conn)
url = "/members?sort_field=email&sort_order=desc"
{:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")
conn = get(conn, url)
# Check that the sort state is correctly applied
assert has_element?(view, "[data-testid='email'][aria-label='descending']")
end
# The LiveView must have parsed the params and stored them as atoms.
assert conn.assigns.sort_field == :email
assert conn.assigns.sort_order == :desc
test "handle_params handles invalid sort field gracefully", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=&sort_field=invalid_field&sort_order=asc")
# Should not crash and should show default first name order
assert has_element?(view, "[data-testid='first_name'][aria-label='ascending']")
end
test "handle_params preserves search query with sort params", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=desc")
# Both search and sort should be preserved
assert has_element?(view, "[data-testid='email'][aria-label='descending']")
end
end
describe "search and sort integration" do
test "search maintains sort state", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=&sort_field=email&sort_order=desc")
# Perform search
view
|> element("[data-testid='search-input']")
|> render_change(%{value: "test"})
# Sort state should be maintained
assert has_element?(view, "[data-testid='email'][aria-label='descending']")
end
test "sort maintains search state", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members?query=test&sort_field=email&sort_order=asc")
# Perform sort
view
|> element("[data-testid='email']")
|> render_click()
# Search state should be maintained
assert_patch(view, "/members?query=test&sort_field=email&sort_order=desc")
end
end