Skip to content

Commit

Permalink
Live reloading LiveViews without a hard refresh (#2323)
Browse files Browse the repository at this point in the history
* render diffs without full page reload

* accepts multiple topics-patterns

* fix tests, add live reload test

* subscribe just to live_view topic

* Update mix.exs

Co-authored-by: Chris McCord <chris@chrismccord.com>
  • Loading branch information
bemesa21 and chrismccord committed Nov 29, 2022
1 parent 62c02b0 commit de1a2cd
Show file tree
Hide file tree
Showing 23 changed files with 249 additions and 22 deletions.
24 changes: 24 additions & 0 deletions lib/phoenix_live_view/channel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ defmodule Phoenix.LiveView.Channel do
handle_redirect(state, command, flash, nil)
end

def handle_info({:phoenix_live_reload, _topic, _changed_file}, %{socket: socket} = state) do
Phoenix.CodeReloader.reload(socket.endpoint)

new_socket =
Enum.reduce(socket.assigns, socket, fn {key, val}, socket ->
Utils.force_assign(socket, key, val)
end)

handle_changed(state, new_socket, nil)
end

def handle_info(msg, %{socket: socket} = state) do
msg
|> view_handle_info(socket)
Expand Down Expand Up @@ -1008,6 +1019,7 @@ defmodule Phoenix.LiveView.Channel do
|> build_state(phx_socket)
|> maybe_call_mount_handle_params(router, url, params)
|> reply_mount(from, verified, route)
|> maybe_subscribe_to_live_reload()

{:error, :noproc} ->
GenServer.reply(from, {:error, %{reason: "stale"}})
Expand Down Expand Up @@ -1308,4 +1320,16 @@ defmodule Phoenix.LiveView.Channel do
_ -> nil
end
end

defp maybe_subscribe_to_live_reload({:noreply, state}) do
live_reload_config = state.socket.endpoint.config(:live_reload)

if live_reload_config[:notify][:live_view] do
state.socket.endpoint.subscribe("live_view")
end

{:noreply, state}
end

defp maybe_subscribe_to_live_reload(response), do: response
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ defmodule Phoenix.LiveView.MixProject do
{:floki, "~> 0.30.0", only: :test},
{:ex_doc, "~> 0.28", only: :docs},
{:makeup_eex, ">= 0.1.1", only: :docs},
{:html_entities, ">= 0.0.0", only: :test}
{:html_entities, ">= 0.0.0", only: :test},
{:phoenix_live_reload, "~> 1.4.1", only: :test}
]
end

Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/assigns_test.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
defmodule Phoenix.LiveView.AssignsTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Plug.Conn
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.Endpoint
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup do
{:ok, conn: Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), %{})}
end
Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/collocated_test.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
defmodule Phoenix.LiveView.CollocatedTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint, CollocatedLive, CollocatedComponent}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

test "supports collocated views" do
{:ok, view, html} = live_isolated(build_conn(), CollocatedLive)
assert html =~ "Hello collocated world from live!\n</div>"
Expand Down
10 changes: 8 additions & 2 deletions test/phoenix_live_view/integrations/connect_test.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
defmodule Phoenix.LiveView.ConnectTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.LiveViewTest
import Phoenix.ConnTest
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Phoenix.LiveViewTest.Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

describe "connect_params" do
test "can be read on mount" do
{:ok, live, _html} =
Expand Down Expand Up @@ -40,4 +46,4 @@ defmodule Phoenix.LiveView.ConnectTest do
assert_html.(render(live))
end
end
end
end
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/elements_test.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
defmodule Phoenix.LiveView.ElementsTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

Expand All @@ -15,6 +16,11 @@ defmodule Phoenix.LiveView.ElementsTest do
view |> element("#component-last-event") |> render() |> HtmlEntities.decode()
end

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup do
conn = Phoenix.ConnTest.build_conn()
{:ok, live, _} = live(conn, "/elements")
Expand Down
5 changes: 3 additions & 2 deletions test/phoenix_live_view/integrations/event_test.exs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
defmodule Phoenix.LiveView.EventTest do
use ExUnit.Case
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.{Component, LiveView}
alias Phoenix.LiveViewTest.{Endpoint}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
ExUnit.CaptureLog.capture_log(fn -> Endpoint.start_link() end)
Helpers.start_endpoint(@endpoint)
:ok
end

Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/flash_test.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
defmodule Phoenix.LiveView.FlashIntegrationTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveView
alias Phoenix.LiveViewTest.{Endpoint, Router}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup do
conn =
Phoenix.ConnTest.build_conn(:get, "http://www.example.com/", nil)
Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/hooks_test.exs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
defmodule Phoenix.LiveView.HooksTest do
use ExUnit.Case
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.Component
alias Phoenix.LiveViewTest.{Endpoint, HooksLive}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup do
{:ok, conn: Plug.Test.init_test_session(build_conn(), %{})}
end
Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/layout_test.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
defmodule Phoenix.LiveView.LayoutTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint, LayoutView}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok,
conn: Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), config[:session] || %{})}
Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/live_components_test.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
defmodule Phoenix.LiveView.LiveComponentsTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint, DOM, StatefulComponent}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint
@moduletag session: %{names: ["chris", "jose"], from: nil}

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok,
conn: Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), config[:session] || %{})}
Expand Down
45 changes: 45 additions & 0 deletions test/phoenix_live_view/integrations/live_reload_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule Phoenix.LiveView.LiveReloadTest do
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.ChannelTest
import Phoenix.LiveViewTest

alias Phoenix.LiveReloader
alias Phoenix.LiveReloader.Channel
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Helpers.Endpoint
@pubsub Phoenix.LiveView.PubSub

setup config do
ExUnit.CaptureLog.capture_log(fn ->
start_supervised!(@endpoint)

{:ok, _} =
Supervisor.start_link([Phoenix.PubSub.child_spec(name: @pubsub)],
strategy: :one_for_one
)
end)

{:ok, _, socket} =
LiveReloader.Socket |> socket() |> subscribe_and_join(Channel, "phoenix:live_reload", %{})
conn = Plug.Test.init_test_session(build_conn(), config[:session] || %{})
{:ok, conn: conn, socket: socket}
end

test "LiveView renders again when the phoenix_live_reload is received", %{conn: conn, socket: socket} do
Phoenix.PubSub.subscribe(@pubsub, "live_view")

Application.put_env(:phoenix_live_view, :vsn, 1)
{:ok, lv, _html} = live(conn, "/live-reload")
assert render(lv) =~ "<div>Version 1</div>"

send(socket.channel_pid, {:file_event, self(), {"lib/test_auth_web/live/user_live.ex", :created}})
Application.put_env(:phoenix_live_view, :vsn, 2)

assert_receive {:phoenix_live_reload, :live_view, "lib/test_auth_web/live/user_live.ex"}
assert render(lv) =~ "<div>Version 2</div>"
end

end
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/live_view_test.exs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
defmodule Phoenix.LiveView.LiveViewTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.HTML
alias Phoenix.LiveView
alias Phoenix.LiveViewTest.{Endpoint, DOM}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
end
Expand Down
6 changes: 4 additions & 2 deletions test/phoenix_live_view/integrations/navigation_test.exs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
defmodule Phoenix.LiveView.NavigationTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.LiveViewTest.{Endpoint, DOM}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup do
{:ok, conn: Plug.Test.init_test_session(build_conn(), %{})}
end

setup_all do
Endpoint.start_link()
Helpers.start_endpoint(@endpoint)
:ok
end

Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/nested_test.exs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
defmodule Phoenix.LiveView.NestedTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false

import Plug.Conn
import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.LiveView
alias Phoenix.LiveViewTest.{Endpoint, DOM, ClockLive, ClockControlsLive, LiveInComponent}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
end
Expand Down
6 changes: 6 additions & 0 deletions test/phoenix_live_view/integrations/telemetry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ defmodule Phoenix.LiveView.TelemtryTest do

alias Phoenix.LiveView.Socket
alias Phoenix.LiveViewTest.Endpoint
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint
@moduletag session: %{names: ["chris", "jose"], from: nil}

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
end
Expand Down
8 changes: 7 additions & 1 deletion test/phoenix_live_view/integrations/update_test.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
defmodule Phoenix.LiveView.UpdateTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import Phoenix.ConnTest

import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint, DOM}
alias Phoenix.LiveView.LiveReloadTestHelpers, as: Helpers

@endpoint Endpoint

setup_all do
Helpers.start_endpoint(@endpoint)
:ok
end

setup config do
{:ok,
conn: Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), config[:session] || %{})}
Expand Down
Loading

0 comments on commit de1a2cd

Please sign in to comment.