use super::*;
#[test]
fn modset_extended_keys_mode_2() {
let (_p, w) = parse(b"\x1b[>4;2m");
assert!(w.has_call("mode_clear(0x248000)")); assert!(w.has_call("mode_set(0x40000)")); }
#[test]
fn modoff_clears_extended_keys() {
let (_p, w) = parse(b"\x1b[>4n");
assert!(w.has_call("mode_clear(0x248000)")); }
#[test]
fn kitty_keyboard_requests_are_consumed_without_negotiating_support() {
for request in [
b"\x1b[=8u".as_slice(),
b"\x1b[=0u".as_slice(),
b"\x1b[=1;3u".as_slice(),
b"\x1b[>1u".as_slice(),
b"\x1b[>0u".as_slice(),
b"\x1b[>1;3u".as_slice(),
b"\x1b[<u".as_slice(),
b"\x1b[<2u".as_slice(),
b"\x1b[?u".as_slice(),
] {
let (parser, writer) = parse(request);
assert!(parser.reply_buf.is_empty(), "request {request:?}");
assert_eq!(writer.mode, MODE_CURSOR | MODE_WRAP, "request {request:?}");
assert!(!writer.has_call("mode_set("), "request {request:?}");
assert!(!writer.has_call("mode_clear("), "request {request:?}");
}
}
#[test]
fn kitty_keyboard_requests_preserve_xterm_extended_key_mode() {
let (_parser, writer) = parse(b"\x1b[>4;2m\x1b[=8u\x1b[>1u\x1b[<u\x1b[?u");
let mode_transitions = writer
.calls
.iter()
.filter(|call| call.starts_with("mode_set(") || call.starts_with("mode_clear("))
.map(String::as_str)
.collect::<Vec<_>>();
assert_eq!(writer.mode, MODE_CURSOR | MODE_WRAP | MODE_KEYS_EXTENDED_2);
assert_eq!(
mode_transitions,
["mode_clear(0x248000)", "mode_set(0x40000)"]
);
}
#[test]
fn ground_timer_active_in_non_ground_state() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
assert!(!parser.ground_timer_active());
parser.parse(b"\x1b]", &mut writer);
assert_eq!(parser.state(), InputState::OscString);
assert!(parser.ground_timer_active());
}
#[test]
fn ground_timer_expired_resets_to_ground() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b]2;incomplete", &mut writer);
assert_eq!(parser.state(), InputState::OscString);
parser.ground_timer_expired();
assert_eq!(parser.state(), InputState::Ground);
assert!(!parser.ground_timer_active());
}
#[test]
fn ground_timer_expired_clears_since_ground_buffer() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b]2;incomplete", &mut writer);
assert!(!parser.take_since_ground().is_empty());
parser.parse(b"\x1b]2;incomplete", &mut writer);
parser.ground_timer_expired();
assert_eq!(parser.state(), InputState::Ground);
assert!(parser.take_since_ground().is_empty());
}
#[test]
fn since_ground_is_bounded_for_unterminated_strings() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.set_input_buffer_limit(INPUT_BUF_START);
parser.parse(b"\x1b]", &mut writer);
let flood = vec![b'a'; INPUT_BUF_START * 4];
parser.parse(&flood, &mut writer);
assert_eq!(parser.state(), InputState::OscString);
assert_eq!(parser.take_since_ground().len(), INPUT_BUF_START);
}
#[test]
fn discard_on_parameter_overflow() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
let mut seq = b"\x1b[".to_vec();
seq.extend(std::iter::repeat_n(b'1', 70));
seq.push(b'A'); parser.parse(&seq, &mut writer);
assert!(!writer.has_call("cursor_up"));
}
#[test]
fn csi_ignore_state_absorbs_then_returns_to_ground() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b[1", &mut writer);
assert_eq!(parser.state(), InputState::CsiParameter);
parser.parse(b"<", &mut writer); assert_eq!(parser.state(), InputState::CsiIgnore);
parser.parse(b"12345", &mut writer);
assert_eq!(parser.state(), InputState::CsiIgnore);
parser.parse(b"A", &mut writer);
assert_eq!(parser.state(), InputState::Ground);
assert!(!writer.has_call("cursor_up"));
}
#[test]
fn utf8_truncated_sequence_emits_replacement_on_c0() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\xc3", &mut writer);
assert!(writer.chars.is_empty()); parser.parse(b"\x07", &mut writer); assert!(writer.chars.contains(&'\u{FFFD}'));
assert!(writer.has_call("bell()"));
}
#[test]
fn utf8_overlong_start_byte_emits_replacement() {
let (_, w) = parse(b"\xfe\xff");
for c in &w.chars {
assert_eq!(*c, '\u{FFFD}');
}
}
#[test]
fn utf8_invalid_continuation_restarts_sequence() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\xe2\xc3\xa9", &mut writer);
assert!(writer.chars.contains(&'\u{FFFD}'));
assert!(writer.chars.contains(&'é'));
}
#[test]
fn decrpm_focuson_query() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b[?1004h", &mut writer);
parser.parse(b"\x1b[?1004$p", &mut writer);
let replies = String::from_utf8_lossy(&parser.reply_buf);
assert_eq!(replies.as_ref(), "\x1b[?1004;1$y"); }
#[test]
fn decrpm_bracketpaste_not_set() {
let (p, _w) = parse(b"\x1b[?2004$p");
let replies = String::from_utf8_lossy(&p.reply_buf);
assert_eq!(replies.as_ref(), "\x1b[?2004;2$y"); }
#[test]
fn scp_rcp_saves_and_restores_cursor_and_cell_state() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b[1;3m", &mut writer); writer.cx = 10;
writer.cy = 5;
parser.parse(b"\x1b[s", &mut writer);
parser.parse(b"\x1b[0m", &mut writer);
writer.cx = 0;
writer.cy = 0;
parser.parse(b"\x1b[u", &mut writer);
assert!(parser.cell_state().attr() & GridAttr::BRIGHT != 0);
assert!(parser.cell_state().attr() & GridAttr::ITALICS != 0);
assert!(writer.has_call("cursor_move(10, 5, false)"));
}
#[test]
fn decsc_decrc_saves_charset_state() {
let mut parser = InputParser::new();
let mut writer = RecordingWriter::new(80, 24);
parser.parse(b"\x1b(0", &mut writer);
assert_eq!(parser.cell_state().g0set, 1);
parser.parse(b"\x1b7", &mut writer);
parser.parse(b"\x1b(B", &mut writer);
assert_eq!(parser.cell_state().g0set, 0);
parser.parse(b"\x1b8", &mut writer);
assert_eq!(parser.cell_state().g0set, 1);
}