From b72446b80eb862b8b860b77cbb85a7dec1c6924d Mon Sep 17 00:00:00 2001 From: Willem van den Ende Date: Wed, 28 Oct 2026 22:27:59 +0000 Subject: [PATCH] yak(behavior-tests-editorial-liveviews-microprints-editor-dashboard-llh3): behavior tests: editorial LiveViews (microprints, editor dashboard) --- app/mix.exs | 12 ++--- .../live/editor_dashboard_live_test.exs | 35 +++++++++++++ .../live/microprints_live_test.exs | 50 +++++++++++++------ .../firehose_web/live/source_viewer_test.exs | 11 +--- app/test/support/microprints_case.ex | 43 ++++++++++++++++ 5 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 app/test/support/microprints_case.ex diff --git a/app/mix.exs b/app/mix.exs index e2cbaaf..2b41fa7 100644 --- a/app/mix.exs +++ b/app/mix.exs @@ -114,12 +114,12 @@ defmodule Firehose.MixProject do # land; never lower it. A drop means the change (or an exclusion) is wrong, # not the floor. # - # Measured 2026-10-26 (PostgreSQL on localhost:5432, exclusions below): - # 86.79% after the last exclusion revision (86.17% before excluding the - # embed_templates wrappers and Firehose.Application), identical via + # Measured 2026-10-28 (PostgreSQL on localhost:5432, exclusions below): + # 87.56% after the editorial-LiveView behavior tests (86.97% before them, and + # 86.79% / 86.17% at earlier exclusion revisions), identical via # `mix test --cover` and via `mix test --cover --export-coverage default` + - # `mix test.coverage`, and stable across seeds. Floor is the integer below - # that — so a floor of 86 means "at least 86.00%". + # `mix test.coverage`. Floor is the integer below that — so a floor of 87 + # means "at least 87.00%". # # `test.coverage` deletes stale exports before sampling: `mix test.coverage` # imports *every* .coverdata it finds, so leftover exports from earlier runs @@ -165,7 +165,7 @@ defmodule Firehose.MixProject do # that is where the behavior-test work shows up as a higher floor. defp test_coverage do [ - summary: [threshold: 86], + summary: [threshold: 87], ignore_modules: [ Firehose.Repo, Firehose.Mailer, diff --git a/app/test/firehose_web/live/editor_dashboard_live_test.exs b/app/test/firehose_web/live/editor_dashboard_live_test.exs index 94c5f00..c53274d 100644 --- a/app/test/firehose_web/live/editor_dashboard_live_test.exs +++ b/app/test/firehose_web/live/editor_dashboard_live_test.exs @@ -87,6 +87,41 @@ defmodule FirehoseWeb.EditorDashboardLiveTest do refute html =~ "Live Post" end + test "switching Drafts -> Scheduled -> Drafts shows the matching panel", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/editor/dashboard") + + # Both panels are always in the DOM, and only the inactive one is marked + # `hidden`: the panel's own class is the only server-side signal of what + # the reader sees. The panel-scoped content assertions below keep this + # from being a class check alone. + assert has_element?(view, "#drafts-tab") + refute has_element?(view, "#drafts-tab.hidden") + assert has_element?(view, "#scheduled-tab.hidden") + + drafts_html = view |> element("#drafts-tab") |> render() + assert drafts_html =~ "Draft Post" + refute drafts_html =~ "Scheduled Post" + + view |> element(~s(button[phx-value-tab="scheduled"])) |> render_click() + + assert has_element?(view, "#scheduled-tab") + refute has_element?(view, "#scheduled-tab.hidden") + assert has_element?(view, "#drafts-tab.hidden") + + scheduled_html = view |> element("#scheduled-tab") |> render() + assert scheduled_html =~ "Scheduled Post" + refute scheduled_html =~ "Draft Post" + + view |> element(~s(button[phx-value-tab="drafts"])) |> render_click() + + refute has_element?(view, "#drafts-tab.hidden") + assert has_element?(view, "#scheduled-tab.hidden") + + drafts_again_html = view |> element("#drafts-tab") |> render() + assert drafts_again_html =~ "Draft Post" + refute drafts_again_html =~ "Scheduled Post" + end + test "renders with app layout including menu", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/editor/dashboard") # Verify the app layout is applied by checking for menu elements diff --git a/app/test/firehose_web/live/microprints_live_test.exs b/app/test/firehose_web/live/microprints_live_test.exs index 6c9723b..6b432eb 100644 --- a/app/test/firehose_web/live/microprints_live_test.exs +++ b/app/test/firehose_web/live/microprints_live_test.exs @@ -3,6 +3,7 @@ alias FirehoseWeb.MicroprintsLive defmodule FirehoseWeb.MicroprintsLiveTest do use FirehoseWeb.ConnCase, async: true + import FirehoseWeb.MicroprintsCase import Phoenix.LiveViewTest describe "scan_source_files/0" do @@ -81,18 +82,15 @@ defmodule FirehoseWeb.MicroprintsLiveTest do {:ok, view, _html} = live(conn, ~p"/microprints") # Expand file A - view - |> element("button[phx-value-path=\"#{file_a}\"]", "Expand") - |> render_click() + expand_file(view, file_a) # Verify file A shows "Collapse" button (it's expanded) html = render(view) assert html =~ ~s(phx-value-path="#{file_a}") assert html =~ "Collapse" - # Highlight a line in file B by dispatching the highlight_line event - # (microprint renders multiple rect segments per line, element() can't pick one) - render_click(view, "highlight_line", %{"line" => "1", "path" => file_b}) + # Highlight a line in file B + click_microprint_rect(view, file_b, 1) # file A should be collapsed when highlighting a different file html = render(view) @@ -112,9 +110,7 @@ defmodule FirehoseWeb.MicroprintsLiveTest do {:ok, view, _html} = live(conn, ~p"/microprints") # Expand file A - view - |> element("button[phx-value-path=\"#{file_a}\"]", "Expand") - |> render_click() + expand_file(view, file_a) # Capture file A's source content from the rendered HTML html_a = render(view) @@ -123,9 +119,7 @@ defmodule FirehoseWeb.MicroprintsLiveTest do "File A source should be visible when expanded" # Expand file B (should auto-collapse A) - view - |> element("button[phx-value-path=\"#{file_b}\"]", "Expand") - |> render_click() + expand_file(view, file_b) # Capture file B's source content html_b = render(view) @@ -144,6 +138,34 @@ defmodule FirehoseWeb.MicroprintsLiveTest do end end + describe "collapsing an expanded file" do + test "hides the source lines and brings the Expand control back", %{conn: conn} do + # The URL route is used rather than clicking Expand: `restore_state_from_params/2` + # validates the param against the scanned cards (Enum.find over @microprints), + # so a real scanned path is required either way - see the "unknown expanded + # path in URL is ignored" test for the other half of that contract. + path = MicroprintsLive.scan_source_files() |> List.first() + + {:ok, view, _html} = live(conn, ~p"/microprints?expanded=#{path}") + + assert has_element?(view, "#line-1"), + "restored expansion should render the file's source lines" + + assert has_element?(view, ~s(button[phx-value-path="#{path}"]), "Collapse") + + collapse_file(view, path) + + refute has_element?(view, "#line-1"), + "collapsing should stop rendering the source viewer" + + refute render(view) =~ "Collapse", + "no card should still be expanded once the only expanded one is collapsed" + + assert has_element?(view, ~s(button[phx-value-path="#{path}"]), "Expand"), + "the card's control should be Expand again" + end + end + describe "app layout with menu" do test "renders the microprints page", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/microprints") @@ -190,9 +212,7 @@ defmodule FirehoseWeb.MicroprintsLiveTest do {:ok, view, _html} = live(conn, ~p"/microprints") - view - |> element("button[phx-value-path=\"#{file_a}\"]", "Expand") - |> render_click() + expand_file(view, file_a) # URL should contain expanded param after clicking Expand html = render(view) diff --git a/app/test/firehose_web/live/source_viewer_test.exs b/app/test/firehose_web/live/source_viewer_test.exs index b727887..96422eb 100644 --- a/app/test/firehose_web/live/source_viewer_test.exs +++ b/app/test/firehose_web/live/source_viewer_test.exs @@ -1,20 +1,11 @@ defmodule FirehoseWeb.SourceViewerTest do use FirehoseWeb.ConnCase, async: true + import FirehoseWeb.MicroprintsCase import Phoenix.LiveViewTest alias FirehoseWeb.MicroprintsLive - defp expand_file(view, path) do - view - |> element("button[phx-value-path=\"#{path}\"]", "Expand") - |> render_click() - end - - defp click_microprint_rect(view, path, line) do - render_click(view, "highlight_line", %{"line" => Integer.to_string(line), "path" => path}) - end - describe "line elements" do test "render with unique ids when file is expanded", %{conn: conn} do files = MicroprintsLive.scan_source_files() diff --git a/app/test/support/microprints_case.ex b/app/test/support/microprints_case.ex new file mode 100644 index 0000000..dde3a2a --- /dev/null +++ b/app/test/support/microprints_case.ex @@ -0,0 +1,43 @@ +defmodule FirehoseWeb.MicroprintsCase do + @moduledoc """ + Helpers for driving the `/microprints` LiveView. + + Both `FirehoseWeb.MicroprintsLiveTest` and `FirehoseWeb.SourceViewerTest` walk + the same page through the same two controls (the per-card Expand/Collapse + button and the microprint rects), so the mechanics live here instead of being + re-implemented in each test module. + + A plain module, imported by the tests, rather than a `use`-able case template: + the LiveView tests own their own setup and only share these three actions. + """ + + import Phoenix.LiveViewTest + + @doc """ + Clicks the Expand control of the card for `path` and returns the render. + """ + def expand_file(view, path) do + view + |> element(~s(button[phx-value-path="#{path}"]), "Expand") + |> render_click() + end + + @doc """ + Clicks the Collapse control of the card for `path` and returns the render. + """ + def collapse_file(view, path) do + view + |> element(~s(button[phx-value-path="#{path}"]), "Collapse") + |> render_click() + end + + @doc """ + Clicks a single line of a microprint, highlighting it in the source viewer. + + The microprint renders several rect segments per line, so `element/3` cannot + pick one; the `highlight_line` event is dispatched directly instead. + """ + def click_microprint_rect(view, path, line) do + render_click(view, "highlight_line", %{"line" => Integer.to_string(line), "path" => path}) + end +end