:lv_send_update — mutates a Phoenix.LiveView call that sends new assigns to a child
Phoenix.LiveComponent for re-rendering. Two kinds, each a variant label:
remove drops the update ("does the update matter?"):
send_update(Cart, id: "cart", count: 3) -> :ok
send_update(pid, Cart, id: "cart", count: 3) -> :ok
send_update_after(Cart, [id: "cart"], 1_000) -> :erlang.make_ref()
send_update_after(pid, Cart, [id: "cart"], 1_000) -> :erlang.make_ref()immediate keeps the update but drops the delay ("does the timing matter?"):
send_update_after(Cart, [id: "cart"], 1_000) -> send_update(Cart, id: "cart")
:erlang.make_ref()A remove survivor means no test asserts the child component received the update — that it
re-renders with the new assigns (Phoenix.LiveViewTest.render_component/2, or asserting the
live render reflects the change). Like :lv_event, this mutation removes a side effect:
a server→component update in this case, rather than a server→client event.
An immediate survivor means no test depends on the update being deferred — the delay
could be deleted without a failure.
Suppress one kind with # mutare:ignore[lv_send_update:remove] / [lv_send_update:immediate],
or the whole family with # mutare:ignore[lv_send_update].
Unlike push_event (socket-in, socket-out), send_update/send_update_after are
fire-and-forget side effects that never return the socket — so both kinds collapse the whole
call to a happy-path return (not to an argument), and the two calls differ. send_update's
return is undocumented (the return value of send/2), so the conventional :ok
stands in; the documented return type of send_update_after is reference() for the
scheduled timer, so its replacements end in a fresh :erlang.make_ref(). The immediate
mutant is a two-expression block for the same reason: send_update/2,3 alone would change
the return type, while
(send_update(...); :erlang.make_ref()) sends the update immediately and keeps the documented
ref type. Matching that type allows the result of either replacement to be passed to
Process.cancel_timer/1 — cancelling an unknown ref returns false, whereas passing :ok
would raise, killing the mutant without testing the update behaviour.
The delay argument itself is marked with the shared :timeout label
(Mutare.Mutator.argument_marks/1), so core's IntegerLiteral/AtomLiteral leave the
duration literal alone. The immediate mutant tests whether the delay matters; an off-by-one
mutation such as 1_000 → 1_001 is unlikely to affect observable behaviour, which is why
those families skip timeout literals.
No socket flows through these calls, so a pipe into one feeds the module/pid slot
(Cart |> send_update(id: 1)); it is the same call, and gets the same mutants, reported
over the whole pipe. Only calls to send_update/2,3 and send_update_after/3,4 are
mutated, so a name-matched call of any other arity (reachable only by an explicit
qualifier) is left alone, keeping every metamutant compiling.
Matches direct (Phoenix.LiveView.send_update(...)), aliased, and bare-imported
(use-injected) calls. Like :lv_event, this family has a high equivalent-mutant rate in
suites that never assert a component re-rendered — drop it from your preset if that is noisy.