:mail_layout — mutates the seam that decides which layout wraps the rendered body.
Two kinds, variant-labelled:
Setter removal (labels put / put_new) — a Phoenix.Swoosh layout-configuration call
collapses to the email it would have returned:
put_layout(email, {LayoutView, "email.html"}) -> email
put_new_layout(email, {LayoutView, :email}) -> email
email |> put_layout({LayoutView, "email.html"}) -> Elixir.Function.identity()put (put_layout/2) leaves the email with its previous layout, usually the
use-configured one; put_new (put_new_layout/2) leaves the layout unset and the body
renders bare. Either way the templates still render and the email still ships.
Layout suppression at the render site (label off) — render_body/2,3 takes the layout
from its assigns when one is given there (phoenix_swoosh's per-render override), so setting
it to false renders the body with no layout at all:
render_body(email, :welcome, %{name: name})
-> render_body(email, :welcome, %{name: name, layout: false})
render_body(email, :welcome, %{layout: {LayoutView, :promo}})
-> render_body(email, :welcome, %{layout: false})
email |> render_body(:welcome)
-> email |> render_body(:welcome, %{layout: false})A survivor of either kind means no test asserts which layout wrapped the rendered body — the
branded wrapper, the unsubscribe footer, the logo header. Suppress one kind with
# mutare:ignore[mail_layout:put] / [mail_layout:put_new] / [mail_layout:off], or the
family with # mutare:ignore[mail_layout].
Why the render site, and when off fires
Real mailers rarely call put_layout at all: the idiomatic place for a layout is the use
line (use Phoenix.Swoosh, view: MyApp.EmailView, layout: {MyApp.LayoutView, :email}), which
is compile-time configuration Mutare cannot reach (see the README's "What's deliberately out
of scope"). Without the off mutant this family would have nothing to say about the majority
of layout-using mailers. The assigns override is the runtime seam that same configuration
flows through, so mutating it reaches the layout wherever it was configured.
Suppressing a layout that was never in effect is an equivalent mutant, and this package does
not mint those, so off fires only when a layout demonstrably is in effect at the site:
- the mailer's
use Phoenix.Swooshline configured one — reported to this family byMutare.Phoenix.Swoosh.LayoutConfigured, the marker the package's:extensionsentry injects while expanding thatuse; or - the call's own assigns literal carries a truthy
layout:entry, which needs no marker because the author wrote the layout right there.
Without the :extensions entry there is no marker, so off fires on the second case only.
A mailer that configures its layout by calling put_layout gets the put removal at that
call instead — the same gap, owned once.
Written forms and arities
Only the real arities fire — both setters are /2, and render_body is /2,3 (/2 is the
use-injected wrapper's default-assigns form) — so a name-matched call of any other arity is
left alone, keeping every metamutant compiling. Piped setter calls are removed with the
Elixir.Function.identity() no-op stage.
Matches direct (Phoenix.Swoosh.put_layout(...)), aliased, and bare-imported calls. Unlike
:render_body, the setters' bare form needs no use-expansion override: they are genuine
Phoenix.Swoosh exports that the injected import Phoenix.Swoosh, except: [render_body: 3]
really does bring into scope, so Mutare's in-process use expansion resolves them on its
own. Bare render_body calls do need it, which is why this family declares the same
render_body routes :render_body does — from one internal routes helper, so the fact has a
single home. Identical declarations from two providers coalesce in the registry, so either
family alone still resolves and pins the call.
The layout argument is pinned
The layout value — {LayoutView, "email.html"}, {LayoutView, :email}, a bare name once a
layout view is set, or false — is structural configuration, not a computed value: a
perturbed template name inside the tuple is a missing-template crash at render (an
uninformative kill), and a flipped false is a put_layout-contract raise. Both setters'
argument 1 is routed :raw in the call-routing registry, which covers the position's
whole subtree (the string inside the tuple included) against every family — the reason a
route is used here rather than an argument mark, which pins only the argument's own node.
The same value written as a layout: assign is not pinned: it sits inside the assigns
map, which stays ordinary runtime data so core's value families can keep mutating the
template variables around it. Core will therefore perturb an assigns layout tuple into
missing-template crash-kills, as it would anywhere. Routing the whole assigns argument :raw
would cost every useful mutation inside it, and :interior (which spares only the map's own
collapse) does not reach a tuple nested in it. Named rather than hidden: at such a site,
# mutare:ignore is the tool.
Deliberately left alone
put_view/2andput_new_view/2are not mutated: removing them leaves the render with no view module in the standalone flow, andrender_bodythen raises ("a view module was not specified") — a crash-kill, not a signal. There is nothing to pin either: their argument is a module alias, which no value family mutates.layout/1is a getter with no seam.- Narrowing a layout name (
{LayoutView, :email}→{LayoutView, "email.html"}, so both body parts render inside the html layout) is the exact analogue of:render_body's template narrowing and probes a real gap — no test asserts the text part's wrapper. It is deferred rather than rejected: whether an html layout renders around a text body cleanly or raises insidePhoenix.Viewis a fact about the real library this package does not yet verify, and a mutant whose kill mode is unknown is not worth minting. The sites are also vanishingly rare (see "Why the render site" above). - Swapping
put_new_layoutforput_layout(and the same for the view setters) is the phoenix_swoosh analogue ofMutare.Mutators.MapKeyword'sput ↔ put_newlattice and would collide with nothing. It is not minted because the onlyput_new_layoutcall in a typical mailer is the one theusewrapper generates — macro-generated code, which Mutare does not mutate.