# `Masu.Test`
[🔗](https://github.com/hattoriai/masu/blob/v0.7.0/lib/masu/test.ex#L1)

Helpers for `Phoenix.LiveViewTest`, to read and query the grids of a
LiveView 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"} | _] = rows(view, "orders")
    end

The functions send the events that the browser sends (see `Masu.Grid`),
so the grid applies a sort, a filter or a search as it does for a
person: the source sorts, filters and counts, and `rows/3` reads the
rows it gives.

Changes broadcast with `Masu.Live` before a call are in the rows it
reads: the call waits for the LiveView to take them.

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, a list for a column of many values (`field={[:tags,
:name]}`), and for a column with content the text of its cell.

# `action`

```elixir
@spec action(struct(), String.t(), String.t()) ::
  :ok | {:ok, String.t()} | {:error, String.t() | :refused} | :async
```

Runs the action `label` on the rows selected in the grid `id` (`:action`,
see `Masu.Action`), as its button in the bar above the grid does, past
its question (`confirm`). Rows are selected with `select/4` or
`select_all/3`.

Returns the action's result: `:ok`, `{:ok, message}`, `{:error,
message}` or `:async` (see `Masu.Action`), or `{:error, :refused}`
when the server refuses it: the grid does not offer it (`:if`), or no
row is selected.

# `actions`

```elixir
@spec actions(struct(), String.t(), :selection) :: [String.t()]
```

The labels of the actions of your own (`Masu.Action`) that the grid `id`
offers: on the rows selected (`actions(view, id, :selection)`, while
rows are selected), on the row `row_id` (`actions(view, id, :row,
row_id)`: those it shows, available or not), or for the column `key`
in its header menu (`:header`) or in the menu of its cells (`:cell`).

# `actions`

```elixir
@spec actions(struct(), String.t(), :header | :cell | :row, term()) :: [String.t()]
```

# `activate_row`

```elixir
@spec activate_row(struct(), String.t(), term()) ::
  :ok | {:navigate | :patch, String.t()} | {:error, :refused}
```

Opens the row `row_id` of the grid `id` (`on_row_activate`), as a click
on it or Enter does. The grid reads the row from its source.

Returns what the grid does: `{:patch, path}` or `{:navigate, path}`
when the function gives one, else `:ok`; `{:error, :refused}` when the
grid has no `on_row_activate` or no row `row_id` in its query.

# `add_rows`

```elixir
@spec add_rows(struct(), String.t(), map() | [map()], keyword()) ::
  {:ok, [term()]}
  | {:error, ok: term(), error: [{String.t() | nil, String.t()}]}
```

Adds rows to the grid `id`, as the person does with "New row" or a
paste below the last row: each a map of column keys (or atoms) to
values, as the browser sends them. The grid saves each through its
`create`, or its `changeset` of its `struct`.

Returns `{:ok, ids}` when every row was saved, or `{:error, results}`,
a result for each row: `{:ok, id}` or `{:error, [{key, message}]}` (a
key of nil for an error of the row). Options: `:origin`, as `edit/4`.

# `apply_view`

```elixir
@spec apply_view(struct(), String.t(), map()) :: [String.t()]
```

Applies a view to the grid `id`, as a link or a saved view does: params
as `view/2` gives them (`%{}` for the grid's own view). Returns the
keys of the columns of the parts that no longer apply (see
`Masu.View.from_params/3`).

# `ask_count`

```elixir
@spec ask_count(struct(), String.t()) :: {:exact | :at_least, non_neg_integer()}
```

Asks the grid `id` for its count, as the person does when they go to
the end of a grid that does not know it (Ctrl+End): `{:exact, n}`, or
`{:at_least, n}` from a source that does not know it either.

# `cell_action`

```elixir
@spec cell_action(
  struct(),
  String.t(),
  String.t(),
  String.t() | atom(),
  term(),
  keyword()
) :: struct()
```

Runs the item `label` of the menu of the cell of the row `row_id` and
the column `key` of the grid `id`, as `header_action/4` does. The grid
reads the row and the value of the cell from its source.

Options: `:range`, `{ids, keys}`: a range of cells selected, its rows'
ids and its columns' keys, that the cell is in.

# `close_editor`

```elixir
@spec close_editor(struct(), String.t()) :: struct()
```

Closes the editor of a cell of the grid `id`, as Escape does. Returns the view.

# `close_panel`

```elixir
@spec close_panel(struct(), String.t()) :: struct()
```

Closes the panel of an item of the grid `id`, as Escape does. Returns the view.

# `collapse`

```elixir
@spec collapse(struct(), String.t(), term()) :: struct()
```

Closes the row `row_id` of a tree in the grid `id`. Returns the view.

# `columns`

```elixir
@spec columns(struct(), String.t()) :: [String.t()]
```

The labels of the columns of the grid `id`, in order.

# `count`

```elixir
@spec count(struct(), String.t()) :: {:exact | :at_least, non_neg_integer()}
```

What the grid `id` knows of its count: `{:exact, n}`, or
`{:at_least, n}` while it does not know it (`count={:none}`, a count
not in yet, or a source that does not count). `total/2` gives `n`.

# `delete_rows`

```elixir
@spec delete_rows(struct(), String.t(), [term()] | :selection, keyword()) ::
  {:ok, String.t() | nil}
  | :async
  | {:error, [{term(), String.t()}], String.t() | nil}
```

Deletes the rows `ids` of the grid `id`, or, with `:selection`, the
rows selected (`select/4`, `select_all/3`), as the person does with
Ctrl+Delete. Returns `{:ok, undo}`, with the token that brings them back
(`restore_rows/3`; nil when the grid cannot), `:async` when the grid's
`on_delete` deletes them later, or `{:error, errors, undo}` with
`{row_id, message}` for each row that was not deleted. Options:
`:origin`, as `edit/4`.

# `detail`

```elixir
@spec detail(struct(), String.t(), term(), boolean()) :: struct()
```

Opens (or, with `false`, closes) the detail of the row `row_id` in the
grid `id`: the grid renders its `:detail` slot for the row, which
`Phoenix.LiveViewTest.render/1` then has. Returns the view.

# `edit`

```elixir
@spec edit(struct(), String.t(), [tuple()], keyword()) ::
  :ok | :async | {:error, [{term(), String.t(), String.t()}]}
```

Edits cells of the grid `id`, as a person typing in them, pasting or
undoing does: `edits` is a list of `{row_id, key, value}`, the value as
the browser sends it (a string, or `nil` to empty the cell). The grid
saves the edits of each row together.

An edit may be `{row_id, key, value, before}`, with the value the person
saw: the grid refuses it when the cell's value changed meanwhile, as it
does for the browser.

Returns `:ok` when every edit was saved, `:async` when the grid's
`on_update` saves them later (`Masu.Write.async/1`), or `{:error,
errors}` with `{row_id, key, message}` for each cell that was not.

Options: `:origin`, what the person did, as `Masu.Write`'s `origin`
says (`:paste`, `:fill`...); `:test` unless given.

# `expand`

```elixir
@spec expand(struct(), String.t(), term()) :: struct()
```

Opens the row `row_id` of a tree in the grid `id`, as a click on its
arrow (or →) does: its children show under it. Returns the view.

# `expand_siblings`

```elixir
@spec expand_siblings(struct(), String.t(), term()) :: struct()
```

Opens every row beside `row_id` that has children, as `*` does.
Returns the view.

# `export`

```elixir
@spec export(struct(), String.t(), :csv | :xlsx, keyword()) :: {String.t(), binary()}
```

Exports the grid `id` as its Export menu does (`Masu.Export`), and
downloads the file from the link the browser gets, through the
endpoint and the router's export route (`Masu.Router`): `{name,
binary}`. A `conn` (`Phoenix.ConnTest.build_conn/0`, with what the
route's pipeline wants of it, a signed-in user) is given with `:conn`.

# `export_url`

```elixir
@spec export_url(struct(), String.t(), :csv | :xlsx) :: String.t()
```

The link the Export menu of the grid `id` gives the browser, for a
test of the route itself: that it wants a signed-in user, say.

# `filter`

```elixir
@spec filter(struct(), String.t(), [tuple()]) :: struct()
```

Filters the grid `id`: `filters` is a list of `{key, op, value}` (see
`Masu.Filter` for the operators), which replaces the grid's filters.
Returns the view.

    filter(view, "orders", [{"customer.name", :eq, "Ana"}, {"tags", :all_of, ["gift"]}])

# `group`

```elixir
@spec group(struct(), String.t(), [String.t() | atom()]) :: struct()
```

Groups the rows of the grid `id` by columns (`Masu.Group`), as the
header menu does: `keys`, the first the outermost; `[]` stops
grouping. Returns the view.

# `groups`

```elixir
@spec groups(struct(), String.t(), keyword()) :: [map()]
```

The rows of a grouped grid `id` that are groups, in order, as maps of
`:id` (what `expand/3` takes), `:key`, `:value`, `:count`, `:level`,
`:expanded` and `:totals` (by column key). Options as `rows/3`.

# `header_action`

```elixir
@spec header_action(struct(), String.t(), String.t(), String.t() | atom()) :: struct()
```

Runs the item `label` of the header menu of the column `key` of the
grid `id`, as a click on it does: its function, its event to the
LiveView, or its panel, which `Phoenix.LiveViewTest.render/1` then has.
An item that is only a JS command runs in the browser, not here.
Returns the view.

# `history`

```elixir
@spec history(struct(), String.t(), term(), String.t() | atom() | nil) :: [map()]
```

The history of the cell of the row `row_id` and the column `key` of the
grid `id` (`history`, see `Masu.History`), or of the row when `key` is
nil, newest first, as its popover shows it (none for a row the grid's
query does not show): maps of `:id`, `:key`,
`:op`, `:before`, `:after` (as the browser gets them), `:user`,
`:origin`, `:batch`, `:batch_size` and `:at`.

# `ids`

```elixir
@spec ids(struct(), String.t(), keyword()) :: [term()]
```

The ids of the rows of the grid `id`, as `rows/3` reads them.

# `layout`

```elixir
@spec layout(struct(), String.t(), keyword()) :: struct()
```

Changes the layout of the grid `id`, as its columns menu, its headers
and their edges do: any of `order: keys` (every column, in order),
`hidden: keys`, `pinned: %{key => :start | :end}` and `widths: %{key =>
pixels}`. Returns the view.

# `nodes`

```elixir
@spec nodes(struct(), String.t(), keyword()) :: [map()]
```

The rows of a tree (`Masu.Tree`) in the grid `id`, in order, as maps
of `:id`, `:level` (0 for a root), `:children` (whether it has any) and
`:expanded`. Options as `rows/3`.

# `open_editor`

```elixir
@spec open_editor(struct(), String.t(), term(), String.t() | atom()) :: struct()
```

Opens the editor of the cell of the row `row_id` and the column `key`
of the grid `id`, when the column's `editor` is a form of yours: the
grid renders it, which `Phoenix.LiveViewTest.render/1` then has. A
value it would post is saved with `edit/4`, as the browser saves it.
Returns the view.

# `page`

```elixir
@spec page(struct(), String.t(), pos_integer()) :: [term()]
```

Goes to a page of the grid `id` (`paging={:pages}`), from 1, as its
pager does, and gives the ids of its rows.

# `place`

```elixir
@spec place(struct(), String.t(), term()) :: non_neg_integer() | nil
```

The place (from 0) of the row `row_id` in the grid's current order, or
`nil` when its query leaves the row out: where the grid scrolls to
follow another person to it.

# `restore`

```elixir
@spec restore(struct(), String.t(), String.t() | {:batch, String.t()}) ::
  {:ok, non_neg_integer()} | {:error, [{term(), String.t(), String.t()}]}
```

Brings back the value before an entry of the history of the grid `id`
(its `:id`, `history/4`), or, with `{:batch, batch}`, every cell of a
batch (someone's paste), as its Restore does: an edit, refused for a
cell that changed since. Returns `{:ok, count}` of the cells restored,
or `{:error, errors}` as `edit/4` does.

# `restore_rows`

```elixir
@spec restore_rows(struct(), String.t(), String.t()) ::
  {:ok, [term()]} | {:error, term()}
```

Brings back the rows of a delete (its `undo` token, `delete_rows/3`), as
the person does with Ctrl+Z. Returns `{:ok, ids}`.

# `row_action`

```elixir
@spec row_action(struct(), String.t(), String.t(), term()) ::
  :ok
  | {:ok, String.t()}
  | {:error, String.t() | :refused}
  | :async
  | {:navigate | :patch, String.t()}
```

Runs the action `label` on the row `row_id` of the grid `id`
(`:row_action`, see `Masu.Action`), as its item in the row's menu
does, past its question (`confirm`). The grid reads the row from its
source.

Returns the action's result, as `action/3` does; for a link, the path
it goes to, `{:navigate, path}` or `{:patch, path}`; and `{:error,
:refused}` when the server refuses it: the grid does not offer it, the
row does not show it, or it is disabled for the row.

# `rows`

```elixir
@spec rows(struct(), String.t(), keyword()) :: [map()]
```

The rows of the grid `id`, in its current order.

Options: `:from` (0), the first row, and `:count` (100), how many
(at most 500, as the browser asks).

# `search`

```elixir
@spec search(struct(), String.t(), String.t()) :: struct()
```

Searches the grid `id` as its search box does. Returns the view.

# `select`

```elixir
@spec select(struct(), String.t(), [term()], boolean()) :: struct()
```

Selects rows of the grid `id` (`selectable`) by their ids, as their
boxes do, or, with `false`, takes them out. Returns the view.

# `select_all`

```elixir
@spec select_all(struct(), String.t(), boolean()) :: struct()
```

Selects every row of the query of the grid `id` (or, with `false`, none). Returns the view.

# `selection`

```elixir
@spec selection(struct(), String.t()) :: map()
```

The selection of the grid `id`: `%{all: boolean, ids: [...], except:
[...], count: n}`.

# `sort`

```elixir
@spec sort(struct(), String.t(), term()) :: struct()
```

Sorts the grid `id` as clicks on its headers do: `sorts` is a key, a
`{key, :asc | :desc}`, or a list of them (the first the most
important). `[]` takes the order away. Returns the view.

A column of a related row's field (`field={[:customer, :name]}`) is
named by its key, the path with dots unless given:
`sort(view, "orders", {"customer.name", :desc})`.

# `total`

```elixir
@spec total(struct(), String.t()) :: non_neg_integer()
```

How many rows the grid `id` has for its current query.

# `totals`

```elixir
@spec totals(struct(), String.t()) :: map() | nil
```

The totals of the query of the grid `id`, by column key: the row of
totals at its bottom (`Masu.Group`). `nil` when no column has an
aggregate.

# `values`

```elixir
@spec values(struct(), String.t(), String.t(), keyword()) :: [
  {term(), non_neg_integer()}
]
```

The values that the filter menu of the column `key` offers: the values
of the column among the rows of the query (without the column's own
filters), each with its count, the most frequent first. `:search`
narrows them as the menu's search box does.

# `view`

```elixir
@spec view(struct(), String.t()) :: map()
```

The view of the grid `id`, as its params (see `Masu.View`): what differs
from the grid's own view. `%{}` is the grid's own view.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
