Some checks failed
deploy / deploy (push) Has been cancelled
Legal pages (privacy, delivery, terms) now auto-populate content from shop settings on mount, show auto-generated vs customised badges, and have a regenerate button. Theme editor gains alt text fields for logo, header, and icon images. Image picker in page builder now has an upload button and alt text warning badges. Clearing unused image references shows an orphan info flash. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
348 lines
11 KiB
Elixir
348 lines
11 KiB
Elixir
defmodule Berrypod.LegalPagesTest do
|
|
use Berrypod.DataCase, async: true
|
|
|
|
alias Berrypod.LegalPages
|
|
alias Berrypod.Settings
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
defp has_heading?(blocks, text) do
|
|
Enum.any?(blocks, &(&1.type == :heading and &1.text == text))
|
|
end
|
|
|
|
defp has_text_containing?(blocks, fragment) do
|
|
Enum.any?(blocks, fn block ->
|
|
case block do
|
|
%{text: text} when is_binary(text) -> String.contains?(text, fragment)
|
|
%{items: items} -> Enum.any?(items, &String.contains?(&1, fragment))
|
|
_ -> false
|
|
end
|
|
end)
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# privacy_content/0
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "privacy_content/0" do
|
|
test "returns a list of blocks with :lead as the first" do
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert is_list(blocks)
|
|
assert hd(blocks).type == :lead
|
|
end
|
|
|
|
test "last block is a :closing disclaimer" do
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert List.last(blocks).type == :closing
|
|
assert String.contains?(List.last(blocks).text, "legal advice")
|
|
end
|
|
|
|
test "always includes core sections" do
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_heading?(blocks, "What we collect")
|
|
assert has_heading?(blocks, "Payment")
|
|
assert has_heading?(blocks, "Cookies")
|
|
assert has_heading?(blocks, "Third parties")
|
|
assert has_heading?(blocks, "Your rights")
|
|
assert has_heading?(blocks, "Contact")
|
|
end
|
|
|
|
test "includes PECR cart recovery wording for GB shop" do
|
|
Settings.put_setting("shop_country", "GB")
|
|
Settings.set_abandoned_cart_recovery(true)
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_heading?(blocks, "Cart recovery")
|
|
assert has_text_containing?(blocks, "PECR")
|
|
assert has_text_containing?(blocks, "Regulation 22")
|
|
end
|
|
|
|
test "includes GDPR legitimate interests wording for EU shop" do
|
|
Settings.put_setting("shop_country", "DE")
|
|
Settings.set_abandoned_cart_recovery(true)
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_heading?(blocks, "Cart recovery")
|
|
assert has_text_containing?(blocks, "Article 6(1)(f)")
|
|
refute has_text_containing?(blocks, "PECR")
|
|
end
|
|
|
|
test "includes generic cart recovery wording for non-UK/EU shop" do
|
|
Settings.put_setting("shop_country", "US")
|
|
Settings.set_abandoned_cart_recovery(true)
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_heading?(blocks, "Cart recovery")
|
|
refute has_text_containing?(blocks, "PECR")
|
|
refute has_text_containing?(blocks, "Article 6(1)(f)")
|
|
end
|
|
|
|
test "omits cart recovery section when feature is disabled" do
|
|
Settings.set_abandoned_cart_recovery(false)
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
refute has_heading?(blocks, "Cart recovery")
|
|
end
|
|
|
|
test "includes newsletter section when enabled" do
|
|
Settings.put_setting("newsletter_enabled", true, "boolean")
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_heading?(blocks, "Newsletter")
|
|
end
|
|
|
|
test "omits newsletter section when disabled" do
|
|
Settings.put_setting("newsletter_enabled", false, "boolean")
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
refute has_heading?(blocks, "Newsletter")
|
|
end
|
|
|
|
test "includes jurisdiction language in lead for GB" do
|
|
Settings.put_setting("shop_country", "GB")
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_text_containing?(blocks, "UK GDPR")
|
|
assert has_text_containing?(blocks, "PECR")
|
|
end
|
|
|
|
test "includes GDPR jurisdiction language for EU country" do
|
|
Settings.put_setting("shop_country", "FR")
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_text_containing?(blocks, "EU General Data Protection Regulation")
|
|
end
|
|
|
|
test "includes contact email when set" do
|
|
Settings.put_setting("contact_email", "hello@example.com")
|
|
|
|
blocks = LegalPages.privacy_content()
|
|
|
|
assert has_text_containing?(blocks, "hello@example.com")
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# delivery_content/0
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "delivery_content/0" do
|
|
test "returns a list of blocks with :lead as the first" do
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
assert is_list(blocks)
|
|
assert hd(blocks).type == :lead
|
|
end
|
|
|
|
test "always includes returns, cancellations, and production sections" do
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
assert has_heading?(blocks, "Production time")
|
|
assert has_heading?(blocks, "Returns & exchanges")
|
|
assert has_heading?(blocks, "Cancellations")
|
|
end
|
|
|
|
test "cites Consumer Contracts Regulations in returns section" do
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
assert has_text_containing?(blocks, "Consumer Contracts")
|
|
assert has_text_containing?(blocks, "Regulation 28(1)(b)")
|
|
end
|
|
|
|
test "cites Consumer Rights Act for defective goods" do
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
assert has_text_containing?(blocks, "Consumer Rights Act 2015")
|
|
end
|
|
|
|
test "handles empty shipping countries gracefully" do
|
|
# No shipping rates seeded — list will be empty in test
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
# Should not crash and should include a fallback paragraph
|
|
assert is_list(blocks)
|
|
assert has_text_containing?(blocks, "worldwide")
|
|
end
|
|
|
|
test "last block is a :closing disclaimer" do
|
|
blocks = LegalPages.delivery_content()
|
|
|
|
assert List.last(blocks).type == :closing
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# terms_content/0
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "terms_content/0" do
|
|
test "returns a list of blocks with :lead as the first" do
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert is_list(blocks)
|
|
assert hd(blocks).type == :lead
|
|
end
|
|
|
|
test "always includes core sections" do
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert has_heading?(blocks, "Products")
|
|
assert has_heading?(blocks, "Payment")
|
|
assert has_heading?(blocks, "Returns")
|
|
assert has_heading?(blocks, "Governing law")
|
|
assert has_heading?(blocks, "Changes")
|
|
end
|
|
|
|
test "governing law is English law for GB shop" do
|
|
Settings.put_setting("shop_country", "GB")
|
|
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert has_text_containing?(blocks, "English law")
|
|
end
|
|
|
|
test "governing law references EU regulations for EU shop" do
|
|
Settings.put_setting("shop_country", "DE")
|
|
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert has_text_containing?(blocks, "EU regulations")
|
|
end
|
|
|
|
test "includes VAT clause when vat_enabled is true" do
|
|
Settings.put_setting("vat_enabled", true, "boolean")
|
|
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert has_text_containing?(blocks, "VAT")
|
|
end
|
|
|
|
test "omits VAT clause when vat_enabled is false" do
|
|
Settings.put_setting("vat_enabled", false, "boolean")
|
|
|
|
blocks = LegalPages.terms_content()
|
|
|
|
refute has_text_containing?(blocks, "VAT")
|
|
end
|
|
|
|
test "includes shop name in lead" do
|
|
Settings.put_setting("shop_name", "Petal & Ink")
|
|
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert has_text_containing?(blocks, "Petal & Ink")
|
|
end
|
|
|
|
test "last block is a :closing disclaimer" do
|
|
blocks = LegalPages.terms_content()
|
|
|
|
assert List.last(blocks).type == :closing
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# regenerate_legal_content/1
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "regenerate_legal_content/1" do
|
|
test "returns a non-empty string for privacy" do
|
|
result = LegalPages.regenerate_legal_content("privacy")
|
|
assert is_binary(result)
|
|
assert String.length(result) > 100
|
|
end
|
|
|
|
test "returns a non-empty string for delivery" do
|
|
result = LegalPages.regenerate_legal_content("delivery")
|
|
assert is_binary(result)
|
|
assert String.length(result) > 100
|
|
end
|
|
|
|
test "returns a non-empty string for terms" do
|
|
result = LegalPages.regenerate_legal_content("terms")
|
|
assert is_binary(result)
|
|
assert String.length(result) > 100
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# rich_text_to_plain/1
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "rich_text_to_plain/1" do
|
|
test "converts headings to markdown-style" do
|
|
blocks = [%{type: :heading, text: "Hello"}]
|
|
assert LegalPages.rich_text_to_plain(blocks) == "## Hello"
|
|
end
|
|
|
|
test "converts paragraphs as-is" do
|
|
blocks = [%{type: :paragraph, text: "Some text here."}]
|
|
assert LegalPages.rich_text_to_plain(blocks) == "Some text here."
|
|
end
|
|
|
|
test "converts lists to bullet points" do
|
|
blocks = [%{type: :list, items: ["One", "Two", "Three"]}]
|
|
assert LegalPages.rich_text_to_plain(blocks) == "• One\n• Two\n• Three"
|
|
end
|
|
|
|
test "converts lead blocks" do
|
|
blocks = [%{type: :lead, text: "Important intro"}]
|
|
assert LegalPages.rich_text_to_plain(blocks) == "Important intro"
|
|
end
|
|
|
|
test "converts updated_at blocks" do
|
|
blocks = [%{type: :updated_at, date: "28 February 2026"}]
|
|
assert LegalPages.rich_text_to_plain(blocks) == "Last updated: 28 February 2026"
|
|
end
|
|
|
|
test "joins multiple blocks with double newlines" do
|
|
blocks = [
|
|
%{type: :heading, text: "Title"},
|
|
%{type: :paragraph, text: "Body text."}
|
|
]
|
|
|
|
assert LegalPages.rich_text_to_plain(blocks) == "## Title\n\nBody text."
|
|
end
|
|
|
|
test "skips unknown block types" do
|
|
blocks = [
|
|
%{type: :heading, text: "Title"},
|
|
%{type: :unknown_thing, data: "whatever"},
|
|
%{type: :paragraph, text: "Body."}
|
|
]
|
|
|
|
assert LegalPages.rich_text_to_plain(blocks) == "## Title\n\nBody."
|
|
end
|
|
end
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# legal_slug?/1
|
|
# ---------------------------------------------------------------------------
|
|
|
|
describe "legal_slug?/1" do
|
|
test "returns true for legal page slugs" do
|
|
assert LegalPages.legal_slug?("privacy")
|
|
assert LegalPages.legal_slug?("delivery")
|
|
assert LegalPages.legal_slug?("terms")
|
|
end
|
|
|
|
test "returns false for non-legal slugs" do
|
|
refute LegalPages.legal_slug?("home")
|
|
refute LegalPages.legal_slug?("about")
|
|
refute LegalPages.legal_slug?("contact")
|
|
end
|
|
end
|
|
end
|