use super::*;
#[test]
fn osc_4_palette() {
let (_p, w) = parse(b"\x1b]4;1;red\x1b\\");
assert!(w.has_call("osc_palette("));
}
#[test]
fn osc_7_path() {
let (_p, w) = parse(b"\x1b]7;file:///tmp\x1b\\");
assert!(w.has_call("set_path(\"file:///tmp\")"));
}
#[test]
fn osc_8_hyperlink() {
let (_p, w) = parse(b"\x1b]8;;https://example.com\x1b\\");
assert!(w.has_call("osc_hyperlink("));
}
#[test]
fn osc_52_clipboard() {
let (_p, w) = parse(b"\x1b]52;c;SGVsbG8=\x1b\\");
assert!(w.has_call("osc_clipboard("));
}
#[test]
fn osc_133_shell_integration() {
let (_p, w) = parse(b"\x1b]133;A\x1b\\");
assert!(w.has_call("osc_shell_integration("));
}
#[test]
fn dcs_passthrough_tmux_prefix() {
let (_p, w) = parse(b"\x1bPtmux;hello world\x1b\\");
assert!(w.has_call("dcs_passthrough(\"hello world\")"));
}
#[test]
fn dcs_passthrough_tmux_prefix_preserves_inner_escape() {
let (_p, w) = parse(b"\x1bPtmux;\x1b]52;c;QQ==\x07\x1b\\");
assert!(w.has_call("dcs_passthrough(\"\\u{1b}]52;c;QQ==\\u{7}\")"));
}
#[test]
fn dcs_passthrough_tmux_prefix_decodes_doubled_inner_escape() {
let (_p, w) = parse(b"\x1bPtmux;\x1b\x1b]52;c;QQ==\x07\x1b\\");
assert!(w.has_call("dcs_passthrough(\"\\u{1b}]52;c;QQ==\\u{7}\")"));
}
#[test]
fn dcs_tmux_wrapped_osc_bel_without_outer_st_recovers_to_ground() {
let (p, w) = parse(b"\x1bPtmux;\x1b]4;0;?\x07AFTER");
assert_eq!(p.state(), InputState::Ground);
assert!(w.has_call("dcs_passthrough(\"\\u{1b}]4;0;?\\u{7}\")"));
assert_eq!(w.chars, vec!['A', 'F', 'T', 'E', 'R']);
}
#[test]
fn dcs_tmux_wrapped_osc_bel_recovers_after_doubled_inner_escape() {
let (p, w) = parse(b"\x1bPtmux;\x1b\x1b]4;0;?\x07AFTER");
assert_eq!(p.state(), InputState::Ground);
assert!(w.has_call("dcs_passthrough(\"\\u{1b}]4;0;?\\u{7}\")"));
assert_eq!(w.chars, vec!['A', 'F', 'T', 'E', 'R']);
}
#[test]
fn dcs_tmux_bel_does_not_terminate_non_osc_passthrough() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1bPtmux;not-osc\x07AFTER", &mut writer);
assert_eq!(parser.state(), InputState::DcsHandler);
assert!(!writer.has_call("dcs_passthrough"));
assert!(writer.chars.is_empty());
parser.parse(b"\x1b\\", &mut writer);
assert_eq!(parser.state(), InputState::Ground);
assert!(writer.has_call("dcs_passthrough(\"not-osc\\u{7}AFTER\")"));
}
#[test]
fn dcs_sixel_uses_passthrough() {
let (_p, w) = parse(b"\x1bPq\"1;1;2;2#0!10~\x1b\\");
assert!(w.has_call("sixel_passthrough(\"q\\\"1;1;2;2#0!10~\")"));
}
#[test]
fn dcs_sixel_preserves_parameters() {
let (_p, w) = parse(b"\x1bP1;2q#0!10~\x1b\\");
assert!(w.has_call("sixel_passthrough(\"1;2q#0!10~\")"));
}
#[test]
fn dcs_decrqss_unrecognized() {
let (p, _w) = parse(b"\x1bP$qx\x1b\\");
let replies = String::from_utf8_lossy(&p.reply_buf);
assert!(replies.contains("\x1bP0$r\x1b\\"));
}
#[test]
fn dcs_decrqss_is_not_sixel() {
let (_p, w) = parse(b"\x1bP$qm\x1b\\");
assert!(!w.has_call("sixel_passthrough"));
}
#[test]
fn rep_repeats_last_printable_character() {
let (_p, w) = parse(b"X\x1b[3b");
let x_count = w.chars.iter().filter(|&&c| c == 'X').count();
assert_eq!(x_count, 4); }
#[test]
fn rep_does_nothing_without_prior_print() {
let (_p, w) = parse(b"\x1b[3b");
assert!(w.chars.is_empty());
}
#[test]
fn rep_reports_recovery_rebase_when_it_uses_a_prior_feed_character() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"X", &mut writer);
assert!(!parser.take_recovery_rebase_required());
parser.parse(b"\x1b[3b", &mut writer);
assert!(parser.take_recovery_rebase_required());
assert!(!parser.take_recovery_rebase_required());
}
#[test]
fn rep_after_a_character_in_the_same_feed_is_self_contained() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"X\x1b[3b", &mut writer);
assert!(!parser.take_recovery_rebase_required());
}
#[test]
fn fragmented_rep_reports_recovery_only_when_the_final_byte_dispatches() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\xe7\x95\x8c\x1b[", &mut writer);
assert!(!parser.take_recovery_rebase_required());
parser.parse(b"2", &mut writer);
assert!(!parser.take_recovery_rebase_required());
parser.parse(b"b", &mut writer);
assert!(parser.take_recovery_rebase_required());
assert_eq!(
writer
.chars
.iter()
.filter(|&&character| character == '界')
.count(),
3
);
}
#[test]
fn c0_clears_input_last_so_rep_fails() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"A\x07\x1b[1b", &mut writer);
let a_count = writer.chars.iter().filter(|&&c| c == 'A').count();
assert_eq!(a_count, 1);
}
#[test]
fn discard_on_intermediate_overflow() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b[", &mut writer);
parser.parse(b" ", &mut writer); parser.parse(b" ", &mut writer); parser.parse(b" ", &mut writer); parser.parse(b" ", &mut writer); parser.parse(b"m", &mut writer); assert!(!writer.has_call("mode_set"));
}
#[test]
fn winops_size_report_18() {
let (p, _w) = parse(b"\x1b[18t");
let replies = String::from_utf8_lossy(&p.reply_buf);
assert_eq!(replies.as_ref(), "\x1b[8;24;80t");
}
#[test]
fn winops_title_push_pop() {
let (_p, w) = parse(b"\x1b[22;0t");
assert!(w.has_call("push_title()"));
let (_p, w) = parse(b"\x1b[23;0t");
assert!(w.has_call("pop_title()"));
}
#[test]
fn all_17_states_exist() {
let states = [
InputState::Ground,
InputState::EscEnter,
InputState::EscIntermediate,
InputState::CsiEnter,
InputState::CsiParameter,
InputState::CsiIntermediate,
InputState::CsiIgnore,
InputState::DcsEnter,
InputState::DcsParameter,
InputState::DcsIntermediate,
InputState::DcsHandler,
InputState::DcsEscape,
InputState::DcsIgnore,
InputState::OscString,
InputState::ApcString,
InputState::RenameString,
InputState::ConsumeSt,
];
assert_eq!(states.len(), 17);
for state in states {
assert!(
!state.transition_table().is_empty(),
"state {state:?} has empty transition table"
);
}
}
#[test]
fn osc_10_unknown_fg_query_is_silent_with_st() {
let (p, _w) = parse(b"\x1b]10;?\x1b\\");
assert!(p.reply_buf.is_empty());
}
#[test]
fn osc_11_unknown_bg_query_is_silent_with_bel() {
let (p, _w) = parse(b"\x1b]11;?\x07");
assert!(p.reply_buf.is_empty());
}
#[test]
fn osc_12_unknown_cursor_query_is_silent() {
let (p, _w) = parse(b"\x1b]12;?\x1b\\");
assert!(p.reply_buf.is_empty());
}
#[test]
fn osc_11_set_then_query_round_trips_product_divergence() {
let (p, _w) = parse(b"\x1b]11;rgb:1111/2222/3333\x1b\\\x1b]11;?\x1b\\");
let replies = String::from_utf8_lossy(&p.reply_buf);
assert_eq!(replies.as_ref(), "\x1b]11;rgb:1111/2222/3333\x1b\\");
}
#[test]
fn osc_111_reset_makes_background_unknown_again() {
let (p, _w) = parse(b"\x1b]11;rgb:1111/2222/3333\x1b\\\x1b]111\x1b\\\x1b]11;?\x1b\\");
assert!(p.reply_buf.is_empty());
}
#[test]
fn osc_10_set_still_reaches_writer() {
let (_p, w) = parse(b"\x1b]10;rgb:eeee/eeee/eeee\x1b\\");
assert!(w.has_call("osc_fg_colour("));
}
#[test]
fn osc_11_oversized_set_is_not_stored_so_queries_cannot_amplify() {
let mut payload = Vec::new();
payload.extend_from_slice(b"\x1b]11;rgb:");
payload.extend(std::iter::repeat_n(b'A', 1_000_000));
payload.extend_from_slice(b"\x1b\\");
for _ in 0..100 {
payload.extend_from_slice(b"\x1b]11;?\x07");
}
let (p, _w) = parse(&payload);
assert!(
p.reply_buf.len() < 100_000,
"reply buffer amplified to {} bytes",
p.reply_buf.len()
);
assert!(p.reply_buf.is_empty());
}