Fix shared element state across component instances

This commit is contained in:
2026-11-09 14:04:25 +07:00
parent f022f08947
commit 73f7dc31d4
20 changed files with 489 additions and 136 deletions
+133
View File
@@ -322,3 +322,136 @@ fn disabled_trigger_ignores_clicks() {
assert!(changes.borrow().is_empty());
assert!(visual.debug_bounds("menu-panel").is_none());
}
struct PairHarness {
open_a: bool,
open_b: bool,
changes_a: Changes,
changes_b: Changes,
activated_a: Activated,
activated_b: Activated,
}
impl Render for PairHarness {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (changes_a, changes_b) = (self.changes_a.clone(), self.changes_b.clone());
let (activated_a, activated_b) = (self.activated_a.clone(), self.activated_b.clone());
let menu_a = DropdownMenu::new("pair-a")
.label("Alpha")
.variant(ButtonVariant::Secondary)
.open(self.open_a)
.on_open_change({
let view = cx.entity();
let changes = changes_a.clone();
move |next, _, cx| {
changes.borrow_mut().push(next);
view.update(cx, |harness, cx| {
harness.open_a = next;
cx.notify();
});
}
})
.items(move |_, _| {
vec![
MenuItem::new("png").handler({
let activated = activated_a.clone();
move |_, _| activated.borrow_mut().push("png")
}),
MenuItem::new("pdf").handler({
let activated = activated_a.clone();
move |_, _| activated.borrow_mut().push("pdf")
}),
]
});
let menu_b = DropdownMenu::new("pair-b")
.label("Beta")
.variant(ButtonVariant::Secondary)
.open(self.open_b)
.on_open_change({
let view = cx.entity();
let changes = changes_b.clone();
move |next, _, cx| {
changes.borrow_mut().push(next);
view.update(cx, |harness, cx| {
harness.open_b = next;
cx.notify();
});
}
})
.items(move |_, _| {
vec![
MenuItem::new("zip").handler({
let activated = activated_b.clone();
move |_, _| activated.borrow_mut().push("zip")
}),
MenuItem::new("7z").handler({
let activated = activated_b.clone();
move |_, _| activated.borrow_mut().push("7z")
}),
]
});
div().size_full().flex().gap_2().child(menu_a).child(menu_b)
}
}
fn pair_window() -> (
VisualTestContext,
AnyWindowHandle,
Changes,
Changes,
Activated,
Activated,
) {
let mut cx = TestAppContext::single();
cx.update(Theme::init);
let changes_a = Rc::new(RefCell::new(Vec::new()));
let changes_b = Rc::new(RefCell::new(Vec::new()));
let activated_a = Rc::new(RefCell::new(Vec::new()));
let activated_b = Rc::new(RefCell::new(Vec::new()));
let (changes_a_for_window, changes_b_for_window) = (changes_a.clone(), changes_b.clone());
let (activated_a_for_window, activated_b_for_window) =
(activated_a.clone(), activated_b.clone());
let window = cx.add_window(move |_, _| PairHarness {
open_a: false,
open_b: false,
changes_a: changes_a_for_window,
changes_b: changes_b_for_window,
activated_a: activated_a_for_window,
activated_b: activated_b_for_window,
});
let window = window.into();
cx.update_window(window, |_, window, cx| {
window.draw(cx).clear(cx);
})
.unwrap();
(
VisualTestContext::from_window(window, &cx),
window,
changes_a,
changes_b,
activated_a,
activated_b,
)
}
/// With a closed sibling dropdown on screen, a pointer-opened menu must still
/// arm rows with the keyboard: the menu's open/selection state is per
/// instance, not shared keyed state.
#[test]
fn sibling_dropdowns_keep_independent_menu_state() {
let (mut visual, window, changes_a, changes_b, activated_a, activated_b) = pair_window();
// Menu A sits at the window's top-left; menu B stays closed beside it.
click(&mut visual, point(px(30.), px(14.)));
assert_eq!(*changes_a.borrow(), vec![true]);
draw(&mut visual, window);
visual.simulate_keystrokes("down enter");
assert_eq!(*activated_a.borrow(), vec!["png"]);
assert_eq!(*changes_a.borrow(), vec![true, false]);
assert!(changes_b.borrow().is_empty(), "B must stay closed");
assert!(activated_b.borrow().is_empty());
}