Better TestWindow support

This commit is contained in:
Conrad Irwin
2024-01-04 08:00:40 -07:00
parent 073128e4f3
commit 5c32dd5688
4 changed files with 171 additions and 144 deletions
Generated
+1 -1
View File
@@ -9514,7 +9514,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.119.0"
version = "0.120.0"
dependencies = [
"activity_indicator",
"ai",
+26 -106
View File
@@ -1,14 +1,13 @@
use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
InputEvent, IntoElement, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task,
TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext,
WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
#[derive(Clone)]
pub struct TestAppContext {
@@ -185,42 +184,7 @@ impl TestAppContext {
}
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
let (mut handlers, scale_factor) = self
.app
.borrow_mut()
.update_window(window_handle, |_, cx| {
let platform_window = cx.window.platform_window.as_test().unwrap();
let scale_factor = platform_window.scale_factor();
match &mut platform_window.bounds {
WindowBounds::Fullscreen | WindowBounds::Maximized => {
platform_window.bounds = WindowBounds::Fixed(Bounds {
origin: Point::default(),
size: size.map(|pixels| f64::from(pixels).into()),
});
}
WindowBounds::Fixed(bounds) => {
bounds.size = size.map(|pixels| f64::from(pixels).into());
}
}
(
mem::take(&mut platform_window.handlers.lock().resize),
scale_factor,
)
})
.unwrap();
for handler in &mut handlers {
handler(size, scale_factor);
}
self.app
.borrow_mut()
.update_window(window_handle, |_, cx| {
let platform_window = cx.window.platform_window.as_test().unwrap();
platform_window.handlers.lock().resize = handlers;
})
.unwrap();
self.test_window(window_handle).simulate_resize(size);
}
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
@@ -313,41 +277,22 @@ impl TestAppContext {
keystroke: Keystroke,
is_held: bool,
) {
let keystroke2 = keystroke.clone();
let handled = window
.update(self, |_, cx| {
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
})
.is_ok_and(|handled| handled);
if handled {
return;
}
let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
let Some(input_handler) = input_handler else {
panic!(
"dispatch_keystroke {:?} failed to dispatch action or input",
&keystroke2
);
};
let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
input_handler.lock().replace_text_in_range(None, &text);
self.test_window(window)
.simulate_keystroke(keystroke, is_held)
}
pub fn update_test_window<R>(
&mut self,
window: AnyWindowHandle,
f: impl FnOnce(&mut TestWindow) -> R,
) -> R {
window
.update(self, |_, cx| {
f(cx.window
.platform_window
.as_any_mut()
.downcast_mut::<TestWindow>()
.unwrap())
})
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app
.borrow_mut()
.windows
.get_mut(window.id)
.unwrap()
.as_mut()
.unwrap()
.platform_window
.as_test()
.unwrap()
.clone()
}
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
@@ -563,11 +508,7 @@ impl<'a> VisualTestContext<'a> {
}
pub fn window_title(&mut self) -> Option<String> {
self.cx
.update_window(self.window, |_, cx| {
cx.window.platform_window.as_test().unwrap().title.clone()
})
.unwrap()
self.cx.test_window(self.window).0.lock().title.clone()
}
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
@@ -579,36 +520,15 @@ impl<'a> VisualTestContext<'a> {
}
pub fn simulate_activation(&mut self) {
self.simulate_window_events(&mut |handlers| {
handlers
.active_status_change
.iter_mut()
.for_each(|f| f(true));
})
self.cx
.test_window(self.window)
.simulate_active_status_change(true)
}
pub fn simulate_deactivation(&mut self) {
self.simulate_window_events(&mut |handlers| {
handlers
.active_status_change
.iter_mut()
.for_each(|f| f(false));
})
}
fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
let handlers = self
.cx
.update_window(self.window, |_, cx| {
cx.window
.platform_window
.as_test()
.unwrap()
.handlers
.clone()
})
.unwrap();
f(&mut *handlers.lock());
self.cx
.test_window(self.window)
.simulate_active_status_change(false)
}
}
+10 -6
View File
@@ -19,7 +19,7 @@ pub struct TestPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
pub(crate) active_window: RefCell<Option<TestWindow>>,
active_display: Rc<dyn PlatformDisplay>,
active_cursor: Mutex<CursorStyle>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
@@ -106,7 +106,7 @@ impl Platform for TestPlatform {
}
fn activate(&self, _ignoring_other_apps: bool) {
unimplemented!()
//
}
fn hide(&self) {
@@ -130,7 +130,10 @@ impl Platform for TestPlatform {
}
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
self.active_window.lock().clone()
self.active_window
.borrow()
.as_ref()
.map(|window| window.0.lock().handle)
}
fn open_window(
@@ -139,12 +142,13 @@ impl Platform for TestPlatform {
options: WindowOptions,
_draw: Box<dyn FnMut() -> Result<Scene>>,
) -> Box<dyn crate::PlatformWindow> {
*self.active_window.lock() = Some(handle);
Box::new(TestWindow::new(
let window = TestWindow::new(
options,
handle,
self.weak.clone(),
self.active_display.clone(),
))
);
Box::new(window)
}
fn set_display_link_output_callback(
+134 -31
View File
@@ -1,7 +1,8 @@
use crate::{
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
WindowBounds, WindowOptions,
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
Keystroke, Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds,
WindowOptions,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -10,51 +11,122 @@ use std::{
sync::{self, Arc},
};
#[derive(Default)]
pub(crate) struct TestWindowHandlers {
pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
pub(crate) moved: Vec<Box<dyn FnMut()>>,
pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
}
pub struct TestWindow {
pub struct TestWindowState {
pub(crate) bounds: WindowBounds,
pub(crate) handle: AnyWindowHandle,
display: Rc<dyn PlatformDisplay>,
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<Box<dyn PlatformInputHandler>>,
}
#[derive(Clone)]
pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
impl TestWindow {
pub fn new(
options: WindowOptions,
handle: AnyWindowHandle,
platform: Weak<TestPlatform>,
display: Rc<dyn PlatformDisplay>,
) -> Self {
Self {
Self(Arc::new(Mutex::new(TestWindowState {
bounds: options.bounds,
display,
platform,
input_handler: None,
handle,
sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(),
title: Default::default(),
edited: false,
input_callback: None,
active_status_change_callback: None,
resize_callback: None,
moved_callback: None,
input_handler: None,
})))
}
pub fn simulate_resize(&mut self, size: Size<Pixels>) {
let scale_factor = self.scale_factor();
let mut lock = self.0.lock();
let Some(mut callback) = lock.resize_callback.take() else {
return;
};
match &mut lock.bounds {
WindowBounds::Fullscreen | WindowBounds::Maximized => {
lock.bounds = WindowBounds::Fixed(Bounds {
origin: Point::default(),
size: size.map(|pixels| f64::from(pixels).into()),
});
}
WindowBounds::Fixed(bounds) => {
bounds.size = size.map(|pixels| f64::from(pixels).into());
}
}
drop(lock);
callback(size, scale_factor);
self.0.lock().resize_callback = Some(callback);
}
pub fn simulate_active_status_change(&self, active: bool) {
let mut lock = self.0.lock();
let Some(mut callback) = lock.active_status_change_callback.take() else {
return;
};
drop(lock);
callback(active);
self.0.lock().active_status_change_callback = Some(callback);
}
pub fn simulate_input(&mut self, event: InputEvent) -> bool {
let mut lock = self.0.lock();
let Some(mut callback) = lock.input_callback.take() else {
return false;
};
drop(lock);
let result = callback(event);
self.0.lock().input_callback = Some(callback);
result
}
pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
if self.simulate_input(InputEvent::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
})) {
return;
}
let mut lock = self.0.lock();
let Some(mut input_handler) = lock.input_handler.take() else {
panic!(
"simulate_keystroke {:?} input event was not handled and there was no active input",
&keystroke
);
};
drop(lock);
let text = keystroke.ime_key.unwrap_or(keystroke.key);
input_handler.replace_text_in_range(None, &text);
self.0.lock().input_handler = Some(input_handler);
}
}
impl PlatformWindow for TestWindow {
fn bounds(&self) -> WindowBounds {
self.bounds
self.0.lock().bounds
}
fn content_size(&self) -> Size<Pixels> {
let bounds = match self.bounds {
let bounds = match self.bounds() {
WindowBounds::Fixed(bounds) => bounds,
WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
};
@@ -74,7 +146,7 @@ impl PlatformWindow for TestWindow {
}
fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
self.display.clone()
self.0.lock().display.clone()
}
fn mouse_position(&self) -> Point<Pixels> {
@@ -90,11 +162,11 @@ impl PlatformWindow for TestWindow {
}
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
self.input_handler = Some(Arc::new(Mutex::new(input_handler)));
self.0.lock().input_handler = Some(input_handler);
}
fn clear_input_handler(&mut self) {
self.input_handler = None;
self.0.lock().input_handler = None;
}
fn prompt(
@@ -103,19 +175,50 @@ impl PlatformWindow for TestWindow {
_msg: &str,
_answers: &[&str],
) -> futures::channel::oneshot::Receiver<usize> {
self.platform.upgrade().expect("platform dropped").prompt()
self.0
.lock()
.platform
.upgrade()
.expect("platform dropped")
.prompt()
}
fn activate(&self) {
unimplemented!()
let this = self.clone();
let executor = self
.0
.lock()
.platform
.upgrade()
.unwrap()
.foreground_executor()
.clone();
executor
.spawn(async move {
let state = this.0.lock();
let platform = state.platform.upgrade().unwrap();
let previous_window = platform.active_window.borrow_mut().replace(this.clone());
drop(state);
drop(platform);
if let Some(previous_window) = previous_window {
if Arc::ptr_eq(&previous_window.0, &this.0) {
return;
}
previous_window.simulate_active_status_change(false);
}
this.simulate_active_status_change(true);
})
.detach();
}
fn set_title(&mut self, title: &str) {
self.title = Some(title.to_owned());
self.0.lock().title = Some(title.to_owned());
}
fn set_edited(&mut self, edited: bool) {
self.edited = edited;
self.0.lock().edited = edited;
}
fn show_character_palette(&self) {
@@ -135,15 +238,15 @@ impl PlatformWindow for TestWindow {
}
fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
self.handlers.lock().input.push(callback)
self.0.lock().input_callback = Some(callback)
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.handlers.lock().active_status_change.push(callback)
self.0.lock().active_status_change_callback = Some(callback)
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.handlers.lock().resize.push(callback)
self.0.lock().resize_callback = Some(callback)
}
fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
@@ -151,7 +254,7 @@ impl PlatformWindow for TestWindow {
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.handlers.lock().moved.push(callback)
self.0.lock().moved_callback = Some(callback)
}
fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
@@ -175,7 +278,7 @@ impl PlatformWindow for TestWindow {
}
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.sprite_atlas.clone()
self.0.lock().sprite_atlas.clone()
}
fn as_test(&mut self) -> Option<&mut TestWindow> {