use super::*;
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_trait_object_safety() {
fn _accepts_module_ref(_: &dyn Module) {}
fn _accepts_boxed_module(_: Box<dyn Module>) {}
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_deferred_probing() {
struct DeferredModule {
ready: bool,
}
impl Module for DeferredModule {
fn id(&self) -> ModuleId {
ModuleId::new("deferred")
}
fn name(&self) -> &'static str {
"Deferred Module"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
if self.ready {
ProbeResult::Success
} else {
ProbeResult::Defer("waiting for dependency".into())
}
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let ready_module = DeferredModule { ready: true };
assert_eq!(ready_module.id(), ModuleId::new("deferred"));
assert_eq!(ready_module.name(), "Deferred Module");
let not_ready = DeferredModule { ready: false };
assert_eq!(not_ready.id(), ModuleId::new("deferred"));
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_default_impls() {
struct MinimalModule;
impl Module for MinimalModule {
fn id(&self) -> ModuleId {
ModuleId::new("minimal")
}
fn name(&self) -> &'static str {
"Minimal"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut m = MinimalModule;
assert!(m.dependencies().is_empty());
assert!(m.optional_dependencies().is_empty());
assert!(m.extension_kinds().is_empty());
assert!(m.commands().is_empty());
assert!(m.keybindings().is_empty());
assert!(m.event_handlers().is_empty());
assert!(!m.supports_hot_reload());
assert!(m.save_state().is_none());
let result = m.restore_state(&[1, 2, 3]);
assert!(result.is_err());
assert!(m.on_unload().is_ok());
assert_eq!(m.api_version(), API_VERSION);
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_lifecycle_hooks_via_dyn() {
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
struct HookModule {
all_loaded: Arc<AtomicBool>,
buffer_focus: Arc<AtomicBool>,
on_unload: Arc<AtomicBool>,
}
impl Module for HookModule {
fn id(&self) -> ModuleId {
ModuleId::new("hook-test")
}
fn name(&self) -> &'static str {
"Hook Test"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn on_all_loaded(&mut self, _ctx: &ModuleContext) {
self.all_loaded.store(true, Ordering::SeqCst);
}
fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext) {
self.buffer_focus.store(true, Ordering::SeqCst);
}
fn on_unload(&mut self) -> Result<(), ModuleError> {
self.on_unload.store(true, Ordering::SeqCst);
Ok(())
}
}
let all_loaded = Arc::new(AtomicBool::new(false));
let buffer_focus = Arc::new(AtomicBool::new(false));
let unload = Arc::new(AtomicBool::new(false));
let mut module: Box<dyn Module> = Box::new(HookModule {
all_loaded: Arc::clone(&all_loaded),
buffer_focus: Arc::clone(&buffer_focus),
on_unload: Arc::clone(&unload),
});
let ctx = ModuleContext::default();
module.on_all_loaded(&ctx);
assert!(all_loaded.load(Ordering::SeqCst));
module.on_buffer_focus(BufferId::from_raw(1), &ctx);
assert!(buffer_focus.load(Ordering::SeqCst));
assert!(module.on_unload().is_ok());
assert!(unload.load(Ordering::SeqCst));
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_default_lifecycle_hooks_via_dyn() {
struct DefaultModule;
impl Module for DefaultModule {
fn id(&self) -> ModuleId {
ModuleId::new("default-hooks")
}
fn name(&self) -> &'static str {
"Default Hooks"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut module: Box<dyn Module> = Box::new(DefaultModule);
let ctx = ModuleContext::default();
module.on_all_loaded(&ctx);
module.on_buffer_focus(BufferId::from_raw(42), &ctx);
assert!(module.on_unload().is_ok());
assert!(module.commands().is_empty());
assert!(module.keybindings().is_empty());
assert!(module.event_handlers().is_empty());
assert!(module.dependencies().is_empty());
assert!(module.optional_dependencies().is_empty());
assert!(!module.supports_hot_reload());
assert!(module.save_state().is_none());
assert!(module.restore_state(&[1, 2]).is_err());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_init_exit_via_dyn() {
struct InitExitModule {
initialized: bool,
}
impl Module for InitExitModule {
fn id(&self) -> ModuleId {
ModuleId::new("init-exit")
}
fn name(&self) -> &'static str {
"Init Exit"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
self.initialized = true;
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
self.initialized = false;
Ok(())
}
}
let mut module: Box<dyn Module> = Box::new(InitExitModule { initialized: false });
let ctx = ModuleContext::default();
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
assert!(module.exit().is_ok());
assert_eq!(module.id(), ModuleId::new("init-exit"));
assert_eq!(module.name(), "Init Exit");
assert_eq!(module.version(), Version::new(1, 0, 0));
assert_eq!(module.api_version(), API_VERSION);
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_hot_reload_via_dyn() {
struct HotReloadModule {
state: u32,
}
impl Module for HotReloadModule {
fn id(&self) -> ModuleId {
ModuleId::new("hot-reload")
}
fn name(&self) -> &'static str {
"Hot Reload"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn supports_hot_reload(&self) -> bool {
true
}
fn save_state(&self) -> Option<Box<[u8]>> {
let bytes = self.state.to_le_bytes();
Some(bytes.to_vec().into_boxed_slice())
}
fn restore_state(&mut self, state: &[u8]) -> Result<(), ModuleError> {
if state.len() < 4 {
return Err(ModuleError::InitFailed("state too short".into()));
}
let bytes: [u8; 4] = state[..4]
.try_into()
.map_err(|_| ModuleError::InitFailed("invalid state".into()))?;
self.state = u32::from_le_bytes(bytes);
Ok(())
}
}
let mut module: Box<dyn Module> = Box::new(HotReloadModule { state: 42 });
assert!(module.supports_hot_reload());
let saved = module.save_state().unwrap();
assert_eq!(saved.len(), 4);
assert!(module.restore_state(&saved).is_ok());
assert!(module.restore_state(&[1]).is_err());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_with_dependencies_via_dyn() {
struct DepModule;
impl Module for DepModule {
fn id(&self) -> ModuleId {
ModuleId::new("dep-module")
}
fn name(&self) -> &'static str {
"Dep Module"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn dependencies(&self) -> Vec<ModuleId> {
vec![ModuleId::new("core-module")]
}
fn optional_dependencies(&self) -> Vec<ModuleId> {
vec![ModuleId::new("feat-lsp")]
}
}
let mut module: Box<dyn Module> = Box::new(DepModule);
let deps = module.dependencies();
assert_eq!(deps.len(), 1);
assert_eq!(deps[0], ModuleId::new("core-module"));
let opt_deps = module.optional_dependencies();
assert_eq!(opt_deps.len(), 1);
assert_eq!(opt_deps[0], ModuleId::new("feat-lsp"));
let ctx = ModuleContext::default();
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_deferred_module_init_both_paths() {
struct DeferredModule {
ready: bool,
}
impl Module for DeferredModule {
fn id(&self) -> ModuleId {
ModuleId::new("deferred")
}
fn name(&self) -> &'static str {
"Deferred Module"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
if self.ready {
ProbeResult::Success
} else {
ProbeResult::Defer("waiting for dependency".into())
}
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let ctx = ModuleContext::default();
let mut not_ready = DeferredModule { ready: false };
assert_eq!(not_ready.id(), ModuleId::new("deferred"));
assert_eq!(not_ready.name(), "Deferred Module");
assert_eq!(not_ready.version(), Version::new(1, 0, 0));
let result = not_ready.init(&ctx);
assert!(matches!(result, ProbeResult::Defer(_)));
assert!(not_ready.exit().is_ok());
let mut ready = DeferredModule { ready: true };
let result = ready.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
assert!(ready.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_minimal_module_full_lifecycle() {
struct MinimalModule;
impl Module for MinimalModule {
fn id(&self) -> ModuleId {
ModuleId::new("minimal")
}
fn name(&self) -> &'static str {
"Minimal"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut m = MinimalModule;
let ctx = ModuleContext::default();
assert_eq!(m.id(), ModuleId::new("minimal"));
assert_eq!(m.name(), "Minimal");
assert_eq!(m.version(), Version::new(0, 1, 0));
assert_eq!(m.api_version(), API_VERSION);
let result = m.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
m.on_all_loaded(&ctx);
m.on_buffer_focus(BufferId::from_raw(1), &ctx);
assert!(m.on_unload().is_ok());
assert!(m.exit().is_ok());
assert!(m.dependencies().is_empty());
assert!(m.optional_dependencies().is_empty());
assert!(m.commands().is_empty());
assert!(m.keybindings().is_empty());
assert!(m.event_handlers().is_empty());
assert!(!m.supports_hot_reload());
assert!(m.save_state().is_none());
assert!(m.restore_state(&[1, 2, 3]).is_err());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_hook_module_init_exit_exercised() {
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
struct HookModule {
all_loaded: Arc<AtomicBool>,
buffer_focus: Arc<AtomicBool>,
on_unload: Arc<AtomicBool>,
}
impl Module for HookModule {
fn id(&self) -> ModuleId {
ModuleId::new("hook-test")
}
fn name(&self) -> &'static str {
"Hook Test"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn on_all_loaded(&mut self, _ctx: &ModuleContext) {
self.all_loaded.store(true, Ordering::SeqCst);
}
fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext) {
self.buffer_focus.store(true, Ordering::SeqCst);
}
fn on_unload(&mut self) -> Result<(), ModuleError> {
self.on_unload.store(true, Ordering::SeqCst);
Ok(())
}
}
let all_loaded = Arc::new(AtomicBool::new(false));
let buffer_focus = Arc::new(AtomicBool::new(false));
let unload = Arc::new(AtomicBool::new(false));
let mut module: Box<dyn Module> = Box::new(HookModule {
all_loaded: Arc::clone(&all_loaded),
buffer_focus: Arc::clone(&buffer_focus),
on_unload: Arc::clone(&unload),
});
assert_eq!(module.id(), ModuleId::new("hook-test"));
assert_eq!(module.name(), "Hook Test");
assert_eq!(module.version(), Version::new(1, 0, 0));
let ctx = ModuleContext::default();
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
module.on_all_loaded(&ctx);
assert!(all_loaded.load(Ordering::SeqCst));
module.on_buffer_focus(BufferId::from_raw(1), &ctx);
assert!(buffer_focus.load(Ordering::SeqCst));
assert!(module.on_unload().is_ok());
assert!(unload.load(Ordering::SeqCst));
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_default_module_full_exercise() {
struct DefaultModule;
impl Module for DefaultModule {
fn id(&self) -> ModuleId {
ModuleId::new("default-hooks")
}
fn name(&self) -> &'static str {
"Default Hooks"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut module: Box<dyn Module> = Box::new(DefaultModule);
let ctx = ModuleContext::default();
assert_eq!(module.id(), ModuleId::new("default-hooks"));
assert_eq!(module.name(), "Default Hooks");
assert_eq!(module.version(), Version::new(0, 1, 0));
assert_eq!(module.api_version(), API_VERSION);
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
module.on_all_loaded(&ctx);
module.on_buffer_focus(BufferId::from_raw(42), &ctx);
assert!(module.on_unload().is_ok());
assert!(module.exit().is_ok());
assert!(module.commands().is_empty());
assert!(module.keybindings().is_empty());
assert!(module.event_handlers().is_empty());
assert!(module.dependencies().is_empty());
assert!(module.optional_dependencies().is_empty());
assert!(!module.supports_hot_reload());
assert!(module.save_state().is_none());
assert!(module.restore_state(&[1, 2]).is_err());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_hot_reload_module_full_exercise() {
struct HotReloadModule {
state: u32,
}
impl Module for HotReloadModule {
fn id(&self) -> ModuleId {
ModuleId::new("hot-reload")
}
fn name(&self) -> &'static str {
"Hot Reload"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn supports_hot_reload(&self) -> bool {
true
}
fn save_state(&self) -> Option<Box<[u8]>> {
let bytes = self.state.to_le_bytes();
Some(bytes.to_vec().into_boxed_slice())
}
fn restore_state(&mut self, state: &[u8]) -> Result<(), ModuleError> {
if state.len() < 4 {
return Err(ModuleError::InitFailed("state too short".into()));
}
let bytes: [u8; 4] = state[..4]
.try_into()
.map_err(|_| ModuleError::InitFailed("invalid state".into()))?;
self.state = u32::from_le_bytes(bytes);
Ok(())
}
}
let mut module: Box<dyn Module> = Box::new(HotReloadModule { state: 42 });
let ctx = ModuleContext::default();
assert_eq!(module.id(), ModuleId::new("hot-reload"));
assert_eq!(module.name(), "Hot Reload");
assert_eq!(module.version(), Version::new(1, 0, 0));
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
assert!(module.supports_hot_reload());
let saved = module.save_state().unwrap();
assert_eq!(saved.len(), 4);
assert!(module.restore_state(&saved).is_ok());
let short_result = module.restore_state(&[1]);
assert!(short_result.is_err());
let empty_result = module.restore_state(&[]);
assert!(empty_result.is_err());
assert!(module.restore_state(&[0, 0, 0, 0]).is_ok());
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_failed_init() {
struct FailingModule;
impl Module for FailingModule {
fn id(&self) -> ModuleId {
ModuleId::new("failing")
}
fn name(&self) -> &'static str {
"Failing Module"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Failed(ModuleError::InitFailed("config missing".into()))
}
fn exit(&mut self) -> Result<(), ModuleError> {
Err(ModuleError::InitFailed("never initialized".into()))
}
}
let mut module = FailingModule;
let ctx = ModuleContext::default();
assert_eq!(module.id(), ModuleId::new("failing"));
assert_eq!(module.name(), "Failing Module");
assert_eq!(module.version(), Version::new(0, 1, 0));
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Failed(_)));
let exit_result = module.exit();
assert!(exit_result.is_err());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_on_unload_error() {
struct UnloadFailModule;
impl Module for UnloadFailModule {
fn id(&self) -> ModuleId {
ModuleId::new("unload-fail")
}
fn name(&self) -> &'static str {
"Unload Fail"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn on_unload(&mut self) -> Result<(), ModuleError> {
Err(ModuleError::InitFailed("resource leak".into()))
}
}
let mut module = UnloadFailModule;
let ctx = ModuleContext::default();
assert_eq!(module.id(), ModuleId::new("unload-fail"));
assert_eq!(module.name(), "Unload Fail");
assert_eq!(module.version(), Version::new(1, 0, 0));
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
let unload_result = module.on_unload();
assert!(unload_result.is_err());
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_with_custom_api_version() {
struct CustomApiModule;
impl Module for CustomApiModule {
fn id(&self) -> ModuleId {
ModuleId::new("custom-api")
}
fn name(&self) -> &'static str {
"Custom API"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn api_version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut module = CustomApiModule;
let ctx = ModuleContext::default();
assert_eq!(module.api_version(), Version::new(0, 1, 0));
assert_ne!(module.api_version(), API_VERSION);
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_with_registrations() {
struct RegistrationModule;
impl Module for RegistrationModule {
fn id(&self) -> ModuleId {
ModuleId::new("reg-module")
}
fn name(&self) -> &'static str {
"Registration Module"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn commands(&self) -> Vec<CommandRegistration> {
vec![CommandRegistration::new("test-cmd").with_name("Test Command")]
}
fn keybindings(&self) -> Vec<KeybindingRegistration> {
use crate::core::CommandId;
let cmd_id = CommandId::new(ModuleId::new("reg-module"), "test-cmd");
vec![KeybindingRegistration::new("t", cmd_id).with_modes(&["normal"])]
}
fn event_handlers(&self) -> Vec<EventHandlerRegistration> {
vec![EventHandlerRegistration::new("BufferChanged").with_priority(50)]
}
}
let mut module: Box<dyn Module> = Box::new(RegistrationModule);
let ctx = ModuleContext::default();
assert_eq!(module.id(), ModuleId::new("reg-module"));
assert_eq!(module.name(), "Registration Module");
assert_eq!(module.version(), Version::new(1, 0, 0));
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
let cmds = module.commands();
assert_eq!(cmds.len(), 1);
assert_eq!(cmds[0].id, "test-cmd");
assert_eq!(cmds[0].name, "Test Command");
let keys = module.keybindings();
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].keys, "t");
let events = module.event_handlers();
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_type, "BufferChanged");
assert_eq!(events[0].priority, 50);
assert!(module.exit().is_ok());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_restore_state_default_error_message() {
struct NoHotReload;
impl Module for NoHotReload {
fn id(&self) -> ModuleId {
ModuleId::new("no-hot-reload")
}
fn name(&self) -> &'static str {
"No Hot Reload"
}
fn version(&self) -> Version {
Version::new(0, 1, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
}
let mut module = NoHotReload;
let result = module.restore_state(&[1, 2, 3, 4]);
assert!(result.is_err());
if let Err(ModuleError::InitFailed(msg)) = result {
assert!(msg.contains("hot reload not supported"));
} else {
panic!("expected InitFailed error");
}
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_module_multiple_buffer_focus_calls() {
struct CountingModule {
focus_count: u32,
}
impl Module for CountingModule {
fn id(&self) -> ModuleId {
ModuleId::new("counting")
}
fn name(&self) -> &'static str {
"Counting"
}
fn version(&self) -> Version {
Version::new(1, 0, 0)
}
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
Ok(())
}
fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext) {
self.focus_count += 1;
}
}
let mut module = CountingModule { focus_count: 0 };
let ctx = ModuleContext::default();
let result = module.init(&ctx);
assert!(matches!(result, ProbeResult::Success));
module.on_buffer_focus(BufferId::from_raw(1), &ctx);
module.on_buffer_focus(BufferId::from_raw(2), &ctx);
module.on_buffer_focus(BufferId::from_raw(3), &ctx);
assert_eq!(module.focus_count, 3);
assert!(module.exit().is_ok());
}