diff --git a/PROGRESS.md b/PROGRESS.md index f726af3..a050130 100644 --- a/PROGRESS.md +++ b/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 ### 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 -2. **Cart page** — cart items, quantity changes, order summary, checkout link -3. **Home page** — hero section, featured products, category nav (mostly presentational) +- **Product detail page** (15 tests) — rendering, breadcrumbs, variant selection, price updates, add-to-cart, related products, fallback for unknown IDs +- **Cart page** (10 tests) — empty state, item display with DB fixtures, order summary, increment/decrement, remove, checkout button +- **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 **Status:** Future (Tier 4) diff --git a/test/simpleshop_theme_web/live/shop_live/cart_test.exs b/test/simpleshop_theme_web/live/shop_live/cart_test.exs new file mode 100644 index 0000000..d432af2 --- /dev/null +++ b/test/simpleshop_theme_web/live/shop_live/cart_test.exs @@ -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 =~ "Cart" + end + end +end diff --git a/test/simpleshop_theme_web/live/shop_live/home_test.exs b/test/simpleshop_theme_web/live/shop_live/home_test.exs new file mode 100644 index 0000000..7ae6a2e --- /dev/null +++ b/test/simpleshop_theme_web/live/shop_live/home_test.exs @@ -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 diff --git a/test/simpleshop_theme_web/live/shop_live/product_show_test.exs b/test/simpleshop_theme_web/live/shop_live/product_show_test.exs new file mode 100644 index 0000000..dd2d4a2 --- /dev/null +++ b/test/simpleshop_theme_web/live/shop_live/product_show_test.exs @@ -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