11import { fireEvent , render } from '@testing-library/vue'
2+ import { describe , expect , it } from 'vitest'
23
34import VIcon from '../VIcon/VIcon.vue'
45
56import Pagination from './DsfrPagination.vue'
67
8+ function makePages ( n : number ) {
9+ return Array . from ( { length : n } , ( _ , i ) => ( {
10+ label : String ( i + 1 ) ,
11+ href : `#p${ i + 1 } ` ,
12+ title : `page ${ i + 1 } ` ,
13+ } ) )
14+ }
15+
716describe ( 'DsfrPagination' , ( ) => {
8- it ( 'should render a list of links to give quick access to several pages' , async ( ) => {
17+ it ( 'should render a list of links to give quick access to several pages without title if undefined ' , async ( ) => {
918 // Given
1019 const pages = [
1120 { label : '1' , href : '/#' } ,
@@ -17,7 +26,7 @@ describe('DsfrPagination', () => {
1726 const currentPage = 1
1827
1928 // When
20- const { getByText, emitted } = render ( Pagination , {
29+ const { getByText, getAllByRole , emitted } = render ( Pagination , {
2130 global : {
2231 components : {
2332 VIcon,
@@ -31,9 +40,207 @@ describe('DsfrPagination', () => {
3140
3241 const thirdLink = getByText ( '3' )
3342 await fireEvent . click ( thirdLink )
43+ const pageLinks = getAllByRole ( 'link' , { name : / \d + / } )
3444
3545 // Then
3646 expect ( emitted ( ) [ 'update:current-page' ] ) . toBeTruthy ( )
3747 expect ( emitted ( ) [ 'update:current-page' ] [ 0 ] [ 0 ] ) . toBe ( 2 )
48+ pageLinks . forEach ( ( link ) => {
49+ expect ( link . getAttribute ( 'title' ) ) . toBe ( null )
50+ } )
51+ } )
52+
53+ it ( 'should render a list of links without title if equal to label' , async ( ) => {
54+ // Given
55+ const pages = [
56+ { label : '1' , title : '1' , href : '/#' } ,
57+ { label : '2' , title : '2' , href : '/#' } ,
58+ { label : '3' , title : '3' , href : '/#' } ,
59+ { label : '4' , title : '4' , href : '/#' } ,
60+ { label : '5' , title : '5' , href : '/#' } ,
61+ ]
62+ const currentPage = 1
63+
64+ // When
65+ const { getAllByRole } = render ( Pagination , {
66+ global : {
67+ components : {
68+ VIcon,
69+ } ,
70+ } ,
71+ props : {
72+ pages,
73+ currentPage,
74+ currentPageTitleSuffix : ' - page courante' ,
75+ } ,
76+ } )
77+
78+ const pageLinks = getAllByRole ( 'link' , { name : / \d + / } )
79+
80+ // Then
81+ pageLinks . forEach ( ( link ) => {
82+ if ( link . ariaCurrent !== 'page' ) {
83+ expect ( link . getAttribute ( 'title' ) ) . toBe ( null )
84+ } else {
85+ expect ( link . getAttribute ( 'title' ) ) . equal ( '2 - page courante' )
86+ }
87+ } )
88+ } )
89+
90+ it ( 'should render a list of links with appropriate title' , async ( ) => {
91+ // Given
92+ const pages = makePages ( 6 )
93+
94+ // When
95+ const { getByRole } = render ( Pagination , {
96+ global : { components : { VIcon } } ,
97+ props : {
98+ pages,
99+ currentPage : 2 ,
100+ truncLimit : 4 ,
101+ firstPageTitle : 'Première page' ,
102+ lastPageTitle : 'Dernière page' ,
103+ nextPageTitle : 'Page suivante' ,
104+ prevPageTitle : 'Page précédente' ,
105+ currentPageTitleSuffix : ' - page courante' ,
106+ ellipsisTitle : 'Pages intermédiaires non affichées' ,
107+ } ,
108+ } )
109+
110+ const nextLink = getByRole ( 'link' , { name : 'Page suivante' } )
111+ const prevLink = getByRole ( 'link' , { name : 'Page précédente' } )
112+ const firstLink = getByRole ( 'link' , { name : 'Première page' } )
113+ const lastLink = getByRole ( 'link' , { name : 'Dernière page' } )
114+ const currentLink = getByRole ( 'link' , { current : 'page' } )
115+ // Then
116+ expect ( nextLink . getAttribute ( 'title' ) ) . toBe ( 'Page suivante' )
117+ expect ( prevLink . getAttribute ( 'title' ) ) . toBe ( 'Page précédente' )
118+ expect ( firstLink . getAttribute ( 'title' ) ) . toBe ( 'Première page' )
119+ expect ( lastLink . getAttribute ( 'title' ) ) . toBe ( 'Dernière page' )
120+ expect ( currentLink . getAttribute ( 'title' ) ) . toBe ( 'page 3 - page courante' )
121+ } )
122+
123+ it ( 'renders navigation with default aria-label' , ( ) => {
124+ // Given
125+ const pages = makePages ( 3 )
126+
127+ // When
128+ const { getByRole } = render ( Pagination , {
129+ global : { components : { VIcon } } ,
130+ props : { pages } ,
131+ } )
132+
133+ // Then
134+ expect ( getByRole ( 'navigation' , { name : 'Pagination' } ) ) . toBeTruthy ( )
135+ } )
136+
137+ it ( 'emits update:current-page when using navigation controls and page links' , async ( ) => {
138+ // Given
139+ const pages = makePages ( 5 )
140+
141+ // When
142+ const { getByRole, getByText, emitted } = render ( Pagination , {
143+ global : { components : { VIcon } } ,
144+ props : { pages, currentPage : 1 } ,
145+ } )
146+
147+ await fireEvent . click ( getByRole ( 'link' , { name : 'Page suivante' } ) ) // next -> 2
148+ await fireEvent . click ( getByRole ( 'link' , { name : 'Page précédente' } ) ) // prev -> 0
149+ await fireEvent . click ( getByRole ( 'link' , { name : 'Première page' } ) ) // first -> 0
150+ await fireEvent . click ( getByRole ( 'link' , { name : 'Dernière page' } ) ) // last -> pages.length - 1
151+ await fireEvent . click ( getByText ( '3' ) ) // specific page -> index 2
152+
153+ // Then
154+ const emits = emitted ( ) [ 'update:current-page' ]
155+ expect ( emits ) . toBeTruthy ( )
156+ expect ( emits ! [ 0 ] [ 0 ] ) . toBe ( 2 )
157+ expect ( emits ! [ 1 ] [ 0 ] ) . toBe ( 0 )
158+ expect ( emits ! [ 2 ] [ 0 ] ) . toBe ( 0 )
159+ expect ( emits ! [ 3 ] [ 0 ] ) . toBe ( pages . length - 1 )
160+ expect ( emits ! [ 4 ] [ 0 ] ) . toBe ( 2 )
161+ } )
162+
163+ it ( 'applies truncation and shows ellipsis when pages.length > truncLimit' , ( ) => {
164+ // Given
165+ const pages = makePages ( 15 )
166+ const currentPage = 5
167+
168+ // When
169+ const { getAllByRole, getAllByText } = render ( Pagination , {
170+ global : { components : { VIcon } } ,
171+ props : { pages, currentPage } ,
172+ } )
173+
174+ // Then
175+ // find links whose accessible name contains a digit (covers cases with ellipsis + digits)
176+ const pageLinks = getAllByRole ( 'link' , { name : / \d + / } )
177+ const ellipsis = getAllByText ( '...' )
178+
179+ const firstEllipsis = ellipsis [ 0 ] . textContent ?. trim ( ) ?? ''
180+ expect ( firstEllipsis . includes ( '...' ) ) . toBe ( true )
181+
182+ expect ( pageLinks . length ) . toBeLessThan ( pages . length )
183+
184+ const lastEllipsis = ellipsis [ 1 ] . textContent ?. trim ( ) ?? ''
185+ expect ( lastEllipsis . includes ( '...' ) ) . toBe ( true )
186+ } )
187+
188+ it ( 'should have correct title attributes on page links' , ( ) => {
189+ // Given
190+ const pages = makePages ( 5 )
191+
192+ // When
193+ const { getAllByRole } = render ( Pagination , {
194+ global : { components : { VIcon } } ,
195+ props : { pages, currentPage : 0 , currentPageTitleSuffix : ' - page courante' } ,
196+ } )
197+
198+ // Then
199+ const pageLinks = getAllByRole ( 'link' , { name : / \d + / } )
200+ pageLinks . forEach ( ( link , index ) => {
201+ if ( link . ariaCurrent === 'page' ) {
202+ expect ( link . getAttribute ( 'title' ) ) . toBe ( `page ${ index + 1 } - page courante` )
203+ } else {
204+ expect ( link . getAttribute ( 'title' ) ) . toBe ( `page ${ index + 1 } ` )
205+ }
206+ } )
207+ } )
208+
209+ it ( 'should have correct title attributes on page links when titles are equal to labels' , ( ) => {
210+ // Given
211+ const pages = makePages ( 5 ) . map ( ( page ) => ( { ...page , title : page . label } ) )
212+
213+ // When
214+ const { getAllByRole } = render ( Pagination , {
215+ global : { components : { VIcon } } ,
216+ props : { pages, currentPage : 0 , currentPageTitleSuffix : ' - page courante' } ,
217+ } )
218+
219+ // Then
220+ const pageLinks = getAllByRole ( 'link' , { name : / \d + / } )
221+ pageLinks . forEach ( ( link ) => {
222+ if ( link . ariaCurrent !== 'page' ) {
223+ expect ( link . getAttribute ( 'title' ) ) . toBe ( null )
224+ } else {
225+ expect ( link . getAttribute ( 'title' ) ) . equal ( '1 - page courante' )
226+ }
227+ } )
228+ } )
229+
230+ it ( 'should disable navigation controls and add disabled class at boundaries' , ( ) => {
231+ // Given
232+ const pages = makePages ( 5 )
233+
234+ // When
235+ const { getByRole } = render ( Pagination , {
236+ global : { components : { VIcon } } ,
237+ props : { pages, currentPage : 0 } ,
238+ } )
239+
240+ // Then
241+ expect ( getByRole ( 'link' , { name : 'Première page' } ) . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' )
242+ expect ( getByRole ( 'link' , { name : 'Première page' } ) . classList . contains ( 'fr-pagination__link--disabled' ) ) . toBe ( true )
243+ expect ( getByRole ( 'link' , { name : 'Page précédente' } ) . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' )
244+ expect ( getByRole ( 'link' , { name : 'Page précédente' } ) . classList . contains ( 'fr-pagination__link--disabled' ) ) . toBe ( true )
38245 } )
39246} )
0 commit comments