use justerm_core::{Engine, Key, KeyAction, KeyEvent, Modifiers};
fn key_press(k: Key) -> KeyEvent {
KeyEvent {
key: k,
..Default::default()
}
}
#[test]
fn kitty_query_reports_zero_when_unset() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[?u"); assert_eq!(t.drain_replies(), b"\x1b[?0u");
}
#[test]
fn push_sets_current_flags_reported_by_query() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>5u"); t.feed(b"\x1b[?u");
assert_eq!(t.drain_replies(), b"\x1b[?5u");
}
#[test]
fn pop_restores_previous_flags() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>5u"); t.feed(b"\x1b[>9u"); t.feed(b"\x1b[<u"); t.feed(b"\x1b[?u");
assert_eq!(t.drain_replies(), b"\x1b[?5u");
}
#[test]
fn set_modes_modify_current_flags() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[=3;1u\x1b[?u"); assert_eq!(t.drain_replies(), b"\x1b[?3u");
t.feed(b"\x1b[=8;2u\x1b[?u"); assert_eq!(t.drain_replies(), b"\x1b[?11u");
t.feed(b"\x1b[=1;3u\x1b[?u"); assert_eq!(t.drain_replies(), b"\x1b[?10u");
}
#[test]
fn disambiguate_encodes_shift_enter_as_csi_u() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u"); let ev = KeyEvent {
key: Key::Enter,
mods: Modifiers::SHIFT,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[13;2u");
}
#[test]
fn report_events_encodes_release_as_csi_u() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>2u"); let ev = KeyEvent {
key: Key::Enter,
mods: Modifiers::empty(),
action: KeyAction::Release,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[13;1:3u");
}
#[test]
fn report_events_keeps_press_legacy() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>2u"); let ev = KeyEvent {
key: Key::Enter,
mods: Modifiers::empty(),
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\r");
}
#[test]
fn kitty_super_modifier_uses_kitty_bit_value() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u"); let ev = KeyEvent {
key: Key::Enter,
mods: Modifiers::SUPER,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[13;9u");
}
#[test]
fn disambiguate_escape_becomes_csi_u() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u"); assert_eq!(t.encode_key(key_press(Key::Escape)).unwrap(), b"\x1b[27u");
}
#[test]
fn disambiguate_keeps_enter_tab_backspace_legacy() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u");
assert_eq!(t.encode_key(key_press(Key::Enter)).unwrap(), b"\r");
assert_eq!(t.encode_key(key_press(Key::Tab)).unwrap(), b"\t");
assert_eq!(t.encode_key(key_press(Key::Backspace)).unwrap(), vec![0x7f]);
}
#[test]
fn functional_key_with_event_uses_legacy_form_plus_params() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>2u"); let ev = KeyEvent {
key: Key::Up,
mods: Modifiers::empty(),
action: KeyAction::Release,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[1;1:3A");
}
#[test]
fn functional_key_unmodified_press_stays_legacy() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u"); assert_eq!(t.encode_key(key_press(Key::Up)).unwrap(), b"\x1b[A");
assert_eq!(t.encode_key(key_press(Key::Delete)).unwrap(), b"\x1b[3~");
}
#[test]
fn functional_key_super_modifier_kitty_form() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u");
let ev = KeyEvent {
key: Key::Delete,
mods: Modifiers::SUPER,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[3;9~");
}
#[test]
fn all_as_escape_encodes_char_as_csi_u() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>8u"); let ev = KeyEvent {
key: Key::Char('a'),
mods: Modifiers::empty(),
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[97u");
}
#[test]
fn all_as_escape_covers_enter_tab_backspace() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>8u"); assert_eq!(t.encode_key(key_press(Key::Enter)).unwrap(), b"\x1b[13u");
assert_eq!(t.encode_key(key_press(Key::Tab)).unwrap(), b"\x1b[9u");
assert_eq!(
t.encode_key(key_press(Key::Backspace)).unwrap(),
b"\x1b[127u"
);
}
#[test]
fn report_events_encodes_repeat() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>2u"); let ev = KeyEvent {
key: Key::Enter,
action: KeyAction::Repeat,
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[13;1:2u");
}
#[test]
fn char_without_all_as_escape_stays_legacy() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>1u"); assert_eq!(t.encode_key(key_press(Key::Char('a'))).unwrap(), b"a");
}
#[test]
fn alternate_keys_includes_shifted_codepoint() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>5u"); let ev = KeyEvent {
key: Key::Char('a'),
mods: Modifiers::SHIFT,
shifted_key: Some('A'),
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[97:65;2u");
}
#[test]
fn associated_text_appends_text_codepoint() {
let mut t = Engine::new(80, 24);
t.feed(b"\x1b[>24u"); let ev = KeyEvent {
key: Key::Char('a'),
text: Some('a'),
..Default::default()
};
assert_eq!(t.encode_key(ev).unwrap(), b"\x1b[97;1;97u");
}
#[test]
fn neovim_kitty_session_consumed_to_stable_state() {
let raw = include_bytes!("fixtures/neovim_kitty.raw");
let mut t = Engine::new(80, 24);
t.feed(raw);
t.feed(b"\x1b[?u");
let replies = t.drain_replies();
assert!(
replies.windows(5).any(|w| w == b"\x1b[?0u"),
"kitty flags should net to 0 after the session's push/pop"
);
assert_eq!(t.grid().cell(0, 0).c(), 'S'); }
#[test]
fn neovim_kitty_session_answers_da1_query() {
let raw = include_bytes!("fixtures/neovim_kitty.raw");
let mut t = Engine::new(80, 24);
t.feed(raw);
let replies = t.drain_replies();
assert!(
replies.windows(9).any(|w| w == b"\x1b[?62;22c"),
"DA1 query in the stream should have been answered"
);
}