Mutare.Phoenix.LiveView.Stream (mutare_phoenix_live_view v0.1.0)

Copy Markdown View Source

:lv_stream — mutates Phoenix.LiveView stream operations. Three kinds, each a variant label:

swap inverts an operation for its opposite:

stream_insert(socket, name, item)  <->  stream_delete(socket, name, item)

Inserting where the code meant to delete (or vice versa) leaves the wrong rows in the phx-update="stream" container. A test that asserts a stream's contents after the event kills it; one that only checks the event returned lets it survive. Only the 3-argument form is swapped — stream_insert also exists at arity 4 but stream_delete does not, so a stream_insert/4 gets the option mutations below instead, keeping every mutant compiling.

at swaps the at: option between its two boundary positions on stream_insert/4:

stream_insert(socket, name, item, at: 0)   <->  stream_insert(socket, name, item, at: -1)

Prepend vs append (-1, the default, appends). Tests overwhelmingly assert an item is present, rarely where — a survivor means nothing pins the insertion position. Only the two boundary literals swap; any other at: value (a computed index, at: 2) is left to core's IntegerLiteral. The swap substitutes exactly the at: value node, so core's Mutare.Transform.Overlap recognises it as covering and prunes IntegerLiteral's redundant leaf mutants (0 → 1, 0 → -1) at that node — this family's position swap is the one informative mutant there.

limit drops the limit: entry from stream/4 and stream_insert/4:

stream(socket, name, items, limit: 10)     ->  stream(socket, name, items)
stream_insert(socket, name, item, at: 0, limit: 10)
                                           ->  stream_insert(socket, name, item, at: 0)

The unbounded stream is a valid-but-wrong program: without the limit the client container grows without pruning. A survivor means no test fills past the limit and asserts the pruned contents. (Presence-drop of the entry is not core-owned; the limit's numeric value stays IntegerLiteral's — an off-by-one on the boundary is a genuinely different question.)

No reset: mutation, deliberately: dropping reset: true is equivalent to core BooleanLiteral's true → false flip on the same literal (false is the default), so a drop here would double-report the same mutant — an ownership violation. The same holds for stream_insert/4's boolean update_only: option.

Suppress one kind with # mutare:ignore[lv_stream:swap] / [lv_stream:at] / [lv_stream:limit], or the whole family with # mutare:ignore[lv_stream]. Pipe forms (socket |> stream_insert(:songs, song)) count toward every arity gate.

Matches direct, aliased, and bare-imported (use-injected) calls. This family has a high equivalent-mutant rate in apps that don't assert stream contents — drop it from your preset if that is noisy.