# `Masu.Test`
[🔗](https://github.com/hattoriai/masu/blob/v0.1.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, and for a column with content the text of its cell.

# `actions`

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

The labels of the items of the app's own (`Masu.Action`) that the grid
`id` offers for the column `key`: in its header menu (`:header`), or in
the menu of its cells (`:cell`).

# `add_rows`

```elixir
@spec add_rows(struct(), String.t(), map() | [map()]) ::
  {: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).

# `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_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) ::
  {:ok, String.t() | nil} | {: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), or `{:error, errors, undo}`
with `{row_id, message}` for each row that was not deleted.

# `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(), [{term(), String.t() | atom(), term()}]) ::
  :ok | {: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, or `{:error, errors}` with
`{row_id, key, message}` for each cell that was not.

# `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.

# `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.

# `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`.

# `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_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}`.

# `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.

# `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*
