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")
end

The 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), or nil when 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, or nil to 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. See Masu.Filter for 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; see Masu.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, :expanded and :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/2 gives n.
  • 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 with paging={: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) and restore_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) and selection(view, id).

Trees and details

  • nodes(view, id, opts): the rows of a tree, as maps of :id, :level, :children and :expanded.
  • expand(view, id, row_id), collapse(view, id, row_id) and expand_siblings(view, id, row_id): as the arrows and *.
  • detail(view, id, row_id, open \\ true): opens a row's detail, which render/1 then has; a grid in it is read with its own id.
  • actions(view, id, :header | :cell, key): the labels of the items the grid offers for a column.

  • header_action(view, id, label, key) and cell_action(view, id, label, key, row_id, opts): run an item, as a click on it does; its panel is then in render/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.