add SettingsField struct and repeater field type for block settings
All checks were successful
deploy / deploy (push) Successful in 1m23s
All checks were successful
deploy / deploy (push) Successful in 1m23s
Introduces typed settings schema with SettingsField struct, replaces the read-only JSON textarea with a full repeater UI for info_card items. Supports add, remove, reorder and inline editing of repeater items. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -478,4 +478,153 @@ defmodule BerrypodWeb.Admin.PagesTest do
|
||||
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
|
||||
|
||||
defp count_repeater_items(html) do
|
||||
Regex.scan(~r/class="repeater-item"/, html) |> length()
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user