test/openagents_web/live/components_live_test.exs

58e6347eeb72 · 6 KB

defmodule OpenAgentsWeb.ComponentsLiveTest do
  use OpenAgentsWeb.ConnCase, async: true

  import Phoenix.LiveViewTest

  alias OpenAgentsWeb.ComponentCatalog

  test "the index lists every component in the catalog", %{conn: conn} do
    {:ok, view, html} = live(conn, ~p"/components")

    assert html =~ "Component library"
    assert has_element?(view, "#components-index")

    for item <- ComponentCatalog.items() do
      assert has_element?(view, ~s{a[href="/components/#{item.slug}"]}),
             "index is missing a link to #{item.slug}"
    end
  end

  test "every catalog slug renders its own page", %{conn: conn} do
    for item <- ComponentCatalog.items() do
      {:ok, view, html} = live(conn, ~p"/components/#{item.slug}")

      assert has_element?(view, "#component-#{item.slug}"),
             "#{item.slug} did not render a component page"

      assert html =~ item.title
      assert html =~ item.source
    end
  end

  test "each page marks its own sidebar row as selected", %{conn: conn} do
    for item <- ComponentCatalog.items() do
      {:ok, view, _html} = live(conn, ~p"/components/#{item.slug}")

      assert has_element?(
               view,
               ~s{.sidebar-row[data-selected] a[href="/components/#{item.slug}"]}
             ),
             "#{item.slug} did not mark its sidebar row selected"
    end
  end

  test "the sidebar is present on component pages", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/openagents-button")

    assert has_element?(view, ~s{nav[aria-label="Component library"]})
    assert has_element?(view, ~s{a[href="/components/icons"]})
    assert has_element?(view, ~s(#docs-sidebar-collapse-toggle[aria-controls="docs-sidebar"]))
    assert has_element?(view, ~s(#docs-sidebar-expand-toggle[aria-controls="docs-sidebar"]))
  end

  test "an unknown slug redirects back to the index", %{conn: conn} do
    assert {:error, {:live_redirect, %{to: "/components"}}} =
             live(conn, ~p"/components/not-a-component")
  end

  test "the icons page keeps its own route ahead of the slug route", %{conn: conn} do
    {:ok, view, html} = live(conn, ~p"/components/icons")

    assert html =~ "glyphs"
    refute has_element?(view, "#component-icons")
  end

  test "the button page renders the button variants", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/openagents-button")

    assert has_element?(view, "#demo-button-primary", "Primary")
    assert has_element?(view, "#demo-button-disabled")
  end

  test "the input page renders the demo form", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/openagents-input")

    assert has_element?(view, "#component-form")
  end

  test "the repository view page composes trail, sections, tree, and rail", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/openagents-repo-view")

    assert has_element?(view, ".repo-page .breadcrumb__current", "openagents.com")
    assert has_element?(view, ~s{.repo-tabs__tab[aria-current="page"]}, "Code")
    assert has_element?(view, ".repo-view__main .file-table .file-row__message")
    assert has_element?(view, ".repo-view__rail .repo-about__contributors")
  end

  test "repository sections are links, not a tab widget", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/openagents-repo-tabs")

    # A tab role promises panels that swap in place. These change the URL, so
    # the current one is marked by aria-current and nothing claims to be a tab.
    assert has_element?(view, "nav.repo-tabs[aria-label]")
    refute has_element?(view, ~s{.repo-tabs [role="tab"]})
    assert has_element?(view, ".repo-tabs__tab .repo-tabs__count", "12")
  end

  test "the tool page walks every tool state", %{conn: conn} do
    # The status badge is the taxonomy this component exists for. A page that
    # showed one successful call would document the state nobody needs help
    # reading and hide the six that matter.
    {:ok, view, _html} = live(conn, ~p"/components/ai-tool")

    for state <- ~w(
          input-streaming input-available output-available output-error
          approval-requested approval-responded output-denied
        ) do
      assert has_element?(view, "#demo-tool-#{state}"),
             "the tool page does not show the #{state} state"
    end
  end

  test "the composer page shows all four submit statuses", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/ai-prompt-input")

    for status <- ~w(ready submitted streaming error) do
      assert has_element?(view, "#demo-submit-#{status}"),
             "the composer page does not show the #{status} submit control"
    end

    # The streaming control is a stop button, not a disabled send: it is the one
    # status where the reader still has something to do.
    assert has_element?(view, ~s{#demo-submit-streaming[aria-label="Stop"]})
  end

  test "the reasoning page shows the block both open and closed", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components/ai-reasoning")

    assert has_element?(view, "details#demo-reasoning-open[open]")
    refute has_element?(view, "details#demo-reasoning-closed[open]")
  end

  test "the catalog exposes no nonfunctional theme control", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/components")

    refute has_element?(view, ~s{a[href="/components/theme-toggle"]})
  end

  test "the theme control is one action, not a three-rung picker" do
    # This previously asserted three buttons -- system, light, dark. The owner
    # replaced that with a single toggle: storing nothing IS system, and it is
    # the default until someone chooses, so a third button made the common case
    # (never touching this) look like an unmade decision.
    document =
      OpenAgentsWeb.Layouts.theme_toggle(%{})
      |> rendered_to_string()
      |> LazyHTML.from_fragment()

    buttons = document |> LazyHTML.query(~s{button.theme-toggle}) |> LazyHTML.to_tree()
    assert length(buttons) == 1

    assert document
           |> LazyHTML.query(~s{button.theme-toggle[aria-label="Toggle theme"]})
           |> LazyHTML.to_tree() != []

    # Both glyphs ship; which one shows is decided at runtime from the effective
    # theme, because with nothing stored only the browser knows it.
    for glyph <- ~w(theme-toggle__sun theme-toggle__moon) do
      assert document |> LazyHTML.query(~s{.#{glyph}}) |> LazyHTML.to_tree() != [],
             "the #{glyph} glyph is missing, so the control cannot name its destination"
    end

    # No rung survives from the old picker.
    assert document |> LazyHTML.query(~s{[data-theme-option]}) |> LazyHTML.to_tree() == []
  end
end