defmodule MakeupTest.Lexer.HTMLFormatterTest do
  use ExUnit.Case, async: true
  import ExUnitProperties
  alias Makeup.Formatters.HTML.HTMLFormatter

  test "edge case - character with value = 128" do
    # Handles an edge case in the HTML formatter (already fixed),
    # in which the case c == 128 wasn't handled.
    # The previous version would raise an error.

    # Encode the right hand side as a concatenation of binaries
    # to make it more obvious:
    assert HTMLFormatter.format_as_binary([{:string, %{}, [128]}]) ==
      ~S[<pre class="highlight"><code>] <>
      ~S[<span class="s">] <>
      << 128 :: utf8 >> <>
      ~S[</span>] <>
      ~S[</code></pre>]
  end

  test "encode ASCII character (c <= 127)" do
    # Handles an edge case in the HTML formatter (already fixed),
    # in which the case c == 128 wasn't handled.
    # The previous version would raise an error.

    # Encode the right hand side as a concatenation of binaries
    # to make it more obvious:
    check all c <- StreamData.integer(1..127),
              not(c in [?&, ?<, ?>, ?", ?']) do
      html =
        ~S[<pre class="highlight"><code>] <>
        ~S[<span class="s">] <>
        << c >> <>
        ~S[</span>] <>
        ~S[</code></pre>]

      assert HTMLFormatter.format_as_binary([{:string, %{}, [c]}]) == html
      assert HTMLFormatter.format_as_binary([{:string, %{}, c}]) == html
    end
  end

  test "encode ASCII character (c >= 128)" do
    # Some of these characters won't be valid unicode but that doesn't matter

    # Encode the right hand side as a concatenation of binaries
    # to make it more obvious:
    check all c <- StreamData.integer(128..16666) do
      html =
        ~S[<pre class="highlight"><code>] <>
        ~S[<span class="s">] <>
        << c :: utf8 >> <>
        ~S[</span>] <>
        ~S[</code></pre>]

      assert HTMLFormatter.format_as_binary([{:string, %{}, [c]}]) == html
      assert HTMLFormatter.format_as_binary([{:string, %{}, c}]) == html
    end
  end

  test "special characters are correctly encoded" do
    for {c, encoded} <- [
      {?&, "&amp;"},
      {?<, "&lt;"},
      {?>, "&gt;"},
      {?", "&quot;"},
      {?', "&#39;"}
    ] do
      assert HTMLFormatter.format_as_binary([{:string, %{}, [c]}]) ==
        ~S[<pre class="highlight"><code>] <>
        ~S[<span class="s">] <>
        encoded <>
        ~S[</span>] <>
        ~S[</code></pre>]
    end
  end

  test "group ids are encoded before being written to data attributes" do
    html =
      HTMLFormatter.format_as_binary([
        {:punctuation, %{group_id: ~S[group-1" onmouseover="alert(1)]}, "("}
      ])

    assert html ==
      ~S[<pre class="highlight"><code>] <>
      ~S[<span class="p" data-group-id="group-1&quot; onmouseover=&quot;alert(1)">(</span>] <>
      ~S[</code></pre>]
  end

  test "raise exception when token is not an iolist" do
    msg = "Found `%{}` inside what should be an iolist"

    assert_raise RuntimeError, msg, fn ->
      assert HTMLFormatter.format_as_binary([{:string, %{}, %{}}])
    end
  end
end
