How to test and assert order of elements in LiveView?

I have a LiveView page that returns a list of students names and ids sorted in a table.

In test,
{:ok, index, html} = live(conn, Routes.students_path(conn, :index, class.id))

I can see that the html here has the names of the students sorted but there are other unnecessary contents(for my test in this case) in between these names. So how can I eliminate other contents and just test only the names are in the expected sort order?

Have you considered using Floki to parse your HTML output? I’m sure you’ll find your way with it.

1 Like

In LiveViewTest helper is a function element/3 that you use to retrieve html elements from your view and then validate the elements. The documentation has some nice examples:

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3

I would also recommend to keep the tests of views simple and check only that the important stuff is there.

Move the ordering of your students to a public function and test that function. This will make testing so much easier.

1 Like

I’m working through this same issue right now; testing an ordering function in LiveView where users can drag divs to re-order them.

To the best of my knowledge element/3 will throw if more than one item matches. It seems like there is no way to do this except for using something like Floki? If anyone has any other ides or suggestions I’d love to hear them.

I just take the simple boneheaded approach:

insert(
  :list,
  items: [
    build(:item, name: "One"),
    build(:item, name: "Two"),
    build(:item, name: "Three")
  ]
)

assert lv
       |> element(".list-container")
       |> render() =~ ~r/One.*Two.*Three/s

The s regex modifier allows matching over line breaks.

3 Likes

Oh snap yeah that’s a much simpler idea. Thanks.

1 Like

I had started a PR for GitHub - mohamed-tallarium/Mononcle (which uses Floki to represent some higher-level assertions) that would take a tab-separated string to represent an expected table and compare it against the table in the DOM.

(Certainly I’ve used that technique in other environments to make the expected table state as clear as possible)

1 Like

HtmlQuery has a table function: HtmlQuery — HtmlQuery v1.2.2

iex> html = "<table> <tr><th>A</th><th>B</th><th>C</th></tr> <tr><td>1</td><td>2</td><td>3</td></tr> </table>"

iex> HtmlQuery.table(html)

[
  ["A", "B", "C"],
  ["1", "2", "3"]
]

It also has a bunch of other useful functions for parsing HTML. It pairs nicely with Pages, a library that implements the page pattern for UI testing and works with controllers and LiveViews without the involvement of a web browser.

2 Likes

So I don’t know why I never thought of this before but out of the box you can use CSS selectors along with has_element?/3 for much more fine-grained and reliable results than my current regex solution:

assert has_element?(lv, ".my-list li:nth-child(1)", "One")
assert has_element?(lv, ".my-list li:nth-child(2)", "Two")
assert has_element?(lv, ".my-list li:nth-child(3)", "Three")

cc: @travisf

3 Likes

PhoenixTest now has a very clean way to do this:

visit(conn, "/")
|> assert_has(".my-list li", "One", at: 1)
|> assert_has(".my-list li", "Two", at: 2)
|> assert_has(".my-list li", "Three", at: 3)
2 Likes