Musubi.Testing (musubi v0.3.0)

Copy Markdown View Source

Test entry point for Musubi root stores, analogous to Phoenix.LiveViewTest. Wraps Musubi.Page.Server.start_link/1 with test-friendly defaults and exposes the primary assertion surface (render/2).

Primary surface

Assert against the rendered wire-shape map — the same contract a client observes — not internal socket.assigns. assigns/2 is an escape hatch for state not surfaced through render/1; prefer render/2 for contract assertions.

Example

page = Musubi.Testing.mount(RoomStore, %{"room_code" => "AB12"})
Musubi.Testing.dispatch_command(page, :ko, %{"target" => "p2"})

assert Musubi.Testing.render(page) == %{
  winner: :p1,
  hp: %{p1: 100, p2: 0}
}

Summary

Types

t()

Handle returned by mount/3; passed back into the other helpers.

Functions

Runs the allow_upload preflight against the mounted page and returns the reply.

Returns the raw socket.assigns for the addressed store.

Dispatches a command to a mounted store. Defaults to the root (store_id: []); pass a child path to address a nested store.

Mounts module as a root page. Push patches are delivered to the calling process; consume them with ExUnit.Assertions.assert_receive/2. Tears down on test exit via start_supervised!.

Runs the addressed store's render/1 against its current socket and returns the wire-shape map. Primary assertion surface — what the client would observe after the next reconcile.

Sends an external-mode progress event for an entry on the mounted page.

Simulates a full upload for one entry: sends a single complete chunk via the page server's upload_channel_chunk API, then waits for the resulting patch envelope to land.

Types

t()

@type t() :: %Musubi.Testing{pid: pid(), root: module(), transport: pid()}

Handle returned by mount/3; passed back into the other helpers.

Functions

allow_upload(testing, name, entries, opts \\ [], store_id \\ [])

@spec allow_upload(t(), atom(), [map()], keyword(), Musubi.Socket.store_id()) ::
  {:ok, Musubi.Page.Server.preflight_reply()} | {:error, atom()}

Runs the allow_upload preflight against the mounted page and returns the reply.

Bypasses the transport channel — useful for tests that exercise the preflight + entry add-op path without a Phoenix channel in the way.

Options

  • :endpoint — Phoenix endpoint module used to sign tokens. Defaults to Musubi.Testing.TestEndpoint, a stub endpoint automatically registered in test mode.

assigns(testing, store_id \\ [])

@spec assigns(t(), Musubi.Socket.store_id()) :: map()

Returns the raw socket.assigns for the addressed store.

Escape hatch — prefer render/2 for contract assertions. Use only when the value you need is not exposed through render/1 (e.g. a private field captured for later use).

dispatch_command(testing, name, payload, store_id \\ [])

@spec dispatch_command(t(), atom(), map(), Musubi.Socket.store_id()) ::
  {:ok, map()} | {:error, term()}

Dispatches a command to a mounted store. Defaults to the root (store_id: []); pass a child path to address a nested store.

Mirrors the client-side proxy.dispatchCommand(name, payload) contract.

mount(module, params \\ %{}, opts \\ [])

@spec mount(module(), map(), keyword()) :: t()

Mounts module as a root page. Push patches are delivered to the calling process; consume them with ExUnit.Assertions.assert_receive/2. Tears down on test exit via start_supervised!.

Options

  • :transport_pid — pid that receives push patches. Defaults to self().

render(testing, store_id \\ [])

@spec render(t(), Musubi.Socket.store_id()) :: map()

Runs the addressed store's render/1 against its current socket and returns the wire-shape map. Primary assertion surface — what the client would observe after the next reconcile.

Values are returned as native Elixir terms (atom literals stay atoms); the JSON-string transformation happens on the way out to the client, not inside render/1.

simulate_external_progress(testing, name, entry_ref, progress, store_id \\ [])

@spec simulate_external_progress(
  t(),
  atom(),
  String.t(),
  non_neg_integer(),
  Musubi.Socket.store_id()
) ::
  :ok

Sends an external-mode progress event for an entry on the mounted page.

simulate_upload(testing, name, entry_ref, bytes_total, store_id \\ [])

@spec simulate_upload(
  t(),
  atom(),
  String.t(),
  non_neg_integer(),
  Musubi.Socket.store_id()
) :: :ok

Simulates a full upload for one entry: sends a single complete chunk via the page server's upload_channel_chunk API, then waits for the resulting patch envelope to land.