Skip to content

Conversation

javier-godoy
Copy link
Member

@javier-godoy javier-godoy commented Sep 11, 2025

Close #134

Summary by CodeRabbit

  • Bug Fixes

    • Improved grid column index calculation to treat consecutive identical cells as a single step, fixing header/footer selection and joined-column behavior.
    • More reliable application of CSS classes to specific header cells.
  • Tests

    • Added integration tests validating header styling across multiple rows and columns.
    • Added test helpers and remote-callable test contracts to access header rows/cells and verify applied classes.

Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Updates HeaderFooterStylesHelper's column-index computation to treat consecutive columns mapping to the same header/footer cell as a single index step. Adds integration test support: RPC interfaces, test view, integration test, and DOM helpers to verify header cell classes.

Changes

Cohort / File(s) Summary
Core index calculation fix
src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java
Reworks getColumnIndex to increment the column index only when the mapped cell value changes, collapsing consecutive visible columns that map to the same cell (handles joined cells).
IT: header/footer styles (RPC, view, test)
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java, src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesView.java, src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java
Adds an RMI-callable interface for header row/cell wrappers, a test view implementing the interface with join/getCell semantics and styling wrappers, and an integration test that assigns and verifies CSS classes on header cells across rows.
Test DOM helper utilities
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java
Adds getHeaderRow(int) and getHeaderCell(int,int) to fetch header TR/TH elements for test assertions; updates license year metadata.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • mlopezFC
  • paodb

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

Pre-merge checks (4 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The current title directly names the primary change — correcting the column index calculation when cells are joined — so it accurately reflects the main intent of the changeset; it is slightly redundant ("fix: fix") but remains specific and relevant.
Linked Issues Check ✅ Passed The change to HeaderFooterStylesHelper replaces the per-column increment with logic that collapses consecutive visible columns mapping to the same cell, which directly addresses issue #134's incorrect index calculation for joined cells; the PR also adds integration tests and test helpers that verify header cell styling, supporting the fix. No exported/public signatures were altered according to the summary.
Out of Scope Changes Check ✅ Passed The modifications are confined to the intended helper logic and accompanying integration-test files (plus a license year update); the provided summary shows no unrelated production code changes or API/signature changes outside the stated objectives.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-134

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (14)
src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (3)

133-151: Joined-cells index calc: correct approach; remove unused param and harden lookup

Logic to increment only on cell transitions fixes #134. Minor refinements:

  • Drop the unused rowIndex param.
  • Cache target and return -1 if not found (defensive).
-    private int getColumnIndex(int rowIndex) {
+    private int getColumnIndex() {
       ROW row = getRowSelector().getRow();
       int j = -1;
-
-      CELL last = null;
+      CELL last = null;
+      final CELL target = getCell();
       for (Column<?> c : helper.getGrid().getColumns()) {
         if (c.isVisible()) {
-          CELL curr = getCell(row, c);
-          if (curr != last) {
-            ++j;
-            last = curr;
-            if (curr == getCell()) {
-              break;
-            }
-          }
+          CELL curr = getCell(row, c);
+          if (curr == last) continue;
+          ++j;
+          last = curr;
+          if (curr == target) return j;
         }
       }
-      return j;
+      return -1; // not found; selector will be null
     }

Also update the caller:

// outside the selected range (in getSelector)
int j = getColumnIndex();

141-146: Intentional reference equality

Using ==/!= for CELL comparison is correct here because joined header/footer cells are the same instance across columns. Consider a short comment to preempt “use equals()” static-analysis noise.


5-5: License year

Year range updated here. Ensure other modified files carry the same range for consistency.

src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java (3)

131-135: Guard against OOB and improve error messages

Add bounds checks so failures are clearer during debugging.

-  public WebElement getHeaderRow(int rowIndex) {
-    WebElement thead = $("*").id("header");
-    List<WebElement> headerRows = thead.findElements(By.tagName("tr"));
-    return headerRows.get(rowIndex);
-  }
+  public WebElement getHeaderRow(int rowIndex) {
+    WebElement thead = $("*").id("header");
+    List<WebElement> headerRows = thead.findElements(By.tagName("tr"));
+    if (rowIndex < 0 || rowIndex >= headerRows.size()) {
+      throw new IndexOutOfBoundsException("rowIndex=" + rowIndex + ", headerRows=" + headerRows.size());
+    }
+    return headerRows.get(rowIndex);
+  }

137-140: Cell index bounds checks

Same here for columns.

-  public WebElement getHeaderCell(int rowIndex, int columnIndex) {
-    List<WebElement> headerCells = getHeaderRow(rowIndex).findElements(By.tagName("th"));
-    return headerCells.get(columnIndex);
-  }
+  public WebElement getHeaderCell(int rowIndex, int columnIndex) {
+    List<WebElement> headerCells = getHeaderRow(rowIndex).findElements(By.tagName("th"));
+    if (columnIndex < 0 || columnIndex >= headerCells.size()) {
+      throw new IndexOutOfBoundsException(
+          "rowIndex=" + rowIndex + ", columnIndex=" + columnIndex + ", headerCells=" + headerCells.size());
+    }
+    return headerCells.get(columnIndex);
+  }

131-140: Prefer TestBenchElement for chaining

Returning TestBenchElement improves fluency with TB APIs (optional).

src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java (2)

56-61: Stabilize assertions with an explicit wait

Class mutations are scheduled via beforeClientResponse; add a small wait to avoid flakiness.

-    assertEquals("row0-cell0", grid.getHeaderCell(0, 0).getAttribute("class"));
-    assertEquals("row0-cell1", grid.getHeaderCell(0, 1).getAttribute("class"));
+    waitUntil(driver -> "row0-cell0".equals(grid.getHeaderCell(0, 0).getAttribute("class")));
+    waitUntil(driver -> "row0-cell1".equals(grid.getHeaderCell(0, 1).getAttribute("class")));

45-63: Name the test after the behavior under test

Optional: rename to clarify the intent.

-  public void testHeaderVisible() {
+  public void testHeaderClassesAppliedToJoinedCells() {
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java (2)

28-31: Clarify index semantics (visible vs. all columns) and preconditions.

Explicitly documenting that indexes are 0-based and refer to visible columns will keep tests aligned with HeaderFooterStylesHelper’s visible-column logic and prevent ambiguity when columns are hidden.

   public interface HeaderRowWrapper extends GridStylesHelper, RmiRemote {
-    HeaderCellWrapper getCell(int columnIndex);
-    HeaderCellWrapper join(int... columnIndexes);
+    /** Returns the header cell by visible column index (0-based). */
+    HeaderCellWrapper getCell(int columnIndex);
+    /**
+     * Joins consecutive visible columns by index (0-based).
+     * Requires at least two indexes.
+     */
+    HeaderCellWrapper join(int... columnIndexes);
   }

35-35: Document rowIndex meaning.

Small doc helps: rowIndex is 0-based in grid.getHeaderRows() order (top to bottom).

-  HeaderRowWrapper getRow(int rowIndex);
+  /** Returns the header row by index (0-based, topmost header row is 0). */
+  HeaderRowWrapper getRow(int rowIndex);
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesView.java (4)

98-113: Map by visible-column index; add preconditions; use extension methods consistently.

Avoid addressing columns by absolute index from getColumns() (includes hidden ones). This keeps the test API consistent with the helper’s visible-column semantics and prevents brittle failures when columns are toggled. Also enforce minimal args for join and prefer the extension method consistently.

   private final class HeaderRowWrapperImpl extends StylesWrapper implements HeaderRowWrapper {
     private final HeaderRow row;
 
     public HeaderRowWrapperImpl(HeaderRow row) {
-      super(GridHelper.getHeaderStyles(grid, row));
+      super(grid.getHeaderStyles(row));
       this.row = row;
     }
 
+    private Column<?> visibleColumnAt(int visibleIndex) {
+      int j = -1;
+      for (Column<?> c : grid.getColumns()) {
+        if (c.isVisible() && ++j == visibleIndex) {
+          return c;
+        }
+      }
+      throw new IndexOutOfBoundsException("Visible columnIndex out of range: " + visibleIndex);
+    }
+
     @Override
     public HeaderCellWrapper getCell(int columnIndex) {
-      HeaderCell cell = row.getCell(grid.getColumns().get(columnIndex));
-      return new HeaderCellWrapperImpl(grid.getHeaderStyles(cell));
+      HeaderCell cell = row.getCell(visibleColumnAt(columnIndex));
+      return new HeaderCellWrapperImpl(grid.getHeaderStyles(cell));
     }
 
     @Override
     public HeaderCellWrapper join(int... columnIndexes) {
-      HeaderCell cell = row.join(IntStream.of(columnIndexes)
-        .mapToObj(grid.getColumns()::get)
-        .toArray(Column[]::new));
+      if (columnIndexes == null || columnIndexes.length < 2) {
+        throw new IllegalArgumentException("join requires at least 2 visible column indexes");
+      }
+      Column<?>[] cols = IntStream.of(columnIndexes)
+        .mapToObj(this::visibleColumnAt)
+        .toArray(Column[]::new);
+      HeaderCell cell = row.join(cols);
       cell.setText("join " + IntStream.of(columnIndexes)
         .mapToObj(Integer::toString)
         .collect(Collectors.joining()));
-      return new HeaderCellWrapperImpl(GridHelper.getHeaderStyles(grid, cell));
+      return new HeaderCellWrapperImpl(grid.getHeaderStyles(cell));
     }

118-121: Bounds-check rowIndex for clearer failures.

Helpful in IT logs and prevents NPEs from deeper layers.

   @Override
   public HeaderRowWrapper getRow(int rowIndex) {
-    HeaderRow row = grid.getHeaderRows().get(rowIndex);
+    if (rowIndex < 0 || rowIndex >= grid.getHeaderRows().size()) {
+      throw new IndexOutOfBoundsException("rowIndex out of range: " + rowIndex);
+    }
+    HeaderRow row = grid.getHeaderRows().get(rowIndex);
     return new HeaderRowWrapperImpl(row);
   }

46-51: Make grid final and assign via this for clarity/immutability.

Small cleanup; reduces accidental reassignment.

-  private Grid<Integer> grid;
+  private final Grid<Integer> grid;
@@
-    grid = new Grid<>();
+    this.grid = new Grid<>();

58-68: Remove commented-out placeholder code.

Keeps the IT view minimal and focused.

-    // HeaderRow row0 = ;
-    // HeaderRow row1 = grid.prependHeaderRow();
-    // HeaderCell cell01 = row1.join(col0, col1);
-    // HeaderCell cell23 = row1.join(col2, col3);
-    // grid.getHeaderStyles(row0.getCell(col0)).setClassName("cell0");
-    // grid.getHeaderStyles(row0.getCell(col1)).setClassName("cell1");
-    // grid.getHeaderStyles(row0).setClassName("row0");
-    // grid.getHeaderStyles(cell01).setClassName("cell01");
-    // grid.getHeaderStyles(cell23).setClassName("cell23");
-    // grid.getHeaderStyles(row1).setClassName("row1");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96ab041 and e00bb22.

📒 Files selected for processing (5)
  • src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (2 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java (2 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java (1 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java (1 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesView.java (1 hunks)
🔇 Additional comments (4)
src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (1)

153-158: nth-child offset remains correct

j + 1 aligns the zero-based computed index with CSS 1-based nth-child. Looks good.

src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java (2)

45-55: Good coverage of joined vs. non-joined rows

Test exercises both a joined header row and a non-joined row. Nicely isolates the regression.


51-55: Confirm intended number of cells in row 0

Row 0 joins (0–1) and (2–3). If a 5th column exists, there might be a third header cell (col 4). If intentional to have only two cells, please confirm the view setup; otherwise, consider asserting the third cell too.

src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java (1)

26-37: LGTM overall — clean RPC contract for tests.

Interfaces extend RmiRemote correctly and the surface matches the intended test usage.

Copy link

Copy link
Member

@paodb paodb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java (1)

131-140: Harden header accessors (bounds checks; return TestBenchElement for consistency).

Current methods can throw IndexOutOfBoundsException with little context and return WebElement while most helpers return TestBenchElement. Add explicit bounds validation and use TestBenchElement throughout.

-  public WebElement getHeaderRow(int rowIndex) {
-    WebElement thead = $("*").id("header");
-    List<WebElement> headerRows = thead.findElements(By.tagName("tr"));
-    return headerRows.get(rowIndex);
-  }
+  public TestBenchElement getHeaderRow(int rowIndex) {
+    TestBenchElement thead = $(TestBenchElement.class).id("header");
+    List<TestBenchElement> headerRows = thead.$(TestBenchElement.class).tagName("tr").all();
+    if (rowIndex < 0 || rowIndex >= headerRows.size()) {
+      throw new IndexOutOfBoundsException("Header row index out of bounds: " + rowIndex);
+    }
+    return headerRows.get(rowIndex);
+  }
 
-  public WebElement getHeaderCell(int rowIndex, int columnIndex) {
-    List<WebElement> headerCells = getHeaderRow(rowIndex).findElements(By.tagName("th"));
-    return headerCells.get(columnIndex);
-  }
+  public TestBenchElement getHeaderCell(int rowIndex, int columnIndex) {
+    List<TestBenchElement> headerCells =
+        getHeaderRow(rowIndex).$(TestBenchElement.class).tagName("th").all();
+    if (columnIndex < 0 || columnIndex >= headerCells.size()) {
+      throw new IndexOutOfBoundsException("Header column index out of bounds: " + columnIndex);
+    }
+    return headerCells.get(columnIndex);
+  }

Optional: if Vaadin ever renames the internal “header” id, consider scoping via executeScript on this element’s shadowRoot rather than relying on a fixed id.

src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (1)

133-151: Remove unused parameter and micro-cleanup.

rowIndex is unused; simplifying the signature clarifies intent. Also cache getCell() to avoid repeated calls inside the loop.

-    private int getColumnIndex(int rowIndex) {
+    private int getColumnIndex() {
       ROW row = getRowSelector().getRow();
       int j = -1;
-      CELL last = null;
+      CELL last = null;
+      CELL target = getCell();
       for (Column<?> c : helper.getGrid().getColumns()) {
         if (c.isVisible()) {
-          CELL curr = getCell(row, c);
+          CELL curr = getCell(row, c);
           if (curr != last) {
             ++j;
             last = curr;
-            if (curr == getCell()) {
+            if (curr == target) {
               break;
             }
           }
         }
       }
       return j;
     }
@@
     public final Serializable[] getSelector() {
       int i = getRowSelector().getRowIndex();
-      int j = getColumnIndex(i);
+      int j = getColumnIndex();
       return j < 0 ? null : new Serializable[] {getSelectorTemplate(), i, j + 1};
     }

Also applies to: 155-158

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e00bb22 and 431432f.

📒 Files selected for processing (5)
  • src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (2 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java (2 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java (1 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java (1 hunks)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesView.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesView.java
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesIT.java
  • src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/HeaderFooterStylesCallables.java
🔇 Additional comments (3)
src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java (1)

5-5: License year bump — OK.

src/main/java/com/flowingcode/vaadin/addons/gridhelpers/HeaderFooterStylesHelper.java (2)

5-5: License year bump — OK.


137-147: Correctly collapse joined cells — fix looks right.

Incrementing the index only when the cell instance changes eliminates overcounting across colspans and aligns nth-child with the rendered TH sequence. Nice.

@paodb paodb merged commit a682c77 into master Sep 12, 2025
5 checks passed
@paodb paodb deleted the fix-134 branch September 12, 2025 12:24
@github-project-automation github-project-automation bot moved this from To Do to Pending release in Flowing Code Addons Sep 12, 2025
@javier-godoy javier-godoy moved this from Pending release to Done in Flowing Code Addons Sep 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Incorrect column index calculation when cells are joined

2 participants