use std::cell::RefCell;
use super::*;
use crate::sway::ipc::GET_OUTPUTS;
use crate::sway::{SwayEvent, SwayEventKind};
const MODE_EVENT: u32 = 0x8000_0002;
const WINDOW_EVENT: u32 = 0x8000_0003;
fn recorder() -> (impl Fn(&SwayEvent), Rc<RefCell<Vec<SwayEvent>>>) {
let heard = Rc::new(RefCell::new(Vec::new()));
let keep = Rc::clone(&heard);
(
move |event: &SwayEvent| keep.borrow_mut().push(event.clone()),
heard,
)
}
fn event(kind: SwayEventKind, data: &str) -> SwayEvent {
SwayEvent {
kind,
data: data.to_owned(),
}
}
#[test]
fn each_kind_has_its_own_code_and_name() {
for kind in SwayEventKind::ALL {
let (code, name) = kind.wire();
assert_eq!(SwayEventKind::from_code(code), Some(kind), "{name}");
let same_name = SwayEventKind::ALL
.iter()
.filter(|other| other.wire().1 == name);
assert_eq!(same_name.count(), 1, "{name}");
}
assert_eq!(SwayEventKind::from_code(GET_WORKSPACES), None);
}
#[test]
fn a_listener_subscribes_to_its_kinds_alone_and_hears_their_events() {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let sway = shell.cx.sway();
let (first, heard) = recorder();
sway.on_event(&[SwayEventKind::Mode, SwayEventKind::Window], first);
assert_eq!(peer.read(), (SUBSCRIBE, r#"["mode","window"]"#.to_owned()));
peer.send(SUBSCRIBE, r#"{"success":true}"#);
let (second, also_heard) = recorder();
sway.on_event(&[SwayEventKind::Mode], second);
let (third, workspace_heard) = recorder();
sway.on_event(&[SwayEventKind::Workspace], third);
peer.read_nothing();
let mode = r#"{"change":"resize","pango_markup":false}"#;
peer.send(MODE_EVENT, mode);
peer.send(WINDOW_EVENT, r#"{"change":"focus"}"#);
shell.turn();
let mode = event(SwayEventKind::Mode, mode);
let window = event(SwayEventKind::Window, r#"{"change":"focus"}"#);
assert_eq!(*heard.borrow(), [mode.clone(), window]);
assert_eq!(*also_heard.borrow(), [mode]);
peer.send_event(&mut shell);
assert_eq!(
*workspace_heard.borrow(),
[event(SwayEventKind::Workspace, "{}")]
);
peer.read_nothing();
}
#[test]
fn a_bad_event_is_logged_and_skipped() {
let logs = capture_logs(|| {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let (handler, heard) = recorder();
shell.cx.sway().on_event(&[SwayEventKind::Mode], handler);
peer.read();
let mut bytes = Vec::new();
ipc::encode(MODE_EVENT, b"{not json", &mut bytes);
ipc::encode(MODE_EVENT, b"\"\xff\"", &mut bytes);
ipc::encode(0x8000_0099, b"{}", &mut bytes);
ipc::encode(MODE_EVENT, br#"{"change":"default"}"#, &mut bytes);
peer.0.write_all(&bytes).expect("a write to the service");
shell.turn();
let default = event(SwayEventKind::Mode, r#"{"change":"default"}"#);
assert_eq!(*heard.borrow(), [default]);
});
assert_eq!(logs.matches("a bad event from Sway").count(), 2, "{logs}");
}
#[test]
fn a_panicking_listener_is_logged_and_dropped_and_the_rest_go_on() {
let mut at = String::new();
let logs = capture_logs(|| {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let sway = shell.cx.sway();
let runs = Rc::new(Cell::new(0));
let counted = Rc::clone(&runs);
at = format!("{}:{}", file!(), line!() + 1);
sway.on_event(&[SwayEventKind::Mode], move |_| {
counted.set(counted.get() + 1);
panic!("a bad binding");
});
peer.read();
let calls = sway.clone();
let (record, heard) = recorder();
sway.on_event(&[SwayEventKind::Mode], move |event| {
calls.dispatch("mode default");
calls.outputs();
record(event);
});
peer.send(MODE_EVENT, r#"{"change":"resize"}"#);
peer.send(MODE_EVENT, r#"{"change":"default"}"#);
shell.turn();
assert_eq!(runs.get(), 1, "not called after its panic");
assert_eq!(heard.borrow().len(), 2);
assert_eq!(peer.read(), (RUN_COMMAND, "mode default".to_owned()));
assert_eq!(peer.read(), (SUBSCRIBE, r#"["output"]"#.to_owned()));
peer.read();
assert_eq!(peer.read(), (RUN_COMMAND, "mode default".to_owned()));
peer.send_event(&mut shell);
assert_eq!(peer.read(), (GET_OUTPUTS, String::new()));
peer.send(GET_WORKSPACES, &two().0);
shell.turn();
assert_eq!(sway.workspaces().get_untracked(), two().1);
});
let logged = format!("Sway on_event (called at {at}");
assert_eq!(logs.matches(&logged).count(), 1, "{logs}");
assert!(logs.contains("panicked: a bad binding"), "{logs}");
}
#[test]
fn a_listener_goes_with_its_context_and_its_kind_with_the_next_event() {
let runtime = Runtime::current();
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
peer.send(0x8000_0007, r#"{"first":true,"payload":""}"#);
shell.turn();
peer.read_nothing();
assert!(fake.try_accept().is_none());
let nodes = runtime.node_count();
let output = shell.cx.child();
let (handler, heard) = recorder();
output.sway().on_event(&[SwayEventKind::Window], handler);
assert_eq!(peer.read(), (SUBSCRIBE, r#"["window"]"#.to_owned()));
peer.send(SUBSCRIBE, r#"{"success":true}"#);
output.owner().dispose();
assert_eq!(runtime.node_count(), nodes);
peer.read_nothing();
peer.send(WINDOW_EVENT, r#"{"change":"focus"}"#);
shell.turn();
peer.read_end();
let mut peer = fake.accept();
peer.read_opening();
assert!(heard.borrow().is_empty());
assert_eq!(
shell.cx.sway().workspaces().get_untracked(),
one().1,
"the list stays"
);
peer.send(SUBSCRIBE, r#"{"success":true}"#);
peer.send(GET_WORKSPACES, &one().0);
shell.turn();
peer.read_nothing();
}
#[test]
fn a_reconnect_waits_until_sway_answered_everything_sent() {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let child = shell.cx.child();
let (handler, _heard) = recorder();
child.sway().on_event(&[SwayEventKind::Mode], handler);
assert_eq!(peer.read(), (SUBSCRIBE, r#"["mode"]"#.to_owned()));
peer.send(SUBSCRIBE, r#"{"success":true}"#);
child.owner().dispose();
shell.cx.sway().dispatch("workspace 2");
assert_eq!(peer.read().0, RUN_COMMAND);
peer.send(MODE_EVENT, "{}");
shell.turn();
assert!(fake.try_accept().is_none(), "no reconnect before the reply");
peer.send(RUN_COMMAND, r#"[{"success":true}]"#);
shell.turn();
let mut new = fake.accept();
assert_eq!(new.read(), (SUBSCRIBE, r#"["workspace"]"#.to_owned()));
}
#[test]
fn on_event_on_a_context_that_went_is_logged_and_does_nothing() {
let fake = FakeSway::new();
let (shell, mut peer) = Harness::connect(&fake);
let child = shell.cx.child();
let stale = child.sway();
child.owner().dispose();
let (handler, heard) = recorder();
let logs = capture_logs(|| stale.on_event(&[SwayEventKind::Mode], handler));
assert!(logs.contains("context that is gone"), "{logs}");
peer.read_nothing();
assert_eq!(Rc::strong_count(&heard), 1, "the handler wasn't kept");
}
#[test]
fn a_handler_that_takes_anothers_context_stops_it() {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let other = shell.cx.child();
let other_owner = other.owner();
shell
.cx
.sway()
.on_event(&[SwayEventKind::Mode], move |_| other_owner.dispose());
let (handler, heard) = recorder();
other.sway().on_event(&[SwayEventKind::Mode], handler);
peer.read();
peer.send(MODE_EVENT, "{}");
shell.turn();
assert!(heard.borrow().is_empty(), "called after its context went");
}
#[test]
fn a_new_connection_after_a_drop_keeps_the_handlers_kinds() {
let fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let (handler, heard) = recorder();
shell.cx.sway().on_event(&[SwayEventKind::Mode], handler);
assert_eq!(peer.read(), (SUBSCRIBE, r#"["mode"]"#.to_owned()));
drop(peer);
shell.turn();
shell.run_for(RETRY + RETRY / 2);
let mut peer = fake.accept();
assert_eq!(
peer.read(),
(SUBSCRIBE, r#"["workspace","mode"]"#.to_owned())
);
peer.read();
peer.send(MODE_EVENT, "{}");
shell.turn();
assert_eq!(heard.borrow().len(), 1);
}
#[test]
fn a_new_connection_sway_refuses_is_logged_once() {
let logs = capture_logs(|| {
let mut fake = FakeSway::new();
let (mut shell, mut peer) = Harness::connect(&fake);
let child = shell.cx.child();
let (handler, _heard) = recorder();
child.sway().on_event(&[SwayEventKind::Mode], handler);
peer.read();
peer.send(SUBSCRIBE, r#"{"success":true}"#);
child.owner().dispose();
fake.close();
peer.send(MODE_EVENT, "{}");
shell.turn();
});
assert!(logs.contains("lost the connection to Sway"), "{logs}");
assert!(!logs.contains("shows no workspaces"), "{logs}");
}