defmodule AmmopricesWeb.CaliberLive.ShowTest do use AmmopricesWeb.ConnCase import Ammoprices.Fixtures import Phoenix.LiveViewTest setup do caliber = caliber_fixture(%{name: "9mm Luger", slug: "9mm-luger", category: "handgun"}) retailer = retailer_fixture(%{name: "Lucky Gunner", slug: "lucky-gunner", base_url: "https://www.luckygunner.com"}) %{caliber: caliber, retailer: retailer} end describe "CaliberLive.Show" do test "renders caliber page with title", %{conn: conn, caliber: caliber} do {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#caliber-heading") end test "renders product listings with stream", %{conn: conn, caliber: caliber, retailer: retailer} do product = product_fixture(%{caliber: caliber, retailer: retailer, title: "Federal 9mm 115gr"}) now = DateTime.truncate(DateTime.utc_now(), :second) snapshot_fixture(%{product: product, price_per_round_cents: 28, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#products") assert has_element?(view, "#products [id]") end test "shows empty state when no products", %{conn: conn, caliber: caliber} do {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#products-empty") end test "filters by in-stock", %{conn: conn, caliber: caliber, retailer: retailer} do in_stock = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true, title: "In Stock Product"}) oos = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: false, title: "Out of Stock Product"}) now = DateTime.truncate(DateTime.utc_now(), :second) snapshot_fixture(%{product: in_stock, price_per_round_cents: 28, in_stock: true, recorded_at: now}) snapshot_fixture(%{product: oos, price_per_round_cents: 22, in_stock: false, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") # Default is in_stock: true assert has_element?(view, "#filter-in-stock") end test "renders price stats banner", %{conn: conn, caliber: caliber, retailer: retailer} do product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) now = DateTime.truncate(DateTime.utc_now(), :second) snapshot_fixture(%{product: product, price_per_round_cents: 25, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#price-stats") end test "renders chart container with data attribute", %{conn: conn, caliber: caliber, retailer: retailer} do product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) now = DateTime.truncate(DateTime.utc_now(), :second) snapshot_fixture(%{product: product, price_per_round_cents: 30, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#price-chart-container") assert has_element?(view, "[data-chart-data]") end test "renders time range buttons", %{conn: conn, caliber: caliber} do {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#range-7d") assert has_element?(view, "#range-30d") assert has_element?(view, "#range-90d") assert has_element?(view, "#range-1y") assert has_element?(view, "#range-all") end test "changes chart range when range button clicked", %{conn: conn, caliber: caliber, retailer: retailer} do product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) now = DateTime.truncate(DateTime.utc_now(), :second) snapshot_fixture(%{product: product, price_per_round_cents: 30, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert view |> element("#range-7d") |> render_click() assert has_element?(view, "#price-chart-container") end test "renders casing filter buttons", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) brass = product_fixture(%{caliber: caliber, retailer: retailer, casing: "brass", in_stock: true}) steel = product_fixture(%{caliber: caliber, retailer: retailer, casing: "steel", in_stock: true}) snapshot_fixture(%{product: brass, price_per_round_cents: 28, in_stock: true, recorded_at: now}) snapshot_fixture(%{product: steel, price_per_round_cents: 20, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#filter-casing-brass") assert has_element?(view, "#filter-casing-steel") end test "renders grain weight filter buttons", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) p115 = product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 115, in_stock: true}) p147 = product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 147, in_stock: true}) snapshot_fixture(%{product: p115, price_per_round_cents: 25, in_stock: true, recorded_at: now}) snapshot_fixture(%{product: p147, price_per_round_cents: 40, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#filter-grain-115") assert has_element?(view, "#filter-grain-147") end test "renders subsonic filter toggle", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) snapshot_fixture(%{product: product, price_per_round_cents: 25, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#filter-subsonic") end test "displays grain weight and brand in product table", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) product = product_fixture(%{ caliber: caliber, retailer: retailer, grain_weight: 115, brand: "Federal", in_stock: true }) snapshot_fixture(%{product: product, price_per_round_cents: 28, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") assert has_element?(view, "#products [id]") end test "filters by casing when clicked", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) brass = product_fixture(%{caliber: caliber, retailer: retailer, casing: "brass", in_stock: true, title: "Brass Ammo"}) steel = product_fixture(%{caliber: caliber, retailer: retailer, casing: "steel", in_stock: true, title: "Steel Ammo"}) snapshot_fixture(%{product: brass, price_per_round_cents: 28, in_stock: true, recorded_at: now}) snapshot_fixture(%{product: steel, price_per_round_cents: 20, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") # Click steel filter view |> element("#filter-casing-steel") |> render_click() # Should still have the filter button active assert has_element?(view, "#filter-casing-steel") end test "receives real-time price updates via PubSub", %{conn: conn, caliber: caliber, retailer: retailer} do now = DateTime.truncate(DateTime.utc_now(), :second) product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) snapshot_fixture(%{product: product, price_per_round_cents: 30, in_stock: true, recorded_at: now}) {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") # Add a new product + snapshot new_product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true, title: "New Product", casing: "brass"}) snapshot_fixture(%{product: new_product, price_per_round_cents: 18, in_stock: true, recorded_at: now}) # Broadcast price update Phoenix.PubSub.broadcast(Ammoprices.PubSub, "prices:updated", {:prices_updated, %{}}) # The view should re-render with updated data html = render(view) assert html =~ "New Product" end end end