一键导入
gpui-testing
Testing GPUI applications and components. Use when writing tests, testing async operations, simulating user input, or debugging test failures.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Testing GPUI applications and components. Use when writing tests, testing async operations, simulating user input, or debugging test failures.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Actions and event handling in GPUI applications. Use when implementing user interactions, keyboard shortcuts, custom actions, or event-driven behavior.
Async patterns and concurrency in GPUI applications. Use when implementing background tasks, async/await operations, task management, or concurrent operations.
UI element creation and composition with GPUI. Use when building element trees, creating custom components, implementing layouts, or working with conditional rendering.
Entity and context patterns for state management in GPUI. Use when managing component state, implementing entity lifecycle, handling subscriptions, or avoiding common entity access pitfalls.
| name | gpui-testing |
| description | Testing GPUI applications and components. Use when writing tests, testing async operations, simulating user input, or debugging test failures. |
This skill covers testing patterns for GPUI applications.
GPUI provides testing utilities for:
run_until_parked()#[cfg(test)]
mod tests {
use super::*;
use gpui::*;
#[gpui::test]
async fn test_my_view(cx: &mut TestAppContext) {
// Test code here
}
}
#[gpui::test]
async fn test_counter(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
assert_eq!(counter.read(cx).count, 0);
}
#[gpui::test]
async fn test_async_operation(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
view.start_async_work(cx);
});
// Wait for all async work to complete
cx.background_executor.run_until_parked();
// Check results
assert_eq!(view.read(cx).status, "Complete");
}
IMPORTANT: Use GPUI executor timers, not smol::Timer:
#[gpui::test]
async fn test_with_delay(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
// ✅ CORRECT - Use GPUI timer
cx.background_executor.timer(Duration::from_secs(1)).await;
// ❌ WRONG - Don't use smol::Timer
// smol::Timer::after(Duration::from_secs(1)).await;
cx.background_executor.run_until_parked();
}
Why: GPUI's scheduler tracks GPUI timers but not smol::Timer, which can cause "nothing left to run" errors in run_until_parked().
#[gpui::test]
async fn test_increment(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
counter.update(cx, |counter, cx| {
counter.increment(cx);
});
assert_eq!(counter.read(cx).count, 1);
}
#[gpui::test]
async fn test_notify(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
let observed = Arc::new(AtomicBool::new(false));
let observed_clone = observed.clone();
cx.observe(&view, move |_view, _cx| {
observed_clone.store(true, Ordering::SeqCst);
});
view.update(cx, |view, cx| {
view.data = "changed".into();
cx.notify();
});
assert!(observed.load(Ordering::SeqCst));
}
actions!(test, [TestAction]);
#[gpui::test]
async fn test_action_handling(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
// Simulate action dispatch
view.update(cx, |view, cx| {
view.handle_action(&TestAction, cx);
});
assert_eq!(view.read(cx).action_count, 1);
}
#[derive(Clone, Debug)]
enum MyEvent {
ValueChanged(i32),
}
impl EventEmitter<MyEvent> for MyView {}
#[gpui::test]
async fn test_event_emission(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
let received_events = Arc::new(Mutex::new(Vec::new()));
let events_clone = received_events.clone();
cx.subscribe(&view, move |_this, _emitter, event, _cx| {
events_clone.lock().unwrap().push(event.clone());
});
view.update(cx, |view, cx| {
cx.emit(MyEvent::ValueChanged(42));
});
let events = received_events.lock().unwrap();
assert_eq!(events.len(), 1);
}
#[gpui::test]
async fn test_background_task(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
cx.spawn(async move |this, cx| {
let result = cx.background_spawn(async {
// Expensive computation
42
}).await;
this.update(&mut *cx, |view, cx| {
view.result = Some(result);
cx.notify();
})?;
Ok(())
}).detach();
});
// Wait for all async work
cx.background_executor.run_until_parked();
assert_eq!(view.read(cx).result, Some(42));
}
#[gpui::test]
async fn test_task_cancellation(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
view.start_long_task(cx);
});
// Cancel task
view.update(cx, |view, cx| {
view.cancel_task();
});
cx.background_executor.run_until_parked();
// Task should not have completed
assert_eq!(view.read(cx).task_completed, false);
}
#[gpui::test]
async fn test_state_changes(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
// Initial state
assert_eq!(view.read(cx).count, 0);
// After update
view.update(cx, |view, cx| {
view.count = 10;
cx.notify();
});
assert_eq!(view.read(cx).count, 10);
}
#[gpui::test]
async fn test_with_assertions(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
view.process_data(vec![1, 2, 3], cx);
});
cx.background_executor.run_until_parked();
let view_state = view.read(cx);
assert!(view_state.is_processed);
assert_eq!(view_state.items.len(), 3);
assert!(view_state.error.is_none());
}
#[gpui::test]
async fn test_with_setup(cx: &mut TestAppContext) {
// Setup
let state = cx.new(|_| AppState::default());
cx.set_global(state.clone());
// Test
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
view.use_global_state(cx);
});
// Assertions
assert!(view.read(cx).has_state);
// Teardown is automatic when test ends
}
#[gpui::test]
async fn test_error_handling(cx: &mut TestAppContext) {
let view = cx.new(|_| MyView::new());
view.update(cx, |view, cx| {
cx.spawn(async move |this, cx| {
// Simulate error
let result: Result<(), anyhow::Error> = Err(anyhow::anyhow!("Test error"));
this.update(&mut *cx, |view, cx| {
view.error = result.err().map(|e| e.to_string());
cx.notify();
})?;
Ok(())
}).detach();
});
cx.background_executor.run_until_parked();
assert!(view.read(cx).error.is_some());
}
#[gpui::test] attribute: Required for GPUI testscx.background_executor.timer() instead of smol::Timerrun_until_parked(): For async operations| Mistake | Problem | Solution |
|---|---|---|
Using smol::Timer | "Nothing left to run" | Use cx.background_executor.timer() |
Not calling run_until_parked() | Async work doesn't complete | Call before assertions |
Forgetting #[gpui::test] | Test doesn't run properly | Use #[gpui::test] attribute |
| Not handling errors in async | Test failures unclear | Propagate errors with ? |
| Testing too much at once | Hard to debug failures | Split into smaller tests |
#[cfg(test)]
mod tests {
use super::*;
use gpui::*;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
#[gpui::test]
async fn test_counter_initialization(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
assert_eq!(counter.read(cx).count, 0);
}
#[gpui::test]
async fn test_counter_increment(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
counter.update(cx, |counter, cx| {
counter.increment(cx);
});
assert_eq!(counter.read(cx).count, 1);
}
#[gpui::test]
async fn test_counter_async_increment(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
counter.update(cx, |counter, cx| {
cx.spawn(async move |this, cx| {
// Simulate async delay
cx.background_executor.timer(Duration::from_millis(100)).await;
this.update(&mut *cx, |counter, cx| {
counter.count += 1;
cx.notify();
})?;
Ok(())
}).detach();
});
cx.background_executor.run_until_parked();
assert_eq!(counter.read(cx).count, 1);
}
#[gpui::test]
async fn test_counter_notify(cx: &mut TestAppContext) {
let counter = cx.new(|_| Counter { count: 0 });
let notified = Arc::new(AtomicBool::new(false));
let notified_clone = notified.clone();
cx.observe(&counter, move |_counter, _cx| {
notified_clone.store(true, Ordering::SeqCst);
});
counter.update(cx, |counter, cx| {
counter.count = 5;
cx.notify();
});
assert!(notified.load(Ordering::SeqCst));
}
}
#[gpui::test] for GPUI testscx.background_executor.timer() for delaysrun_until_parked() to complete async work.update() and .read()Arc and Mutex for tracking callbackssmol::Timer in tests