use std::time::Duration;
use super::common::unique_pipe_name;
use super::test_desktop::{
DaemonGuard, TestDesktop, TestWindow, query_windows, start_test_daemon, unique_title,
};
use flow_wm::ipc::message::{SocketMessage, SocketResponse};
use flow_wm::ipc::transport;
fn wait(ms: u64) {
std::thread::sleep(Duration::from_millis(ms));
}
fn send_ipc(pipe: &str, msg: &SocketMessage) -> Result<SocketResponse, String> {
for attempt in 0..5 {
match transport::send_message_to(pipe, msg) {
Ok(resp) => return Ok(resp),
Err(e) if e.kind() == std::io::ErrorKind::ConnectionRefused && attempt < 4 => {
wait(100);
continue;
}
Err(e) => return Err(format!("IPC failed after {} attempts: {e}", attempt + 1)),
}
}
unreachable!("retry loop exhausted without return")
}
fn column_of(json: &serde_json::Value, title: &str) -> Option<i64> {
json["windows"]
.as_array()?
.iter()
.find(|w| w["title"].as_str() == Some(title))
.and_then(|w| w["state"]["Tiling"]["Active"]["col"].as_i64())
}
fn debug_titles(json: &serde_json::Value) -> String {
json["windows"]
.as_array()
.map(|arr| {
arr.iter()
.map(|w| {
let title = w["title"].as_str().unwrap_or("?");
let col = w["state"]["Tiling"]["Active"]["col"].as_i64();
format!("{title}@col{col:?}")
})
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_else(|| "<no windows array>".into())
}
#[test]
fn window_creation_inserts_after_focused() {
let td = TestDesktop::create().expect("test desktop");
let pipe = unique_pipe_name();
let mut _child = start_test_daemon(&pipe, &td.name).expect("start daemon");
let _guard = DaemonGuard::new(&pipe);
wait(500);
let t1 = unique_title("Ins-A");
let t2 = unique_title("Ins-B");
let t3 = unique_title("Ins-C");
let w1 = TestWindow::create(&t1).expect("create W1");
wait(300);
let w2 = TestWindow::create(&t2).expect("create W2");
wait(1000);
let json = query_windows(&pipe).expect("query after W1+W2");
let col1 = column_of(&json, &t1).unwrap_or_else(|| {
panic!(
"W1 '{t1}' should be tiling. Windows: {}",
debug_titles(&json)
)
});
let col2 = column_of(&json, &t2).unwrap_or_else(|| {
panic!(
"W2 '{t2}' should be tiling. Windows: {}",
debug_titles(&json)
)
});
assert_eq!(
col1,
0,
"W1 should start at col 0. Windows: {}",
debug_titles(&json)
);
assert_eq!(
col2,
1,
"W2 should start at col 1. Windows: {}",
debug_titles(&json)
);
let resp = send_ipc(&pipe, &SocketMessage::FocusLeft).expect("FocusLeft IPC");
assert!(
matches!(resp, SocketResponse::Ok),
"FocusLeft should succeed, got: {resp:?}"
);
wait(500);
let w3 = TestWindow::create(&t3).expect("create W3");
wait(1500);
let json2 = query_windows(&pipe).expect("query after insert");
let col1b = column_of(&json2, &t1)
.unwrap_or_else(|| panic!("W1 missing after insert. Windows: {}", debug_titles(&json2)));
let col2b = column_of(&json2, &t2)
.unwrap_or_else(|| panic!("W2 missing after insert. Windows: {}", debug_titles(&json2)));
let col3 = column_of(&json2, &t3)
.unwrap_or_else(|| panic!("W3 missing after insert. Windows: {}", debug_titles(&json2)));
assert_eq!(
col1b,
0,
"W1 should stay at col 0 after insert. Windows: {}",
debug_titles(&json2)
);
assert_eq!(
col3,
1,
"W3 (new) should be at col 1 — inserted after focused W1. Windows: {}",
debug_titles(&json2)
);
assert_eq!(
col2b,
2,
"W2 should shift to col 2 (pushed right by insertion). Windows: {}",
debug_titles(&json2)
);
println!("✓ window_creation_inserts_after_focused: [W1={col1b}][W3={col3}][W2={col2b}]");
drop(w1);
drop(w2);
drop(w3);
drop(td);
}
#[ignore = "non-deterministic on isolated test desktop: daemon hooks may not register/tile windows before the assertion (startup hook race)"]
#[test]
fn window_creation_first_window_is_sole_column() {
let td = TestDesktop::create().expect("test desktop");
let pipe = unique_pipe_name();
let mut _child = start_test_daemon(&pipe, &td.name).expect("start daemon");
let _guard = DaemonGuard::new(&pipe);
wait(500);
let t1 = unique_title("First");
let w1 = TestWindow::create(&t1).expect("create window");
wait(1000);
let json = query_windows(&pipe).expect("query after first window");
let col = column_of(&json, &t1).unwrap_or_else(|| {
panic!(
"window '{t1}' should be tiling. Windows: {}",
debug_titles(&json)
)
});
assert_eq!(
col,
0,
"first window should be at col 0. Windows: {}",
debug_titles(&json)
);
println!("✓ window_creation_first_window_is_sole_column: W1 at col {col}");
drop(w1);
drop(td);
}
#[test]
fn window_creation_successive_appends_when_last_focused() {
let td = TestDesktop::create().expect("test desktop");
let pipe = unique_pipe_name();
let mut _child = start_test_daemon(&pipe, &td.name).expect("start daemon");
let _guard = DaemonGuard::new(&pipe);
wait(500);
let t1 = unique_title("Succ-1");
let t2 = unique_title("Succ-2");
let t3 = unique_title("Succ-3");
let w1 = TestWindow::create(&t1).expect("create W1");
wait(400);
let w2 = TestWindow::create(&t2).expect("create W2");
wait(400);
let w3 = TestWindow::create(&t3).expect("create W3");
wait(1500);
let json = query_windows(&pipe).expect("query after three windows");
let col1 = column_of(&json, &t1)
.unwrap_or_else(|| panic!("W1 missing. Windows: {}", debug_titles(&json)));
let col2 = column_of(&json, &t2)
.unwrap_or_else(|| panic!("W2 missing. Windows: {}", debug_titles(&json)));
let col3 = column_of(&json, &t3)
.unwrap_or_else(|| panic!("W3 missing. Windows: {}", debug_titles(&json)));
assert_eq!(
col1,
0,
"W1 should be col 0. Windows: {}",
debug_titles(&json)
);
assert_eq!(
col2,
1,
"W2 should be col 1. Windows: {}",
debug_titles(&json)
);
assert_eq!(
col3,
2,
"W3 should be col 2. Windows: {}",
debug_titles(&json)
);
println!("✓ window_creation_successive_appends: [W1={col1}][W2={col2}][W3={col3}]");
drop(w1);
drop(w2);
drop(w3);
drop(td);
}