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

Add some EventStore Tests #541

Merged
merged 4 commits into from
Jun 18, 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
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.16.2-otp-26
erlang 26.0.2
elixir 1.16.3-otp-26
erlang 26.2.5
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.15.7-erlang-26.0.2-debian-bullseye-20231009-slim
#
ARG ELIXIR_VERSION=1.16.2
ARG OTP_VERSION=26.0.2
ARG ELIXIR_VERSION=1.16.3
ARG OTP_VERSION=26.2.5
ARG DEBIAN_VERSION=bullseye-20240513-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
Expand Down
15 changes: 14 additions & 1 deletion assets/js/hooks/events/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
deleteItem,
focusItem,
moveItem,
getItemById,
} from "../item";
import { getNodeByItem } from "../node";

export function handleList({ nodes }: { nodes: Node[] }) {
const container: HTMLDivElement = this.el.querySelector(".children");
Expand Down Expand Up @@ -36,12 +38,23 @@ export function handleList({ nodes }: { nodes: Node[] }) {
focusItem(lastItem);
}

export function handleInsert({ node }: { node: Node }) {
export function handleInsert({
node,
next_id,
}: {
node: Node;
next_id: string | undefined;
}) {
const container: HTMLDivElement = this.el.querySelector(".children");

const item = createItem(node);
container.append(item);
moveItem(node, container);
const nextItem = getItemById(next_id) as HTMLDivElement;
const nextNode = getNodeByItem(nextItem);
nextNode.prev_id = node.uuid;
nextNode.dirty = false;
moveItem(nextNode, container);
}

export function handleContentChange({ node }: { node: Node }) {
Expand Down
10 changes: 10 additions & 0 deletions assets/js/hooks/events/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getItemByNode,
moveItem,
setItemDirty,
setItemParent,
setItemPrev,
} from "../item";
import { getNodeByEvent, getNodeByItem } from "../node";

Expand Down Expand Up @@ -92,6 +94,14 @@ export function keydown(event: KeyboardEvent) {
const newItem = createItem(newNode);
item.after(newItem);
focusItem(newItem, false);

const oldNextItem = newItem.nextSibling as HTMLDivElement | null;
if (oldNextItem) {
const nextNode = getNodeByItem(oldNextItem);
nextNode.prev_id = newNode.uuid;
nextNode.dirty = true;
setItemPrev(oldNextItem, newNode.uuid);
}
break;

case "Backspace":
Expand Down
46 changes: 26 additions & 20 deletions assets/js/hooks/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,22 @@ export function createItem({ uuid, content, parent_id, prev_id, dirty }: Node) {

const item = document.createElement("div");
item.id = `outline-node-${uuid}`;
item.className = "my-1 bg-gray-100 data-[dirty=true]:bg-red-100";
item.className = "my-1 data-[dirty=true]:bg-red-100";
item.setAttribute("data-parent", parent_id || "");
item.setAttribute("data-prev", prev_id || "");

const link = document.createElement("a");
link.className = "block float-left my-0.5 bg-gray-200 rounded-full";
link.className = "block float-left my-0.5 rounded-full";
link.href = `#${uuid}`;
link.innerHTML =
'<svg viewBox="0 0 18 18" fill="currentColor" class="w-5 h-5"><circle cx="9" cy="9" r="3.5"></circle></svg>';
item.appendChild(link);

const contentWrap = document.createElement("div");
contentWrap.className = "ml-5 bg-gray-300 content";
contentWrap.contentEditable = "true";
item.appendChild(contentWrap);

const span = document.createElement("span");
span.className = "bg-gray-400 innerContent";
span.textContent = content || " ";
contentWrap.appendChild(span);
const input = document.createElement("div");
input.className = "ml-5 content";
input.contentEditable = "true";
input.textContent = content || "";
item.appendChild(input);

const childContainer = document.createElement("div");
childContainer.className = "ml-5 children";
Expand All @@ -51,8 +47,8 @@ export function changeItemContent({ uuid, content, dirty }: Node) {

const newContent = content || "";

const span = item.querySelector(".innerContent") as HTMLSpanElement;
if (span.textContent != newContent) span.textContent = newContent;
const input = item.querySelector(".content") as HTMLDivElement;
if (input.textContent != newContent) input.textContent = newContent;

setItemDirty(item, dirty);

Expand All @@ -62,7 +58,7 @@ export function changeItemContent({ uuid, content, dirty }: Node) {
export function moveItem(
{ uuid, parent_id, prev_id, dirty }: Node,
container: HTMLDivElement,
force: boolean = false
force: boolean = false,
) {
const item = getItemById(uuid);
if (!item) return;
Expand All @@ -75,8 +71,8 @@ export function moveItem(
)
return;

item.setAttribute("data-parent", parent_id || "");
item.setAttribute("data-prev", prev_id || "");
setItemParent(item, parent_id);
setItemPrev(item, prev_id);

const prevItem = getItemById(prev_id);
const parentItem = getItemById(parent_id);
Expand All @@ -94,6 +90,16 @@ export function moveItem(
return item;
}

export function setItemParent(
item: HTMLDivElement,
parent_id: string | undefined,
) {
item.setAttribute("data-parent", parent_id || "");
}
export function setItemPrev(item: HTMLDivElement, prev_id: string | undefined) {
item.setAttribute("data-prev", prev_id || "");
}

export function deleteItem({ uuid }: Node) {
const item = getItemById(uuid);
if (!item) return;
Expand All @@ -112,7 +118,7 @@ export function getItemByNode({ uuid }: Node) {
return getItemById(uuid);
}

function getItemById(uuid: string | undefined) {
export function getItemById(uuid: string | undefined) {
if (!uuid) return null;

return document.getElementById(`outline-node-${uuid}`) as HTMLDivElement;
Expand All @@ -126,12 +132,12 @@ export function getItemByEvent(event: Event): HTMLDivElement {
}

export function focusItem(item: HTMLDivElement, toEnd: boolean = true) {
const contentWrap = item.querySelector(".innerContent") as HTMLDivElement;
contentWrap.focus();
const input = item.querySelector(".content") as HTMLDivElement;
input.focus();

if (toEnd) {
const range = document.createRange();
range.setStart(contentWrap, 0);
range.setStart(input, 0);
range.collapse(true);

const selection = window.getSelection();
Expand Down
4 changes: 2 additions & 2 deletions assets/js/hooks/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export function getNodeByEvent(event: Event): Node {

export function getNodeByItem(item: HTMLDivElement): Node {
const uuid = item.id.split("outline-node-")[1] as UUID;
const span = item.querySelector(".innerContent") as HTMLSpanElement;
const content = span.textContent || "";
const input = item.querySelector(".content") as HTMLDivElement;
const content = input.textContent || "";

const parent_id = (item.getAttribute("data-parent") as UUID) || undefined;
const prev_id = (item.getAttribute("data-prev") as UUID) || undefined;
Expand Down
22 changes: 18 additions & 4 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
defmodule Radiator.Outline.NodeRepoResult do
@moduledoc """
Generic result structure for node operations.
"""
defstruct [
:node,
:old_prev_id,
:old_next_id,
:next_id,
:children
]
end

defmodule Radiator.Outline do
@moduledoc """
The Outline context.
Expand All @@ -6,6 +19,7 @@ defmodule Radiator.Outline do
import Ecto.Query, warn: false

alias Radiator.Outline.Node
alias Radiator.Outline.NodeRepoResult
alias Radiator.Outline.NodeRepository
alias Radiator.Repo

Expand All @@ -17,7 +31,7 @@ defmodule Radiator.Outline do
## Examples

iex> insert_node(%{content: 'foo'})
{:ok, %Node{}}
{:ok, %NodeRepoResult{}}

iex> insert_node(%{content: value})
{:error, :parent_and_prev_not_consistent}
Expand All @@ -34,7 +48,7 @@ defmodule Radiator.Outline do
parent_node_id = attrs["parent_id"]
episode_id = attrs["episode_id"]
# find Node which has been previously connected to prev_node
node_to_move =
next_node =
Node
|> where(episode_id: ^episode_id)
|> where_prev_node_equals(prev_node_id)
Expand All @@ -45,9 +59,9 @@ defmodule Radiator.Outline do
prev_node <- NodeRepository.get_node_if(prev_node_id),
true <- parent_and_prev_consistent?(parent_node, prev_node),
{:ok, node} <- NodeRepository.create_node(attrs),
{:ok, _node_to_move} <- move_node_if(node_to_move, nil, node.uuid),
{:ok, _node_to_move} <- move_node_if(next_node, nil, node.uuid),
{:ok, node} <- move_node_if(node, parent_node_id, prev_node_id) do
node
%NodeRepoResult{node: node, next_id: get_node_id(next_node)}
else
false ->
Repo.rollback("Insert node failed. Parent and prev node are not consistent.")
Expand Down
2 changes: 1 addition & 1 deletion lib/radiator/outline/event/node_deleted_event.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Radiator.Outline.Event.NodeDeletedEvent do
@moduledoc false
defstruct [:event_id, :node_id, :user_id]
defstruct [:event_id, :node_id, :user_id, :children]
end
2 changes: 1 addition & 1 deletion lib/radiator/outline/event/node_inserted_event.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Radiator.Outline.Event.NodeInsertedEvent do
@moduledoc false

defstruct [:event_id, :node, :user_id]
defstruct [:event_id, :node, :user_id, :next_id]
end
11 changes: 10 additions & 1 deletion lib/radiator/outline/event/node_moved_event.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
defmodule Radiator.Outline.Event.NodeMovedEvent do
@moduledoc false
defstruct [:event_id, :node_id, :parent_id, :prev_id, :user_id]
defstruct [
:event_id,
:node_id,
:parent_id,
:prev_id,
:user_id,
:old_prev_id,
:old_next_id,
:next_id
]
end
10 changes: 8 additions & 2 deletions lib/radiator/outline/event_consumer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Radiator.Outline.EventConsumer do

alias Radiator.EventStore
alias Radiator.Outline
alias Radiator.Outline.NodeRepoResult

alias Radiator.Outline.Command.{
ChangeNodeContentCommand,
Expand Down Expand Up @@ -80,8 +81,13 @@ defmodule Radiator.Outline.EventConsumer do
:ok
end

defp handle_insert_node_result({:ok, node}, command) do
%NodeInsertedEvent{node: node, event_id: command.event_id, user_id: command.user_id}
defp handle_insert_node_result({:ok, %NodeRepoResult{node: node, next_id: next_id}}, command) do
%NodeInsertedEvent{
node: node,
event_id: command.event_id,
user_id: command.user_id,
next_id: next_id
}
|> EventStore.persist_event()
|> Dispatch.broadcast()

Expand Down
2 changes: 1 addition & 1 deletion lib/radiator_web/controllers/api/outline_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ defmodule RadiatorWeb.Api.OutlineController do
})
end

defp get_response({:ok, node}), do: {200, %{uuid: node.uuid}}
defp get_response({:ok, %{node: node}}), do: {200, %{uuid: node.uuid}}
defp get_response({:error, _}), do: {400, %{error: "params"}}
end
4 changes: 2 additions & 2 deletions lib/radiator_web/live/episode_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ defmodule RadiatorWeb.EpisodeLive.Index do
|> reply(:noreply)
end

def handle_info(%NodeInsertedEvent{node: node}, socket) do
def handle_info(%NodeInsertedEvent{node: node, next_id: next_id}, socket) do
socket
|> push_event("insert", %{node: node})
|> push_event("insert", %{node: node, next_id: next_id})
|> reply(:noreply)
end

Expand Down
Loading
Loading