Skip to content

Commit

Permalink
add: outline context
Browse files Browse the repository at this point in the history
  • Loading branch information
sorax committed Nov 21, 2023
1 parent 5842a79 commit 191e7cb
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 6 deletions.
104 changes: 104 additions & 0 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule Radiator.Outline do
@moduledoc """
The Outline context.
"""

import Ecto.Query, warn: false
alias Radiator.Repo

alias Radiator.Outline.Node

@doc """
Returns the list of nodes.
## Examples
iex> list_nodes()
[%Node{}, ...]
"""
def list_nodes do
Repo.all(Node)
end

@doc """
Gets a single node.
Raises `Ecto.NoResultsError` if the Node does not exist.
## Examples
iex> get_node!(123)
%Node{}
iex> get_node!(456)
** (Ecto.NoResultsError)
"""
def get_node!(id), do: Repo.get!(Node, id)

@doc """
Creates a node.
## Examples
iex> create_node(%{field: value})
{:ok, %Node{}}
iex> create_node(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_node(attrs \\ %{}) do
%Node{}
|> Node.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a node.
## Examples
iex> update_node(node, %{field: new_value})
{:ok, %Node{}}
iex> update_node(node, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_node(%Node{} = node, attrs) do
node
|> Node.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a node.
## Examples
iex> delete_node(node)
{:ok, %Node{}}
iex> delete_node(node)
{:error, %Ecto.Changeset{}}
"""
def delete_node(%Node{} = node) do
Repo.delete(node)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking node changes.
## Examples
iex> change_node(node)
%Ecto.Changeset{data: %Node{}}
"""
def change_node(%Node{} = node, attrs \\ %{}) do
Node.changeset(node, attrs)
end
end
22 changes: 22 additions & 0 deletions lib/radiator/outline/node.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Radiator.Outline.Node do
use Ecto.Schema
import Ecto.Changeset

@primary_key {:uuid, :binary_id, autogenerate: true}
schema "outline_nodes" do
field :content, :string

timestamps(type: :utc_datetime)
end

@fields [
:content
]

@doc false
def changeset(node, attrs) do
node
|> cast(attrs, @fields)
|> validate_required(@fields)
end
end
33 changes: 33 additions & 0 deletions lib/radiator_web/live/outline_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
defmodule RadiatorWeb.OutlineLive.Index do
use RadiatorWeb, :live_view

alias Radiator.Outline
alias Radiator.Outline.Node

@impl true
def mount(_params, _session, socket) do
node = %Node{}
changeset = Outline.change_node(node)

socket
|> assign(:page_title, "Outline")
|> assign(:node, node)
|> assign(:form, to_form(changeset))
|> stream_configure(:nodes, dom_id: &"node-#{&1.uuid}")
|> stream(:nodes, Outline.list_nodes())
|> reply(:ok)
end

@impl true
def handle_event("update", %{"node" => _params}, socket) do
socket
|> reply(:noreply)
end

@impl true
def handle_event("next", %{"node" => params}, socket) do
{:ok, node} = Outline.create_node(params)

socket
|> stream_insert(:nodes, node, at: 0)
|> reply(:noreply)
end

@impl true
def handle_event("delete", %{"uuid" => uuid}, socket) do
node = Outline.get_node!(uuid)
{:ok, _} = Outline.delete_node(node)

{:noreply, stream_delete(socket, :nodes, node)}
end
end
21 changes: 21 additions & 0 deletions lib/radiator_web/live/outline_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
<section>
<h1>Outline</h1>

<h2>Inbox</h2>

<.focus_wrap id="input-form-wrap">
<.form id="inbox-form" for={@form} phx-change="update" phx-submit="next">
<.input type="text" field={@form[:content]} />
<.input type="hidden" field={@form[:uuid]} />
</.form>
</.focus_wrap>

<ul id="inbox" class="mt-8 list-disc list-inside" phx-update="stream">
<li :for={{dom_id, node} <- @streams.nodes} id={dom_id}>
<%= node.content %>
<.link
phx-click={JS.push("delete", value: %{uuid: node.uuid}) |> hide("##{dom_id}")}
data-confirm="Delete?"
>
<.icon name="hero-x-circle" class="w-5 h-5" />
<div class="sr-only">Delete</div>
</.link>
</li>
</ul>
</section>
12 changes: 12 additions & 0 deletions priv/repo/migrations/20231120103619_create_outline_nodes.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Radiator.Repo.Migrations.CreateOutlineNodes do
use Ecto.Migration

def change do
create table(:outline_nodes, primary_key: false) do
add :uuid, :uuid, primary_key: true
add :content, :text

timestamps(type: :utc_datetime)
end
end
end
59 changes: 59 additions & 0 deletions test/radiator/outline_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Radiator.OutlineTest do
use Radiator.DataCase

alias Radiator.Outline

describe "outline_nodes" do
alias Radiator.Outline.Node

import Radiator.OutlineFixtures

@invalid_attrs %{content: nil}

test "list_nodes/0 returns all nodes" do
node = node_fixture()
assert Outline.list_nodes() == [node]
end

test "get_node!/1 returns the node with given id" do
node = node_fixture()
assert Outline.get_node!(node.uuid) == node
end

test "create_node/1 with valid data creates a node" do
valid_attrs = %{content: "some content"}

assert {:ok, %Node{} = node} = Outline.create_node(valid_attrs)
assert node.content == "some content"
end

test "create_node/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Outline.create_node(@invalid_attrs)
end

test "update_node/2 with valid data updates the node" do
node = node_fixture()
update_attrs = %{content: "some updated content"}

assert {:ok, %Node{} = node} = Outline.update_node(node, update_attrs)
assert node.content == "some updated content"
end

test "update_node/2 with invalid data returns error changeset" do
node = node_fixture()
assert {:error, %Ecto.Changeset{}} = Outline.update_node(node, @invalid_attrs)
assert node == Outline.get_node!(node.uuid)
end

test "delete_node/1 deletes the node" do
node = node_fixture()
assert {:ok, %Node{}} = Outline.delete_node(node)
assert_raise Ecto.NoResultsError, fn -> Outline.get_node!(node.uuid) end
end

test "change_node/1 returns a node changeset" do
node = node_fixture()
assert %Ecto.Changeset{} = Outline.change_node(node)
end
end
end
37 changes: 31 additions & 6 deletions test/radiator_web/live/outline_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ defmodule RadiatorWeb.OutlineLiveTest do

import Phoenix.LiveViewTest
import Radiator.AccountsFixtures
import Radiator.OutlineFixtures

describe "Outline page" do
describe "Outline page is restricted" do
test "can render if user is logged in", %{conn: conn} do
{:ok, _live, html} = conn |> log_in_user(user_fixture()) |> live(~p"/admin/outline")
user = user_fixture()
{:ok, _live, html} = conn |> log_in_user(user) |> live(~p"/admin/outline")

assert html =~ "Outline</h1>"
end
Expand All @@ -23,15 +25,38 @@ defmodule RadiatorWeb.OutlineLiveTest do
describe "Outline page has an inbox" do
setup %{conn: conn} do
user = user_fixture()
%{conn: log_in_user(conn, user)}
node = node_fixture()
%{conn: log_in_user(conn, user), node: node}
end

test "lists all nodes", %{conn: conn, node: node} do
{:ok, _live, html} = live(conn, ~p"/admin/outline")

assert html =~ "Inbox</h2>"
assert html =~ node.content
end

test "save new node", %{conn: conn} do
{:ok, live, _html} = live(conn, ~p"/admin/outline")

assert live
|> form("#inbox-form", node: %{content: "new node content"})
|> render_submit()

assert live
|> element("ul#inbox")
|> render() =~ "new node content"
end

test "inbox has a headline", %{conn: conn} do
test "delete existing node", %{conn: conn, node: node} do
{:ok, live, _html} = live(conn, ~p"/admin/outline")

assert live
|> element("h2", "Inbox")
|> render() =~ "Inbox"
|> element("#node-#{node.uuid} a", "Delete")
|> render_click()

refute live
|> has_element?("#node-#{node.uuid}")
end
end
end
18 changes: 18 additions & 0 deletions test/support/fixtures/outline_fixtures.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Radiator.OutlineFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Radiator.Outline` context.
"""

@doc """
Generate a node.
"""
def node_fixture(attrs \\ %{}) do
{:ok, node} =
attrs
|> Enum.into(%{content: "some content"})
|> Radiator.Outline.create_node()

node
end
end

0 comments on commit 191e7cb

Please sign in to comment.