add pagination across all admin and shop views
All checks were successful
deploy / deploy (push) Successful in 1m38s
All checks were successful
deploy / deploy (push) Successful in 1m38s
URL-based offset pagination with ?page=N for bookmarkable pages. Admin views use push_patch, shop collection uses navigate links. Responsive on mobile with horizontal-scroll tables and stacking pagination controls. Includes dev seed script for testing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,8 @@ defmodule Berrypod.Newsletter.NotifierTest do
|
||||
|
||||
describe "deliver_test/2" do
|
||||
test "sends test email with [Test] prefix in subject" do
|
||||
campaign = campaign_fixture(subject: "Big launch", body: "Preview this!\n\n{{unsubscribe_url}}")
|
||||
campaign =
|
||||
campaign_fixture(subject: "Big launch", body: "Preview this!\n\n{{unsubscribe_url}}")
|
||||
|
||||
assert {:ok, _} = Notifier.deliver_test(campaign, "admin@example.com")
|
||||
|
||||
|
||||
155
test/berrypod/pagination_test.exs
Normal file
155
test/berrypod/pagination_test.exs
Normal file
@@ -0,0 +1,155 @@
|
||||
defmodule Berrypod.PaginationTest do
|
||||
use Berrypod.DataCase, async: true
|
||||
|
||||
alias Berrypod.Pagination
|
||||
|
||||
describe "page_numbers/1" do
|
||||
test "returns all pages when total <= 7" do
|
||||
assert Pagination.page_numbers(page(1, 1)) == [1]
|
||||
assert Pagination.page_numbers(page(1, 5)) == [1, 2, 3, 4, 5]
|
||||
assert Pagination.page_numbers(page(3, 7)) == [1, 2, 3, 4, 5, 6, 7]
|
||||
end
|
||||
|
||||
test "shows ellipsis for large page counts" do
|
||||
result = Pagination.page_numbers(page(1, 20))
|
||||
assert result == [1, 2, :ellipsis, 20]
|
||||
end
|
||||
|
||||
test "shows window around current page" do
|
||||
result = Pagination.page_numbers(page(10, 20))
|
||||
assert result == [1, :ellipsis, 9, 10, 11, :ellipsis, 20]
|
||||
end
|
||||
|
||||
test "current page near start" do
|
||||
result = Pagination.page_numbers(page(2, 20))
|
||||
assert result == [1, 2, 3, :ellipsis, 20]
|
||||
end
|
||||
|
||||
test "current page near end" do
|
||||
result = Pagination.page_numbers(page(19, 20))
|
||||
assert result == [1, :ellipsis, 18, 19, 20]
|
||||
end
|
||||
|
||||
test "current page at last" do
|
||||
result = Pagination.page_numbers(page(20, 20))
|
||||
assert result == [1, :ellipsis, 19, 20]
|
||||
end
|
||||
|
||||
test "8 pages with current at 4" do
|
||||
result = Pagination.page_numbers(page(4, 8))
|
||||
assert result == [1, :ellipsis, 3, 4, 5, :ellipsis, 8]
|
||||
end
|
||||
end
|
||||
|
||||
describe "showing_text/1" do
|
||||
test "returns showing range" do
|
||||
assert Pagination.showing_text(page(1, 5, 25, 120)) == "Showing 1\u201325 of 120"
|
||||
assert Pagination.showing_text(page(2, 5, 25, 120)) == "Showing 26\u201350 of 120"
|
||||
assert Pagination.showing_text(page(5, 5, 25, 120)) == "Showing 101\u2013120 of 120"
|
||||
end
|
||||
|
||||
test "returns no results when empty" do
|
||||
assert Pagination.showing_text(page(1, 1, 25, 0)) == "No results"
|
||||
end
|
||||
end
|
||||
|
||||
describe "parse_page/1" do
|
||||
test "returns 1 for nil" do
|
||||
assert Pagination.parse_page(%{}) == 1
|
||||
end
|
||||
|
||||
test "parses valid string" do
|
||||
assert Pagination.parse_page(%{"page" => "3"}) == 3
|
||||
end
|
||||
|
||||
test "returns 1 for invalid string" do
|
||||
assert Pagination.parse_page(%{"page" => "abc"}) == 1
|
||||
assert Pagination.parse_page(%{"page" => "0"}) == 1
|
||||
assert Pagination.parse_page(%{"page" => "-1"}) == 1
|
||||
end
|
||||
|
||||
test "accepts integer values" do
|
||||
assert Pagination.parse_page(%{"page" => 5}) == 5
|
||||
end
|
||||
|
||||
test "uses custom key" do
|
||||
assert Pagination.parse_page(%{"p" => "7"}, "p") == 7
|
||||
end
|
||||
end
|
||||
|
||||
describe "paginate/2" do
|
||||
test "paginates a query" do
|
||||
# Create some subscribers as test data
|
||||
for i <- 1..7 do
|
||||
%Berrypod.Newsletter.Subscriber{}
|
||||
|> Berrypod.Newsletter.Subscriber.changeset(%{
|
||||
email: "pag#{i}@example.com",
|
||||
status: "confirmed",
|
||||
confirmed_at: DateTime.utc_now() |> DateTime.truncate(:second),
|
||||
consent_text: "test"
|
||||
})
|
||||
|> Berrypod.Repo.insert!()
|
||||
end
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
query =
|
||||
from(s in Berrypod.Newsletter.Subscriber,
|
||||
order_by: [asc: s.email]
|
||||
)
|
||||
|
||||
# Page 1 of 3 (3 per page, 7 total)
|
||||
result = Pagination.paginate(query, page: 1, per_page: 3)
|
||||
assert length(result.items) == 3
|
||||
assert result.page == 1
|
||||
assert result.per_page == 3
|
||||
assert result.total_count == 7
|
||||
assert result.total_pages == 3
|
||||
|
||||
# Page 3 has 1 item
|
||||
result = Pagination.paginate(query, page: 3, per_page: 3)
|
||||
assert length(result.items) == 1
|
||||
assert result.page == 3
|
||||
|
||||
# Page beyond range is clamped
|
||||
result = Pagination.paginate(query, page: 99, per_page: 3)
|
||||
assert result.page == 3
|
||||
end
|
||||
|
||||
test "handles empty result set" do
|
||||
import Ecto.Query
|
||||
|
||||
query =
|
||||
from(s in Berrypod.Newsletter.Subscriber,
|
||||
where: s.email == "nonexistent@nope.com"
|
||||
)
|
||||
|
||||
result = Pagination.paginate(query, page: 1, per_page: 25)
|
||||
assert result.items == []
|
||||
assert result.total_count == 0
|
||||
assert result.total_pages == 1
|
||||
assert result.page == 1
|
||||
end
|
||||
end
|
||||
|
||||
# Helpers for building test structs
|
||||
defp page(current, total) do
|
||||
%Pagination{
|
||||
items: [],
|
||||
page: current,
|
||||
per_page: 25,
|
||||
total_count: total * 25,
|
||||
total_pages: total
|
||||
}
|
||||
end
|
||||
|
||||
defp page(current, total_pages, per_page, total_count) do
|
||||
%Pagination{
|
||||
items: [],
|
||||
page: current,
|
||||
per_page: per_page,
|
||||
total_count: total_count,
|
||||
total_pages: total_pages
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user