Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add move node #525

Merged
merged 1 commit into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,57 @@ defmodule Radiator.Outline do
end)
end

@doc """
Moves a node to another parent.

## Examples

iex> move_node(node_id, new_prev_id, new_parent_id)
{:ok, %Node{}}
"""
def move_node(node_id, new_prev_id, new_parent_id) do
case NodeRepository.get_node(node_id) do
nil ->
{:error, :not_found}

node ->
prev_node = get_prev_node(node)
parent_node = get_parent_node(node)

if get_node_id(prev_node) != new_prev_id || get_node_id(parent_node) != new_parent_id do
do_move_node(node, new_prev_id, new_parent_id, prev_node, parent_node)
else
{:ok, node}
end
end
end

# low level function to move a node
defp do_move_node(node, new_prev_id, new_parent_id, prev_node, parent_node) do
Repo.transaction(fn ->
old_next_node =
Node
|> where_prev_node_equals(node.uuid)
|> where_parent_node_equals(get_node_id(parent_node))
|> Repo.one()

new_next_node =
Node
|> where_prev_node_equals(new_prev_id)
|> where_parent_node_equals(new_parent_id)
|> Repo.one()

{:ok, node} = move_node_if(node, new_parent_id, new_prev_id)

{:ok, _old_next_node} =
move_node_if(old_next_node, get_node_id(parent_node), get_node_id(prev_node))

{:ok, _new_next_node} = move_node_if(new_next_node, new_parent_id, get_node_id(node))
end)

{:ok, node}
end

@doc """
Updates a nodes content.

Expand Down Expand Up @@ -139,6 +190,26 @@ defmodule Radiator.Outline do
|> Repo.one()
end

@doc """
Returns the parent node of a given node in the outline tree.
Returns `nil` if parent_id of the node is nil.

## Examples
iex> get_parent_node(%Node{parent_id: nil})
nil

iex> get_parent_node(%Node{parent_id: 42})
%Node{uuid: 42}

"""
def get_parent_node(node) when is_nil(node.parent_id), do: nil

def get_parent_node(node) do
Node
|> where([n], n.uuid == ^node.parent_id)
|> Repo.one()
end

@doc """
Returns all child nodes of a given node.
## Examples
Expand Down
286 changes: 286 additions & 0 deletions test/radiator/outline_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,251 @@ defmodule Radiator.OutlineTest do
end
end

describe "move_node/3 - simple context" do
setup :simple_node_fixture

# before 1 2
# after 2 1
test "move node on same level", %{
node_1: node_1,
node_2: node_2
} do
{:ok, _} = Outline.move_node(node_2.uuid, nil, node_2.parent_id)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_1.prev_id == node_2.uuid
assert node_2.prev_id == nil
end

# before 1 2
# after 2 1
test "move node on same level - move the other node", %{
node_1: node_1,
node_2: node_2
} do
{:ok, _} = Outline.move_node(node_1.uuid, node_2.uuid, node_1.parent_id)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_1.prev_id == node_2.uuid
assert node_2.prev_id == nil
end

test "ignore when nothing should change", %{
node_1: node_1,
node_2: node_2
} do
{:ok, _} = Outline.move_node(node_2.uuid, node_1.uuid, node_2.parent_id)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_2.prev_id == node_1.uuid
assert node_1.prev_id == nil
end

test "ignore when nothing should change - other way around", %{
node_1: node_1,
node_2: node_2
} do
{:ok, _} = Outline.move_node(node_1.uuid, nil, node_2.parent_id)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_2.prev_id == node_1.uuid
assert node_1.prev_id == nil
end

test "move node below other node", %{
node_1: node_1,
node_2: node_2
} do
{:ok, _} = Outline.move_node(node_2.uuid, nil, node_1.uuid)

# reload nodes
node_1 = Repo.reload!(node_1)
node_2 = Repo.reload!(node_2)

assert node_1.prev_id == nil
assert node_2.prev_id == nil

assert node_1.parent_id == nil
assert node_2.parent_id == node_1.uuid
end
end

describe "move_node/3" do
setup :complex_node_fixture

# before 1 2 3 4 5
# after 1 2 4 3 5
test "move node 4 within list to node 2", %{
node_2: node_2,
node_3: node_3,
node_4: node_4,
node_5: node_5
} do
{:ok, _} = Outline.move_node(node_4.uuid, node_2.uuid, node_4.parent_id)

# reload nodes
node_5 = Repo.reload!(node_5)
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)

assert node_4.prev_id == node_2.uuid
assert node_3.prev_id == node_4.uuid
assert node_5.prev_id == node_3.uuid
end

# before 1 2 3 4 5
# after 4 1 2 3 5
test "move node 4 to the top of the list", %{
node_1: node_1,
node_2: node_2,
node_3: node_3,
node_4: node_4,
node_5: node_5
} do
{:ok, _} = Outline.move_node(node_4.uuid, nil, node_4.parent_id)

# reload nodes
node_5 = Repo.reload!(node_5)
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)
node_1 = Repo.reload!(node_1)

assert node_4.prev_id == nil
assert node_1.prev_id == node_4.uuid
assert node_2.prev_id == node_1.uuid
assert node_3.prev_id == node_2.uuid
assert node_5.prev_id == node_3.uuid
end

# before 1 2 3 4 5
# after 1 3 4 5 2
test "move node 2 to the end of the list", %{
node_1: node_1,
node_2: node_2,
node_3: node_3,
node_4: node_4,
node_5: node_5
} do
{:ok, _} = Outline.move_node(node_2.uuid, node_5.uuid, node_2.parent_id)

# reload nodes
node_5 = Repo.reload!(node_5)
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)
node_1 = Repo.reload!(node_1)

assert node_3.prev_id == node_1.uuid
assert node_4.prev_id == node_3.uuid
assert node_5.prev_id == node_4.uuid
assert node_2.prev_id == node_5.uuid
end

# before 1 2 3 4 5
# after 2 3 4 5 1
test "move first node to the end of the list", %{
node_1: node_1,
node_2: node_2,
node_3: node_3,
node_4: node_4,
node_5: node_5
} do
{:ok, _} = Outline.move_node(node_1.uuid, node_5.uuid, node_2.parent_id)

# reload nodes
node_5 = Repo.reload!(node_5)
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)
node_1 = Repo.reload!(node_1)

assert node_1.prev_id == node_5.uuid
assert node_5.prev_id == node_4.uuid
assert node_3.prev_id == node_2.uuid
assert node_2.prev_id == nil
end

# before 1 2 3 4 5
# after 5 1 2 3 4
test "move last node to the top of the list", %{
node_1: node_1,
node_2: node_2,
node_3: node_3,
node_4: node_4,
node_5: node_5
} do
{:ok, _} = Outline.move_node(node_5.uuid, nil, node_2.parent_id)

# reload nodes
node_5 = Repo.reload!(node_5)
node_4 = Repo.reload!(node_4)
node_3 = Repo.reload!(node_3)
node_2 = Repo.reload!(node_2)
node_1 = Repo.reload!(node_1)

assert node_5.prev_id == nil
assert node_1.prev_id == node_5.uuid
assert node_2.prev_id == node_1.uuid
assert node_3.prev_id == node_2.uuid
assert node_4.prev_id == node_3.uuid
end

test "move nested node to a new parent", %{
node_2: node_2,
node_3: node_3,
nested_node_1: nested_node_1,
nested_node_2: nested_node_2
} do
{:ok, _} = Outline.move_node(nested_node_1.uuid, nil, node_2.uuid)

# reload nodes
nested_node_1 = Repo.reload!(nested_node_1)
nested_node_2 = Repo.reload!(nested_node_2)

assert nested_node_1.parent_id == node_2.uuid
assert nested_node_2.parent_id == node_3.uuid
assert nested_node_1.prev_id == nil
assert nested_node_2.prev_id == nil
end

test "move node with child elements to top", %{
parent_node: parent_node,
node_3: node_3,
nested_node_1: nested_node_1,
nested_node_2: nested_node_2
} do
{:ok, _} = Outline.move_node(node_3.uuid, nil, nil)

# reload nodes
parent_node = Repo.reload!(parent_node)
node_3 = Repo.reload!(node_3)
nested_node_1 = Repo.reload!(nested_node_1)
nested_node_2 = Repo.reload!(nested_node_2)

assert node_3.prev_id == nil
assert node_3.parent_id == nil
assert parent_node.prev_id == node_3.uuid
assert nested_node_1.parent_id == node_3.uuid
assert nested_node_2.parent_id == node_3.uuid
assert nested_node_1.prev_id == nil
assert nested_node_2.prev_id == nested_node_1.uuid
end
end

describe "remove_node/1" do
setup :complex_node_fixture

Expand Down Expand Up @@ -422,6 +667,36 @@ defmodule Radiator.OutlineTest do
assert node.level == level
end

defp simple_node_fixture(_) do
episode = PodcastFixtures.episode_fixture()

node_1 =
node_fixture(
episode_id: episode.id,
parent_id: nil,
prev_id: nil,
content: "node_1"
)

node_2 =
node_fixture(
episode_id: episode.id,
parent_id: nil,
prev_id: node_1.uuid,
content: "node_2"
)

assert node_2.prev_id == node_1.uuid
assert node_1.prev_id == nil
assert node_1.parent_id == nil
assert node_2.parent_id == nil

%{
node_1: node_1,
node_2: node_2
}
end

defp complex_node_fixture(_) do
episode = PodcastFixtures.episode_fixture()

Expand Down Expand Up @@ -497,6 +772,17 @@ defmodule Radiator.OutlineTest do
content: "nested_node_2"
)

assert node_5.prev_id == node_4.uuid
assert node_4.prev_id == node_3.uuid
assert node_3.prev_id == node_2.uuid
assert node_2.prev_id == node_1.uuid
assert node_1.prev_id == nil

assert nested_node_1.parent_id == node_3.uuid
assert nested_node_2.parent_id == node_3.uuid
assert nested_node_1.prev_id == nil
assert nested_node_2.prev_id == nested_node_1.uuid

%{
node_1: node_1,
node_2: node_2,
Expand Down
Loading