diff --git a/lib/beacon/boot.ex b/lib/beacon/boot.ex index 208fbc33..0db0cf0c 100644 --- a/lib/beacon/boot.ex +++ b/lib/beacon/boot.ex @@ -56,6 +56,7 @@ defmodule Beacon.Boot do Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_layouts_modules(config.site) end), Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_error_page_module(config.site) end), Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_pages_modules(config.site, per_page: 20) end), + Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_info_handlers_module(config.site) end), Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_event_handlers_module(config.site) end) # TODO: load main pages (order_by: path, per_page: 10) to avoid SEO issues ] diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 20ef70ca..3a7312a5 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -35,6 +35,7 @@ defmodule Beacon.Content do alias Beacon.Content.ComponentSlot alias Beacon.Content.ComponentSlotAttr alias Beacon.Content.ErrorPage + alias Beacon.Content.InfoHandler alias Beacon.Content.EventHandler alias Beacon.Content.Layout alias Beacon.Content.LayoutEvent @@ -3931,6 +3932,147 @@ defmodule Beacon.Content do repo(site).delete(live_data_assign) end + @doc """ + Creates a new info handler for creating shared handle_info callbacks. + + ## Example + + iex> create_info_handler(%{site: "my_site", msg: "{:new_msg, arg}", code: "{:noreply, socket}"}) + {:ok, %InfoHandler{}} + + """ + @doc type: :info_handlers + @spec create_info_handler(map()) :: {:ok, InfoHandler.t()} | {:error, Changeset.t()} + def create_info_handler(attrs) do + changeset = InfoHandler.changeset(%InfoHandler{}, attrs) + site = Changeset.get_field(changeset, :site) + + changeset + |> validate_info_handler() + |> repo(site).insert() + |> tap(&maybe_broadcast_updated_content_event(&1, :info_handler)) + end + + @spec validate_info_handler(Changeset.t(), [String.t()]) :: Changeset.t() + defp validate_info_handler(changeset, imports \\ []) do + code = Changeset.get_field(changeset, :code) + msg = Changeset.get_field(changeset, :msg) + site = Changeset.get_field(changeset, :site) + metadata = %Beacon.Template.LoadMetadata{site: site} + var = ["socket", msg] + imports = ["Phoenix.LiveView"] ++ imports + + do_validate_template(changeset, :code, :elixir, code, metadata, var, imports) + end + + @doc """ + Creates a info handler, raising an error if unsuccessful. + + Returns the new info handler if successful, otherwise raises a `RuntimeError`. + + ## Example + + iex> create_info_handler!(%{site: "my_site", msg: "{:new_msg, arg}", code: "{:noreply, socket}"}) + %InfoHandler{} + """ + @doc type: :info_handlers + @spec create_info_handler!(map()) :: InfoHandler.t() + def create_info_handler!(attrs \\ %{}) do + case create_info_handler(attrs) do + {:ok, info_handler} -> info_handler + {:error, changeset} -> raise "failed to create info handler, got: #{inspect(changeset.errors)}" + end + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking info handler changes. + + ## Example + + iex> change_info_handler(info_handler, %{code: {:noreply, socket}}) + %Ecto.Changeset{data: %InfoHandler{}} + + """ + @doc type: :info_handlers + @spec change_info_handler(InfoHandler.t(), map()) :: Changeset.t() + def change_info_handler(%InfoHandler{} = info_handler, attrs \\ %{}) do + InfoHandler.changeset(info_handler, attrs) + end + + @doc """ + Gets a single info handler by `id`. + + ## Example + + iex> get_single_info_handler(:my_site, "fefebbfe-f732-4119-9116-d031d04f5a2c") + %InfoHandler{} + + """ + @doc type: :info_handlers + @spec get_info_handler(Site.t(), UUID.t()) :: InfoHandler.t() | nil + def get_info_handler(site, id) when is_atom(site) and is_binary(id) do + repo(site).get(InfoHandler, id) + end + + @doc """ + Same as `get_info_handler/2` but raises an error if no result is found. + """ + @doc type: :info_handlers + @spec get_info_handler!(Site.t(), UUID.t()) :: InfoHandler.t() + def get_info_handler!(site, id) when is_atom(site) and is_binary(id) do + repo(site).get!(InfoHandler, id) + end + + @doc """ + Lists all info handlers for a given site. + + ## Example + + iex> list_info_handlers() + + """ + @doc type: :info_handlers + @spec list_info_handlers(Site.t()) :: [InfoHandler.t()] + def list_info_handlers(site) do + repo(site).all( + from h in InfoHandler, + where: h.site == ^site, + order_by: [asc: h.inserted_at] + ) + end + + @doc """ + Updates a info handler. + + ## Example + + iex> update_info_handler(info_handler, %{msg: "{:new_msg, arg}"}) + {:ok, %InfoHandler{}} + + """ + @doc type: :info_handlers + @spec update_info_handler(InfoHandler.t(), map()) :: {:ok, InfoHandler.t()} + def update_info_handler(%InfoHandler{} = info_handler, attrs) do + changeset = InfoHandler.changeset(info_handler, attrs) + site = Changeset.get_field(changeset, :site) + + changeset + |> validate_info_handler(["Phoenix.Component"]) + |> repo(site).update() + |> tap(&maybe_broadcast_updated_content_event(&1, :info_handler)) + end + + @doc """ + Deletes info handler. + """ + @doc type: :info_handlers + @spec delete_info_handler(InfoHandler.t()) :: {:ok, InfoHandler.t()} | {:error, Changeset.t()} + def delete_info_handler(info_handler) do + info_handler + |> repo(info_handler).delete() + |> tap(&maybe_broadcast_updated_content_event(&1, :info_handler)) + end + ## Utils defp do_validate_template(changeset, field, format, template, metadata, vars \\ [], imports \\ []) diff --git a/lib/beacon/content/info_handler.ex b/lib/beacon/content/info_handler.ex new file mode 100644 index 00000000..2627c424 --- /dev/null +++ b/lib/beacon/content/info_handler.ex @@ -0,0 +1,46 @@ +defmodule Beacon.Content.InfoHandler do + @moduledoc """ + Beacon's representation of a LiveView [handle_info/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_info/2) + that applies to all of a site's pages. + + This is the Elixir code which will handle messages from other Elixir processes. + + > #### Do not create or edit info handlers manually {: .warning} + > + > Use the public functions in `Beacon.Content` instead. + > The functions in that module guarantee that all dependencies + > are created correctly and all processes are updated. + > Manipulating data manually will most likely result + > in inconsistent behavior and crashes. + """ + + use Beacon.Schema + + import Ecto.Changeset + + @type t :: %__MODULE__{ + id: Ecto.UUID.t(), + site: Beacon.Types.Site.t(), + msg: binary(), + code: binary(), + inserted_at: DateTime.t(), + updated_at: DateTime.t() + } + + schema "beacon_info_handlers" do + field :site, Beacon.Types.Site + field :msg, :string + field :code, :string + + timestamps() + end + + @doc false + def changeset(%__MODULE__{} = info_handler, attrs) do + fields = ~w(site msg code)a + + info_handler + |> cast(attrs, fields) + |> validate_required(fields) + end +end diff --git a/lib/beacon/loader.ex b/lib/beacon/loader.ex index dd019bed..32cbe7fb 100644 --- a/lib/beacon/loader.ex +++ b/lib/beacon/loader.ex @@ -165,6 +165,10 @@ defmodule Beacon.Loader do Loader.Page.module_name(site, page_id) end + def fetch_info_handlers_module(site) do + Loader.InfoHandlers.module_name(site) + end + def maybe_reload_page_module(site, page_id) do maybe_reload(Loader.Page.module_name(site, page_id), fn -> reload_page_module(site, page_id) end) end @@ -218,6 +222,10 @@ defmodule Beacon.Loader do GenServer.call(worker(site), {:unload_page_module, page_id}, @timeout) end + def reload_info_handlers_module(site) do + GenServer.call(worker(site), :reload_info_handlers_module, @timeout) + end + defp maybe_reload(module, reload_fun) do if :erlang.module_loaded(module) do {:ok, module} @@ -296,6 +304,11 @@ defmodule Beacon.Loader do {:noreply, config} end + def handle_info({:content_updated, :info_handler, %{site: site}}, config) do + reload_info_handlers_module(site) + {:noreply, config} + end + def handle_info({:content_updated, :event_handler, %{site: site}}, config) do reload_event_handlers_module(site) {:noreply, config} diff --git a/lib/beacon/loader/info_handlers.ex b/lib/beacon/loader/info_handlers.ex new file mode 100644 index 00000000..6e6a23ce --- /dev/null +++ b/lib/beacon/loader/info_handlers.ex @@ -0,0 +1,32 @@ +defmodule Beacon.Loader.InfoHandlers do + @moduledoc false + alias Beacon.Loader + + def module_name(site), do: Loader.module_name(site, "InfoHandlers") + + def build_ast(site, info_handlers) do + module = module_name(site) + functions = Enum.map(info_handlers, &build_fn/1) + + quote do + defmodule unquote(module) do + import Phoenix.LiveView + import Phoenix.Component, except: [assign: 2, assign: 3, assign_new: 3] + import Beacon.Web, only: [assign: 2, assign: 3, assign_new: 3] + + (unquote_splicing(functions)) + end + end + end + + defp build_fn(info_handler) do + %{site: site, msg: msg, code: code} = info_handler + Beacon.safe_code_check!(site, code) + + quote do + def handle_info(unquote(Code.string_to_quoted!(msg)), var!(socket)) do + unquote(Code.string_to_quoted!(code)) + end + end + end +end diff --git a/lib/beacon/loader/page.ex b/lib/beacon/loader/page.ex index 9d5a934e..c32fc84b 100644 --- a/lib/beacon/loader/page.ex +++ b/lib/beacon/loader/page.ex @@ -16,7 +16,7 @@ defmodule Beacon.Loader.Page do # Group function heads together to avoid compiler warnings functions = [ - for fun <- [&page/1, &page_assigns/1, &handle_event/1, &helper/1] do + for fun <- [&page/1, &page_assigns/1, &handle_event/1, &handle_info/1, &helper/1] do fun.(page) end, render(page), @@ -37,9 +37,11 @@ defmodule Beacon.Loader.Page do import PhoenixHTMLHelpers.Link import PhoenixHTMLHelpers.Tag import PhoenixHTMLHelpers.Format + import Phoenix.LiveView import Phoenix.Component, except: [assign: 2, assign: 3, assign_new: 3] import Beacon.Web, only: [assign: 2, assign: 3, assign_new: 3] import Beacon.Router, only: [beacon_asset_path: 2, beacon_asset_url: 2] + use Gettext, backend: Beacon.Gettext import unquote(routes_module) import unquote(components_module) @@ -138,6 +140,22 @@ defmodule Beacon.Loader.Page do end) end + defp handle_info(page) do + %{site: site} = page + + info_handlers = Content.list_info_handlers(site) + + Enum.map(info_handlers, fn info_handler -> + Beacon.safe_code_check!(site, info_handler.code) + + quote do + def handle_info(unquote(Code.string_to_quoted!(info_handler.msg)), var!(socket)) do + unquote(Code.string_to_quoted!(info_handler.code)) + end + end + end) + end + # TODO: validate fn name and args def helper(%{site: site, helpers: helpers}) do Enum.map(helpers, fn helper -> diff --git a/lib/beacon/loader/worker.ex b/lib/beacon/loader/worker.ex index e1b21a13..d3c101b4 100644 --- a/lib/beacon/loader/worker.ex +++ b/lib/beacon/loader/worker.ex @@ -444,6 +444,14 @@ defmodule Beacon.Loader.Worker do end end + def handle_call(:reload_info_handlers_module, _from, config) do + %{site: site} = config + info_handlers = Content.list_info_handlers(site) + ast = Loader.InfoHandlers.build_ast(site, info_handlers) + result = compile_module(site, ast, "info_handlers") + stop(result, config) + end + def handle_info(msg, config) do Logger.warning("Beacon.Loader.Worker can not handle the message: #{inspect(msg)}") {:noreply, config} diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 571f6082..bc7d2efb 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -34,6 +34,15 @@ defmodule Beacon.Migrations.V002 do |> then(&repo().insert_all("beacon_event_handlers", &1, [])) drop_if_exists table(:beacon_page_event_handlers) + + create_if_not_exists table(:beacon_info_handlers, primary_key: false) do + add :id, :binary_id, primary_key: true + add :site, :text, null: false + add :msg, :text, null: false + add :code, :text, null: false + + timestamps(type: :utc_datetime_usec) + end end def down do @@ -50,5 +59,7 @@ defmodule Beacon.Migrations.V002 do # global event handlers can't be converted back into page event handlers drop_if_exists table(:beacon_event_handlers) + + drop_if_exists table(:beacon_info_handlers) end end diff --git a/lib/beacon/web/beacon_assigns.ex b/lib/beacon/web/beacon_assigns.ex index f757fb49..4f552d91 100644 --- a/lib/beacon/web/beacon_assigns.ex +++ b/lib/beacon/web/beacon_assigns.ex @@ -39,6 +39,7 @@ defmodule Beacon.Web.BeaconAssigns do private: %{ page_module: nil, components_module: nil, + info_handlers_module: nil, event_handlers_module: nil, live_data_keys: [], live_path: [] @@ -66,6 +67,7 @@ defmodule Beacon.Web.BeaconAssigns do path_params = Beacon.Router.path_params(page.path, path_info) page_title = Beacon.Web.DataSource.page_title(site, page.id, live_data) components_module = Beacon.Loader.Components.module_name(site) + info_handlers_module = Beacon.Loader.InfoHandlers.module_name(site) event_handlers_module = Beacon.Loader.EventHandlers.module_name(site) %__MODULE__{ @@ -76,6 +78,7 @@ defmodule Beacon.Web.BeaconAssigns do private: %{ page_module: page_module, components_module: components_module, + info_handlers_module: info_handlers_module, event_handlers_module: event_handlers_module, live_data_keys: Map.keys(live_data), live_path: path_info diff --git a/lib/beacon/web/components/layouts/app.html.heex b/lib/beacon/web/components/layouts/app.html.heex index bcfe1599..81ac27d9 100644 --- a/lib/beacon/web/components/layouts/app.html.heex +++ b/lib/beacon/web/components/layouts/app.html.heex @@ -1,16 +1,5 @@
- - - - Attempting to reconnect - <%= @inner_content %>
diff --git a/lib/beacon/web/components/layouts/dynamic.html.heex b/lib/beacon/web/components/layouts/dynamic.html.heex index 30a1d588..e486bf02 100644 --- a/lib/beacon/web/components/layouts/dynamic.html.heex +++ b/lib/beacon/web/components/layouts/dynamic.html.heex @@ -1 +1,2 @@ + <%= render_dynamic_layout(assigns) %> diff --git a/lib/beacon/web/live/page_live.ex b/lib/beacon/web/live/page_live.ex index 91215bbc..a6a8d47e 100644 --- a/lib/beacon/web/live/page_live.ex +++ b/lib/beacon/web/live/page_live.ex @@ -53,14 +53,25 @@ defmodule Beacon.Web.PageLive do end def handle_info(msg, socket) do - Logger.warning(""" - unhandled message: + %{page_module: page_module, live_path: live_path, info_handlers_module: info_handlers_module} = socket.assigns.beacon.private + %{site: site, id: page_id} = Beacon.apply_mfa(page_module, :page_assigns, [[:site, :id]]) - #{inspect(msg)} + result = + Beacon.apply_mfa( + info_handlers_module, + :handle_info, + [msg, socket], + context: %{site: site, page_id: page_id, live_path: live_path} + ) - """) + case result do + {:noreply, %Phoenix.LiveView.Socket{} = socket} -> + {:noreply, socket} - {:noreply, socket} + other -> + raise Beacon.Web.ServerError, + "handle_info for #{live_path} expected return of {:noreply, %Phoenix.LiveView.Socket{}}, but got #{inspect(other)}" + end end def handle_event(event_name, event_params, socket) do diff --git a/lib/errors.ex b/lib/errors.ex index 7e666bce..c5f8ad24 100644 --- a/lib/errors.ex +++ b/lib/errors.ex @@ -83,9 +83,9 @@ end defmodule Beacon.Web.ServerError do @moduledoc """ - Raised when a `Beacon.Content.EventHandler` returns an invalid response. + Raised when a `Beacon.Content.EventHandler` or a `Beacon.Content.InfoHandler` returns an invalid response. - If you're seeing this error, check the code in your site's event handlers, and + If you're seeing this error, check the code in your site's event handlers or info handlers, and ensure that each one returns `{:noreply, socket}`. """ diff --git a/mix.exs b/mix.exs index 5e0bed09..15d735a4 100644 --- a/mix.exs +++ b/mix.exs @@ -131,7 +131,8 @@ defmodule Beacon.MixProject do "Functions: Snippets": &(&1[:type] == :snippets), "Functions: Event Handlers": &(&1[:type] == :event_handlers), "Functions: Error Pages": &(&1[:type] == :error_pages), - "Functions: Live Data": &(&1[:type] == :live_data) + "Functions: Live Data": &(&1[:type] == :live_data), + "Functions: Info Handlers": &(&1[:type] == :info_handlers) ], skip_undefined_reference_warnings_on: ["CHANGELOG.md"] ] @@ -166,6 +167,7 @@ defmodule Beacon.MixProject do Beacon.Content.ComponentSlotAttr, Beacon.Content.ErrorPage, Beacon.Content.EventHandler, + Beacon.Content.InfoHandler, Beacon.Content.Layout, Beacon.Content.LayoutEvent, Beacon.Content.LayoutSnapshot, diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs index c8a2db1e..c2d20c0a 100644 --- a/test/beacon/content_test.exs +++ b/test/beacon/content_test.exs @@ -7,6 +7,7 @@ defmodule Beacon.ContentTest do alias Beacon.Content.Component alias Beacon.Content.ErrorPage alias Beacon.Content.EventHandler + alias Beacon.Content.InfoHandler alias Beacon.Content.Layout alias Beacon.Content.LayoutEvent alias Beacon.Content.LayoutSnapshot @@ -983,4 +984,143 @@ defmodule Beacon.ContentTest do assert %{assigns: []} = Repo.preload(live_data, :assigns) end end + + describe "info_handlers" do + setup do + code = ~S""" + socket = + socket + |> redirect(to: "/home") + |> put_flash( + :error, + "Your email (#{email_address}) is incorrectly formatted. Please format it correctly." + ) + + {:noreply, socket} + """ + + msg = "{:incorrect_format, email_address}" + + %{msg: msg, code: code} + end + + test "success: create_info_handler/1", %{msg: msg, code: code} do + attrs = %{site: :my_site, msg: msg, code: code} + + assert {:ok, %InfoHandler{} = info_handler} = Content.create_info_handler(attrs) + assert %InfoHandler{site: :my_site, msg: ^msg, code: ^code} = info_handler + end + + test "error: create_info_handler/1 validates code", %{msg: msg} do + assert {:error, %{errors: [error]}} = + Content.create_info_handler(%{ + site: :my_site, + msg: msg, + code: ":no_reply, socket" + }) + + {:code, {"invalid", [compilation_error: compilation_error]}} = error + + assert compilation_error =~ "unexpectedly reached end of line" + + refute Repo.one(InfoHandler) + end + + test "success: create_info_handler!/1", %{msg: msg, code: code} do + Content.create_info_handler!(%{ + site: :my_site, + msg: msg, + code: code + }) + + assert %InfoHandler{site: :my_site, msg: ^msg, code: ^code} = Repo.one(InfoHandler) + end + + test "success: get_info_handler/2", %{msg: msg, code: code} do + info_handler = info_handler_fixture(%{msg: msg, code: code}) + site = info_handler.site + handler_from_db = Content.get_info_handler(site, info_handler.id) + + assert %InfoHandler{site: ^site, msg: ^msg, code: ^code} = handler_from_db + end + + test "success: get_info_handler!/2", %{msg: msg, code: code} do + info_handler = info_handler_fixture(%{msg: msg, code: code}) + site = info_handler.site + handler_from_db = Content.get_info_handler!(site, info_handler.id) + + assert %InfoHandler{site: ^site, msg: ^msg, code: ^code} = handler_from_db + end + + test "success: list_info_handlers/1" do + info_handlers = for _ <- 1..3, do: info_handler_fixture() + + result = Content.list_info_handlers(:my_site) + + assert Enum.sort(info_handlers) == Enum.sort(result) + end + + test "success: update_info_handler/2" do + code = ~S""" + socket = + socket + |> assign(email: nil) + |> put_flash( + :error, + "There was an error." + ) + + {:noreply, socket} + """ + + msg = "{:email_address_error}" + + info_handler = info_handler_fixture() + attrs = %{code: code, msg: msg} + + refute info_handler.code == code + refute info_handler.msg == msg + + assert {:ok, %InfoHandler{} = info_handler_from_db} = Content.update_info_handler(info_handler, attrs) + assert %InfoHandler{code: ^code, msg: ^msg} = info_handler_from_db + end + + test "error: update_info_handler/2" do + code = ~S""" + socket = + socket + |> assign(email: nil) + |> put_flash( + :error, + "There was an error." + ) + + :noreply, socket + """ + + msg = "{:email_address_error}" + + info_handler = info_handler_fixture() + attrs = %{code: code, msg: msg} + + refute info_handler.code == code + refute info_handler.msg == msg + + {:error, %{errors: [error]}} = Content.update_info_handler(info_handler, attrs) + + {:code, {"invalid", [compilation_error: compilation_error]}} = error + + assert compilation_error =~ "unexpectedly reached end of line" + end + + test "success: delete_info_handler/1", %{msg: msg, code: code} do + info_handler = info_handler_fixture(%{msg: msg, code: code}) + site = info_handler.site + + assert Repo.one(InfoHandler) + + assert {:ok, %InfoHandler{site: ^site, msg: ^msg, code: ^code}} = Content.delete_info_handler(info_handler) + refute Repo.one(InfoHandler) + end + end end diff --git a/test/beacon/lifecycle/template_test.exs b/test/beacon/lifecycle/template_test.exs index ad799fba..79aa7204 100644 --- a/test/beacon/lifecycle/template_test.exs +++ b/test/beacon/lifecycle/template_test.exs @@ -20,7 +20,6 @@ defmodule Beacon.Lifecycle.TemplateTest do Beacon.Loader.reload_page_module(page.site, page.id) env = Beacon.Web.PageLive.make_env(:my_site) - assert %Phoenix.LiveView.Rendered{static: ["
\n

my_site#home

\n
"]} = - Lifecycle.Template.render_template(page, %{}, env) + assert %Phoenix.LiveView.Rendered{static: ["
\n

my_site#home

\n
"]} = Lifecycle.Template.render_template(page, %{}, env) end end diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 69109a5b..baf09086 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -90,6 +90,18 @@ defmodule Beacon.Web.Live.PageLiveTest do """ }) + info_handler_fixture(%{ + site: :my_site, + msg: "{:incorrect_format, email}", + code: """ + socket = + socket + |> put_flash(:error, "Your email (\#{email}) is incorrectly formatted. Please format it correctly.") + + {:noreply, socket} + """ + }) + Content.publish_page(page_home) _page_without_meta_tags = @@ -108,6 +120,7 @@ defmodule Beacon.Web.Live.PageLiveTest do Loader.reload_components_module(:my_site) Loader.reload_layouts_modules(:my_site) Loader.reload_pages_modules(:my_site) + Loader.reload_info_handlers_module(:my_site) Loader.reload_event_handlers_module(:my_site) [layout: layout] @@ -259,6 +272,18 @@ defmodule Beacon.Web.Live.PageLiveTest do |> render_submit() =~ "Hello Beacon" end + test "info handler", %{conn: conn} do + {:ok, view, _html} = live(conn, "/home/hello") + + email = "email@email" + + refute render(view) =~ "Your email (#{email}) is incorrectly formatted. Please format it correctly." + + send(view.pid, {:incorrect_format, email}) + + assert render(view) =~ "Your email (#{email}) is incorrectly formatted. Please format it correctly." + end + test "helper", %{conn: conn} do {:ok, _view, html} = live(conn, "/home/hello") diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex index b52f7aa5..bea896f9 100644 --- a/test/support/fixtures.ex +++ b/test/support/fixtures.ex @@ -4,6 +4,7 @@ defmodule Beacon.Fixtures do alias Beacon.Content alias Beacon.Content.ErrorPage alias Beacon.Content.EventHandler + alias Beacon.Content.InfoHandler alias Beacon.Content.PageVariant alias Beacon.MediaLibrary alias Beacon.MediaLibrary.UploadMetadata @@ -223,4 +224,29 @@ defmodule Beacon.Fixtures do live_data end + + def info_handler_fixture(attrs \\ %{}) do + code = ~S""" + socket = + socket + |> put_flash( + :info, + "We just sent an email to your address (#{email})!" + ) + + {:noreply, socket} + """ + + msg = "{:email_sent, email}" + + full_attrs = %{ + site: attrs[:site] || :my_site, + msg: attrs[:msg] || msg, + code: attrs[:code] || code + } + + %InfoHandler{} + |> InfoHandler.changeset(full_attrs) + |> repo(full_attrs.site).insert!() + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index ceae09d9..176c41f8 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -165,6 +165,7 @@ Enum.each( Beacon.Content.ErrorPage, Beacon.Content.Page, Beacon.Content.Layout, + Beacon.Content.InfoHandler, Beacon.Content.EventHandler ], &Beacon.BeaconTest.Repo.delete_all/1 @@ -174,6 +175,7 @@ Enum.each( for site <- [:my_site, :not_booted, :s3_site, :data_source_test, :default_meta_tags_test, :lifecycle_test, :lifecycle_test_fail] do Beacon.Loader.reload_routes_module(site) Beacon.Loader.reload_components_module(site) + Beacon.Loader.reload_info_handlers_module(site) Beacon.Loader.reload_event_handlers_module(site) end