# `Mirage`
[🔗](https://github.com/sodapopcan/mirage/blob/main/lib/mirage.ex#L1)

Browserless test framework for the Hologram web framework.

Mirage initializes a page or component and expands its template into
a fully-resolved DOM that tests can make assertions and trigger events
against.

# `blur`

```elixir
@spec blur(Mirage.Session.t(), String.t(), String.t() | keyword()) ::
  Mirage.Session.t()
```

Trigger a blur event on an element.

Accepts the same options as `Mirage.click/3`.

# `check`

```elixir
@spec check(Mirage.Session.t(), String.t(), keyword()) :: Mirage.Session.t()
```

Checks a checkbox by its associated label text.

Triggers the input's `$change` as well as its form's `$change` event (if it
has one).

Matches exactly by default; pass `exact: false` to match substrings.
Raises if no matching radio button is found, or if more than one matches.

# `choose`

```elixir
@spec choose(Mirage.Session.t(), String.t(), keyword()) :: Mirage.Session.t()
```

Selects a radio button by its associated label.

Triggers the input's `$change` event as well as its form's `$change` event (if
there is one).

Labels may wrap the input or reference it via a `for`/`id` pair.

Matches exactly by default; pass `exact: false` to match substrings.
Raises if no matching radio button is found, or if more than one matches.

## Example

    %Hologram.Server{}
    |> visit(Profile)
    |> choose("robot")
    |> assert_has("p", "Your gender is 'robot'")

# `click`

```elixir
@spec click(Mirage.Session.t(), String.t(), String.t() | keyword()) ::
  Mirage.Session.t()
```

Trigger a `$click` event on the element matching the given CSS selector.

Any actions or commands will be run.  If the click triggers a page navigation,
the new page will be loaded into the session.  When clicking a submit button
that belongs to a form, that form's `$submit` event will be triggered.

Raises if no matching element with a `$click` handler is found, or if more
than one matches.

## Examples

    %Hologram.Server{}
    |> visit(SignUpPage)
    |> fill_in("Name", with: "Bender")
    |> fill_in("Password", with: "killallhumans")
    |> click("button", "Submit")
    |> assert_page(WelcomePage)

    %Hologram.Server{}
    |> visit(HomePage)
    |> click("button", "Log out")

## Options

  * `:text` - Match on the element's inner text.
  * `:exact` - Set to `false` to match on a substring of an element's text.
    Default is `true` meaning you must provide an exact match.

# `click_button`

```elixir
@spec click_button(Mirage.Session.t(), String.t(), keyword(any())) ::
  Mirage.Session.t()
```

Click on a button by its text.

If it's a submit button belonging to a form, it will trigger that form's
`$submit` event.

This is otherwise shorthand for `Mirage.click/3` with `"button"` as its selector.

# `click_link`

```elixir
@spec click_link(Mirage.Session.t(), String.t(), keyword(any())) :: Mirage.Session.t()
```

Click on a link by its text.

This is simply a shorthand for `Mirage.click/3` with `"a"` as its selector.

# `fill_in`

```elixir
@spec fill_in(Mirage.Session.t(), String.t(), keyword()) :: Mirage.Session.t()
```

Fill in an `input` or `textarea` by its label.

Finds an input by its associated label and triggers the input's `$change`
and as well as its form's `$change` event (if it has one).

Labels may be associated with their input either by wrapping the input
(`<label>Name <input/></label>`) or via a `for` attribute matching the
input's `id`.

Matches exactly by default; pass `exact: false` to match substrings.
Raises if no matching label is found, or if more than one matches.

# `fill_in_hidden`

```elixir
@spec fill_in_hidden(Mirage.Session.t(), String.t(), keyword()) :: Mirage.Session.t()
```

Fill in a hidden input by its `name` attribute.

Unlike `fill_in/3`, this targets hidden inputs (`type="hidden"`) directly
by name rather than by label text. Raises if the input is not hidden, or if
it is disabled or readonly.

Triggers the input's `$change` event and its form's `$change` event (if
it has one).

## Example

    %Hologram.Server{}
    |> visit(CheckoutPage)
    |> fill_in_hidden("csrf_token", with: "abc123")

### Note

**This function should generally be avoided.**  It is useful if you are using
a JavaScript library that fills in hidden fields via non-transpiled JS.

# `focus`

```elixir
@spec focus(Mirage.Session.t(), String.t(), String.t() | keyword()) ::
  Mirage.Session.t()
```

Trigger a focus event on an element.

Accepts the same options as `Mirage.click/3`.

# `mount`

```elixir
@spec mount(function(), {module(), keyword()} | [{module(), keyword()}]) ::
  Mirage.Session.t()
```

Mount a component in isolation.

Pass a `~HOLO` template containing a single component.  Props, cid, and slot
content are all declared in the markup itself:

    ~HOLO"""
    <MyApp.Components.PoplarTracker cid="counter" eaten={0} />
    """
    |> mount()
    |> click("button", "Eat a poplar")
    |> assert_has("p", "Number of poplars eaten: 1")

Context can be provided as a `{Namespace, key: value}` tuple.  Props declared
with `from_context` will be populated from matching context values.

    ~HOLO"""
    <MyApp.Components.PoplarTracker cid="counter">
      <p>{@user.name} eats too many poplars</p>
    </MyApp.Components.PoplarTracker>
    """
    |> mount({MyApp, user: current_user, theme: "dark"})

For multiple namespaces, use a list of tuples:

    ~HOLO"""
    <MyApp.Dashboard cid="dash" />
    """
    |> mount([{MyApp, user: current_user}, {Themes, mode: "dark"}])

# `open_browser`

```elixir
@spec open_browser(Mirage.Session.t(), keyword() | function()) :: Mirage.Session.t()
```

Opens the current page HTML in the default browser.

    session
    |> fill_in("Name", with: "Philip")
    |> open_browser()
    |> assert_has("Philip")

When using with a component (via `Mirage.mount/2`), the output will be wrapped
in a thin layout bringing in your app's styles as well as a small bit of CSS that
center's the component in the viewport.

## Options

  * `:wrap` - When `false`, skips the layout wrapper entirely and outputs
    raw component HTML.  Defaults to `true`.
  * `:center` - When `false`, omits the centering CSS.  Defaults to `true`.

Both options can be configured globally:

    # config/test.exs
    config :mirage, open_browser: [center: false, wrap: false]

# `reload`

```elixir
@spec reload(Mirage.Session.t()) :: Mirage.Session.t()
```

"Reloads" the current page by revisiting it with the current params.

All client-side state (component state, checked radios, etc.) is reset,
just like a real browser reload.

    session
    |> fill_in("Name", with: "Leela")
    |> reload()
    |> refute_has("input", value: "Leela")

# `select`

```elixir
@spec select(Mirage.Session.t(), String.t(), String.t(), keyword()) ::
  Mirage.Session.t()
```

Selects an option in a `<select>` box by its label.

Triggers the select's `$change` event with the option's `value`
attribute (defaulting to the option's inner text when no `value` attribute
is present).

Labels may wrap the select element or reference it via a `for`/`id` pair.

Works with multi-selects.

Matches exactly by default; pass `exact: false` to match substrings.
Raises if no matching label or option is found, or if more than one matches.

## Example

    %Hologram.Server{}
    |> visit(EditProfilePage)
    |> select("Company", "Planet Express")
    |> assert_has("p", "You work for 'Planet Express'")

# `select_text`

```elixir
@spec select_text(Mirage.Session.t(), String.t(), String.t() | keyword()) ::
  Mirage.Session.t()
```

Triggers a `$select` event on a text input or textarea by its label selecting
the text given to `option_text`.

When `text` is omitted, all text in the input is selected.

Raises if the label does not point to an input that accepts text (i.e. raises
for checkboxes, radios, selects, and non-text input types).

## Examples

    session
    |> fill_in("Bio", with: "I'm a bending unit. I bend girders.")
    |> select_text("Bio", "girder")

    session
    |> fill_in("Bio", with: "My hobbies include smoking cigars, drinking, and killing all humans")
    |> select_text("Bio")

# `uncheck`

```elixir
@spec uncheck(Mirage.Session.t(), String.t(), keyword()) :: Mirage.Session.t()
```

Unchecks a checkbox by its associated label.

Trigger's the input's `$change` event as well as its form's `$change` event
(if it has one).

Matches exactly by default; pass `exact: false` to match substrings.
Raises if no matching radio button is found, or if more than one matches.

# `visit`

```elixir
@spec visit(Hologram.Server.t() | Mirage.Session.t(), module(), keyword()) ::
  Mirage.Session.t()
```

Entry point to create a session.

Takes a `%Hologram.Server{}` or `%Mirage.Session{}`, a page module, and
optionally any params.  Returns a session which the rest of `Mirage` can use.

    visit(%Hologram.Server{}, MyPage, user_id: 42)

    %Hologram.Server{}
    |> Hologram.Server.put_session(:user_id, 42)
    |> visit(MyPage)

When given a session, the server state is carried over (useful for
client-side navigation):

    visit(session, OtherPage, id: 7)

# `within`

```elixir
@spec within(Mirage.Session.t(), String.t(), (Mirage.Session.t() -&gt;
                                          Mirage.Session.t())) ::
  Mirage.Session.t()
```

Scopes all operations within the given block to descendants of the element
matching `selector`.

    session
    |> within(".sidebar", fn session ->
      session
      |> assert_has("a", "Home")
      |> click_link("Home")
    end)

# `within_article`

```elixir
@spec within_article(Mirage.Session.t(), String.t(), (Mirage.Session.t() -&gt;
                                                  Mirage.Session.t())) ::
  Mirage.Session.t()
```

Scopes to the `<article>` whose first heading (`h1` - `h6`) matches `header`.

    session
    |> within_article("Blog Post", fn session ->
      assert_has(session, "p", "Post content")
    end)

# `within_fieldset`

```elixir
@spec within_fieldset(Mirage.Session.t(), String.t(), (Mirage.Session.t() -&gt;
                                                   Mirage.Session.t())) ::
  Mirage.Session.t()
```

Scopes to the `<fieldset>` whose `<legend>` matches `legend`.

    session
    |> within_fieldset("Account", fn session ->
      assert_has(session, "input#username")
    end)

# `within_section`

```elixir
@spec within_section(
  Mirage.Session.t(),
  String.t(),
  String.t(),
  (Mirage.Session.t() -&gt;
     Mirage.Session.t())
) ::
  Mirage.Session.t()
```

Scopes to the `<section>` whose first heading (`h1` - `h6`) matches `header`.

    session
    |> within_section("Settings", fn session ->
      assert_has(session, "Send me update", "No")
    end)

This can also be used more generally when given a CSS selector as the second
argument.

    session
    |> within_section("div[role=article]", "My header", fn session ->
      assert_has(session, "p", "content")
    end)

# `assert_disabled`

Asserts that the input identified by its label text is disabled.

    session
    |> assert_disabled("Email")

# `assert_has`

Asserts that the session's DOM contains exactly one element matching the
given CSS selector (and optional filters).

Raises if no element matches or if more than one element matches.

    session
    |> assert_has("button")
    |> assert_has("h1", "Welcome")
    |> assert_has("input#email", value: "bender@planetexpress.com")

## Options

  * `:text` — also require the element's inner text (trimmed) to equal this value
  * `:value` — also require the element's `value` attribute to equal this value
  * `:at` — match only the element at this 1-based position among all nodes
    matching the selector

# `assert_page`

```elixir
@spec assert_page(Mirage.Session.t(), module(), keyword()) ::
  Mirage.Session.t() | no_return()
```

Asserts that we are on a specific page.  Useful after redirect.

Optionally takes a keyword list of expected params:

    session
    |> click_link("Profile")
    |> assert_page(ProfilePage, user_id: 42)

# `assert_readonly`

Asserts that the input identified by its label text is readonly.

    session
    |> assert_readonly("User ID")

# `refute_disabled`

Asserts that the input identified by its label text is *not* disabled.

    session
    |> refute_disabled("Email")

# `refute_has`

Asserts that the session's DOM does *not* contain any element matching the
given CSS selector (and optional filters).

    session
    |> refute_has(".error")
    |> refute_has("p", text: "Deleted")

Accepts the same options as `assert_has/3`.

# `refute_readonly`

Asserts that the input identified by its label text is *not* readonly.

    session
    |> refute_readonly("Email")

---

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