2026-02-26 18:07:57 +00:00
|
|
|
defmodule BerrypodWeb.PageRendererTest do
|
|
|
|
|
use BerrypodWeb.ConnCase, async: false
|
|
|
|
|
|
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
|
|
|
|
|
|
alias Berrypod.Pages
|
|
|
|
|
alias Berrypod.Settings.ThemeSettings
|
|
|
|
|
alias BerrypodWeb.PageRenderer
|
|
|
|
|
|
|
|
|
|
# Minimal assigns that every page needs (the stuff shop_layout requires)
|
|
|
|
|
defp base_assigns do
|
|
|
|
|
%{
|
|
|
|
|
__changed__: nil,
|
|
|
|
|
theme_settings: %ThemeSettings{},
|
|
|
|
|
logo_image: nil,
|
|
|
|
|
header_image: nil,
|
|
|
|
|
mode: :shop,
|
|
|
|
|
cart_items: [],
|
|
|
|
|
cart_count: 0,
|
|
|
|
|
cart_subtotal: "£0.00",
|
|
|
|
|
cart_total: nil,
|
|
|
|
|
cart_drawer_open: false,
|
|
|
|
|
cart_status: nil,
|
|
|
|
|
is_admin: false,
|
|
|
|
|
search_query: "",
|
|
|
|
|
search_results: [],
|
|
|
|
|
search_open: false,
|
|
|
|
|
categories: [],
|
|
|
|
|
shipping_estimate: nil,
|
|
|
|
|
country_code: "GB",
|
|
|
|
|
available_countries: [],
|
|
|
|
|
flash: %{}
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp render_page(slug, extra_assigns \\ %{}) do
|
|
|
|
|
page = Pages.get_page(slug)
|
|
|
|
|
|
|
|
|
|
assigns =
|
|
|
|
|
base_assigns()
|
|
|
|
|
|> Map.merge(extra_assigns)
|
|
|
|
|
|> Map.put(:page, page)
|
|
|
|
|
|
|
|
|
|
PageRenderer.render_page(assigns)
|
|
|
|
|
|> rendered_to_string()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "render_page/1" do
|
|
|
|
|
test "home page renders hero and featured products" do
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
2026-03-03 00:56:01 +00:00
|
|
|
assert html =~ "Your headline goes here"
|
2026-02-26 18:07:57 +00:00
|
|
|
assert html =~ "Shop the collection"
|
|
|
|
|
assert html =~ "Featured products"
|
2026-03-03 00:56:01 +00:00
|
|
|
assert html =~ "Your story in a nutshell"
|
2026-02-26 18:07:57 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "about page renders hero and content area" do
|
|
|
|
|
html =
|
|
|
|
|
render_page("about", %{content_blocks: [%{type: :paragraph, text: "Test about text"}]})
|
|
|
|
|
|
2026-03-03 00:56:01 +00:00
|
|
|
assert html =~ "About us"
|
2026-02-26 18:07:57 +00:00
|
|
|
assert html =~ "content-body"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "delivery page renders hero" do
|
|
|
|
|
html = render_page("delivery", %{content_blocks: []})
|
|
|
|
|
|
2026-02-26 18:29:20 +00:00
|
|
|
assert html =~ "Delivery & returns"
|
2026-02-26 18:07:57 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "privacy page renders hero" do
|
|
|
|
|
html = render_page("privacy", %{content_blocks: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Privacy policy"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "terms page renders hero" do
|
|
|
|
|
html = render_page("terms", %{content_blocks: []})
|
|
|
|
|
|
2026-02-26 18:29:20 +00:00
|
|
|
assert html =~ "Terms of service"
|
2026-02-26 18:07:57 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "contact page renders hero, form, and sidebar blocks" do
|
|
|
|
|
html = render_page("contact", %{tracking_state: :idle})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Get in touch"
|
|
|
|
|
assert html =~ "Send a message"
|
|
|
|
|
assert html =~ "Track your order"
|
|
|
|
|
assert html =~ "Handy to know"
|
|
|
|
|
assert html =~ "Newsletter"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "collection page renders header, filter bar, and grid" do
|
|
|
|
|
html = render_page("collection", %{products: [], collection_title: "All Products"})
|
|
|
|
|
|
|
|
|
|
assert html =~ "All Products"
|
|
|
|
|
assert html =~ "filter-bar"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "pdp page renders breadcrumb and product hero when product provided" do
|
|
|
|
|
product = %{
|
|
|
|
|
id: "test-id",
|
|
|
|
|
title: "Test Product",
|
|
|
|
|
slug: "test-product",
|
|
|
|
|
category: "Art Prints",
|
|
|
|
|
description: "A lovely test product",
|
|
|
|
|
cheapest_price: 2500,
|
|
|
|
|
compare_at_price: nil,
|
|
|
|
|
on_sale: false,
|
|
|
|
|
in_stock: true,
|
|
|
|
|
provider_data: %{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
html =
|
|
|
|
|
render_page("pdp", %{
|
|
|
|
|
product: product,
|
|
|
|
|
gallery_images: [],
|
|
|
|
|
display_price: 2500,
|
|
|
|
|
selected_variant: nil,
|
|
|
|
|
option_types: [],
|
|
|
|
|
selected_options: %{},
|
|
|
|
|
available_options: %{},
|
|
|
|
|
option_urls: %{},
|
|
|
|
|
quantity: 1,
|
|
|
|
|
related_products: [],
|
|
|
|
|
reviews: [],
|
|
|
|
|
average_rating: 5,
|
|
|
|
|
total_count: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Art Prints"
|
|
|
|
|
assert html =~ "Test Product"
|
|
|
|
|
assert html =~ "pdp-grid"
|
|
|
|
|
assert html =~ "Add to basket"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "cart page renders empty state when no items" do
|
|
|
|
|
html = render_page("cart")
|
|
|
|
|
|
|
|
|
|
assert html =~ "Your basket"
|
|
|
|
|
assert html =~ "cart-empty"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "cart page renders items when present" do
|
|
|
|
|
items = [
|
|
|
|
|
%{
|
|
|
|
|
variant_id: "v1",
|
|
|
|
|
name: "Test T-Shirt",
|
|
|
|
|
variant: "Black / M",
|
|
|
|
|
price: 2500,
|
|
|
|
|
quantity: 1,
|
|
|
|
|
image: nil,
|
|
|
|
|
product_id: "test-t-shirt",
|
|
|
|
|
in_stock: true
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
html =
|
|
|
|
|
render_page("cart", %{
|
|
|
|
|
cart_items: items,
|
|
|
|
|
cart_count: 1,
|
|
|
|
|
cart_subtotal: "£25.00",
|
|
|
|
|
cart_page_subtotal: 2500
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Your basket"
|
|
|
|
|
assert html =~ "cart-page-list"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "search page renders search form" do
|
|
|
|
|
html = render_page("search", %{search_page_query: "", search_page_results: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Search"
|
|
|
|
|
assert html =~ ~s(name="q")
|
|
|
|
|
assert html =~ "Search products..."
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "checkout success renders pending state when no order" do
|
|
|
|
|
html = render_page("checkout_success", %{order: nil})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Processing your payment"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "orders page renders empty state when orders nil" do
|
|
|
|
|
html = render_page("orders", %{orders: nil, lookup_email: nil})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Your orders"
|
|
|
|
|
assert html =~ "expired or is invalid"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "orders page renders no orders message" do
|
|
|
|
|
html = render_page("orders", %{orders: [], lookup_email: "test@example.com"})
|
|
|
|
|
|
|
|
|
|
assert html =~ "test@example.com"
|
|
|
|
|
assert html =~ "No orders found"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "order detail page renders nothing when no order" do
|
|
|
|
|
html = render_page("order_detail", %{order: nil})
|
|
|
|
|
|
|
|
|
|
# Should not crash, just render empty
|
|
|
|
|
assert html =~ "main"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "error page renders error hero" do
|
|
|
|
|
html =
|
|
|
|
|
render_page("error", %{
|
|
|
|
|
error_code: "404",
|
|
|
|
|
error_title: "Page not found",
|
|
|
|
|
error_description: "Sorry, we couldn't find that page.",
|
|
|
|
|
products: []
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert html =~ "404"
|
|
|
|
|
assert html =~ "Page not found"
|
|
|
|
|
assert html =~ "Go to Homepage"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "page_main_class/1" do
|
|
|
|
|
test "returns correct classes for each page" do
|
|
|
|
|
assert PageRenderer.page_main_class("contact") == "page-container contact-main"
|
|
|
|
|
assert PageRenderer.page_main_class("cart") == "page-container"
|
|
|
|
|
assert PageRenderer.page_main_class("checkout_success") == "page-container checkout-main"
|
|
|
|
|
assert PageRenderer.page_main_class("orders") == "page-container orders-main"
|
|
|
|
|
assert PageRenderer.page_main_class("order_detail") == "page-container order-detail-main"
|
|
|
|
|
assert PageRenderer.page_main_class("error") == "error-main"
|
|
|
|
|
assert PageRenderer.page_main_class("about") == "content-page"
|
|
|
|
|
assert PageRenderer.page_main_class("home") == nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-28 17:33:25 +00:00
|
|
|
describe "utility blocks" do
|
|
|
|
|
setup do
|
|
|
|
|
on_exit(fn ->
|
|
|
|
|
import Ecto.Query
|
|
|
|
|
Berrypod.Repo.delete_all(from p in Berrypod.Pages.Page, where: p.slug == "home")
|
|
|
|
|
Berrypod.Pages.PageCache.invalidate_all()
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
:ok
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "spacer block renders with size" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [%{"id" => "blk_sp", "type" => "spacer", "settings" => %{"size" => "large"}}]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ ~s(class="block-spacer")
|
|
|
|
|
assert html =~ ~s(data-size="large")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "divider block renders with style" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [%{"id" => "blk_dv", "type" => "divider", "settings" => %{"style" => "dots"}}]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ ~s(class="block-divider")
|
|
|
|
|
assert html =~ ~s(data-style="dots")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "button block renders themed link" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [
|
|
|
|
|
%{
|
|
|
|
|
"id" => "blk_btn",
|
|
|
|
|
"type" => "button",
|
|
|
|
|
"settings" => %{
|
|
|
|
|
"text" => "Shop now",
|
|
|
|
|
"href" => "/collections/all",
|
|
|
|
|
"style" => "primary",
|
|
|
|
|
"alignment" => "centre"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Shop now"
|
|
|
|
|
assert html =~ ~s(data-align="centre")
|
|
|
|
|
assert html =~ "themed-button"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "video embed renders YouTube iframe" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [
|
|
|
|
|
%{
|
|
|
|
|
"id" => "blk_vid",
|
|
|
|
|
"type" => "video_embed",
|
|
|
|
|
"settings" => %{
|
|
|
|
|
"url" => "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
|
|
|
"caption" => "Demo video"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
|
|
|
assert html =~ "Demo video"
|
|
|
|
|
assert html =~ "video-embed"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "video embed renders Vimeo iframe" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [
|
|
|
|
|
%{
|
|
|
|
|
"id" => "blk_vim",
|
|
|
|
|
"type" => "video_embed",
|
|
|
|
|
"settings" => %{"url" => "https://vimeo.com/123456789"}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "player.vimeo.com/video/123456789"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "video embed shows fallback link for unknown URLs" do
|
|
|
|
|
{:ok, _} =
|
|
|
|
|
Pages.save_page("home", %{
|
|
|
|
|
title: "Home",
|
|
|
|
|
blocks: [
|
|
|
|
|
%{
|
|
|
|
|
"id" => "blk_unk",
|
|
|
|
|
"type" => "video_embed",
|
|
|
|
|
"settings" => %{"url" => "https://example.com/video", "caption" => "My video"}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
html = render_page("home", %{products: []})
|
|
|
|
|
|
|
|
|
|
assert html =~ "video-embed-fallback"
|
|
|
|
|
assert html =~ "My video"
|
|
|
|
|
refute html =~ "<iframe"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-26 18:07:57 +00:00
|
|
|
describe "format_order_status/1" do
|
|
|
|
|
test "maps status strings to display text" do
|
|
|
|
|
assert PageRenderer.format_order_status("unfulfilled") == "Being prepared"
|
|
|
|
|
assert PageRenderer.format_order_status("shipped") == "On its way"
|
|
|
|
|
assert PageRenderer.format_order_status("delivered") == "Delivered"
|
|
|
|
|
assert PageRenderer.format_order_status("unknown") == "unknown"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|