mod common;
use wdotool_core::backend::mock::{BackendCall, MockBackend};
use wdotool_core::detector::Environment;
use wdotool_core::types::{WindowGeometry, WindowId, WindowInfo};
use wdotool_core::WdoError;
fn win(id: &str, title: &str, app_id: Option<&str>, pid: Option<u32>) -> WindowInfo {
WindowInfo {
id: WindowId(id.into()),
title: title.into(),
app_id: app_id.map(str::to_string),
pid,
}
}
fn mock_with_windows(windows: Vec<WindowInfo>) -> MockBackend {
let mock = MockBackend::new();
mock.set_windows(windows);
mock
}
#[tokio::test]
async fn search_no_filter_lists_all_windows_tab_separated() {
let mock = mock_with_windows(vec![
win("w1", "Firefox", Some("org.mozilla.firefox"), Some(100)),
win("w2", "Terminal", Some("kitty"), Some(200)),
]);
let r = common::run_with(&["search"], mock, Environment::default()).await;
assert!(r.exit.is_success(), "stderr: {}", r.stderr);
assert_eq!(r.stdout, "w1\tFirefox\nw2\tTerminal\n");
assert_eq!(r.calls, vec![BackendCall::ListWindows]);
}
#[tokio::test]
async fn search_no_match_exits_with_code_1() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, None)]);
let r = common::run_with(
&["search", "--name", "Chromium"],
mock,
Environment::default(),
)
.await;
assert!(!r.exit.is_success());
assert_eq!(r.exit.0, 1);
assert!(r.error.is_none(), "no match should not raise an error");
assert_eq!(r.stdout, "");
}
#[tokio::test]
async fn search_default_substring_match_is_case_sensitive() {
let r = common::run_with(
&["search", "--name", "Fire"],
mock_with_windows(vec![win("w1", "Firefox - Wikipedia", None, None)]),
Environment::default(),
)
.await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "w1\tFirefox - Wikipedia\n");
let r = common::run_with(
&["search", "--name", "fire"],
mock_with_windows(vec![win("w1", "Firefox - Wikipedia", None, None)]),
Environment::default(),
)
.await;
assert_eq!(r.exit.0, 1);
assert_eq!(r.stdout, "");
}
#[tokio::test]
async fn search_ignore_case_makes_substring_match() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, None)]);
let r = common::run_with(
&["search", "--ignore-case", "--name", "FIRE"],
mock,
Environment::default(),
)
.await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "w1\tFirefox\n");
}
#[tokio::test]
async fn search_regex_flag_treats_pattern_as_regex() {
let mock = mock_with_windows(vec![
win("w1", "Firefox", None, None),
win("w2", "Fire fox", None, None),
win("w3", "Mozilla", None, None),
]);
let r = common::run_with(
&["search", "--regex", "--name", "Fire.fox"],
mock,
Environment::default(),
)
.await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "w2\tFire fox\n");
}
#[tokio::test]
async fn search_any_combines_filters_with_or() {
let mock = mock_with_windows(vec![
win("w1", "Firefox", Some("none"), Some(999)),
win("w2", "Slack", Some("kitty"), Some(999)),
]);
let r = common::run_with(
&["search", "--any", "--name", "Firefox", "--class", "kitty"],
mock,
Environment::default(),
)
.await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "w1\tFirefox\nw2\tSlack\n");
}
#[tokio::test]
async fn getactivewindow_prints_id_when_active() {
let mock = MockBackend::new();
mock.set_active_window(Some(win("active-id", "Active", None, None)));
let r = common::run_with(&["getactivewindow"], mock, Environment::default()).await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "active-id\n");
assert_eq!(r.calls, vec![BackendCall::ActiveWindow]);
}
#[tokio::test]
async fn getactivewindow_with_no_active_returns_window_not_found() {
let r = common::run(&["getactivewindow"]).await;
let err = r.error.expect("expected an error");
assert!(matches!(err, WdoError::WindowNotFound(_)), "got: {err:?}");
}
#[tokio::test]
async fn windowactivate_passes_id_verbatim() {
let r = common::run(&["windowactivate", "some-id"]).await;
assert!(r.exit.is_success());
assert_eq!(
r.calls,
vec![BackendCall::ActivateWindow(WindowId("some-id".into()))]
);
}
#[tokio::test]
async fn windowclose_passes_id_verbatim() {
let r = common::run(&["windowclose", "some-id"]).await;
assert!(r.exit.is_success());
assert_eq!(
r.calls,
vec![BackendCall::CloseWindow(WindowId("some-id".into()))]
);
}
#[tokio::test]
async fn getwindowname_prints_title_for_known_id() {
let mock = mock_with_windows(vec![win("w1", "Firefox - News", None, None)]);
let r = common::run_with(&["getwindowname", "w1"], mock, Environment::default()).await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "Firefox - News\n");
}
#[tokio::test]
async fn getwindowname_unknown_id_errors() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, None)]);
let r = common::run_with(&["getwindowname", "missing"], mock, Environment::default()).await;
let err = r.error.expect("expected an error");
assert!(matches!(err, WdoError::WindowNotFound(_)), "got: {err:?}");
}
#[tokio::test]
async fn getwindowpid_prints_pid_when_set() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, Some(4242))]);
let r = common::run_with(&["getwindowpid", "w1"], mock, Environment::default()).await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "4242\n");
}
#[tokio::test]
async fn getwindowpid_exits_1_when_pid_absent() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, None)]);
let r = common::run_with(&["getwindowpid", "w1"], mock, Environment::default()).await;
assert_eq!(r.exit.0, 1);
assert!(r.error.is_none());
assert_eq!(r.stdout, "");
assert!(
r.stderr.contains("pid not available"),
"stderr: {}",
r.stderr
);
}
#[tokio::test]
async fn getwindowclassname_prints_app_id_when_set() {
let mock = mock_with_windows(vec![win(
"w1",
"Firefox",
Some("org.mozilla.firefox"),
None,
)]);
let r = common::run_with(&["getwindowclassname", "w1"], mock, Environment::default()).await;
assert!(r.exit.is_success());
assert_eq!(r.stdout, "org.mozilla.firefox\n");
}
#[tokio::test]
async fn getwindowclassname_exits_1_when_app_id_absent() {
let mock = mock_with_windows(vec![win("w1", "Firefox", None, None)]);
let r = common::run_with(&["getwindowclassname", "w1"], mock, Environment::default()).await;
assert_eq!(r.exit.0, 1);
assert!(r.error.is_none());
assert!(r.stderr.contains("classname"), "stderr: {}", r.stderr);
}
#[tokio::test]
async fn getwindowgeometry_prints_xdotool_format_when_supported() {
let mock = MockBackend::new();
mock.set_geometry(
"w1",
WindowGeometry {
x: 100,
y: 200,
width: 800,
height: 600,
},
);
let r = common::run_with(&["getwindowgeometry", "w1"], mock, Environment::default()).await;
assert!(r.exit.is_success(), "stderr: {}", r.stderr);
assert_eq!(
r.stdout,
"Window w1\n Position: 100,200\n Geometry: 800x600\n"
);
}
#[tokio::test]
async fn getwindowgeometry_exits_1_when_backend_returns_none() {
let r = common::run(&["getwindowgeometry", "w1"]).await;
assert_eq!(r.exit.0, 1);
assert!(r.error.is_none());
assert!(
r.stderr.contains("window geometry is unreadable"),
"stderr: {}",
r.stderr
);
}