Skip to content

feat!: return the component the grid rendered from GridTester.getCellComponent - #250

Merged
mcollovati merged 2 commits into
mainfrom
fix/235-grid-cell-component-live-instance
Sep 19, 2026
Merged

mcollovati merged 2 commits into
mainfrom
fix/235-grid-cell-component-live-instance

Conversation

@totally-not-ai

Copy link
Copy Markdown
Contributor

Summary

GridTester.getCellComponent used to render the cell again on every call and attach the fresh copy to the grid, so a test never acted on the component the grid actually shows. It now returns the component the grid rendered for that row and column. The old behaviour is still available as the new renderCellComponent.

Fixes #235

What changed

Breaking: getCellComponent(int, int) and getCellComponent(int, String) behave differently. This affects every test that reads a component cell.

  • It returns the component the grid rendered for the client, which is the one the browser shows. Reading the same cell twice gives the same instance, and the instance is replaced when the row is rendered anew (for example after refreshItem(...)).
  • Nothing is attached to the grid any more, so a later find(...) no longer reports leftover copies. Tests that counted those copies will see fewer components.
  • A row the client has not asked for yet is scrolled into view first, the way a user reaches it. Reading a cell can therefore scroll the grid.
  • It now throws IllegalStateException for a cell the grid renders nothing for: a hidden column, or a data provider whose items are not equal across fetches. The message points at renderCellComponent.
  • getCellText on a ComponentRenderer column reads the same rendered component. For a renderer that returns no component it now returns "" instead of null, because the grid renders an empty text node in that place. It can also throw for the cases listed above.

New, additive:

  • renderCellComponent(int, int) and renderCellComponent(int, String) keep the old behaviour — render the cell on its own and attach the copy to the grid. Use them for cells the grid does not render, such as those of a hidden column, and to keep older tests working.
  • An internal helper class reads the rendered components from the column's data generator.

A column index addresses only visible columns, so a hidden column's cells are reachable only by key, through renderCellComponent(int, String).

README and the find(...) javadocs were updated to match.

Use case

You have a grid with a component column of "Approve" buttons. A test clicks the button on the second row and checks that the row's item was really approved. With the old behaviour the click went to a throwaway copy, so nothing in the grid reacted.

var grid_ = test(view.requestGrid);

// the button the grid shows on row 1
var approve = (Button) grid_.getCellComponent(1, "approve");
test(approve).click();

assertTrue(view.requests.get(1).isApproved());

// after the row is refreshed, read the cell again to get the new button
view.requestGrid.getListDataView().refreshItem(view.requests.get(1));
var updated = (Button) grid_.getCellComponent(1, "approve");
assertEquals("Approved", updated.getText());

For a column that is hidden on purpose, render the cell instead:

Button hidden = (Button) grid_.renderCellComponent(1, "hiddenAction");

API Changes

com.vaadin.flow.component.grid.GridTester

// Added
public Component renderCellComponent(int row, int column) // former getCellComponent behaviour: renders a fresh copy and attaches it to the grid
public Component renderCellComponent(int row, String columnName) // former getCellComponent behaviour; the only way to reach a hidden column's cell

// Changed
- public Component getCellComponent(int row, int column) // rendered a new component per call and appended it to the grid
+ public Component getCellComponent(int row, int column) // returns the component the grid rendered; scrolls the row into view; throws IllegalStateException when the grid rendered none
- public Component getCellComponent(int row, String columnName) // rendered a new component per call and appended it to the grid
+ public Component getCellComponent(int row, String columnName) // returns the component the grid rendered; throws IllegalStateException for a hidden column
- public String getCellText(int row, int column) // returned null for a ComponentRenderer cell that rendered no component
+ public String getCellText(int row, int column) // reads the grid-rendered component; returns "" instead of null; may scroll or throw IllegalStateException

com.vaadin.browserless.internal.RenderedComponentSupport

// Added
public final class RenderedComponentSupport // for internal use only
public static Component getRenderedComponent(Grid.Column<?> column, String itemKey, BiFunction<Class<?>, String, Field> fieldGetter)

Test summary

  • Reading the same component cell twice returns one and the same instance, and leaves nothing attached to the grid for find(...) to report.
  • Refreshing an item replaces the cell's component: the old one is detached and the new one is the attached, rendered instance.
  • A row the client has not asked for yet is scrolled into view and stays rendered on later reads.
  • A hidden column and a data provider whose items differ across fetches both fail with a clear message that points at renderCellComponent, which then returns a component for those cells.
  • renderCellComponent still renders a new copy per call, attaches it to the grid, and that copy is not the component the grid shows.
  • A renderer that returns no component yields the empty text node the grid renders, so the cell text is "".

GridTester.getCellComponent rendered the cell again on every call and
appended the fresh copy to the grid, so reading the same cell twice left
two components behind, find() reported both, and the component a test
acted on was never the one the grid shows.

It now returns the component the grid itself rendered for the row and
column: reading a cell twice gives the same instance, nothing is
appended, and the instance is replaced when the row is rendered anew.
A row the client has not asked for yet is scrolled into view first, the
way a user reaches it. The previous behaviour is available as
renderCellComponent, for the cells the grid does not render, such as
those of a hidden column.

Fixes #235
Pins the failure a data provider whose items are not equal across
fetches now produces, including the advice to render the cell instead,
and turns the null-render case into an assertion about the component
rather than a second copy of the text assertion.

`RenderedComponentSupport` reads the rendered components straight from
the generator's map, and reaches the private fields with `ReflectTools`
instead of hand-rolling the lookup. The `getCellText` and
`renderCellComponent` javadocs say that reading a cell can scroll the
grid and fail, and that a column index never addresses a hidden column.
@mcollovati
mcollovati force-pushed the fix/235-grid-cell-component-live-instance branch from 4f79556 to 3fb151c Compare September 19, 2026 05:52
@mcollovati
mcollovati merged commit 5bb1538 into main Sep 19, 2026
6 checks passed
@mcollovati
mcollovati deleted the fix/235-grid-cell-component-live-instance branch September 19, 2026 06:29
mcollovati pushed a commit that referenced this pull request Sep 21, 2026
…Component (#250) (CP: 25.3) (#257)

This PR cherry-picks changes from the original PR #250 to branch 25.3.
---
#### Original PR description
> ## Summary
> 
> `GridTester.getCellComponent` used to render the cell again on every
call and attach the fresh copy to the grid, so a test never acted on the
component the grid actually shows. It now returns the component the grid
rendered for that row and column. The old behaviour is still available
as the new `renderCellComponent`.
> 
> Fixes #235
> 
> ## What changed
> 
> **Breaking:** `getCellComponent(int, int)` and `getCellComponent(int,
String)` behave differently. This affects every test that reads a
component cell.
> 
> - It returns the component the grid rendered for the client, which is
the one the browser shows. Reading the same cell twice gives the same
instance, and the instance is replaced when the row is rendered anew
(for example after `refreshItem(...)`).
> - Nothing is attached to the grid any more, so a later `find(...)` no
longer reports leftover copies. Tests that counted those copies will see
fewer components.
> - A row the client has not asked for yet is scrolled into view first,
the way a user reaches it. Reading a cell can therefore scroll the grid.
> - It now throws `IllegalStateException` for a cell the grid renders
nothing for: a hidden column, or a data provider whose items are not
equal across fetches. The message points at `renderCellComponent`.
> - `getCellText` on a `ComponentRenderer` column reads the same
rendered component. For a renderer that returns no component it now
returns `""` instead of `null`, because the grid renders an empty text
node in that place. It can also throw for the cases listed above.
> 
> New, additive:
> 
> - `renderCellComponent(int, int)` and `renderCellComponent(int,
String)` keep the old behaviour — render the cell on its own and attach
the copy to the grid. Use them for cells the grid does not render, such
as those of a hidden column, and to keep older tests working.
> - An internal helper class reads the rendered components from the
column's data generator.
> 
> A column index addresses only visible columns, so a hidden column's
cells are reachable only by key, through `renderCellComponent(int,
String)`.
> 
> README and the `find(...)` javadocs were updated to match.
> 
> ## Use case
> 
> You have a grid with a component column of "Approve" buttons. A test
clicks the button on the second row and checks that the row's item was
really approved. With the old behaviour the click went to a throwaway
copy, so nothing in the grid reacted.
> 
> ```java
> var grid_ = test(view.requestGrid);
> 
> // the button the grid shows on row 1
> var approve = (Button) grid_.getCellComponent(1, "approve");
> test(approve).click();
> 
> assertTrue(view.requests.get(1).isApproved());
> 
> // after the row is refreshed, read the cell again to get the new
button
> view.requestGrid.getListDataView().refreshItem(view.requests.get(1));
> var updated = (Button) grid_.getCellComponent(1, "approve");
> assertEquals("Approved", updated.getText());
> ```
> 
> For a column that is hidden on purpose, render the cell instead:
> 
> ```java
> Button hidden = (Button) grid_.renderCellComponent(1, "hiddenAction");
> ```
> 
> ## API Changes
> 
> ### com.vaadin.flow.component.grid.GridTester
> 
> ```java
> // Added
> public Component renderCellComponent(int row, int column) // former
getCellComponent behaviour: renders a fresh copy and attaches it to the
grid
> public Component renderCellComponent(int row, String columnName) //
former getCellComponent behaviour; the only way to reach a hidden
column's cell
> 
> // Changed
> - public Component getCellComponent(int row, int column) // rendered a
new component per call and appended it to the grid
> + public Component getCellComponent(int row, int column) // returns
the component the grid rendered; scrolls the row into view; throws
IllegalStateException when the grid rendered none
> - public Component getCellComponent(int row, String columnName) //
rendered a new component per call and appended it to the grid
> + public Component getCellComponent(int row, String columnName) //
returns the component the grid rendered; throws IllegalStateException
for a hidden column
> - public String getCellText(int row, int column) // returned null for
a ComponentRenderer cell that rendered no component
> + public String getCellText(int row, int column) // reads the
grid-rendered component; returns "" instead of null; may scroll or throw
IllegalStateException
> ```
> 
> ### com.vaadin.browserless.internal.RenderedComponentSupport
> 
> ```java
> // Added
> public final class RenderedComponentSupport // for internal use only
> public static Component getRenderedComponent(Grid.Column<?> column,
String itemKey, BiFunction<Class<?>, String, Field> fieldGetter)
> ```
> 
> ## Test summary
> 
> - Reading the same component cell twice returns one and the same
instance, and leaves nothing attached to the grid for `find(...)` to
report.
> - Refreshing an item replaces the cell's component: the old one is
detached and the new one is the attached, rendered instance.
> - A row the client has not asked for yet is scrolled into view and
stays rendered on later reads.
> - A hidden column and a data provider whose items differ across
fetches both fail with a clear message that points at
`renderCellComponent`, which then returns a component for those cells.
> - `renderCellComponent` still renders a new copy per call, attaches it
to the grid, and that copy is not the component the grid shows.
> - A renderer that returns no component yields the empty text node the
grid renders, so the cell text is `""`.

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GridTester.getCellComponent accumulates a new attached component in the grid on every call

2 participants