#[derive(Debug, Default)]
pub struct AiSummaryState {
pub summary: Option<String>,
pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
pub diff_sha: Option<String>,
pub unread: bool,
pub show_panel: bool,
pub scroll: usize,
}
impl AiSummaryState {
pub fn set(&mut self, markdown: String, diff_sha: Option<String>) {
self.summary = Some(markdown);
self.updated_at = Some(chrono::Utc::now());
self.diff_sha = diff_sha;
self.unread = !self.show_panel;
self.scroll = 0;
}
pub fn clear(&mut self) {
self.summary = None;
self.updated_at = None;
self.diff_sha = None;
self.unread = false;
self.scroll = 0;
self.show_panel = false;
}
pub fn toggle_panel(&mut self) {
self.show_panel = !self.show_panel;
if self.show_panel {
self.unread = false;
}
}
pub fn scroll_down(&mut self, lines: usize) {
self.scroll = self.scroll.saturating_add(lines);
}
pub fn scroll_up(&mut self, lines: usize) {
self.scroll = self.scroll.saturating_sub(lines);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_stamps_timestamp_and_sets_unread_when_panel_closed() {
let mut s = AiSummaryState::default();
s.set("hello".into(), Some("sha123".into()));
assert_eq!(s.summary.as_deref(), Some("hello"));
assert!(s.updated_at.is_some());
assert_eq!(s.diff_sha.as_deref(), Some("sha123"));
assert!(s.unread, "panel was closed → should mark unread");
assert_eq!(s.scroll, 0);
}
#[test]
fn set_leaves_unread_false_when_panel_is_open() {
let mut s = AiSummaryState {
show_panel: true,
..Default::default()
};
s.set("hi".into(), None);
assert!(!s.unread, "panel was open → no new unread badge");
}
#[test]
fn clear_resets_every_field() {
let mut s = AiSummaryState::default();
s.set("x".into(), Some("sha".into()));
s.show_panel = true;
s.scroll = 42;
s.clear();
assert!(s.summary.is_none());
assert!(s.updated_at.is_none());
assert!(s.diff_sha.is_none());
assert!(!s.unread);
assert!(!s.show_panel);
assert_eq!(s.scroll, 0);
}
#[test]
fn toggle_panel_clears_unread_on_open() {
let mut s = AiSummaryState {
unread: true,
show_panel: false,
..Default::default()
};
s.toggle_panel();
assert!(s.show_panel);
assert!(!s.unread, "opening the panel must clear unread");
s.toggle_panel();
assert!(!s.show_panel);
}
#[test]
fn scroll_is_saturating() {
let mut s = AiSummaryState::default();
s.scroll_up(5); assert_eq!(s.scroll, 0);
s.scroll_down(3);
assert_eq!(s.scroll, 3);
s.scroll_down(usize::MAX); assert_eq!(s.scroll, usize::MAX);
}
}