Hi! In my Elm project, I recently implemented pagination. I ended up defining a “widget” (i.e. view helper/utility function) that looks something like this:
To render, this “widget” has a bunch of logic to figure out how many pages there are, how many page numbers to show, whether the “Previous” and “Next” buttons are active, whether to put the “…” ellipsis anywhere, etc. It was sufficiently complicated that after I wrote it, I thought I would try testing it, which I attempted to do with the HTML testing functions in elm-explorations/test
. I found it very difficult to write tests that expressed the kinds of invariants I had in mind. I thought I would ask if I was missing something obvious or if there’s some way to write tests that I hadn’t considered.
Here are the tests I wanted to write and what I eventually did to try to implement them.
Test 1: there should generally be 9 page numbers you can click on
For this test, I wanted to render the pagination widget with 100 pages and a random page number and ensure that the “window” of page numbers around the current page remains constant. It was easy to figure out how to find only links, but the “previous”/“next” links don’t really count for this test. I couldn’t find any way to exclude them – all selectors are positive and I didn’t see a “not” or “drop” – so I ended up adding a class to the rendered HTML for page numbers, so I could findAll
the elements with the page-number
class. This feels a little dirty, because these classes will end up in my optimized build but only exist to facilitate testing.
Once I had that, I was faced with the problem that the first page/last page buttons are also rendered as numbers, so have the same classes. I could have added another class to distinguish “window” numbers from the first page/last page, but this was getting complicated enough. I settled for writing the test to ensure that there are either 10 or 11 numbers (you will always see at least one, and possibly both, of the first/last buttons).
Test 2: there should never be any ellipses between consecutive numbers
For this test, I re-used the same 100-page widget with a random “current page” idea. It was harder to figure out how to write the test itself. Finding an ellipsis is easy, but then using that to get the numbers on either side is challenging. It’s possible to get an element for a specific index, but it’s not possible to get the index of the ellipsis – you’d have to encode out-of-band knowledge about where to find it, which makes the test more brittle.
Even if you could knew which indexes were before and after the ellipsis, there aren’t any real ways to extract information from the HTML. There’s no way to say “Get the text, parse it and see if it’s the same as the other number +/- 1”. You can check for specific values (2 for the beginning, or 99 on the other side), but even that isn’t super helpful.
An alternative approach might be something like “Ensure that there is either the ‘first page’ button and an ellipsis but no 2, or instead that there is the ‘first page button’ and a 2 but no ellipsis”. But I couldn’t figure out how to do this either; there’s no Expect.any
(only Expect.all
), so you can’t check for “one of these cases”. There is an Expect.onFail
, but you can’t use it to recover from failure, just to provide a different error message.
In the end, I abandoned writing Quickcheck-style tests for this. I handwrote test cases for page=5, page=6, and page=7 and wrote assertions about what I expected to find in each case. This isn’t great because it relies on knowledge of the boundary conditions and edge cases of the pagination widget. Doing the same thing for the ellipsis at the other end of the widget seemed like too much work so I didn’t bother (hopefully all my bugs are symmetric).
Missing links
I think I could have written more effective tests if I had had access to one of:
- some kind of DOM navigation: you can navigate to the children of a node, but not back to the parents, nor forward/backward to sibling nodes
- extracting information from
Query
s: get the text contents, convert aMultiple
into aList Single
, find the index of a thing. There’seach
but there’s nothing like afold
that would let you carry information from node to node. There’shas
andhasNot
, but they produceExpectations
, which are hard to work with. - a
not
selector - when using
Expectations
is necessary, more Boolean operators on them
Instead, I had to rely on knowledge of the internals of the code under test, and hard-coded results.
Is any of this kind of functionality on the roadmap for elm-explorations/test
? Based on the name, I assume it’s still being defined. Or am I maybe using it wrong, or is there something unique and unusual about my use case? Thanks!