diff --git a/assets/js/alpine_helpers.js b/assets/js/alpine_helpers.js
index 2d8bd93..9c2367f 100644
--- a/assets/js/alpine_helpers.js
+++ b/assets/js/alpine_helpers.js
@@ -40,5 +40,9 @@ window.dispatchFor = (elementOrId, eventName, detail = {}) => {
const element =
typeof elementOrId === 'string' ? document.getElementById(elementOrId) : elementOrId
- element.dispatchEvent(new CustomEvent(eventName, { detail }))
+ // This is needed to ensure the DOM has updated before dispatching the event.
+ // Doing so ensures that the latest DOM state is what's sent to the server
+ setTimeout(() => {
+ element.dispatchEvent(new Event(eventName, { bubbles: true, detail }))
+ }, 0)
}
diff --git a/assets/js/app.js b/assets/js/app.js
index f9accf3..e6e0219 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -47,29 +47,6 @@ let liveSocket = new LiveSocket(document.body.dataset.socketPath, Socket, {
}
})
}
- },
- 'formless-input': {
- mounted() {
- const subscribedEvents = this.el.dataset.subscribe.split(' ')
- const eventName = this.el.dataset.eventName || ''
- const identifier = this.el.dataset.identifier || ''
-
- subscribedEvents.forEach((domEvent) => {
- this.el.addEventListener(domEvent, () => {
- // This ensures that the event is pushed to the server after the input value has been updated
- // so that the server has the most up-to-date value
- setTimeout(() => {
- this.pushEvent('formless-input', {
- value: this.el.value,
- id: identifier,
- event: eventName,
- dom_id: this.el.id,
- dom_event: domEvent
- })
- }, 0)
- })
- })
- }
}
}
})
diff --git a/lib/pinchflat_web/components/custom_components/table_components.ex b/lib/pinchflat_web/components/custom_components/table_components.ex
index 7c661ba..106c45a 100644
--- a/lib/pinchflat_web/components/custom_components/table_components.ex
+++ b/lib/pinchflat_web/components/custom_components/table_components.ex
@@ -16,6 +16,8 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
"""
attr :rows, :list, required: true
attr :table_class, :string, default: ""
+ attr :sort_key, :string, default: nil
+ attr :sort_direction, :string, default: nil
attr :row_item, :any,
default: &Function.identity/1,
@@ -24,6 +26,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
slot :col, required: true do
attr :label, :string
attr :class, :string
+ attr :sort_key, :string
end
def table(assigns) do
@@ -31,8 +34,20 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
- |
- {col[:label]}
+ |
+
+ {col[:label]}
+ <.icon
+ :if={to_string(@sort_key) == col[:sort_key]}
+ name={if @sort_direction == :asc, do: "hero-chevron-up", else: "hero-chevron-down"}
+ class="w-3 h-3 mt-2 ml-1 absolute"
+ />
+
|
@@ -70,9 +85,9 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
= @total_pages && "cursor-not-allowed"
]}
phx-click={@page_number != @total_pages && "page_change"}
phx-value-direction="inc"
diff --git a/lib/pinchflat_web/components/custom_components/text_components.ex b/lib/pinchflat_web/components/custom_components/text_components.ex
index d2bc8f6..2145bae 100644
--- a/lib/pinchflat_web/components/custom_components/text_components.ex
+++ b/lib/pinchflat_web/components/custom_components/text_components.ex
@@ -2,6 +2,7 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do
@moduledoc false
use Phoenix.Component
+ alias Pinchflat.Utils.NumberUtils
alias PinchflatWeb.CoreComponents
@doc """
@@ -125,4 +126,24 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do
{@word}{if @count == 1, do: "", else: @suffix}
"""
end
+
+ @doc """
+ Renders a human-readable byte size
+ """
+
+ attr :byte_size, :integer, required: true
+
+ def readable_filesize(assigns) do
+ {num, suffix} = NumberUtils.human_byte_size(assigns.byte_size, precision: 2)
+
+ assigns =
+ Map.merge(assigns, %{
+ num: num,
+ suffix: suffix
+ })
+
+ ~H"""
+ <.localized_number number={@num} /> {@suffix}
+ """
+ end
end
diff --git a/lib/pinchflat_web/controllers/pages/page_html.ex b/lib/pinchflat_web/controllers/pages/page_html.ex
index f4f2665..16f7731 100644
--- a/lib/pinchflat_web/controllers/pages/page_html.ex
+++ b/lib/pinchflat_web/controllers/pages/page_html.ex
@@ -1,23 +1,5 @@
defmodule PinchflatWeb.Pages.PageHTML do
use PinchflatWeb, :html
- alias Pinchflat.Utils.NumberUtils
-
embed_templates "page_html/*"
-
- attr :media_filesize, :integer, required: true
-
- def readable_media_filesize(assigns) do
- {num, suffix} = NumberUtils.human_byte_size(assigns.media_filesize, precision: 2)
-
- assigns =
- Map.merge(assigns, %{
- num: num,
- suffix: suffix
- })
-
- ~H"""
- <.localized_number number={@num} /> {@suffix}
- """
- end
end
diff --git a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex
index a70eb28..1579c31 100644
--- a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex
+++ b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex
@@ -33,7 +33,7 @@
Library Size
- <.readable_media_filesize media_filesize={@media_item_size} />
+ <.readable_filesize byte_size={@media_item_size} />
diff --git a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
index b6be923..4761552 100644
--- a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
+++ b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex
@@ -11,8 +11,12 @@
-
- {live_render(@conn, PinchflatWeb.Sources.IndexTableLive)}
-
+ {live_render(@conn, PinchflatWeb.Sources.SourceLive.IndexTableLive,
+ session: %{
+ "initial_sort_key" => :custom_name,
+ "initial_sort_direction" => :asc,
+ "results_per_page" => 10
+ }
+ )}
diff --git a/lib/pinchflat_web/controllers/sources/source_live/index_table_live.ex b/lib/pinchflat_web/controllers/sources/source_live/index_table_live.ex
new file mode 100644
index 0000000..5ada1b0
--- /dev/null
+++ b/lib/pinchflat_web/controllers/sources/source_live/index_table_live.ex
@@ -0,0 +1,108 @@
+defmodule PinchflatWeb.Sources.SourceLive.IndexTableLive do
+ use PinchflatWeb, :live_view
+ use Pinchflat.Media.MediaQuery
+ use Pinchflat.Sources.SourcesQuery
+
+ import PinchflatWeb.Helpers.SortingHelpers
+ import PinchflatWeb.Helpers.PaginationHelpers
+
+ alias Pinchflat.Repo
+ alias Pinchflat.Sources.Source
+ alias Pinchflat.Media.MediaItem
+
+ def mount(_params, session, socket) do
+ limit = session["results_per_page"]
+
+ initial_params =
+ Map.merge(
+ %{
+ sort_key: session["initial_sort_key"],
+ sort_direction: session["initial_sort_direction"]
+ },
+ get_pagination_attributes(sources_query(), 1, limit)
+ )
+
+ socket
+ |> assign(initial_params)
+ |> set_sources()
+ |> then(&{:ok, &1})
+ end
+
+ def handle_event("page_change", %{"direction" => direction}, %{assigns: assigns} = socket) do
+ new_page = update_page_number(assigns.page, direction, assigns.total_pages)
+
+ socket
+ |> assign(get_pagination_attributes(sources_query(), new_page, assigns.limit))
+ |> set_sources()
+ |> then(&{:noreply, &1})
+ end
+
+ def handle_event("sort_update", %{"sort_key" => sort_key}, %{assigns: assigns} = socket) do
+ new_sort_key = String.to_existing_atom(sort_key)
+
+ new_params = %{
+ sort_key: new_sort_key,
+ sort_direction: get_sort_direction(assigns.sort_key, new_sort_key, assigns.sort_direction)
+ }
+
+ socket
+ |> assign(new_params)
+ |> set_sources()
+ |> then(&{:noreply, &1})
+ end
+
+ defp sort_attr(:pending_count), do: dynamic([s, mp, dl, pe], pe.pending_count)
+ defp sort_attr(:downloaded_count), do: dynamic([s, mp, dl], dl.downloaded_count)
+ defp sort_attr(:media_size_bytes), do: dynamic([s, mp, dl], dl.media_size_bytes)
+ defp sort_attr(:media_profile_name), do: dynamic([s, mp], mp.name)
+ defp sort_attr(:custom_name), do: dynamic([s], s.custom_name)
+ defp sort_attr(:enabled), do: dynamic([s], s.enabled)
+
+ defp set_sources(%{assigns: assigns} = socket) do
+ sources =
+ sources_query()
+ |> order_by(^[{assigns.sort_direction, sort_attr(assigns.sort_key)}, asc: :id])
+ |> limit(^assigns.limit)
+ |> offset(^assigns.offset)
+ |> Repo.all()
+
+ assign(socket, %{sources: sources})
+ end
+
+ defp sources_query do
+ downloaded_subquery =
+ from(
+ m in MediaItem,
+ select: %{downloaded_count: count(m.id), source_id: m.source_id, media_size_bytes: sum(m.media_size_bytes)},
+ where: ^MediaQuery.downloaded(),
+ group_by: m.source_id
+ )
+
+ pending_subquery =
+ from(
+ m in MediaItem,
+ inner_join: s in assoc(m, :source),
+ inner_join: mp in assoc(s, :media_profile),
+ select: %{pending_count: count(m.id), source_id: m.source_id},
+ where: ^MediaQuery.pending(),
+ group_by: m.source_id
+ )
+
+ from s in Source,
+ as: :source,
+ inner_join: mp in assoc(s, :media_profile),
+ left_join: d in subquery(downloaded_subquery),
+ on: d.source_id == s.id,
+ left_join: p in subquery(pending_subquery),
+ on: p.source_id == s.id,
+ on: d.source_id == s.id,
+ where: is_nil(s.marked_for_deletion_at) and is_nil(mp.marked_for_deletion_at),
+ preload: [media_profile: mp],
+ select: map(s, ^Source.__schema__(:fields)),
+ select_merge: %{
+ downloaded_count: coalesce(d.downloaded_count, 0),
+ pending_count: coalesce(p.pending_count, 0),
+ media_size_bytes: coalesce(d.media_size_bytes, 0)
+ }
+ end
+end
diff --git a/lib/pinchflat_web/controllers/sources/source_live/index_table_live.html.heex b/lib/pinchflat_web/controllers/sources/source_live/index_table_live.html.heex
new file mode 100644
index 0000000..cf2ce18
--- /dev/null
+++ b/lib/pinchflat_web/controllers/sources/source_live/index_table_live.html.heex
@@ -0,0 +1,41 @@
+
+ <.table rows={@sources} table_class="text-white" sort_key={@sort_key} sort_direction={@sort_direction}>
+ <:col :let={source} label="Name" sort_key="custom_name" class="truncate max-w-xs">
+ <.subtle_link href={~p"/sources/#{source.id}"}>
+ {source.custom_name}
+
+
+ <:col :let={source} label="Pending" sort_key="pending_count">
+ <.subtle_link href={~p"/sources/#{source.id}/#tab-pending"}>
+ <.localized_number number={source.pending_count} />
+
+
+ <:col :let={source} label="Downloaded" sort_key="downloaded_count">
+ <.subtle_link href={~p"/sources/#{source.id}/#tab-downloaded"}>
+ <.localized_number number={source.downloaded_count} />
+
+
+ <:col :let={source} label="Size" sort_key="media_size_bytes">
+ <.readable_filesize byte_size={source.media_size_bytes} />
+
+ <:col :let={source} label="Media Profile" sort_key="media_profile_name">
+ <.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
+ {source.media_profile.name}
+
+
+ <:col :let={source} label="Enabled?" sort_key="enabled">
+ <.live_component
+ module={PinchflatWeb.Sources.SourceLive.SourceEnableToggle}
+ source={source}
+ id={"source_#{source.id}_enabled"}
+ />
+
+ <:col :let={source} label="" class="flex place-content-evenly">
+ <.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
+
+
+
+
+ <.live_pagination_controls page_number={@page} total_pages={@total_pages} />
+
+
diff --git a/lib/pinchflat_web/controllers/sources/source_live/source_enable_toggle.ex b/lib/pinchflat_web/controllers/sources/source_live/source_enable_toggle.ex
new file mode 100644
index 0000000..57bc3b1
--- /dev/null
+++ b/lib/pinchflat_web/controllers/sources/source_live/source_enable_toggle.ex
@@ -0,0 +1,35 @@
+defmodule PinchflatWeb.Sources.SourceLive.SourceEnableToggle do
+ use PinchflatWeb, :live_component
+
+ alias Pinchflat.Sources
+ alias Pinchflat.Sources.Source
+
+ def render(assigns) do
+ ~H"""
+
+ <.form :let={f} for={@form} phx-change="update" phx-target={@myself} class="enabled_toggle_form">
+ <.input id={"source_#{@source_id}_enabled_input"} field={f[:enabled]} type="toggle" />
+
+
+ """
+ end
+
+ def update(assigns, socket) do
+ initial_data = %{
+ source_id: assigns.source.id,
+ form: Sources.change_source(%Source{}, assigns.source)
+ }
+
+ socket
+ |> assign(initial_data)
+ |> then(&{:ok, &1})
+ end
+
+ def handle_event("update", %{"source" => source_params}, %{assigns: assigns} = socket) do
+ assigns.source_id
+ |> Sources.get_source!()
+ |> Sources.update_source(source_params)
+
+ {:noreply, socket}
+ end
+end
diff --git a/lib/pinchflat_web/helpers/pagination_helpers.ex b/lib/pinchflat_web/helpers/pagination_helpers.ex
new file mode 100644
index 0000000..50c3928
--- /dev/null
+++ b/lib/pinchflat_web/helpers/pagination_helpers.ex
@@ -0,0 +1,45 @@
+defmodule PinchflatWeb.Helpers.PaginationHelpers do
+ @moduledoc """
+ Methods for working with pagination, usually in the context of LiveViews or LiveComponents.
+
+ These methods are fairly simple, but they're commonly repeated across different Live entities
+ """
+
+ alias Pinchflat.Repo
+ alias Pinchflat.Utils.NumberUtils
+
+ @doc """
+ Given a query, a page number, and a number of records per page, returns a map of pagination attributes.
+
+ Returns map()
+ """
+ def get_pagination_attributes(query, page, records_per_page) do
+ total_record_count = Repo.aggregate(query, :count, :id)
+ total_pages = max(ceil(total_record_count / records_per_page), 1)
+ clamped_page = NumberUtils.clamp(page, 1, total_pages)
+
+ %{
+ page: clamped_page,
+ total_pages: total_pages,
+ total_record_count: total_record_count,
+ limit: records_per_page,
+ offset: (clamped_page - 1) * records_per_page
+ }
+ end
+
+ @doc """
+ Given a current page number, a direction to move in, and the total number of pages, returns the updated page number.
+ The updated page number is clamped to the range [1, total_pages].
+
+ Returns integer()
+ """
+ def update_page_number(current_page, direction, total_pages) do
+ updated_page =
+ case to_string(direction) do
+ "inc" -> current_page + 1
+ "dec" -> current_page - 1
+ end
+
+ NumberUtils.clamp(updated_page, 1, total_pages)
+ end
+end
diff --git a/lib/pinchflat_web/helpers/sorting_helpers.ex b/lib/pinchflat_web/helpers/sorting_helpers.ex
new file mode 100644
index 0000000..fb65e62
--- /dev/null
+++ b/lib/pinchflat_web/helpers/sorting_helpers.ex
@@ -0,0 +1,20 @@
+defmodule PinchflatWeb.Helpers.SortingHelpers do
+ @moduledoc """
+ Methods for working with sorting, usually in the context of LiveViews or LiveComponents.
+
+ These methods are fairly simple, but they're commonly repeated across different Live entities
+ """
+
+ @doc """
+ Given the old sort attribute, the new sort attribute, and the old sort direction, returns the new sort direction.
+
+ Returns :asc | :desc
+ """
+ def get_sort_direction(old_sort_attr, new_sort_attr, old_sort_direction) do
+ case {new_sort_attr, old_sort_direction} do
+ {^old_sort_attr, :desc} -> :asc
+ {^old_sort_attr, _} -> :desc
+ _ -> :asc
+ end
+ end
+end
diff --git a/test/pinchflat_web/controllers/sources/index_table_live_test.exs b/test/pinchflat_web/controllers/sources/index_table_live_test.exs
index 659bd04..675e47d 100644
--- a/test/pinchflat_web/controllers/sources/index_table_live_test.exs
+++ b/test/pinchflat_web/controllers/sources/index_table_live_test.exs
@@ -1,4 +1,4 @@
-defmodule PinchflatWeb.Sources.IndexTableLiveTest do
+defmodule PinchflatWeb.Sources.SourceLive.IndexTableLiveTest do
use PinchflatWeb.ConnCase
import Phoenix.LiveViewTest
@@ -6,13 +6,13 @@ defmodule PinchflatWeb.Sources.IndexTableLiveTest do
import Pinchflat.ProfilesFixtures
alias Pinchflat.Sources.Source
- alias PinchflatWeb.Sources.IndexTableLive
+ alias PinchflatWeb.Sources.SourceLive.IndexTableLive
describe "initial rendering" do
test "lists all sources", %{conn: conn} do
source = source_fixture()
- {:ok, _view, html} = live_isolated(conn, IndexTableLive)
+ {:ok, _view, html} = live_isolated(conn, IndexTableLive, session: create_session())
assert html =~ source.custom_name
end
@@ -20,7 +20,7 @@ defmodule PinchflatWeb.Sources.IndexTableLiveTest do
test "omits sources that have marked_for_deletion_at set", %{conn: conn} do
source = source_fixture(marked_for_deletion_at: DateTime.utc_now())
- {:ok, _view, html} = live_isolated(conn, IndexTableLive)
+ {:ok, _view, html} = live_isolated(conn, IndexTableLive, session: create_session())
refute html =~ source.custom_name
end
@@ -29,27 +29,102 @@ defmodule PinchflatWeb.Sources.IndexTableLiveTest do
media_profile = media_profile_fixture(marked_for_deletion_at: DateTime.utc_now())
source = source_fixture(media_profile_id: media_profile.id)
- {:ok, _view, html} = live_isolated(conn, IndexTableLive)
+ {:ok, _view, html} = live_isolated(conn, IndexTableLive, session: create_session())
refute html =~ source.custom_name
end
end
- describe "when a source is enabled or disabled" do
+ describe "when testing sorting" do
+ test "sorts by the custom_name by default", %{conn: conn} do
+ source1 = source_fixture(custom_name: "Source_B")
+ source2 = source_fixture(custom_name: "Source_A")
+
+ {:ok, view, _html} = live_isolated(conn, IndexTableLive, session: create_session())
+ assert render_element(view, "tbody tr:first-child") =~ source2.custom_name
+ assert render_element(view, "tbody tr:last-child") =~ source1.custom_name
+ end
+
+ test "clicking the row will change the sort direction", %{conn: conn} do
+ source1 = source_fixture(custom_name: "Source_B")
+ source2 = source_fixture(custom_name: "Source_A")
+
+ {:ok, view, _html} = live_isolated(conn, IndexTableLive, session: create_session())
+
+ # Click the row to change the sort direction
+ click_element(view, "th", "Name")
+
+ assert render_element(view, "tbody tr:first-child") =~ source1.custom_name
+ assert render_element(view, "tbody tr:last-child") =~ source2.custom_name
+ end
+
+ test "clicking a different row will sort by that attribute", %{conn: conn} do
+ source1 = source_fixture(custom_name: "Source_A", enabled: true)
+ source2 = source_fixture(custom_name: "Source_A", enabled: false)
+
+ {:ok, view, _html} = live_isolated(conn, IndexTableLive, session: create_session())
+
+ # Click the row to change the sort field
+ click_element(view, "th", "Enabled?")
+
+ assert render_element(view, "tbody tr:first-child") =~ source2.custom_name
+ assert render_element(view, "tbody tr:last-child") =~ source1.custom_name
+
+ # Click the row to again change the sort direcation
+ click_element(view, "th", "Enabled?")
+ assert render_element(view, "tbody tr:first-child") =~ source1.custom_name
+ assert render_element(view, "tbody tr:last-child") =~ source2.custom_name
+ end
+ end
+
+ describe "when testing pagination" do
+ test "moving to the next page loads new records", %{conn: conn} do
+ source1 = source_fixture(custom_name: "Source_A")
+ source2 = source_fixture(custom_name: "Source_B")
+
+ session = Map.merge(create_session(), %{"results_per_page" => 1})
+ {:ok, view, _html} = live_isolated(conn, IndexTableLive, session: session)
+
+ assert render_element(view, "tbody") =~ source1.custom_name
+ refute render_element(view, "tbody") =~ source2.custom_name
+
+ click_element(view, "span.pagination-next")
+
+ refute render_element(view, "tbody") =~ source1.custom_name
+ assert render_element(view, "tbody") =~ source2.custom_name
+ end
+ end
+
+ describe "when testing the enable toggle" do
test "updates the source's enabled status", %{conn: conn} do
source = source_fixture(enabled: true)
- {:ok, view, _html} = live_isolated(conn, IndexTableLive)
+ {:ok, view, _html} = live_isolated(conn, IndexTableLive, session: create_session())
- params = %{
- "event" => "toggle_enabled",
- "id" => source.id,
- "value" => "false"
- }
-
- # Send an event to the server directly
- render_change(view, "formless-input", params)
+ view
+ |> element(".enabled_toggle_form")
+ |> render_change(%{source: %{"enabled" => false}})
assert %{enabled: false} = Repo.get!(Source, source.id)
end
end
+
+ defp click_element(view, selector, text_filter \\ nil) do
+ view
+ |> element(selector, text_filter)
+ |> render_click()
+ end
+
+ defp render_element(view, selector) do
+ view
+ |> element(selector)
+ |> render()
+ end
+
+ defp create_session do
+ %{
+ "initial_sort_key" => :custom_name,
+ "initial_sort_direction" => :asc,
+ "results_per_page" => 10
+ }
+ end
end
diff --git a/test/pinchflat_web/controllers/sources/source_enable_toggle_test.exs b/test/pinchflat_web/controllers/sources/source_enable_toggle_test.exs
new file mode 100644
index 0000000..e1abd1d
--- /dev/null
+++ b/test/pinchflat_web/controllers/sources/source_enable_toggle_test.exs
@@ -0,0 +1,26 @@
+defmodule PinchflatWeb.Sources.SourceLive.SourceEnableToggleTest do
+ use PinchflatWeb.ConnCase
+
+ import Phoenix.LiveViewTest
+
+ alias PinchflatWeb.Sources.SourceLive.SourceEnableToggle
+
+ describe "initial rendering" do
+ test "renders a toggle in the on position if the source is enabled" do
+ source = %{id: 1, enabled: true}
+
+ html = render_component(SourceEnableToggle, %{id: :foo, source: source})
+
+ # This is checking the Alpine attrs which is a good-enough proxy for the toggle position
+ assert html =~ "{ enabled: true }"
+ end
+
+ test "renders a toggle in the off position if the source is disabled" do
+ source = %{id: 1, enabled: false}
+
+ html = render_component(SourceEnableToggle, %{id: :foo, source: source})
+
+ assert html =~ "{ enabled: false }"
+ end
+ end
+end
diff --git a/test/pinchflat_web/helpers/pagination_helpers_test.exs b/test/pinchflat_web/helpers/pagination_helpers_test.exs
new file mode 100644
index 0000000..c15a2d3
--- /dev/null
+++ b/test/pinchflat_web/helpers/pagination_helpers_test.exs
@@ -0,0 +1,96 @@
+defmodule PinchflatWeb.Helpers.PaginationHelpersTest do
+ use Pinchflat.DataCase
+ import Pinchflat.SourcesFixtures
+
+ alias Pinchflat.Sources.Source
+ alias PinchflatWeb.Helpers.PaginationHelpers
+
+ describe "get_pagination_attributes/3" do
+ test "returns the correct pagination attributes" do
+ source_fixture()
+ query = from(s in Source, select: s.id)
+ page = 1
+ records_per_page = 10
+
+ pagination_attributes = PaginationHelpers.get_pagination_attributes(query, page, records_per_page)
+
+ assert pagination_attributes.page == 1
+ assert pagination_attributes.total_pages == 1
+ assert pagination_attributes.total_record_count == 1
+ assert pagination_attributes.limit == 10
+ assert pagination_attributes.offset == 0
+ end
+
+ test "returns the correct pagination attributes when there are multiple pages" do
+ source_fixture()
+ source_fixture()
+
+ query = from(s in Source, select: s.id)
+ page = 1
+ records_per_page = 1
+
+ pagination_attributes = PaginationHelpers.get_pagination_attributes(query, page, records_per_page)
+
+ assert pagination_attributes.page == 1
+ assert pagination_attributes.total_pages == 2
+ assert pagination_attributes.total_record_count == 2
+ assert pagination_attributes.limit == 1
+ assert pagination_attributes.offset == 0
+ end
+
+ test "returns the correct attributes when on a page other than the first" do
+ source_fixture()
+ source_fixture()
+
+ query = from(s in Source, select: s.id)
+ page = 2
+ records_per_page = 1
+
+ pagination_attributes = PaginationHelpers.get_pagination_attributes(query, page, records_per_page)
+
+ assert pagination_attributes.page == 2
+ assert pagination_attributes.total_pages == 2
+ assert pagination_attributes.total_record_count == 2
+ assert pagination_attributes.limit == 1
+ assert pagination_attributes.offset == 1
+ end
+ end
+
+ describe "update_page_number/3" do
+ test "increments the page number" do
+ current_page = 1
+ total_pages = 2
+
+ updated_page = PaginationHelpers.update_page_number(current_page, :inc, total_pages)
+
+ assert updated_page == 2
+ end
+
+ test "decrements the page number" do
+ current_page = 2
+ total_pages = 2
+
+ updated_page = PaginationHelpers.update_page_number(current_page, :dec, total_pages)
+
+ assert updated_page == 1
+ end
+
+ test "doesn't overflow the page number" do
+ current_page = 2
+ total_pages = 2
+
+ updated_page = PaginationHelpers.update_page_number(current_page, :inc, total_pages)
+
+ assert updated_page == 2
+ end
+
+ test "doesn't underflow the page number" do
+ current_page = 1
+ total_pages = 2
+
+ updated_page = PaginationHelpers.update_page_number(current_page, :dec, total_pages)
+
+ assert updated_page == 1
+ end
+ end
+end
diff --git a/test/pinchflat_web/helpers/sorting_helpers_test.exs b/test/pinchflat_web/helpers/sorting_helpers_test.exs
new file mode 100644
index 0000000..7f1d81b
--- /dev/null
+++ b/test/pinchflat_web/helpers/sorting_helpers_test.exs
@@ -0,0 +1,31 @@
+defmodule PinchflatWeb.Helpers.SortingHelpersTest do
+ use Pinchflat.DataCase
+
+ alias PinchflatWeb.Helpers.SortingHelpers
+
+ describe "get_sort_direction/3" do
+ test "returns the correct sort direction when the new sort attribute is the same as the old sort attribute" do
+ old_sort_attr = "name"
+ new_sort_attr = "name"
+ old_sort_direction = :desc
+
+ assert SortingHelpers.get_sort_direction(old_sort_attr, new_sort_attr, old_sort_direction) == :asc
+ end
+
+ test "returns the correct sort direction when the new sort attribute is the same as the old sort attribute in the other direction" do
+ old_sort_attr = "name"
+ new_sort_attr = "name"
+ old_sort_direction = :asc
+
+ assert SortingHelpers.get_sort_direction(old_sort_attr, new_sort_attr, old_sort_direction) == :desc
+ end
+
+ test "returns the correct sort direction when the new sort attribute is different from the old sort attribute" do
+ old_sort_attr = "name"
+ new_sort_attr = "date"
+ old_sort_direction = :asc
+
+ assert SortingHelpers.get_sort_direction(old_sort_attr, new_sort_attr, old_sort_direction) == :asc
+ end
+ end
+end