Packages

A real-browser external-app verifier for patch-safe Phoenix LiveView interactions, with secondary unstyled reference components.

Current section

Files

Jump to
live_interaction_contracts lib mix tasks live_interaction_contracts.compare.ex
Raw

lib/mix/tasks/live_interaction_contracts.compare.ex

defmodule Mix.Tasks.LiveInteractionContracts.Compare do
@shortdoc "Diff two harness result sets (e.g. before/after a LiveView upgrade)"
@moduledoc """
The version-upgrade fuse. Runs the harness against two LiveView versions and
reports which browser-native machines changed patch-safety behavior — so an
interaction-heavy app can see, before merging a LiveView bump, exactly which
delegated machines drifted.
# produce two result sets:
mix live_interaction_contracts.test --suite harness --lv 1.2.5 # writes priv/harness/results.json
cp priv/harness/results.json /tmp/a.json
mix live_interaction_contracts.test --suite harness --lv "github:phoenixframework/phoenix_live_view#main"
cp priv/harness/results.json /tmp/b.json
# diff them:
mix live_interaction_contracts.compare --a /tmp/a.json --b /tmp/b.json
Exits non-zero if any machine's survival changed (a drift is a merge-blocking
signal). "No drift" = the observed behavior the contracts rely on is unchanged.
"""
use Mix.Task
@nondestructive ~w(siblingPatch contentPatch attributePatch reorderNearby streamNearby pushPatch reconnect)
@impl true
def run(argv) do
{opts, _, _} = OptionParser.parse(argv, strict: [a: :string, b: :string])
a = load(opts[:a] || Mix.raise("--a required"))
b = load(opts[:b] || Mix.raise("--b required"))
va = get_in(a, ["versions", "live_view"]) || "?"
vb = get_in(b, ["versions", "live_view"]) || "?"
engines = Map.keys(a["engines"]) |> Enum.filter(&Map.has_key?(b["engines"], &1))
diffs =
for e <- engines,
machine <- machine_keys(a, e),
patch <- @nondestructive,
sa = survived(machine, cell(a, e, machine, patch)),
sb = survived(machine, cell(b, e, machine, patch)),
sa != sb do
" #{e} · #{machine} · #{patch}: #{va}=#{surv(sa)}#{vb}=#{surv(sb)}"
end
Mix.shell().info("== compare LiveView #{va}#{vb} (engines: #{Enum.join(engines, ", ")})")
if diffs == [] do
Mix.shell().info("✓ no drift — every tested machine has identical patch-safety on both versions")
else
Mix.shell().error("DRIFT DETECTED (#{length(diffs)}):\n" <> Enum.join(diffs, "\n"))
Mix.raise("interaction behavior drifted between #{va} and #{vb} — review before upgrading")
end
end
defp load(path), do: File.read!(path) |> JSON.decode!()
defp machine_keys(data, e), do: (data["engines"][e]["runs"] |> hd() |> Map.keys()) -- ["ime"]
defp cell(data, e, machine, patch), do: get_in(data, ["engines", e, "runs", Access.at(0), machine, "patches", patch, "after"])
defp survived(_m, nil), do: nil
defp survived("pop", a), do: a["open"] == true
defp survived("dlg", a), do: a["open"] == true
defp survived("det", a), do: a["open"] == true
defp survived("sel", a), do: a["value"] == "b"
defp survived("inp", a), do: a["value"] == "typed-value" and a["focused"] == true
defp survived("scr", a), do: a["scrollTop"] == 500
defp survived(_, _), do: nil
defp surv(true), do: "green"
defp surv(false), do: "red"
defp surv(nil), do: "n/a"
end