use super::*;
use ratatui::backend::TestBackend;
use std::time::{Duration, Instant};
fn wait_until(mut cond: impl FnMut() -> bool) {
let start = Instant::now();
while !cond() && start.elapsed() < Duration::from_secs(5) {
std::thread::sleep(Duration::from_millis(2));
}
}
fn started_skim_with(options: SkimOptions, items: &[&str]) -> Skim<TestBackend> {
let (tx, rx) = crate::prelude::unbounded();
let batch: Vec<Arc<dyn SkimItem>> = items
.iter()
.map(|s| Arc::new(s.to_string()) as Arc<dyn SkimItem>)
.collect();
tx.send(batch).unwrap();
drop(tx);
let backend = TestBackend::new(40, 10);
let tui = Tui::new_with_height_and_backend(backend, Size::Percent(100)).unwrap();
let mut skim = Skim::<TestBackend>::init(options, Some(rx)).unwrap();
skim.init_tui_with(tui);
skim.start();
skim
}
fn started_skim(items: &[&str]) -> Skim<TestBackend> {
started_skim_with(SkimOptions::default().build(), items)
}
#[test]
fn accessors_expose_app_and_tui() {
let mut skim = started_skim(&["alpha", "beta"]);
assert!(!skim.app().should_quit);
skim.app_mut().input.value = "x".to_string();
assert_eq!(skim.app().input.value, "x");
let _ = skim.tui_ref();
let _ = skim.tui_mut();
let (_app, _tui) = skim.app_and_tui();
let _sender = skim.event_sender();
assert!(matches!(skim.final_event(), Event::Quit));
assert!(!skim.should_quit());
}
#[test]
fn should_enter_is_false_in_filter_mode() {
let mut options = SkimOptions::default();
options.filter = Some(String::new());
let options = options.build();
let mut skim = started_skim_with(options, &["a", "b", "c"]);
assert!(!skim.should_enter());
assert_eq!(skim.app().item_list.items.len(), 3);
}
#[test]
fn should_enter_is_false_for_select_1_single_match() {
let mut options = SkimOptions::default();
options.select_1 = true;
let options = options.build();
let mut skim = started_skim_with(options, &["only"]);
assert!(!skim.should_enter());
}
#[test]
fn should_enter_is_true_in_sync_mode_with_matches() {
let mut options = SkimOptions::default();
options.sync = true;
let options = options.build();
let mut skim = started_skim_with(options, &["a", "b"]);
assert!(skim.should_enter());
}
#[test]
fn should_enter_is_true_for_exit_0_with_matches() {
let mut options = SkimOptions::default();
options.exit_0 = true;
let options = options.build();
let mut skim = started_skim_with(options, &["a", "b"]);
assert!(skim.should_enter());
}
#[test]
fn output_collects_results_and_marks_abort() {
let mut skim = started_skim(&["a", "b"]);
wait_until(|| skim.check_reader());
wait_until(|| skim.matcher_stopped());
let output = skim.output();
assert!(output.is_abort);
assert_eq!(output.cmd, "");
}
#[test]
fn output_uses_input_as_cmd_in_interactive_mode() {
let mut options = SkimOptions::default();
options.interactive = true;
let options = options.build();
let mut skim = started_skim_with(options, &["a"]);
skim.app_mut().input.value = "typed".to_string();
wait_until(|| skim.check_reader());
let output = skim.output();
assert_eq!(output.cmd, "typed");
assert_eq!(output.query, "typed");
}
#[test]
fn output_uses_cmd_query_when_set() {
let mut options = SkimOptions::default();
options.cmd_query = Some("preset".to_string());
let options = options.build();
let mut skim = started_skim_with(options, &["a"]);
wait_until(|| skim.check_reader());
let output = skim.output();
assert_eq!(output.cmd, "preset");
}
#[test]
fn multi_selection_flows_into_output_and_serializes() {
use crate::Rank;
use crate::item::{MatchedItem, RankBuilder};
let mut options = SkimOptions::default();
options.multi = true;
let options = options.build();
let mut skim = started_skim_with(options, &["a", "b", "c"]);
wait_until(|| skim.check_reader());
wait_until(|| skim.matcher_stopped());
let rb = RankBuilder::default();
let mk = |t: &str| MatchedItem::new(Arc::new(t.to_string()) as Arc<dyn SkimItem>, Rank::default(), None, &rb);
skim.app_mut().item_list.append(&mut vec![mk("a"), mk("b"), mk("c")]);
skim.app_mut().item_list.toggle_at(0);
skim.app_mut().item_list.toggle_at(2);
let output = skim.output();
let texts: Vec<String> = output.selected_items.iter().map(|i| i.output().into_owned()).collect();
assert_eq!(texts, vec!["a", "c"]);
let mut print0 = SkimOptions::default();
print0.print0 = true;
let bin = crate::BinOptions::from_opts(&print0);
let mut buf = Vec::new();
output.write_output(&mut buf, &bin).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "a\0c\0");
}
#[test]
fn reader_and_matcher_complete() {
let mut skim = started_skim(&["one", "two", "three"]);
wait_until(|| skim.check_reader());
assert!(skim.reader_done());
wait_until(|| skim.matcher_stopped());
assert!(skim.matcher_stopped());
}
#[test]
fn try_flush_render_emits_render_when_due() {
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
let mut skim = started_skim(&["a"]);
skim.app.needs_render.store(true, Ordering::Relaxed);
let now = Instant::now();
skim.app.last_render_timer = now.checked_sub(Duration::from_secs(1)).unwrap_or(now);
skim.try_flush_render();
assert!(!skim.app.needs_render.load(Ordering::Relaxed));
let mut saw_render = false;
while let Ok(ev) = skim.tui.as_mut().unwrap().event_rx.try_recv() {
if matches!(ev, Event::Render) {
saw_render = true;
}
}
assert!(saw_render);
}
#[test]
fn try_flush_render_noop_when_not_needed() {
use std::sync::atomic::Ordering;
let mut skim = started_skim(&["a"]);
skim.app.needs_render.store(false, Ordering::Relaxed);
skim.try_flush_render();
assert!(!skim.app.needs_render.load(Ordering::Relaxed));
}