diff --git a/.changeset/serious-melons-own.md b/.changeset/serious-melons-own.md new file mode 100644 index 00000000000..124bfcd306c --- /dev/null +++ b/.changeset/serious-melons-own.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Pagination: Optimize the page rendering algorithm and prevent layout shifts. diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-colorblind-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-colorblind-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-dimmed-linux.png index e218ec5b0cd..f5ab3fc8b55 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-dimmed-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-high-contrast-linux.png index 1d8e74c1f75..baf44f68958 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-high-contrast-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-tritanopia-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-tritanopia-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-colorblind-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-colorblind-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-colorblind-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-high-contrast-linux.png index 86a42dfef3b..2f505621529 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-high-contrast-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-tritanopia-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-tritanopia-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Default-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-colorblind-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-colorblind-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-dimmed-linux.png index e218ec5b0cd..f5ab3fc8b55 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-dimmed-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-high-contrast-linux.png index 1d8e74c1f75..baf44f68958 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-high-contrast-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-tritanopia-linux.png index 7b494e5c706..9f2e9f30682 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-tritanopia-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-colorblind-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-colorblind-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-colorblind-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-high-contrast-linux.png index 86a42dfef3b..2f505621529 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-high-contrast-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-tritanopia-linux.png index a4fe17ed97b..277a20c467a 100644 Binary files a/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-tritanopia-linux.png and b/.playwright/snapshots/components/Pagination.test.ts-snapshots/Pagehead-Dev-Default-light-tritanopia-linux.png differ diff --git a/packages/react/src/Pagination/Pagination.module.css b/packages/react/src/Pagination/Pagination.module.css index 1f16c3281df..8f5857fc50b 100644 --- a/packages/react/src/Pagination/Pagination.module.css +++ b/packages/react/src/Pagination/Pagination.module.css @@ -103,16 +103,8 @@ box-shadow: inset 0 0 0 3px var(--fgColor-onEmphasis); } -.Page[aria-disabled]:first-child, -.Page[aria-disabled]:hover:first-child { - /* stylelint-disable-next-line primer/spacing */ - margin: 0 2px; - /* stylelint-disable-next-line primer/spacing */ - margin-right: 6px; -} - -.Page[aria-disabled], -.Page[aria-disabled]:hover, +.Page[aria-hidden], +.Page[aria-hidden]:hover, .Page[role='presentation'], .Page[role='presentation']:hover { color: var(--fgColor-disabled); diff --git a/packages/react/src/Pagination/Pagination.tsx b/packages/react/src/Pagination/Pagination.tsx index f28d9f05310..aafea554592 100644 --- a/packages/react/src/Pagination/Pagination.tsx +++ b/packages/react/src/Pagination/Pagination.tsx @@ -91,17 +91,8 @@ const Page = toggleStyledComponent( box-shadow: inset 0 0 0 3px ${get('colors.fg.onEmphasis')}; } - &[aria-disabled], - &[aria-disabled]:hover { - margin: 0 2px; - - &:first-child { - margin-right: 6px; - } - } - - &[aria-disabled], - &[aria-disabled]:hover, + &[aria-hidden], + &[aria-hidden]:hover, &[role='presentation'], &[role='presentation']:hover { color: ${get('colors.primer.fg.disabled')}; // check diff --git a/packages/react/src/Pagination/model.tsx b/packages/react/src/Pagination/model.tsx index 2883f802444..ee9a094562f 100644 --- a/packages/react/src/Pagination/model.tsx +++ b/packages/react/src/Pagination/model.tsx @@ -5,125 +5,126 @@ export function buildPaginationModel( marginPageCount: number, surroundingPageCount: number, ) { - const pages = [] + const prev: PageType = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} + const next: PageType = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} + if (!showPages) { + return [prev, next] + } - if (showPages) { - const pageNums: Array = [] - const addPage = (n: number) => { - if (n >= 1 && n <= pageCount) { - pageNums.push(n) - } - } + if (pageCount <= 0) { + return [prev, {...next, disabled: true}] + } - // Start by defining the window of pages to show around the current page. - // If the window goes off either edge, shift it until it fits. - let extentLeft = currentPage - surroundingPageCount - let extentRight = currentPage + surroundingPageCount - if (extentLeft < 1 && extentRight > pageCount) { - // Our window is larger than the entire range, - // so simply display every page. - extentLeft = 1 - extentRight = pageCount - } else if (extentLeft < 1) { - while (extentLeft < 1) { - extentLeft++ - extentRight++ - } - } else if (extentRight > pageCount) { - while (extentRight > pageCount) { - extentLeft-- - extentRight-- - } - } + const pages: PageType[] = [] - // Next, include the pages in the margins. - // If a margin page is already covered in the window, - // extend the window to the other direction. - for (let i = 1; i <= marginPageCount; i++) { - const leftPage = i - const rightPage = pageCount - (i - 1) - if (leftPage >= extentLeft) { - extentRight++ - } else { - addPage(leftPage) - } - if (rightPage <= extentRight) { - extentLeft-- - } else { - addPage(rightPage) - } - } + // number of pages shown on each side of the current page + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // standardGap: 3 + const standardGap = surroundingPageCount + marginPageCount - for (let i = extentLeft; i <= extentRight; i++) { - addPage(i) - } + // the maximum number of pages that can be shown at a given time + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // maxVisiblePages: 7 + const maxVisiblePages = standardGap + standardGap + 1 - const sorted = pageNums - .slice() - .sort((a, b) => a - b) - .filter((item, idx, ary) => !idx || item !== ary[idx - 1]) - for (let idx = 0; idx < sorted.length; idx++) { - const num = sorted[idx] - const selected = num === currentPage - const last = sorted[idx - 1] - const next = sorted[idx + 1] - const lastDelta = num - last - const nextDelta = num - next - const precedesBreak = nextDelta !== -1 - - if (idx === 0) { - if (num !== 1) { - // If the first page isn't page one, - // we need to add a break - pages.push({ - type: 'BREAK', - num: 1, - }) - } - pages.push({ - type: 'NUM', - num, - selected, - precedesBreak, - }) - } else { - if (lastDelta === 1) { - pages.push({ - type: 'NUM', - num, - selected, - precedesBreak, - }) - } else { - // We skipped some, so add a break - pages.push({ - type: 'BREAK', - num: num - 1, - }) - pages.push({ - type: 'NUM', - num, - selected, - precedesBreak: false, - }) - } - } - } + // if the number of pages is less than the maximum number of pages that can be shown just return all of them + if (pageCount <= maxVisiblePages) { + addPages(1, pageCount, false) + return [prev, ...pages, next] + } + + // startGap is the number of pages hidden by the start ellipsis + // startOffset is the number of pages to offset at the start to compensate + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // startGap: 5 + // startOffset: 0 + // when the margin and the surrounding windows overlap. + // [1, _2_, 3, 4, 5, 6, ..., 15] + // startGap = 0 + // startOffset: -3 <-- + let startGap = 0 + let startOffset = 0 - const lastPage = pages[pages.length - 1] - if (lastPage.type === 'NUM' && lastPage.num !== pageCount) { - // The last page we rendered wasn't the actual last page, - // so we need an additional break + // When there is overlap + if (currentPage - standardGap - 1 <= 1) { + startOffset = currentPage - standardGap - 2 + } else { + startGap = currentPage - standardGap - 1 + } + + // These are equivalent to startGap and startOffset but at the end of the list + let endGap = 0 + let endOffset = 0 + + // When there is overlap + if (pageCount - currentPage - standardGap <= 1) { + endOffset = pageCount - currentPage - standardGap - 1 + } else { + endGap = pageCount - currentPage - standardGap + } + + const hasStartEllipsis = startGap > 0 + const hasEndEllipsis = endGap > 0 + + // add pages "before" the start ellipsis (if any) + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // marginPageCount: 1 + // addPages(1, 1, true) + addPages(1, marginPageCount, hasStartEllipsis) + + if (hasStartEllipsis) { + addEllipsis(marginPageCount) + } + + // add middle pages + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // marginPageCount: 1 + // surroundingPageCount: 2 + // startGap: 5 + // startOffset: 0 + // endGap: 3 + // endOffset: 0 + // addPages(7, 11, true) + addPages( + marginPageCount + startGap + endOffset + 1, + pageCount - startOffset - endGap - marginPageCount, + hasEndEllipsis, + ) + + if (hasEndEllipsis) { + addEllipsis(pageCount - startOffset - endGap - marginPageCount) + } + + // add pages "after" the start ellipsis (if any) + // [1, ..., 7, 8, _9_, 10, 11, ..., 15] + // marginPageCount: 1 + // surroundingPageCount: 2 + // startGap: 5 + // startOffset: 0 + // endGap: 3 + // endOffset: 0 + // addPages(15, 15) + addPages(pageCount - marginPageCount + 1, pageCount) + + return [prev, ...pages, next] + + function addEllipsis(previousPage: number): void { + pages.push({ + type: 'BREAK', + num: previousPage + 1, + }) + } + + function addPages(start: number, end: number, precedesBreak: boolean = false): void { + for (let i = start; i <= end; i++) { pages.push({ - type: 'BREAK', - num: pageCount, + type: 'NUM', + num: i, + selected: i === currentPage, + precedesBreak: i === end && precedesBreak, }) } } - - const prev = {type: 'PREV', num: currentPage - 1, disabled: currentPage === 1} - const next = {type: 'NEXT', num: currentPage + 1, disabled: currentPage === pageCount} - return [prev, ...pages, next] } type PageType = { @@ -148,7 +149,7 @@ export function buildComponentData( key = 'page-prev' content = 'Previous' if (page.disabled) { - Object.assign(props, {'aria-hidden': 'true'}) + Object.assign(props, {rel: 'prev', 'aria-hidden': 'true'}) } else { Object.assign(props, { rel: 'prev', @@ -163,7 +164,7 @@ export function buildComponentData( key = 'page-next' content = 'Next' if (page.disabled) { - Object.assign(props, {'aria-hidden': 'true'}) + Object.assign(props, {rel: 'next', 'aria-hidden': 'true'}) } else { Object.assign(props, { rel: 'next', diff --git a/packages/react/src/__tests__/Pagination/PaginationModel.test.tsx b/packages/react/src/__tests__/Pagination/PaginationModel.test.tsx index 3a3ba2e6273..a7ca5a88c1e 100644 --- a/packages/react/src/__tests__/Pagination/PaginationModel.test.tsx +++ b/packages/react/src/__tests__/Pagination/PaginationModel.test.tsx @@ -14,6 +14,170 @@ function last(array: Array, count = 1) { } describe('Pagination model', () => { + it('correctly handles negative pages', () => { + const model = buildPaginationModel(-10, 1, true, 1, 2) + expect(first(model).type).toEqual('PREV') + expect(first(model).disabled).toBe(true) + expect(last(model).type).toEqual('NEXT') + expect(last(model).disabled).toBe(true) + expect(model.length).toBe(2) + }) + + it('correctly handles zero pages', () => { + const model = buildPaginationModel(0, 1, true, 1, 2) + expect(first(model).type).toEqual('PREV') + expect(first(model).disabled).toBe(true) + expect(last(model).type).toEqual('NEXT') + expect(last(model).disabled).toBe(true) + expect(model.length).toBe(2) + }) + + it('correctly handles 1 page', () => { + const model = buildPaginationModel(1, 1, true, 1, 2) + expect(first(model).type).toEqual('PREV') + expect(first(model).disabled).toBe(true) + expect(last(model).type).toEqual('NEXT') + expect(last(model).disabled).toBe(true) + expect(model.length).toBe(3) + }) + + it('correctly handles zero margin pages', () => { + const model = buildPaginationModel(6, 2, true, 0, 2) + + const expected = [ + { + type: 'PREV', + num: 1, + disabled: false, + }, + { + type: 'NUM', + num: 1, + selected: false, + precedesBreak: false, + }, + { + type: 'NUM', + num: 2, + selected: true, + precedesBreak: false, + }, + { + type: 'NUM', + num: 3, + selected: false, + precedesBreak: false, + }, + { + type: 'NUM', + num: 4, + selected: false, + precedesBreak: false, + }, + { + type: 'NUM', + num: 5, + selected: false, + precedesBreak: false, + }, + { + type: 'NUM', + num: 6, + selected: false, + precedesBreak: true, + }, + { + type: 'BREAK', + num: 7, + }, + { + type: 'NEXT', + num: 3, + disabled: false, + }, + ] + + expect(model).toMatchObject(expected) + }) + + it('correctly handles zero surrounding pages', () => { + const model = buildPaginationModel(7, 4, true, 1, 0) + + const expected = [ + { + type: 'PREV', + num: 3, + disabled: false, + }, + { + type: 'NUM', + num: 1, + selected: false, + precedesBreak: true, + }, + { + type: 'BREAK', + num: 2, + }, + { + type: 'NUM', + num: 4, + selected: true, + precedesBreak: true, + }, + { + type: 'BREAK', + num: 5, + }, + { + type: 'NUM', + num: 7, + selected: false, + precedesBreak: false, + }, + { + type: 'NEXT', + num: 5, + disabled: false, + }, + ] + + expect(model).toMatchObject(expected) + }) + + it('correctly handles zero margin and surrounding pages', () => { + const model = buildPaginationModel(50, 3, true, 0, 0) + + const expected = [ + { + type: 'PREV', + num: 2, + disabled: false, + }, + { + type: 'BREAK', + num: 1, + }, + { + type: 'NUM', + num: 3, + selected: true, + precedesBreak: true, + }, + { + type: 'BREAK', + num: 4, + }, + { + type: 'NEXT', + num: 4, + disabled: false, + }, + ] + + expect(model).toMatchObject(expected) + }) + it('sets disabled on prev links', () => { const model1 = buildPaginationModel(10, 1, true, 1, 2) expect(first(model1).type).toEqual('PREV') @@ -94,26 +258,60 @@ describe('Pagination model', () => { {type: 'NUM', num: 2, selected: true}, {type: 'NUM', num: 3}, // normally with a surround of 1, only 1 and 3 would be shown - // however, since 1 was already shown, we extend to 4 - {type: 'NUM', num: 4, precedesBreak: true}, + // however, since we don't overlap, the window is extended to 5 + {type: 'NUM', num: 4}, + {type: 'NUM', num: 5, precedesBreak: true}, {type: 'BREAK'}, ] - expect(first(model, 6)).toMatchObject(expected) + expect(first(model, 7)).toMatchObject(expected) }) it('adds items to the left if it hits bounds to the right', () => { const model = buildPaginationModel(15, 14, true, 1, 1) const expected = [ // normally with a surround of 1, only 13 and 15 would be shown - // however, since 15 was already shown, we extend to 12 + // however, since we don't overlap, the window is extended to 11 {type: 'BREAK'}, + {type: 'NUM', num: 11}, {type: 'NUM', num: 12}, {type: 'NUM', num: 13}, {type: 'NUM', num: 14, selected: true}, {type: 'NUM', num: 15}, {type: 'NEXT', num: 15}, ] - expect(last(model, 6)).toMatchObject(expected) + expect(last(model, 7)).toMatchObject(expected) + }) + + it('adds a page when there would be only one page hidden by the left ellipsis', () => { + const model = buildPaginationModel(15, 5, true, 1, 2) + const expected = [ + {type: 'PREV', num: 4}, + {type: 'NUM', num: 1}, + {type: 'NUM', num: 2}, + {type: 'NUM', num: 3}, + {type: 'NUM', num: 4}, + {type: 'NUM', num: 5, selected: true}, + {type: 'NUM', num: 6}, + {type: 'NUM', num: 7, precedesBreak: true}, + {type: 'BREAK'}, + ] + expect(first(model, 9)).toMatchObject(expected) + }) + + it('adds a page when there would be only one page hidden by the right ellipsis', () => { + const model = buildPaginationModel(15, 11, true, 1, 2) + const expected = [ + {type: 'BREAK'}, + {type: 'NUM', num: 9}, + {type: 'NUM', num: 10}, + {type: 'NUM', num: 11, selected: true}, + {type: 'NUM', num: 12}, + {type: 'NUM', num: 13}, + {type: 'NUM', num: 14}, + {type: 'NUM', num: 15}, + {type: 'NEXT', num: 12}, + ] + expect(last(model, 9)).toMatchObject(expected) }) it('correctly creates breaks next to the next/prev links when margin is 0', () => { @@ -124,7 +322,7 @@ describe('Pagination model', () => { {type: 'NUM', num: 4}, {type: 'NUM', num: 5, selected: true}, {type: 'NUM', num: 6, precedesBreak: true}, - {type: 'BREAK', num: 10}, + {type: 'BREAK', num: 7}, {type: 'NEXT'}, ] expect(model).toMatchObject(expected)