refactor: move embedded tests into dedicated test directory
This commit is contained in:
+2
-275
@@ -295,278 +295,5 @@ fn hex_value(component: &[u8]) -> Option<u32> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crossterm::event::{KeyEvent, MouseEvent, MouseEventKind};
|
||||
|
||||
fn key(code: KeyCode, modifiers: KeyModifiers) -> Event {
|
||||
Event::Key(KeyEvent::new(code, modifiers))
|
||||
}
|
||||
|
||||
fn bytes(collector: &mut TerminalColors, text: &str, now: Instant) {
|
||||
for ch in text.chars() {
|
||||
let modifiers = if ch.is_ascii_uppercase() {
|
||||
KeyModifiers::SHIFT
|
||||
} else {
|
||||
KeyModifiers::NONE
|
||||
};
|
||||
collector.push(key(KeyCode::Char(ch), modifiers), now);
|
||||
}
|
||||
}
|
||||
|
||||
fn report(collector: &mut TerminalColors, body: &str, now: Instant) {
|
||||
collector.push(key(KeyCode::Char(']'), KeyModifiers::ALT), now);
|
||||
bytes(collector, body, now);
|
||||
collector.push(key(KeyCode::Char('g'), KeyModifiers::CONTROL), now);
|
||||
}
|
||||
|
||||
fn drain(collector: &mut TerminalColors) -> Vec<Event> {
|
||||
std::iter::from_fn(|| collector.pop()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragmented_triple_accepts_bel_and_both_st_encodings() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
collector.query(&mut Vec::new(), now).unwrap();
|
||||
collector.push(key(KeyCode::Esc, KeyModifiers::NONE), now);
|
||||
collector.push(
|
||||
key(KeyCode::Char(']'), KeyModifiers::NONE),
|
||||
now + Duration::from_millis(1),
|
||||
);
|
||||
bytes(
|
||||
&mut collector,
|
||||
"11;rgb:0/12/345",
|
||||
now + Duration::from_millis(2),
|
||||
);
|
||||
collector.push(
|
||||
key(KeyCode::Esc, KeyModifiers::NONE),
|
||||
now + Duration::from_millis(3),
|
||||
);
|
||||
collector.push(
|
||||
key(KeyCode::Char('\\'), KeyModifiers::NONE),
|
||||
now + Duration::from_millis(4),
|
||||
);
|
||||
assert_eq!(collector.colors, None);
|
||||
report(
|
||||
&mut collector,
|
||||
"4;2;rgb:f/ffff/ABCD",
|
||||
now + Duration::from_millis(5),
|
||||
);
|
||||
collector.push(
|
||||
key(KeyCode::Char(']'), KeyModifiers::ALT),
|
||||
now + Duration::from_millis(6),
|
||||
);
|
||||
bytes(
|
||||
&mut collector,
|
||||
"4;1;#FF1234",
|
||||
now + Duration::from_millis(7),
|
||||
);
|
||||
collector.push(
|
||||
key(KeyCode::Char('\\'), KeyModifiers::ALT),
|
||||
now + Duration::from_millis(8),
|
||||
);
|
||||
assert_eq!(
|
||||
collector.colors,
|
||||
Some(ThemeColors {
|
||||
background: [0, 18, 52],
|
||||
red: [255, 18, 52],
|
||||
green: [255, 255, 171],
|
||||
})
|
||||
);
|
||||
assert!(drain(&mut collector).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_refresh_atomically_replaces_light_and_dark_triples() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
let palettes = [
|
||||
("11;#FDF6E3", [253, 246, 227]),
|
||||
("11;rgb:0000/0000/0000", [0, 0, 0]),
|
||||
];
|
||||
for (cycle, (background, expected)) in palettes.into_iter().enumerate() {
|
||||
let time = now + Duration::from_secs(cycle as u64);
|
||||
let focus = Event::FocusGained;
|
||||
collector.push(focus.clone(), time);
|
||||
assert_eq!(collector.pop(), Some(focus));
|
||||
let previous = collector.colors;
|
||||
collector.query(&mut Vec::new(), time).unwrap();
|
||||
report(&mut collector, "4;1;rgb:ff/00/00", time);
|
||||
report(&mut collector, background, time);
|
||||
assert_eq!(collector.colors, previous);
|
||||
report(&mut collector, "4;2;rgb:00/ff/00", time);
|
||||
assert_eq!(
|
||||
collector.colors,
|
||||
Some(ThemeColors {
|
||||
background: expected,
|
||||
red: [255, 0, 0],
|
||||
green: [0, 255, 0],
|
||||
})
|
||||
);
|
||||
assert!(drain(&mut collector).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn periodic_refresh_detects_silent_change_without_overlapping_queries() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
let mut output = Vec::new();
|
||||
assert!(collector.refresh_due(now));
|
||||
collector.query(&mut output, now).unwrap();
|
||||
report(&mut collector, "11;#101010", now);
|
||||
report(&mut collector, "4;1;#FF0000", now);
|
||||
report(&mut collector, "4;2;#00FF00", now);
|
||||
let previous = collector.colors;
|
||||
assert!(previous.is_some());
|
||||
|
||||
// The terminal changes its palette without emitting a notification.
|
||||
let refresh = now + Duration::from_secs(1);
|
||||
assert!(!collector.refresh_due(refresh - Duration::from_nanos(1)));
|
||||
assert!(collector.refresh_due(refresh));
|
||||
collector.query(&mut output, refresh).unwrap();
|
||||
report(&mut collector, "11;#EEEEEE", refresh);
|
||||
report(&mut collector, "4;1;#AA1122", refresh);
|
||||
assert_eq!(collector.colors, previous);
|
||||
let typed = key(KeyCode::Char('i'), KeyModifiers::NONE);
|
||||
collector.push(typed.clone(), refresh);
|
||||
assert_eq!(drain(&mut collector), vec![typed]);
|
||||
|
||||
// Even an overdue probe must wait until the incomplete query expires.
|
||||
let retry = refresh + Duration::from_secs(1);
|
||||
assert!(!collector.refresh_due(retry));
|
||||
collector.expire(retry);
|
||||
assert_eq!(collector.colors, None);
|
||||
assert!(collector.refresh_due(retry));
|
||||
collector.query(&mut output, retry).unwrap();
|
||||
report(&mut collector, "11;#EEEEEE", retry);
|
||||
report(&mut collector, "4;1;#AA1122", retry);
|
||||
assert_eq!(collector.colors, None);
|
||||
report(&mut collector, "4;2;#22BB33", retry);
|
||||
assert_eq!(
|
||||
collector.colors,
|
||||
Some(ThemeColors {
|
||||
background: [238, 238, 238],
|
||||
red: [170, 17, 34],
|
||||
green: [34, 187, 51],
|
||||
})
|
||||
);
|
||||
assert!(drain(&mut collector).is_empty());
|
||||
assert!(!collector.refresh_due(retry + Duration::from_millis(999)));
|
||||
assert!(collector.refresh_due(retry + Duration::from_secs(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_refresh_clears_stale_colors_and_consumes_late_partial_reply() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
collector.query(&mut Vec::new(), now).unwrap();
|
||||
report(&mut collector, "11;#101010", now);
|
||||
report(&mut collector, "4;1;#FF0000", now);
|
||||
report(&mut collector, "4;2;#00FF00", now);
|
||||
assert!(collector.colors.is_some());
|
||||
let refresh = now + Duration::from_secs(1);
|
||||
collector.query(&mut Vec::new(), refresh).unwrap();
|
||||
report(&mut collector, "11;#EEEEEE", refresh);
|
||||
collector.push(key(KeyCode::Char(']'), KeyModifiers::ALT), refresh);
|
||||
bytes(&mut collector, "4;1;rgb:ffff/", refresh);
|
||||
collector.expire(refresh + QUERY_TIMEOUT);
|
||||
assert_eq!(collector.colors, None);
|
||||
let late = refresh + Duration::from_secs(1);
|
||||
bytes(&mut collector, "0000/0000", late);
|
||||
collector.push(key(KeyCode::Esc, KeyModifiers::NONE), late);
|
||||
collector.expire(late + Duration::from_secs(1));
|
||||
collector.push(
|
||||
key(KeyCode::Char('\\'), KeyModifiers::NONE),
|
||||
late + Duration::from_secs(1),
|
||||
);
|
||||
report(&mut collector, "4;2;#00FF00", late + Duration::from_secs(1));
|
||||
assert_eq!(collector.colors, None);
|
||||
assert!(drain(&mut collector).is_empty());
|
||||
let typed = key(KeyCode::Char('i'), KeyModifiers::NONE);
|
||||
collector.push(typed.clone(), late + Duration::from_secs(2));
|
||||
assert_eq!(collector.pop(), Some(typed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_events_and_rejected_candidates_replay_exactly() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
collector.query(&mut Vec::new(), now).unwrap();
|
||||
let events = vec![
|
||||
key(KeyCode::Char(']'), KeyModifiers::ALT),
|
||||
key(KeyCode::Char('4'), KeyModifiers::NONE),
|
||||
key(KeyCode::Char(';'), KeyModifiers::NONE),
|
||||
key(KeyCode::Char('3'), KeyModifiers::NONE),
|
||||
key(KeyCode::Char('g'), KeyModifiers::CONTROL),
|
||||
key(KeyCode::Char(']'), KeyModifiers::ALT | KeyModifiers::SHIFT),
|
||||
Event::Key(KeyEvent::new_with_kind(
|
||||
KeyCode::Char(']'),
|
||||
KeyModifiers::ALT,
|
||||
KeyEventKind::Release,
|
||||
)),
|
||||
Event::Key(KeyEvent::new_with_kind(
|
||||
KeyCode::Char('A'),
|
||||
KeyModifiers::SHIFT,
|
||||
KeyEventKind::Repeat,
|
||||
)),
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::CAPS_LOCK,
|
||||
}),
|
||||
Event::Mouse(MouseEvent {
|
||||
kind: MouseEventKind::Moved,
|
||||
column: 2,
|
||||
row: 3,
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Event::FocusLost,
|
||||
Event::Resize(80, 24),
|
||||
key(KeyCode::Char(']'), KeyModifiers::ALT),
|
||||
Event::Paste("\x1b]11;rgb:ff/ff/ff\x07".into()),
|
||||
Event::FocusGained,
|
||||
];
|
||||
for event in &events {
|
||||
collector.push(event.clone(), now);
|
||||
}
|
||||
assert_eq!(drain(&mut collector), events);
|
||||
assert_eq!(collector.colors, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_or_oversized_reports_and_lone_escape_do_not_eat_input() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
for body in [
|
||||
"11;rgb:12345/0/0",
|
||||
"11;rgb:1//3",
|
||||
"11;#12345",
|
||||
"11;rgb:1/2/3/4",
|
||||
] {
|
||||
let mut expected = vec![key(KeyCode::Char(']'), KeyModifiers::ALT)];
|
||||
expected.extend(
|
||||
body.chars()
|
||||
.map(|ch| key(KeyCode::Char(ch), KeyModifiers::NONE)),
|
||||
);
|
||||
expected.push(key(KeyCode::Char('g'), KeyModifiers::CONTROL));
|
||||
for event in &expected {
|
||||
collector.push(event.clone(), now);
|
||||
}
|
||||
assert_eq!(drain(&mut collector), expected);
|
||||
}
|
||||
let escape = key(KeyCode::Esc, KeyModifiers::NONE);
|
||||
collector.push(escape.clone(), now);
|
||||
assert_eq!(collector.pop(), None);
|
||||
collector.expire(now + ESCAPE_TIMEOUT);
|
||||
assert_eq!(collector.pop(), Some(escape));
|
||||
assert_eq!(collector.pop(), None);
|
||||
let start = key(KeyCode::Char(']'), KeyModifiers::ALT);
|
||||
collector.push(start.clone(), now + Duration::from_secs(1));
|
||||
collector.expire(now + Duration::from_secs(1) + QUERY_TIMEOUT);
|
||||
assert_eq!(collector.pop(), Some(start));
|
||||
assert_eq!(collector.pop(), None);
|
||||
}
|
||||
}
|
||||
#[path = "../tests/unit/terminal_colors.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user