Masu.Test reads and queries the grids of a LiveView in
Phoenix.LiveViewTest, with no browser.
import Masu.Test
test "finds the late orders", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/orders")
assert total(view, "orders") == 120
view
|> filter("orders", [{"status", :eq, "late"}])
|> sort("orders", {"total", :desc})
assert [%{"number" => "A-1042", "total" => "250.50"} | _] = rows(view, "orders")
endThe functions send the events that the browser sends, so the grid
applies a sort, a filter or a search as it does for a person: the source
sorts, filters and counts (in the database, for an Ecto source), and
rows/3 reads the rows it gives.
Reading
rows(view, id, from: 0, count: 100): the rows, in order. A row is a map of each column's key to its value as the browser gets it: numbers and booleans as they are, decimals as strings, dates and times in ISO 8601, and for a column of your own HEEx the text of its cell.ids(view, id): the ids of the rows.total(view, id): how many rows the query has.columns(view, id): the labels, in order.values(view, id, key, search: nil): what the column's filter menu offers, as{value, count}, the most frequent first.place(view, id, row_id): where the row is in the grid's order (from 0), ornilwhen its filters leave it out: what a person who follows someone scrolls to.
Editing
edit(view, id, edits): edits cells as a person does, with a list of{row_id, key, value}(the value as the browser sends it: a string, ornilto empty the cell). Gives:ok, or{:error, errors}with{row_id, key, message}for each cell that was not saved. An edit{row_id, key, value, before}carries the value the person saw, and is refused when the cell changed meanwhile.
Querying
Each returns the view, so they pipe:
sort(view, id, sorts): a key,{key, :asc | :desc}, or a list of them, the first the most important.[]takes the order away.filter(view, id, filters): a list of{key, op, value}, which replaces the grid's filters. SeeMasu.Filterfor the operators.search(view, id, text): as the search box.
A query that the grid refuses (a filter on a column that does not filter, a value that is not of the column's type) fails the test with the grid's reason.
Layout and views
layout(view, id, changes): moves, hides, pins and resizes columns, as the columns menu and the headers do:order: keys,hidden: keys,pinned: %{key => :start | :end},widths: %{key => pixels}.view(view, id): the grid's view, as its params (%{}is the grid's own view; seeMasu.View).apply_view(view, id, params): applies a view, as a link or a saved view does; gives the keys of the parts that no longer apply.
columns/2 and rows/3 read the columns that show, in their order.
Groups and totals
group(view, id, keys): groups by columns, as the header menu does;[]stops.groups(view, id, opts): the groups' rows, as maps of:id,:key,:value,:count,:level,:expandedand:totals.totals(view, id): the row of totals, by column key.
Counts and pages
count(view, id):{:exact, n}, or{:at_least, n}while the grid does not know its count;total/2givesn.ask_count(view, id): asks for the count, as Ctrl+End does.page(view, id, page): goes to a page (from 1) of a grid withpaging={:pages}, and gives the ids of its rows.
New and deleted rows
add_rows(view, id, rows): adds rows as "+ New row" does, each a map of column keys to values;{:ok, ids}, or{:error, results}.delete_rows(view, id, ids_or_selection)andrestore_rows(view, id, undo): deletes rows (or:selection), and brings them back.
Export and selection
export(view, id, format, opts \\ []): exports as the Export menu does, downloads the file through your router's export route, and gives it,{name, binary};conn:a conn with what the route's pipeline wants (a signed-in user).export_url(view, id, format): the link the browser would get, to test the route itself (that it refuses a person who is not signed in).select(view, id, ids, on \\ true),select_all(view, id, on \\ true)andselection(view, id).
Trees and details
nodes(view, id, opts): the rows of a tree, as maps of:id,:level,:childrenand:expanded.expand(view, id, row_id),collapse(view, id, row_id)andexpand_siblings(view, id, row_id): as the arrows and*.detail(view, id, row_id, open \\ true): opens a row's detail, whichrender/1then has; a grid in it is read with its own id.
Menus of your own
actions(view, id, :header | :cell, key): the labels of the items the grid offers for a column.header_action(view, id, label, key)andcell_action(view, id, label, key, row_id, opts): run an item, as a click on it does; its panel is then inrender/1.close_panel(view, id)closes it. See Menus of your own.
Slow sources and controlled grids
The helpers wait for a grid's tasks (load={:async}), as the browser
does, and fail with the grid's error when a read fails: a test reads the
same either way. In a controlled grid (on_query), a sort or a filter
reaches the LiveView, which runs its query: render_async/1 waits for
a query that it runs in a task, before the rows are read.
What these tests do not cover
Masu.Test does not run the browser: the drawing of the rows, the
keyboard, the selection and the clipboard are tested by Masu's own
browser tests.