Easiest way to render a .heex template to an HTML string?

Usually in tests of my views, I have used safe_to_string() to convert a returned :safe tuple to HTML that I can assert on. For example:

test "greets the user" do
  user = %{name: "Kurt"}
  html = MyView.render("some_template.html", user: user) |> Phoenix.HTML.safe_to_string()

  assert html =~ "Hello, Kurt"
end

However, if the template is a .heex file, this no longer works because the return struct is a %Phoenix.LiveView.Rendered{} struct, e.g.:

%Phoenix.LiveView.Rendered{
  dynamic: #Function<0.64041902/1 in PeanutButterWeb.PageView."hello.html"/1>,
  fingerprint: 29760870404026531075951007545402085092,
  root: true,
  static: ["<p>Hello, Kurt</p>"]
}

This is also true, even if I’m rendering from a plain view.

Is there a simple way to convert this struct to plain html?

Thanks :+1:

2 Likes

Okay, here’s what I found after digging around for a while:

test "greets the user" do
  user = %{name: "Kurt"}
  html = MyView.render("some_template.html", user: user)
        |> Phoenix.HTML.Safe.to_iodata()
        |> IO.iodata_to_binary()

  assert html =~ "Hello, Kurt"
end

Will give the returned HTML as string, ready for assertions.

By the way, there are very good docs available about it here: Phoenix.LiveView.Engine — Phoenix LiveView v0.16.3

7 Likes

Just to add: this also works for rendering components to plain HTML, without having to define a separate template / view, for example:

DevApp.ProductComponent.render(%{product: %{id: 1}}) 
|> Phoenix.HTML.Safe.to_iodata() 
|> IO.iodata_to_binary()
4 Likes

what do you mean by: DevApp.ProductComponent

I mean in my case I don’t use any kind of component, so what that part is supposed to be?

I have a partial that I want to send it as a string from my controller json response, do you have any idea?

You might have a look at the solution…

what do you mean?

I meant there is an example with view in the solution…

Now You don’t have view anymore, but normally html files should be the replacement.

but that what I was asking, because we have no views now, what’s the appropriate code, I am new to phx so without seeing any concrete code I couldn’t imagine what it should look like

Something like this…

iex(1)> h = ArenaWeb.PageHTML.home []
%Phoenix.LiveView.Rendered{
  static: ["<h1>Welcome!</h1>\n<p>Hello world!</p>\n\n", "\n<br>\n\n",
   "\n<br>\n\n<i class=\"bi bi-0-circle\"></i>\n<i class=\"bi bi-alarm\"></i>\n<i class=\"bi bi-apple\"></i>\n\n",
   "\n", "\n\n", "\n",
   "\n<div class=\"dropdown\">\n    <button class=\"btn btn-danger dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n        Dropdown button\n    </button>\n    <ul class=\"dropdown-menu\">\n        <li><a class=\"dropdown-item\" href=\"#\">Action</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Another action</a></li>\n        <li><a class=\"dropdown-item\" href=\"#\">Something else here</a></li>\n    </ul>\n</div>\n\n",
   "\n\n", "\n\n", "\n\n", ""],
  dynamic: #Function<0.126745719/1 in ArenaWeb.PageHTML.home/1>,
  fingerprint: 298082719659021152739576324523550059913,
  root: false,
  caller: :not_available
}
iex(2)> h.static |> Phoenix.HTML.Safe.to_iodata() |> IO.iodata_to_binary()         
"&lt;h1&gt;Welcome!&lt;/h1&gt;\n&lt;p&gt;Hello world!&lt;/p&gt;\n\n\n&lt;br&gt;\n\n\n&lt;br&gt;\n\n&lt;i class=&quot;bi bi-0-circle&quot;&gt;&lt;/i&gt;\n&lt;i class=&quot;bi bi-alarm&quot;&gt;&lt;/i&gt;\n&lt;i class=&quot;bi bi-apple&quot;&gt;&lt;/i&gt;\n\n\n\n\n\n\n&lt;div class=&quot;dropdown&quot;&gt;\n    &lt;button class=&quot;btn btn-danger dropdown-toggle&quot; type=&quot;button&quot; data-bs-toggle=&quot;dropdown&quot; aria-expanded=&quot;false&quot;&gt;\n        Dropdown button\n    &lt;/button&gt;\n    &lt;ul class=&quot;dropdown-menu&quot;&gt;\n        &lt;li&gt;&lt;a class=&quot;dropdown-item&quot; href=&quot;#&quot;&gt;Action&lt;/a&gt;&lt;/li&gt;\n        &lt;li&gt;&lt;a class=&quot;dropdown-item&quot; href=&quot;#&quot;&gt;Another action&lt;/a&gt;&lt;/li&gt;\n        &lt;li&gt;&lt;a class=&quot;dropdown-item&quot; href=&quot;#&quot;&gt;Something else here&lt;/a&gt;&lt;/li&gt;\n    &lt;/ul&gt;\n&lt;/div&gt;\n\n\n\n\n\n\n\n"

Sending html on the wire is not optimal.

I have used this technique in Rails long time ago, with ajax calls…

But now Liveview does the same in more optimized way (data on the wire)

Try to learn Liveview… it’s worth it

Thank you for the suggestion, I know about liveview and I will look at it when I am ready, for now I just have a specific use case to send the html that way

from your output I see that tags are replaced by &lt and &gt how I can only stripes the tags coming from the assigns, as the static tags in the template are already safe