Skip to main content

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}