add integration tests for product, cart and home pages
37 new LiveView integration tests covering the three previously untested shop pages. Product detail tests cover variant selection, price updates and add-to-cart. Cart tests use DB fixtures with session seeding for hydration. Home tests cover hero, categories, featured products and navigation links. 612 total tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3b8d5faf3b
commit
518da36c8f
12
PROGRESS.md
12
PROGRESS.md
@ -209,13 +209,15 @@ All 8 items from the plan done. Key wins: ThemeHook eliminated mount duplication
|
|||||||
See: [docs/plans/dry-refactor.md](docs/plans/dry-refactor.md) for full analysis and plan
|
See: [docs/plans/dry-refactor.md](docs/plans/dry-refactor.md) for full analysis and plan
|
||||||
|
|
||||||
### Shop Page Integration Tests
|
### Shop Page Integration Tests
|
||||||
**Status:** Follow-up
|
**Status:** Complete
|
||||||
|
|
||||||
Home, product detail, and cart pages have no LiveView integration tests. Collection and content pages are well-covered (16 and 10 tests respectively). Priority order by logic complexity:
|
All shop pages now have LiveView integration tests (612 total):
|
||||||
|
|
||||||
1. **Product detail page** — variant selection, add-to-cart, gallery, breadcrumb
|
- **Product detail page** (15 tests) — rendering, breadcrumbs, variant selection, price updates, add-to-cart, related products, fallback for unknown IDs
|
||||||
2. **Cart page** — cart items, quantity changes, order summary, checkout link
|
- **Cart page** (10 tests) — empty state, item display with DB fixtures, order summary, increment/decrement, remove, checkout button
|
||||||
3. **Home page** — hero section, featured products, category nav (mostly presentational)
|
- **Home page** (12 tests) — hero section, category nav, featured products, image+text section, navigation links
|
||||||
|
- **Collection page** (16 tests, pre-existing) — category filtering, sorting, URL params
|
||||||
|
- **Content pages** (10 tests, pre-existing) — about, delivery, privacy, terms
|
||||||
|
|
||||||
### Page Editor
|
### Page Editor
|
||||||
**Status:** Future (Tier 4)
|
**Status:** Future (Tier 4)
|
||||||
|
|||||||
111
test/simpleshop_theme_web/live/shop_live/cart_test.exs
Normal file
111
test/simpleshop_theme_web/live/shop_live/cart_test.exs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
defmodule SimpleshopThemeWeb.ShopLive.CartTest do
|
||||||
|
use SimpleshopThemeWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias SimpleshopTheme.ProductsFixtures
|
||||||
|
|
||||||
|
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 =~ SimpleshopTheme.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("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("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
|
||||||
89
test/simpleshop_theme_web/live/shop_live/home_test.exs
Normal file
89
test/simpleshop_theme_web/live/shop_live/home_test.exs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
defmodule SimpleshopThemeWeb.ShopLive.HomeTest do
|
||||||
|
use SimpleshopThemeWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Theme.PreviewData
|
||||||
|
|
||||||
|
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", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/")
|
||||||
|
|
||||||
|
categories = PreviewData.categories()
|
||||||
|
|
||||||
|
for category <- Enum.take(categories, 3) do
|
||||||
|
assert html =~ category.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders featured products section", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/")
|
||||||
|
|
||||||
|
assert html =~ "Featured products"
|
||||||
|
|
||||||
|
products = PreviewData.products()
|
||||||
|
first_product = List.first(products)
|
||||||
|
|
||||||
|
assert html =~ first_product.name
|
||||||
|
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"/")
|
||||||
|
|
||||||
|
# Header should be present (part of shop_layout)
|
||||||
|
assert html =~ "SimpleShop"
|
||||||
|
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
|
||||||
155
test/simpleshop_theme_web/live/shop_live/product_show_test.exs
Normal file
155
test/simpleshop_theme_web/live/shop_live/product_show_test.exs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
defmodule SimpleshopThemeWeb.ShopLive.ProductShowTest do
|
||||||
|
use SimpleshopThemeWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Theme.PreviewData
|
||||||
|
|
||||||
|
describe "Product detail page" do
|
||||||
|
test "renders product page with product name", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ product.name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders product description", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ product.description
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders product price", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ SimpleshopTheme.Cart.format_price(product.price)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders breadcrumb with Home link", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ "Home"
|
||||||
|
assert html =~ ~s(href="/")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders breadcrumb with category link", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ product.category
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders add to cart button", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ "Add to basket"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders related products section", %{conn: conn} do
|
||||||
|
product = List.first(PreviewData.products())
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
# Should show other products, not the current one
|
||||||
|
other_product = Enum.at(PreviewData.products(), 1)
|
||||||
|
assert html =~ other_product.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Variant selection" do
|
||||||
|
test "renders variant selectors for product with options", %{conn: conn} do
|
||||||
|
# Product "1" (Mountain Sunrise Art Print) has Size options
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/1")
|
||||||
|
|
||||||
|
assert html =~ "Size"
|
||||||
|
assert html =~ "8×10"
|
||||||
|
assert html =~ "12×18"
|
||||||
|
assert html =~ "18×24"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders color and size selectors for apparel", %{conn: conn} do
|
||||||
|
# Product "6" (Forest Silhouette T-Shirt) has Color and Size options
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/6")
|
||||||
|
|
||||||
|
assert html =~ "Color"
|
||||||
|
assert html =~ "Size"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "selecting a size updates the price", %{conn: conn} do
|
||||||
|
# Product "1" has variants: 8×10 = £19.99, 12×18 = £24.00, 18×24 = £32.00
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/products/1")
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("button[phx-value-value='18×24']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert html =~ "£32.00"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "selecting a colour updates available sizes", %{conn: conn} do
|
||||||
|
# Product "6": White / XL and White / 2XL are unavailable
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/products/6")
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("button[aria-label='Select White']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
# XL should be disabled (unavailable in White)
|
||||||
|
assert html =~ "disabled"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows 'One size' for products without options", %{conn: conn} do
|
||||||
|
# Product "2" (Ocean Waves Art Print) has no option_types
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/2")
|
||||||
|
|
||||||
|
assert html =~ "One size"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Add to cart" do
|
||||||
|
test "add to cart opens the cart drawer", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/products/1")
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("button", "Add to basket")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
# Cart drawer should now be open (the aria live region gets updated)
|
||||||
|
assert html =~ "added to cart"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "add to cart updates cart count", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/products/1")
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("button", "Add to basket")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
# The cart drawer should show the item
|
||||||
|
assert html =~ "Mountain Sunrise Art Print"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Navigation" do
|
||||||
|
test "product links navigate to correct product page", %{conn: conn} do
|
||||||
|
product = Enum.at(PreviewData.products(), 1)
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
|
||||||
|
|
||||||
|
assert html =~ product.name
|
||||||
|
end
|
||||||
|
|
||||||
|
test "falls back to first product for unknown ID", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/products/nonexistent")
|
||||||
|
|
||||||
|
first_product = List.first(PreviewData.products())
|
||||||
|
assert html =~ first_product.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user