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
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
Functions
@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 toMusubi.Testing.TestEndpoint, a stub endpoint automatically registered in test mode.
@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).
@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.
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 toself().
@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.
@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.
@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.