All checks were successful
deploy / deploy (push) Successful in 1m24s
New block types: spacer, divider, button/CTA, video embed (YouTube, Vimeo with privacy-enhanced embeds, fallback for unknown URLs). Page templates (blank, content, landing) shown when creating custom pages. Duplicate page action on admin index with slug deduplication. Fix block picker on shop edit sidebar being cut off on mobile by accounting for bottom nav and making the grid scrollable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
637 lines
20 KiB
Elixir
637 lines
20 KiB
Elixir
defmodule Berrypod.PagesTest do
|
|
use Berrypod.DataCase, async: false
|
|
|
|
alias Berrypod.Pages
|
|
alias Berrypod.Pages.{Page, BlockTypes, Defaults, PageCache}
|
|
|
|
setup do
|
|
# Clear cached pages between tests so save_page side effects don't leak
|
|
PageCache.invalidate_all()
|
|
:ok
|
|
end
|
|
|
|
describe "get_page/1" do
|
|
test "returns defaults when nothing in DB" do
|
|
page = Pages.get_page("home")
|
|
|
|
assert page.slug == "home"
|
|
assert page.title == "Home page"
|
|
assert is_list(page.blocks)
|
|
assert length(page.blocks) == 4
|
|
|
|
types = Enum.map(page.blocks, & &1["type"])
|
|
assert types == ["hero", "category_nav", "featured_products", "image_text"]
|
|
end
|
|
|
|
test "returns DB version when page has been saved" do
|
|
{:ok, _} =
|
|
Pages.save_page("home", %{
|
|
title: "Custom home",
|
|
blocks: [%{"id" => "blk_test", "type" => "hero", "settings" => %{}}]
|
|
})
|
|
|
|
page = Pages.get_page("home")
|
|
|
|
assert page.slug == "home"
|
|
assert page.title == "Custom home"
|
|
assert length(page.blocks) == 1
|
|
end
|
|
|
|
test "each block has an id, type, and settings" do
|
|
page = Pages.get_page("contact")
|
|
|
|
for block <- page.blocks do
|
|
assert is_binary(block["id"]), "block missing id"
|
|
assert is_binary(block["type"]), "block missing type"
|
|
assert is_map(block["settings"]), "block missing settings"
|
|
end
|
|
end
|
|
|
|
test "returns nil for unknown custom slug" do
|
|
assert Pages.get_page("does-not-exist") == nil
|
|
end
|
|
|
|
test "returns page data for created custom page" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "my-page", title: "My page"})
|
|
page = Pages.get_page("my-page")
|
|
|
|
assert page.slug == "my-page"
|
|
assert page.title == "My page"
|
|
assert page.type == "custom"
|
|
end
|
|
end
|
|
|
|
describe "list_system_pages/0" do
|
|
test "returns all 14 pages" do
|
|
pages = Pages.list_system_pages()
|
|
|
|
assert length(pages) == 14
|
|
slugs = Enum.map(pages, & &1.slug)
|
|
assert "home" in slugs
|
|
assert "pdp" in slugs
|
|
assert "error" in slugs
|
|
end
|
|
end
|
|
|
|
describe "list_custom_pages/0" do
|
|
test "returns empty list when no custom pages exist" do
|
|
assert Pages.list_custom_pages() == []
|
|
end
|
|
|
|
test "returns custom pages ordered by title" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "zebra", title: "Zebra"})
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "apple", title: "Apple"})
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "mango", title: "Mango"})
|
|
|
|
pages = Pages.list_custom_pages()
|
|
titles = Enum.map(pages, & &1.title)
|
|
assert titles == ["Apple", "Mango", "Zebra"]
|
|
end
|
|
|
|
test "does not include system pages" do
|
|
{:ok, _} = Pages.save_page("home", %{title: "Home page", blocks: []})
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "custom-one", title: "Custom"})
|
|
|
|
pages = Pages.list_custom_pages()
|
|
slugs = Enum.map(pages, & &1.slug)
|
|
assert "custom-one" in slugs
|
|
refute "home" in slugs
|
|
end
|
|
end
|
|
|
|
describe "list_all_pages/0" do
|
|
test "includes both system and custom pages" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
|
|
pages = Pages.list_all_pages()
|
|
slugs = Enum.map(pages, & &1.slug)
|
|
assert "home" in slugs
|
|
assert "faq" in slugs
|
|
end
|
|
end
|
|
|
|
describe "save_page/2" do
|
|
test "creates a new page in the DB" do
|
|
blocks = [%{"id" => "blk_1", "type" => "hero", "settings" => %{"title" => "Hello"}}]
|
|
|
|
assert {:ok, page} = Pages.save_page("about", %{title: "About us", blocks: blocks})
|
|
|
|
assert page.slug == "about"
|
|
assert page.title == "About us"
|
|
assert length(page.blocks) == 1
|
|
end
|
|
|
|
test "updates an existing page" do
|
|
{:ok, _} = Pages.save_page("about", %{title: "About v1", blocks: []})
|
|
{:ok, page} = Pages.save_page("about", %{title: "About v2", blocks: []})
|
|
|
|
assert page.title == "About v2"
|
|
assert Repo.aggregate(from(p in Page, where: p.slug == "about"), :count) == 1
|
|
end
|
|
|
|
test "invalidates cache on save" do
|
|
# Prime the cache
|
|
_page = Pages.get_page("home")
|
|
|
|
# Save a new version
|
|
{:ok, _} = Pages.save_page("home", %{title: "New home", blocks: []})
|
|
|
|
# Should return the new version
|
|
page = Pages.get_page("home")
|
|
assert page.title == "New home"
|
|
end
|
|
|
|
test "rejects invalid slug format" do
|
|
assert {:error, changeset} =
|
|
Pages.save_page("UPPER CASE", %{title: "Bad", blocks: []})
|
|
|
|
assert errors_on(changeset).slug
|
|
end
|
|
end
|
|
|
|
describe "create_custom_page/1" do
|
|
test "creates a custom page with valid slug" do
|
|
assert {:ok, page} = Pages.create_custom_page(%{slug: "our-story", title: "Our story"})
|
|
|
|
assert page.slug == "our-story"
|
|
assert page.title == "Our story"
|
|
assert page.type == "custom"
|
|
assert page.blocks == []
|
|
assert page.published == true
|
|
end
|
|
|
|
test "rejects duplicate slug" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "faq", title: "FAQ"})
|
|
assert {:error, changeset} = Pages.create_custom_page(%{slug: "faq", title: "FAQ 2"})
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "rejects system slug" do
|
|
assert {:error, changeset} = Pages.create_custom_page(%{slug: "home", title: "Home"})
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "rejects reserved path" do
|
|
assert {:error, changeset} = Pages.create_custom_page(%{slug: "admin", title: "Admin"})
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "rejects invalid slug format" do
|
|
assert {:error, changeset} =
|
|
Pages.create_custom_page(%{slug: "My Page", title: "My page"})
|
|
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "rejects slug with leading hyphen" do
|
|
assert {:error, changeset} = Pages.create_custom_page(%{slug: "-bad", title: "Bad"})
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "rejects slug with trailing hyphen" do
|
|
assert {:error, changeset} = Pages.create_custom_page(%{slug: "bad-", title: "Bad"})
|
|
assert errors_on(changeset).slug
|
|
end
|
|
|
|
test "accepts single-character slug" do
|
|
assert {:ok, page} = Pages.create_custom_page(%{slug: "x", title: "X"})
|
|
assert page.slug == "x"
|
|
end
|
|
|
|
test "populates cache on create" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "cached-page", title: "Cached"})
|
|
assert {:ok, data} = PageCache.get("cached-page")
|
|
assert data.title == "Cached"
|
|
assert data.type == "custom"
|
|
end
|
|
end
|
|
|
|
describe "delete_custom_page/1" do
|
|
test "deletes a custom page" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "temp", title: "Temp"})
|
|
page_struct = Pages.get_page_struct("temp")
|
|
assert {:ok, _} = Pages.delete_custom_page(page_struct)
|
|
assert Pages.get_page("temp") == nil
|
|
end
|
|
|
|
test "refuses to delete a system page" do
|
|
{:ok, page} = Pages.save_page("home", %{title: "Home page", blocks: []})
|
|
assert {:error, :system_page} = Pages.delete_custom_page(page)
|
|
end
|
|
|
|
test "invalidates cache on delete" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "bye", title: "Bye"})
|
|
page_struct = Pages.get_page_struct("bye")
|
|
Pages.delete_custom_page(page_struct)
|
|
assert :miss = PageCache.get("bye")
|
|
end
|
|
end
|
|
|
|
describe "update_custom_page/2" do
|
|
test "updates title" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "updates", title: "V1"})
|
|
page = Pages.get_page_struct("updates")
|
|
assert {:ok, updated} = Pages.update_custom_page(page, %{title: "V2"})
|
|
assert updated.title == "V2"
|
|
end
|
|
|
|
test "slug change creates redirect" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "old-name", title: "Page"})
|
|
page = Pages.get_page_struct("old-name")
|
|
assert {:ok, updated} = Pages.update_custom_page(page, %{slug: "new-name"})
|
|
assert updated.slug == "new-name"
|
|
|
|
assert {:ok, redirect} = Berrypod.Redirects.lookup("/old-name")
|
|
assert redirect.to_path == "/new-name"
|
|
end
|
|
|
|
test "slug change invalidates old cache and populates new" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "rename-me", title: "Page"})
|
|
page = Pages.get_page_struct("rename-me")
|
|
{:ok, _} = Pages.update_custom_page(page, %{slug: "renamed"})
|
|
|
|
assert :miss = PageCache.get("rename-me")
|
|
assert {:ok, cached} = PageCache.get("renamed")
|
|
assert cached.slug == "renamed"
|
|
end
|
|
|
|
test "updates meta and nav fields" do
|
|
{:ok, _} = Pages.create_custom_page(%{slug: "meta-test", title: "Meta"})
|
|
page = Pages.get_page_struct("meta-test")
|
|
|
|
{:ok, updated} =
|
|
Pages.update_custom_page(page, %{
|
|
meta_description: "A test page",
|
|
show_in_nav: true,
|
|
nav_label: "Test",
|
|
nav_position: 5
|
|
})
|
|
|
|
assert updated.meta_description == "A test page"
|
|
assert updated.show_in_nav == true
|
|
assert updated.nav_label == "Test"
|
|
assert updated.nav_position == 5
|
|
end
|
|
|
|
test "refuses to update a system page" do
|
|
{:ok, page} = Pages.save_page("home", %{title: "Home page", blocks: []})
|
|
assert {:error, :system_page} = Pages.update_custom_page(page, %{title: "Nope"})
|
|
end
|
|
end
|
|
|
|
describe "reset_page/1" do
|
|
test "restores defaults after customisation" do
|
|
{:ok, _} =
|
|
Pages.save_page("home", %{
|
|
title: "Custom",
|
|
blocks: [%{"id" => "blk_1", "type" => "hero", "settings" => %{}}]
|
|
})
|
|
|
|
assert :ok = Pages.reset_page("home")
|
|
|
|
page = Pages.get_page("home")
|
|
assert page.title == "Home page"
|
|
assert length(page.blocks) == 4
|
|
end
|
|
|
|
test "is a no-op for a page that was never saved" do
|
|
assert :ok = Pages.reset_page("cart")
|
|
end
|
|
end
|
|
|
|
describe "load_block_data/2" do
|
|
test "returns empty map when no blocks have data loaders" do
|
|
blocks = [
|
|
%{"type" => "hero", "settings" => %{}},
|
|
%{"type" => "category_nav", "settings" => %{}}
|
|
]
|
|
|
|
assert Pages.load_block_data(blocks, %{}) == %{}
|
|
end
|
|
|
|
test "loads review data when reviews_section block is present" do
|
|
blocks = [%{"type" => "reviews_section", "settings" => %{}}]
|
|
data = Pages.load_block_data(blocks, %{})
|
|
|
|
assert is_list(data.reviews)
|
|
assert data.average_rating == 5
|
|
assert data.total_count == 24
|
|
end
|
|
|
|
test "loads featured products with configurable count" do
|
|
blocks = [%{"type" => "featured_products", "settings" => %{"product_count" => 4}}]
|
|
data = Pages.load_block_data(blocks, %{mode: :preview})
|
|
|
|
assert is_list(data.products)
|
|
end
|
|
|
|
test "loads related products in preview mode" do
|
|
blocks = [%{"type" => "related_products", "settings" => %{}}]
|
|
data = Pages.load_block_data(blocks, %{mode: :preview})
|
|
|
|
assert is_list(data.related_products)
|
|
end
|
|
|
|
test "loads related products from DB with product context" do
|
|
blocks = [%{"type" => "related_products", "settings" => %{}}]
|
|
data = Pages.load_block_data(blocks, %{product: %{category: "Prints", id: "fake-id"}})
|
|
|
|
assert is_list(data.related_products)
|
|
end
|
|
|
|
test "merges data from multiple loaders" do
|
|
blocks = [
|
|
%{"type" => "featured_products", "settings" => %{}},
|
|
%{"type" => "reviews_section", "settings" => %{}}
|
|
]
|
|
|
|
data = Pages.load_block_data(blocks, %{mode: :preview})
|
|
|
|
assert Map.has_key?(data, :products)
|
|
assert Map.has_key?(data, :reviews)
|
|
end
|
|
end
|
|
|
|
describe "BlockTypes" do
|
|
test "all/0 returns all block types" do
|
|
types = BlockTypes.all()
|
|
|
|
assert is_map(types)
|
|
assert map_size(types) > 20
|
|
assert Map.has_key?(types, "hero")
|
|
assert Map.has_key?(types, "product_hero")
|
|
assert Map.has_key?(types, "search_results")
|
|
end
|
|
|
|
test "get/1 returns a block type definition" do
|
|
hero = BlockTypes.get("hero")
|
|
|
|
assert hero.name == "Hero banner"
|
|
assert hero.icon == "hero-megaphone"
|
|
assert hero.allowed_on == :all
|
|
assert is_list(hero.settings_schema)
|
|
end
|
|
|
|
test "get/1 returns nil for unknown type" do
|
|
assert BlockTypes.get("nonexistent") == nil
|
|
end
|
|
|
|
test "allowed_for/1 returns portable + page-specific blocks" do
|
|
pdp_types = BlockTypes.allowed_for("pdp")
|
|
|
|
# Portable blocks are included
|
|
assert Map.has_key?(pdp_types, "hero")
|
|
assert Map.has_key?(pdp_types, "featured_products")
|
|
|
|
# PDP-specific blocks are included
|
|
assert Map.has_key?(pdp_types, "product_hero")
|
|
assert Map.has_key?(pdp_types, "breadcrumb")
|
|
|
|
# Other page-specific blocks are excluded
|
|
refute Map.has_key?(pdp_types, "cart_items")
|
|
refute Map.has_key?(pdp_types, "contact_form")
|
|
end
|
|
|
|
test "allowed_for/1 includes only relevant page-specific blocks" do
|
|
cart_types = BlockTypes.allowed_for("cart")
|
|
|
|
assert Map.has_key?(cart_types, "cart_items")
|
|
assert Map.has_key?(cart_types, "order_summary")
|
|
refute Map.has_key?(cart_types, "product_hero")
|
|
end
|
|
|
|
test "allowed_for/1 returns only portable blocks for custom pages" do
|
|
custom_types = BlockTypes.allowed_for("my-custom-page")
|
|
|
|
# Portable blocks are included
|
|
assert Map.has_key?(custom_types, "hero")
|
|
assert Map.has_key?(custom_types, "featured_products")
|
|
assert Map.has_key?(custom_types, "image_text")
|
|
|
|
# Page-specific blocks are excluded
|
|
refute Map.has_key?(custom_types, "cart_items")
|
|
refute Map.has_key?(custom_types, "product_hero")
|
|
refute Map.has_key?(custom_types, "contact_form")
|
|
end
|
|
|
|
test "every block type has required fields" do
|
|
for {type, def} <- BlockTypes.all() do
|
|
assert is_binary(def.name), "#{type} missing name"
|
|
assert is_binary(def.icon), "#{type} missing icon"
|
|
assert def.allowed_on == :all or is_list(def.allowed_on), "#{type} bad allowed_on"
|
|
assert is_list(def.settings_schema), "#{type} missing settings_schema"
|
|
end
|
|
end
|
|
|
|
test "settings_schema entries have required fields" do
|
|
for {type, def} <- BlockTypes.all(), setting <- def.settings_schema do
|
|
assert is_binary(setting.key), "#{type} setting missing key"
|
|
assert is_binary(setting.label), "#{type} setting missing label"
|
|
assert is_atom(setting.type), "#{type} setting missing type"
|
|
assert Map.has_key?(setting, :default), "#{type} setting #{setting.key} missing default"
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "Defaults" do
|
|
test "all/0 returns 14 page definitions" do
|
|
all = Defaults.all()
|
|
assert length(all) == 14
|
|
end
|
|
|
|
test "every default page has blocks matching registered types" do
|
|
for page <- Defaults.all() do
|
|
assert is_binary(page.slug), "page missing slug"
|
|
assert is_binary(page.title), "page missing title"
|
|
assert is_list(page.blocks), "#{page.slug} blocks is not a list"
|
|
|
|
for block <- page.blocks do
|
|
assert BlockTypes.get(block["type"]),
|
|
"#{page.slug} has unknown block type: #{block["type"]}"
|
|
end
|
|
end
|
|
end
|
|
|
|
test "every default block type is allowed on its page" do
|
|
for page <- Defaults.all() do
|
|
allowed = BlockTypes.allowed_for(page.slug)
|
|
|
|
for block <- page.blocks do
|
|
assert Map.has_key?(allowed, block["type"]),
|
|
"#{block["type"]} not allowed on #{page.slug}"
|
|
end
|
|
end
|
|
end
|
|
|
|
test "block IDs are unique within each page" do
|
|
for page <- Defaults.all() do
|
|
ids = Enum.map(page.blocks, & &1["id"])
|
|
assert ids == Enum.uniq(ids), "duplicate block IDs on #{page.slug}"
|
|
end
|
|
end
|
|
|
|
test "home page has the expected blocks" do
|
|
page = Defaults.for_slug("home")
|
|
|
|
types = Enum.map(page.blocks, & &1["type"])
|
|
assert types == ["hero", "category_nav", "featured_products", "image_text"]
|
|
end
|
|
|
|
test "pdp page has the expected blocks" do
|
|
page = Defaults.for_slug("pdp")
|
|
|
|
types = Enum.map(page.blocks, & &1["type"])
|
|
|
|
assert types == [
|
|
"breadcrumb",
|
|
"product_hero",
|
|
"trust_badges",
|
|
"product_details",
|
|
"reviews_section",
|
|
"related_products"
|
|
]
|
|
end
|
|
|
|
test "contact page has the expected blocks" do
|
|
page = Defaults.for_slug("contact")
|
|
|
|
types = Enum.map(page.blocks, & &1["type"])
|
|
|
|
assert types == [
|
|
"hero",
|
|
"contact_form",
|
|
"order_tracking_card",
|
|
"info_card",
|
|
"newsletter_card",
|
|
"social_links_card"
|
|
]
|
|
end
|
|
|
|
test "unknown slug returns humanised title and empty blocks" do
|
|
page = Defaults.for_slug("our-story")
|
|
|
|
assert page.slug == "our-story"
|
|
assert page.title == "Our story"
|
|
assert page.blocks == []
|
|
end
|
|
|
|
test "generate_block_id/0 produces unique prefixed IDs" do
|
|
id1 = Defaults.generate_block_id()
|
|
id2 = Defaults.generate_block_id()
|
|
|
|
assert String.starts_with?(id1, "blk_")
|
|
assert String.starts_with?(id2, "blk_")
|
|
assert id1 != id2
|
|
end
|
|
end
|
|
|
|
describe "PageCache" do
|
|
test "get/1 returns :miss for uncached slug" do
|
|
assert :miss = PageCache.get("nonexistent_slug_#{System.unique_integer()}")
|
|
end
|
|
|
|
test "put/2 and get/1 round-trip" do
|
|
slug = "test_cache_#{System.unique_integer()}"
|
|
page_data = %{slug: slug, title: "Test", blocks: []}
|
|
|
|
assert :ok = PageCache.put(slug, page_data)
|
|
assert {:ok, ^page_data} = PageCache.get(slug)
|
|
end
|
|
|
|
test "invalidate/1 removes cached entry" do
|
|
slug = "test_invalidate_#{System.unique_integer()}"
|
|
PageCache.put(slug, %{slug: slug, title: "Test", blocks: []})
|
|
|
|
assert :ok = PageCache.invalidate(slug)
|
|
assert :miss = PageCache.get(slug)
|
|
end
|
|
end
|
|
|
|
describe "Page schema" do
|
|
test "system_slugs/0 returns 14 slugs" do
|
|
assert length(Page.system_slugs()) == 14
|
|
end
|
|
|
|
test "system_slug?/1 returns true for system slugs" do
|
|
assert Page.system_slug?("home")
|
|
assert Page.system_slug?("cart")
|
|
refute Page.system_slug?("my-page")
|
|
end
|
|
|
|
test "reserved_path?/1 returns true for reserved paths" do
|
|
assert Page.reserved_path?("admin")
|
|
assert Page.reserved_path?("collections")
|
|
refute Page.reserved_path?("faq")
|
|
end
|
|
end
|
|
|
|
describe "templates" do
|
|
test "templates/0 returns available templates" do
|
|
templates = Defaults.templates()
|
|
|
|
assert length(templates) == 3
|
|
keys = Enum.map(templates, & &1.key)
|
|
assert "blank" in keys
|
|
assert "content" in keys
|
|
assert "landing" in keys
|
|
end
|
|
|
|
test "template_blocks/1 returns blocks for content template" do
|
|
blocks = Defaults.template_blocks("content")
|
|
|
|
types = Enum.map(blocks, & &1["type"])
|
|
assert types == ["hero", "content_body"]
|
|
end
|
|
|
|
test "template_blocks/1 returns blocks for landing template" do
|
|
blocks = Defaults.template_blocks("landing")
|
|
|
|
types = Enum.map(blocks, & &1["type"])
|
|
assert "hero" in types
|
|
assert "featured_products" in types
|
|
assert "button" in types
|
|
end
|
|
|
|
test "template_blocks/1 returns empty list for blank" do
|
|
assert Defaults.template_blocks("blank") == []
|
|
end
|
|
|
|
test "template blocks have unique IDs" do
|
|
for tmpl <- Defaults.templates() do
|
|
blocks = Defaults.template_blocks(tmpl.key)
|
|
ids = Enum.map(blocks, & &1["id"])
|
|
assert ids == Enum.uniq(ids), "duplicate IDs in #{tmpl.key} template"
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "duplicate_custom_page/1" do
|
|
test "creates a draft copy with -copy slug" do
|
|
{:ok, original} =
|
|
Pages.create_custom_page(%{
|
|
"slug" => "test-original",
|
|
"title" => "Test Page",
|
|
"blocks" => [%{"id" => "blk_1", "type" => "hero", "settings" => %{"title" => "Hello"}}]
|
|
})
|
|
|
|
{:ok, copy} = Pages.duplicate_custom_page(original)
|
|
|
|
assert copy.slug == "test-original-copy"
|
|
assert copy.title == "Test Page (copy)"
|
|
assert copy.published == false
|
|
assert length(copy.blocks) == 1
|
|
assert hd(copy.blocks)["type"] == "hero"
|
|
end
|
|
|
|
test "deduplicates slug when copy already exists" do
|
|
{:ok, original} =
|
|
Pages.create_custom_page(%{"slug" => "dup-test", "title" => "Dup Test"})
|
|
|
|
{:ok, _first_copy} = Pages.duplicate_custom_page(original)
|
|
{:ok, second_copy} = Pages.duplicate_custom_page(original)
|
|
|
|
assert second_copy.slug == "dup-test-copy-2"
|
|
end
|
|
end
|
|
end
|