rename project from SimpleshopTheme to Berrypod

All modules, configs, paths, and references updated.
836 tests pass, zero warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-18 21:23:15 +00:00
parent c65e777832
commit 9528700862
300 changed files with 23932 additions and 1349 deletions

View File

@@ -0,0 +1,118 @@
defmodule BerrypodWeb.Shop.CartTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
alias Berrypod.ProductsFixtures
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
:ok
end
defp create_cart_with_product(_context) do
product = ProductsFixtures.complete_product_fixture(%{title: "Test Art Print"})
variant = List.first(product.variants)
%{product: product, variant: variant}
end
defp conn_with_cart(conn, variant_id, qty \\ 1) do
conn
|> Phoenix.ConnTest.init_test_session(%{"cart" => [{variant_id, qty}]})
end
describe "Empty cart" do
test "renders empty cart state", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/cart")
assert html =~ "Your basket"
assert html =~ "Your basket is empty"
end
test "shows continue shopping link when empty", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/cart")
assert html =~ "Continue shopping"
end
end
describe "Cart with items" do
setup [:create_cart_with_product]
test "displays cart item name", %{conn: conn, product: product, variant: variant} do
{:ok, _view, html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
assert html =~ product.title
end
test "displays order summary", %{conn: conn, variant: variant} do
{:ok, _view, html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
assert html =~ "Order summary"
assert html =~ "Subtotal"
end
test "displays formatted subtotal", %{conn: conn, variant: variant} do
{:ok, _view, html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
assert html =~ Berrypod.Cart.format_price(variant.price)
end
test "displays checkout button", %{conn: conn, variant: variant} do
{:ok, _view, html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
assert html =~ "Checkout"
end
test "incrementing quantity updates the display", %{
conn: conn,
product: product,
variant: variant
} do
{:ok, view, _html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
html =
view
|> element("#main-content button[aria-label='Increase quantity of #{product.title}']")
|> render_click()
assert html =~ "Quantity updated to 2"
end
test "decrementing to zero removes the item", %{
conn: conn,
product: product,
variant: variant
} do
{:ok, view, _html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
html =
view
|> element("#main-content button[aria-label='Decrease quantity of #{product.title}']")
|> render_click()
assert html =~ "Your basket is empty"
end
test "remove button removes the item", %{conn: conn, product: product, variant: variant} do
{:ok, view, _html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
html =
view
|> element("#main-content button[aria-label='Remove #{product.title} from cart']")
|> render_click()
assert html =~ "Your basket is empty"
end
end
describe "Cart page title" do
test "page title is Cart", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/cart")
assert html =~ "<title>Cart</title>"
end
end
end

View File

@@ -0,0 +1,179 @@
defmodule BerrypodWeb.Shop.CollectionTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
import Berrypod.ProductsFixtures
alias Berrypod.Products
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
pc = provider_connection_fixture()
print =
product_fixture(%{
provider_connection: pc,
title: "Mountain Sunrise Print",
category: "Art Prints"
})
product_variant_fixture(%{product: print, title: "8x10", price: 1999})
Products.recompute_cached_fields(print)
shirt =
product_fixture(%{
provider_connection: pc,
title: "Forest T-Shirt",
category: "Apparel",
on_sale: true
})
product_variant_fixture(%{
product: shirt,
title: "Large",
price: 2999,
compare_at_price: 3999
})
Products.recompute_cached_fields(shirt)
%{print: print, shirt: shirt}
end
describe "Collection page" do
test "renders collection page for /collections/all", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ "All Products"
end
test "renders collection page for specific category", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/art-prints")
assert html =~ "Art Prints"
end
test "displays products", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ print.title
end
test "displays category filter buttons", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ "Art Prints"
assert html =~ "Apparel"
end
test "displays sort dropdown", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ "Featured"
assert html =~ "Newest"
assert html =~ "Price: Low to High"
assert html =~ "Price: High to Low"
assert html =~ "Name: A-Z"
assert html =~ "Name: Z-A"
end
test "redirects to /collections/all for unknown category", %{conn: conn} do
{:error, {:live_redirect, %{to: to, flash: flash}}} =
live(conn, ~p"/collections/nonexistent")
assert to == "/collections/all"
assert flash["error"] == "Collection not found"
end
test "filters products by category", %{conn: conn, print: print, shirt: shirt} do
{:ok, _view, html} = live(conn, ~p"/collections/art-prints")
assert html =~ print.title
refute html =~ shirt.title
end
end
describe "Sorting" do
test "sorts by price ascending", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/collections/all")
html =
view
|> element("form[phx-change='sort_changed']")
|> render_change(%{sort: "price_asc"})
assert html =~ "Price: Low to High"
end
test "sorts by price descending", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/collections/all")
html =
view
|> element("form[phx-change='sort_changed']")
|> render_change(%{sort: "price_desc"})
assert html =~ "Price: High to Low"
end
test "sorts by name ascending", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/collections/all")
html =
view
|> element("form[phx-change='sort_changed']")
|> render_change(%{sort: "name_asc"})
assert html =~ "Name: A-Z"
end
test "sorts by name descending", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/collections/all")
html =
view
|> element("form[phx-change='sort_changed']")
|> render_change(%{sort: "name_desc"})
assert html =~ "Name: Z-A"
end
test "sort parameter is preserved in URL", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/collections/all?sort=price_asc")
html = render(view)
assert html =~ "selected"
end
test "default sort is featured", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ ~r/<option[^>]*value="featured"[^>]*selected/
end
end
describe "Navigation" do
test "category links preserve sort order", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all?sort=price_desc")
assert html =~ "/collections/art-prints?sort=price_desc"
end
test "All button link preserves sort order", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/art-prints?sort=name_asc")
assert html =~ "/collections/all?sort=name_asc"
end
test "featured sort does not include query param in links", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/collections/all")
assert html =~ ~s(href="/collections/art-prints")
refute html =~ "/collections/art-prints?sort=featured"
end
end
end

View File

@@ -0,0 +1,77 @@
defmodule BerrypodWeb.Shop.ComingSoonTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
alias Berrypod.Settings
describe "coming soon page" do
test "renders when site is not live and admin exists", %{conn: conn} do
user_fixture()
{:ok, _view, html} = live(conn, ~p"/coming-soon")
assert html =~ "Coming soon"
assert html =~ "getting things ready"
end
test "displays the shop name", %{conn: conn} do
{:ok, _} = Settings.update_theme_settings(%{site_name: "My Test Shop"})
{:ok, _view, html} = live(conn, ~p"/coming-soon")
assert html =~ "My Test Shop"
end
end
describe "site live gate" do
test "redirects unauthenticated visitors to coming soon when not live", %{conn: conn} do
# Create admin so the gate activates (fresh installs bypass)
user_fixture()
assert {:error, {:redirect, %{to: "/coming-soon"}}} = live(conn, ~p"/")
end
test "allows authenticated admin through when not live", %{conn: conn} do
user = user_fixture()
conn = log_in_user(conn, user)
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Shop the collection"
end
test "allows everyone through when site is live", %{conn: conn} do
user_fixture()
{:ok, _} = Settings.set_site_live(true)
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Shop the collection"
end
test "redirects to registration on fresh install (no admin)", %{conn: conn} do
# No admin created — redirect to registration
assert {:error, {:redirect, %{to: "/users/register"}}} = live(conn, ~p"/")
end
test "redirects when session token is stale (user deleted)", %{conn: conn} do
user = user_fixture()
conn = log_in_user(conn, user)
# Delete the user — session cookie is now stale
Berrypod.Repo.delete!(user)
assert {:error, {:redirect, %{to: "/users/register"}}} = live(conn, ~p"/")
end
test "gates all public shop routes", %{conn: conn} do
user_fixture()
assert {:error, {:redirect, %{to: "/coming-soon"}}} = live(conn, ~p"/about")
assert {:error, {:redirect, %{to: "/coming-soon"}}} = live(conn, ~p"/collections/all")
assert {:error, {:redirect, %{to: "/coming-soon"}}} = live(conn, ~p"/cart")
end
end
end

View File

@@ -0,0 +1,96 @@
defmodule BerrypodWeb.Shop.ContentTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
:ok
end
describe "About page" do
test "renders about page", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/about")
assert html =~ "About the studio"
assert html =~ "sample about page"
end
test "displays about image", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/about")
assert html =~ "night-sky-blanket"
end
end
describe "Delivery page" do
test "renders delivery page", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/delivery")
assert html =~ "Delivery &amp; returns"
assert html =~ "shipping and returns"
end
test "displays delivery content", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/delivery")
assert html =~ "Shipping"
assert html =~ "Returns &amp; exchanges"
assert html =~ "Cancellations"
end
test "displays list items", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/delivery")
assert html =~ "United Kingdom"
assert html =~ "58 business days"
end
end
describe "Privacy page" do
test "renders privacy page", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/privacy")
assert html =~ "Privacy policy"
assert html =~ "personal information"
end
test "displays privacy content", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/privacy")
assert html =~ "What we collect"
assert html =~ "Cookies"
assert html =~ "Your rights"
end
end
describe "Terms page" do
test "renders terms page", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/terms")
assert html =~ "Terms of service"
assert html =~ "The legal bits"
end
test "displays terms content", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/terms")
assert html =~ "Products"
assert html =~ "Orders &amp; payment"
assert html =~ "Intellectual property"
end
end
describe "Footer links" do
test "footer contains policy page links", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/about")
assert html =~ ~s(href="/delivery")
assert html =~ ~s(href="/privacy")
assert html =~ ~s(href="/terms")
assert html =~ ~s(href="/contact")
end
end
end

View File

@@ -0,0 +1,101 @@
defmodule BerrypodWeb.Shop.HomeTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
import Berrypod.ProductsFixtures
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
conn = provider_connection_fixture()
product =
product_fixture(%{
provider_connection: conn,
title: "Mountain Sunrise Print",
category: "Art Prints"
})
product_variant_fixture(%{product: product, title: "8x10", price: 1999})
# Recompute so cheapest_price is set
Berrypod.Products.recompute_cached_fields(product)
%{product: product}
end
describe "Home page" do
test "renders the home page", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Original designs, printed on demand"
end
test "renders hero section with CTA", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Shop the collection"
end
test "renders category navigation with real categories", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Art Prints"
end
test "renders featured products section", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Featured products"
assert html =~ product.title
end
test "renders image and text section", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Made with passion, printed with care"
assert html =~ "Learn more about the studio"
end
test "renders header with shop name", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ "Store Name"
end
test "renders footer with links", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(href="/about")
assert html =~ ~s(href="/contact")
end
end
describe "Navigation links" do
test "category links point to collections", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(href="/collections/)
end
test "product links point to product pages", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(href="/products/)
end
test "hero CTA links to collections", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(href="/collections/all")
end
test "about link in image section", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(href="/about")
end
end
end

View File

@@ -0,0 +1,412 @@
defmodule BerrypodWeb.Shop.ProductShowTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
import Berrypod.ProductsFixtures
alias Berrypod.Products
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
pc = provider_connection_fixture()
# Art print with size options and 2 images (for gallery tests)
print =
product_fixture(%{
provider_connection: pc,
title: "Mountain Sunrise Print",
category: "Art Prints",
description: "A beautiful mountain sunrise art print",
provider_data: %{
"options" => [
%{
"name" => "Size",
"type" => "size",
"values" => [
%{"title" => "8x10"},
%{"title" => "12x18"},
%{"title" => "18x24"}
]
}
]
}
})
product_variant_fixture(%{
product: print,
title: "8x10",
price: 1999,
options: %{"Size" => "8x10"}
})
product_variant_fixture(%{
product: print,
title: "12x18",
price: 2400,
options: %{"Size" => "12x18"}
})
product_variant_fixture(%{
product: print,
title: "18x24",
price: 3200,
options: %{"Size" => "18x24"}
})
product_image_fixture(%{
product: print,
position: 0,
src: "https://example.com/print-1.jpg"
})
product_image_fixture(%{
product: print,
position: 1,
src: "https://example.com/print-2.jpg"
})
Products.recompute_cached_fields(print)
# T-shirt with colour + size options (some sizes unavailable in white)
shirt =
product_fixture(%{
provider_connection: pc,
title: "Forest T-Shirt",
category: "Apparel",
description: "A forest themed t-shirt",
provider_data: %{
"options" => [
%{
"name" => "Color",
"type" => "color",
"values" => [
%{"title" => "Black", "colors" => ["#000000"]},
%{"title" => "White", "colors" => ["#FFFFFF"]}
]
},
%{
"name" => "Size",
"type" => "size",
"values" => [
%{"title" => "M"},
%{"title" => "L"},
%{"title" => "XL"}
]
}
]
}
})
for color <- ["Black", "White"], size <- ["M", "L"] do
product_variant_fixture(%{
product: shirt,
title: "#{color} / #{size}",
price: 2999,
options: %{"Color" => color, "Size" => size}
})
end
# Black / XL available
product_variant_fixture(%{
product: shirt,
title: "Black / XL",
price: 2999,
options: %{"Color" => "Black", "Size" => "XL"}
})
# White / XL unavailable
product_variant_fixture(%{
product: shirt,
title: "White / XL",
price: 2999,
options: %{"Color" => "White", "Size" => "XL"},
is_available: false
})
product_image_fixture(%{
product: shirt,
position: 0,
src: "https://example.com/shirt-1.jpg"
})
Products.recompute_cached_fields(shirt)
# Another art print for related products
related =
product_fixture(%{
provider_connection: pc,
title: "Ocean Waves Print",
category: "Art Prints"
})
product_variant_fixture(%{
product: related,
title: "12x18",
price: 2400,
options: %{"Size" => "12x18"}
})
Products.recompute_cached_fields(related)
%{print: print, shirt: shirt, related: related}
end
describe "Product detail page" do
test "renders product page with product name", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ print.title
end
test "renders product description", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ print.description
end
test "renders product price", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
# Cheapest variant is 8x10 at £19.99
assert html =~ "£19.99"
end
test "renders breadcrumb with category link", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ "Art Prints"
assert html =~ "/collections/"
end
test "renders add to cart button", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ "Add to basket"
end
test "renders related products section", %{conn: conn, print: print, related: related} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ related.title
end
end
describe "Variant selection" do
test "renders variant selectors for product with size options", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ "Size"
assert html =~ "8x10"
assert html =~ "12x18"
assert html =~ "18x24"
end
test "renders colour and size selectors for apparel", %{conn: conn, shirt: shirt} do
{:ok, _view, html} = live(conn, ~p"/products/#{shirt.slug}")
assert html =~ "Color"
assert html =~ "Size"
end
test "selecting a size updates the price", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
html =
view
|> element("button[phx-value-selected='18x24']")
|> render_click()
assert html =~ "£32.00"
end
test "selecting a colour auto-adjusts size if needed", %{conn: conn, shirt: shirt} do
{:ok, view, _html} = live(conn, ~p"/products/#{shirt.slug}")
# Select White — M and L are available, XL is not
html =
view
|> element("button[aria-label='Select White']")
|> render_click()
# White is selected, size M should still be selected (valid combo)
assert html =~ ~s(aria-pressed="true")
end
test "shows variant for single-variant product", %{conn: conn, related: related} do
{:ok, _view, html} = live(conn, ~p"/products/#{related.slug}")
# Ocean Waves Print has no provider_data options, so shows "One size"
assert html =~ "One size"
end
end
describe "Quantity selector" do
test "renders quantity selector with initial value of 1", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, "button[phx-click='decrement_quantity']")
assert has_element?(view, "button[phx-click='increment_quantity']")
end
test "decrement button is disabled at quantity 1", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, "button[phx-click='decrement_quantity'][disabled]")
end
test "increment increases quantity", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
html =
view
|> element("button[phx-click='increment_quantity']")
|> render_click()
# Quantity now 2, decrement no longer disabled
refute html =~ ~s(phx-click="decrement_quantity" disabled)
end
test "decrement decreases quantity", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
# Increment twice to get to 3
view |> element("button[phx-click='increment_quantity']") |> render_click()
view |> element("button[phx-click='increment_quantity']") |> render_click()
html =
view
|> element("button[phx-click='decrement_quantity']")
|> render_click()
# Should be back to 2 — decrement still enabled
refute html =~ ~s(phx-click="decrement_quantity" disabled)
end
test "quantity resets to 1 after adding to cart", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
# Increment to 3
view |> element("button[phx-click='increment_quantity']") |> render_click()
view |> element("button[phx-click='increment_quantity']") |> render_click()
# Add to cart
html =
view
|> element("button", "Add to basket")
|> render_click()
# Decrement should be disabled again (quantity reset to 1)
assert html =~ ~s(phx-click="decrement_quantity")
assert html =~ "disabled"
end
end
describe "Add to cart" do
test "add to cart opens the cart drawer", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
html =
view
|> element("button", "Add to basket")
|> render_click()
assert html =~ "added to cart"
end
test "add to cart shows product in cart drawer", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
html =
view
|> element("button", "Add to basket")
|> render_click()
assert html =~ "Mountain Sunrise Print"
end
end
describe "Product gallery" do
test "renders carousel with hook and accessibility attrs", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ ~s(phx-hook="ProductImageScroll")
assert html =~ ~s(role="region")
assert html =~ ~s(aria-label="Product images")
end
test "renders all gallery images with alt text", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ "#{print.title} — image 1 of"
assert html =~ "#{print.title} — image 2 of"
end
test "renders dot indicators for multi-image gallery", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, ".product-image-dots")
assert has_element?(view, ".product-image-dot")
end
test "renders thumbnail grid for multi-image gallery", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, ".pdp-gallery-thumbs")
assert has_element?(view, ".pdp-thumbnail")
end
test "renders prev/next navigation arrows", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, "button.pdp-nav-prev")
assert has_element?(view, "button.pdp-nav-next")
assert has_element?(view, ~s(button[aria-label="Previous image"]))
assert has_element?(view, ~s(button[aria-label="Next image"]))
end
test "renders lightbox dialog", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, "dialog#pdp-lightbox")
end
test "renders lightbox click target for desktop", %{conn: conn, print: print} do
{:ok, view, _html} = live(conn, ~p"/products/#{print.slug}")
assert has_element?(view, ".pdp-lightbox-click")
end
test "thumbnails have correct aria-labels", %{conn: conn, print: print} do
{:ok, _view, html} = live(conn, ~p"/products/#{print.slug}")
assert html =~ "View image 1 of"
assert html =~ "View image 2 of"
end
end
describe "Product gallery edge cases" do
test "single image renders without carousel or dots", %{conn: conn, shirt: shirt} do
{:ok, view, _html} = live(conn, ~p"/products/#{shirt.slug}")
# Shirt has only 1 image — should render single view, not carousel
assert has_element?(view, ".pdp-gallery-single")
refute has_element?(view, ".product-image-dots")
end
end
describe "Navigation" do
test "product links navigate to correct product page", %{conn: conn, shirt: shirt} do
{:ok, _view, html} = live(conn, ~p"/products/#{shirt.slug}")
assert html =~ shirt.title
end
test "unknown slug redirects to collections", %{conn: conn} do
assert {:error, {:live_redirect, %{to: "/collections/all"}}} =
live(conn, ~p"/products/nonexistent")
end
end
end

View File

@@ -0,0 +1,131 @@
defmodule BerrypodWeb.Shop.SearchIntegrationTest do
use BerrypodWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import Berrypod.AccountsFixtures
import Berrypod.ProductsFixtures
alias Berrypod.Search
setup do
user_fixture()
{:ok, _} = Berrypod.Settings.set_site_live(true)
pc = provider_connection_fixture()
mountain =
product_fixture(%{
provider_connection: pc,
title: "Mountain Sunrise Print",
description: "A landscape scene at dawn",
category: "Art Prints"
})
product_variant_fixture(%{product: mountain, title: "8x10", price: 1999})
Berrypod.Products.recompute_cached_fields(mountain)
ocean =
product_fixture(%{
provider_connection: pc,
title: "Ocean Waves Notebook",
description: "Spiral-bound notebook with ocean art",
category: "Stationery"
})
product_variant_fixture(%{product: ocean, title: "A5", price: 1299})
Berrypod.Products.recompute_cached_fields(ocean)
Search.rebuild_index()
%{mountain: mountain, ocean: ocean}
end
describe "search event" do
test "returns matching products", %{conn: conn, mountain: mountain} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "mountain"})
assert html =~ mountain.title
assert html =~ ~p"/products/#{mountain.slug}"
end
test "shows no results message for unmatched query", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "xyznonexistent"})
assert html =~ "No products found"
end
test "ignores short queries", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "a"})
refute html =~ "No products found"
refute html =~ ~s(role="option")
end
end
describe "clear_search event" do
test "resets search state", %{conn: conn, mountain: mountain} do
{:ok, view, _html} = live(conn, ~p"/")
# First search to get results
html = render_hook(view, "search", %{"value" => "mountain"})
assert html =~ mountain.title
# Clear search
html = render_hook(view, "clear_search", %{})
refute html =~ ~s(No products found)
# Results list should be gone
refute html =~ ~s(role="option")
end
end
describe "search results rendering" do
test "result links use navigate for LiveView navigation", %{conn: conn, mountain: mountain} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "mountain"})
assert html =~ ~s(href="/products/#{mountain.slug}")
assert html =~ ~s(data-phx-link="redirect")
end
test "results have ARIA listbox and option roles", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "mountain"})
assert html =~ ~s(role="listbox")
assert html =~ ~s(role="option")
end
test "search input has combobox ARIA attributes", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
assert html =~ ~s(role="combobox")
assert html =~ ~s(aria-autocomplete="list")
assert html =~ ~s(aria-controls="search-results-list")
end
test "multiple results render in order", %{conn: conn, mountain: mountain, ocean: ocean} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "print notebook"})
assert html =~ mountain.title || html =~ ocean.title
end
test "shows category and price in results", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
html = render_hook(view, "search", %{"value" => "mountain"})
assert html =~ "Art Prints"
end
end
end