use rmux_core::LifecycleEvent;
use rmux_proto::{
HookLifecycle, HookName, LinkWindowRequest, Request, Response, ScopeSelector, SessionName,
SetHookRequest, WindowTarget,
};
use tokio::sync::oneshot;
use super::{
create_session, register_control_client, run_control_command, run_detached_command,
settle_control_notifications, wait_for_buffer_text, RequestHandler,
};
use crate::handler::pane_group_transfer_tests::create_grouped_session;
#[tokio::test]
async fn linked_and_grouped_join_move_keep_requested_resize_alias() {
for operation in ["join-pane", "move-pane"] {
for family in ["linked", "grouped"] {
assert_transfer_alias(operation, family).await;
}
}
}
async fn assert_transfer_alias(operation: &str, family: &str) {
let label = format!("{operation}-{family}");
let alpha = session_name(&format!("resize-alias-alpha-{label}"));
let beta = session_name(&format!("resize-alias-beta-{label}"));
let source = session_name(&format!("resize-alias-source-{label}"));
let buffer = format!("resize-alias-events-{label}");
let handler = RequestHandler::new();
let lifecycle_dispatch = handler
.take_lifecycle_dispatch_receiver()
.expect("test owns lifecycle dispatch");
let (hook_shutdown_tx, hook_shutdown_rx) = oneshot::channel();
let hook_handler = handler.clone();
let hook_task = tokio::spawn(async move {
hook_handler
.consume_lifecycle_hooks(lifecycle_dispatch, hook_shutdown_rx)
.await;
});
create_session(&handler, &alpha).await;
if family == "grouped" {
create_grouped_session(&handler, beta.as_str(), &alpha).await;
} else {
create_session(&handler, &beta).await;
let linked = handler
.handle(Request::LinkWindow(LinkWindowRequest {
source: WindowTarget::with_window(alpha.clone(), 0),
target: WindowTarget::with_window(beta.clone(), 0),
after: false,
before: false,
kill_destination: true,
detached: true,
}))
.await;
assert!(matches!(linked, Response::LinkWindow(_)), "{linked:?}");
}
let requested = alias_opposite_hashmap_first(&handler, &alpha, &beta).await;
create_session(&handler, &source).await;
run_detached_command(
&handler,
&format!("set-window-option -t {requested}:0 window-size manual"),
)
.await;
run_detached_command(
&handler,
&format!("resize-window -t {requested}:0 -x 1 -y 1"),
)
.await;
run_detached_command(&handler, &format!("set-buffer -b {buffer} ''")).await;
set_resize_hook(&handler, &buffer).await;
let (control_pid, mut notifications) = register_control_client(&handler, &requested).await;
let _ = settle_control_notifications(&mut notifications).await;
let mut lifecycle_events = handler.subscribe_lifecycle_events();
let command = format!("{operation} -h -d -s {source}:0.0 -t {requested}:0.0");
run_control_command(&handler, control_pid, &command).await;
let control_lines = settle_control_notifications(&mut notifications).await;
let hook_events = wait_for_buffer_text(&handler, &buffer, requested.as_str()).await;
let mut events = Vec::new();
while let Ok(Ok(event)) =
tokio::time::timeout(super::CONTROL_NOTIFICATION_POLL, lifecycle_events.recv()).await
{
events.push(event.event);
}
let resized_targets = events
.iter()
.filter_map(|event| match event {
LifecycleEvent::WindowResized { target } => Some(target.clone()),
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(
resized_targets,
vec![WindowTarget::with_window(requested.clone(), 0)],
"{label}: lifecycle resize must retain the requested alias; events={events:?}"
);
assert_eq!(
hook_events,
Some(requested.to_string()),
"{label}: the hook format context must retain the requested session alias"
);
assert_eq!(
control_lines
.iter()
.filter(|line| line.starts_with("%layout-change "))
.count(),
1,
"{label}: linked/grouped aliases must still receive one control layout"
);
let _ = hook_shutdown_tx.send(());
hook_task.await.expect("lifecycle hook task");
}
async fn alias_opposite_hashmap_first(
handler: &RequestHandler,
alpha: &SessionName,
beta: &SessionName,
) -> SessionName {
let state = handler.state.lock().await;
let shared_window_id = state
.sessions
.session(alpha)
.expect("alpha")
.window_at(0)
.expect("alpha window")
.id();
let first = state
.sessions
.iter()
.find_map(|(session_name, session)| {
session
.window_at(0)
.is_some_and(|window| window.id() == shared_window_id)
.then(|| session_name.clone())
})
.expect("shared alias");
if first == *alpha {
beta.clone()
} else {
alpha.clone()
}
}
async fn set_resize_hook(handler: &RequestHandler, buffer: &str) {
let response = handler
.handle(Request::SetHook(SetHookRequest {
scope: ScopeSelector::Global,
hook: HookName::WindowResized,
command: format!("run-shell -C 'set-buffer -b {buffer} #{{session_name}}'"),
lifecycle: HookLifecycle::Persistent,
}))
.await;
assert!(matches!(response, Response::SetHook(_)), "{response:?}");
}
fn session_name(value: &str) -> SessionName {
SessionName::new(value).expect("test session name")
}