Skip to content

test: Add find_all and configurable clicks to the UI test harness - #3384

Merged
huacnlee merged 5 commits into
longbridge:mainfrom
AncientPixel:kit-find-all-modifier-clicks
Oct 7, 2026
Merged

huacnlee merged 5 commits into
longbridge:mainfrom
AncientPixel:kit-find-all-modifier-clicks

Conversation

@AncientPixel

@AncientPixel AncientPixel commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

UI integration tests could neither count repeated local IDs nor exercise clicks with modifiers. This adds collection queries and configurable native clicks through the existing TestWindowExt trait. The new required methods are an intentional breaking change for downstream trait implementations.

  • TestWindowExt::find_all returns every registered matching snapshot, including invisible elements. Results follow the current frame's bounds origin (y, then x); equal-origin order is unspecified. This is geometry, not stable identity, paint order, or business order. Existing unique queries still reject ambiguous IDs.
  • TestWindowExt provides click_with_options and a centered click_with_modifiers convenience method. ClickOptions combines local offset, mouse button, positive click count, and modifiers using private fields and builders.
  • Configurable clicks dispatch modifier-change events when needed, carry the requested modifiers through move/down/up, and restore the previous modifier state afterward. Caps lock is preserved. Existing convenience methods retain their behavior.
  • ScopedWindow exposes the same new capabilities inside its identity scope.
  • English, Chinese, and skill documentation describe imports, configuration, state restoration, and snapshot ordering/visibility boundaries.

For example:

use gpui_kit::test::{ClickOptions, TestWindowExt};

let options = ClickOptions::new()
    .with_offset(point(px(8.), px(8.)))
    .with_button(MouseButton::Right)
    .with_count(2)
    .with_modifiers(Modifiers::shift());
window.click_with_options("row", options, cx);
let visible_count = window.find_all("notification")
    .iter().filter(|element| element.visible()).count();

This change was written with AI assistance (Claude Code and OpenAI Codex).

Public API

gpui-kit::test

  • TestWindowExt::find_all(&self, id: impl Into<ElementId>) -> Vec<ElementSnapshot>: all registered matches from the last completed frame; empty when absent.
  • TestWindowExt::click_with_options(&mut self, id: impl Into<ElementId>, options: ClickOptions, cx: &mut App): dispatches a configured click and restores modifier state.
  • TestWindowExt::click_with_modifiers(&mut self, id: impl Into<ElementId>, modifiers: Modifiers, cx: &mut App): default method for one centered left click with modifiers.
  • pub struct ClickOptions: caller-owned click configuration with private fields; implements Clone, Copy, Debug, and Default.
  • ClickOptions::new() -> Self: creates a centered single left click without modifiers.
  • ClickOptions::with_offset(self, offset: Point<Pixels>) -> Self: sets a local offset from the target's top-left corner.
  • ClickOptions::offset(&self) -> Option<Point<Pixels>>: returns the offset; None means center.
  • ClickOptions::with_button(self, button: MouseButton) -> Self: sets the mouse button.
  • ClickOptions::button(&self) -> MouseButton: reads the mouse button.
  • ClickOptions::with_count(self, count: usize) -> Self: sets the click count; panics for zero.
  • ClickOptions::count(&self) -> usize: reads the positive click count.
  • ClickOptions::with_modifiers(self, modifiers: Modifiers) -> Self: sets the modifiers for the sequence.
  • ClickOptions::modifiers(&self) -> Modifiers: reads the modifiers.
  • ScopedWindow::find_all(&self, id: impl Into<ElementId>) -> Vec<ElementSnapshot>: collection query restricted to the scope.
  • ScopedWindow::click_with_options(&mut self, id: impl Into<ElementId>, options: ClickOptions, cx: &mut App): configured click restricted to the scope.
  • ScopedWindow::click_with_modifiers(&mut self, id: impl Into<ElementId>, modifiers: Modifiers, cx: &mut App): centered modified left click restricted to the scope.

gpui-base::test_support (internal, #[doc(hidden)])

  • find_all(window: &Window, scope: &[ElementId], id: &ElementId) -> Vec<ElementSnapshot>: lookup behind Kit's collection queries.
  • Existing find(window: &Window, scope: &[ElementId], id: &ElementId) -> Option<ElementSnapshot> delegates to the shared lookup and retains its ambiguity assertion.

Breaking Changes

TestWindowExt gains two required methods: find_all and click_with_options.
Downstream implementations must implement both. click_with_modifiers has a default
implementation through click_with_options. Call sites continue to import only
TestWindowExt for all window queries and interactions.

 impl TestWindowExt for MyTestWindow {
     // Existing methods remain.
+    fn find_all(&self, id: impl Into<ElementId>) -> Vec<ElementSnapshot> {
+        self.window.find_all(id)
+    }
+    fn click_with_options(&mut self, id: impl Into<ElementId>, options: ClickOptions, cx: &mut App) {
+        self.window.click_with_options(id, options, cx);
+    }
 }

Validation

Linux validation:

  • cargo check -p gpui-kit --features test-support --tests --locked
  • cargo test -p gpui-kit --features test-support --test interactions --test window --test collections --test overlays --locked
  • cargo clippy -p gpui-kit --features test-support --tests --locked --no-deps -- -D warnings
  • cargo fmt --all --check
  • git diff --check

Regression coverage checks modifiers on pointer events, modifier-change callbacks and restoration of both neutral and pre-existing states, combined offset/right-double-click/Shift on both window and scoped APIs, configuration defaults and invalid zero counts, repeated-ID scope filtering, and stacked notifications. macOS and Windows validation remains with CI.

AncientPixel and others added 2 commits October 6, 2026 22:05
The pointer helpers sent every mouse move, down and up event with
`Modifiers::default()`, so a UI test could not reach code that reads
`event.modifiers()`, such as a List row confirming with
`secondary: e.modifiers().secondary()`.

`click_with_modifiers` left-clicks a target with the given modifiers on
the move, down and up events. It mirrors GPUI's
`VisualTestContext::simulate_click(position, modifiers)` and is
available on both `TestWindowExt` and `ScopedWindow`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRpJSFk3zTwdgAJHehdZFm
`find` and `try_find` panic when an ID matches more than one element,
and `within` needs a single scope. A test could not count repeated
elements whose paths differ only by generated IDs, such as a stack of
notifications that each render a `notification` toast.

`find_all` returns every match in the window or scope, or an empty
`Vec`. The registry is a `HashMap`, so matches are sorted top to
bottom, then left to right by bounds origin, with the path as the tie
breaker. `find` now reuses the same lookup and keeps its ambiguity
assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRpJSFk3zTwdgAJHehdZFm
@AncientPixel

Copy link
Copy Markdown
Contributor Author

The failing GPUI Fast (windows-latest) check isn't from this change. It fails at link time with LNK1123: failure during conversion to COFF, the same way it fails on main at c0bebdc, the commit this branch is based on. #3380 looks like the fix. Once it lands I'll rebase onto main so CI runs again.

@huacnlee huacnlee added the Breaking Changes Contains breaking changes; excluded from automatic cherry-picks to stable. label Oct 7, 2026
@huacnlee huacnlee changed the title kit: Add find_all and click_with_modifiers to the test harness test: Add find_all and configurable clicks to the UI test harness Oct 7, 2026
@huacnlee
huacnlee enabled auto-merge (squash) October 7, 2026 05:45
@huacnlee
huacnlee merged commit 6bb649e into longbridge:main Oct 7, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking Changes Contains breaking changes; excluded from automatic cherry-picks to stable.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants