Merge pull request #22475 from opf/71063-create-a-pagination-component-based-on-the-primer-specification

[71063] Update PVC with new Pagination component and Banner styles
This commit is contained in:
Behrokh Satarnejad
2026-03-25 14:02:04 +01:00
committed by GitHub
parent 5c6b584946
commit 7014e13d3e
17 changed files with 178 additions and 285 deletions
+1 -1
View File
@@ -432,4 +432,4 @@ end
gem "openproject-octicons", "~>19.32.0"
gem "openproject-octicons_helper", "~>19.32.0"
gem "openproject-primer_view_components", "~>0.83.0"
gem "openproject-primer_view_components", "~>0.84.1"
+3 -3
View File
@@ -902,7 +902,7 @@ GEM
actionview
openproject-octicons (= 19.32.1)
railties
openproject-primer_view_components (0.83.0)
openproject-primer_view_components (0.84.1)
actionview (>= 7.2.0)
activesupport (>= 7.2.0)
openproject-octicons (>= 19.30.1)
@@ -1687,7 +1687,7 @@ DEPENDENCIES
openproject-octicons (~> 19.32.0)
openproject-octicons_helper (~> 19.32.0)
openproject-openid_connect!
openproject-primer_view_components (~> 0.83.0)
openproject-primer_view_components (~> 0.84.1)
openproject-recaptcha!
openproject-reporting!
openproject-storages!
@@ -2070,7 +2070,7 @@ CHECKSUMS
openproject-octicons (19.32.1) sha256=32253f3256ad4e1aec36442558ce140623c01e5241d9b90f6eb6d317f462781e
openproject-octicons_helper (19.32.1) sha256=7676059927ae940170fb13d62f88b885985a3f0d483e1bb246475afcffd90f8f
openproject-openid_connect (1.0.0)
openproject-primer_view_components (0.83.0) sha256=5e0b89f6467f5c69f017dd14723be28dcc34c842312999c954184ef5b82b83b9
openproject-primer_view_components (0.84.1) sha256=ea0a8da1bc45c8f0ddc13ab279535297ca63f974da72449fbb3a9e4b4e9753d7
openproject-recaptcha (1.0.0)
openproject-reporting (1.0.0)
openproject-storages (1.0.0)
+63 -38
View File
@@ -39,7 +39,7 @@ module PaginationHelper
return unless paginator.total_entries > 0
content_tag(:div, class: "op-pagination") do
concat pagination_pages_section(paginator, renderer: OpenProject::LinkRenderer, params:, allowed_params:, **)
concat pagination_pages_section(paginator, params:, allowed_params:, **)
concat pagination_options_section(paginator, params:, allowed_params:) if per_page_links
end
end
@@ -113,12 +113,57 @@ module PaginationHelper
private
def pagination_pages_section(paginator, **)
content_tag(:nav, class: "op-pagination--pages", aria: { label: I18n.t(:"js.pagination.page_navigation") }) do
pagination_entries(paginator, **)
def pagination_pages_section(paginator, params:, allowed_params:, **)
content_tag(:div, class: "op-pagination--pages") do
safe_join(
[
render_primer_pagination(paginator, params:, allowed_params:, **),
pagination_range(paginator)
]
)
end
end
def render_primer_pagination(paginator, params:, allowed_params:, turbo: false, turbo_action: nil, **)
link_arguments = {}
link_arguments[:data] = {}
link_arguments[:data][:turbo_stream] = true if turbo
link_arguments[:data][:turbo_action] = turbo_action if turbo_action.present?
link_arguments.delete(:data) if link_arguments[:data].empty?
render(
Primer::OpenProject::Pagination.new(
page_count: paginator.total_pages,
current_page: paginator.current_page,
href_builder: ->(page) { pagination_href(page, params:, allowed_params:) },
link_arguments:
)
)
end
def pagination_href(page, params:, allowed_params:)
allowed_params ||= %w[filters sortBy]
url_for(
params
.merge(page:)
.merge(safe_query_params(allowed_params))
)
end
def pagination_range(paginator)
page_first = paginator.offset + 1
page_last = paginator.offset + paginator.length
total = paginator.total_entries
content_tag(
:div,
"(#{page_first} - #{page_last}/#{total})",
class: "op-pagination--range",
aria: { live: "polite" }
)
end
def pagination_options_section(paginator, params:, allowed_params:)
per_page_options = Setting.per_page_options_array
return "" if per_page_options.empty?
@@ -135,31 +180,11 @@ module PaginationHelper
end
end
##
# Builds the pagination nav with pages and range
def pagination_entries(paginator, **)
page_first = paginator.offset + 1
page_last = paginator.offset + paginator.length
total = paginator.total_entries
content_tag(:ul, class: "op-pagination--items op-pagination--items_start", role: "presentation") do
concat will_paginate(paginator, **, container: false)
concat content_tag(
:li,
"(#{page_first} - #{page_last}/#{total})",
class: "op-pagination--range",
aria: { live: "polite" }
)
end
end
def pagination_options_list(per_pages, current_per_page:, **)
content_tag(:ul, class: "op-pagination--items op-pagination--items_end", role: "presentation") do
safe_join [
content_tag(:li, I18n.t(:label_per_page), class: "op-pagination--label"),
per_pages.map { |per_page| pagination_options_item(per_page, current: per_page == current_per_page, **) }
]
end
safe_join [
content_tag(:span, I18n.t(:label_per_page), class: "op-pagination--label"),
per_pages.map { |per_page| pagination_options_item(per_page, current: per_page == current_per_page, **) }
]
end
##
@@ -167,15 +192,15 @@ module PaginationHelper
# determined from available options in the settings.
def pagination_options_item(per_page, current:, **options)
label = I18n.t("js.pagination.pages.show_per_page", number: per_page)
content_tag(:li, class: ["op-pagination--item", { "op-pagination--item_current": current }]) do
link_to_unless(
current,
per_page,
options.merge(page: 1, per_page:),
class: "op-pagination--item-link", aria: { label: }, target: "_top"
) do
content_tag(:span, per_page, aria: { label:, current: "page" }, tabindex: 0)
end
end
aria_props = { label: }
aria_props[:current] = "page" if current
link_to(
per_page,
options.merge(page: 1, per_page:),
class: "Page",
aria: aria_props,
target: "_top"
)
end
end
@@ -11,6 +11,7 @@
[data-color-mode][data-dark-theme=dark] {
--fgColor-accent: var(--accent-color) !important;
--fgColor-link: var(--accent-color) !important;
--bgColor-accent-emphasis: var(--accent-color) !important;
--control-checked-bgColor-rest: var(--control-checked-color) !important;
--control-checked-bgColor-active: var(--control-checked-color) !important;
--control-checked-bgColor-hover: var(--control-checked-color--major1) !important;
+9
View File
@@ -716,6 +716,15 @@ en:
confirmation_live_message_checked: "The button to proceed is now active."
confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue."
pagination:
label: "Pagination"
prev: "Previous"
prev_page: "Previous Page"
next: "Next"
next_page: "Next Page"
page: "Page %{number}"
page_with_more: "Page %{number}..."
mcp_configurations:
server_url_component:
caption: "The URL at which the OpenProject MCP server will be reachable. Required for setting up MCP clients."
+14 -14
View File
@@ -57,12 +57,12 @@
"@ng-select/ng-select": "^20.1.0",
"@ngneat/content-loader": "^7.0.0",
"@openproject/octicons-angular": "^19.32.0",
"@openproject/primer-view-components": "^0.83.0",
"@openproject/primer-view-components": "^0.84.1",
"@openproject/reactivestates": "^3.0.1",
"@primer/css": "^22.1.0",
"@primer/live-region-element": "^0.8.0",
"@primer/primitives": "^11.5.1",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.83.0",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.84.1",
"@rails/request.js": "^0.0.13",
"@stimulus-components/auto-submit": "^6.0.0",
"@stimulus-components/reveal": "^5.0.0",
@@ -7428,9 +7428,9 @@
}
},
"node_modules/@openproject/primer-view-components": {
"version": "0.83.0",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
"version": "0.84.1",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
"license": "MIT",
"dependencies": {
"@github/auto-check-element": "^6.0.0",
@@ -7832,9 +7832,9 @@
},
"node_modules/@primer/view-components": {
"name": "@openproject/primer-view-components",
"version": "0.83.0",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
"version": "0.84.1",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
"license": "MIT",
"dependencies": {
"@github/auto-check-element": "^6.0.0",
@@ -30413,9 +30413,9 @@
}
},
"@openproject/primer-view-components": {
"version": "0.83.0",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
"version": "0.84.1",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
"requires": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
@@ -30608,9 +30608,9 @@
"integrity": "sha512-NB9uYfJ01FVY6zp+33EoUbJ0paS3JrWY+PqdHPebTvyRtQgL3sX8//3jWqjt3/jL81UMEulJRM2A0hPj0/vFpQ=="
},
"@primer/view-components": {
"version": "npm:@openproject/primer-view-components@0.83.0",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
"version": "npm:@openproject/primer-view-components@0.84.1",
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
"requires": {
"@github/auto-check-element": "^6.0.0",
"@github/auto-complete-element": "^3.8.0",
+2 -2
View File
@@ -112,12 +112,12 @@
"@ng-select/ng-select": "^20.1.0",
"@ngneat/content-loader": "^7.0.0",
"@openproject/octicons-angular": "^19.32.0",
"@openproject/primer-view-components": "^0.83.0",
"@openproject/primer-view-components": "^0.84.1",
"@openproject/reactivestates": "^3.0.1",
"@primer/css": "^22.1.0",
"@primer/live-region-element": "^0.8.0",
"@primer/primitives": "^11.5.1",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.83.0",
"@primer/view-components": "npm:@openproject/primer-view-components@^0.84.1",
"@rails/request.js": "^0.0.13",
"@stimulus-components/auto-submit": "^6.0.0",
"@stimulus-components/reveal": "^5.0.0",
@@ -1,7 +1,7 @@
@if (pagination && isVisible && pagination.total) {
<div class="op-pagination">
<nav class="op-pagination--pages" [attr.aria-label]="text.page_navigation">
<ul class="op-pagination--items op-pagination--items_start" role="presentation">
<ul class="op-pagination--items" role="presentation">
<li [hidden]="pagination.page === 1 || !showPageSelections" class="op-pagination--item">
<button
class="op-pagination--item-link op-pagination--item-link_prev"
@@ -109,7 +109,7 @@
<div class="op-pagination--options">
<nav [attr.aria-label]="text.per_page_navigation">
<ul
class="op-pagination--items op-pagination--items_end"
class="op-pagination--items"
role="presentation">
<li
class="op-pagination--label"
@@ -35,17 +35,19 @@ $pagination--font-size: 0.8125rem
min-height: 45px
&--pages
display: flex
flex-grow: 2
flex-shrink: 2
margin: 10px 5px 10px 0
&--options
display: flex
align-items: center
justify-content: end
flex-grow: 1
flex-shrink: 1
margin: 10px 0 0 5px
@media screen and (max-width: $breakpoint-sm)
display: none
display: none
&--items
list-style-type: none
@@ -55,12 +57,6 @@ $pagination--font-size: 0.8125rem
padding: 0
font-size: $pagination--font-size
&_start
justify-content: flex-start
&_end
justify-content: flex-end
&--item
min-width: 25px
margin: 0 5px 0 0
@@ -126,11 +122,15 @@ $pagination--font-size: 0.8125rem
&--range,
&--info
display: flex
align-items: center
flex: 1
margin: 0 0 0 5px
padding: 3px 0
display: block
@include text-shortener
&--info
flex-basis: auto
&--range,
&--label
color: var(--fgColor-muted)
@@ -166,3 +166,19 @@ ul.SegmentedControl,
.Box-row:is(.Box-row--draggable)
padding-left: 0
// Apply the mobile styles as soon as the banner itself is small
// Styles are copied from the PVC repo
.op-primer-flash
x-banner
container-type: inline-size
display: block
.Banner
@container (max-width: 543.98px)
grid-template-areas: 'visual message close' '. actions actions'
grid-template-columns: min-content 1fr min-content
grid-template-rows: min-content min-content
& .Banner-actions
margin: var(--base-size-8) 0 0 var(--base-size-8)
-143
View File
@@ -1,143 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
class LinkRenderer < ::WillPaginate::ActionView::LinkRenderer
include ActionView::Helpers::OutputSafetyHelper
protected
def merge_get_params(url_params)
params = super
allowed_params ? params.slice(*allowed_params) : params
end
def page_number(page)
label = I18n.t("js.pagination.pages.page_number", number: page)
if page == current_page
tag(:li,
tag(:em, page, "aria-label": label, "aria-current": "page", tabindex: 0),
class: "op-pagination--item op-pagination--item_current")
else
tag(:li,
link(page, page, class: "op-pagination--item-link", "aria-label": label),
class: "op-pagination--item")
end
end
def gap
tag(:li,
tag(:span, "&#x2026;", "aria-hidden": "true") +
tag(:span, I18n.t(:"js.pagination.pages_skipped"), class: "sr-only"),
class: "op-pagination--space")
end
def previous_page
num = @collection.current_page > 1 && (@collection.current_page - 1)
previous_or_next_page(
num,
safe_join_components(
render_octicon(:"chevron-left", class_suffix: "prev"),
I18n.t(:label_previous)
),
"prev"
)
end
def next_page
num = @collection.current_page < total_pages && (@collection.current_page + 1)
previous_or_next_page(
num,
safe_join_components(
I18n.t(:label_next),
render_octicon(:"chevron-right", class_suffix: "next")
),
"next"
)
end
def previous_or_next_page(page, text, class_suffix)
if page
tag(:li,
link(text, page, { class: "op-pagination--item-link op-pagination--item-link_#{class_suffix}" }),
class: "op-pagination--item")
else
""
end
end
private
def link(text, target, attributes)
new_attributes = attributes.dup
new_attributes["data-turbo-stream"] = true if turbo?
new_attributes["data-turbo-action"] = turbo_action if turbo_action.present?
super(text, target, new_attributes)
end
def allowed_params
@options[:allowed_params]
end
# Customize the Turbo visit action for pagination links. Can be set to "advance" or "replace".
# "advance" - push a new entry onto the history stack.
# "replace" - replace the current history entry.
# See: https://turbo.hotwired.dev/reference/attributes
#
# Example: Promoting a Frame Navigation to a Page Visit
# By default navigation within a turbo frame does not change the rest of the browser's state,
# but you can promote a frame navigation a "Visit" by setting the turbo-action attribute to "advance".
# See: https://turbo.hotwired.dev/handbook/frames#promoting-a-frame-navigation-to-a-page-visit
#
def turbo_action
@options[:turbo_action]
end
def turbo?
@options[:turbo]
end
def safe_join_components(*components)
safe_join(components, " ")
end
def render_octicon(icon_name, class_suffix:, **)
@template.render(
Primer::Beta::Octicon.new(
icon_name,
size: :xsmall,
classes: ["op-pagination--item-link-icon", "op-pagination--item-link-icon_#{class_suffix}"],
**
)
)
end
end
end
@@ -1,27 +0,0 @@
<div class="op-pagination">
<nav class="op-pagination--pages">
<ul class="op-pagination--items op-pagination--items_start">
<li class="op-pagination--item -prev"><a class="op-pagination--item-link">Previous</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">1</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">2</a></li>
<li class="op-pagination--item op-pagination--item op-pagination--item_current">3</li>
<li class="op-pagination--item"><a class="op-pagination--item-link">4</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">5</a></li>
<li class="op-pagination--space">…</li>
<li class="op-pagination--item"><a class="op-pagination--item-link">34</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">35</a></li>
<li class="op-pagination--item -next"><a class="op-pagination--item-link">Next</a></li>
<li class="op-pagination--range" title="(1 - 50/820)">(1 - 50/820)</li>
</ul>
</nav>
<div class="op-pagination--options">
<ul class="op-pagination--items op-pagination--items_end">
<li class="op-pagination--label" title="Per page:">Per page:</li>
<li class="op-pagination--item"><a class="op-pagination--item-link">10</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">20</a></li>
<li class="op-pagination--item op-pagination--item op-pagination--item_current">50</li>
<li class="op-pagination--item"><a class="op-pagination--item-link">100</a></li>
<li class="op-pagination--item"><a class="op-pagination--item-link">200</a></li>
</ul>
</div>
</div>
@@ -98,7 +98,9 @@ module Pages
end
def expect_to_be_on_page(number)
expect(page).to have_css(".op-pagination--item_current", text: number)
within ".PaginationContainer" do
expect(page).to have_css("a.Page", text: number, aria: { current: "page" })
end
end
def to_page(number)
@@ -55,7 +55,7 @@
end
end
component.with_row do
component.with_row(py: 0) do
helpers.pagination_links_full(documents)
end
end
@@ -167,7 +167,7 @@ RSpec.describe "Admin lists project mappings for a storage",
aggregate_failures "pagination links maintain the correct url" do
within ".op-pagination" do
pagination_links = page.all(".op-pagination--item-link")
pagination_links = page.all("a.Page")
expect(pagination_links.size).to be_positive
pagination_links.each do |pagination_link|
+27 -24
View File
@@ -74,11 +74,18 @@ RSpec.describe PaginationHelper do
end
it "renders a labelled nav element" do
expect(pagination).to have_element "nav", aria: { label: "Pagination navigation" }
expect(pagination).to have_element "nav", aria: { label: "Pagination" }
end
it "renders 2 presentational lists" do
expect(pagination).to have_css "ul.op-pagination--items[role=presentation]", count: 2
it "renders the main pagination nav" do
expect(pagination).to have_css("nav[aria-label='Pagination']")
end
it "renders the per-page options list" do
expect(pagination).to have_css(".op-pagination--options")
expect(pagination).to have_css("nav", accessible_name: "Items per page selection")
expect(pagination).to have_css(".op-pagination--label", text: "Per page")
expect(pagination).to have_css("a.Page", minimum: 1)
end
it "has a next page link" do
@@ -95,8 +102,9 @@ RSpec.describe PaginationHelper do
it "has links to every page except the current one" do
pages.excluding(current_page).each do |page|
path = work_packages_path(page:)
expect(pagination).to have_link text: page, exact_text: true, href: path, current: nil do |link|
expect(pagination).to have_link(page.to_s, href: path) do |link|
expect(link["aria-label"]).to eq "Page #{page}"
expect(link["aria-current"]).to be_nil
end
end
end
@@ -105,11 +113,9 @@ RSpec.describe PaginationHelper do
expect(pagination).to have_no_link text: current_page, exact_text: true, current: "page"
end
it "has a focusable element for the current page" do
expect(pagination).to have_css ".op-pagination--item_current", text: current_page, exact_text: true
expect(pagination).to have_element text: current_page, exact_text: true, aria: { current: "page" } do |element|
it "renders the current page with aria-current" do
expect(pagination).to have_element(aria: { current: "page" }, text: current_page.to_s) do |element|
expect(element["aria-label"]).to eq "Page #{current_page}"
expect(element["tabindex"]).to eq "0"
end
end
@@ -129,7 +135,10 @@ RSpec.describe PaginationHelper do
href = work_packages_path({ page: i }.merge(params))
expect(pagination).to have_link text: i, exact_text: true, href:, current: nil
expect(pagination).to have_link(i.to_s, href:) do |link|
expect(link["aria-label"]).to eq "Page #{i}"
expect(link["aria-current"]).to be_nil
end
end
end
@@ -143,29 +152,23 @@ RSpec.describe PaginationHelper do
expect(pagination).to have_element "nav", aria: { label: "Items per page selection" }
end
it "has a link to every per page option except the current one" do
it "has a link to every per page option" do
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
[50, 100].each do |other_per_page|
[10, 50, 100].each do |other_per_page|
path = work_packages_path(page: current_page, per_page: other_per_page)
expect(pagination_options).to have_link href: path, current: nil do |link|
expect(pagination_options).to have_link href: path do |link|
expect(link["aria-label"]).to eq "Show #{other_per_page} per page"
end
end
end
end
it "does not have a link to the current per page option" do
it "marks the current per page option with aria-current" do
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
expect(pagination_options).to have_no_link text: per_page, exact_text: true, current: "page"
end
end
it "has a focusable element for the current per page option" do
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
expect(pagination_options).to have_css(".op-pagination--item_current", text: per_page)
expect(pagination_options).to have_element text: per_page, exact_text: true, aria: { current: "page" } do |element|
expect(element["aria-label"]).to eq "Show #{per_page} per page"
expect(element["tabindex"]).to eq "0"
expect(pagination_options).to have_link(per_page.to_s) do |link|
expect(link["aria-label"]).to eq "Show #{per_page} per page"
expect(link["aria-current"]).to eq "page"
expect(link["class"]).to include("Page")
end
end
end
@@ -220,7 +223,7 @@ RSpec.describe PaginationHelper do
end
it "does not render a labelled nav element" do
expect(pagination).to have_no_element "nav", aria: { label: "Pagination navigation" }
expect(pagination).to have_no_element "nav", aria: { label: "Pagination" }
end
end
end
+24 -17
View File
@@ -107,49 +107,56 @@ module Pages
end
def expect_current_page_number(number)
within ".op-pagination--pages" do
expect(page).to have_css(".op-pagination--item_current", text: number)
within ".PaginationContainer" do
expect(page).to have_css("a.Page", text: number.to_s, aria: { current: "page" })
end
end
def expect_total_pages(number)
within ".op-pagination--pages" do
expect(page).to have_css(".op-pagination--item", text: number)
expect(page).to have_no_css(".op-pagination--item", text: number + 1)
expect(page).to have_css(".Page", text: number)
expect(page).to have_no_css(".Page", text: number + 1)
end
end
def expect_page_link(text)
within ".op-pagination--pages" do
expect(page).to have_css("a.op-pagination--item-link", text:)
expect(page).to have_css("a.Page", text:)
end
end
def expect_page_links(model:, current_page: 1)
within ".op-pagination--pages" do
pagination_links = page.all(".op-pagination--item-link")
within ".PaginationContainer" do
pagination_links = page.all("a.Page")
expect(pagination_links.size).to be_positive
page_number_links = pagination_links.reject { |link| link.text =~ /previous|next/i }
page_number_links.each.with_index(1) do |pagination_link, page_number|
page_number_links = pagination_links.select { |link| link["aria-label"]&.match?(/\APage \d+\z/) }
page_number_links.each do |pagination_link|
page_number = pagination_link.text.to_i
uri = URI.parse(pagination_link["href"])
expect(uri.path).to eq(path(model))
expect(uri.query).to include("page=#{page_number}")
end
if current_page > 1
expect(page).to have_link("Previous", href: "#{path(model)}?#{{ page: current_page - 1 }.to_query}")
previous_link = find("a[rel='prev']", visible: true)
previous_uri = URI.parse(previous_link["href"])
expect(previous_uri.path).to eq(path(model))
expect(previous_uri.query).to include("page=#{current_page - 1}")
else
expect(page).to have_link("Next", href: "#{path(model)}?#{{ page: current_page + 1 }.to_query}")
next_link = find("a[rel='next']", visible: true)
next_uri = URI.parse(next_link["href"])
expect(next_uri.path).to eq(path(model))
expect(next_uri.query).to include("page=#{current_page + 1}")
end
end
end
def expect_page_sizes(model:)
within ".op-pagination--options" do
pagination_links = page.all(".op-pagination--item-link")
pagination_links = page.all("a.Page")
expect(pagination_links.size).to be_positive
expect(page).to have_css(".op-pagination--item_current")
pagination_links.each do |pagination_link|
uri = URI.parse(pagination_link["href"])
@@ -443,19 +450,19 @@ module Pages
def set_page_size(size)
within ".op-pagination--options" do
find(".op-pagination--item", text: size).click
find("a.Page", text: size.to_s).click
end
end
def expect_page_size(size)
within ".op-pagination--options" do
expect(page).to have_css(".op-pagination--item_current", text: size)
expect(page).to have_css("a.Page", text: /\A#{size}\z/, aria: { current: "page" })
end
end
def go_to_page(page_number)
within ".op-pagination--pages" do
find(".op-pagination--item-link", text: page_number).click
within ".PaginationContainer" do
click_link accessible_name: "Page #{page_number}"
end
end