Use real divs in tooltip leak tests

Richard Feldman created

Change summary

crates/gpui/src/elements/div.rs | 188 +++++++++++++++++++++++++++++-----
1 file changed, 161 insertions(+), 27 deletions(-)

Detailed changes

crates/gpui/src/elements/div.rs 🔗

@@ -3588,7 +3588,8 @@ impl ScrollHandle {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{AppContext as _, Context, TestAppContext};
+    use crate::{AppContext as _, Context, InputEvent, MouseMoveEvent, TestAppContext};
+    use std::rc::Weak;
 
     struct TestTooltipView;
 
@@ -3598,6 +3599,102 @@ mod tests {
         }
     }
 
+    type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
+
+    struct TooltipCaptureElement {
+        child: AnyElement,
+        captured_active_tooltip: CapturedActiveTooltip,
+    }
+
+    impl IntoElement for TooltipCaptureElement {
+        type Element = Self;
+
+        fn into_element(self) -> Self::Element {
+            self
+        }
+    }
+
+    impl Element for TooltipCaptureElement {
+        type RequestLayoutState = ();
+        type PrepaintState = ();
+
+        fn id(&self) -> Option<ElementId> {
+            None
+        }
+
+        fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+            None
+        }
+
+        fn request_layout(
+            &mut self,
+            _id: Option<&GlobalElementId>,
+            _inspector_id: Option<&InspectorElementId>,
+            window: &mut Window,
+            cx: &mut App,
+        ) -> (LayoutId, Self::RequestLayoutState) {
+            (self.child.request_layout(window, cx), ())
+        }
+
+        fn prepaint(
+            &mut self,
+            _id: Option<&GlobalElementId>,
+            _inspector_id: Option<&InspectorElementId>,
+            _bounds: Bounds<Pixels>,
+            _request_layout: &mut Self::RequestLayoutState,
+            window: &mut Window,
+            cx: &mut App,
+        ) -> Self::PrepaintState {
+            self.child.prepaint(window, cx);
+        }
+
+        fn paint(
+            &mut self,
+            _id: Option<&GlobalElementId>,
+            _inspector_id: Option<&InspectorElementId>,
+            _bounds: Bounds<Pixels>,
+            _request_layout: &mut Self::RequestLayoutState,
+            _prepaint: &mut Self::PrepaintState,
+            window: &mut Window,
+            cx: &mut App,
+        ) {
+            self.child.paint(window, cx);
+            window.with_global_id("target".into(), |global_id, window| {
+                window.with_element_state::<InteractiveElementState, _>(
+                    global_id,
+                    |state, _window| {
+                        let state = state.unwrap();
+                        *self.captured_active_tooltip.borrow_mut() =
+                            state.active_tooltip.as_ref().map(Rc::downgrade);
+                        ((), state)
+                    },
+                )
+            });
+        }
+    }
+
+    struct TooltipOwner {
+        captured_active_tooltip: CapturedActiveTooltip,
+    }
+
+    impl Render for TooltipOwner {
+        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+            TooltipCaptureElement {
+                child: div()
+                    .size_full()
+                    .child(
+                        div()
+                            .id("target")
+                            .w(px(50.))
+                            .h(px(50.))
+                            .tooltip(|_, cx| cx.new(|_| TestTooltipView).into()),
+                    )
+                    .into_any_element(),
+                captured_active_tooltip: self.captured_active_tooltip.clone(),
+            }
+        }
+    }
+
     #[test]
     fn scroll_handle_aligns_wide_children_to_left_edge() {
         let handle = ScrollHandle::new();
@@ -3636,46 +3733,78 @@ mod tests {
         assert_eq!(handle.offset().y, px(-25.));
     }
 
-    #[test]
-    fn tooltip_is_released_when_its_owner_disappears() {
+    fn setup_tooltip_owner_test() -> (
+        TestAppContext,
+        crate::AnyWindowHandle,
+        CapturedActiveTooltip,
+    ) {
         let mut test_app = TestAppContext::single();
-        let window = test_app.add_window(|_, _| crate::Empty);
+        let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
+        let window = test_app.add_window({
+            let captured_active_tooltip = captured_active_tooltip.clone();
+            move |_, _| TooltipOwner {
+                captured_active_tooltip,
+            }
+        });
         let any_window = window.into();
 
-        let active_tooltip: Rc<RefCell<Option<ActiveTooltip>>> = Rc::new(RefCell::new(None));
-        let weak_active_tooltip = Rc::downgrade(&active_tooltip);
-
-        let mut interactivity = Interactivity::default();
-        interactivity.tooltip(|_, cx| cx.new(|_| TestTooltipView).into());
-        let tooltip_builder = interactivity.tooltip_builder.take().unwrap();
-        let tooltip_is_hoverable = tooltip_builder.hoverable;
-        let build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>> =
-            Rc::new(move |window: &mut Window, cx: &mut App| {
-                Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
-            });
-        let check_is_hovered: Rc<dyn Fn(&Window) -> bool> = Rc::new(|_: &Window| true);
-        let check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool> =
-            Rc::new(|_: &Window| true);
+        test_app
+            .update_window(any_window, |_, window, cx| {
+                window.draw(cx).clear();
+            })
+            .unwrap();
 
         test_app
             .update_window(any_window, |_, window, cx| {
-                handle_tooltip_mouse_move(
-                    &active_tooltip,
-                    &build_tooltip,
-                    &check_is_hovered,
-                    &check_is_hovered_during_prepaint,
-                    DispatchPhase::Bubble,
-                    window,
+                window.dispatch_event(
+                    MouseMoveEvent {
+                        position: point(px(10.), px(10.)),
+                        modifiers: Default::default(),
+                        pressed_button: None,
+                    }
+                    .to_platform_input(),
                     cx,
                 );
             })
             .unwrap();
 
+        test_app
+            .update_window(any_window, |_, window, cx| {
+                window.draw(cx).clear();
+            })
+            .unwrap();
+
+        (test_app, any_window, captured_active_tooltip)
+    }
+
+    #[test]
+    fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
+        let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test();
+
+        let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
+        let active_tooltip = weak_active_tooltip.upgrade().unwrap();
         assert!(matches!(
             active_tooltip.borrow().as_ref(),
             Some(ActiveTooltip::WaitingForShow { .. })
         ));
-        assert_eq!(Rc::strong_count(&active_tooltip), 1);
+
+        test_app
+            .update_window(any_window, |_, window, _| {
+                window.remove_window();
+            })
+            .unwrap();
+        test_app.run_until_parked();
+        drop(active_tooltip);
+
+        assert!(weak_active_tooltip.upgrade().is_none());
+    }
+
+    #[test]
+    fn tooltip_is_released_when_its_owner_disappears() {
+        let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test();
+
+        let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
+        let active_tooltip = weak_active_tooltip.upgrade().unwrap();
 
         test_app.dispatcher.advance_clock(TOOLTIP_SHOW_DELAY);
         test_app.run_until_parked();
@@ -3684,8 +3813,13 @@ mod tests {
             active_tooltip.borrow().as_ref(),
             Some(ActiveTooltip::Visible { .. })
         ));
-        assert_eq!(Rc::strong_count(&active_tooltip), 1);
 
+        test_app
+            .update_window(any_window, |_, window, _| {
+                window.remove_window();
+            })
+            .unwrap();
+        test_app.run_until_parked();
         drop(active_tooltip);
 
         assert!(weak_active_tooltip.upgrade().is_none());