Skip to content

fix: find slotted Dialog and Card children via ComponentUtil.getAllChildren - #2291

Merged
Artur- merged 2 commits into
mainfrom
investigate-dialog-header-footer-query
Sep 7, 2026
Merged

Artur- merged 2 commits into
mainfrom
investigate-dialog-header-footer-query

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

What

Component tree walking in TestingLifecycleHook.getAllChildren now unions a component's own getChildren() with ComponentUtil.getAllChildren(component), instead of using getChildren() plus virtual children only.

Why

Vaadin 25.3 slots the Dialog header and footer wrappers into the light DOM, and Dialog.getChildren() filters those wrappers out. As a result, components added through getHeader() / getFooter() were no longer part of the walked component tree, and ComponentQuery could not find them:

dialog.getFooter().add(new Button("Save"));
// before: ComponentQuery found nothing
$(Button.class).withText("Save").single();

Card behaves the same way — it excludes every child carrying a slot attribute from getChildren(), so setHeader(), setHeaderPrefix(), setHeaderSuffix() and addToFooter() content was invisible to queries too.

How

The fallback branch of the walker now merges both sources and de-duplicates:

else -> (component.children.toList()
        + ComponentUtil.getAllChildren(component).toList()).distinct()

Neither source is complete on its own:

  • getChildren() can hide element children (Dialog, Card and their slotted content).
  • ComponentUtil.getAllChildren() walks the element hierarchy — regular and virtual children, descending through non-component wrapper elements — but misses children that are not part of the element tree at all, e.g. ContextMenu, which keeps its items in a server-side MenuManager list that getChildren() delegates to.

Using getAllChildren() also keeps the virtual children previously added for karibu-testing#85, so that fix is preserved. The special-cased branches above the fallback (MenuItemBase, MenuBar, templates, Grid.Column, ColumnGroup, Composite) are untouched.

This ports vaadin/browserless-test#142 to the vaadin-testbench-unit modules.

Test summary

# Status What the test verifies Why it matters
1 On an opened Dialog, the button added via getHeader().add(...) is returned by $(Button.class).withText("Header").single() and is the same instance held by the view The regression that motivated the fix — slotted header content must be queryable
2 Same for the button added via getFooter().add(...) Footer wrapper is slotted and filtered out of Dialog.getChildren() too
3 Regular Dialog content added with dialog.add(...) is still found Guards against the union breaking the previously working path
4 On a Card, content plus setHeader, setHeaderPrefix, setHeaderSuffix and addToFooter components are all found by query Card excludes every slot-carrying child, covering the second affected component
5 Every assertion uses .single(), so a component appearing twice in the walked tree fails the test Unioning two overlapping child sources must not duplicate components in query results
6 ContextMenu items — which live in a server-side MenuManager and are absent from the element tree — are still discoverable ComponentUtil.getAllChildren() alone would lose them; pinned by pre-existing ContextMenuTesterTest (unchanged here)

Tests added/changed on this branch:

  • DialogTesterTest.openedDialog_headerAndFooterComponentsAreFoundByQuery — rows 1, 2, 3, 5
  • CardTesterTest.slottedComponentsAreFoundByQuery — rows 4, 5
  • DialogView (fixture) — adds header/content/footer buttons used by rows 1–3; CardView (new fixture) — backs row 4
  • Row 6 is covered by the pre-existing ContextMenuTesterTest, deliberately left as-is to act as the regression guard for the getChildren() half of the union

Deliberately untested: the untouched special-case branches of the walker (MenuBar, templates, Grid.Column, Composite), which keep their existing coverage; and the junit6/quarkus/vaadin-testbench-unit modules, which consume the same shared Kotlin code path exercised by the junit5 tests.

…tQuery

Vaadin 25.3 slots the Dialog header/footer wrappers into the light DOM and
excludes them from Dialog.getChildren(), so the browserless component tree
walker no longer sees components added via getHeader()/getFooter().

Adds a failing test that queries header, content and footer components of an
opened Dialog.
Vaadin 25.3 slots the Dialog header and footer wrappers into the light DOM
and Dialog.getChildren() filters those wrappers out, so components added via
getHeader()/getFooter() were no longer part of the walked component tree and
ComponentQuery could not find them. Card has the same behaviour: it excludes
every child carrying a slot attribute from getChildren().

Union the component's own children with ComponentUtil.getAllChildren(), which
walks the element hierarchy (regular and virtual children, descending through
non-component wrapper elements). Neither source alone is complete, since
getChildren() may hide element children while getAllChildren() misses children
that are not part of the element tree, such as ContextMenu items.

Ports vaadin/browserless-test#142 to the vaadin-testbench-unit modules.
@Artur-
Artur- enabled auto-merge (squash) September 7, 2026 12:06
@Artur-
Artur- disabled auto-merge September 7, 2026 12:06
@Artur-
Artur- enabled auto-merge (squash) September 7, 2026 12:06
@Artur-
Artur- merged commit 5a40f5d into main Sep 7, 2026
10 checks passed
@Artur-
Artur- deleted the investigate-dialog-header-footer-query branch September 7, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant