add legal page generator for privacy, delivery, and terms
All checks were successful
deploy / deploy (push) Successful in 1m23s

Replaces hardcoded PreviewData placeholders with generated content
derived from real shop state: connected providers (production lead
times), shipping countries (grouped by region), shop country
(jurisdiction language and governing law), and feature flags
(abandoned cart recovery section, newsletter, VAT clause).

Returns policy correctly cites Consumer Contracts Regulations Reg
28(1)(b) for POD exemption and Consumer Rights Act for defective goods.
Cart recovery section uses jurisdiction-specific wording: PECR Reg 22
for UK, GDPR Art 6(1)(f) for EU, generic otherwise.

About page unchanged — shop owner's story to tell.

26 new tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-02-24 13:48:49 +00:00
parent fd355c3397
commit 933f685b63
5 changed files with 665 additions and 13 deletions

View File

@ -130,12 +130,12 @@ Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [admin-font-loading.m
| | **Other features** | | | |
| ~~72~~ | ~~Order status lookup — wire up existing stub on contact page (UI already exists, backend unbuilt)~~ | — | 1.5h | done |
| | **Abandoned cart recovery** ([plan](docs/plans/abandoned-cart.md)) | | | |
| 75 | Handle `checkout.session.expired` webhook, store abandoned cart record | — | 1h | planned |
| 76 | Send single recovery email (plain text, no tracking, clear opt-out) | 75 | 1h | planned |
| 77 | Suppression list (unsubscribe), 30-day data pruning Oban job, Stripe footer notice | 76 | 1h | planned |
| ~~75~~ | ~~Handle `checkout.session.expired` webhook, store abandoned cart record~~ | — | 1h | done |
| ~~76~~ | ~~Send single recovery email (plain text, no tracking, clear opt-out)~~ | 75 | 1h | done |
| ~~77~~ | ~~Suppression list (unsubscribe), 30-day data pruning Oban job, Stripe footer notice~~ | 76 | 1h | done |
| | **Legal page generator** ([plan](docs/plans/legal-page-generator.md)) | | | |
| 83 | `LegalPages` module — generate accurate privacy, delivery, and terms content from settings + provider + shipping data | — | 2.5h | planned |
| 84 | Wire `LegalPages` into `Content` LiveView — replace `PreviewData` calls, add tests | 83 | 45m | planned |
| ~~83~~ | ~~`LegalPages` module — generate accurate privacy, delivery, and terms content from settings + provider + shipping data~~ | — | 2.5h | done |
| ~~84~~ | ~~Wire `LegalPages` into `Content` LiveView — replace `PreviewData` calls, add tests~~ | 83 | 45m | done |
| 85 | Page editor integration — "Regenerate" button, auto-regenerate on settings change, customised vs auto label | 83, 19 | 1.5h | planned |
| | **Platform site** | | | |
| 73 | Platform/marketing site — brochure, pricing, sign-up | — | TBD | planned |

397
lib/berrypod/legal_pages.ex Normal file
View File

@ -0,0 +1,397 @@
defmodule Berrypod.LegalPages do
@moduledoc """
Generates legally accurate content for the shop's policy pages.
Content is derived from actual shop settings connected providers,
shipping destinations, enabled features, and shop country. Each function
returns a list of content blocks in the format used by `<.rich_text>`.
Not legal advice. All generated pages include a disclaimer.
"""
alias Berrypod.{Products, Settings, Shipping}
@eu_countries ~w(AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK)
@north_america ~w(US CA)
# =============================================================================
# Public API
# =============================================================================
@doc """
Generates privacy policy content blocks.
Sections adapt based on shop country (jurisdiction language), connected
providers (third-party sharing), and enabled features (cart recovery,
newsletter).
"""
def privacy_content do
shop_name = Settings.get_setting("shop_name", "this shop")
contact_email = Settings.get_setting("contact_email")
shop_country = Settings.get_setting("shop_country", "GB")
abandoned_cart = Settings.abandoned_cart_recovery_enabled?()
newsletter = Settings.get_setting("newsletter_enabled", false)
providers = connected_provider_names()
intro = [
%{
type: :lead,
text:
"#{shop_name} takes your privacy seriously. This policy explains what personal data we collect and how we use it, #{jurisdiction(shop_country)}."
},
%{type: :heading, text: "What we collect"},
%{
type: :paragraph,
text:
"When you place an order, we collect the information needed to fulfil it: your name, email address, and shipping address. Our lawful basis is performance of a contract (Article 6(1)(b) UK/EU GDPR)."
},
%{type: :heading, text: "Payment"},
%{
type: :paragraph,
text:
"Payment is processed by Stripe. We never see or store your card number or payment details — these go directly to Stripe and are subject to their privacy policy."
},
%{type: :heading, text: "Analytics"},
%{
type: :paragraph,
text:
"We collect privacy-first analytics: page views, device type, browser, operating system, and general country (derived from your IP address at the time of the visit — your IP address itself is not stored). No cookies are used for analytics, and no personal data is retained."
},
%{type: :heading, text: "Cookies"},
%{
type: :list,
items: [
"_berrypod_session — keeps your shopping cart and login session active. Expires after 7 days or when you log out. Strictly necessary; no consent required.",
"country_code — remembers your shipping country preference. Stored for 1 year. Strictly necessary for showing correct shipping rates; no consent required."
]
},
%{
type: :paragraph,
text:
"We do not use advertising cookies, tracking pixels, or third-party analytics cookies."
}
]
cart_recovery_blocks = if abandoned_cart, do: cart_recovery_section(shop_country), else: []
newsletter_blocks = if newsletter, do: newsletter_section(), else: []
retention_items =
[
"Order data is kept for 7 years (required by UK company law and HMRC accounting rules).",
"Analytics data is retained for 2 years.",
if(abandoned_cart,
do:
"Cart recovery data (email address and basket contents) is deleted within 30 days whether or not an email was sent.",
else: nil
)
]
|> Enum.reject(&is_nil/1)
tail = [
%{type: :heading, text: "Third parties"},
%{type: :paragraph, text: provider_sharing_text(providers)},
%{type: :heading, text: "Data retention"},
%{type: :list, items: retention_items},
%{type: :heading, text: "Your rights"},
%{
type: :paragraph,
text:
"You have the right to access, correct, or request deletion of your personal data at any time. Note that certain data may need to be retained for the statutory periods above. You also have the right to object to marketing communications."
},
%{type: :heading, text: "Contact"},
%{type: :paragraph, text: contact_text(contact_email)},
%{type: :closing, text: legal_disclaimer()}
]
intro ++ cart_recovery_blocks ++ newsletter_blocks ++ tail
end
@doc """
Generates delivery & returns content blocks.
Production lead times are derived from connected providers. Shipping
destinations are grouped by region from the shipping_rates table.
Returns policy cites the correct UK legal exemptions for POD.
"""
def delivery_content do
providers = connected_providers()
countries = Shipping.list_available_countries_with_names()
contact_email = Settings.get_setting("contact_email")
[
%{
type: :lead,
text:
"All products are printed on demand and dispatched directly from our print provider. Here's everything you need to know about delivery and returns."
},
%{type: :heading, text: "Production time"},
%{type: :paragraph, text: production_lead_time(providers)},
%{type: :heading, text: "Delivery"}
] ++
shipping_region_blocks(countries) ++
[
%{type: :heading, text: "Returns & exchanges"},
%{
type: :paragraph,
text:
"Because every item is made to order, we cannot accept returns for change of mind. Under the Consumer Contracts (Information, Cancellation and Additional Charges) Regulations 2013, Regulation 28(1)(b), the 14-day cancellation right does not apply to goods made to the consumer's specifications or clearly personalised. Every product in this shop qualifies for this exemption."
},
%{
type: :paragraph,
text:
"Your rights under the Consumer Rights Act 2015 are unaffected. If your order arrives damaged or with a printing defect, you're entitled to a repair, replacement, or refund. Contact us within 14 days of receiving your order with your order number and a photo of the issue."
},
%{type: :heading, text: "Cancellations"},
%{type: :paragraph, text: cancellation_text(contact_email)},
%{type: :closing, text: legal_disclaimer()}
]
end
@doc """
Generates terms of service content blocks.
Governing law is derived from shop country. VAT clause included only
when the vat_enabled setting is true.
"""
def terms_content do
shop_name = Settings.get_setting("shop_name", "this shop")
shop_country = Settings.get_setting("shop_country", "GB")
vat_enabled = Settings.get_setting("vat_enabled", false)
law = governing_law(shop_country)
payment_text =
"Payment is taken at the time of purchase via Stripe. Your order is confirmed only on successful payment." <>
if(vat_enabled, do: " Prices include VAT where applicable.", else: "")
[
%{
type: :lead,
text:
"By placing an order through #{shop_name}, you agree to these terms. These terms are governed by #{law}."
},
%{type: :heading, text: "Products"},
%{
type: :paragraph,
text:
"All products are made to order using print-on-demand services. Colours may vary slightly between screen and the finished print — minor differences are normal. We do our best to represent products accurately in photos and descriptions."
},
%{type: :heading, text: "Payment"},
%{type: :paragraph, text: payment_text},
%{type: :heading, text: "Delivery"},
%{
type: :paragraph,
text:
"Orders include a production period before dispatch. Delivery times are estimates and may vary. We are not liable for delays caused by print providers or postal services. See our delivery page for current timeframes."
},
%{type: :heading, text: "Returns"},
%{
type: :paragraph,
text:
"As all items are made to order, we do not accept returns for change of mind — this falls under the Consumer Contracts (Information, Cancellation and Additional Charges) Regulations 2013, Regulation 28(1)(b). If an item arrives damaged or defective, you are entitled to a remedy under the Consumer Rights Act 2015. See our delivery and returns page for the full process."
},
%{type: :heading, text: "Intellectual property"},
%{
type: :paragraph,
text:
"All designs, images, and content on this site are the property of #{shop_name} and may not be reproduced without permission. Purchasing a product grants a personal-use licence only."
},
%{type: :heading, text: "Liability"},
%{
type: :paragraph,
text:
"Our liability is limited to the value of your order. We are not responsible for delays or errors caused by print providers, postal services, or other third parties beyond our reasonable control."
},
%{type: :heading, text: "Governing law"},
%{
type: :paragraph,
text:
"These terms are governed by #{law}. Any dispute will be subject to the exclusive jurisdiction of the relevant courts."
},
%{type: :heading, text: "Changes"},
%{
type: :paragraph,
text:
"We may update these terms from time to time. Any changes apply to orders placed after the update. The current version is always available at this page."
},
%{type: :closing, text: legal_disclaimer()}
]
end
# =============================================================================
# Private helpers
# =============================================================================
defp connected_providers do
Products.list_provider_connections()
|> Enum.filter(& &1.enabled)
end
defp connected_provider_names do
connected_providers()
|> Enum.map(fn conn ->
case conn.provider_type do
"printify" -> "Printify"
"printful" -> "Printful"
_ -> conn.name || "our print provider"
end
end)
end
defp jurisdiction("GB"),
do: "under UK GDPR and the Privacy and Electronic Communications Regulations (PECR)"
defp jurisdiction(code) when code in @eu_countries,
do: "under the EU General Data Protection Regulation (GDPR)"
defp jurisdiction(_), do: "under applicable data protection laws"
defp governing_law("GB"), do: "English law"
defp governing_law(code) when code in @eu_countries do
name = Shipping.country_name(code)
"the law of #{name} and EU regulations"
end
defp governing_law(code) do
name = Shipping.country_name(code)
if name == code, do: "applicable local law", else: "the law of #{name}"
end
defp production_lead_time([]) do
"Orders are printed on demand and typically take several business days to produce before dispatch. Production time varies by product."
end
defp production_lead_time(connections) do
types = MapSet.new(connections, & &1.provider_type)
cond do
MapSet.member?(types, "printify") and MapSet.member?(types, "printful") ->
"Orders are printed on demand. Production time varies by product: Printify items typically take 27 business days, Printful items typically take 25 business days."
MapSet.member?(types, "printify") ->
"Orders are printed on demand and typically take 27 business days to produce before dispatch."
MapSet.member?(types, "printful") ->
"Orders are printed on demand and typically take 25 business days to produce before dispatch."
true ->
"Orders are printed on demand and typically take several business days to produce before dispatch."
end
end
defp shipping_region_blocks([]) do
[
%{
type: :paragraph,
text:
"We ship worldwide. Contact us for current delivery time estimates for your location."
}
]
end
defp shipping_region_blocks(country_tuples) do
codes = Enum.map(country_tuples, &elem(&1, 0))
known = ["GB"] ++ @eu_countries ++ @north_america
items =
[
"GB" in codes && "United Kingdom: typically 13 business days after dispatch",
Enum.any?(codes, &(&1 in @eu_countries)) &&
"Europe: typically 37 business days after dispatch",
Enum.any?(codes, &(&1 in @north_america)) &&
"United States and Canada: typically 510 business days after dispatch",
Enum.any?(codes, &(&1 not in known)) &&
"Rest of world: typically 721 business days after dispatch"
]
|> Enum.filter(&(&1 != false))
[
%{type: :list, items: items},
%{
type: :paragraph,
text:
"Delivery times are estimates and are in addition to the production time above. You'll receive tracking information once your order is dispatched."
}
]
end
defp cart_recovery_section("GB") do
[
%{type: :heading, text: "Cart recovery"},
%{
type: :paragraph,
text:
"If you enter your email address during checkout but don't complete your order, we may send you one follow-up email about the items in your basket. We do this under the soft opt-in rule in the Privacy and Electronic Communications Regulations (PECR), Regulation 22, which permits a single reminder email when your address was collected during a checkout you started. We will never send more than one email per abandoned basket. You can unsubscribe at any time using the link in that email. We store your email address and basket contents for this purpose only, and delete them within 30 days whether or not an email was sent. No tracking pixels or click-tracking links are used in these emails."
}
]
end
defp cart_recovery_section(code) when code in @eu_countries do
[
%{type: :heading, text: "Cart recovery"},
%{
type: :paragraph,
text:
"If you enter your email address during checkout but don't complete your order, we may send you one follow-up email about the items in your basket. Our lawful basis is legitimate interests (EU GDPR Article 6(1)(f)) — recovering a genuine sale that was already in progress. We only ever send this once. You can unsubscribe at any time using the link in the email. We store your email address and basket contents for this purpose only, and delete them within 30 days. No tracking pixels or click-tracking links are used in these emails."
}
]
end
defp cart_recovery_section(_) do
[
%{type: :heading, text: "Cart recovery"},
%{
type: :paragraph,
text:
"If you start a checkout and don't complete it, we may send you one follow-up email about the items you were buying. We send this once only. You can unsubscribe using the link in the email. We delete this data within 30 days. No tracking pixels or click-tracking links are used in these emails."
}
]
end
defp newsletter_section do
[
%{type: :heading, text: "Newsletter"},
%{
type: :paragraph,
text:
"If you subscribe to our newsletter, we use your email address to send marketing emails about new products and offers. We'll only contact you if you've opted in. You can unsubscribe at any time using the link in any email we send."
}
]
end
defp provider_sharing_text([]) do
"To fulfil your order, we share your name and shipping address with our print-on-demand provider. Payment details are handled by Stripe. Both process data in accordance with their own privacy policies."
end
defp provider_sharing_text([name]) do
"To fulfil your order, we share your name and shipping address with #{name}. Payment details are handled by Stripe. Both process data in accordance with their own privacy policies."
end
defp provider_sharing_text(names) do
joined = Enum.join(names, " and ")
"To fulfil your order, we share your name and shipping address with #{joined}. Payment details are handled by Stripe. All process data in accordance with their own privacy policies."
end
defp contact_text(nil),
do: "For any data-related questions or requests, get in touch via our contact page."
defp contact_text(""), do: contact_text(nil)
defp contact_text(email),
do: "For any data-related questions or requests, contact us at #{email}."
defp cancellation_text(nil),
do:
"Orders can be cancelled within approximately 2 hours of being placed, before production begins. Contact us via our contact page as soon as possible if you need to cancel."
defp cancellation_text(""), do: cancellation_text(nil)
defp cancellation_text(email),
do:
"Orders can be cancelled within approximately 2 hours of being placed, before production begins. Get in touch at #{email} as soon as possible if you need to cancel."
defp legal_disclaimer do
"This page was generated from this shop's configuration. It's a good starting point — review it and take independent legal advice if you're unsure about anything."
end
end

View File

@ -1,6 +1,7 @@
defmodule BerrypodWeb.Shop.Content do
use BerrypodWeb, :live_view
alias Berrypod.LegalPages
alias Berrypod.Theme.PreviewData
@impl true
@ -44,7 +45,7 @@ defmodule BerrypodWeb.Shop.Content do
active_page: "delivery",
hero_title: "Delivery & returns",
hero_description: "Everything you need to know about shipping and returns",
content_blocks: PreviewData.delivery_content()
content_blocks: LegalPages.delivery_content()
}
end
@ -56,7 +57,7 @@ defmodule BerrypodWeb.Shop.Content do
active_page: "privacy",
hero_title: "Privacy policy",
hero_description: "How we handle your personal information",
content_blocks: PreviewData.privacy_content()
content_blocks: LegalPages.privacy_content()
}
end
@ -68,7 +69,7 @@ defmodule BerrypodWeb.Shop.Content do
active_page: "terms",
hero_title: "Terms of service",
hero_description: "The legal bits",
content_blocks: PreviewData.terms_content()
content_blocks: LegalPages.terms_content()
}
end
end

View File

@ -0,0 +1,255 @@
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
end

View File

@ -36,16 +36,15 @@ defmodule BerrypodWeb.Shop.ContentTest do
test "displays delivery content", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/delivery")
assert html =~ "Shipping"
assert html =~ "Production time"
assert html =~ "Returns &amp; exchanges"
assert html =~ "Cancellations"
end
test "displays list items", %{conn: conn} do
test "displays shipping fallback when no rates are configured", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/delivery")
assert html =~ "United Kingdom"
assert html =~ "58 business days"
assert html =~ "worldwide"
end
end
@ -78,7 +77,7 @@ defmodule BerrypodWeb.Shop.ContentTest do
{:ok, _view, html} = live(conn, ~p"/terms")
assert html =~ "Products"
assert html =~ "Orders &amp; payment"
assert html =~ "Payment"
assert html =~ "Intellectual property"
end
end