#[cfg(test)]
mod tests {
use ratatui::{Terminal, backend::TestBackend};
use crate::config::{
AuthConfig, AzureConfig, Config, JiraSourceConfig, SourceConfig, SyncConfig,
};
use crate::tui::app::App;
use crate::tui::prefs::Prefs;
use crate::tui::widgets::source_table::SourceTable;
fn cfg() -> Config {
Config {
azure: AzureConfig {
endpoint: "x".into(),
api_key: "k".into(),
},
sources: vec![SourceConfig::Jira(JiraSourceConfig {
name: "my-jira".into(),
url: "x".into(),
auth: AuthConfig::DataCenter { pat: "p".into() },
projects: vec!["DO".into(), "HR".into()],
index: "i".into(),
})],
sync: SyncConfig::default(),
}
}
fn rendered_text(app: &App, w: u16, h: u16) -> String {
let backend = TestBackend::new(w, h);
let mut term = Terminal::new(backend).unwrap();
term.draw(|f| {
f.render_widget(SourceTable { app }, f.area());
})
.unwrap();
let buf = term.backend().buffer();
(0..buf.area.height)
.map(|y| {
(0..buf.area.width)
.map(|x| buf[(x, y)].symbol())
.collect::<String>()
})
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn renders_column_headings() {
let app = App::new(&cfg(), Prefs::default());
let text = rendered_text(&app, 100, 10);
assert!(text.contains("Source"), "missing Source heading:\n{text}");
assert!(text.contains("Status"), "missing Status heading");
assert!(text.contains("Items"), "missing Items heading");
assert!(text.contains("Rate"), "missing Rate heading");
assert!(text.contains("Last item"), "missing Last item heading");
assert!(text.contains("Updated"), "missing Updated heading");
}
#[test]
fn renders_source_row_and_expanded_subsources() {
let app = App::new(&cfg(), Prefs::default());
let text = rendered_text(&app, 100, 10);
assert!(text.contains("my-jira"));
assert!(text.contains("DO"));
assert!(text.contains("HR"));
}
#[test]
fn collapsed_source_hides_subsources() {
let mut app = App::new(&cfg(), Prefs::default());
app.prefs.toggle_source_collapsed("my-jira");
let text = rendered_text(&app, 100, 10);
assert!(text.contains("my-jira"));
assert!(!text.contains(" DO"));
assert!(!text.contains(" HR"));
}
use crate::tui::widgets::azure_panel::AzurePanelWidget;
#[test]
fn azure_panel_shows_plain_english_labels() {
let app = App::new(&cfg(), Prefs::default());
let mut term = Terminal::new(TestBackend::new(100, 12)).unwrap();
term.draw(|f| {
f.render_widget(
AzurePanelWidget {
panel: &app.azure,
drops: 0,
focused: false,
backoff_reason: None,
},
f.area(),
);
})
.unwrap();
let buf = term.backend().buffer();
let text: String = (0..buf.area.height)
.flat_map(|y| (0..buf.area.width).map(move |x| buf[(x, y)].symbol().to_string()))
.collect::<Vec<_>>()
.join("");
assert!(text.contains("Total requests"), "rendered: {text}");
assert!(text.contains("median"), "rendered: {text}");
assert!(text.contains("Failed (4xx)"));
assert!(text.contains("Failed (5xx)"));
assert!(text.contains("Throttled"));
}
use crate::tui::widgets::drilldown::Drilldown;
#[test]
fn drilldown_shows_subsource_details() {
let mut app = App::new(&cfg(), Prefs::default());
app.apply(crate::tui::events::QuelchEvent::SubsourceBatch {
source: "my-jira".into(),
subsource: "DO".into(),
fetched: 5,
cursor: chrono::Utc::now(),
sample_id: "DO-42".into(),
});
for i in 0..3 {
app.apply(crate::tui::events::QuelchEvent::DocSynced {
source: "my-jira".into(),
subsource: "DO".into(),
id: format!("DO-{i}"),
updated: chrono::Utc::now(),
});
}
app.move_selection_down();
let mut term = Terminal::new(TestBackend::new(60, 20)).unwrap();
term.draw(|f| {
f.render_widget(Drilldown { app: &app }, f.area());
})
.unwrap();
let buf = term.backend().buffer();
let text: String = (0..buf.area.height)
.flat_map(|y| (0..buf.area.width).map(move |x| buf[(x, y)].symbol().to_string()))
.collect::<Vec<_>>()
.join("");
assert!(text.contains("Docs synced"));
assert!(text.contains("Recent"));
assert!(text.contains("DO-2"));
}
use crate::tui::widgets::help_overlay::HelpOverlay;
#[test]
fn help_overlay_lists_key_bindings() {
let mut term = Terminal::new(TestBackend::new(70, 30)).unwrap();
term.draw(|f| {
f.render_widget(HelpOverlay {}, f.area());
})
.unwrap();
let buf = term.backend().buffer();
let text: String = (0..buf.area.height)
.flat_map(|y| (0..buf.area.width).map(move |x| buf[(x, y)].symbol().to_string()))
.collect::<Vec<_>>()
.join("");
assert!(text.contains("Keyboard shortcuts"));
assert!(text.contains("sync now"));
assert!(text.contains("pause"));
assert!(text.contains("quit"));
}
use crate::tui::app::LogLine;
use crate::tui::widgets::log_view::LogView;
use std::collections::VecDeque;
#[test]
fn log_view_renders_column_headings() {
let mut lines = VecDeque::new();
lines.push_back(LogLine {
ts: chrono::Utc::now(),
level: tracing::Level::INFO,
target: "quelch::sync".into(),
message: "Cycle starting".into(),
});
let view = LogView {
lines: &lines,
focused: false,
};
let mut term = Terminal::new(TestBackend::new(100, 10)).unwrap();
term.draw(|f| f.render_widget(view, f.area())).unwrap();
let buf = term.backend().buffer();
let text: String = (0..buf.area.height)
.flat_map(|y| (0..buf.area.width).map(move |x| buf[(x, y)].symbol().to_string()))
.collect::<Vec<_>>()
.join("");
assert!(text.contains("LEVEL"));
assert!(text.contains("TIME"));
assert!(text.contains("TARGET"));
assert!(text.contains("MESSAGE"));
assert!(text.contains("Cycle starting"));
}
}