reovim_kernel/testing.rs
1//! Test utilities for kernel-level testing.
2//!
3//! Provides shared helpers used by all modules and drivers that need
4//! a real in-memory `BufferManager` for testing. This eliminates the
5//! need to duplicate `TestBufferManager` and `create_test_context()`
6//! across 30+ test modules.
7//!
8//! # Usage
9//!
10//! ```ignore
11//! use reovim_kernel::testing::{create_test_context, setup_buffer};
12//!
13//! let ctx = create_test_context();
14//! let buffer_id = setup_buffer(&ctx, "hello world");
15//! ```
16//!
17//! # Architecture
18//!
19//! This module is unconditionally compiled (not `#[cfg(test)]`) so that
20//! downstream crates can use it in their test modules. This follows the
21//! same pattern as `reovim_driver_session::testing`.
22
23use std::{collections::HashMap, sync::Arc};
24
25use crate::api::v1::{
26 Buffer, BufferError, BufferId, BufferManager, EventBus, KernelContext, MarkBank, ModeId,
27 ModuleId, MotionEngine, OptionRegistry, RwLock, ServiceRegistry, TextObjectEngine,
28};
29
30/// In-memory buffer manager for testing.
31///
32/// Unlike `KernelContext::default()` which uses a `StubBufferManager`
33/// (returns `None` for all lookups), this implementation actually stores
34/// and retrieves buffers. Use this when tests need real buffer operations.
35pub struct TestBufferManager {
36 buffers: RwLock<HashMap<BufferId, Arc<RwLock<Buffer>>>>,
37}
38
39impl TestBufferManager {
40 /// Create a new empty test buffer manager.
41 #[must_use]
42 pub fn new() -> Self {
43 Self {
44 buffers: RwLock::new(HashMap::new()),
45 }
46 }
47}
48
49impl Default for TestBufferManager {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[cfg_attr(coverage_nightly, coverage(off))]
56impl BufferManager for TestBufferManager {
57 fn get(&self, id: BufferId) -> Option<Arc<RwLock<Buffer>>> {
58 self.buffers.read().get(&id).cloned()
59 }
60
61 fn create(&self) -> BufferId {
62 let id = BufferId::new();
63 let buffer = Arc::new(RwLock::new(Buffer::new()));
64 self.buffers.write().insert(id, buffer);
65 id
66 }
67
68 fn register(&self, buffer: Buffer) -> BufferId {
69 let id = BufferId::new();
70 let buffer = Arc::new(RwLock::new(buffer));
71 self.buffers.write().insert(id, buffer);
72 id
73 }
74
75 fn unregister(&self, id: BufferId) -> Result<Buffer, BufferError> {
76 self.buffers
77 .write()
78 .remove(&id)
79 .map_or(Err(BufferError::NotFound(id)), |arc_buffer| {
80 Arc::try_unwrap(arc_buffer)
81 .map_or_else(|arc| Ok(arc.read().clone()), |rwlock| Ok(rwlock.into_inner()))
82 })
83 }
84
85 fn list(&self) -> Vec<BufferId> {
86 self.buffers.read().keys().copied().collect()
87 }
88
89 fn count(&self) -> usize {
90 self.buffers.read().len()
91 }
92}
93
94/// Create a `KernelContext` with a real in-memory buffer manager.
95///
96/// This is the standard test context used across all modules. It provides:
97/// - Real `TestBufferManager` (stores and retrieves buffers)
98/// - Fresh `EventBus`
99/// - Default `MotionEngine`, `TextObjectEngine`
100/// - Empty `MarkBank`, `OptionRegistry`, `ServiceRegistry`
101///
102/// For tests that need services (undo, search, etc.), create a context
103/// with this function and then register services on `ctx.services`.
104#[must_use]
105pub fn create_test_context() -> KernelContext {
106 KernelContext::new(
107 Arc::new(EventBus::new()),
108 Arc::new(TestBufferManager::new()),
109 Arc::new(MotionEngine),
110 Arc::new(TextObjectEngine),
111 Arc::new(RwLock::new(MarkBank::new())),
112 Arc::new(OptionRegistry::new()),
113 Arc::new(ServiceRegistry::new()),
114 )
115}
116
117/// Standard test mode ID for unit tests.
118///
119/// Returns `ModeId::new(ModuleId::new("test"), "normal")`.
120/// Use this instead of defining `fn test_mode()` locally in test modules.
121#[must_use]
122pub const fn test_mode() -> ModeId {
123 ModeId::new(ModuleId::new("test"), "normal")
124}
125
126/// Create a buffer with content and register it in the context.
127///
128/// Convenience helper that combines `Buffer::from_string()` and
129/// `ctx.buffers.register()`.
130#[must_use]
131pub fn setup_buffer(ctx: &KernelContext, content: &str) -> BufferId {
132 let buffer = Buffer::from_string(content);
133 ctx.buffers.register(buffer)
134}