# LiveView Test Template Copy-pasteable template for testing a LiveView page following TowerOps conventions. Replace `__RESOURCE__` with PascalCase resource (e.g., `Schedule`). Replace `__resource__` with snake_case (e.g., `schedule`). Replace `__resources__` with plural snake_case (e.g., `schedules`). Replace `__CONTEXT__` with context module name (e.g., `Schedules`). --- ## `test/towerops_web/live/__resource___live_test.exs` ```elixir defmodule ToweropsWeb.__RESOURCE__LiveTest do use ToweropsWeb.ConnCase import Phoenix.LiveViewTest alias Towerops.__CONTEXT__ setup :register_and_log_in_user setup %{conn: conn, user: user} do {:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org", use_sites: true}, user.id) conn = Plug.Conn.put_session(conn, :current_organization_id, organization.id) %{conn: conn, organization: organization} end describe "Index" do test "requires authentication" do conn = build_conn() {:error, redirect} = live(conn, ~p"/__resources__") assert {:redirect, %{to: path}} = redirect assert path == ~p"/users/log-in" end test "renders page title and header", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/__resources__") assert html =~ "__RESOURCES_TITLE__" end test "shows empty state when no data exists", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/__resources__") assert html =~ "No __resources__ found" end test "lists existing records", %{conn: conn, organization: org} do {:ok, _} = __CONTEXT__.create___resource__(%{ name: "Test Item", organization_id: org.id }) {:ok, _view, html} = live(conn, ~p"/__resources__") assert html =~ "Test Item" end test "filters by status via URL params", %{conn: conn, organization: org} do {:ok, _} = __CONTEXT__.create___resource__(%{ name: "Active Item", organization_id: org.id, enabled: true }) {:ok, _} = __CONTEXT__.create___resource__(%{ name: "Archived Item", organization_id: org.id, enabled: false }) {:ok, view, _html} = live(conn, ~p"/__resources__?filter=active") html = render(view) assert html =~ "Active Item" refute html =~ "Archived Item" end test "navigates to new form", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/__resources__") assert view |> element("a", "New") |> render_click() |> follow_redirect(conn, ~p"/__resources__/new") end test "deletes a record", %{conn: conn, organization: org} do {:ok, item} = __CONTEXT__.create___resource__(%{ name: "To Delete", organization_id: org.id }) {:ok, view, html} = live(conn, ~p"/__resources__") assert html =~ "To Delete" view |> element("button[phx-click='delete'][phx-value-id='#{item.id}']") |> render_click() html = render(view) refute html =~ "To Delete" end end describe "Form - New" do test "renders new form", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/__resources__/new") assert html =~ "New __RESOURCE_TITLE__" assert html =~ "Save" end test "validates form on change", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/__resources__/new") html = view |> form("#__resource__-form", __resource__: %{name: ""}) |> render_change() assert html =~ "can't be blank" end test "creates record on valid submit", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/__resources__/new") {:ok, _view, html} = view |> form("#__resource__-form", __resource__: %{name: "New Item"}) |> render_submit() |> follow_redirect(conn) assert html =~ "__RESOURCE_TITLE__ created" assert html =~ "New Item" end end describe "Form - Edit" do test "renders edit form with existing data", %{conn: conn, organization: org} do {:ok, item} = __CONTEXT__.create___resource__(%{ name: "Existing Item", organization_id: org.id }) {:ok, _view, html} = live(conn, ~p"/__resources__/#{item.id}/edit") assert html =~ "Edit __RESOURCE_TITLE__" assert html =~ "Existing Item" end test "updates record on valid submit", %{conn: conn, organization: org} do {:ok, item} = __CONTEXT__.create___resource__(%{ name: "Original", organization_id: org.id }) {:ok, view, _html} = live(conn, ~p"/__resources__/#{item.id}/edit") {:ok, _view, html} = view |> form("#__resource__-form", __resource__: %{name: "Updated"}) |> render_submit() |> follow_redirect(conn) assert html =~ "__RESOURCE_TITLE__ updated" assert html =~ "Updated" end test "shows delete button in edit mode", %{conn: conn, organization: org} do {:ok, item} = __CONTEXT__.create___resource__(%{ name: "Delete Me", organization_id: org.id }) {:ok, _view, html} = live(conn, ~p"/__resources__/#{item.id}/edit") assert html =~ "Delete" end test "deletes record from edit form", %{conn: conn, organization: org} do {:ok, item} = __CONTEXT__.create___resource__(%{ name: "Delete Me", organization_id: org.id }) {:ok, view, _html} = live(conn, ~p"/__resources__/#{item.id}/edit") {:ok, _view, html} = view |> element("button[phx-click='delete']") |> render_click() |> follow_redirect(conn, ~p"/__resources__") assert html =~ "__RESOURCE_TITLE__ deleted" refute html =~ "Delete Me" end end end ``` --- ## Notes - **`register_and_log_in_user`** is a shared setup helper defined in `ConnCase` — it creates a user and logs them in. - **`Plug.Conn.put_session(:current_organization_id)`** sets the org context for the session. - Use **`live/2`** to mount a LiveView, returns `{:ok, view, html}`. - Use **`render_click/1`**, **`render_change/1`**, **`render_submit/1`** for events. - Use **`element/2,3`** to target specific DOM elements. - Use **`follow_redirect/2`** after `push_navigate` actions. - Use **`render/1`** to get the current HTML after an event.