@automattic/vip-design-system 2.16.1 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,28 +14,33 @@ import { baseControlBorderStyle as borderStyle } from '../Form/Input.styles';
14
14
 
15
15
  interface FormSelectArrowProps {
16
16
  iconSize?: number;
17
+ separator?: boolean;
17
18
  }
18
19
 
19
- const arrowStyles: ThemeUIStyleObject = {
20
- position: 'absolute',
21
- paddingLeft: 2,
22
- borderLeftWidth: borderStyle.borderWidth,
23
- borderLeftStyle: borderStyle.borderStyle,
24
- borderLeftColor: borderStyle.borderColor,
25
- right: 3,
26
- top: '7px',
27
- pointerEvents: 'none',
28
- svg: {
29
- fill: borderStyle.borderColor,
30
- },
31
- };
32
-
33
20
  export const FormSelectArrow = React.forwardRef< SVGSVGElement, FormSelectArrowProps >(
34
- ( { iconSize = 24, ...props }, forwardRef ) => (
35
- <div ref={ forwardRef as React.RefObject< HTMLDivElement > }>
36
- <MdExpandMore aria-hidden="true" size={ iconSize } sx={ arrowStyles } { ...props } />
37
- </div>
38
- )
21
+ ( { iconSize = 24, separator = true, ...props }, forwardRef ) => {
22
+ const arrowStyles: ThemeUIStyleObject = {
23
+ position: 'absolute',
24
+ right: 3,
25
+ top: '7px',
26
+ pointerEvents: 'none',
27
+ svg: {
28
+ fill: borderStyle.borderColor,
29
+ },
30
+ ...( separator && {
31
+ paddingLeft: 2,
32
+ borderLeftWidth: borderStyle.borderWidth,
33
+ borderLeftStyle: borderStyle.borderStyle,
34
+ borderLeftColor: borderStyle.borderColor,
35
+ } ),
36
+ };
37
+
38
+ return (
39
+ <div ref={ forwardRef as React.RefObject< HTMLDivElement > }>
40
+ <MdExpandMore aria-hidden="true" size={ iconSize } sx={ arrowStyles } { ...props } />
41
+ </div>
42
+ );
43
+ }
39
44
  );
40
45
 
41
46
  FormSelectArrow.displayName = 'FormSelectArrow';
@@ -0,0 +1,210 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import { useState } from 'react';
4
+
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import { Pagination } from './Pagination';
9
+ import { Badge } from '../Badge';
10
+ import { Flex } from '../Flex';
11
+ import { Text } from '../Text';
12
+
13
+ import type { StoryObj, Meta } from '@storybook/react';
14
+
15
+ const meta: Meta< typeof Pagination > = {
16
+ title: 'Pagination',
17
+ component: Pagination,
18
+ parameters: {
19
+ docs: {
20
+ description: {
21
+ component: `
22
+ A Pagination component for navigating paged data.
23
+
24
+ ## Variants
25
+
26
+ - **full** (default): Shows individual page number buttons with ellipsis for large page counts.
27
+ - **compact**: Shows a dropdown page selector instead of individual page numbers.
28
+
29
+ ## Component Properties
30
+ `,
31
+ },
32
+ },
33
+ },
34
+ };
35
+
36
+ export default meta;
37
+
38
+ type Story = StoryObj< typeof Pagination >;
39
+
40
+ const PaginationWithState = ( {
41
+ initialPage = 1,
42
+ totalItems = 200,
43
+ initialItemsPerPage = 20,
44
+ displayItemsPerPageSelector = false,
45
+ ...props
46
+ }: {
47
+ initialPage?: number;
48
+ totalItems?: number;
49
+ initialItemsPerPage?: number;
50
+ variant?: 'full' | 'compact';
51
+ pageSizeOptions?: number[];
52
+ displayItemsPerPageSelector?: boolean;
53
+ } ) => {
54
+ const [ currentPage, setCurrentPage ] = useState( initialPage );
55
+ const [ itemsPerPage, setItemsPerPage ] = useState( initialItemsPerPage );
56
+ const totalPages = Math.ceil( totalItems / itemsPerPage );
57
+
58
+ return (
59
+ <Pagination
60
+ currentPage={ currentPage }
61
+ totalItems={ totalItems }
62
+ itemsPerPage={ itemsPerPage }
63
+ onPageChange={ setCurrentPage }
64
+ displayItemsPerPageSelector={ displayItemsPerPageSelector }
65
+ onItemsPerPageChange={ size => {
66
+ setItemsPerPage( size );
67
+ setCurrentPage( 1 );
68
+ } }
69
+ { ...props }
70
+ >
71
+ <Flex sx={ { justifyContent: 'center', alignItems: 'center', verticalAlign: 'middle' } }>
72
+ <Badge variant="gold" sx={ { mr: 2 } }>
73
+ DEBUG
74
+ </Badge>
75
+ <Text>
76
+ Page { currentPage } of { totalPages }
77
+ </Text>
78
+ </Flex>
79
+ </Pagination>
80
+ );
81
+ };
82
+
83
+ const OpenEndedPaginationWithState = ( {
84
+ initialPage = 1,
85
+ initialItemsPerPage = 20,
86
+ hasNextPage,
87
+ ...props
88
+ }: {
89
+ initialPage?: number;
90
+ initialItemsPerPage?: number;
91
+ hasNextPage?: boolean;
92
+ variant?: 'full' | 'compact';
93
+ } ) => {
94
+ const [ currentPage, setCurrentPage ] = useState( initialPage );
95
+ const [ itemsPerPage, setItemsPerPage ] = useState( initialItemsPerPage );
96
+
97
+ return (
98
+ <Pagination
99
+ currentPage={ currentPage }
100
+ itemsPerPage={ itemsPerPage }
101
+ onPageChange={ setCurrentPage }
102
+ onItemsPerPageChange={ size => {
103
+ setItemsPerPage( size );
104
+ setCurrentPage( 1 );
105
+ } }
106
+ hasNextPage={ hasNextPage }
107
+ { ...props }
108
+ >
109
+ <Flex sx={ { justifyContent: 'center', alignItems: 'center', verticalAlign: 'middle' } }>
110
+ <Badge variant="gold" sx={ { mr: 2 } }>
111
+ DEBUG
112
+ </Badge>
113
+ <Text>Page { currentPage } (open-ended)</Text>
114
+ </Flex>
115
+ </Pagination>
116
+ );
117
+ };
118
+
119
+ export const Default: Story = {
120
+ render: () => <PaginationWithState />,
121
+ };
122
+
123
+ export const Compact: Story = {
124
+ render: () => <PaginationWithState variant="compact" />,
125
+ };
126
+
127
+ export const FewPages: Story = {
128
+ render: () => <PaginationWithState totalItems={ 200 } initialItemsPerPage={ 10 } />,
129
+ };
130
+
131
+ export const MiddlePage: Story = {
132
+ render: () => (
133
+ <PaginationWithState totalItems={ 500 } initialItemsPerPage={ 10 } initialPage={ 25 } />
134
+ ),
135
+ };
136
+
137
+ export const CustomPageSizes: Story = {
138
+ render: () => (
139
+ <PaginationWithState
140
+ totalItems={ 1000 }
141
+ initialItemsPerPage={ 25 }
142
+ pageSizeOptions={ [ 25, 50, 100, 250 ] }
143
+ />
144
+ ),
145
+ };
146
+
147
+ export const WithItemsPerPageSelector: Story = {
148
+ render: () => (
149
+ <PaginationWithState
150
+ totalItems={ 100 }
151
+ initialItemsPerPage={ 25 }
152
+ displayItemsPerPageSelector={ true }
153
+ />
154
+ ),
155
+ };
156
+
157
+ const CursorBasedPaginationWithState = () => {
158
+ const [ currentPage, setCurrentPage ] = useState( 1 );
159
+ const [ itemsPerPage, setItemsPerPage ] = useState( 20 );
160
+ const [ maxVisited, setMaxVisited ] = useState( 1 );
161
+
162
+ const handlePageChange = ( page: number ) => {
163
+ setCurrentPage( page );
164
+ setMaxVisited( prev => Math.max( prev, page ) );
165
+ };
166
+
167
+ const hasNextPage = true; // Simulate always having a next page
168
+ const maxReachablePage = hasNextPage ? maxVisited + 1 : maxVisited;
169
+
170
+ return (
171
+ <Pagination
172
+ currentPage={ currentPage }
173
+ itemsPerPage={ itemsPerPage }
174
+ onPageChange={ handlePageChange }
175
+ onItemsPerPageChange={ size => {
176
+ setItemsPerPage( size );
177
+ setCurrentPage( 1 );
178
+ setMaxVisited( 1 );
179
+ } }
180
+ hasNextPage={ hasNextPage }
181
+ maxReachablePage={ maxReachablePage }
182
+ displayItemsPerPageSelector
183
+ >
184
+ <Flex sx={ { justifyContent: 'center', alignItems: 'center', verticalAlign: 'middle' } }>
185
+ <Badge variant="gold" sx={ { mr: 2 } }>
186
+ DEBUG
187
+ </Badge>
188
+ <Text>
189
+ Page { currentPage } — max reachable: { maxReachablePage }
190
+ </Text>
191
+ </Flex>
192
+ </Pagination>
193
+ );
194
+ };
195
+
196
+ export const OpenEndedCursorBased: Story = {
197
+ render: () => <CursorBasedPaginationWithState />,
198
+ };
199
+
200
+ export const OpenEnded: Story = {
201
+ render: () => <OpenEndedPaginationWithState />,
202
+ };
203
+
204
+ export const OpenEndedCompact: Story = {
205
+ render: () => <OpenEndedPaginationWithState variant="compact" />,
206
+ };
207
+
208
+ export const OpenEndedLastPage: Story = {
209
+ render: () => <OpenEndedPaginationWithState hasNextPage={ false } initialPage={ 15 } />,
210
+ };
@@ -0,0 +1,324 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { axe } from 'jest-axe';
6
+ import '@testing-library/jest-dom';
7
+
8
+ import { Pagination, getPageNumbers } from './Pagination';
9
+
10
+ const defaultProps = {
11
+ currentPage: 1,
12
+ totalItems: 200,
13
+ totalPages: 10,
14
+ itemsPerPage: 20,
15
+ onPageChange: jest.fn(),
16
+ onItemsPerPageChange: jest.fn(),
17
+ };
18
+
19
+ describe( '<Pagination />', () => {
20
+ beforeEach( () => {
21
+ jest.clearAllMocks();
22
+ } );
23
+
24
+ it( 'renders a nav landmark with aria-label', () => {
25
+ render( <Pagination { ...defaultProps } /> );
26
+
27
+ expect( screen.getByRole( 'navigation', { name: 'Pagination' } ) ).toBeInTheDocument();
28
+ } );
29
+
30
+ it( 'renders prev and next arrow buttons', () => {
31
+ render( <Pagination { ...defaultProps } /> );
32
+
33
+ expect( screen.getByRole( 'button', { name: 'Previous page' } ) ).toBeInTheDocument();
34
+ expect( screen.getByRole( 'button', { name: 'Next page' } ) ).toBeInTheDocument();
35
+ } );
36
+
37
+ it( 'disables prev button on first page', () => {
38
+ render( <Pagination { ...defaultProps } currentPage={ 1 } /> );
39
+
40
+ expect( screen.getByRole( 'button', { name: 'Previous page' } ) ).toBeDisabled();
41
+ } );
42
+
43
+ it( 'disables next button on last page', () => {
44
+ render( <Pagination { ...defaultProps } currentPage={ 10 } /> );
45
+
46
+ expect( screen.getByRole( 'button', { name: 'Next page' } ) ).toBeDisabled();
47
+ } );
48
+
49
+ it( 'marks current page with aria-current', () => {
50
+ render( <Pagination { ...defaultProps } currentPage={ 3 } /> );
51
+
52
+ expect( screen.getByRole( 'button', { name: 'Go to page 3' } ) ).toHaveAttribute(
53
+ 'aria-current',
54
+ 'page'
55
+ );
56
+ } );
57
+
58
+ it( 'does not mark non-current pages with aria-current', () => {
59
+ render( <Pagination { ...defaultProps } currentPage={ 3 } /> );
60
+
61
+ expect( screen.getByRole( 'button', { name: 'Go to page 1' } ) ).not.toHaveAttribute(
62
+ 'aria-current'
63
+ );
64
+ } );
65
+
66
+ it( 'calls onPageChange when clicking a page number', async () => {
67
+ const user = userEvent.setup();
68
+ render( <Pagination { ...defaultProps } currentPage={ 1 } /> );
69
+
70
+ await user.click( screen.getByRole( 'button', { name: 'Go to page 2' } ) );
71
+
72
+ expect( defaultProps.onPageChange ).toHaveBeenCalledWith( 2 );
73
+ } );
74
+
75
+ it( 'calls onPageChange when clicking next', async () => {
76
+ const user = userEvent.setup();
77
+ render( <Pagination { ...defaultProps } currentPage={ 3 } /> );
78
+
79
+ await user.click( screen.getByRole( 'button', { name: 'Next page' } ) );
80
+
81
+ expect( defaultProps.onPageChange ).toHaveBeenCalledWith( 4 );
82
+ } );
83
+
84
+ it( 'calls onPageChange when clicking prev', async () => {
85
+ const user = userEvent.setup();
86
+ render( <Pagination { ...defaultProps } currentPage={ 3 } /> );
87
+
88
+ await user.click( screen.getByRole( 'button', { name: 'Previous page' } ) );
89
+
90
+ expect( defaultProps.onPageChange ).toHaveBeenCalledWith( 2 );
91
+ } );
92
+
93
+ it( 'shows ellipsis for large page counts', () => {
94
+ render( <Pagination { ...defaultProps } currentPage={ 10 } totalPages={ 20 } /> );
95
+
96
+ // Intermediate pages are replaced by ellipsis icon
97
+ expect( screen.queryByRole( 'button', { name: 'Go to page 2' } ) ).not.toBeInTheDocument();
98
+ expect( screen.queryByRole( 'button', { name: 'Go to page 3' } ) ).not.toBeInTheDocument();
99
+ } );
100
+
101
+ it( 'does not show ellipsis for small page counts', () => {
102
+ render( <Pagination { ...defaultProps } currentPage={ 1 } totalPages={ 5 } /> );
103
+
104
+ // All pages are rendered when total is small
105
+ expect( screen.getByRole( 'button', { name: 'Go to page 2' } ) ).toBeInTheDocument();
106
+ expect( screen.getByRole( 'button', { name: 'Go to page 3' } ) ).toBeInTheDocument();
107
+ expect( screen.getByRole( 'button', { name: 'Go to page 4' } ) ).toBeInTheDocument();
108
+ expect( screen.getByRole( 'button', { name: 'Go to page 5' } ) ).toBeInTheDocument();
109
+ } );
110
+
111
+ it( 'renders compact variant with Page text', () => {
112
+ render( <Pagination { ...defaultProps } variant="compact" currentPage={ 3 } /> );
113
+
114
+ expect( screen.getByText( 'Page' ) ).toBeInTheDocument();
115
+ expect( screen.getByText( 'of 10' ) ).toBeInTheDocument();
116
+ } );
117
+
118
+ it( 'renders custom pageSizeOptions in the trigger', () => {
119
+ render(
120
+ <Pagination
121
+ { ...defaultProps }
122
+ displayItemsPerPageSelector
123
+ pageSizeOptions={ [ 5, 25, 75 ] }
124
+ itemsPerPage={ 5 }
125
+ />
126
+ );
127
+
128
+ expect( screen.getByRole( 'combobox' ) ).toBeInTheDocument();
129
+ expect( screen.getAllByRole( 'option' ) ).toHaveLength( 3 );
130
+ } );
131
+
132
+ it( 'has no accessibility violations (full variant)', async () => {
133
+ const { container } = render( <Pagination { ...defaultProps } currentPage={ 5 } /> );
134
+
135
+ expect( await axe( container ) ).toHaveNoViolations();
136
+ } );
137
+
138
+ it( 'has no accessibility violations (compact variant)', async () => {
139
+ const { container } = render(
140
+ <Pagination { ...defaultProps } variant="compact" currentPage={ 5 } />
141
+ );
142
+
143
+ expect( await axe( container ) ).toHaveNoViolations();
144
+ } );
145
+ } );
146
+
147
+ describe( 'getPageNumbers', () => {
148
+ it( 'returns all pages when totalPages <= 8', () => {
149
+ expect( getPageNumbers( 1, 5 ) ).toEqual( [ 1, 2, 3, 4, 5 ] );
150
+ expect( getPageNumbers( 3, 7 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7 ] );
151
+ expect( getPageNumbers( 4, 8 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7, 8 ] );
152
+ } );
153
+
154
+ it( 'returns single page', () => {
155
+ expect( getPageNumbers( 1, 1 ) ).toEqual( [ 1 ] );
156
+ } );
157
+
158
+ it( 'always returns 8 items when totalPages > 8', () => {
159
+ for ( let cp = 1; cp <= 20; cp++ ) {
160
+ expect( getPageNumbers( cp, 20 ) ).toHaveLength( 8 );
161
+ }
162
+ } );
163
+
164
+ it( 'shows end ellipsis when current page is near start', () => {
165
+ expect( getPageNumbers( 1, 10 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 'ellipsis', 10 ] );
166
+ expect( getPageNumbers( 3, 10 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 'ellipsis', 10 ] );
167
+ expect( getPageNumbers( 5, 10 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 'ellipsis', 10 ] );
168
+ } );
169
+
170
+ it( 'shows start ellipsis when current page is near end', () => {
171
+ expect( getPageNumbers( 9, 10 ) ).toEqual( [ 1, 'ellipsis', 5, 6, 7, 8, 9, 10 ] );
172
+ expect( getPageNumbers( 8, 10 ) ).toEqual( [ 1, 'ellipsis', 5, 6, 7, 8, 9, 10 ] );
173
+ } );
174
+
175
+ it( 'shows both ellipsis when current page is in the middle', () => {
176
+ expect( getPageNumbers( 10, 20 ) ).toEqual( [ 1, 'ellipsis', 9, 10, 11, 12, 'ellipsis', 20 ] );
177
+ } );
178
+ } );
179
+
180
+ describe( 'getPageNumbers (open-ended with maxReachablePage)', () => {
181
+ it( 'caps pages to maxReachablePage when near start', () => {
182
+ expect( getPageNumbers( 1, undefined, true, 2 ) ).toEqual( [ 1, 2 ] );
183
+ } );
184
+
185
+ it( 'shows all reachable pages when they fit', () => {
186
+ expect( getPageNumbers( 3, undefined, true, 4 ) ).toEqual( [ 1, 2, 3, 4 ] );
187
+ } );
188
+
189
+ it( 'shows ellipsis for large reachable ranges', () => {
190
+ expect( getPageNumbers( 8, undefined, true, 9 ) ).toEqual( [ 1, 'ellipsis', 7, 8, 9 ] );
191
+ } );
192
+
193
+ it( 'shows both ellipsis when end is far from current page', () => {
194
+ expect( getPageNumbers( 8, undefined, true, 15 ) ).toEqual( [
195
+ 1,
196
+ 'ellipsis',
197
+ 7,
198
+ 8,
199
+ 9,
200
+ 10,
201
+ 'ellipsis',
202
+ 15,
203
+ ] );
204
+ } );
205
+
206
+ it( 'returns all pages when maxReachablePage <= 8', () => {
207
+ expect( getPageNumbers( 1, undefined, true, 8 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7, 8 ] );
208
+ } );
209
+
210
+ it( 'does not affect behavior when maxReachablePage is undefined', () => {
211
+ expect( getPageNumbers( 1, undefined, true ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7, 'ellipsis' ] );
212
+ } );
213
+ } );
214
+
215
+ describe( 'getPageNumbers (open-ended)', () => {
216
+ it( 'always returns 8 items when page >= 6', () => {
217
+ for ( let cp = 6; cp <= 20; cp++ ) {
218
+ expect( getPageNumbers( cp ) ).toHaveLength( 8 );
219
+ }
220
+ } );
221
+
222
+ it( 'returns near-start pattern for pages 1-5', () => {
223
+ expect( getPageNumbers( 1 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7, 'ellipsis' ] );
224
+ expect( getPageNumbers( 5 ) ).toEqual( [ 1, 2, 3, 4, 5, 6, 7, 'ellipsis' ] );
225
+ } );
226
+
227
+ it( 'returns middle pattern with trailing ellipsis for higher pages', () => {
228
+ expect( getPageNumbers( 10 ) ).toEqual( [ 1, 'ellipsis', 9, 10, 11, 12, 13, 'ellipsis' ] );
229
+ } );
230
+
231
+ it( 'excludes forward pages and trailing ellipsis when hasNextPage is false', () => {
232
+ expect( getPageNumbers( 1, undefined, false ) ).toEqual( [ 1 ] );
233
+ expect( getPageNumbers( 5, undefined, false ) ).toEqual( [ 1, 2, 3, 4, 5 ] );
234
+ expect( getPageNumbers( 10, undefined, false ) ).toEqual( [
235
+ 1,
236
+ 'ellipsis',
237
+ 5,
238
+ 6,
239
+ 7,
240
+ 8,
241
+ 9,
242
+ 10,
243
+ ] );
244
+ } );
245
+ } );
246
+
247
+ describe( '<Pagination /> with maxReachablePage', () => {
248
+ const maxReachableProps = {
249
+ currentPage: 1,
250
+ itemsPerPage: 20,
251
+ hasNextPage: true,
252
+ maxReachablePage: 2,
253
+ onPageChange: jest.fn(),
254
+ onItemsPerPageChange: jest.fn(),
255
+ };
256
+
257
+ it( 'only renders reachable page buttons', () => {
258
+ render( <Pagination { ...maxReachableProps } /> );
259
+
260
+ expect( screen.getByRole( 'button', { name: 'Go to page 1' } ) ).toBeInTheDocument();
261
+ expect( screen.getByRole( 'button', { name: 'Go to page 2' } ) ).toBeInTheDocument();
262
+ expect( screen.queryByRole( 'button', { name: 'Go to page 3' } ) ).not.toBeInTheDocument();
263
+ expect( screen.queryByRole( 'button', { name: 'Go to page 7' } ) ).not.toBeInTheDocument();
264
+ } );
265
+
266
+ it( 'has no accessibility violations', async () => {
267
+ const { container } = render( <Pagination { ...maxReachableProps } /> );
268
+
269
+ expect( await axe( container ) ).toHaveNoViolations();
270
+ } );
271
+ } );
272
+
273
+ describe( '<Pagination /> open-ended mode', () => {
274
+ const openEndedProps = {
275
+ currentPage: 5,
276
+ itemsPerPage: 20,
277
+ onPageChange: jest.fn(),
278
+ onItemsPerPageChange: jest.fn(),
279
+ };
280
+
281
+ beforeEach( () => {
282
+ jest.clearAllMocks();
283
+ } );
284
+
285
+ it( 'enables "Next" button when totalPages is omitted', () => {
286
+ render( <Pagination { ...openEndedProps } /> );
287
+
288
+ expect( screen.getByRole( 'button', { name: 'Next page' } ) ).not.toBeDisabled();
289
+ } );
290
+
291
+ it( 'disables "Next" button when hasNextPage is false', () => {
292
+ render( <Pagination { ...openEndedProps } hasNextPage={ false } /> );
293
+
294
+ expect( screen.getByRole( 'button', { name: 'Next page' } ) ).toBeDisabled();
295
+ } );
296
+
297
+ it( 'calls onPageChange when clicking next in open-ended mode', async () => {
298
+ const user = userEvent.setup();
299
+ render( <Pagination { ...openEndedProps } /> );
300
+
301
+ await user.click( screen.getByRole( 'button', { name: 'Next page' } ) );
302
+
303
+ expect( openEndedProps.onPageChange ).toHaveBeenCalledWith( 6 );
304
+ } );
305
+
306
+ it( 'renders compact variant with "Page" but without "of Y"', () => {
307
+ render( <Pagination { ...openEndedProps } variant="compact" /> );
308
+
309
+ expect( screen.getByText( 'Page' ) ).toBeInTheDocument();
310
+ expect( screen.queryByText( /of \d+/ ) ).not.toBeInTheDocument();
311
+ } );
312
+
313
+ it( 'has no accessibility violations (open-ended full)', async () => {
314
+ const { container } = render( <Pagination { ...openEndedProps } /> );
315
+
316
+ expect( await axe( container ) ).toHaveNoViolations();
317
+ } );
318
+
319
+ it( 'has no accessibility violations (open-ended compact)', async () => {
320
+ const { container } = render( <Pagination { ...openEndedProps } variant="compact" /> );
321
+
322
+ expect( await axe( container ) ).toHaveNoViolations();
323
+ } );
324
+ } );