feat(import): confirm column mapping in a preview before importing members
This commit is contained in:
parent
a93dd9d535
commit
68a1a9530a
8 changed files with 816 additions and 38 deletions
31
test/mv_web/live/import_live/components_test.exs
Normal file
31
test/mv_web/live/import_live/components_test.exs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
defmodule MvWeb.ImportLive.ComponentsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias MvWeb.ImportLive.Components
|
||||
|
||||
describe "import_button_disabled?/2" do
|
||||
@done_entry %{done?: true}
|
||||
|
||||
test "disables the Start Import button while the preview is displayed" do
|
||||
# During :preview the upload entry is done, but re-clicking Start Import
|
||||
# would re-run the upload processing and overwrite the current preview.
|
||||
assert Components.import_button_disabled?(:preview, [@done_entry]) == true
|
||||
end
|
||||
|
||||
test "disables the button while an import is running" do
|
||||
assert Components.import_button_disabled?(:running, [@done_entry]) == true
|
||||
end
|
||||
|
||||
test "disables the button when there are no upload entries" do
|
||||
assert Components.import_button_disabled?(:idle, []) == true
|
||||
end
|
||||
|
||||
test "disables the button while an upload entry is not yet done" do
|
||||
assert Components.import_button_disabled?(:idle, [%{done?: false}]) == true
|
||||
end
|
||||
|
||||
test "enables the button at idle with a completed upload" do
|
||||
assert Components.import_button_disabled?(:idle, [@done_entry]) == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -27,6 +27,16 @@ defmodule MvWeb.ImportLiveTest do
|
|||
|
||||
defp submit_import(view), do: view |> form("#csv-upload-form", %{}) |> render_submit()
|
||||
|
||||
defp confirm_import(view),
|
||||
do: view |> element("[data-testid='confirm-import-button']") |> render_click()
|
||||
|
||||
# Full flow: upload, enter preview (start), then confirm to begin processing.
|
||||
defp run_full_import(view, csv_content, filename \\ "test_import.csv") do
|
||||
upload_csv_file(view, csv_content, filename)
|
||||
submit_import(view)
|
||||
confirm_import(view)
|
||||
end
|
||||
|
||||
defp wait_for_import_completion, do: Process.sleep(1000)
|
||||
|
||||
# ---------- Business logic: Authorization ----------
|
||||
|
|
@ -56,8 +66,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
|> File.read!()
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content)
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content)
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
|
@ -121,8 +130,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
invalid_csv: csv_content
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content, "invalid_import.csv")
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content, "invalid_import.csv")
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
|
@ -141,8 +149,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
invalid_rows =
|
||||
for i <- 1..100, do: "Row#{i};Last#{i};;Country#{i};City#{i};Street#{i};12345\n"
|
||||
|
||||
upload_csv_file(view, header <> Enum.join(invalid_rows), "large_invalid.csv")
|
||||
submit_import(view)
|
||||
run_full_import(view, header <> Enum.join(invalid_rows), "large_invalid.csv")
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
|
@ -174,8 +181,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_bom_semicolon.csv"])
|
||||
|> File.read!()
|
||||
|
||||
upload_csv_file(view, csv_content, "bom_import.csv")
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content, "bom_import.csv")
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
|
@ -193,8 +199,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
Path.join([__DIR__, "..", "..", "fixtures", "csv_with_empty_lines.csv"])
|
||||
|> File.read!()
|
||||
|
||||
upload_csv_file(view, csv_content, "empty_lines.csv")
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content, "empty_lines.csv")
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-error-list']")
|
||||
|
|
@ -208,8 +213,7 @@ defmodule MvWeb.ImportLiveTest do
|
|||
unknown_custom_field_csv: csv_content
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content, "unknown_custom.csv")
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content, "unknown_custom.csv")
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
|
@ -254,14 +258,27 @@ defmodule MvWeb.ImportLiveTest do
|
|||
assert has_element?(view, "[data-testid='csv-upload-form'] input[type='file']")
|
||||
end
|
||||
|
||||
test "custom fields notice lists accepted groups and fee-type column names", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/import")
|
||||
|
||||
# Groups column variants (both EN and DE)
|
||||
assert html =~ "Groups"
|
||||
assert html =~ "Gruppen"
|
||||
# Fee type column variants (both EN and DE)
|
||||
assert html =~ "Beitragsart"
|
||||
assert html =~ "Fee Type"
|
||||
assert html =~ "fee type"
|
||||
# Fee status is always ignored (named explicitly)
|
||||
assert html =~ "Bezahlstatus"
|
||||
end
|
||||
|
||||
test "after successful import, progress container has aria-live", %{conn: conn} do
|
||||
csv_content =
|
||||
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
||||
|> File.read!()
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content)
|
||||
submit_import(view)
|
||||
run_full_import(view, csv_content)
|
||||
wait_for_import_completion()
|
||||
assert has_element?(view, "[data-testid='import-progress-container']")
|
||||
html = render(view)
|
||||
|
|
@ -280,4 +297,187 @@ defmodule MvWeb.ImportLiveTest do
|
|||
html = render(view)
|
||||
assert html =~ "Failed to prepare"
|
||||
end
|
||||
|
||||
describe "preview state machine" do
|
||||
setup %{conn: conn} do
|
||||
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> MvWeb.ConnCase.conn_with_password_user(admin_user)
|
||||
|> put_locale_en()
|
||||
|
||||
valid_csv =
|
||||
Path.join([__DIR__, "..", "..", "fixtures", "valid_member_import.csv"])
|
||||
|> File.read!()
|
||||
|
||||
{:ok, conn: conn, valid_csv: valid_csv}
|
||||
end
|
||||
|
||||
test "start_import transitions to preview without processing", %{
|
||||
conn: conn,
|
||||
valid_csv: csv_content
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content)
|
||||
submit_import(view)
|
||||
|
||||
# Preview is shown; no results panel yet because nothing was processed.
|
||||
assert has_element?(view, "[data-testid='import-preview']")
|
||||
refute has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
||||
# No member was created during preview (read-only step).
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{:ok, members} = Membership.list_members(actor: system_actor)
|
||||
|
||||
refute Enum.any?(
|
||||
members,
|
||||
&(&1.email in ["alice.smith@example.com", "bob.johnson@example.com"])
|
||||
)
|
||||
end
|
||||
|
||||
test "confirm_import starts processing and creates members", %{
|
||||
conn: conn,
|
||||
valid_csv: csv_content
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
run_full_import(view, csv_content)
|
||||
wait_for_import_completion()
|
||||
|
||||
assert has_element?(view, "[data-testid='import-results-panel']")
|
||||
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
{:ok, members} = Membership.list_members(actor: system_actor)
|
||||
|
||||
imported =
|
||||
Enum.filter(
|
||||
members,
|
||||
&(&1.email in ["alice.smith@example.com", "bob.johnson@example.com"])
|
||||
)
|
||||
|
||||
assert length(imported) == 2
|
||||
end
|
||||
|
||||
test "cancel_import returns to idle and hides the preview", %{
|
||||
conn: conn,
|
||||
valid_csv: csv_content
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv_content)
|
||||
submit_import(view)
|
||||
assert has_element?(view, "[data-testid='import-preview']")
|
||||
|
||||
view |> element("[data-testid='cancel-import-button']") |> render_click()
|
||||
|
||||
refute has_element?(view, "[data-testid='import-preview']")
|
||||
refute has_element?(view, "[data-testid='import-results-panel']")
|
||||
end
|
||||
end
|
||||
|
||||
describe "preview contents" do
|
||||
setup %{conn: conn} do
|
||||
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> MvWeb.ConnCase.conn_with_password_user(admin_user)
|
||||
|> put_locale_en()
|
||||
|
||||
{:ok, conn: conn}
|
||||
end
|
||||
|
||||
test "shows the column mapping table with roles for each column", %{conn: conn} do
|
||||
csv = "email;Gruppen;Beitragsart;Bezahlstatus;UnknownCol\na@e.com;Chor;Premium;paid;x"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
assert has_element?(view, "[data-testid='preview-mapping-table']")
|
||||
html = render(view)
|
||||
|
||||
assert html =~ "email"
|
||||
assert html =~ "Gruppen"
|
||||
assert html =~ "Beitragsart"
|
||||
assert html =~ "Bezahlstatus"
|
||||
assert html =~ "UnknownCol"
|
||||
end
|
||||
|
||||
test "lists every CSV column exactly once in the mapping table", %{conn: conn} do
|
||||
headers = ["email", "Gruppen", "Beitragsart", "Bezahlstatus", "UnknownCol"]
|
||||
csv = Enum.join(headers, ";") <> "\na@e.com;Chor;Premium;paid;x"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
# Count the data rows via their stable testid so the assertion is independent
|
||||
# of how Phoenix renders class attributes or tr tags (§1.15).
|
||||
html = render(view)
|
||||
|
||||
row_count =
|
||||
html |> String.split(~s(data-testid="preview-column-row")) |> length() |> Kernel.-(1)
|
||||
|
||||
assert row_count == length(headers)
|
||||
end
|
||||
|
||||
test "shows up to 3 sample data rows", %{conn: conn} do
|
||||
csv = "email\nr1@e.com\nr2@e.com\nr3@e.com\nr4@e.com"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
html = render(view)
|
||||
assert html =~ "r1@e.com"
|
||||
assert html =~ "r2@e.com"
|
||||
assert html =~ "r3@e.com"
|
||||
refute html =~ "r4@e.com"
|
||||
end
|
||||
|
||||
test "shows an auto-create notice for unknown group names", %{conn: conn} do
|
||||
csv = "email;Gruppen\na@e.com;Ganz Neue Gruppe"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
assert has_element?(view, "[data-testid='preview-groups-notice']")
|
||||
assert render(view) =~ "Ganz Neue Gruppe"
|
||||
end
|
||||
|
||||
test "shows a warning and link for unknown fee-type names", %{conn: conn} do
|
||||
csv = "email;Beitragsart\na@e.com;Phantom Tarif"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
assert has_element?(view, "[data-testid='preview-fee-type-warning']")
|
||||
html = render(view)
|
||||
assert html =~ "Phantom Tarif"
|
||||
assert html =~ "/membership_fee_settings"
|
||||
end
|
||||
|
||||
test "shows an info notice when fee-type cells are empty", %{conn: conn} do
|
||||
csv = "email;Beitragsart\na@e.com;\nb@e.com;"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
assert has_element?(view, "[data-testid='preview-fee-type-info']")
|
||||
end
|
||||
|
||||
test "shows a warning for unknown custom-field columns", %{conn: conn} do
|
||||
csv = "email;TotallyUnknown\na@e.com;value"
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/import")
|
||||
upload_csv_file(view, csv)
|
||||
submit_import(view)
|
||||
|
||||
assert has_element?(view, "[data-testid='preview-unknown-warning']")
|
||||
assert render(view) =~ "TotallyUnknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue