2026-02-26 21:15:01 +00:00
|
|
|
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
|
2026-02-26 21:47:24 +00:00
|
|
|
|
|
|
|
|
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
|
2026-02-27 00:54:13 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-02-27 08:06:17 +00:00
|
|
|
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
|
|
|
|
|
|
2026-02-27 00:54:13 +00:00
|
|
|
defp count_repeater_items(html) do
|
|
|
|
|
Regex.scan(~r/class="repeater-item"/, html) |> length()
|
|
|
|
|
end
|
2026-02-26 21:15:01 +00:00
|
|
|
end
|