diff --git a/assets/js/hooks/events/listener.ts b/assets/js/hooks/events/listener.ts index 7323af38..2f6149d3 100644 --- a/assets/js/hooks/events/listener.ts +++ b/assets/js/hooks/events/listener.ts @@ -26,7 +26,7 @@ export function input(event: Event) { const item = getItemByNode(node); item && setItemDirty(item, true); - this.pushEvent("update_node", node); + this.pushEvent("update_node_content", node); } export function keydown(event: KeyboardEvent) { @@ -77,7 +77,7 @@ export function keydown(event: KeyboardEvent) { node.dirty = true; updateItem(node, container); - this.pushEvent("update_node", node); + this.pushEvent("update_node_content", node); const newNode: Node = { uuid: self.crypto.randomUUID(), @@ -105,7 +105,7 @@ export function keydown(event: KeyboardEvent) { prevNode.dirty = true; updateItem(prevNode, container); focusItem(prevItem); - this.pushEvent("update_node", prevNode); + this.pushEvent("update_node_content", prevNode); deleteItem(node); this.pushEvent("delete_node", node); @@ -121,7 +121,7 @@ export function keydown(event: KeyboardEvent) { node.dirty = true; updateItem(node, container); focusItem(item); - this.pushEvent("update_node", node); + this.pushEvent("update_node_content", node); deleteItem(nextNode); this.pushEvent("delete_node", nextNode); @@ -138,7 +138,7 @@ export function keydown(event: KeyboardEvent) { // updateItem(node, container); // focusItem(item); - // this.pushEvent("update_node", node); + // this.pushEvent("move_node", node); // } } else { // indent diff --git a/lib/radiator_web/live/episode_live/index.ex b/lib/radiator_web/live/episode_live/index.ex index 2f50d3a1..8ce3c576 100644 --- a/lib/radiator_web/live/episode_live/index.ex +++ b/lib/radiator_web/live/episode_live/index.ex @@ -2,7 +2,14 @@ defmodule RadiatorWeb.EpisodeLive.Index do use RadiatorWeb, :live_view alias Radiator.Outline.{Dispatch, NodeRepository} - alias Radiator.Outline.Event.{NodeContentChangedEvent, NodeDeletedEvent, NodeInsertedEvent} + + alias Radiator.Outline.Event.{ + NodeContentChangedEvent, + NodeDeletedEvent, + NodeInsertedEvent, + NodeMovedEvent + } + alias Radiator.Podcast alias Radiator.Podcast.Episode @@ -58,7 +65,7 @@ defmodule RadiatorWeb.EpisodeLive.Index do |> reply(:noreply) end - def handle_event("update_node", %{"uuid" => uuid, "content" => content}, socket) do + def handle_event("update_node_content", %{"uuid" => uuid, "content" => content}, socket) do user = socket.assigns.current_user Dispatch.change_node_content(uuid, content, user.id, generate_event_id(socket.id)) @@ -165,6 +172,15 @@ defmodule RadiatorWeb.EpisodeLive.Index do |> reply(:noreply) end + def handle_info( + %NodeMovedEvent{node_id: id, parent_id: parent_id, prev_id: prev_id}, + socket + ) do + socket + |> push_event("move", %{node: %{uuid: id, parent_id: parent_id, prev_id: prev_id}}) + |> reply(:noreply) + end + def handle_info(%NodeDeletedEvent{node_id: id}, socket) do socket |> push_event("delete", %{node: %{uuid: id}}) diff --git a/test/radiator_web/live/episode_live_test.exs b/test/radiator_web/live/episode_live_test.exs index 3b122fef..a04ffb2a 100644 --- a/test/radiator_web/live/episode_live_test.exs +++ b/test/radiator_web/live/episode_live_test.exs @@ -6,9 +6,11 @@ defmodule RadiatorWeb.EpisodeLiveTest do import Radiator.PodcastFixtures import Radiator.OutlineFixtures - # alias Radiator.Outline.Node + alias Radiator.Outline.Node alias Radiator.Outline.NodeRepository + @additional_keep_alive 2000 + describe "Episode page is restricted" do setup do %{show: show_fixture()} @@ -51,11 +53,9 @@ defmodule RadiatorWeb.EpisodeLiveTest do show = show_fixture() episode = episode_fixture(%{show_id: show.id}) - node_1 = node_fixture(%{episode_id: episode.id}) - node_2 = node_fixture(%{episode_id: episode.id, prev_id: node_1.uuid}) - _node_21 = node_fixture(%{episode_id: episode.id, parent_id: node_2.uuid}) + node = node_fixture(%{episode_id: episode.id}) - %{conn: log_in_user(conn, user), show: show, episode: episode, nodes: [node_1, node_2]} + %{conn: log_in_user(conn, user), show: show, episode: episode, node: node} end test "lists all nodes", %{conn: conn, show: show} do @@ -66,7 +66,7 @@ defmodule RadiatorWeb.EpisodeLiveTest do assert_push_event(live, "list", %{nodes: ^nodes}) end - test "insert a new node", %{conn: conn, show: show, nodes: [node_1 | _]} do + test "insert a new node", %{conn: conn, show: show, node: node} do {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") {:ok, other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") @@ -74,77 +74,87 @@ defmodule RadiatorWeb.EpisodeLiveTest do params = %{ "uuid" => uuid, - "content" => "new node temp content", + "content" => "new node content", # "parent_id" => nil, - "prev_id" => node_1.uuid + "prev_id" => node.uuid } assert live |> render_hook(:create_node, params) - node = NodeRepository.get_node!(uuid) - assert node.parent_id == nil - assert node.prev_id == node_1.uuid + keep_liveview_alive() + + assert %Node{} = new_node = NodeRepository.get_node!(uuid) + assert new_node.uuid == uuid + assert new_node.parent_id == nil + assert new_node.prev_id == node.uuid assert_push_event(live, "clean", %{node: %{uuid: ^uuid}}) - assert_push_event(other_live, "insert", %{node: ^node}) + assert_push_event(other_live, "insert", %{node: ^new_node}) end - # test "receive node inserted event after inserting a node", %{conn: conn, show: show} do - # {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - - # event_id1 = Ecto.UUID.generate() - # event_id2 = Ecto.UUID.generate() - # content1 = Ecto.UUID.generate() - # content2 = Ecto.UUID.generate() - # params1 = %{"event_id" => event_id1, "content" => content1} - # params2 = %{"event_id" => event_id2, "content" => content2} - - # assert live |> render_hook(:create_node, params1) - # assert live |> render_hook(:create_node, params2) - - # assert_push_event( - # live, - # "insert", - # %{node: %Node{content: ^content1}, event_id: ^event_id1}, - # 1000 - # ) - - # assert_push_event( - # live, - # "insert", - # %{node: %Node{content: ^content2}, event_id: ^event_id2}, - # 1000 - # ) - # end - - # test "update node", %{conn: conn, show: show, episode: episode} do - # {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - # {:ok, _other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - - # node = node_fixture(%{episode_id: episode.id}) - - # update_attrs = %{ - # content: "update node content", - # event_id: Ecto.UUID.generate() - # } - - # params = node |> Map.from_struct() |> Map.merge(update_attrs) - # assert live |> render_hook(:update_node, params) - - # updated_node = NodeRepository.get_node!(node.uuid) - # assert updated_node.content == update_attrs.content - # end - - # test "delete node", %{conn: conn, show: show, episode: episode} do - # {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - # {:ok, _other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - - # node = node_fixture(%{episode_id: episode.id}) - # params = Map.from_struct(node) - - # assert live |> render_hook(:delete_node, params) - # end + test "update node content", %{conn: conn, show: show, node: %{uuid: uuid}} do + {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + {:ok, other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + + content = "updated node content" + params = %{"uuid" => uuid, "content" => content} + + assert live |> render_hook(:update_node_content, params) + + keep_liveview_alive() + + assert %Node{content: ^content} = NodeRepository.get_node!(uuid) + + assert_push_event(live, "clean", %{node: %{uuid: ^uuid}}) + assert_push_event(other_live, "change_content", %{node: %{uuid: ^uuid, content: ^content}}) + end + + test "move node", %{conn: conn, show: show, episode: episode, node: node1} do + %{uuid: uuid1, parent_id: parent_id1} = node1 + + uuid2 = Ecto.UUID.generate() + + node2 = + node_fixture(%{ + uuid: uuid2, + episode_id: episode.id, + parent_id: parent_id1, + prev_id: uuid1 + }) + + {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + {:ok, _other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + + params = node2 |> Map.merge(%{parent_id: uuid1, prev_id: nil}) |> Map.from_struct() + + assert live |> render_hook(:move_node, params) + + keep_liveview_alive() + + ### assert %Node{parent_id: ^uuid1} = NodeRepository.get_node!(uuid2) + + ### assert_push_event(live, "clean", %{node: %{uuid: ^uuid2}}) + ### assert_push_event(other_live, "move", %{node: %{uuid: ^uuid2, parent_id: ^uuid1, prev_id: nil}}) + end + + test "delete node", %{conn: conn, show: show, node: %{uuid: uuid}} do + {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + {:ok, other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + + params = %{"uuid" => uuid} + + assert live |> render_hook(:delete_node, params) + + keep_liveview_alive() + + assert NodeRepository.get_node(uuid) == nil + + assert_push_event(live, "clean", %{node: %{uuid: ^uuid}}) + assert_push_event(other_live, "delete", %{node: %{uuid: ^uuid}}) + end end - # defp generate_event_id(id), do: Ecto.UUID.generate() <> ":" <> id + defp keep_liveview_alive do + :timer.sleep(@additional_keep_alive) + end end