:lv_send_update — mutates a Phoenix.LiveView component-update call, the message that tells
a child Phoenix.LiveComponent to re-render with new assigns. 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 was told to update — that it
re-renders with the new assigns (Phoenix.LiveViewTest.render_component/2, or asserting the
live render reflects the change). It is the LiveComponent sibling of :lv_event's dropped
client push: same "forgot to fire a side effect" removal, aimed at the server→component
channel rather than the server→client one. An immediate survivor means no test depends on
the update being deferred — the delay could be deleted (or garbled) 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 (under the hood, the raw send/2 echo), so the conventional :ok
stands in; send_update_after documents its return — the scheduled timer's reference() —
so its no-ops 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()) fires the update now and keeps the documented ref
type. Matching that type keeps both mutants clean no-ops even at the rare site that captures
the ref to Process.cancel_timer/1 it later — cancelling an unknown ref returns false,
where a :ok there would raise (an uninformative crash-kill this package exists to avoid).
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 owns the timing question, and a 1_000 → 1_001
off-by-one there is the near-unkillable noise those families' own timeout table exists to
avoid.
Because no socket flows through, these calls are never idiomatically piped (a direct pipe
would feed the module/pid slot), so a piped occurrence is left alone rather than given a
non-faithful Function.identity() pass-through. Only the real arities fire — send_update/2,3
and send_update_after/3,4 — 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.