mod common;
use common::Spy;
use std::cell::RefCell;
use std::rc::Rc;
use tiny_pubsub::PubSub;
#[test]
fn ancestor_subscriber_receives_original_message() {
let bus: PubSub<String> = PubSub::new();
let spy_a = Spy::new();
let spy_abc = Spy::new();
bus.subscribe("a", spy_a.subscriber());
bus.subscribe("a.b.c", spy_abc.subscriber());
let _ = bus.publish_sync("a.b.c", "payload".into());
assert!(spy_a.called_with_message("a.b.c"));
assert!(spy_abc.called_with_message("a.b.c"));
}
#[test]
fn wildcard_fires_last_and_counts_for_publish_return() {
let bus: PubSub<String> = PubSub::new();
bus.subscribe_all(|_, _| {});
assert!(bus.publish_sync("anything", String::new()));
let bus2: PubSub<&'static str> = PubSub::new();
let order = Rc::new(RefCell::new(Vec::<&'static str>::new()));
let o1 = order.clone();
bus2.subscribe("x", move |_, _| o1.borrow_mut().push("x"));
let o2 = order.clone();
bus2.subscribe_all(move |_, _| o2.borrow_mut().push("star"));
let _ = bus2.publish_sync("x", "");
assert_eq!(*order.borrow(), vec!["x", "star"]);
}
#[test]
fn subscribe_once_is_not_reentrant() {
let bus: Rc<PubSub<String>> = Rc::new(PubSub::new());
let count = Rc::new(RefCell::new(0u32));
let bus_inner = bus.clone();
let count_inner = count.clone();
bus.subscribe_once("topic", move |_, _| {
*count_inner.borrow_mut() += 1;
let _ = bus_inner.publish_sync("topic", String::new());
});
let _ = bus.publish_sync("topic", String::new());
assert_eq!(*count.borrow(), 1);
}
#[test]
fn delivery_preserves_subscription_order() {
let bus: PubSub<&'static str> = PubSub::new();
let order = Rc::new(RefCell::new(Vec::<&'static str>::new()));
let o1 = order.clone();
bus.subscribe("topic", move |_, _| o1.borrow_mut().push("first"));
let o2 = order.clone();
bus.subscribe("topic", move |_, _| o2.borrow_mut().push("second"));
let _ = bus.publish_sync("topic", "");
assert_eq!(*order.borrow(), vec!["first", "second"]);
}
#[test]
fn no_dot_topic_delivers_self_then_wildcard() {
let bus: PubSub<String> = PubSub::new();
let spy = Spy::new();
let star = Spy::new();
bus.subscribe("plain", spy.subscriber());
bus.subscribe_all(star.subscriber());
let _ = bus.publish_sync("plain", String::new());
assert!(spy.called_once());
assert!(star.called_once());
}
#[test]
fn empty_topic_is_valid() {
let bus: PubSub<String> = PubSub::new();
let spy = Spy::new();
bus.subscribe("", spy.subscriber());
let _ = bus.publish_sync("", String::new());
assert!(spy.called_once());
}
#[test]
fn trailing_dot_topic_walk() {
let bus: PubSub<String> = PubSub::new();
let spy_dot = Spy::new();
let spy_a = Spy::new();
bus.subscribe("a.", spy_dot.subscriber());
bus.subscribe("a", spy_a.subscriber());
let _ = bus.publish_sync("a.", String::new());
assert!(spy_dot.called_once());
assert!(spy_a.called_once());
}
#[test]
fn hierarchy_matches_independent_reference() {
let all_topics = ["a", "a.b", "a.b.c", "a.b.c.d", "a.x", "a.x.y", "z", "*"];
let published = ["a.b.c.d", "a.b", "a", "a.x.y", "z", "q.r"];
for pub_topic in published {
let bus: PubSub<String> = PubSub::new();
let spies: Vec<(&str, Spy)> = all_topics.iter().map(|t| (*t, Spy::new())).collect();
for (topic, spy) in &spies {
bus.subscribe(*topic, spy.subscriber());
}
let _ = bus.publish_sync(pub_topic, String::new());
let expected = expected_matches(pub_topic);
for (topic, spy) in &spies {
let want = expected.contains(&topic.to_string());
assert_eq!(
spy.called(),
want,
"topic {topic} should fire={want} when publishing {pub_topic}"
);
}
}
}
fn expected_matches(published: &str) -> Vec<String> {
let mut out = vec![published.to_string()];
let mut topic = published.to_string();
while let Some(pos) = topic.rfind('.') {
topic.truncate(pos);
out.push(topic.clone());
}
out.push("*".to_string());
out
}