Return value of Phoenix.LiveViewTest element/3

First of all, hi everyone!
I am a developer with py and js background in the process of learing Elixir (mandatory disclaimer).

I am writing tests for a Liveview project.
I have a form wich filters a list of items.
I would like to write a few tests to document the filtering behaviour.

I would like to assert that the number of elements on the page after the filters activation is correct.
First thing came to mind was has_element?/3 wich led me to element/3, and I saw that those functions raise an error if more that one element is returned.

Now, the questions:

  1. Am I on the wrong path entirely? (In wanting to test the correct count of elements are rendered)
  2. On IRC I was suggested to use an external lib to count the elements, I’d prefer not (if there’s a vanilla way)
  3. I was wondering if an elements/3 is planned, or it’s a design decision to have that return only one element.

Thank you,
Carlo

1 Like

I’d say you’re on the right track. Maybe try something like this? Note the selector. You’d have to give a class to the list items:

option_count = length(list_of_options) 

assert view |> element(".list-item:nth-of-type(#{option_count})") |> has_element?()
refute view |> element(".list-item:nth-of-type(#{option_count + 1})") |> has_element?()

LiveViewTest uses Floki under the hood. See available selectors here: Overview — Floki v0.30.1

Floki does parse HTML into a tree data structure, which can be used to count elements. I think clever use of selectors should work well, though.

2 Likes

You can render the LiveView to a string with Phoenix.LiveViewTest.render/1, then parse with Floki.parse_fragment!/1, then search with Floki.find/2. After that, you could extract the text of all the matches with Floki.text/2 or extract some attribute with Floki.attribute/2. (As @a8t mentioned, Floki is already part of your app if you’re using LiveView.)

So your code might look something like:

items = 
  view
  |> render()
  |> Floki.parse_fragment!()
  |> Floki.find(".list-item")
  |> Enum.map(&Floki.text(&1, sep: " "))

assert items == ["item 1", "item 2"]

or if your items have some unique attribute, for example data-role:

items = 
  view
  |> render()
  |> Floki.parse_fragment!()
  |> Floki.find(".list-item")
  |> Enum.map(&Floki.attribute(&1, "data-role"))

assert items == ["role-1", "role-2"]

You could then extract that into a handy helper function, so you could end up with something like:

assert find_attrs(view, ".list-item", "data-role") == ["role-1", "role-2"]
3 Likes

Brillant! I love it and it’s working like a charm.

Thank you, that should definitely be in the docs, being that powerful.

Will mark as solved, thank you!

This is dependent on the formatting of the error, but so far it works, I think it’s the most creative I got, lately :smiley:

no_elements = 3
selector = "#orders > tr"
assert_raise ArgumentError, ~r/^expected selector \"#{selector}\" to return a single element, but got #{no_elements}/, fn ->
  render(element(index_view, selector))
end

To be honest though, I think there should be a count_element utility or a suggested way to do this.