defmodule BerrypodWeb.Admin.PagesTest do use BerrypodWeb.ConnCase, async: false import Phoenix.LiveViewTest import Berrypod.AccountsFixtures alias Berrypod.Pages alias Berrypod.Pages.PageCache setup do PageCache.invalidate_all() user = user_fixture() %{user: user} end describe "unauthenticated" do test "redirects to login", %{conn: conn} do {:error, redirect} = live(conn, ~p"/admin/pages") assert {:redirect, %{to: path}} = redirect assert path == ~p"/users/log-in" end end describe "page list" do setup %{conn: conn, user: user} do %{conn: log_in_user(conn, user)} end test "renders page list with groups", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/admin/pages") assert html =~ "Pages" assert html =~ "Marketing" assert html =~ "Legal" assert html =~ "Shop" assert html =~ "Orders" assert html =~ "System" end test "shows all 14 pages", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages") assert has_element?(view, ".page-card-title", "Home page") assert has_element?(view, ".page-card-title", "About") assert has_element?(view, ".page-card-title", "Contact") assert has_element?(view, ".page-card-title", "Product page") assert has_element?(view, ".page-card-title", "Error") end test "shows block count per page", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages") # Home has 4 default blocks assert has_element?(view, ".page-card-meta", "4 blocks") end test "links to editor", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages") view |> element("a[href='/admin/pages/home']") |> render_click() assert_redirect(view, ~p"/admin/pages/home") end end describe "page editor" do setup %{conn: conn, user: user} do %{conn: log_in_user(conn, user)} end test "renders editor with blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") assert has_element?(view, ".block-card-name", "Hero banner") assert has_element?(view, ".block-card-name", "Category navigation") assert has_element?(view, ".block-card-name", "Featured products") assert has_element?(view, ".block-card-name", "Image + text") end test "shows position numbers", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") assert has_element?(view, ".block-card-position", "1") assert has_element?(view, ".block-card-position", "4") end test "shows back link", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") assert has_element?(view, "a[href='/admin/pages']", "Pages") end test "move block up reorders blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") # Get the second block (category_nav) and move it up page = Pages.get_page("home") second_block = Enum.at(page.blocks, 1) render_click(view, "move_up", %{"id" => second_block["id"]}) # The ARIA live region announces the move assert has_element?(view, "[aria-live='polite']", "Category navigation moved to position 1") end test "move block down reorders blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") first_block = Enum.at(page.blocks, 0) render_click(view, "move_down", %{"id" => first_block["id"]}) # Hero banner should now be at position 2 assert has_element?(view, "[aria-live='polite']", "Hero banner moved to position 2") end test "move up disabled for first block", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") first_block = Enum.at(page.blocks, 0) assert has_element?( view, "button[phx-value-id='#{first_block["id"]}'][phx-click='move_up'][disabled]" ) end test "move down disabled for last block", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") last_block = Enum.at(page.blocks, -1) assert has_element?( view, "button[phx-value-id='#{last_block["id"]}'][phx-click='move_down'][disabled]" ) end test "remove block", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") block = Enum.at(page.blocks, 1) render_click(view, "remove_block", %{"id" => block["id"]}) refute has_element?(view, ".block-card-name", "Category navigation") assert has_element?(view, ".block-card-position", "3") refute has_element?(view, ".block-card-position", "4") end test "duplicate block", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") block = Enum.at(page.blocks, 0) render_click(view, "duplicate_block", %{"id" => block["id"]}) # Should now have 5 blocks (position 5 exists) assert has_element?(view, ".block-card-position", "5") assert has_element?(view, "[aria-live='polite']", "Hero banner duplicated") end test "add block via picker", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") render_click(view, "show_picker") assert has_element?(view, ".block-picker") render_click(view, "add_block", %{"type" => "trust_badges"}) assert has_element?(view, ".block-card-name", "Trust badges") refute has_element?(view, ".block-picker") end test "picker filter narrows results", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") render_click(view, "show_picker") render_keyup(view, "filter_picker", %{"value" => "hero"}) assert has_element?(view, ".block-picker-item", "Hero banner") refute has_element?(view, ".block-picker-item", "Trust badges") end test "picker only shows blocks allowed on page", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") render_click(view, "show_picker") assert has_element?(view, ".block-picker-item", "Hero banner") refute has_element?(view, ".block-picker-item", "Product hero") refute has_element?(view, ".block-picker-item", "Cart items") end test "save persists blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") block = Enum.at(page.blocks, 1) render_click(view, "remove_block", %{"id" => block["id"]}) render_click(view, "save") assert render(view) =~ "Page saved" saved = Pages.get_page("home") assert length(saved.blocks) == 3 types = Enum.map(saved.blocks, & &1["type"]) refute "category_nav" in types end test "reset to defaults restores original blocks", %{conn: conn} do {:ok, _} = Pages.save_page("home", %{ title: "Home page", blocks: [%{"id" => "blk_test", "type" => "hero", "settings" => %{}}] }) {:ok, view, _html} = live(conn, ~p"/admin/pages/home") # Should show only 1 block assert has_element?(view, ".block-card-position", "1") refute has_element?(view, ".block-card-position", "2") render_click(view, "reset_defaults") assert render(view) =~ "Page reset to defaults" # Should now have 4 default blocks assert has_element?(view, ".block-card-position", "4") end test "dirty flag appears after changes", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") refute has_element?(view, ".admin-badge-warning", "Unsaved changes") page = Pages.get_page("home") block = Enum.at(page.blocks, 0) render_click(view, "move_down", %{"id" => block["id"]}) assert has_element?(view, ".admin-badge-warning", "Unsaved changes") end test "dirty flag clears after save", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") block = Enum.at(page.blocks, 0) render_click(view, "move_down", %{"id" => block["id"]}) assert has_element?(view, ".admin-badge-warning") render_click(view, "save") refute has_element?(view, ".admin-badge-warning") end test "save button disabled when not dirty", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") assert has_element?(view, "button[disabled]", "Save") end end describe "page editor for page-specific pages" do setup %{conn: conn, user: user} do %{conn: log_in_user(conn, user)} end test "PDP editor shows PDP blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/pdp") assert has_element?(view, ".block-card-name", "Breadcrumb") assert has_element?(view, ".block-card-name", "Product hero") assert has_element?(view, ".block-card-name", "Trust badges") end test "PDP picker shows PDP-specific blocks", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/pdp") render_click(view, "show_picker") assert has_element?(view, ".block-picker-item", "Product hero") assert has_element?(view, ".block-picker-item", "Hero banner") refute has_element?(view, ".block-picker-item", "Cart items") end test "error page editor works", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/error") assert has_element?(view, ".block-card-name", "Hero banner") assert has_element?(view, ".block-card-name", "Featured products") end end describe "block settings editing" do setup %{conn: conn, user: user} do %{conn: log_in_user(conn, user)} end test "edit button shown only for blocks with settings", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) category_nav = Enum.at(page.blocks, 1) # Hero has settings — edit button present assert has_element?(view, "#block-edit-btn-#{hero["id"]}") # Category nav has no settings — no edit button refute has_element?(view, "#block-edit-btn-#{category_nav["id"]}") end test "toggle expand shows settings form", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) # Settings panel not visible initially refute has_element?(view, "#block-settings-#{hero["id"]}") # Click edit to expand render_click(view, "toggle_expand", %{"id" => hero["id"]}) # Settings panel now visible assert has_element?(view, "#block-settings-#{hero["id"]}") assert has_element?(view, "[aria-live='polite']", "Hero banner settings expanded") end test "toggle collapse hides settings form", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) # Expand then collapse render_click(view, "toggle_expand", %{"id" => hero["id"]}) assert has_element?(view, "#block-settings-#{hero["id"]}") render_click(view, "toggle_expand", %{"id" => hero["id"]}) refute has_element?(view, "#block-settings-#{hero["id"]}") assert has_element?(view, "[aria-live='polite']", "Hero banner settings collapsed") end test "aria-expanded reflects toggle state", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) assert has_element?(view, "#block-edit-btn-#{hero["id"]}[aria-expanded='false']") render_click(view, "toggle_expand", %{"id" => hero["id"]}) assert has_element?(view, "#block-edit-btn-#{hero["id"]}[aria-expanded='true']") end test "settings form renders fields from schema", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) render_click(view, "toggle_expand", %{"id" => hero["id"]}) # Hero has text fields: title, description, cta_text, etc. assert has_element?(view, "#block-#{hero["id"]}-title") assert has_element?(view, "#block-#{hero["id"]}-description") assert has_element?(view, "#block-#{hero["id"]}-cta_text") # And a select field: variant assert has_element?(view, "#block-#{hero["id"]}-variant") end test "editing settings updates working state and sets dirty", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) render_click(view, "toggle_expand", %{"id" => hero["id"]}) # Edit the title render_change(view, "update_block_settings", %{ "block_id" => hero["id"], "block_settings" => %{"title" => "New hero title"} }) # Dirty flag should appear assert has_element?(view, ".admin-badge-warning", "Unsaved changes") end test "edited settings persist after save", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) render_click(view, "toggle_expand", %{"id" => hero["id"]}) render_change(view, "update_block_settings", %{ "block_id" => hero["id"], "block_settings" => %{"title" => "Updated title", "description" => "Updated desc"} }) render_click(view, "save") saved = Pages.get_page("home") saved_hero = Enum.find(saved.blocks, &(&1["type"] == "hero")) assert saved_hero["settings"]["title"] == "Updated title" assert saved_hero["settings"]["description"] == "Updated desc" end test "number fields are coerced to integers", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") featured = Enum.find(page.blocks, &(&1["type"] == "featured_products")) render_click(view, "toggle_expand", %{"id" => featured["id"]}) render_change(view, "update_block_settings", %{ "block_id" => featured["id"], "block_settings" => %{"product_count" => "4"} }) render_click(view, "save") saved = Pages.get_page("home") saved_featured = Enum.find(saved.blocks, &(&1["type"] == "featured_products")) assert saved_featured["settings"]["product_count"] == 4 end test "select fields render with options", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) render_click(view, "toggle_expand", %{"id" => hero["id"]}) html = render(view) # Variant select should have the expected options assert html =~ "default" assert html =~ "sunken" end test "multiple blocks can be expanded simultaneously", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) featured = Enum.find(page.blocks, &(&1["type"] == "featured_products")) render_click(view, "toggle_expand", %{"id" => hero["id"]}) render_click(view, "toggle_expand", %{"id" => featured["id"]}) assert has_element?(view, "#block-settings-#{hero["id"]}") assert has_element?(view, "#block-settings-#{featured["id"]}") end test "expanded card has expanded class", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/pages/home") page = Pages.get_page("home") hero = Enum.at(page.blocks, 0) refute has_element?(view, "#block-#{hero["id"]}.block-card-expanded") render_click(view, "toggle_expand", %{"id" => hero["id"]}) assert has_element?(view, "#block-#{hero["id"]}.block-card-expanded") end end end