use super::*;
#[test]
fn cursor_style_default() {
let parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
assert_eq!(parser.screen().cursor_style(), CursorStyle::Default);
}
#[test]
fn cursor_style_all_values() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
let cases = [
(0, CursorStyle::Default),
(1, CursorStyle::BlinkingBlock),
(2, CursorStyle::SteadyBlock),
(3, CursorStyle::BlinkingUnderline),
(4, CursorStyle::SteadyUnderline),
(5, CursorStyle::BlinkingBar),
(6, CursorStyle::SteadyBar),
];
for (param, expected) in cases {
let seq = format!("\x1b[{param} q");
process(&mut parser, seq.as_bytes());
assert_eq!(
parser.screen().cursor_style(),
expected,
"DECSCUSR param {param} did not map to the documented cursor style",
);
}
}
#[test]
fn cursor_style_invalid_ignored() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[2 q");
assert_eq!(parser.screen().cursor_style(), CursorStyle::SteadyBlock);
process(&mut parser, b"\x1b[99 q");
assert_eq!(
parser.screen().cursor_style(),
CursorStyle::SteadyBlock,
"invalid DECSCUSR param 99 must leave the previous style untouched",
);
}
#[test]
fn cursor_style_reset_by_ris() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[5 q");
assert_eq!(parser.screen().cursor_style(), CursorStyle::BlinkingBar);
process(&mut parser, b"\x1bc");
assert_eq!(parser.screen().cursor_style(), CursorStyle::Default);
}
fn cursor_style_events(parser: &mut crate::Parser) -> Vec<CursorStyle> {
parser
.screen_mut()
.drain_events()
.into_iter()
.filter_map(|e| match e {
ScreenEvent::CursorStyleChanged(s) => Some(s),
_ => None,
})
.collect()
}
#[test]
fn cursor_style_changed_fires_on_decscusr() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
assert!(cursor_style_events(&mut parser).is_empty());
process(&mut parser, b"\x1b[5 q");
assert_eq!(
cursor_style_events(&mut parser),
vec![CursorStyle::BlinkingBar]
);
assert_eq!(parser.screen().cursor_style(), CursorStyle::BlinkingBar);
assert!(cursor_style_events(&mut parser).is_empty());
}
#[test]
fn cursor_style_changed_not_fired_for_same_style() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[2 q");
assert_eq!(
cursor_style_events(&mut parser),
vec![CursorStyle::SteadyBlock]
);
process(&mut parser, b"\x1b[2 q");
assert!(cursor_style_events(&mut parser).is_empty());
}
#[test]
fn cursor_style_changed_survives_intermediate_content() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[5 q");
assert_eq!(
cursor_style_events(&mut parser),
vec![CursorStyle::BlinkingBar]
);
process(&mut parser, b"hello");
assert!(cursor_style_events(&mut parser).is_empty());
process(&mut parser, b"\x1b[2 q");
assert_eq!(
cursor_style_events(&mut parser),
vec![CursorStyle::SteadyBlock]
);
assert_eq!(parser.screen().cursor_style(), CursorStyle::SteadyBlock);
}
#[test]
fn cursor_style_changed_emits_per_change_within_chunk() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[5 q\x1b[2 q");
assert_eq!(
cursor_style_events(&mut parser),
vec![CursorStyle::BlinkingBar, CursorStyle::SteadyBlock]
);
assert_eq!(parser.screen().cursor_style(), CursorStyle::SteadyBlock);
}
#[test]
fn cursor_style_changed_reset_by_ris() {
let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
process(&mut parser, b"\x1b[5 q");
let _ = cursor_style_events(&mut parser);
process(&mut parser, b"\x1bc");
assert_eq!(cursor_style_events(&mut parser), vec![CursorStyle::Default]);
assert_eq!(parser.screen().cursor_style(), CursorStyle::Default);
}
#[test]
fn cursor_style_shape_collapses_blink_pairs() {
assert_eq!(CursorStyle::Default.shape(), CursorShape::Default);
assert_eq!(
CursorStyle::BlinkingBlock.shape(),
CursorShape::Block,
"BlinkingBlock and SteadyBlock must share the Block shape",
);
assert_eq!(CursorStyle::SteadyBlock.shape(), CursorShape::Block);
assert_eq!(
CursorStyle::BlinkingUnderline.shape(),
CursorShape::Underline,
"BlinkingUnderline and SteadyUnderline must share the Underline shape",
);
assert_eq!(CursorStyle::SteadyUnderline.shape(), CursorShape::Underline);
assert_eq!(
CursorStyle::BlinkingBar.shape(),
CursorShape::Bar,
"BlinkingBar and SteadyBar must share the Bar shape",
);
assert_eq!(CursorStyle::SteadyBar.shape(), CursorShape::Bar);
}
#[test]
fn cursor_style_blink_only_for_blinking_variants() {
assert!(
!CursorStyle::Default.blink(),
"Default must not advertise blink"
);
assert!(
CursorStyle::BlinkingBlock.blink(),
"BlinkingBlock must report blink=true",
);
assert!(
!CursorStyle::SteadyBlock.blink(),
"SteadyBlock must report blink=false (steady is the no-blink half of the pair)",
);
assert!(CursorStyle::BlinkingUnderline.blink());
assert!(
!CursorStyle::SteadyUnderline.blink(),
"SteadyUnderline must report blink=false",
);
assert!(CursorStyle::BlinkingBar.blink());
assert!(
!CursorStyle::SteadyBar.blink(),
"SteadyBar must report blink=false",
);
}