Mutare.Phoenix.LiveView.SendUpdate (mutare_phoenix_live_view v0.2.1)

Copy Markdown View Source

: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.