From 45fbd4d78ea314908c2fac12895a0c72894603a5 Mon Sep 17 00:00:00 2001 From: XingY Date: Tue, 8 Sep 2026 18:59:54 -0700 Subject: [PATCH 1/3] GitHub Issue 1534: Cap row counts for React grid pagination --- src/org/labkey/test/components/ui/Pager.java | 31 +++++++++++++++++++ .../test/components/ui/grids/GridBar.java | 5 +++ .../test/components/ui/grids/QueryGrid.java | 9 ++++++ 3 files changed, 45 insertions(+) diff --git a/src/org/labkey/test/components/ui/Pager.java b/src/org/labkey/test/components/ui/Pager.java index 86f4ad0d3a..ad17ae79c9 100644 --- a/src/org/labkey/test/components/ui/Pager.java +++ b/src/org/labkey/test/components/ui/Pager.java @@ -67,6 +67,37 @@ public MultiMenu getDropDownMenu() return elementCache().jumpToDropdown; } + /** + * The "Last Page" jump is present but disabled when already on the last page, and removed entirely when the total + * row count is capped (the true last page is unknown). This reports only the actionable case. + * @return true if the pager's "Last Page" jump is present and enabled + */ + public boolean isLastPageAvailable() + { + if (!isLastPageOptionPresent()) + return false; + + MultiMenu menu = elementCache().jumpToDropdown; + try + { + return !menu.isMenuItemDisabled("Last Page"); + } + finally + { + menu.collapse(); + } + } + + /** + * Unlike {@link #isLastPageAvailable()}, this ignores whether the item is enabled; a present-but-disabled + * "Last Page" (i.e. already on the last page) still counts as present. + * @return true if the pager's jump menu contains a "Last Page" item + */ + public boolean isLastPageOptionPresent() + { + return elementCache().jumpToDropdown.getMenuText().contains("Last Page"); + } + public int getCurrentPage() // only works on GridPanel { return Integer.parseInt(elementCache().currentPageButton.getText()); diff --git a/src/org/labkey/test/components/ui/grids/GridBar.java b/src/org/labkey/test/components/ui/grids/GridBar.java index 3825120339..92eca58ae9 100644 --- a/src/org/labkey/test/components/ui/grids/GridBar.java +++ b/src/org/labkey/test/components/ui/grids/GridBar.java @@ -154,6 +154,11 @@ public boolean isOnLastPage() return !pager().isNextEnabled(); } + public boolean canJumpToLastPage() + { + return pager().isLastPageAvailable(); + } + /** * clicks the 'next' button on the pager associated with this grid and waits for the grid to update */ diff --git a/src/org/labkey/test/components/ui/grids/QueryGrid.java b/src/org/labkey/test/components/ui/grids/QueryGrid.java index 429dabe6d9..3a760efbde 100644 --- a/src/org/labkey/test/components/ui/grids/QueryGrid.java +++ b/src/org/labkey/test/components/ui/grids/QueryGrid.java @@ -217,6 +217,15 @@ public int getRecordCount() return getGridBar().getRecordCount(); } + /** + * The "Last Page" jump is hidden when the total row count is capped, because the true last page is unknown. + * @return true if the grid can jump to its last page + */ + public boolean canJumpToLastPage() + { + return getGridBar().canJumpToLastPage(); + } + public QueryGrid waitForRecordCount(int expectedCount) { return waitForRecordCount(expectedCount, WAIT_FOR_JAVASCRIPT); From f3058483031c1b83c5bd2e6c77ce9bd5ca871eba Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 9 Sep 2026 18:23:06 -0700 Subject: [PATCH 2/3] Support Jump To Page N for capped pagination --- src/org/labkey/test/components/ui/Pager.java | 45 +++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/components/ui/Pager.java b/src/org/labkey/test/components/ui/Pager.java index ad17ae79c9..5d03761b23 100644 --- a/src/org/labkey/test/components/ui/Pager.java +++ b/src/org/labkey/test/components/ui/Pager.java @@ -24,6 +24,8 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; +import java.util.regex.Pattern; + /** * Wrapper for UI component defined in 'packages/components/src/internal/components/gridbar/PageSizeSelector.tsx' * Or maybe 'packages/components/src/internal/components/pagination/PageSizeMenu.tsx' @@ -67,9 +69,13 @@ public MultiMenu getDropDownMenu() return elementCache().jumpToDropdown; } + // When the row count is capped the true last page is unknown, so the jump is labeled "Page N" (e.g. "Page 5,000") + // and targets the last page of the known range instead of "Last Page". + private static final Pattern CAPPED_LAST_PAGE_OPTION = Pattern.compile("Page [\\d,]+"); + /** - * The "Last Page" jump is present but disabled when already on the last page, and removed entirely when the total - * row count is capped (the true last page is unknown). This reports only the actionable case. + * The "Last Page" jump is present but disabled when already on the last page, and replaced by a capped "Page N" + * jump when the count is capped (see {@link #isCappedLastPageAvailable()}). This reports only the actionable case. * @return true if the pager's "Last Page" jump is present and enabled */ public boolean isLastPageAvailable() @@ -98,6 +104,41 @@ public boolean isLastPageOptionPresent() return elementCache().jumpToDropdown.getMenuText().contains("Last Page"); } + /** + * When the row count is capped, the last-page jump targets the last page of the known range and is labeled "Page N" + * (e.g. "Page 5,000") instead of "Last Page". + * @return the capped "Page N" jump label, or null if the pager shows a normal "Last Page" jump or none at all + */ + public String getCappedLastPageLabel() + { + return elementCache().jumpToDropdown.getMenuText().stream() + .filter(text -> CAPPED_LAST_PAGE_OPTION.matcher(text).matches()) + .findFirst() + .orElse(null); + } + + /** + * When the row count is capped, the last-page jump targets the last page of the known range and is labeled "Page N" + * (e.g. "Page 5,000") instead of "Last Page". + * @return true if a capped "Page N" last-page jump is present and enabled + */ + public boolean isCappedLastPageAvailable() + { + String label = getCappedLastPageLabel(); + if (label == null) + return false; + + MultiMenu menu = elementCache().jumpToDropdown; + try + { + return !menu.isMenuItemDisabled(label); + } + finally + { + menu.collapse(); + } + } + public int getCurrentPage() // only works on GridPanel { return Integer.parseInt(elementCache().currentPageButton.getText()); From 642a183877b425c4eb4c8badfdf1cd659ff6d89a Mon Sep 17 00:00:00 2001 From: XingY Date: Fri, 11 Sep 2026 13:24:37 -0700 Subject: [PATCH 3/3] Support Last Page --- src/org/labkey/test/components/ui/Pager.java | 49 ++++++++------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/org/labkey/test/components/ui/Pager.java b/src/org/labkey/test/components/ui/Pager.java index 5d03761b23..1a90ef34c9 100644 --- a/src/org/labkey/test/components/ui/Pager.java +++ b/src/org/labkey/test/components/ui/Pager.java @@ -16,6 +16,7 @@ package org.labkey.test.components.ui; import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; import org.labkey.test.components.Component; import org.labkey.test.components.UpdatingComponent; import org.labkey.test.components.WebDriverComponent; @@ -24,8 +25,6 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; -import java.util.regex.Pattern; - /** * Wrapper for UI component defined in 'packages/components/src/internal/components/gridbar/PageSizeSelector.tsx' * Or maybe 'packages/components/src/internal/components/pagination/PageSizeMenu.tsx' @@ -69,13 +68,9 @@ public MultiMenu getDropDownMenu() return elementCache().jumpToDropdown; } - // When the row count is capped the true last page is unknown, so the jump is labeled "Page N" (e.g. "Page 5,000") - // and targets the last page of the known range instead of "Last Page". - private static final Pattern CAPPED_LAST_PAGE_OPTION = Pattern.compile("Page [\\d,]+"); - /** - * The "Last Page" jump is present but disabled when already on the last page, and replaced by a capped "Page N" - * jump when the count is capped (see {@link #isCappedLastPageAvailable()}). This reports only the actionable case. + * The "Last Page" jump is always present; it is disabled only when already on the last page of an exact count. + * While the count is capped it stays enabled and lands on the true last row (resolving the exact count first). * @return true if the pager's "Last Page" jump is present and enabled */ public boolean isLastPageAvailable() @@ -104,34 +99,18 @@ public boolean isLastPageOptionPresent() return elementCache().jumpToDropdown.getMenuText().contains("Last Page"); } - /** - * When the row count is capped, the last-page jump targets the last page of the known range and is labeled "Page N" - * (e.g. "Page 5,000") instead of "Last Page". - * @return the capped "Page N" jump label, or null if the pager shows a normal "Last Page" jump or none at all - */ - public String getCappedLastPageLabel() - { - return elementCache().jumpToDropdown.getMenuText().stream() - .filter(text -> CAPPED_LAST_PAGE_OPTION.matcher(text).matches()) - .findFirst() - .orElse(null); - } + // The "Count All Rows" jump appears only while the row count is capped; it computes the exact total on demand. + private static final String COUNT_ALL_ROWS_OPTION = "Count All Rows"; /** - * When the row count is capped, the last-page jump targets the last page of the known range and is labeled "Page N" - * (e.g. "Page 5,000") instead of "Last Page". - * @return true if a capped "Page N" last-page jump is present and enabled + * @return true if the pager's jump menu offers the "Count All Rows" action, i.e. the row count is currently capped */ - public boolean isCappedLastPageAvailable() + public boolean isCountAllRowsAvailable() { - String label = getCappedLastPageLabel(); - if (label == null) - return false; - MultiMenu menu = elementCache().jumpToDropdown; try { - return !menu.isMenuItemDisabled(label); + return menu.getMenuText().contains(COUNT_ALL_ROWS_OPTION); } finally { @@ -139,6 +118,18 @@ public boolean isCappedLastPageAvailable() } } + public Pager countAllRows() + { + // Count All Rows re-fires only the total count; rows aren't reloaded, so there's no grid update to wait on. + // Wait for the exact total to render: the summary shows "... of N" (no capped "+"). The total is briefly hidden + // behind a spinner while counting, so require the " of " to be back before checking that the "+" is gone. + elementCache().jumpToDropdown.clickSubMenu(false, COUNT_ALL_ROWS_OPTION); + WebDriverWrapper.waitFor(() -> summary().contains(" of ") && !summary().contains("+"), + "Exact count did not load", 30_000); + + return this; + } + public int getCurrentPage() // only works on GridPanel { return Integer.parseInt(elementCache().currentPageButton.getText());