use {
crate::{
ClientId, Session, WindowLayout,
api::{CommandExecutor, CommandHandle, StateChanges},
extension::ExtensionMap,
runtime::SessionRuntime,
},
reovim_kernel::api::v1::{
Buffer, BufferId, CommandId, HistoryRing, Jumplist, KernelContext, MarkBank, ModeId,
ModeStack, ModuleId, Position, RegisterBank,
},
std::sync::Arc,
};
pub struct TestSessionRuntime {
session: Session,
pub mode_stack: ModeStack,
pub windows: WindowLayout,
pub extensions: ExtensionMap,
compositor: Option<Box<dyn reovim_driver_layout::RootCompositor>>,
tabs: crate::TabPageSet,
registers: RegisterBank,
clipboard_history: HistoryRing,
local_marks: MarkBank,
jumplist: Jumplist,
active_buffer: Option<BufferId>,
terminal_size: (u16, u16),
kernel: KernelContext,
executor: StubExecutor,
changes: StateChanges,
}
impl Default for TestSessionRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl TestSessionRuntime {
fn make_test_kernel() -> KernelContext {
reovim_kernel::testing::create_test_context()
}
#[must_use]
pub fn new() -> Self {
let home_mode = ModeId::new(ModuleId::new("test"), "normal");
Self {
session: Session::new(ClientId::new(1), home_mode.clone()), mode_stack: ModeStack::new(home_mode), windows: WindowLayout::empty(), extensions: ExtensionMap::new(), compositor: None, tabs: crate::TabPageSet::new(), registers: RegisterBank::new(), clipboard_history: HistoryRing::new(), local_marks: MarkBank::new(), jumplist: Jumplist::new(),
active_buffer: None, terminal_size: (80, 24), kernel: Self::make_test_kernel(),
executor: StubExecutor,
changes: StateChanges::new(),
}
}
#[must_use]
pub fn with_home_mode(mode: ModeId) -> Self {
Self {
session: Session::new(ClientId::new(1), mode.clone()), mode_stack: ModeStack::new(mode), windows: WindowLayout::empty(), extensions: ExtensionMap::new(), compositor: None, tabs: crate::TabPageSet::new(), registers: RegisterBank::new(), clipboard_history: HistoryRing::new(), local_marks: MarkBank::new(), jumplist: Jumplist::new(),
active_buffer: None, terminal_size: (80, 24), kernel: Self::make_test_kernel(),
executor: StubExecutor,
changes: StateChanges::new(),
}
}
#[must_use]
pub fn with_buffer(content: &str) -> Self {
let mut test = Self::new();
let buffer = Buffer::from_string(content);
let buffer_id = test.kernel.buffers.register(buffer);
let mut window = crate::Window::new();
window.buffer_id = Some(buffer_id);
test.windows.add(window);
test.active_buffer = Some(buffer_id);
test
}
#[must_use]
pub fn with_buffer_and_mode(content: &str, mode: ModeId) -> Self {
let mut test = Self::with_home_mode(mode);
let buffer = Buffer::from_string(content);
let buffer_id = test.kernel.buffers.register(buffer);
let mut window = crate::Window::new();
window.buffer_id = Some(buffer_id);
test.windows.add(window);
test.active_buffer = Some(buffer_id);
test
}
#[must_use]
pub fn with_window(window: crate::Window, mode: ModeId) -> Self {
let mut test = Self::with_home_mode(mode);
test.active_buffer = window.buffer_id;
test.windows.add(window);
test
}
pub fn with_runtime<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut SessionRuntime<'_>) -> R,
{
use crate::api::ChangeTracker;
let client = crate::ClientContext {
mode_stack: &mut self.mode_stack,
windows: &mut self.windows,
extensions: &mut self.extensions,
compositor: &mut self.compositor,
tabs: &mut self.tabs,
registers: &mut self.registers,
clipboard_history: &mut self.clipboard_history,
local_marks: &mut self.local_marks,
jumplist: &mut self.jumplist,
active_buffer: &mut self.active_buffer,
terminal_size: &mut self.terminal_size,
};
let mut runtime =
SessionRuntime::new(&mut self.session, client, &self.kernel, &self.executor);
let result = f(&mut runtime);
let changes = ChangeTracker::take_changes(&mut runtime);
self.changes.merge(changes);
result
}
pub fn runtime(&mut self) -> SessionRuntime<'_> {
let client = crate::ClientContext {
mode_stack: &mut self.mode_stack,
windows: &mut self.windows,
extensions: &mut self.extensions,
compositor: &mut self.compositor,
tabs: &mut self.tabs,
registers: &mut self.registers,
clipboard_history: &mut self.clipboard_history,
local_marks: &mut self.local_marks,
jumplist: &mut self.jumplist,
active_buffer: &mut self.active_buffer,
terminal_size: &mut self.terminal_size,
};
SessionRuntime::new(&mut self.session, client, &self.kernel, &self.executor)
}
pub fn take_changes(&mut self) -> StateChanges {
std::mem::take(&mut self.changes)
}
#[must_use]
pub const fn changes(&self) -> &StateChanges {
&self.changes
}
pub fn assert_mode(&self, expected: &ModeId) {
let current = self.mode_stack.current(); assert_eq!(current, expected, "Expected mode {expected:?}, got {current:?}");
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn assert_mode_name(&self, expected_name: &str) {
let current = self.mode_stack.current(); assert_eq!(
current.name(),
expected_name,
"Expected mode name '{}', got '{}'",
expected_name,
current.name()
);
}
pub fn assert_mode_depth(&self, expected: usize) {
let depth = self.mode_stack.depth(); assert_eq!(depth, expected, "Expected mode depth {expected}, got {depth}");
}
pub fn assert_cursor(&self, line: usize, column: usize) {
let window = self
.windows .active()
.expect("No active window for cursor assertion");
assert_eq!(
(window.cursor.line, window.cursor.column),
(line, column),
"Expected cursor at ({}, {}), got ({}, {})",
line,
column,
window.cursor.line,
window.cursor.column
);
}
pub fn assert_buffer_content(&self, expected: &str) {
let buffer_id = self
.active_buffer
.expect("No active buffer for content assertion");
let buffer = self
.kernel
.buffers
.get(buffer_id)
.expect("Buffer not found");
let content = buffer.read().content();
assert_eq!(
content, expected,
"Buffer content mismatch.\nExpected:\n{expected}\nGot:\n{content}"
);
}
pub fn assert_line_count(&self, expected: usize) {
let buffer_id = self
.active_buffer
.expect("No active buffer for line count assertion");
let buffer = self
.kernel
.buffers
.get(buffer_id)
.expect("Buffer not found");
let count = buffer.read().line_count();
assert_eq!(count, expected, "Expected {expected} lines, got {count}");
}
pub fn assert_window_count(&self, expected: usize) {
let count = self.windows.len(); assert_eq!(count, expected, "Expected {expected} windows, got {count}");
}
#[must_use]
pub fn current_mode(&self) -> &ModeId {
self.mode_stack.current() }
#[must_use]
pub const fn active_buffer(&self) -> Option<BufferId> {
self.active_buffer
}
#[must_use]
pub fn cursor_position(&self) -> Option<Position> {
self.windows .active()
.map(|w| Position::new(w.cursor.line, w.cursor.column))
}
#[must_use]
pub fn buffer_content(&self) -> Option<String> {
self.active_buffer
.and_then(|id| self.kernel.buffers.get(id).map(|b| b.read().content()))
}
#[must_use]
pub const fn kernel(&self) -> &KernelContext {
&self.kernel
}
#[must_use]
pub const fn session(&self) -> &Session {
&self.session
}
pub fn set_compositor(&mut self, compositor: Box<dyn reovim_driver_layout::RootCompositor>) {
self.compositor = Some(compositor);
}
}
pub struct StubExecutor;
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandExecutor for StubExecutor {
fn get_handle(&self, _id: &CommandId) -> Option<Arc<dyn CommandHandle>> {
None
}
}
#[cfg(test)]
#[path = "testing_tests.rs"]
mod tests;