#!/usr/bin/env elixir # live_interaction_contracts — multi-version conformance harness (test target). # # One LiveView renders every browser-native machine under test (popover, dialog, # details, native select, focused input, scroll) plus shared "nearby" patch # sources (keyed list, stream), and exposes a single generic `bump` event so the # driver can patch any region's content / attribute / sibling in isolation. # # The LiveView version is selected by the LV_SPEC env var so the SAME app can run # against multiple versions: # LV_SPEC unset / "stable" -> {:phoenix_live_view, "~> 1.1"} # LV_SPEC "1.2.7" -> {:phoenix_live_view, "1.2.7"} # LV_SPEC "github:owner/repo#ref" -> git dependency at ref # LV_SPEC "path:/abs/path" -> local path dependency # # Run: LV_SPEC=1.2.7 elixir app.exs (http://127.0.0.1:4123) lv_dep = case System.get_env("LV_SPEC") do nil -> {:phoenix_live_view, "~> 1.1"} "" -> {:phoenix_live_view, "~> 1.1"} "stable" -> {:phoenix_live_view, "~> 1.1"} "path:" <> path -> {:phoenix_live_view, path: path} "github:" <> rest -> case String.split(rest, "#", parts: 2) do [repo, ref] -> {:phoenix_live_view, github: repo, ref: ref} [repo] -> {:phoenix_live_view, github: repo} end version -> {:phoenix_live_view, version} end Mix.install([ {:phoenix, "~> 1.8"}, lv_dep, {:bandit, "~> 1.7"}, {:jason, "~> 1.4"} ]) Application.put_env(:phoenix, :json_library, Jason) Application.put_env(:lic, LICWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: String.to_integer(System.get_env("PORT") || "4123")], server: true, adapter: Bandit.PhoenixAdapter, live_view: [signing_salt: "lic-harness-salt"], secret_key_base: String.duplicate("live-interaction-contracts!", 4), pubsub_server: LIC.PubSub, check_origin: false, debug_errors: true ) defmodule LICWeb.Layout do use Phoenix.Component @phoenix_js File.read!(Application.app_dir(:phoenix, "priv/static/phoenix.min.js")) @lv_js File.read!(Application.app_dir(:phoenix_live_view, "priv/static/phoenix_live_view.min.js")) def render("root.html", assigns) do assigns = assign(assigns, phoenix_js: @phoenix_js, lv_js: @lv_js) ~H""" live_interaction_contracts harness <%= @inner_content %> """ end end defmodule LICWeb.MainLive do use Phoenix.LiveView alias Phoenix.LiveView.JS def mount(_p, _s, socket) do {:ok, socket |> assign(bumps: %{}, order: ["i1", "i2", "i3", "i4"], p: "none", aha_rev: 0) |> stream(:sitems, for(i <- 1..3, do: %{id: "s#{i}", label: "S#{i}-v0"}))} end def handle_params(params, _uri, socket), do: {:noreply, assign(socket, p: params["p"] || "none")} defp b(assigns, k), do: Map.get(assigns.bumps, k, 0) def render(assigns) do ~H"""
<%!-- shared nearby patch sources --%> <%!-- ===== POPOVER (browser top-layer, no attr reflection) ===== --%>
pop-{b(assigns, "pop_content")} aha-pop-{@aha_rev}
sib-{b(assigns, "pop_sib")}
<%!-- ===== DIALOG (open reflected into `open` attribute) ===== --%> dlg-{b(assigns, "dlg_content")} aha-raw-{@aha_rev} <%!-- Same machine and patch as #dlg; only its browser-owned state surface is protected. --%> aha-protected-{@aha_rev}
sib-{b(assigns, "dlg_sib")}
<%!-- Focused causal trial: one acknowledged diff touches all three state holders. --%> <%!-- ===== DETAILS/SUMMARY (open reflected into `open` attribute) ===== --%>
summary
det-{b(assigns, "det_content")}
sib-{b(assigns, "det_sib")}
<%!-- ===== NATIVE SELECT (editing/selection state; popup not automatable) ===== --%>
sib-{b(assigns, "sel_sib")}
<%!-- ===== FOCUS / SELECTION / INPUT EDITING ===== --%> to_string(b(assigns, "inp_val"))} />
sib-{b(assigns, "inp_sib")}
<%!-- ===== SCROLL ===== --%>
row {i} c{b(assigns, "scr_content")}
sib-{b(assigns, "scr_sib")}
<%!-- ===== NAVIGATION ===== --%>
""" end def handle_event("bump", %{"k" => k}, s), do: {:noreply, update(s, :bumps, &Map.update(&1, k, 1, fn n -> n + 1 end))} def handle_event("aha_patch", _, s), do: {:noreply, update(s, :aha_rev, &(&1 + 1))} def handle_event("reorder", _, s), do: {:noreply, assign(s, order: Enum.reverse(s.assigns.order))} def handle_event("s_insert", _, s), do: {:noreply, stream_insert(s, :sitems, %{id: "sx#{System.unique_integer([:positive])}", label: "SX"}, at: 0)} def handle_event("s_reset", _, s), do: {:noreply, stream(s, :sitems, for(i <- 1..3, do: %{id: "s#{i}", label: "S#{i}-R"}), reset: true)} def handle_event("do_patch", _, s), do: {:noreply, push_patch(s, to: "/?p=patched")} def handle_event("do_nav", _, s), do: {:noreply, push_navigate(s, to: "/other")} end defmodule LICWeb.OtherLive do use Phoenix.LiveView def mount(_p, _s, socket), do: {:ok, socket} def render(assigns), do: ~H"""
other
""" def handle_event("back", _, s), do: {:noreply, push_navigate(s, to: "/")} end defmodule LICWeb.Router do use Phoenix.Router import Phoenix.LiveView.Router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :protect_from_forgery plug :put_root_layout, html: {LICWeb.Layout, :root} end scope "/" do pipe_through :browser live "/", LICWeb.MainLive live "/other", LICWeb.OtherLive end end defmodule LICWeb.Endpoint do use Phoenix.Endpoint, otp_app: :lic @session_options [store: :cookie, key: "_lic_key", signing_salt: "lic", same_site: "Lax"] socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] plug Plug.Session, @session_options plug LICWeb.Router end {:ok, _} = Supervisor.start_link([{Phoenix.PubSub, name: LIC.PubSub}, LICWeb.Endpoint], strategy: :one_for_one) versions = %{ phoenix: to_string(Application.spec(:phoenix, :vsn)), live_view: to_string(Application.spec(:phoenix_live_view, :vsn)), elixir: System.version(), otp: System.otp_release(), lv_spec: System.get_env("LV_SPEC") || "stable" } File.write!(System.get_env("VERSIONS_OUT") || "versions.json", Jason.encode!(versions)) IO.puts("VERSIONS " <> Jason.encode!(versions)) IO.puts("READY http://127.0.0.1:#{System.get_env("PORT") || "4123"}") Process.sleep(:infinity)