mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
- **Fix race condition in test_collaborating_with_completion** - **WIP: Integrate scheduler crate into GPUI TestDispatcher** - **WIP: scheduler integration debugging** - **Fix formatting** - **Unify RunnableMeta and add execution tracking to TestScheduler** - **Remove unused execution tracking from TestScheduler and TestDispatcher** - **Add is_ready() to GPUI Task for API parity with scheduler** - **Eliminate RunnableVariant::Compat - all runnables now have source location metadata** - **Update integration plans to reflect completed phases** - **Simplify RunnableVariant to type alias** - **Delegate TestDispatcher task queues to TestScheduler (Phase 2b)** - **Remove waiting_hint/waiting_backtrace and debug logging from TestDispatcher** - **Remove wrapper methods from TestDispatcher - access scheduler() directly** - **Update integration plan with complete state and instructions for full scheduler migration** - **Use scheduler's native timer() and simplify TestDispatcher** - **Fix rng() usage to lock mutex, update plan with SharedRng wrapper** - **Add SharedRng wrapper for ergonomic random number generation** - **Update plan: mark Phase 1 (SharedRng) as complete** - **Update scheduler integration plan with Phase 2 investigation notes** - **Phase 3: Delegate simulate_random_delay to scheduler.yield_random()** - **Phase 4: Remove TaskLabel** - **Phase 5 (WIP): Simplify block_internal and remove unparkers** - **Phase 5 Complete: Scheduler integration finished** - **Update integration plan with code review findings** - **Phase 6 & 7: Restore realtime priority support and delete dead code** - **Add TestApp and TestAppWindow for cleaner GPUI testing** - **Fix formatting across the branch** - **Fix Linux build: add explicit type annotation and rename probability() to weight()** - **Add TestApp and TestAppWindow for cleaner GPUI testing** - **Rename TestAppWindow to TestWindow, internal TestWindow to TestPlatformWindow** - **Remove unused RunnableVariant imports on Linux** - **Add STATUS.md for next agent** - **Run cargo fmt** - **Use per-app element arena only and scope test draws** - **Fix collab tests for scheduler timing and ordering** - **Store element arena on App and route element allocations through draw scope** - **Fix TestScheduler lock ordering between rng and state** - **Fix inlay hints test by explicitly triggering refresh after viewport setup** - **Add scheduler integration regression risk analysis doc** - **Fix tests: avoid caching Entity in global OnceLock for Codestral API key** - **Document learned weak point: global cached Entity handles break across App contexts** - **Add scheduler regression test for block_with_timeout continuation and explicit time advancement** - **Document TestScheduler timeout tick budget behavior and explicit time advancement guidance** - **Add test asserting realtime priority spawns panic under TestDispatcher** - **Document realtime priority determinism contract in tests** - **Remove realtime priority until we have a concrete use case (cc @localcc)** - **Update STATUS for scheduler integration decisions and realtime priority removal** - **Fix prettier docs and clippy in scheduler tests** - **Remove unused imports from Windows dispatcher** - **WIP: scheduler integration debugging + agent terminal diagnostics** - **Update scheduler integration status** - **Remove temporary planning docs, consolidate into scheduler integration doc** - **Remove unrelated changes from scheduler integration** - **Fix clippy errors** - **Add STATUS.md with debugging instructions for Linux/Windows hang** - **WIP: local changes needed by ex** - **Add pointer capture API for stable drag handling** - **Add pointer capture API for stable drag handling** - **chore: update generated cargo manifests** - **gpui: Expose ShapedLine::width() for pen advancement** - **Remove git2 usage from util test.rs** - **Store DiagnosticQuad bounds in logical Pixels** - **WIP: executor and test_app changes for scheduler integration** - **Expose font APIs publicly** - **gpui: add typed diagnostics and record_diagnostic API** - **WIP: gpui test window diagnostics changes** - **Add LineCacheKey trait and shape_line_cached API for content-addressable shaping** - **Fix RenderGlyphParams field additions for Ex compatibility** - **Add doc comment for recommended_rendering_mode, fix formatting** - **Add scheduler_executor() method for Ex compatibility** - **Fix TestWindow -> TestPlatformWindow in test_context.rs** - **Add headless metal renderer and window focus improvements** - **Fix double borrow in TestWindow::simulate_resize** - **Fix cbindgen panic: remove default type parameter from Diagnostic<T>** - **Implement AppContext for HeadlessMetalAppContext** - **Missing trait impls** - **Add ShapedLine::split_at and eliminate re-shaping in soft wraps** - **Add handoff doc for platform-neutral-tests merge** - **Remove ex-only test infrastructure before merging main** - **Add cross-platform HeadlessAppContext with pluggable text system** - **Export platform_text_system() from gpui_windows for cross-platform tests** - **Restore TestApp/TestAppWindow with pluggable text system support** - **Add TestApp::open_window_sized for tests that need specific window dimensions** - **Fix some warnings** - **Fixes** - **Add a platform-neutral headless renderer interface** - **Synchronize Managed texture before CPU readback on discrete GPUs** - **Allow creating TestDispatcher with custom scheduler** Release Notes: - N/A --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: John Tur <john-tur@outlook.com> Co-authored-by: Agus Zubiaga <agus@zed.dev> Co-authored-by: Antonio Scandurra <me@as-cii.com>
607 lines
18 KiB
Rust
607 lines
18 KiB
Rust
//! A clean testing API for GPUI applications.
|
|
//!
|
|
//! `TestApp` provides a simpler alternative to `TestAppContext` with:
|
|
//! - Automatic effect flushing after updates
|
|
//! - Clean window creation and inspection
|
|
//! - Input simulation helpers
|
|
//!
|
|
//! # Example
|
|
//! ```ignore
|
|
//! #[test]
|
|
//! fn test_my_view() {
|
|
//! let mut app = TestApp::new();
|
|
//!
|
|
//! let mut window = app.open_window(|window, cx| {
|
|
//! MyView::new(window, cx)
|
|
//! });
|
|
//!
|
|
//! window.update(|view, window, cx| {
|
|
//! view.do_something(cx);
|
|
//! });
|
|
//!
|
|
//! // Check rendered state
|
|
//! assert_eq!(window.title(), Some("Expected Title"));
|
|
//! }
|
|
//! ```
|
|
|
|
use crate::{
|
|
AnyWindowHandle, App, AppCell, AppContext, AsyncApp, BackgroundExecutor, BorrowAppContext,
|
|
Bounds, ClipboardItem, Context, Entity, ForegroundExecutor, Global, InputEvent, Keystroke,
|
|
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Platform,
|
|
PlatformTextSystem, Point, Render, Size, Task, TestDispatcher, TestPlatform, TextSystem,
|
|
Window, WindowBounds, WindowHandle, WindowOptions, app::GpuiMode,
|
|
};
|
|
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
|
|
|
|
/// A test application context with a clean API.
|
|
///
|
|
/// Unlike `TestAppContext`, `TestApp` automatically flushes effects after
|
|
/// each update and provides simpler window management.
|
|
pub struct TestApp {
|
|
app: Rc<AppCell>,
|
|
platform: Rc<TestPlatform>,
|
|
background_executor: BackgroundExecutor,
|
|
foreground_executor: ForegroundExecutor,
|
|
#[allow(dead_code)]
|
|
dispatcher: TestDispatcher,
|
|
text_system: Arc<TextSystem>,
|
|
}
|
|
|
|
impl TestApp {
|
|
/// Create a new test application.
|
|
pub fn new() -> Self {
|
|
Self::with_seed(0)
|
|
}
|
|
|
|
/// Create a new test application with a specific random seed.
|
|
pub fn with_seed(seed: u64) -> Self {
|
|
Self::build(seed, None, Arc::new(()))
|
|
}
|
|
|
|
/// Create a new test application with a custom text system for real font shaping.
|
|
pub fn with_text_system(text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
|
Self::build(0, Some(text_system), Arc::new(()))
|
|
}
|
|
|
|
/// Create a new test application with a custom text system and asset source.
|
|
pub fn with_text_system_and_assets(
|
|
text_system: Arc<dyn PlatformTextSystem>,
|
|
asset_source: Arc<dyn crate::AssetSource>,
|
|
) -> Self {
|
|
Self::build(0, Some(text_system), asset_source)
|
|
}
|
|
|
|
fn build(
|
|
seed: u64,
|
|
platform_text_system: Option<Arc<dyn PlatformTextSystem>>,
|
|
asset_source: Arc<dyn crate::AssetSource>,
|
|
) -> Self {
|
|
let dispatcher = TestDispatcher::new(seed);
|
|
let arc_dispatcher = Arc::new(dispatcher.clone());
|
|
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
|
|
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
|
|
let platform = match platform_text_system.clone() {
|
|
Some(ts) => TestPlatform::with_text_system(
|
|
background_executor.clone(),
|
|
foreground_executor.clone(),
|
|
ts,
|
|
),
|
|
None => TestPlatform::new(background_executor.clone(), foreground_executor.clone()),
|
|
};
|
|
let http_client = http_client::FakeHttpClient::with_404_response();
|
|
let text_system = Arc::new(TextSystem::new(
|
|
platform_text_system.unwrap_or_else(|| platform.text_system.clone()),
|
|
));
|
|
|
|
let app = App::new_app(platform.clone(), asset_source, http_client);
|
|
app.borrow_mut().mode = GpuiMode::test();
|
|
|
|
Self {
|
|
app,
|
|
platform,
|
|
background_executor,
|
|
foreground_executor,
|
|
dispatcher,
|
|
text_system,
|
|
}
|
|
}
|
|
|
|
/// Run a closure with mutable access to the App context.
|
|
/// Automatically runs until parked after the closure completes.
|
|
pub fn update<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
|
|
let result = {
|
|
let mut app = self.app.borrow_mut();
|
|
app.update(f)
|
|
};
|
|
self.run_until_parked();
|
|
result
|
|
}
|
|
|
|
/// Run a closure with read-only access to the App context.
|
|
pub fn read<R>(&self, f: impl FnOnce(&App) -> R) -> R {
|
|
let app = self.app.borrow();
|
|
f(&app)
|
|
}
|
|
|
|
/// Create a new entity in the app.
|
|
pub fn new_entity<T: 'static>(
|
|
&mut self,
|
|
build: impl FnOnce(&mut Context<T>) -> T,
|
|
) -> Entity<T> {
|
|
self.update(|cx| cx.new(build))
|
|
}
|
|
|
|
/// Update an entity.
|
|
pub fn update_entity<T: 'static, R>(
|
|
&mut self,
|
|
entity: &Entity<T>,
|
|
f: impl FnOnce(&mut T, &mut Context<T>) -> R,
|
|
) -> R {
|
|
self.update(|cx| entity.update(cx, f))
|
|
}
|
|
|
|
/// Read an entity.
|
|
pub fn read_entity<T: 'static, R>(
|
|
&self,
|
|
entity: &Entity<T>,
|
|
f: impl FnOnce(&T, &App) -> R,
|
|
) -> R {
|
|
self.read(|cx| f(entity.read(cx), cx))
|
|
}
|
|
|
|
/// Open a test window with the given root view, using maximized bounds.
|
|
pub fn open_window<V: Render + 'static>(
|
|
&mut self,
|
|
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
|
|
) -> TestAppWindow<V> {
|
|
let bounds = self.read(|cx| Bounds::maximized(None, cx));
|
|
let handle = self.update(|cx| {
|
|
cx.open_window(
|
|
WindowOptions {
|
|
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
|
..Default::default()
|
|
},
|
|
|window, cx| cx.new(|cx| build_view(window, cx)),
|
|
)
|
|
.unwrap()
|
|
});
|
|
|
|
TestAppWindow {
|
|
handle,
|
|
app: self.app.clone(),
|
|
platform: self.platform.clone(),
|
|
background_executor: self.background_executor.clone(),
|
|
}
|
|
}
|
|
|
|
/// Open a test window with specific options.
|
|
pub fn open_window_with_options<V: Render + 'static>(
|
|
&mut self,
|
|
options: WindowOptions,
|
|
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
|
|
) -> TestAppWindow<V> {
|
|
let handle = self.update(|cx| {
|
|
cx.open_window(options, |window, cx| cx.new(|cx| build_view(window, cx)))
|
|
.unwrap()
|
|
});
|
|
|
|
TestAppWindow {
|
|
handle,
|
|
app: self.app.clone(),
|
|
platform: self.platform.clone(),
|
|
background_executor: self.background_executor.clone(),
|
|
}
|
|
}
|
|
|
|
/// Run pending tasks until there's nothing left to do.
|
|
pub fn run_until_parked(&self) {
|
|
self.background_executor.run_until_parked();
|
|
}
|
|
|
|
/// Advance the simulated clock by the given duration.
|
|
pub fn advance_clock(&self, duration: Duration) {
|
|
self.background_executor.advance_clock(duration);
|
|
}
|
|
|
|
/// Spawn a future on the foreground executor.
|
|
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncApp) -> Fut) -> Task<R>
|
|
where
|
|
Fut: Future<Output = R> + 'static,
|
|
R: 'static,
|
|
{
|
|
self.foreground_executor.spawn(f(self.to_async()))
|
|
}
|
|
|
|
/// Spawn a future on the background executor.
|
|
pub fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
|
where
|
|
R: Send + 'static,
|
|
{
|
|
self.background_executor.spawn(future)
|
|
}
|
|
|
|
/// Get an async handle to the app.
|
|
pub fn to_async(&self) -> AsyncApp {
|
|
AsyncApp {
|
|
app: Rc::downgrade(&self.app),
|
|
background_executor: self.background_executor.clone(),
|
|
foreground_executor: self.foreground_executor.clone(),
|
|
}
|
|
}
|
|
|
|
/// Get the background executor.
|
|
pub fn background_executor(&self) -> &BackgroundExecutor {
|
|
&self.background_executor
|
|
}
|
|
|
|
/// Get the foreground executor.
|
|
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
|
&self.foreground_executor
|
|
}
|
|
|
|
/// Get the text system.
|
|
pub fn text_system(&self) -> &Arc<TextSystem> {
|
|
&self.text_system
|
|
}
|
|
|
|
/// Check if a global of the given type exists.
|
|
pub fn has_global<G: Global>(&self) -> bool {
|
|
self.read(|cx| cx.has_global::<G>())
|
|
}
|
|
|
|
/// Set a global value.
|
|
pub fn set_global<G: Global>(&mut self, global: G) {
|
|
self.update(|cx| cx.set_global(global));
|
|
}
|
|
|
|
/// Read a global value.
|
|
pub fn read_global<G: Global, R>(&self, f: impl FnOnce(&G, &App) -> R) -> R {
|
|
self.read(|cx| f(cx.global(), cx))
|
|
}
|
|
|
|
/// Update a global value.
|
|
pub fn update_global<G: Global, R>(&mut self, f: impl FnOnce(&mut G, &mut App) -> R) -> R {
|
|
self.update(|cx| cx.update_global(f))
|
|
}
|
|
|
|
// Platform simulation methods
|
|
|
|
/// Write text to the simulated clipboard.
|
|
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
|
self.platform.write_to_clipboard(item);
|
|
}
|
|
|
|
/// Read from the simulated clipboard.
|
|
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
|
self.platform.read_from_clipboard()
|
|
}
|
|
|
|
/// Get URLs that have been opened via `cx.open_url()`.
|
|
pub fn opened_url(&self) -> Option<String> {
|
|
self.platform.opened_url.borrow().clone()
|
|
}
|
|
|
|
/// Check if a file path prompt is pending.
|
|
pub fn did_prompt_for_new_path(&self) -> bool {
|
|
self.platform.did_prompt_for_new_path()
|
|
}
|
|
|
|
/// Simulate answering a path selection dialog.
|
|
pub fn simulate_new_path_selection(
|
|
&self,
|
|
select: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
|
|
) {
|
|
self.platform.simulate_new_path_selection(select);
|
|
}
|
|
|
|
/// Check if a prompt dialog is pending.
|
|
pub fn has_pending_prompt(&self) -> bool {
|
|
self.platform.has_pending_prompt()
|
|
}
|
|
|
|
/// Simulate answering a prompt dialog.
|
|
pub fn simulate_prompt_answer(&self, button: &str) {
|
|
self.platform.simulate_prompt_answer(button);
|
|
}
|
|
|
|
/// Get all open windows.
|
|
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
|
self.read(|cx| cx.windows())
|
|
}
|
|
}
|
|
|
|
impl Default for TestApp {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// A test window with inspection and simulation capabilities.
|
|
pub struct TestAppWindow<V> {
|
|
handle: WindowHandle<V>,
|
|
app: Rc<AppCell>,
|
|
platform: Rc<TestPlatform>,
|
|
background_executor: BackgroundExecutor,
|
|
}
|
|
|
|
impl<V: 'static + Render> TestAppWindow<V> {
|
|
/// Get the window handle.
|
|
pub fn handle(&self) -> WindowHandle<V> {
|
|
self.handle
|
|
}
|
|
|
|
/// Get the root view entity.
|
|
pub fn root(&self) -> Entity<V> {
|
|
let mut app = self.app.borrow_mut();
|
|
let any_handle: AnyWindowHandle = self.handle.into();
|
|
app.update_window(any_handle, |root_view, _, _| {
|
|
root_view.downcast::<V>().expect("root view type mismatch")
|
|
})
|
|
.expect("window not found")
|
|
}
|
|
|
|
/// Update the root view.
|
|
pub fn update<R>(&mut self, f: impl FnOnce(&mut V, &mut Window, &mut Context<V>) -> R) -> R {
|
|
let result = {
|
|
let mut app = self.app.borrow_mut();
|
|
let any_handle: AnyWindowHandle = self.handle.into();
|
|
app.update_window(any_handle, |root_view, window, cx| {
|
|
let view = root_view.downcast::<V>().expect("root view type mismatch");
|
|
view.update(cx, |view, cx| f(view, window, cx))
|
|
})
|
|
.expect("window not found")
|
|
};
|
|
self.background_executor.run_until_parked();
|
|
result
|
|
}
|
|
|
|
/// Read the root view.
|
|
pub fn read<R>(&self, f: impl FnOnce(&V, &App) -> R) -> R {
|
|
let app = self.app.borrow();
|
|
let view = self
|
|
.app
|
|
.borrow()
|
|
.windows
|
|
.get(self.handle.window_id())
|
|
.and_then(|w| w.as_ref())
|
|
.and_then(|w| w.root.clone())
|
|
.and_then(|r| r.downcast::<V>().ok())
|
|
.expect("window or root view not found");
|
|
f(view.read(&app), &app)
|
|
}
|
|
|
|
/// Get the window title.
|
|
pub fn title(&self) -> Option<String> {
|
|
let app = self.app.borrow();
|
|
app.read_window(&self.handle, |_, _cx| {
|
|
// TODO: expose title through Window API
|
|
None
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
/// Simulate a keystroke.
|
|
pub fn simulate_keystroke(&mut self, keystroke: &str) {
|
|
let keystroke = Keystroke::parse(keystroke).unwrap();
|
|
{
|
|
let mut app = self.app.borrow_mut();
|
|
let any_handle: AnyWindowHandle = self.handle.into();
|
|
app.update_window(any_handle, |_, window, cx| {
|
|
window.dispatch_keystroke(keystroke, cx);
|
|
})
|
|
.unwrap();
|
|
}
|
|
self.background_executor.run_until_parked();
|
|
}
|
|
|
|
/// Simulate multiple keystrokes (space-separated).
|
|
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
|
|
for keystroke in keystrokes.split(' ') {
|
|
self.simulate_keystroke(keystroke);
|
|
}
|
|
}
|
|
|
|
/// Simulate typing text.
|
|
pub fn simulate_input(&mut self, input: &str) {
|
|
for char in input.chars() {
|
|
self.simulate_keystroke(&char.to_string());
|
|
}
|
|
}
|
|
|
|
/// Simulate a mouse move.
|
|
pub fn simulate_mouse_move(&mut self, position: Point<Pixels>) {
|
|
self.simulate_event(MouseMoveEvent {
|
|
position,
|
|
modifiers: Default::default(),
|
|
pressed_button: None,
|
|
});
|
|
}
|
|
|
|
/// Simulate a mouse down event.
|
|
pub fn simulate_mouse_down(&mut self, position: Point<Pixels>, button: MouseButton) {
|
|
self.simulate_event(MouseDownEvent {
|
|
position,
|
|
button,
|
|
modifiers: Default::default(),
|
|
click_count: 1,
|
|
first_mouse: false,
|
|
});
|
|
}
|
|
|
|
/// Simulate a mouse up event.
|
|
pub fn simulate_mouse_up(&mut self, position: Point<Pixels>, button: MouseButton) {
|
|
self.simulate_event(MouseUpEvent {
|
|
position,
|
|
button,
|
|
modifiers: Default::default(),
|
|
click_count: 1,
|
|
});
|
|
}
|
|
|
|
/// Simulate a click at the given position.
|
|
pub fn simulate_click(&mut self, position: Point<Pixels>, button: MouseButton) {
|
|
self.simulate_mouse_down(position, button);
|
|
self.simulate_mouse_up(position, button);
|
|
}
|
|
|
|
/// Simulate a scroll event.
|
|
pub fn simulate_scroll(&mut self, position: Point<Pixels>, delta: Point<Pixels>) {
|
|
self.simulate_event(crate::ScrollWheelEvent {
|
|
position,
|
|
delta: crate::ScrollDelta::Pixels(delta),
|
|
modifiers: Default::default(),
|
|
touch_phase: crate::TouchPhase::Moved,
|
|
});
|
|
}
|
|
|
|
/// Simulate an input event.
|
|
pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
|
|
let platform_input = event.to_platform_input();
|
|
{
|
|
let mut app = self.app.borrow_mut();
|
|
let any_handle: AnyWindowHandle = self.handle.into();
|
|
app.update_window(any_handle, |_, window, cx| {
|
|
window.dispatch_event(platform_input, cx);
|
|
})
|
|
.unwrap();
|
|
}
|
|
self.background_executor.run_until_parked();
|
|
}
|
|
|
|
/// Simulate resizing the window.
|
|
pub fn simulate_resize(&mut self, size: Size<Pixels>) {
|
|
let window_id = self.handle.window_id();
|
|
let mut app = self.app.borrow_mut();
|
|
if let Some(Some(window)) = app.windows.get_mut(window_id) {
|
|
if let Some(test_window) = window.platform_window.as_test() {
|
|
test_window.simulate_resize(size);
|
|
}
|
|
}
|
|
drop(app);
|
|
self.background_executor.run_until_parked();
|
|
}
|
|
|
|
/// Force a redraw of the window.
|
|
pub fn draw(&mut self) {
|
|
let mut app = self.app.borrow_mut();
|
|
let any_handle: AnyWindowHandle = self.handle.into();
|
|
app.update_window(any_handle, |_, window, cx| {
|
|
window.draw(cx).clear();
|
|
})
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
impl<V> Clone for TestAppWindow<V> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
handle: self.handle,
|
|
app: self.app.clone(),
|
|
platform: self.platform.clone(),
|
|
background_executor: self.background_executor.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{FocusHandle, Focusable, div, prelude::*};
|
|
|
|
struct Counter {
|
|
count: usize,
|
|
focus_handle: FocusHandle,
|
|
}
|
|
|
|
impl Counter {
|
|
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
let focus_handle = cx.focus_handle();
|
|
Self {
|
|
count: 0,
|
|
focus_handle,
|
|
}
|
|
}
|
|
|
|
fn increment(&mut self, _cx: &mut Context<Self>) {
|
|
self.count += 1;
|
|
}
|
|
}
|
|
|
|
impl Focusable for Counter {
|
|
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
|
self.focus_handle.clone()
|
|
}
|
|
}
|
|
|
|
impl Render for Counter {
|
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
div().child(format!("Count: {}", self.count))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic_usage() {
|
|
let mut app = TestApp::new();
|
|
|
|
let mut window = app.open_window(Counter::new);
|
|
|
|
window.update(|counter, _window, cx| {
|
|
counter.increment(cx);
|
|
});
|
|
|
|
window.read(|counter, _| {
|
|
assert_eq!(counter.count, 1);
|
|
});
|
|
|
|
drop(window);
|
|
app.update(|cx| cx.shutdown());
|
|
}
|
|
|
|
#[test]
|
|
fn test_entity_creation() {
|
|
let mut app = TestApp::new();
|
|
|
|
let entity = app.new_entity(|cx| Counter {
|
|
count: 42,
|
|
focus_handle: cx.focus_handle(),
|
|
});
|
|
|
|
app.read_entity(&entity, |counter, _| {
|
|
assert_eq!(counter.count, 42);
|
|
});
|
|
|
|
app.update_entity(&entity, |counter, _cx| {
|
|
counter.count += 1;
|
|
});
|
|
|
|
app.read_entity(&entity, |counter, _| {
|
|
assert_eq!(counter.count, 43);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_globals() {
|
|
let mut app = TestApp::new();
|
|
|
|
struct MyGlobal(String);
|
|
impl Global for MyGlobal {}
|
|
|
|
assert!(!app.has_global::<MyGlobal>());
|
|
|
|
app.set_global(MyGlobal("hello".into()));
|
|
|
|
assert!(app.has_global::<MyGlobal>());
|
|
|
|
app.read_global::<MyGlobal, _>(|global, _| {
|
|
assert_eq!(global.0, "hello");
|
|
});
|
|
|
|
app.update_global::<MyGlobal, _>(|global, _| {
|
|
global.0 = "world".into();
|
|
});
|
|
|
|
app.read_global::<MyGlobal, _>(|global, _| {
|
|
assert_eq!(global.0, "world");
|
|
});
|
|
}
|
|
}
|