test/openagents_web/components/graph_test.exs

58e6347eeb72 · 3 KB

defmodule OpenAgentsWeb.UI.GraphTest do
  @moduledoc """
  The geometry is the part that can be silently wrong: a link terminating a few
  units inside a node still *looks* plausible. These assert the boundary
  condition directly, at many angles, rather than eyeballing one rendering.
  """

  use ExUnit.Case, async: true

  alias OpenAgentsWeb.UI.Graph

  describe "unit_vector/2" do
    test "is a unit vector for arbitrary separations" do
      for {a, b} <- [
            {{0.0, 0.0}, {3.0, 4.0}},
            {{-5.0, 2.0}, {7.0, -9.0}},
            {{1.0, 1.0}, {1.0, 9.0}}
          ] do
        {ux, uy} = Graph.unit_vector(a, b)
        assert_in_delta :math.sqrt(ux * ux + uy * uy), 1.0, 1.0e-9
      end
    end

    test "coincident points yield no direction rather than dividing by zero" do
      assert Graph.unit_vector({4.0, 4.0}, {4.0, 4.0}) == {0.0, 0.0}
    end
  end

  describe "surface_point/3" do
    test "a circle's surface point is exactly one radius from its centre" do
      node = %{shape: :circle, x: 10.0, y: 20.0, r: 7.0}

      for degrees <- 0..359//7 do
        rad = degrees * :math.pi() / 180.0
        u = {:math.cos(rad), :math.sin(rad)}
        {x, y} = Graph.surface_point(node, u)

        dx = x - node.x
        dy = y - node.y
        assert_in_delta :math.sqrt(dx * dx + dy * dy), node.r, 1.0e-9
      end
    end

    test "padding pushes the point outward by exactly that much" do
      node = %{shape: :circle, x: 0.0, y: 0.0, r: 5.0}
      {x, _} = Graph.surface_point(node, {1.0, 0.0}, 3)
      assert_in_delta x, 8.0, 1.0e-9
    end

    test "a rect's surface point lands on the boundary, never inside it" do
      node = %{shape: :rect, x: 0.0, y: 0.0, width: 20.0, height: 10.0}

      for degrees <- 0..359//5 do
        rad = degrees * :math.pi() / 180.0
        {x, y} = Graph.surface_point(node, {:math.cos(rad), :math.sin(rad)})

        on_x = abs(abs(x) - 10.0) < 1.0e-9 and abs(y) <= 5.0 + 1.0e-9
        on_y = abs(abs(y) - 5.0) < 1.0e-9 and abs(x) <= 10.0 + 1.0e-9

        assert on_x or on_y, "#{degrees} deg gave {#{x}, #{y}}, not on the boundary"
      end
    end

    test "an axis-aligned vector does not divide by a zero component" do
      node = %{shape: :rect, x: 0.0, y: 0.0, width: 8.0, height: 6.0}
      assert {+0.0, 3.0} = Graph.surface_point(node, {0.0, 1.0})
      assert {4.0, +0.0} = Graph.surface_point(node, {1.0, 0.0})
    end
  end

  describe "link_distance/1" do
    test "proximity encodes kind, matching Unit's ratios" do
      base = Graph.link_distance()

      assert Graph.link_distance(:type) == base / 2
      assert Graph.link_distance(:data) == base / 2
      assert Graph.link_distance(:error) == base * 7 / 8
      assert Graph.link_distance(:exposed) == base * 2 / 3
      assert Graph.link_distance(:normal) == base

      assert Graph.link_distance(:error) < Graph.link_distance(:normal)
    end
  end

  describe "the state taxonomy" do
    test "covers the SCV lifecycle from docs/scv-planning.md" do
      assert Graph.statuses() == [:idle, :running, :paused, :circuit_open, :disabled]
    end

    test "covers the work-item lifecycle from docs/scv-planning.md" do
      assert Graph.item_statuses() ==
               [:discovered, :admitted, :running, :completed, :deferred, :refused, :failed]
    end

    test "every rendered status has a style rule in the style pack" do
      css = File.read!("assets/css/openagents.css")

      for status <- Graph.statuses() ++ Graph.item_statuses() do
        assert css =~ ~s([data-status="#{status}"]),
               "#{status} renders but has no style rule, so it would be indistinguishable"
      end

      for kind <- Graph.link_kinds() -- [:normal] do
        assert css =~ ~s([data-kind="#{kind}"]), "link kind #{kind} has no style rule"
      end
    end
  end

  describe "describe_arc/5" do
    test "produces a valid arc command" do
      assert Graph.describe_arc(6.0, 6.0, 5.0, 210, 330) =~
               ~r/^M [\d.-]+ [\d.-]+ A 5\.0 5\.0 0 0 0/
    end
  end
end