mirror of
https://github.com/kieraneglin/pinchflat.git
synced 2026-01-23 02:24:24 +00:00
[Enhancement] Add sorting, pagination, and new attributes to sources index table (#510)
* WIP - started improving handling of sorting for sources index table * WIP - Added UI to table to indicate sort column and direction * Refactored toggle liveview into a livecomponent * Added sorting for all table attrs * Added pagination to the sources table * Added tests for updated liveviews and live components * Add tests for new helper methods * Added fancy new CSS to my sources table * Added size to sources table * Adds relative div to ensure that sorting arrow doesn't run away * Fixed da tests
This commit is contained in:
parent
e56f39a158
commit
53e106dac2
16 changed files with 547 additions and 67 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<table class={["w-full table-auto bg-boxdark", @table_class]}>
|
||||
<thead>
|
||||
<tr class="text-left bg-meta-4">
|
||||
<th :for={col <- @col} class="px-4 py-4 font-medium text-white xl:pl-11">
|
||||
{col[:label]}
|
||||
<th
|
||||
:for={col <- @col}
|
||||
class={["px-4 py-4 font-medium text-white xl:pl-11", col[:sort_key] && "cursor-pointer"]}
|
||||
phx-click={col[:sort_key] && "sort_update"}
|
||||
phx-value-sort_key={col[:sort_key]}
|
||||
>
|
||||
<div class="relative">
|
||||
{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"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -70,9 +85,9 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
|||
<li>
|
||||
<span
|
||||
class={[
|
||||
"flex h-8 w-8 items-center justify-center rounded",
|
||||
"pagination-prev h-8 w-8 items-center justify-center rounded",
|
||||
@page_number != 1 && "cursor-pointer hover:bg-primary hover:text-white",
|
||||
@page_number == 1 && "cursor-not-allowed"
|
||||
@page_number <= 1 && "cursor-not-allowed"
|
||||
]}
|
||||
phx-click={@page_number != 1 && "page_change"}
|
||||
phx-value-direction="dec"
|
||||
|
|
@ -88,9 +103,9 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
|||
<li>
|
||||
<span
|
||||
class={[
|
||||
"flex h-8 w-8 items-center justify-center rounded",
|
||||
"pagination-next flex h-8 w-8 items-center justify-center rounded",
|
||||
@page_number != @total_pages && "cursor-pointer hover:bg-primary hover:text-white",
|
||||
@page_number == @total_pages && "cursor-not-allowed"
|
||||
@page_number >= @total_pages && "cursor-not-allowed"
|
||||
]}
|
||||
phx-click={@page_number != @total_pages && "page_change"}
|
||||
phx-value-direction="inc"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<span class="flex flex-col items-center py-2">
|
||||
<span class="text-md font-medium">Library Size</span>
|
||||
<h4 class="text-title-md font-bold text-white">
|
||||
<.readable_media_filesize media_filesize={@media_item_size} />
|
||||
<.readable_filesize byte_size={@media_item_size} />
|
||||
</h4>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,12 @@
|
|||
|
||||
<div class="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<div class="flex flex-col gap-10 min-w-max">
|
||||
{live_render(@conn, PinchflatWeb.Sources.IndexTableLive)}
|
||||
</div>
|
||||
{live_render(@conn, PinchflatWeb.Sources.SourceLive.IndexTableLive,
|
||||
session: %{
|
||||
"initial_sort_key" => :custom_name,
|
||||
"initial_sort_direction" => :asc,
|
||||
"results_per_page" => 10
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<div class="flex flex-col min-w-max">
|
||||
<.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}
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="Pending" sort_key="pending_count">
|
||||
<.subtle_link href={~p"/sources/#{source.id}/#tab-pending"}>
|
||||
<.localized_number number={source.pending_count} />
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="Downloaded" sort_key="downloaded_count">
|
||||
<.subtle_link href={~p"/sources/#{source.id}/#tab-downloaded"}>
|
||||
<.localized_number number={source.downloaded_count} />
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="Size" sort_key="media_size_bytes">
|
||||
<.readable_filesize byte_size={source.media_size_bytes} />
|
||||
</:col>
|
||||
<: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}
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="Enabled?" sort_key="enabled">
|
||||
<.live_component
|
||||
module={PinchflatWeb.Sources.SourceLive.SourceEnableToggle}
|
||||
source={source}
|
||||
id={"source_#{source.id}_enabled"}
|
||||
/>
|
||||
</:col>
|
||||
<:col :let={source} label="" class="flex place-content-evenly">
|
||||
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||
</:col>
|
||||
</.table>
|
||||
|
||||
<section class="flex justify-center my-5">
|
||||
<.live_pagination_controls page_number={@page} total_pages={@total_pages} />
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -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"""
|
||||
<div>
|
||||
<.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" />
|
||||
</.form>
|
||||
</div>
|
||||
"""
|
||||
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
|
||||
45
lib/pinchflat_web/helpers/pagination_helpers.ex
Normal file
45
lib/pinchflat_web/helpers/pagination_helpers.ex
Normal file
|
|
@ -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
|
||||
20
lib/pinchflat_web/helpers/sorting_helpers.ex
Normal file
20
lib/pinchflat_web/helpers/sorting_helpers.ex
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
96
test/pinchflat_web/helpers/pagination_helpers_test.exs
Normal file
96
test/pinchflat_web/helpers/pagination_helpers_test.exs
Normal file
|
|
@ -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
|
||||
31
test/pinchflat_web/helpers/sorting_helpers_test.exs
Normal file
31
test/pinchflat_web/helpers/sorting_helpers_test.exs
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue