use std::time::{Duration, Instant};
use crate::tui::render::notice::{
ERROR_TTL, NOTIFICATION_TTL, NoticeKind, notice_expired, notice_label, pick_notice,
};
const TTL: Duration = Duration::from_secs(2);
#[test]
fn untimestamped_notice_never_expires() {
assert!(!notice_expired(None, Instant::now(), TTL));
}
#[test]
fn fresh_notice_is_not_expired() {
let shown = Instant::now();
assert!(!notice_expired(
Some(shown),
shown + Duration::from_millis(1999),
TTL
));
}
#[test]
fn notice_expires_exactly_at_ttl() {
let shown = Instant::now();
assert!(notice_expired(Some(shown), shown + TTL, TTL));
assert!(notice_expired(
Some(shown),
shown + Duration::from_secs(30),
TTL
));
}
#[test]
fn clock_before_shown_at_is_not_expired() {
let shown = Instant::now() + Duration::from_secs(5);
assert!(!notice_expired(Some(shown), Instant::now(), TTL));
}
#[test]
fn error_ttl_is_longer_than_notification_ttl() {
assert_eq!(NOTIFICATION_TTL, Duration::from_secs(2));
assert_eq!(ERROR_TTL, Duration::from_millis(2500));
assert!(ERROR_TTL > NOTIFICATION_TTL);
}
#[test]
fn error_outranks_info_in_the_single_slot() {
assert_eq!(
pick_notice(Some("boom"), Some("Copied to clipboard")),
Some((NoticeKind::Error, "boom"))
);
}
#[test]
fn info_alone_is_shown_as_info() {
assert_eq!(
pick_notice(None, Some("Copied to clipboard")),
Some((NoticeKind::Info, "Copied to clipboard"))
);
}
#[test]
fn no_notice_means_no_title() {
assert_eq!(pick_notice(None, None), None);
}
#[test]
fn short_label_is_padded_with_one_space_each_side() {
assert_eq!(
notice_label("Copied to clipboard", 40).as_deref(),
Some(" Copied to clipboard ")
);
}
#[test]
fn label_that_exactly_fits_is_not_cut() {
assert_eq!(notice_label("abcde", 7).as_deref(), Some(" abcde "));
}
#[test]
fn long_label_is_cut_with_an_ellipsis_inside_the_slot() {
let label = notice_label("Press Esc again to clear input", 12).unwrap();
assert_eq!(label, " Press Esc… ");
assert_eq!(label.chars().count(), 12);
}
#[test]
fn multi_line_error_collapses_to_one_row() {
let error = "render panic at x.rs:1\n\n caller: y\tz ";
assert_eq!(
notice_label(error, 80).as_deref(),
Some(" render panic at x.rs:1 caller: y z ")
);
}
#[test]
fn label_counts_characters_not_bytes() {
assert_eq!(notice_label("éàçñü", 7).as_deref(), Some(" éàçñü "));
}
#[test]
fn slot_too_narrow_for_text_plus_ellipsis_yields_nothing() {
assert_eq!(notice_label("Copied to clipboard", 3), None);
assert_eq!(notice_label("Copied to clipboard", 0), None);
}
#[test]
fn whitespace_only_text_yields_nothing() {
assert_eq!(notice_label(" \n\t ", 40), None);
}
#[test]
fn chat_renderer_no_longer_reads_transient_notices() {
let chat = include_str!("../tui/render/chat.rs");
assert!(
!chat.contains("app.notification"),
"chat.rs reads app.notification again; the notice belongs on the input border"
);
assert!(
!chat.contains("app.error_message"),
"chat.rs reads app.error_message again; the notice belongs on the input border"
);
}
#[test]
fn input_renderer_owns_the_notice() {
let input = include_str!("../tui/render/input.rs");
assert!(input.contains("notice::pick_notice("));
assert!(input.contains("app.notification.as_deref()"));
assert!(input.contains("app.error_message.as_deref()"));
}
#[test]
fn notice_is_appended_to_the_cursor_row_not_a_border_title() {
let input = include_str!("../tui/render/input.rs");
let body = input
.split("pub(super) fn render_input(")
.nth(1)
.and_then(|rest| rest.split("\n}\n").next())
.expect("render_input body");
assert!(
body.contains("input_lines.get_mut(cursor_row)"),
"the notice must be appended to the cursor row of the input"
);
assert_eq!(
body.matches(".title_bottom(").count(),
1,
"only the ctx budget may sit on the bottom border"
);
assert!(
!body.contains("block.title_bottom(title)"),
"the notice must not be a border title"
);
}
#[test]
fn notice_label_fits_the_room_left_on_the_cursor_row() {
let room = 40usize.saturating_sub(3);
assert_eq!(
notice_label("Copied to clipboard", room).as_deref(),
Some(" Copied to clipboard ")
);
assert_eq!(notice_label("Copied to clipboard", 0), None);
}