use {super::*, crate::session::SessionId};
#[test]
fn test_current_timestamp_ms() {
let ts = current_timestamp_ms();
assert!(ts > 1_700_000_000_000);
}
#[test]
fn test_build_notifications_empty_changes() {
let changes = StateChanges::new();
let session = Session::new(SessionId::new("test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert!(notifications.is_empty());
}
#[test]
fn test_build_notifications_mode_changed() {
let mut changes = StateChanges::new();
changes.record_mode_change();
let session = Session::new(SessionId::new("test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "mode_changed");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_mode_notification_fallback_uses_shared_state() {
let session = Session::new(SessionId::new("fallback-test"));
let notification = build_mode_notification(&session, 12345, 999);
assert_eq!(notification.event_type, "mode_changed");
if let Some(notification::Payload::ModeChanged(payload)) = notification.payload {
assert_eq!(payload.name, "normal");
assert_eq!(payload.client_id, 999);
} else {
panic!("Expected ModeChangedPayload");
}
}
#[test]
fn test_cursor_notification_returns_none_for_unknown_client() {
let session = Session::new(SessionId::new("cursor-test"));
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(1);
let notification = build_cursor_notification(&session, buffer_id, 12345, 999);
assert!(notification.is_none());
}
#[test]
fn test_selection_notification_returns_none_for_unknown_client() {
let session = Session::new(SessionId::new("selection-test"));
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(1);
let notification = build_selection_notification(&session, buffer_id, 12345, 999);
assert!(notification.is_none());
}
#[test]
fn test_viewport_notification_returns_none_for_unknown_client() {
let session = Session::new(SessionId::new("viewport-test"));
let window_id = reovim_kernel::api::v1::WindowId::from_raw(1);
let notification = build_viewport_notification(&session, window_id, 12345, 999);
assert!(notification.is_none());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_buffer_modified_notification() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(42);
let notification = build_buffer_modified_notification(buffer_id, 99999);
assert_eq!(notification.event_type, "buffer_modified");
assert_eq!(notification.timestamp_ms, 99999);
if let Some(notification::Payload::BufferModified(payload)) = notification.payload {
assert_eq!(payload.buffer_id, 42);
assert!(payload.change.is_none());
} else {
panic!("Expected BufferModifiedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_buffer_list_notification_added() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(7);
let notification = build_buffer_list_notification("added", buffer_id, 55555);
assert_eq!(notification.event_type, "buffer_list_changed");
assert_eq!(notification.timestamp_ms, 55555);
if let Some(notification::Payload::BufferListChanged(payload)) = notification.payload {
assert_eq!(payload.action, "added");
assert_eq!(payload.buffer_id, 7);
} else {
panic!("Expected BufferListChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_buffer_list_notification_removed() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(3);
let notification = build_buffer_list_notification("removed", buffer_id, 11111);
if let Some(notification::Payload::BufferListChanged(payload)) = notification.payload {
assert_eq!(payload.action, "removed");
assert_eq!(payload.buffer_id, 3);
} else {
panic!("Expected BufferListChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_option_notification_bool() {
let opt_change = reovim_driver_session::api::OptionChange {
name: "number".to_string(),
value: reovim_kernel::api::v1::OptionValue::Bool(true),
window_id: None,
};
let notification = build_option_notification(&opt_change, 77777);
assert_eq!(notification.event_type, "option_changed");
assert_eq!(notification.timestamp_ms, 77777);
if let Some(notification::Payload::OptionChanged(payload)) = notification.payload {
assert_eq!(payload.name, "number");
assert_eq!(
payload.value,
Some(reovim_protocol::v2::option_changed_payload::Value::BoolValue(true))
);
} else {
panic!("Expected OptionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_option_notification_integer() {
let opt_change = reovim_driver_session::api::OptionChange {
name: "tabstop".to_string(),
value: reovim_kernel::api::v1::OptionValue::Integer(4),
window_id: None,
};
let notification = build_option_notification(&opt_change, 88888);
if let Some(notification::Payload::OptionChanged(payload)) = notification.payload {
assert_eq!(payload.name, "tabstop");
assert_eq!(
payload.value,
Some(reovim_protocol::v2::option_changed_payload::Value::IntValue(4))
);
} else {
panic!("Expected OptionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_option_notification_string() {
let opt_change = reovim_driver_session::api::OptionChange {
name: "theme".to_string(),
value: reovim_kernel::api::v1::OptionValue::String("monokai".to_string()),
window_id: None,
};
let notification = build_option_notification(&opt_change, 66666);
if let Some(notification::Payload::OptionChanged(payload)) = notification.payload {
assert_eq!(payload.name, "theme");
assert_eq!(
payload.value,
Some(reovim_protocol::v2::option_changed_payload::Value::StringValue(
"monokai".to_string()
))
);
} else {
panic!("Expected OptionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_option_notification_choice() {
let opt_change = reovim_driver_session::api::OptionChange {
name: "virtualedit".to_string(),
value: reovim_kernel::api::v1::OptionValue::Choice {
value: "block".to_string(),
choices: vec!["block".to_string(), "all".to_string()],
},
window_id: None,
};
let notification = build_option_notification(&opt_change, 44444);
if let Some(notification::Payload::OptionChanged(payload)) = notification.payload {
assert_eq!(payload.name, "virtualedit");
assert_eq!(
payload.value,
Some(reovim_protocol::v2::option_changed_payload::Value::StringValue(
"block".to_string()
))
);
} else {
panic!("Expected OptionChangedPayload");
}
}
#[test]
fn test_build_notifications_buffer_modified() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(5);
let mut changes = StateChanges::new();
changes.record_buffer_modified(buffer_id);
let session = Session::new(SessionId::new("buf-mod-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "buffer_modified");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_notifications_buffer_created() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(10);
let mut changes = StateChanges::new();
changes.buffers_created.push(buffer_id);
let session = Session::new(SessionId::new("buf-create-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "buffer_list_changed");
if let Some(notification::Payload::BufferListChanged(payload)) = ¬ifications[0].payload {
assert_eq!(payload.action, "added");
assert_eq!(payload.buffer_id, 10);
} else {
panic!("Expected BufferListChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_notifications_buffer_deleted() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(20);
let mut changes = StateChanges::new();
changes.buffers_deleted.push(buffer_id);
let session = Session::new(SessionId::new("buf-delete-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "buffer_list_changed");
if let Some(notification::Payload::BufferListChanged(payload)) = ¬ifications[0].payload {
assert_eq!(payload.action, "removed");
assert_eq!(payload.buffer_id, 20);
} else {
panic!("Expected BufferListChangedPayload");
}
}
#[test]
fn test_build_notifications_option_changed() {
let mut changes = StateChanges::new();
changes
.options_changed
.push(reovim_driver_session::api::OptionChange {
name: "number".to_string(),
value: reovim_kernel::api::v1::OptionValue::Bool(true),
window_id: None,
});
let session = Session::new(SessionId::new("opt-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "option_changed");
}
#[test]
fn test_build_notifications_window_changed() {
let mut changes = StateChanges::new();
changes.window_changed = true;
let session = Session::new(SessionId::new("win-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "layout_changed");
}
#[test]
fn test_build_notifications_focus_changed() {
let mut changes = StateChanges::new();
changes.focus_changed = true;
let session = Session::new(SessionId::new("focus-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "layout_changed");
}
#[test]
fn test_build_notifications_multiple_changes() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(1);
let mut changes = StateChanges::new();
changes.record_mode_change();
changes.record_buffer_modified(buffer_id);
changes.buffers_created.push(buffer_id);
changes
.options_changed
.push(reovim_driver_session::api::OptionChange {
name: "wrap".to_string(),
value: reovim_kernel::api::v1::OptionValue::Bool(false),
window_id: None,
});
let session = Session::new(SessionId::new("multi-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 4);
let event_types: Vec<&str> = notifications
.iter()
.map(|n| n.event_type.as_str())
.collect();
assert!(event_types.contains(&"mode_changed"));
assert!(event_types.contains(&"buffer_modified"));
assert!(event_types.contains(&"buffer_list_changed"));
assert!(event_types.contains(&"option_changed"));
}
#[test]
fn test_build_notifications_cursor_moved_no_client() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(1);
let mut changes = StateChanges::new();
changes.record_cursor_move(buffer_id);
let session = Session::new(SessionId::new("cursor-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert!(notifications.is_empty());
}
#[test]
fn test_build_notifications_selection_changed_no_client() {
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(1);
let mut changes = StateChanges::new();
changes.selection_changed = true;
changes.affected_buffers.push(buffer_id);
let session = Session::new(SessionId::new("sel-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert!(notifications.is_empty());
}
#[test]
fn test_build_notifications_scroll_changed_no_client() {
let window_id = reovim_kernel::api::v1::WindowId::from_raw(1);
let mut changes = StateChanges::new();
changes.scroll_changed = true;
changes.scrolled_windows.push(window_id);
let session = Session::new(SessionId::new("scroll-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert!(notifications.is_empty());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_mode_notification_insert_mode_detection() {
let session = Session::new(SessionId::new("insert-test"));
let notification = build_mode_notification(&session, 12345, 999);
if let Some(notification::Payload::ModeChanged(payload)) = notification.payload {
assert!(!payload.is_insert);
assert_eq!(payload.display, "NORMAL");
} else {
panic!("Expected ModeChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_layout_notification_no_compositor_no_client() {
let session = Session::new(SessionId::new("layout-test"));
let notification = build_layout_notification(&session, 12345, 999);
assert_eq!(notification.event_type, "layout_changed");
if let Some(notification::Payload::LayoutChanged(payload)) = notification.payload {
assert!(payload.windows.is_empty());
assert!(payload.focused_window_id.is_none());
} else {
panic!("Expected LayoutChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_notifications_multiple_buffers_created_and_deleted() {
let buf1 = reovim_kernel::api::v1::BufferId::from_raw(1);
let buf2 = reovim_kernel::api::v1::BufferId::from_raw(2);
let buf3 = reovim_kernel::api::v1::BufferId::from_raw(3);
let mut changes = StateChanges::new();
changes.buffers_created.push(buf1);
changes.buffers_created.push(buf2);
changes.buffers_deleted.push(buf3);
let session = Session::new(SessionId::new("multi-buf-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 3);
let mut added_count = 0;
let mut removed_count = 0;
for n in ¬ifications {
if let Some(notification::Payload::BufferListChanged(payload)) = &n.payload {
match payload.action.as_str() {
"added" => added_count += 1,
"removed" => removed_count += 1,
_ => panic!("Unexpected action"),
}
}
}
assert_eq!(added_count, 2);
assert_eq!(removed_count, 1);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_cursor_notification_with_client_and_window() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("cursor-client-test"));
let client_id = crate::session::ClientId::new(1);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(42);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_cursor_notification(&session, buffer_id, 99999, 1);
assert!(notification.is_some());
let n = notification.unwrap();
assert_eq!(n.event_type, "cursor_moved");
assert_eq!(n.timestamp_ms, 99999);
if let Some(notification::Payload::CursorMoved(payload)) = n.payload {
assert_eq!(payload.client_id, 1);
assert!(payload.position.is_some());
let pos = payload.position.unwrap();
assert_eq!(pos.line, 0);
assert_eq!(pos.column, 0);
} else {
panic!("Expected CursorMovedPayload");
}
}
#[test]
fn test_build_cursor_notification_wrong_buffer_returns_none() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("cursor-wrong-buf"));
let client_id = crate::session::ClientId::new(1);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(42);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let other_buffer = reovim_kernel::api::v1::BufferId::from_raw(99);
let notification = build_cursor_notification(&session, other_buffer, 99999, 1);
assert!(notification.is_none());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_selection_notification_with_character_selection() {
use {
reovim_driver_session::{CursorPosition, Window, api::Selection},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId, Position as KernelPosition},
};
let session = Session::new(SessionId::new("sel-char-test"));
let client_id = crate::session::ClientId::new(5);
let mode = ModeId::new(ModuleId::new("test"), "visual");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(10);
let mut window = Window::with_buffer(buffer_id);
window.cursor = CursorPosition { line: 0, column: 3 };
window.selection =
Some(Selection::character(KernelPosition::new(0, 0), KernelPosition::new(0, 5)));
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_selection_notification(&session, buffer_id, 11111, 5);
assert!(notification.is_some());
let n = notification.unwrap();
assert_eq!(n.event_type, "selection_changed");
if let Some(notification::Payload::SelectionChanged(payload)) = n.payload {
assert!(payload.has_selection);
assert_eq!(payload.visual_mode, Some("char".to_string()));
assert_eq!(payload.client_id, 5);
let sel = payload.selection.unwrap();
assert_eq!(sel.start.as_ref().unwrap().line, 0);
assert_eq!(sel.start.as_ref().unwrap().column, 0);
assert_eq!(sel.end.as_ref().unwrap().line, 0);
assert_eq!(sel.end.as_ref().unwrap().column, 5);
} else {
panic!("Expected SelectionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_selection_notification_with_line_selection() {
use {
reovim_driver_session::{Window, api::Selection},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId, Position as KernelPosition},
};
let session = Session::new(SessionId::new("sel-line-test"));
let client_id = crate::session::ClientId::new(3);
let mode = ModeId::new(ModuleId::new("test"), "visual-line");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(20);
let mut window = Window::with_buffer(buffer_id);
window.selection = Some(Selection::line(KernelPosition::new(1, 0), KernelPosition::new(3, 0)));
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_selection_notification(&session, buffer_id, 22222, 3);
assert!(notification.is_some());
let n = notification.unwrap();
if let Some(notification::Payload::SelectionChanged(payload)) = n.payload {
assert!(payload.has_selection);
assert_eq!(payload.visual_mode, Some("line".to_string()));
} else {
panic!("Expected SelectionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_selection_notification_with_block_selection() {
use {
reovim_driver_session::{Window, api::Selection},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId, Position as KernelPosition},
};
let session = Session::new(SessionId::new("sel-block-test"));
let client_id = crate::session::ClientId::new(7);
let mode = ModeId::new(ModuleId::new("test"), "visual-block");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(30);
let mut window = Window::with_buffer(buffer_id);
window.selection = Some(Selection::block(KernelPosition::new(0, 1), KernelPosition::new(2, 4)));
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_selection_notification(&session, buffer_id, 33333, 7);
assert!(notification.is_some());
let n = notification.unwrap();
if let Some(notification::Payload::SelectionChanged(payload)) = n.payload {
assert!(payload.has_selection);
assert_eq!(payload.visual_mode, Some("block".to_string()));
} else {
panic!("Expected SelectionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_selection_notification_no_selection() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("sel-none-test"));
let client_id = crate::session::ClientId::new(2);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(15);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_selection_notification(&session, buffer_id, 44444, 2);
assert!(notification.is_some());
let n = notification.unwrap();
if let Some(notification::Payload::SelectionChanged(payload)) = n.payload {
assert!(!payload.has_selection);
assert!(payload.selection.is_none());
assert!(payload.visual_mode.is_none());
} else {
panic!("Expected SelectionChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_viewport_notification_with_client_window() {
use {
reovim_driver_session::{Viewport, Window},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("viewport-test"));
let client_id = crate::session::ClientId::new(4);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(50);
let mut window = Window::with_buffer(buffer_id);
let window_id = window.id;
let mut viewport = Viewport::new(80, 24);
viewport.scroll_top = 10;
viewport.scroll_left = 5;
window.viewport = viewport;
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_viewport_notification(&session, window_id, 55555, 4);
assert!(notification.is_some());
let n = notification.unwrap();
assert_eq!(n.event_type, "viewport_updated");
assert_eq!(n.timestamp_ms, 55555);
if let Some(notification::Payload::ViewportUpdated(payload)) = n.payload {
assert_eq!(payload.viewport_id, window_id.as_usize() as u64);
assert_eq!(payload.top_line, Some(10));
assert_eq!(payload.left_col, Some(5));
assert_eq!(payload.cursor_line, Some(0));
assert_eq!(payload.cursor_col, Some(0));
} else {
panic!("Expected ViewportUpdatedPayload");
}
}
#[test]
fn test_build_viewport_notification_wrong_window_returns_none() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("viewport-wrong-win"));
let client_id = crate::session::ClientId::new(6);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(60);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let wrong_window_id = reovim_kernel::api::v1::WindowId::from_raw(99999);
let notification = build_viewport_notification(&session, wrong_window_id, 66666, 6);
assert!(notification.is_none());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_mode_notification_with_registered_client() {
use reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId};
let session = Session::new(SessionId::new("mode-client-test"));
let client_id = crate::session::ClientId::new(8);
let mode = ModeId::new(ModuleId::new("test"), "insert");
let mode_stack = ModeStack::new(mode);
let metadata = crate::session::ClientMetadata::default();
let client = crate::session::Client::with_mode_stack(client_id, metadata, mode_stack);
session.add_client_with_state(client);
let notification = build_mode_notification(&session, 77777, 8);
assert_eq!(notification.event_type, "mode_changed");
if let Some(notification::Payload::ModeChanged(payload)) = notification.payload {
assert_eq!(payload.name, "insert");
assert_eq!(payload.display, "INSERT");
assert!(payload.is_insert);
assert_eq!(payload.client_id, 8);
} else {
panic!("Expected ModeChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_layout_notification_with_client_windows() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("layout-client-test"));
let client_id = crate::session::ClientId::new(9);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(70);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let notification = build_layout_notification(&session, 88888, 9);
assert_eq!(notification.event_type, "layout_changed");
if let Some(notification::Payload::LayoutChanged(payload)) = notification.payload {
assert_eq!(payload.windows.len(), 1);
let win = &payload.windows[0];
assert_eq!(win.buffer_id, Some(70));
assert_eq!(win.rect.as_ref().unwrap().width, 80);
assert_eq!(win.rect.as_ref().unwrap().height, 24);
} else {
panic!("Expected LayoutChangedPayload");
}
}
#[test]
fn test_build_notifications_cursor_moved_with_client() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("cursor-notify-test"));
let client_id = crate::session::ClientId::new(10);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(80);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let window = Window::with_buffer(buffer_id);
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let mut changes = StateChanges::new();
changes.record_cursor_move(buffer_id);
let notifications = build_notifications(&changes, &session, 10, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "cursor_moved");
}
#[test]
fn test_build_notifications_selection_changed_with_client() {
use {
reovim_driver_session::{Window, api::Selection},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId, Position as KernelPosition},
};
let session = Session::new(SessionId::new("sel-notify-test"));
let client_id = crate::session::ClientId::new(11);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(90);
let mode = ModeId::new(ModuleId::new("test"), "visual");
let mode_stack = ModeStack::new(mode);
let mut window = Window::with_buffer(buffer_id);
window.selection =
Some(Selection::character(KernelPosition::new(0, 0), KernelPosition::new(0, 3)));
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let mut changes = StateChanges::new();
changes.selection_changed = true;
changes.affected_buffers.push(buffer_id);
let notifications = build_notifications(&changes, &session, 11, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "selection_changed");
}
#[test]
fn test_build_notifications_scroll_changed_with_client() {
use {
reovim_driver_session::Window,
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let session = Session::new(SessionId::new("scroll-notify-test"));
let client_id = crate::session::ClientId::new(12);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(100);
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let window = Window::with_buffer(buffer_id);
let window_id = window.id;
let metadata = crate::session::ClientMetadata::default();
let client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
session.add_client_with_state(client);
let mut changes = StateChanges::new();
changes.scroll_changed = true;
changes.scrolled_windows.push(window_id);
let notifications = build_notifications(&changes, &session, 12, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "viewport_updated");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
#[allow(clippy::too_many_lines)]
fn test_build_layout_notification_with_compositor() {
use reovim_driver_layout::{
CompositeResult, Layer, LayerConfig, LayerId, Rect, RootCompositor, WindowId,
WindowLayerCompositor, WindowPlacement, ZOrder, Zone,
};
struct TestCompositor {
focused: Option<WindowId>,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl RootCompositor for TestCompositor {
fn composite(&self, screen: Rect) -> CompositeResult {
let placement = WindowPlacement::new(
WindowId::from_raw(1),
LayerId::new(0),
Zone::Tiled,
Rect::new(0, 0, screen.width, screen.height),
ZOrder::new(100),
);
CompositeResult {
placements: vec![placement],
focused: self.focused,
active_layer: Some(LayerId::new(0)),
screen,
}
}
fn create_layer(&mut self, _config: LayerConfig) -> LayerId {
LayerId::new(0)
}
fn remove_layer(&mut self, _layer: LayerId) {}
fn layer_by_label(&self, _label: &str) -> Option<LayerId> {
None
}
fn layers(&self) -> Vec<&Layer> {
Vec::new()
}
fn set_layer_visible(&mut self, _layer: LayerId, _visible: bool) {}
fn set_layer_opacity(&mut self, _layer: LayerId, _opacity: f32) {}
fn reorder_layer(&mut self, _layer: LayerId, _new_z: u16) {}
fn set_active_layer(&mut self, _layer: LayerId) {}
fn active_layer(&self) -> Option<LayerId> {
Some(LayerId::new(0))
}
fn set_focus(&mut self, window: WindowId) {
self.focused = Some(window);
}
fn focused(&self) -> Option<WindowId> {
self.focused
}
fn focus_at(&mut self, _x: u16, _y: u16) -> Option<WindowId> {
self.focused
}
fn layer_compositor(&self, _layer: LayerId) -> Option<&dyn WindowLayerCompositor> {
None
}
fn layer_compositor_mut(
&mut self,
_layer: LayerId,
) -> Option<&mut dyn WindowLayerCompositor> {
None
}
fn window_count(&self) -> usize {
1
}
fn set_screen(&mut self, _screen: Rect) {}
fn layer_of(&self, _window: WindowId) -> Option<LayerId> {
Some(LayerId::new(0))
}
fn boxed_clone(&self) -> Box<dyn RootCompositor> {
Box::new(Self {
focused: self.focused,
})
}
}
let state = crate::session::SessionState::default();
let session = Session::from_state(SessionId::new("compositor-test"), state);
let client_id = crate::session::ClientId::new(1);
let mode = reovim_kernel::api::v1::ModeId::new(
reovim_kernel::api::v1::ModuleId::new("test"),
"normal",
);
let mode_stack = reovim_kernel::api::v1::ModeStack::new(mode);
let buffer_id = reovim_kernel::api::v1::BufferId::from_raw(42);
let mut window = reovim_driver_session::Window::with_buffer(buffer_id);
window.id = reovim_kernel::api::v1::WindowId::from_raw(1);
let metadata = crate::session::ClientMetadata::default();
let mut client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
client.state.compositor = Some(Box::new(TestCompositor {
focused: Some(WindowId::from_raw(1)),
}));
session.add_client_with_state(client);
let notification = build_layout_notification(&session, 12345, 1);
assert_eq!(notification.event_type, "layout_changed");
assert_eq!(notification.timestamp_ms, 12345);
if let Some(notification::Payload::LayoutChanged(payload)) = notification.payload {
assert_eq!(payload.windows.len(), 1);
let win = &payload.windows[0];
assert_eq!(win.window_id, 1);
assert_eq!(win.buffer_id, Some(42));
assert!(win.focused);
let rect = win.rect.as_ref().expect("rect should be present");
assert_eq!(rect.x, 0);
assert_eq!(rect.y, 0);
assert_eq!(rect.width, 80);
assert_eq!(rect.height, 24);
assert_eq!(payload.focused_window_id, Some(1));
} else {
panic!("Expected LayoutChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_layout_notification_compositor_no_client() {
let state = crate::session::SessionState::default();
let session = Session::from_state(SessionId::new("compositor-no-client"), state);
let notification = build_layout_notification(&session, 54321, 999);
assert_eq!(notification.event_type, "layout_changed");
if let Some(notification::Payload::LayoutChanged(payload)) = notification.payload {
assert_eq!(payload.windows.len(), 0);
assert!(payload.focused_window_id.is_none());
} else {
panic!("Expected LayoutChangedPayload");
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
#[allow(clippy::too_many_lines)]
fn test_build_layout_notification_compositor_with_active_buffer_fallback() {
use {
parking_lot::RwLock as ParkingLotRwLock,
reovim_driver_buffer::TestBufferManager,
reovim_driver_layout::{
CompositeResult, Layer, LayerConfig, LayerId, Rect, RootCompositor, WindowId,
WindowLayerCompositor, WindowPlacement, ZOrder, Zone,
},
reovim_kernel::api::v1::{
EventBus, KernelContext, MarkBank, MotionEngine, OptionRegistry, ServiceRegistry,
TextObjectEngine,
},
std::sync::Arc,
};
struct TestCompositorTwoWindows;
#[cfg_attr(coverage_nightly, coverage(off))]
impl RootCompositor for TestCompositorTwoWindows {
fn composite(&self, screen: Rect) -> CompositeResult {
let p1 = WindowPlacement::new(
WindowId::from_raw(10),
LayerId::new(0),
Zone::Tiled,
Rect::new(0, 0, screen.width / 2, screen.height),
ZOrder::new(100),
);
let p2 = WindowPlacement::new(
WindowId::from_raw(20),
LayerId::new(0),
Zone::Tiled,
Rect::new(screen.width / 2, 0, screen.width / 2, screen.height),
ZOrder::new(101),
);
CompositeResult {
placements: vec![p1, p2],
focused: Some(WindowId::from_raw(10)),
active_layer: Some(LayerId::new(0)),
screen,
}
}
fn create_layer(&mut self, _config: LayerConfig) -> LayerId {
LayerId::new(0)
}
fn remove_layer(&mut self, _layer: LayerId) {}
fn layer_by_label(&self, _label: &str) -> Option<LayerId> {
None
}
fn layers(&self) -> Vec<&Layer> {
Vec::new()
}
fn set_layer_visible(&mut self, _layer: LayerId, _visible: bool) {}
fn set_layer_opacity(&mut self, _layer: LayerId, _opacity: f32) {}
fn reorder_layer(&mut self, _layer: LayerId, _new_z: u16) {}
fn set_active_layer(&mut self, _layer: LayerId) {}
fn active_layer(&self) -> Option<LayerId> {
Some(LayerId::new(0))
}
fn set_focus(&mut self, _window: WindowId) {}
fn focused(&self) -> Option<WindowId> {
Some(WindowId::from_raw(10))
}
fn focus_at(&mut self, _x: u16, _y: u16) -> Option<WindowId> {
None
}
fn layer_compositor(&self, _layer: LayerId) -> Option<&dyn WindowLayerCompositor> {
None
}
fn layer_compositor_mut(
&mut self,
_layer: LayerId,
) -> Option<&mut dyn WindowLayerCompositor> {
None
}
fn window_count(&self) -> usize {
2
}
fn set_screen(&mut self, _screen: Rect) {}
fn layer_of(&self, _window: WindowId) -> Option<LayerId> {
Some(LayerId::new(0))
}
fn boxed_clone(&self) -> Box<dyn RootCompositor> {
Box::new(Self)
}
}
let kernel = KernelContext::new(
Arc::new(EventBus::new()),
Arc::new(TestBufferManager::new()),
Arc::new(MotionEngine),
Arc::new(TextObjectEngine),
Arc::new(ParkingLotRwLock::new(MarkBank::new())),
Arc::new(OptionRegistry::new()),
Arc::new(ServiceRegistry::new()),
);
let mut state = crate::session::SessionState::with_kernel(kernel);
let buffer_id = state.create_buffer("test content");
let session = Session::from_state(SessionId::new("compositor-fallback"), state);
let client_id = crate::session::ClientId::new(1);
let mode = reovim_kernel::api::v1::ModeId::new(
reovim_kernel::api::v1::ModuleId::new("test"),
"normal",
);
let mode_stack = reovim_kernel::api::v1::ModeStack::new(mode);
let mut window = reovim_driver_session::Window::with_buffer(buffer_id);
window.id = reovim_kernel::api::v1::WindowId::from_raw(10);
let metadata = crate::session::ClientMetadata::default();
let mut client =
crate::session::Client::with_mode_stack_and_window(client_id, metadata, mode_stack, window);
client.state.compositor = Some(Box::new(TestCompositorTwoWindows));
client.state.active_buffer = Some(buffer_id);
session.add_client_with_state(client);
let notification = build_layout_notification(&session, 99999, 1);
if let Some(notification::Payload::LayoutChanged(payload)) = notification.payload {
assert_eq!(payload.windows.len(), 2);
let win10 = payload.windows.iter().find(|w| w.window_id == 10).unwrap();
assert_eq!(win10.buffer_id, Some(buffer_id.as_usize() as u64));
assert!(win10.focused);
let win20 = payload.windows.iter().find(|w| w.window_id == 20).unwrap();
assert_eq!(win20.buffer_id, Some(buffer_id.as_usize() as u64));
assert!(!win20.focused);
} else {
panic!("Expected LayoutChangedPayload");
}
}
#[test]
fn test_build_notifications_multiple_option_changes() {
let mut changes = StateChanges::new();
changes
.options_changed
.push(reovim_driver_session::api::OptionChange {
name: "number".to_string(),
value: reovim_kernel::api::v1::OptionValue::Bool(true),
window_id: None,
});
changes
.options_changed
.push(reovim_driver_session::api::OptionChange {
name: "tabstop".to_string(),
value: reovim_kernel::api::v1::OptionValue::Integer(8),
window_id: None,
});
let session = Session::new(SessionId::new("multi-opt-test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert_eq!(notifications.len(), 2);
assert!(
notifications
.iter()
.all(|n| n.event_type == "option_changed")
);
}
#[test]
fn test_extension_changed_without_bridges_no_notification() {
let mut changes = StateChanges::new();
changes.record_extension_change("cmdline".into());
let session = Session::new(SessionId::new("test"));
let notifications = build_notifications(&changes, &session, 0, None);
assert!(
notifications.is_empty(),
"Should not emit extension notifications without bridges"
);
}
#[test]
fn test_extension_changed_with_bridges_unknown_kind() {
use reovim_driver_session::bridges::BridgeRegistry;
let mut changes = StateChanges::new();
changes.record_extension_change("unknown".into());
let session = Session::new(SessionId::new("test"));
let registry = BridgeRegistry::new(); let notifications = build_notifications(&changes, &session, 0, Some(®istry));
assert!(notifications.is_empty(), "Should not emit notification for unknown bridge kind");
}
struct TestBridgeExtension {
active: bool,
}
impl reovim_driver_session::SessionExtension for TestBridgeExtension {
fn create() -> Self {
Self { active: false }
}
}
struct TestBridge;
impl reovim_driver_session::bridges::ExtensionStateBridge for TestBridge {
fn kind(&self) -> &'static str {
"test-ext"
}
fn scope(&self) -> reovim_driver_session::bridges::ExtensionScope {
reovim_driver_session::bridges::ExtensionScope::Client
}
fn snapshot(
&self,
extensions: &reovim_driver_session::ExtensionMap,
) -> Option<serde_json::Value> {
let ext = extensions.get::<TestBridgeExtension>()?;
Some(serde_json::json!({
"active": ext.active,
}))
}
fn is_active(&self, extensions: &reovim_driver_session::ExtensionMap) -> bool {
extensions
.get::<TestBridgeExtension>()
.is_some_and(|e| e.active)
}
fn on_mode_changed(
&self,
_from: &str,
_to: &str,
_extensions: &mut reovim_driver_session::ExtensionMap,
) {
}
}
#[test]
fn test_extension_changed_with_bridge() {
use reovim_driver_session::bridges::BridgeRegistry;
let mut changes = StateChanges::new();
changes.record_extension_change("test-ext".into());
let session = Session::new(SessionId::new("test"));
let client_id = ClientId::new(42);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
let ext = state.extensions.get_or_insert::<TestBridgeExtension>();
ext.active = true;
});
let mut registry = BridgeRegistry::new();
registry.register(TestBridge);
let notifications = build_notifications(&changes, &session, 42, Some(®istry));
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "extension_updated");
if let Some(notification::Payload::ExtensionUpdated(payload)) = ¬ifications[0].payload {
assert_eq!(payload.kind, "test-ext");
assert_eq!(payload.client_id, 42);
let data: serde_json::Value = serde_json::from_str(&payload.data).expect("valid JSON");
assert_eq!(data["active"], true);
} else {
panic!("Expected ExtensionUpdated payload");
}
}
#[test]
fn test_extension_not_changed_no_notification() {
use reovim_driver_session::bridges::BridgeRegistry;
let changes = StateChanges::new();
let session = Session::new(SessionId::new("test"));
let mut registry = BridgeRegistry::new();
registry.register(TestBridge);
let notifications = build_notifications(&changes, &session, 0, Some(®istry));
assert!(notifications.is_empty());
}
#[test]
fn test_build_extension_notification_shared_scope_returns_notification() {
use reovim_driver_session::{
ExtensionMap,
bridges::{BridgeRegistry, ExtensionScope, ExtensionStateBridge},
};
struct SharedBridge;
#[cfg_attr(coverage_nightly, coverage(off))]
impl ExtensionStateBridge for SharedBridge {
fn kind(&self) -> &'static str {
"shared-test"
}
fn scope(&self) -> ExtensionScope {
ExtensionScope::Shared
}
fn snapshot(&self, _: &ExtensionMap) -> Option<serde_json::Value> {
Some(serde_json::json!({"shared": true}))
}
fn is_active(&self, _: &ExtensionMap) -> bool {
true
}
}
let mut bridges = BridgeRegistry::new();
bridges.register(SharedBridge);
let session = Session::new(SessionId::new("shared-test"));
let result = build_extension_notification("shared-test", &session, 12345, 1, &bridges);
assert!(result.is_some());
let notif = result.unwrap();
assert_eq!(notif.event_type, "extension_updated");
match ¬if.payload {
Some(notification::Payload::ExtensionUpdated(payload)) => {
assert_eq!(payload.kind, "shared-test");
assert!(payload.data.contains("shared"));
}
_ => panic!("expected ExtensionUpdated payload"),
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_extension_notification_shared_scope_empty_returns_none() {
use reovim_driver_session::{
ExtensionMap,
bridges::{BridgeRegistry, ExtensionScope, ExtensionStateBridge},
};
struct EmptySharedBridge;
#[cfg_attr(coverage_nightly, coverage(off))]
impl ExtensionStateBridge for EmptySharedBridge {
fn kind(&self) -> &'static str {
"empty-shared"
}
fn scope(&self) -> ExtensionScope {
ExtensionScope::Shared
}
fn snapshot(&self, _: &ExtensionMap) -> Option<serde_json::Value> {
None
}
fn is_active(&self, _: &ExtensionMap) -> bool {
false
}
}
let mut bridges = BridgeRegistry::new();
bridges.register(EmptySharedBridge);
let session = Session::new(SessionId::new("empty-shared"));
let result = build_extension_notification("empty-shared", &session, 12345, 1, &bridges);
assert!(result.is_none());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_notifications_presence_changed() {
use {
crate::session::{ClientId, ClientPresence, SyncMode},
std::time::SystemTime,
};
let session = Session::new(SessionId::new("presence-test"));
let client_id = ClientId::new(42);
let presence = ClientPresence {
client_id,
client_type: "tui".to_string(),
display_name: "test".to_string(),
buffer_id: Some(1),
cursor: (0, 0),
visible_lines: (0, 24),
mode: "NORMAL".to_string(),
sync_mode: SyncMode::Independent,
joined_at: SystemTime::now(),
};
session.presence().join(presence);
let mut changes = StateChanges::new();
changes.record_presence_change(client_id.as_usize());
let notifications = build_notifications(&changes, &session, 12345, None);
assert_eq!(notifications.len(), 1);
assert_eq!(notifications[0].event_type, "presence_updated");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_notifications_presence_changed_missing_client() {
let session = Session::new(SessionId::new("presence-test2"));
let mut changes = StateChanges::new();
changes.record_presence_change(999);
let notifications = build_notifications(&changes, &session, 12345, None);
assert!(notifications.is_empty());
}