diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 4dbc7be652d3acf73bc90e3c709039fb79223074..b91d5381a0e8cb1476042d52a0c4e6085902d5a2 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -136,6 +136,25 @@ impl Render for () { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement {} } +/// A quick way to create a [`Render`]able view without having to define a new type. +#[cfg(any(test, feature = "test-support"))] +pub struct TestView(Box) -> AnyElement>); + +#[cfg(any(test, feature = "test-support"))] +impl TestView { + /// Construct a TestView from a render closure. + pub fn new) -> AnyElement + 'static>(f: F) -> Self { + Self(Box::new(f)) + } +} + +#[cfg(any(test, feature = "test-support"))] +impl Render for TestView { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + (self.0)(cx) + } +} + /// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to construct reusable `components` out of plain data. Think of /// components as a recipe for a certain pattern of elements. RenderOnce allows diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 4a4c328fd07baf576da83f0dd79e209334335359..edd998c84848d8a5f63ca580f6331449e189bc60 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -94,6 +94,7 @@ type AnyObserver = Box bool + 'static>; type AnyWindowFocusListener = Box bool + 'static>; +#[derive(Debug)] struct FocusEvent { previous_focus_path: SmallVec<[FocusId; 8]>, current_focus_path: SmallVec<[FocusId; 8]>, @@ -2015,11 +2016,12 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + // Always emit a notify effect, so that handlers fire correctly + self.window_cx.app.push_effect(Effect::Notify { + emitter: self.view.model.entity_id, + }); if !self.window.drawing { self.window_cx.window.dirty = true; - self.window_cx.app.push_effect(Effect::Notify { - emitter: self.view.model.entity_id, - }); } } @@ -2751,3 +2753,59 @@ pub fn outline(bounds: impl Into>, border_color: impl Into) border_color: border_color.into(), } } + +#[cfg(test)] +mod test { + + use std::{cell::RefCell, rc::Rc}; + + use crate::{ + self as gpui, div, FocusHandle, InteractiveElement, IntoElement, Render, TestAppContext, + ViewContext, VisualContext, + }; + + #[gpui::test] + fn test_notify_on_focus(cx: &mut TestAppContext) { + struct TestFocusView { + handle: FocusHandle, + } + + impl Render for TestFocusView { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div().id("test").track_focus(&self.handle) + } + } + + let notify_counter = Rc::new(RefCell::new(0)); + + let (notify_producer, cx) = cx.add_window_view(|cx| { + cx.activate_window(); + let handle = cx.focus_handle(); + + cx.on_focus(&handle, |_, cx| { + cx.notify(); + }) + .detach(); + + TestFocusView { handle } + }); + + let focus_handle = cx.update(|cx| notify_producer.read(cx).handle.clone()); + + let _notify_consumer = cx.new_view({ + |cx| { + let notify_counter = notify_counter.clone(); + cx.observe(¬ify_producer, move |_, _, _| { + *notify_counter.borrow_mut() += 1; + }) + .detach(); + } + }); + + cx.update(|cx| { + cx.focus(&focus_handle); + }); + + assert_eq!(*notify_counter.borrow(), 1); + } +}