diff --git a/lib/live_view_native/template/engine.ex b/lib/live_view_native/template/engine.ex index df302ce..33a8970 100644 --- a/lib/live_view_native/template/engine.ex +++ b/lib/live_view_native/template/engine.ex @@ -37,6 +37,10 @@ defmodule LiveViewNative.Template.Engine do @impl true defdelegate annotate_caller(file, line), to: Phoenix.LiveView.HTMLEngine + @doc false + @impl true + defdelegate annotate_slot(name, tag_meta, close_meta, caller), to: Phoenix.LiveView.HTMLEngine + @doc false @impl true def classify_type(":inner_block"), do: {:error, "the slot name :inner_block is reserved"} diff --git a/lib/live_view_native/test/view_tree.ex b/lib/live_view_native/test/view_tree.ex index 02276dc..87ae5db 100644 --- a/lib/live_view_native/test/view_tree.ex +++ b/lib/live_view_native/test/view_tree.ex @@ -4,6 +4,9 @@ defmodule LiveViewNativeTest.ViewTree do @phx_component "data-phx-component" @static :s @components :c + @template :p + @keyed :k + @keyed_count :kc @stream_id :stream def ensure_loaded! do @@ -284,6 +287,38 @@ defmodule LiveViewNativeTest.ViewTree do update_in(rendered[@components], &Map.drop(&1, cids)) end + # We resolve any templates when merging, because subsequent patches can + # contain more templates that are not compatible with previous diffs. + # This prevents template position collisions from breaking rendering. + defp deep_merge_diff(target, %{@template => template} = source), + do: deep_merge_diff(target, resolve_templates(Map.delete(source, @template), template)) + + defp deep_merge_diff(target, %{@keyed => source_keyed} = source) when is_map(target) do + target_keyed = target[@keyed] + + merged_keyed = + case source_keyed[@keyed_count] do + 0 -> + %{@keyed_count => 0} + + count -> + for pos <- 0..(count - 1), into: %{@keyed_count => count} do + value = + case source_keyed[pos] do + nil -> target_keyed[pos] + value when is_number(value) -> target_keyed[value] + value when is_map(value) -> deep_merge_diff(target_keyed[pos], value) + [old_pos, value] -> deep_merge_diff(target_keyed[old_pos], value) + end + + {pos, value} + end + end + + merged = deep_merge_diff(Map.delete(target, @keyed), Map.delete(source, @keyed)) + Map.put(merged, @keyed, merged_keyed) + end + defp deep_merge_diff(_target, %{@static => _} = source), do: source @@ -293,6 +328,42 @@ defmodule LiveViewNativeTest.ViewTree do defp deep_merge_diff(_target, source), do: source + # Template resolution helpers - convert template position references to literal static parts + defp resolve_templates(%{@template => template} = rendered, nil) do + resolve_templates(Map.delete(rendered, @template), template) + end + + defp resolve_templates(%{@static => static} = rendered, template) when is_integer(static) do + resolve_templates(Map.put(rendered, @static, Map.fetch!(template, static)), template) + end + + defp resolve_templates(%{@keyed => keyed} = rendered, template) do + keyed = + case keyed[@keyed_count] do + 0 -> + keyed + + count -> + for pos <- 0..(count - 1), reduce: keyed do + acc -> + case keyed[pos] do + nil -> acc + value -> Map.put(acc, pos, resolve_templates(value, template)) + end + end + end + + rendered + |> Map.put(@keyed, keyed) + |> Map.delete(@template) + end + + defp resolve_templates(rendered, template) when is_map(rendered) and not is_struct(rendered) do + Map.new(rendered, fn {k, v} -> {k, resolve_templates(v, template)} end) + end + + defp resolve_templates(other, _template), do: other + def extract_streams(%{} = source, streams) when not is_struct(source) do Enum.reduce(source, streams, fn {@stream_id, stream}, acc -> [stream | acc] diff --git a/lib/live_view_native_test.ex b/lib/live_view_native_test.ex index 3dbf90b..7e4bd2e 100644 --- a/lib/live_view_native_test.ex +++ b/lib/live_view_native_test.ex @@ -547,7 +547,7 @@ defmodule LiveViewNativeTest do end defp rendered_to_diff_string(rendered, socket) do - {_, diff, _} = Diff.render(socket, rendered, Diff.new_components()) + {diff, _, _} = Diff.render(socket, rendered, Diff.new_fingerprints(), Diff.new_components()) diff |> Diff.to_iodata() |> IO.iodata_to_binary() end diff --git a/mix.exs b/mix.exs index 9c17cce..e929044 100644 --- a/mix.exs +++ b/mix.exs @@ -30,14 +30,15 @@ defmodule LiveViewNative.MixProject do defp deps do [ - {:phoenix, "~> 1.7.21"}, + {:phoenix, "~> 1.8.0"}, {:phoenix_view, "~> 2.0"}, - {:phoenix_live_view, "~> 1.0.18"}, + {:phoenix_live_view, "~> 1.1.0"}, {:phoenix_live_reload, "~> 1.4", only: :test}, {:phoenix_template, "~> 1.0.4"}, {:phoenix_html, "~> 3.3 or ~> 4.0 or ~> 4.1"}, {:decimal, "~> 2.3", only: :test}, {:floki, ">= 0.30.0", only: :test}, + {:lazy_html, ">= 0.1.0", only: :test}, {:gettext, "~> 0.24", only: :test}, {:plug, "~> 1.15"}, {:jason, "~> 1.2"}, diff --git a/mix.lock b/mix.lock index 1073506..bef1294 100644 --- a/mix.lock +++ b/mix.lock @@ -1,17 +1,21 @@ %{ "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, - "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, + "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, + "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, "floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "live_view_native_test_endpoint": {:git, "https://github.com/liveview-native/live_view_native_test_endpoint.git", "be09319cc2def0e93a2aba79d8db7ba989560afa", [branch: "main"]}, + "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, + "live_view_native_test_endpoint": {:git, "https://github.com/liveview-native/live_view_native_test_endpoint.git", "377546781ffc9fdf7588f921f3380480b77a6997", [branch: "main"]}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_eex": {:hex, :makeup_eex, "1.0.0", "436d4c00204c250b17a775d64e197798aaf374627e6a4f2d3fd3074a8db61db4", [:mix], [{:makeup, "~> 1.2.1 or ~> 1.3", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0 or ~> 1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3bb699bc519e4f509f1bf8a2e0ba0e08429edf3580053cd31a4f9c1bc5da86c8"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, @@ -19,20 +23,20 @@ "makeup_html": {:hex, :makeup_html, "0.1.2", "19d4050c0978a4f1618ffe43054c0049f91fe5feeb9ae8d845b5dc79c6008ae5", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b7fb9afedd617d167e6644a0430e49c1279764bfd3153da716d4d2459b0998c5"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, + "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.18", "943431edd0ef8295ffe4949f0897e2cb25c47d3d7ebba2b008d7c68598b887f1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "724934fd0a68ecc57281cee863674454b06163fed7f5b8005b5e201ba4b23316"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.14", "cae84abc4cd00dde4bb200b8516db556704c585c267aff9cd4955ff83cceb86c", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b827980e2bc00fddd8674e3b567519a4e855b5de04bf8607140414f1101e2627"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, - "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, + "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.8.0", "07789e9c03539ee51bb14a07839cc95aa96999fd8846ebfd28c97f0b50c7b612", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9cbfaaf17463334ca31aed38ea7e08a68ee37cabc077b1e9be6d2fb68e0171d0"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, } diff --git a/test/live_view_native/test/diff_test.exs b/test/live_view_native/test/diff_test.exs new file mode 100644 index 0000000..6363120 --- /dev/null +++ b/test/live_view_native/test/diff_test.exs @@ -0,0 +1,109 @@ +defmodule LiveViewNative.DiffTest do + use ExUnit.Case, async: true + + alias LiveViewNativeTest.ViewTree + + defstruct [:foo] + + describe "merge_diff" do + test "merges unless static" do + assert ViewTree.merge_diff(%{0 => "bar", s: "foo"}, %{0 => "baz"}) == + %{0 => "baz", s: "foo", streams: []} + + assert ViewTree.merge_diff(%{s: "foo", d: []}, %{s: "bar"}) == + %{s: "bar", streams: []} + end + + test "resolves moved comprehensions" do + base = %{ + k: %{ + 0 => %{0 => "A"}, + 1 => %{0 => "B"}, + 2 => %{0 => "C", 1 => %{0 => "var1", :s => ["", ""]}}, + kc: 3 + } + } + + diff = %{ + k: %{ + 0 => 1, + 1 => [2, %{1 => %{0 => "var2"}}], + kc: 2 + } + } + + result = %{ + k: %{ + 0 => %{0 => "B"}, + 1 => %{0 => "C", 1 => %{0 => "var2", :s => ["", ""]}}, + kc: 2 + }, + streams: [] + } + + assert ViewTree.merge_diff(base, diff) == result + end + + test "no warning when keyed count is 0" do + base = %{ + k: %{ + 0 => %{0 => "A"}, + 1 => %{0 => "B"}, + 2 => %{0 => "C", 1 => %{0 => "var1", :s => ["", ""]}}, + :kc => 3 + } + } + + diff = %{ + k: %{kc: 0} + } + + result = %{ + k: %{kc: 0}, + streams: [] + } + + assert ViewTree.merge_diff(base, diff) == result + end + + test "ignores structs when resolving templates" do + assert ViewTree.merge_diff(%{0 => %{}}, %{ + 0 => %{:s => 1, 0 => %__MODULE__{foo: :bar}}, + :p => %{1 => ["foo", "bar"]} + }) == %{0 => %{0 => %__MODULE__{foo: :bar}, :s => ["foo", "bar"]}, :streams => []} + end + + test "copies streams" do + base = %{ + k: %{ + 0 => %{0 => "A"}, + 1 => %{0 => "B"}, + 2 => %{0 => "C", 1 => %{0 => "var1", :s => ["", ""]}}, + kc: 3 + }, + stream: "foo" + } + + diff = %{ + k: %{ + 0 => 1, + 1 => [2, %{1 => %{0 => "var2"}}], + kc: 2 + }, + stream: "bar" + } + + result = %{ + k: %{ + 0 => %{0 => "B"}, + 1 => %{0 => "C", 1 => %{0 => "var2", :s => ["", ""]}}, + kc: 2 + }, + stream: "bar", + streams: ["bar"] + } + + assert ViewTree.merge_diff(base, diff) == result + end + end +end diff --git a/test/live_view_native/test/view_tree_test.exs b/test/live_view_native/test/view_tree_test.exs index aaa901c..7446165 100644 --- a/test/live_view_native/test/view_tree_test.exs +++ b/test/live_view_native/test/view_tree_test.exs @@ -1,6 +1,8 @@ defmodule LiveViewNative.ViewTreeTest do use ExUnit.Case, async: true + import LiveViewNativeTest.ViewTree, only: [sigil_X: 2, sigil_x: 2] + alias LiveViewNativeTest.ViewTree describe "find_live_views" do @@ -8,20 +10,18 @@ defmodule LiveViewNative.ViewTreeTest do @too_big_session Enum.map_join(1..4432, fn _ -> "t" end) test "finds views given markup" do - assert ViewTree.find_live_views( - ViewTree.parse(""" - top - - - - bottom - """) - ) == [ + assert ViewTree.find_live_views(~x""" + top + + + + bottom + """) == [ {"phx-123", "SESSION1", nil}, {"phx-456", "SESSION2", "STATIC2"}, {"phx-458", @too_big_session, nil} @@ -31,21 +31,19 @@ defmodule LiveViewNative.ViewTreeTest do end test "returns main live view as first result" do - assert ViewTree.find_live_views( - ViewTree.parse(""" - top - - - - bottom - """) - ) == [ + assert ViewTree.find_live_views(~X""" + top + + + + bottom + """) == [ {"phx-458", "SESSIONMAIN", nil}, {"phx-123", "SESSION1", nil}, {"phx-456", "SESSION2", "STATIC2"} @@ -56,35 +54,39 @@ defmodule LiveViewNative.ViewTreeTest do describe "replace_root_container" do test "replaces tag name and merges attributes" do container = - ViewTree.parse(""" + ~X""" contents - """) + """ + + result = ViewTree.replace_root_container(container, :Span, %{class: "new"}) + [{tag, attrs, children}] = result + normalized = [{tag, Enum.sort(attrs), children}] - assert ViewTree.replace_root_container(container, :Span, %{class: "new"}) == + assert normalized == [ {"Span", [ - {"id", "container"}, + {"class", "new"}, {"data-phx-main", "true"}, {"data-phx-session", "session"}, {"data-phx-static", "static"}, - {"class", "new"} + {"id", "container"} ], ["contents"]} ] end test "does not overwrite reserved attributes" do container = - ViewTree.parse(""" + ~X""" contents - """) + """ new_attrs = %{ "id" => "new", @@ -93,203 +95,20 @@ defmodule LiveViewNative.ViewTreeTest do "data-phx-main" => "new" } - assert ViewTree.replace_root_container(container, :Group, new_attrs) == + result = ViewTree.replace_root_container(container, :Group, new_attrs) + [{tag, attrs, children}] = result + normalized = [{tag, Enum.sort(attrs), children}] + + assert normalized == [ {"Group", [ - {"id", "container"}, {"data-phx-main", "true"}, {"data-phx-session", "session"}, - {"data-phx-static", "static"} + {"data-phx-static", "static"}, + {"id", "container"} ], ["contents"]} ] end end - - describe "patch_id" do - test "updates deeply nested markup" do - lvn = """ - - Hello - - a - a - a - - - """ - - inner_markup = """ - Hello World - - a - - inner - - a - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - refute new_markup =~ ~S(a) - assert new_markup =~ ~S(a) - assert new_markup =~ ~S(inner) - assert new_markup =~ ~S(a) - end - - test "inserts new elements when phx-update=append" do - lvn = """ - - - a - a - a - - - """ - - inner_markup = """ - - a - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - assert new_markup =~ ~S(a) - assert new_markup =~ ~S(a) - assert new_markup =~ ~S(aa) - end - - test "inserts new elements when phx-update=prepend" do - lvn = """ - - - a - a - a - - - """ - - inner_markup = """ - - a - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - assert new_markup =~ ~S(aa) - assert new_markup =~ ~S(a) - assert new_markup =~ ~S(a) - end - - test "updates existing elements when phx-update=append" do - lvn = """ - - - a - a - a - - - """ - - inner_markup = """ - - b - b - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - assert new_markup =~ ~S(b) - assert new_markup =~ ~S(b) - assert new_markup =~ ~S(a) - end - - test "patches only container data attributes when phx-update=ignore" do - lvn = """ - - - a - - - """ - - inner_markup = """ - - b - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - assert new_markup =~ ~S( remove) - refute new_markup =~ ~S( data-remove) - assert new_markup =~ ~S( update="a") - assert new_markup =~ ~S( data-update="b") - refute new_markup =~ ~S( add) - assert new_markup =~ ~S( data-add) - assert new_markup =~ ~S(a) - end - - test "patches elements with special characters in id (issue #3144)" do - lvn = """ - - - a - - - """ - - inner_markup = """ - - b - - """ - - {new_markup, _removed_cids} = - ViewTree.patch_id("phx-458", ViewTree.parse(lvn), ViewTree.parse(inner_markup), []) - - new_markup = ViewTree.to_markup(new_markup) - - assert new_markup =~ ~S(data-attr="b") - assert new_markup =~ ~S(a) - end - end - - describe "merge_diff" do - test "merges unless static" do - assert ViewTree.merge_diff(%{0 => "bar", s: "foo"}, %{0 => "baz"}) == - %{0 => "baz", s: "foo", streams: []} - - assert ViewTree.merge_diff(%{s: "foo", d: []}, %{s: "bar"}) == - %{s: "bar", streams: []} - end - end end