: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)Replacing a deletion with an insertion (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 are swapped; core's IntegerLiteral still applies to other at:
values (a computed index, at: 2). 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. Core mutators do not remove the entry; IntegerLiteral still mutates the limit's
numeric value to test off-by-one errors at the boundary.
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 report the same mutant twice. 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]. A pipe form
(socket |> stream_insert(:songs, song)) is the call it is sugar for, so the socket counts
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.