From 0d733ce4e2755e1f5bc14839e1e239d97a1f8f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Wo=CC=88ginger?= Date: Sun, 29 Dec 2024 17:48:11 +0100 Subject: [PATCH] implement Outline.merge_next_node --- lib/radiator/outline.ex | 51 +++++++++++++++++++++++ lib/radiator/outline/command_processor.ex | 7 +--- lib/radiator/outline/node_repository.ex | 1 + test/radiator/outline_test.exs | 40 ++++++++++++++++++ 4 files changed, 94 insertions(+), 5 deletions(-) diff --git a/lib/radiator/outline.ex b/lib/radiator/outline.ex index b8348081..dd8372ca 100644 --- a/lib/radiator/outline.ex +++ b/lib/radiator/outline.ex @@ -16,6 +16,7 @@ defmodule Radiator.Outline do @moduledoc """ The Outline context. """ + import Ecto.Query alias Radiator.Outline.Node alias Radiator.Outline.NodeRepoResult @@ -346,6 +347,56 @@ defmodule Radiator.Outline do {first, last} end + def merge_prev_node(_node_id) do + nil + end + + def merge_next_node(%Node{} = node) do + merge_next_node(node, NodeRepository.get_next_node(node)) + end + + def merge_next_node(node_id), do: merge_next_node(NodeRepository.get_node!(node_id)) + + def merge_next_node(%Node{} = _node, nil), do: {:error, :no_next_node} + + def merge_next_node( + %Node{uuid: node_id} = node, + %Node{uuid: next_node_id, content: content_of_next_node} = next_node + ) do + Ecto.Multi.new() + |> Ecto.Multi.update_all( + :update_childrens, + fn _ -> + from(n in Node, where: n.parent_id == ^next_node_id, update: [set: [parent_id: ^node_id]]) + end, + [] + ) + |> Ecto.Multi.update(:update_content, fn _changes -> + Node.update_content_changeset(node, %{content: node.content <> content_of_next_node}) + end) + |> Ecto.Multi.run(:delete_next_node, fn _, _ -> + %NodeRepoResult{node: deleted_node, next: updated_next_node} = remove_node(next_node) + {:ok, %{deleted_node: deleted_node, updated_next_node: updated_next_node}} + end) + |> Repo.transaction() + |> case do + {:ok, result} -> + node = result.update_content + + {:ok, + %NodeRepoResult{ + node: result.update_content, + old_next: result.delete_next_node.deleted_node, + next: result.delete_next_node.updated_next_node, + children: NodeRepository.get_all_siblings(node), + outline_node_container_id: node.outline_node_container_id + }} + + {:error, _tag, error, _others} -> + {:error, error} + end + end + @doc """ Removes a node from the tree and deletes it from the repository. Recursivly deletes all children if there are some. diff --git a/lib/radiator/outline/command_processor.ex b/lib/radiator/outline/command_processor.ex index f4c370b8..c7c8b8d5 100644 --- a/lib/radiator/outline/command_processor.ex +++ b/lib/radiator/outline/command_processor.ex @@ -167,12 +167,9 @@ defmodule Radiator.Outline.CommandProcessor do # delete current node end - defp process_command(%MergeNextNodeCommand{node_id: _node_id} = _command) do - # node_id has a next node? + defp process_command(%MergeNextNodeCommand{node_id: node_id} = _command) do + Outline.merge_next_node(node_id) - # get content from next node -> append content to current node - - # delete next node end defp handle_insert_node_result( diff --git a/lib/radiator/outline/node_repository.ex b/lib/radiator/outline/node_repository.ex index 11777fc4..b3b09a3e 100644 --- a/lib/radiator/outline/node_repository.ex +++ b/lib/radiator/outline/node_repository.ex @@ -277,6 +277,7 @@ defmodule Radiator.Outline.NodeRepository do end @doc """ + TODO wrong name: sibling is "geschwister" in german Returns all direct child nodes of a given node. ## Examples iex> get_all_siblings(%Node{}) diff --git a/test/radiator/outline_test.exs b/test/radiator/outline_test.exs index 14633611..dd2b8fc4 100644 --- a/test/radiator/outline_test.exs +++ b/test/radiator/outline_test.exs @@ -832,6 +832,46 @@ defmodule Radiator.OutlineTest do end end + describe "merge_next_node/1" do + setup :complex_node_fixture + + test "merges two nodes into one", %{ + node_2: node_2, + node_3: node_3, + nested_node_1: nested_node_1, + nested_node_2: nested_node_2, + node_4: node_4 + } do + {:ok, result} = Outline.merge_next_node(node_2) + + node_2 = Repo.reload!(node_2) + node_4 = Repo.reload!(node_4) + nested_node_1 = Repo.reload!(nested_node_1) + nested_node_2 = Repo.reload!(nested_node_2) + assert is_nil(NodeRepository.get_node(node_3.uuid)) + assert nested_node_2.parent_id == node_2.uuid + assert nested_node_1.parent_id == node_2.uuid + assert node_4.prev_id == node_2.uuid + assert node_2.content == "node_2node_3" + + assert result.node.uuid == node_2.uuid + assert result.old_next.uuid == node_3.uuid + assert result.next.uuid == node_4.uuid + assert Enum.count(result.children) == 2 + end + + test "ignores command when there is no next node", %{ + node_6: node_6 + } do + old_content = node_6.content + {:error, :no_next_node} = Outline.merge_next_node(node_6) + assert is_nil(NodeRepository.get_next_node(node_6)) + {:error, :no_next_node} = Outline.merge_next_node(node_6) + node_6 = Repo.reload!(node_6) + assert old_content == node_6.content + end + end + describe "indent_node/1 - simple context" do setup :simple_node_fixture