From ce2f9a20ebe33754cf49ccd75d1d86d0b438eda4 Mon Sep 17 00:00:00 2001 From: lprnmns Date: Fri, 4 Sep 2026 16:42:31 +0300 Subject: [PATCH 1/6] [material-ui][Pagination] Hide ellipses with zero boundary and sibling counts --- .../src/usePagination/usePagination.js | 40 ++++++++++--------- .../src/usePagination/usePagination.test.js | 8 +--- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/mui-material/src/usePagination/usePagination.js b/packages/mui-material/src/usePagination/usePagination.js index 60f00c5ca9b022..6d238d26221d44 100644 --- a/packages/mui-material/src/usePagination/usePagination.js +++ b/packages/mui-material/src/usePagination/usePagination.js @@ -71,28 +71,32 @@ export default function usePagination(props = {}) { const itemList = [ ...(showFirstButton ? ['first'] : []), ...(hidePrevButton ? [] : ['previous']), - ...startPages, + ...(boundaryCount === 0 && siblingCount === 0 + ? [page] + : [ + ...startPages, - // Start ellipsis - // eslint-disable-next-line no-nested-ternary - ...(siblingsStart > boundaryCount + 2 - ? ['start-ellipsis'] - : boundaryCount + 1 < count - boundaryCount - ? [boundaryCount + 1] - : []), + // Start ellipsis + // eslint-disable-next-line no-nested-ternary + ...(siblingsStart > boundaryCount + 2 + ? ['start-ellipsis'] + : boundaryCount + 1 < count - boundaryCount + ? [boundaryCount + 1] + : []), - // Sibling pages - ...range(siblingsStart, siblingsEnd), + // Sibling pages + ...range(siblingsStart, siblingsEnd), - // End ellipsis - // eslint-disable-next-line no-nested-ternary - ...(siblingsEnd < count - boundaryCount - 1 - ? ['end-ellipsis'] - : count - boundaryCount > boundaryCount - ? [count - boundaryCount] - : []), + // End ellipsis + // eslint-disable-next-line no-nested-ternary + ...(siblingsEnd < count - boundaryCount - 1 + ? ['end-ellipsis'] + : count - boundaryCount > boundaryCount + ? [count - boundaryCount] + : []), - ...endPages, + ...endPages, + ]), ...(hideNextButton ? [] : ['next']), ...(showLastButton ? ['last'] : []), ]; diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index d1605f245497dc..78119f24ac3fff 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -157,13 +157,7 @@ describe('usePagination', () => { items = renderHook(() => usePagination({ count: 11, page: 6, boundaryCount: 0, siblingCount: 0 }), ).result.current.items; - expect(serialize(items)).to.deep.equal([ - 'previous', - 'start-ellipsis', - 6, - 'end-ellipsis', - 'next', - ]); + expect(serialize(items)).to.deep.equal(['previous', 6, 'next']); items = renderHook(() => usePagination({ count: 11, page: 6, boundaryCount: 0, siblingCount: 1 }), From f34b0a1e04f6bfb6de7c5fc992396cff43dd39e7 Mon Sep 17 00:00:00 2001 From: lprnmns Date: Wed, 9 Sep 2026 13:29:01 +0300 Subject: [PATCH 2/6] [material-ui][Pagination] Handle compact pagination edge cases --- packages/mui-material/src/usePagination/usePagination.js | 6 +++++- .../mui-material/src/usePagination/usePagination.test.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/mui-material/src/usePagination/usePagination.js b/packages/mui-material/src/usePagination/usePagination.js index 6d238d26221d44..e58a46583c2656 100644 --- a/packages/mui-material/src/usePagination/usePagination.js +++ b/packages/mui-material/src/usePagination/usePagination.js @@ -71,7 +71,11 @@ export default function usePagination(props = {}) { const itemList = [ ...(showFirstButton ? ['first'] : []), ...(hidePrevButton ? [] : ['previous']), - ...(boundaryCount === 0 && siblingCount === 0 + ...(boundaryCount === 0 && + siblingCount === 0 && + page >= 1 && + page <= count && + (siblingsStart > boundaryCount + 2 || siblingsEnd < count - boundaryCount - 1) ? [page] : [ ...startPages, diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index fdef2afe17412b..c42310dc11c648 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -204,7 +204,7 @@ describe('usePagination', () => { items = renderHook(() => usePagination({ count: 4, page: 1, boundaryCount: 0, siblingCount: 0 }), ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); + expect(serialize(items)).to.deep.equal(['previous', 1, 'next']); }); it('should stay navigable without previous & next buttons', () => { From 65d4dd87d6768262cf0313168cb1b7337c87a7e4 Mon Sep 17 00:00:00 2001 From: Janpot <2109932+Janpot@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:55:29 +0200 Subject: [PATCH 3/6] [material-ui][Pagination] Add tests for page links near the edges --- .../src/Pagination/Pagination.test.js | 17 +++++ .../src/usePagination/usePagination.test.js | 69 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/packages/mui-material/src/Pagination/Pagination.test.js b/packages/mui-material/src/Pagination/Pagination.test.js index b26afd19e2102b..b28278a687c59f 100644 --- a/packages/mui-material/src/Pagination/Pagination.test.js +++ b/packages/mui-material/src/Pagination/Pagination.test.js @@ -279,6 +279,23 @@ describe('', () => { expect(resultsRef.current).toHaveFocus(); }); + it('keeps a page reachable when both navigation buttons are hidden', () => { + render( + , + ); + + const buttons = screen.getAllByRole('button'); + expect(buttons).to.have.length(2); + expect(buttons.map((button) => button.textContent)).to.deep.equal(['3', '4']); + }); + it('renders no page button when count is zero', () => { render(); diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index c42310dc11c648..5c076d8ed63538 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -220,4 +220,73 @@ describe('usePagination', () => { ).result.current.items; expect(serialize(items)).to.deep.equal([1, 2, 3]); }); + + it('should link a neighbouring page when the current page is near an edge', () => { + [4, 5, 8, 11].forEach((count) => { + [1, 2, count - 1, count].forEach((page) => { + const items = renderHook(() => + usePagination({ count, page, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + const pages = serialize(items).filter((item) => typeof item === 'number'); + expect(pages.length, `count=${count} page=${page}`).to.be.at.least(2); + }); + }); + }); + + it('should keep the neighbouring page linked when count is 4', () => { + let items; + + items = renderHook(() => + usePagination({ count: 4, page: 2, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); + + items = renderHook(() => + usePagination({ count: 4, page: 3, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 3, 4, 'next']); + + items = renderHook(() => + usePagination({ count: 4, page: 4, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 3, 4, 'next']); + }); + + it('should keep the neighbouring page linked on the first & last pages', () => { + let items; + + items = renderHook(() => + usePagination({ count: 11, page: 1, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); + + items = renderHook(() => + usePagination({ count: 11, page: 2, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); + + items = renderHook(() => + usePagination({ count: 11, page: 10, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 10, 11, 'next']); + + items = renderHook(() => + usePagination({ count: 11, page: 11, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 10, 11, 'next']); + }); + + it('should stay navigable without previous & next buttons when count is 4', () => { + const items = renderHook(() => + usePagination({ + count: 4, + page: 3, + boundaryCount: 0, + siblingCount: 0, + hidePrevButton: true, + hideNextButton: true, + }), + ).result.current.items; + expect(serialize(items)).to.deep.equal(['start-ellipsis', 3, 4]); + }); }); From 0e0dcebd92961e838d589114d32ed851c10f6bf9 Mon Sep 17 00:00:00 2001 From: lprnmns Date: Thu, 24 Sep 2026 14:38:56 +0300 Subject: [PATCH 4/6] [material-ui][Pagination] Keep adjacent page links with compact settings --- packages/mui-material/src/usePagination/usePagination.js | 4 ++-- packages/mui-material/src/usePagination/usePagination.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mui-material/src/usePagination/usePagination.js b/packages/mui-material/src/usePagination/usePagination.js index e58a46583c2656..d63740b42549f1 100644 --- a/packages/mui-material/src/usePagination/usePagination.js +++ b/packages/mui-material/src/usePagination/usePagination.js @@ -73,8 +73,8 @@ export default function usePagination(props = {}) { ...(hidePrevButton ? [] : ['previous']), ...(boundaryCount === 0 && siblingCount === 0 && - page >= 1 && - page <= count && + page > 2 && + page < count - 1 && (siblingsStart > boundaryCount + 2 || siblingsEnd < count - boundaryCount - 1) ? [page] : [ diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index 5c076d8ed63538..af6413e52422da 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -204,7 +204,7 @@ describe('usePagination', () => { items = renderHook(() => usePagination({ count: 4, page: 1, boundaryCount: 0, siblingCount: 0 }), ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 1, 'next']); + expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); }); it('should stay navigable without previous & next buttons', () => { From 7dfd9d992466a5bf71b59dadf7b51a5ab8b3548e Mon Sep 17 00:00:00 2001 From: lprnmns Date: Tue, 27 Oct 2026 14:12:45 +0300 Subject: [PATCH 5/6] [material-ui][Pagination] Keep ellipses with zero counts --- .../src/usePagination/usePagination.js | 44 ++++++++----------- .../src/usePagination/usePagination.test.js | 26 ++++++++++- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/packages/mui-material/src/usePagination/usePagination.js b/packages/mui-material/src/usePagination/usePagination.js index d63740b42549f1..60f00c5ca9b022 100644 --- a/packages/mui-material/src/usePagination/usePagination.js +++ b/packages/mui-material/src/usePagination/usePagination.js @@ -71,36 +71,28 @@ export default function usePagination(props = {}) { const itemList = [ ...(showFirstButton ? ['first'] : []), ...(hidePrevButton ? [] : ['previous']), - ...(boundaryCount === 0 && - siblingCount === 0 && - page > 2 && - page < count - 1 && - (siblingsStart > boundaryCount + 2 || siblingsEnd < count - boundaryCount - 1) - ? [page] - : [ - ...startPages, + ...startPages, - // Start ellipsis - // eslint-disable-next-line no-nested-ternary - ...(siblingsStart > boundaryCount + 2 - ? ['start-ellipsis'] - : boundaryCount + 1 < count - boundaryCount - ? [boundaryCount + 1] - : []), + // Start ellipsis + // eslint-disable-next-line no-nested-ternary + ...(siblingsStart > boundaryCount + 2 + ? ['start-ellipsis'] + : boundaryCount + 1 < count - boundaryCount + ? [boundaryCount + 1] + : []), - // Sibling pages - ...range(siblingsStart, siblingsEnd), + // Sibling pages + ...range(siblingsStart, siblingsEnd), - // End ellipsis - // eslint-disable-next-line no-nested-ternary - ...(siblingsEnd < count - boundaryCount - 1 - ? ['end-ellipsis'] - : count - boundaryCount > boundaryCount - ? [count - boundaryCount] - : []), + // End ellipsis + // eslint-disable-next-line no-nested-ternary + ...(siblingsEnd < count - boundaryCount - 1 + ? ['end-ellipsis'] + : count - boundaryCount > boundaryCount + ? [count - boundaryCount] + : []), - ...endPages, - ]), + ...endPages, ...(hideNextButton ? [] : ['next']), ...(showLastButton ? ['last'] : []), ]; diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index af6413e52422da..098b0830ad3f31 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -157,7 +157,13 @@ describe('usePagination', () => { items = renderHook(() => usePagination({ count: 11, page: 6, boundaryCount: 0, siblingCount: 0 }), ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 6, 'next']); + expect(serialize(items)).to.deep.equal([ + 'previous', + 'start-ellipsis', + 6, + 'end-ellipsis', + 'next', + ]); items = renderHook(() => usePagination({ count: 11, page: 6, boundaryCount: 0, siblingCount: 1 }), @@ -173,6 +179,24 @@ describe('usePagination', () => { ]); }); + it('keeps ellipses when boundary and sibling counts are zero', () => { + const expectedByPage = [ + [1, ['previous', 1, 2, 'end-ellipsis', 'next']], + [2, ['previous', 1, 2, 'end-ellipsis', 'next']], + [5, ['previous', 'start-ellipsis', 5, 'end-ellipsis', 'next']], + [7, ['previous', 'start-ellipsis', 7, 'end-ellipsis', 'next']], + [9, ['previous', 'start-ellipsis', 9, 10, 'next']], + [10, ['previous', 'start-ellipsis', 9, 10, 'next']], + ]; + + expectedByPage.forEach(([page, expected]) => { + const items = renderHook(() => + usePagination({ count: 10, page, boundaryCount: 0, siblingCount: 0 }), + ).result.current.items; + expect(serialize(items), `page=${page}`).to.deep.equal(expected); + }); + }); + it('should never render a page item outside of the count range', () => { [0, 1, 2, 3, 4, 5, 11].forEach((count) => { [1, 3, 7].forEach((page) => { From 859a5f635c4632c59e6ee64576f09ec223c29a52 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 28 Oct 2026 12:26:02 +0530 Subject: [PATCH 6/6] Drop redundant tests --- .../src/Pagination/Pagination.test.js | 17 ----- .../src/usePagination/usePagination.test.js | 69 ------------------- 2 files changed, 86 deletions(-) diff --git a/packages/mui-material/src/Pagination/Pagination.test.js b/packages/mui-material/src/Pagination/Pagination.test.js index b28278a687c59f..b26afd19e2102b 100644 --- a/packages/mui-material/src/Pagination/Pagination.test.js +++ b/packages/mui-material/src/Pagination/Pagination.test.js @@ -279,23 +279,6 @@ describe('', () => { expect(resultsRef.current).toHaveFocus(); }); - it('keeps a page reachable when both navigation buttons are hidden', () => { - render( - , - ); - - const buttons = screen.getAllByRole('button'); - expect(buttons).to.have.length(2); - expect(buttons.map((button) => button.textContent)).to.deep.equal(['3', '4']); - }); - it('renders no page button when count is zero', () => { render(); diff --git a/packages/mui-material/src/usePagination/usePagination.test.js b/packages/mui-material/src/usePagination/usePagination.test.js index 098b0830ad3f31..60a114cc6c23f7 100644 --- a/packages/mui-material/src/usePagination/usePagination.test.js +++ b/packages/mui-material/src/usePagination/usePagination.test.js @@ -244,73 +244,4 @@ describe('usePagination', () => { ).result.current.items; expect(serialize(items)).to.deep.equal([1, 2, 3]); }); - - it('should link a neighbouring page when the current page is near an edge', () => { - [4, 5, 8, 11].forEach((count) => { - [1, 2, count - 1, count].forEach((page) => { - const items = renderHook(() => - usePagination({ count, page, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - const pages = serialize(items).filter((item) => typeof item === 'number'); - expect(pages.length, `count=${count} page=${page}`).to.be.at.least(2); - }); - }); - }); - - it('should keep the neighbouring page linked when count is 4', () => { - let items; - - items = renderHook(() => - usePagination({ count: 4, page: 2, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); - - items = renderHook(() => - usePagination({ count: 4, page: 3, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 3, 4, 'next']); - - items = renderHook(() => - usePagination({ count: 4, page: 4, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 3, 4, 'next']); - }); - - it('should keep the neighbouring page linked on the first & last pages', () => { - let items; - - items = renderHook(() => - usePagination({ count: 11, page: 1, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); - - items = renderHook(() => - usePagination({ count: 11, page: 2, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 1, 2, 'end-ellipsis', 'next']); - - items = renderHook(() => - usePagination({ count: 11, page: 10, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 10, 11, 'next']); - - items = renderHook(() => - usePagination({ count: 11, page: 11, boundaryCount: 0, siblingCount: 0 }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['previous', 'start-ellipsis', 10, 11, 'next']); - }); - - it('should stay navigable without previous & next buttons when count is 4', () => { - const items = renderHook(() => - usePagination({ - count: 4, - page: 3, - boundaryCount: 0, - siblingCount: 0, - hidePrevButton: true, - hideNextButton: true, - }), - ).result.current.items; - expect(serialize(items)).to.deep.equal(['start-ellipsis', 3, 4]); - }); });