diff --git a/lib/ex_cell/base.ex b/lib/ex_cell/base.ex index 04d90b2..b7f68e2 100644 --- a/lib/ex_cell/base.ex +++ b/lib/ex_cell/base.ex @@ -4,6 +4,7 @@ defmodule ExCell.Base do defmacro __using__(opts \\ []) do quote do import ExCell.View + import ExCell.LiveView @adapter unquote(opts[:adapter]) @namespace unquote(opts[:namespace]) diff --git a/lib/ex_cell/live_view.ex b/lib/ex_cell/live_view.ex new file mode 100644 index 0000000..2cc3e24 --- /dev/null +++ b/lib/ex_cell/live_view.ex @@ -0,0 +1,63 @@ +defmodule ExCell.LiveView do + @moduledoc """ + Cell helpers used to render the live view cells in both Views and Cells + """ + @view_adapter ExCell.config(:view_adapter, Phoenix.LiveView) + + @doc """ + Renders a cell in the view. + + ### Examples + iex(0)> safe_to_string(AppWeb.AvatarView.live_cell(AvatarLiveCell, socket)) + "
" + """ + def live_cell(cell, conn_or_socket) do + render_cell(cell, conn_or_socket, []) + end + + @doc """ + Renders a cell in the view with children. + + ### Examples + iex(0)> safe_to_string(AppWeb.AvatarView.live_cell(AvatarLiveCell, do: "Foo")) + "
Foo
" + """ + def live_cell(cell, conn_or_socket, do: children) do + render_cell(cell, conn_or_socket, children: children) + end + + @doc """ + Renders a cell in the view with assigns. + + ### Examples + iex(0)> safe_to_string(AppWeb.AvatarView.live_cell(AvatarLiveCell, user: %User{name: "Bar"})) + "
Bar
" + """ + def live_cell(cell, conn_or_socket, assigns) when is_list(assigns) do + render_cell(cell, conn_or_socket, assigns) + end + + @doc """ + Renders a cell in the view with children without a block. + + ### Examples + iex(0)> safe_to_string(AppWeb.AvatarView.live_cell(AvatarLiveCell, "Hello")) + "
Hello
" + """ + def live_cell(cell, conn_or_socket, children) do + render_cell(cell, conn_or_socket, children: children) + end + + def live_cell(cell, conn_or_socket, assigns, do: children) when is_list(assigns) do + render_cell(cell, conn_or_socket, [children: children] ++ assigns) + end + + def live_cell(cell, conn_or_socket, children, assigns) when is_list(assigns) do + render_cell(cell, conn_or_socket, [children: children] ++ assigns) + end + + defp render_cell(cell, conn_or_socket, assigns) do + assigns = Map.new(assigns) + @view_adapter.live_render(conn_or_socket, cell, session: %{assigns: assigns}) + end +end diff --git a/mix.exs b/mix.exs index 9306ae1..c73968c 100644 --- a/mix.exs +++ b/mix.exs @@ -52,6 +52,7 @@ defmodule ExCell.Mixfile do {:excoveralls, "~> 0.7", only: :test}, {:mix_test_watch, "~> 0.3", only: :dev, runtime: false}, {:phoenix_html, "~> 2.10"}, + {:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}, {:phoenix, "~> 1.4.0", optional: true}, {:jason, "~> 1.1"}, {:elixir_uuid, "~> 1.2"} diff --git a/mix.lock b/mix.lock index 44c5a69..348a3e4 100644 --- a/mix.lock +++ b/mix.lock @@ -21,6 +21,7 @@ "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.3", "8eed4a64ff1e12372cd634724bddd69185938f52c18e1396ebac76375d85677d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.2", "f5d27c9b10ce881a60177d2b5227314fc60881e6b66b41dfe3349db6ed06cf57", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "da34e4886885f1f0f0dd7692b72663e4e94d9c98", []}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, "plug": {:hex, :plug, "1.8.0", "9d2685cb007fe5e28ed9ac27af2815bc262b7817a00929ac10f56f169f43b977", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, diff --git a/test/ex_cell/live_view_test.exs b/test/ex_cell/live_view_test.exs new file mode 100644 index 0000000..055e339 --- /dev/null +++ b/test/ex_cell/live_view_test.exs @@ -0,0 +1,48 @@ +defmodule ExCell.LiveViewTest do + use ExCell.ConnCase + alias ExCell.LiveView + + test "live_cell/2 with ExCell", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn) === [conn, :mock_cell, [session: %{assigns: %{}}]] + end + + test "live_cell/3 with assigns", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn, foo: "bar") === [ + conn, + :mock_cell, + [session: %{assigns: %{foo: "bar"}}] + ] + end + + test "live_cell/3 with do block", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn, do: "yes") === [ + conn, + :mock_cell, + [session: %{assigns: %{children: "yes"}}] + ] + end + + test "live_cell/3 with children", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn, "yes") === [ + conn, + :mock_cell, + [session: %{assigns: %{children: "yes"}}] + ] + end + + test "live_cell/3 with assign and do block", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn, [foo: "bar"], do: "yes") === [ + conn, + :mock_cell, + [session: %{assigns: %{children: "yes", foo: "bar"}}] + ] + end + + test "live_cell/3 with children and assign", %{conn: conn} do + assert LiveView.live_cell(:mock_cell, conn, "yes", foo: "bar") === [ + conn, + :mock_cell, + [session: %{assigns: %{children: "yes", foo: "bar"}}] + ] + end +end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex new file mode 100644 index 0000000..062bb2e --- /dev/null +++ b/test/support/conn_case.ex @@ -0,0 +1,28 @@ +defmodule ExCell.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + use Phoenix.ConnTest + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/test/support/mock_view_adapter.ex b/test/support/mock_view_adapter.ex index 4a718a7..6690f3f 100644 --- a/test/support/mock_view_adapter.ex +++ b/test/support/mock_view_adapter.ex @@ -2,4 +2,5 @@ defmodule ExCell.MockViewAdapter do @moduledoc false def render(cell, template, args), do: [cell, template, args] def render_to_string(cell, template, args), do: [cell, template, args] + def live_render(conn, cell, args), do: [conn, cell, args] end