All checks were successful
deploy / deploy (push) Successful in 1m21s
New settings form for creating and editing custom page metadata (title, slug, meta description, published, nav settings). Pages index shows custom pages section with draft badges and delete. Editor shows settings button for custom pages, hides reset to defaults. 20 new tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
904 lines
30 KiB
Elixir
904 lines
30 KiB
Elixir
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
|
|
|
|
describe "repeater fields" do
|
|
setup %{conn: conn, user: user} do
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "repeater items render with input fields", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
# Should render 3 default items with label/value inputs
|
|
html = render(view)
|
|
assert html =~ "Printing"
|
|
assert html =~ "Delivery"
|
|
assert html =~ "Issues"
|
|
end
|
|
|
|
test "editing a repeater item field sets dirty flag", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
render_change(view, "update_block_settings", %{
|
|
"block_id" => info_card["id"],
|
|
"block_settings" => %{
|
|
"title" => "Handy to know",
|
|
"items" => %{
|
|
"0" => %{"label" => "Shipping", "value" => "1-3 business days"},
|
|
"1" => %{
|
|
"label" => "Delivery",
|
|
"value" => "Example: 3-7 business days after printing"
|
|
},
|
|
"2" => %{"label" => "Issues", "value" => "Example: Reprints for any defects"}
|
|
}
|
|
}
|
|
})
|
|
|
|
assert has_element?(view, ".admin-badge-warning", "Unsaved changes")
|
|
end
|
|
|
|
test "adding a repeater item appends an empty item", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
# Should have 3 items initially
|
|
assert render(view) |> count_repeater_items() == 3
|
|
|
|
render_click(view, "repeater_add", %{
|
|
"block-id" => info_card["id"],
|
|
"field" => "items"
|
|
})
|
|
|
|
# Now 4 items
|
|
assert render(view) |> count_repeater_items() == 4
|
|
assert has_element?(view, ".admin-badge-warning", "Unsaved changes")
|
|
end
|
|
|
|
test "removing a repeater item removes it from the list", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
render_click(view, "repeater_remove", %{
|
|
"block-id" => info_card["id"],
|
|
"field" => "items",
|
|
"index" => "0"
|
|
})
|
|
|
|
html = render(view)
|
|
assert count_repeater_items(html) == 2
|
|
# "Printing" was item 0, should be gone
|
|
refute html =~ "Printing"
|
|
assert html =~ "Delivery"
|
|
end
|
|
|
|
test "moving a repeater item changes order", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
render_click(view, "repeater_move", %{
|
|
"block-id" => info_card["id"],
|
|
"field" => "items",
|
|
"index" => "0",
|
|
"dir" => "down"
|
|
})
|
|
|
|
# Save and check the persisted order
|
|
render_click(view, "save")
|
|
|
|
saved = Pages.get_page("contact")
|
|
saved_info = Enum.find(saved.blocks, &(&1["type"] == "info_card"))
|
|
labels = Enum.map(saved_info["settings"]["items"], & &1["label"])
|
|
|
|
# "Printing" moved from 0 to 1
|
|
assert labels == ["Delivery", "Printing", "Issues"]
|
|
end
|
|
|
|
test "repeater changes persist after save", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/contact")
|
|
|
|
page = Pages.get_page("contact")
|
|
info_card = Enum.find(page.blocks, &(&1["type"] == "info_card"))
|
|
|
|
render_click(view, "toggle_expand", %{"id" => info_card["id"]})
|
|
|
|
# Edit item via phx-change
|
|
render_change(view, "update_block_settings", %{
|
|
"block_id" => info_card["id"],
|
|
"block_settings" => %{
|
|
"title" => "Good to know",
|
|
"items" => %{
|
|
"0" => %{"label" => "Shipping", "value" => "Fast shipping"},
|
|
"1" => %{"label" => "Returns", "value" => "Easy returns"}
|
|
}
|
|
}
|
|
})
|
|
|
|
render_click(view, "save")
|
|
|
|
saved = Pages.get_page("contact")
|
|
saved_info = Enum.find(saved.blocks, &(&1["type"] == "info_card"))
|
|
assert saved_info["settings"]["title"] == "Good to know"
|
|
assert length(saved_info["settings"]["items"]) == 2
|
|
assert Enum.at(saved_info["settings"]["items"], 0)["label"] == "Shipping"
|
|
assert Enum.at(saved_info["settings"]["items"], 0)["value"] == "Fast shipping"
|
|
end
|
|
end
|
|
|
|
describe "live preview" do
|
|
setup %{conn: conn, user: user} do
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "preview pane renders page content", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/pages/home")
|
|
|
|
# Preview pane should be in the DOM with the themed class
|
|
assert html =~ "page-editor-preview themed"
|
|
# Should render the page via PageRenderer (hero block is on home)
|
|
assert html =~ "page-editor-preview-pane"
|
|
end
|
|
|
|
test "toggle preview switches between edit and preview on mobile", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/home")
|
|
|
|
# Initially: editor visible, preview hidden on mobile
|
|
assert has_element?(view, ".page-editor-pane:not(.page-editor-pane-hidden-mobile)")
|
|
assert has_element?(view, ".page-editor-preview-hidden-mobile")
|
|
|
|
# Toggle to preview
|
|
render_click(view, "toggle_preview")
|
|
|
|
assert has_element?(view, ".page-editor-pane-hidden-mobile")
|
|
|
|
assert has_element?(
|
|
view,
|
|
".page-editor-preview-pane:not(.page-editor-preview-hidden-mobile)"
|
|
)
|
|
|
|
# Toggle back to edit
|
|
render_click(view, "toggle_preview")
|
|
|
|
assert has_element?(view, ".page-editor-pane:not(.page-editor-pane-hidden-mobile)")
|
|
assert has_element?(view, ".page-editor-preview-hidden-mobile")
|
|
end
|
|
|
|
test "preview updates when block settings change", %{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" => "Preview test title"}
|
|
})
|
|
|
|
# The preview should show the updated title
|
|
html = render(view)
|
|
assert html =~ "Preview test title"
|
|
end
|
|
|
|
test "preview updates after block reorder", %{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"]})
|
|
|
|
# Should still render without errors
|
|
html = render(view)
|
|
assert html =~ "page-editor-preview themed"
|
|
end
|
|
|
|
test "preview toggle button shows in header", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/home")
|
|
|
|
# Toggle button should be present
|
|
assert has_element?(view, ".page-editor-toggle-preview")
|
|
end
|
|
end
|
|
|
|
describe "custom pages on index" do
|
|
setup %{conn: conn, user: user} do
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "shows new page button", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
assert has_element?(view, "a[href='/admin/pages/new']", "New page")
|
|
end
|
|
|
|
test "shows custom pages section when pages exist", %{conn: conn} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
|
|
assert has_element?(view, ".page-group-title", "Custom pages")
|
|
assert has_element?(view, ".page-card-title", "FAQ")
|
|
end
|
|
|
|
test "does not show custom pages section when empty", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
refute has_element?(view, ".page-group-title", "Custom pages")
|
|
end
|
|
|
|
test "shows draft badge for unpublished pages", %{conn: conn} do
|
|
{:ok, _} =
|
|
Pages.create_custom_page(%{slug: "draft-page", title: "Draft", published: false})
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
assert has_element?(view, ".admin-badge-warning", "Draft")
|
|
end
|
|
|
|
test "custom page card links to editor", %{conn: conn} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
|
|
assert has_element?(view, "a[href='/admin/pages/faq']")
|
|
end
|
|
|
|
test "shows slug in card meta", %{conn: conn} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "our-story", title: "Our story"})
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
|
|
assert has_element?(view, ".page-card-meta", "/our-story")
|
|
end
|
|
|
|
test "delete removes page from list", %{conn: conn} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages")
|
|
|
|
assert has_element?(view, ".page-card-title", "FAQ")
|
|
|
|
view
|
|
|> element("button[phx-click='delete_custom_page'][phx-value-slug='faq']")
|
|
|> render_click()
|
|
|
|
refute has_element?(view, ".page-card-title", "FAQ")
|
|
assert render(view) =~ "Page deleted"
|
|
end
|
|
end
|
|
|
|
describe "custom page creation" do
|
|
setup %{conn: conn, user: user} do
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "renders creation form", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
assert has_element?(view, "h1", "New page")
|
|
assert has_element?(view, "#custom-page-form")
|
|
end
|
|
|
|
test "creating with valid data redirects to editor", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "FAQ", slug: "faq"})
|
|
|> render_submit()
|
|
|
|
assert_redirect(view, ~p"/admin/pages/faq")
|
|
end
|
|
|
|
test "creating with empty title shows error", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "", slug: "faq"})
|
|
|> render_submit()
|
|
|
|
assert has_element?(view, "#custom-page-form")
|
|
end
|
|
|
|
test "creating with reserved slug shows error", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
html =
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "Admin", slug: "admin"})
|
|
|> render_submit()
|
|
|
|
assert html =~ "is reserved"
|
|
end
|
|
|
|
test "creating with duplicate slug shows error", %{conn: conn} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
html =
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "FAQ 2", slug: "faq"})
|
|
|> render_submit()
|
|
|
|
assert html =~ "has already been taken"
|
|
end
|
|
|
|
test "auto-slugifies title during validation", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/new")
|
|
|
|
html =
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "Our Story"})
|
|
|> render_change()
|
|
|
|
assert html =~ ~s(value="our-story")
|
|
end
|
|
end
|
|
|
|
describe "custom page settings" do
|
|
setup %{conn: conn, user: user} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "help-page", title: "Help"})
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "renders settings form", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/help-page/settings")
|
|
|
|
assert has_element?(view, "h1", "Page settings")
|
|
assert has_element?(view, "#custom-page-form")
|
|
end
|
|
|
|
test "updating title saves correctly", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/help-page/settings")
|
|
|
|
view
|
|
|> form("#custom-page-form", page: %{title: "Help centre"})
|
|
|> render_submit()
|
|
|
|
assert_redirect(view, ~p"/admin/pages/help-page")
|
|
|
|
page = Pages.get_page("help-page")
|
|
assert page.title == "Help centre"
|
|
end
|
|
|
|
test "updating slug redirects to new editor path", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/help-page/settings")
|
|
|
|
view
|
|
|> form("#custom-page-form", page: %{slug: "support"})
|
|
|> render_submit()
|
|
|
|
assert_redirect(view, ~p"/admin/pages/support")
|
|
end
|
|
|
|
test "settings for nonexistent page redirects", %{conn: conn} do
|
|
assert {:error, {:live_redirect, %{to: "/admin/pages"}}} =
|
|
live(conn, ~p"/admin/pages/nonexistent/settings")
|
|
end
|
|
end
|
|
|
|
describe "editor for custom pages" do
|
|
setup %{conn: conn, user: user} do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "size-guide", title: "Size guide"})
|
|
%{conn: log_in_user(conn, user)}
|
|
end
|
|
|
|
test "shows settings button for custom pages", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/size-guide")
|
|
|
|
assert has_element?(view, "a[href='/admin/pages/size-guide/settings']", "Settings")
|
|
end
|
|
|
|
test "does not show reset to defaults for custom pages", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/size-guide")
|
|
|
|
refute has_element?(view, "button", "Reset to defaults")
|
|
end
|
|
|
|
test "shows reset to defaults for system pages", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/pages/home")
|
|
|
|
assert has_element?(view, "button", "Reset to defaults")
|
|
refute has_element?(view, "a[href='/admin/pages/home/settings']")
|
|
end
|
|
end
|
|
|
|
defp count_repeater_items(html) do
|
|
Regex.scan(~r/class="repeater-item"/, html) |> length()
|
|
end
|
|
end
|