use justerm_core::{Engine, TermEvent};
#[test]
fn osc2_sets_title_bel_terminated() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;hello\x07");
assert_eq!(term.drain_events(), vec![TermEvent::Title("hello".into())]);
}
#[test]
fn osc0_sets_title_st_terminated() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]0;world\x1b\\");
assert_eq!(term.drain_events(), vec![TermEvent::Title("world".into())]);
}
#[test]
fn bel_rings_bell() {
let mut term = Engine::new(80, 24);
term.feed(b"\x07");
assert_eq!(term.drain_events(), vec![TermEvent::Bell]);
}
#[test]
fn osc7_reports_cwd() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]7;file://host/home/ki\x07");
assert_eq!(
term.drain_events(),
vec![TermEvent::Cwd("file://host/home/ki".into())]
);
}
#[test]
fn drain_empties_the_queue() {
let mut term = Engine::new(80, 24);
term.feed(b"\x07");
assert_eq!(term.drain_events(), vec![TermEvent::Bell]);
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn events_preserve_stream_order() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;t1\x07\x07\x1b]7;file://h/p\x07");
assert_eq!(
term.drain_events(),
vec![
TermEvent::Title("t1".into()),
TermEvent::Bell,
TermEvent::Cwd("file://h/p".into()),
]
);
}
#[test]
fn osc8_hyperlink_emits_no_event() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]8;;https://example.com\x07linked\x1b]8;;\x07");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn printing_does_not_emit_events() {
let mut term = Engine::new(80, 24);
term.feed(b"plain text\r\n");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn xtwinops_pop_restores_the_pushed_title() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;sh\x07");
term.feed(b"\x1b[22;0;0t"); term.feed(b"\x1b]2;vi\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;0;0t"); assert_eq!(term.drain_events(), vec![TermEvent::Title("sh".into())]);
}
#[test]
fn xtwinops_nested_pushes_unwind_in_order() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;a\x07\x1b[22t\x1b]2;b\x07\x1b[22t\x1b]2;c\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23t\x1b[23t");
assert_eq!(
term.drain_events(),
vec![TermEvent::Title("b".into()), TermEvent::Title("a".into())]
);
}
#[test]
fn xtwinops_pop_on_an_empty_stack_emits_nothing() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;only\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;0;0t");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn xtwinops_over_popping_leaves_the_current_title_alone() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;base\x07\x1b[22t\x1b]2;inner\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23t\x1b[23t\x1b[23t");
assert_eq!(term.drain_events(), vec![TermEvent::Title("base".into())]);
}
#[test]
fn xtwinops_the_stack_is_ten_deep_and_drops_the_oldest() {
let mut term = Engine::new(80, 24);
for i in 0..11 {
term.feed(format!("\x1b]2;t{i}\x07").as_bytes());
term.feed(b"\x1b[22t");
}
let _ = term.drain_events();
for _ in 0..11 {
term.feed(b"\x1b[23t");
}
let restored: Vec<TermEvent> = (1..=10)
.rev()
.map(|i| TermEvent::Title(format!("t{i}")))
.collect();
assert_eq!(term.drain_events(), restored);
}
#[test]
fn xtwinops_an_icon_only_pop_does_not_restore_the_window_title() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;outer\x07\x1b[22;2t\x1b]2;inner\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;1t"); assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
term.feed(b"\x1b[23;2t"); assert_eq!(term.drain_events(), vec![TermEvent::Title("outer".into())]);
}
#[test]
fn xtwinops_a_window_only_push_is_not_restored_by_an_icon_only_pop() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;win\x07\x1b[22;2t\x1b]2;other\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;1t\x1b[23;1t\x1b[23;1t");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn xtwinops_both_spellings_of_the_both_axes_form_agree() {
let mut two = Engine::new(80, 24);
two.feed(b"\x1b]2;a\x07\x1b[22;0t\x1b]2;b\x07");
let _ = two.drain_events();
two.feed(b"\x1b[23;0t");
let mut three = Engine::new(80, 24);
three.feed(b"\x1b]2;a\x07\x1b[22;0;0t\x1b]2;b\x07");
let _ = three.drain_events();
three.feed(b"\x1b[23;0;0t");
assert_eq!(two.drain_events(), vec![TermEvent::Title("a".into())]);
assert_eq!(three.drain_events(), vec![TermEvent::Title("a".into())]);
}
#[test]
fn xtwinops_a_pop_with_no_title_change_still_reports_the_title() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;same\x07\x1b[22t");
let _ = term.drain_events();
term.feed(b"\x1b[23t");
assert_eq!(term.drain_events(), vec![TermEvent::Title("same".into())]);
}
#[test]
fn xtwinops_a_push_before_any_title_restores_the_empty_one() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b[22;0;0t\x1b]2;vim\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;0;0t");
assert_eq!(term.drain_events(), vec![TermEvent::Title(String::new())]);
}
#[test]
fn xtwinops_an_unrecognised_axis_acts_on_no_axis() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;base\x07\x1b[22;3t\x1b]2;inner\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;0t"); assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn xtwinops_the_third_parameter_is_ignored() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;base\x07\x1b[22;2;3t\x1b]2;inner\x07");
let _ = term.drain_events();
term.feed(b"\x1b[23;2;3t");
assert_eq!(term.drain_events(), vec![TermEvent::Title("base".into())]);
}
#[test]
fn xtwinops_a_reset_between_push_and_pop_makes_the_pop_a_no_op() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;before\x07\x1b[22t");
term.feed(b"\x1bc"); let _ = term.drain_events();
term.feed(b"\x1b[23t");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}
#[test]
fn xtwinops_the_alt_screen_does_not_disturb_the_title_stack() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;shell\x07\x1b[22;0;0t\x1b]2;vim\x07");
term.feed(b"\x1b[?1049h");
term.feed(b"\x1b[?1049l");
let _ = term.drain_events();
term.feed(b"\x1b[23;0;0t");
assert_eq!(term.drain_events(), vec![TermEvent::Title("shell".into())]);
}
#[test]
fn other_window_operations_remain_ignored() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b]2;kept\x07");
let _ = term.drain_events();
term.feed(b"\x1b[18t\x1b[14t\x1b[11t\x1b[24t\x1b[t");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
assert_eq!(term.drain_replies(), Vec::<u8>::new());
term.feed(b"\x1b[23t");
assert_eq!(term.drain_events(), Vec::<TermEvent>::new());
}