diff --git a/Gemfile b/Gemfile index 83d457e..2bfb5d2 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'jbuilder' gem 'bcrypt', '~> 3.1.7' # Pagination [https://ddnexus.github.io/pagy/] -gem 'pagy', '~> 9.3' +gem 'pagy', '~> 43.0' # Charts [https://chartkick.com/] gem 'chartkick' diff --git a/Gemfile.lock b/Gemfile.lock index 2977302..a03e723 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -195,7 +195,9 @@ GEM racc (~> 1.4) noticed (3.0.0) rails (>= 6.1.0) - pagy (9.4.0) + pagy (43.2.4) + json + yaml parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -371,6 +373,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) + yaml (0.4.0) zeitwerk (2.7.4) PLATFORMS @@ -400,7 +403,7 @@ DEPENDENCIES jbuilder minitest (~> 5.25) noticed (~> 3.0) - pagy (~> 9.3) + pagy (~> 43.0) pg (~> 1.1) propshaft puma (>= 5.0) @@ -496,7 +499,7 @@ CHECKSUMS nokogiri (1.19.0-x86_64-linux-gnu) sha256=f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c nokogiri (1.19.0-x86_64-linux-musl) sha256=1c4ca6b381622420073ce6043443af1d321e8ed93cc18b08e2666e5bd02ffae4 noticed (3.0.0) sha256=cbb28f1624b9034117fbe7e1d36facfe624e9ad53a24aa149e2a676336ff8f98 - pagy (9.4.0) sha256=db3f2e043f684155f18f78be62a81e8d033e39b9f97b1e1a8d12ad38d7bce738 + pagy (43.2.4) sha256=b0574f8105e5cc45ed37c4633789a7a64cb517b9e5af2bf697b22c5e880c5f0f parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 @@ -569,6 +572,7 @@ CHECKSUMS websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e + yaml (0.4.0) sha256=240e69d1e6ce3584d6085978719a0faa6218ae426e034d8f9b02fb54d3471942 zeitwerk (2.7.4) sha256=2bef90f356bdafe9a6c2bd32bcd804f83a4f9b8bc27f3600fff051eb3edcec8b BUNDLED WITH diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 185884a..c994d66 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,7 @@ class ApplicationController < ActionController::Base include Authentication include Breadcrumbs - include Pagy::Backend + include Pagy::Method include Authorizable # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. allow_browser versions: :modern diff --git a/app/controllers/problems_controller.rb b/app/controllers/problems_controller.rb index 55585e9..c5c2dcf 100644 --- a/app/controllers/problems_controller.rb +++ b/app/controllers/problems_controller.rb @@ -1,7 +1,7 @@ class ProblemsController < ApplicationController - include Pagy::Backend + include Pagy::Method - rescue_from Pagy::OverflowError, Pagy::VariableError, with: :redirect_to_first_page + rescue_from Pagy::RangeError, Pagy::OptionError, with: :redirect_to_first_page before_action :set_app before_action :set_problem, only: [ :show, :resolve, :unresolve ] @@ -57,7 +57,7 @@ def index end # Pagination with Pagy - @pagy, @problems = pagy(@problems) + @pagy, @problems = pagy(:offset, @problems) end def show diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4c070fb..a33573e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -5,7 +5,7 @@ class UsersController < ApplicationController def index @users = User.all.order(created_at: :desc) - @pagy, @users = pagy(@users) + @pagy, @users = pagy(:offset, @users) end def show diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d7cf47e..532a5e7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,5 @@ module ApplicationHelper - include Pagy::Frontend + include PagyTailwind def nav_link_classes(active:) base = 'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold transition-colors' diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb index c9505d9..89bf7d3 100644 --- a/config/initializers/pagy.rb +++ b/config/initializers/pagy.rb @@ -1,19 +1,16 @@ # frozen_string_literal: true -# Pagy initializer file -# See https://ddnexus.github.io/pagy/guides/quick-start/ +# Pagy initializer file (v43+) +# See https://ddnexus.github.io/pagy/guides/upgrade-guide/ -# Instance variables (defaults shown) -# Pagy::DEFAULT[:limit] = 20 # items per page -# Pagy::DEFAULT[:size] = 7 # nav pages shown -# Pagy::DEFAULT[:ends] = true # if false will show only inner pages - -Pagy::DEFAULT[:limit] = 25 +# Instance variables +Pagy.options[:limit] = 25 +Pagy.options[:raise_range_error] = true # Raise errors for out-of-range pages (enables redirect behavior) # Custom Tailwind-styled pagination helper module PagyTailwind def pagy_tailwind_nav(pagy, id: nil, aria_label: nil, **vars) - p_prev = pagy.prev + p_prev = pagy.previous p_next = pagy.next html = +%(‹ Prev) end - # Page links - pagy.series(**vars).each do |item| + # Page links (series is protected in pagy v43, use send to access it) + pagy.send(:series, **vars).each do |item| html << case item when Integer pagy_tailwind_link(pagy, item, item.to_s, 'rounded-lg border border-gray-300 dark:border-zinc-600 text-gray-700 dark:text-zinc-300 hover:bg-gray-50 dark:hover:bg-zinc-700') when String - %(#{pagy.label_for(item)}) + # Current page - item is already the page number as a string + %(#{item}) when :gap %() end @@ -51,7 +49,7 @@ def pagy_tailwind_nav(pagy, id: nil, aria_label: nil, **vars) end def pagy_tailwind_link(pagy, page, text, classes) - %(#{text}) + %(#{text}) end def pagy_tailwind_info(pagy) @@ -62,5 +60,3 @@ def pagy_tailwind_info(pagy) %(Showing #{from} to #{to} of #{count} results).html_safe end end - -Pagy::Frontend.prepend(PagyTailwind) diff --git a/test/controllers/problems_controller_test.rb b/test/controllers/problems_controller_test.rb index 47374d1..a90f9c8 100644 --- a/test/controllers/problems_controller_test.rb +++ b/test/controllers/problems_controller_test.rb @@ -355,9 +355,10 @@ class ProblemsControllerTest < ActionDispatch::IntegrationTest assert_response :success end - test 'index handles invalid page numbers by redirecting to first page' do + test 'index handles invalid page numbers by normalizing to first page' do + # Pagy 43+ normalizes negative page numbers to page 1 instead of raising errors get app_problems_path(@app, page: -1) - assert_redirected_to app_problems_path(@app) + assert_response :success end test 'index handles overflow page numbers by redirecting to first page' do diff --git a/test/helpers/pagy_tailwind_test.rb b/test/helpers/pagy_tailwind_test.rb new file mode 100644 index 0000000..17b21d3 --- /dev/null +++ b/test/helpers/pagy_tailwind_test.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'pagy/classes/request' +require 'pagy/toolbox/helpers/support/series' +require 'pagy/toolbox/helpers/support/a_lambda' + +class PagyTailwindTest < ActionView::TestCase + include PagyTailwind + + test 'pagy_tailwind_nav renders navigation with previous and next links' do + pagy = create_pagy(count: 100, page: 2, limit: 10) + + html = pagy_tailwind_nav(pagy) + + assert_includes html, 'aria-label="Pagination"' + assert_includes html, '‹ Prev' + assert_includes html, 'Next ›' + assert_includes html, 'href=' + assert html.html_safe? + end + + test 'pagy_tailwind_nav disables previous link on first page' do + pagy = create_pagy(count: 100, page: 1, limit: 10) + + html = pagy_tailwind_nav(pagy) + + # Previous should be a span (disabled), not a link + assert_includes html, '‹ Prev' + # Next should be a link + assert_includes html, 'Next ›' + end + + test 'pagy_tailwind_nav disables next link on last page' do + pagy = create_pagy(count: 30, page: 3, limit: 10) + + html = pagy_tailwind_nav(pagy) + + # Previous should be a link + assert_includes html, '‹ Prev' + # Next should be a span (disabled) + assert_includes html, 'Next ›' + end + + test 'pagy_tailwind_nav highlights current page' do + pagy = create_pagy(count: 100, page: 3, limit: 10) + + html = pagy_tailwind_nav(pagy) + + # Current page should be highlighted with violet background + assert_includes html, 'bg-violet-600 text-white font-medium' + end + + test 'pagy_tailwind_nav renders gap for many pages' do + pagy = create_pagy(count: 500, page: 10, limit: 10) + + html = pagy_tailwind_nav(pagy) + + # Should include ellipsis for gaps + assert_includes html, '…' + end + + test 'pagy_tailwind_nav accepts custom id' do + pagy = create_pagy(count: 100, page: 1, limit: 10) + + html = pagy_tailwind_nav(pagy, id: 'custom-pagination') + + assert_includes html, 'id="custom-pagination"' + end + + test 'pagy_tailwind_nav accepts custom aria_label' do + pagy = create_pagy(count: 100, page: 1, limit: 10) + + html = pagy_tailwind_nav(pagy, aria_label: 'Problem pages') + + assert_includes html, 'aria-label="Problem pages"' + end + + test 'pagy_tailwind_info shows correct range and total' do + pagy = create_pagy(count: 100, page: 2, limit: 10) + + html = pagy_tailwind_info(pagy) + + assert_includes html, 'Showing 11 to 20 of 100 results' + assert html.html_safe? + end + + test 'pagy_tailwind_info shows correct info for first page' do + pagy = create_pagy(count: 50, page: 1, limit: 25) + + html = pagy_tailwind_info(pagy) + + assert_includes html, 'Showing 1 to 25 of 50 results' + end + + test 'pagy_tailwind_info shows correct info for last partial page' do + pagy = create_pagy(count: 33, page: 2, limit: 25) + + html = pagy_tailwind_info(pagy) + + assert_includes html, 'Showing 26 to 33 of 33 results' + end + + private + + def create_pagy(count:, page:, limit:) + # Build options hash similar to how Pagy::Method does it + options = Pagy.options.merge(count:, page:, limit:) + options[:request] = { base_url: 'http://test.host', path: '/test', params: {} } + options[:request] = Pagy::Request.new(options) + Pagy::Offset.new(**options) + end +end