use std::collections::HashMap;
use std::hash::Hash;
use tokio::sync::mpsc;
use crate::app::DiffCache;
use crate::syntax::ParserPool;
use crate::ui::diff_view::build_diff_cache;
pub const MAX_STORE_ENTRIES: usize = 50;
pub const MAX_PREFETCH_FILES: usize = 50;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ScrollMode {
Margin,
Edge,
}
pub struct DiffScrollState {
pub selected_line: usize,
pub scroll_offset: usize,
pub line_count: usize,
mode: ScrollMode,
visible_lines: usize,
}
impl DiffScrollState {
pub fn new(mode: ScrollMode) -> Self {
Self {
selected_line: 0,
scroll_offset: 0,
line_count: 0,
mode,
visible_lines: 0,
}
}
pub fn set_visible_lines(&mut self, visible_lines: usize) {
self.visible_lines = visible_lines;
}
pub fn reset(&mut self) {
self.selected_line = 0;
self.scroll_offset = 0;
}
pub fn set_line_count(&mut self, count: usize) {
self.line_count = count;
if count == 0 {
self.selected_line = 0;
self.scroll_offset = 0;
} else {
let max = count - 1;
if self.selected_line > max {
self.selected_line = max;
}
if self.scroll_offset > max {
self.scroll_offset = max;
}
}
}
pub fn adjust_scroll(&mut self, visible_lines: usize) {
match self.mode {
ScrollMode::Margin => self.adjust_scroll_margin(visible_lines),
ScrollMode::Edge => self.adjust_scroll_edge(visible_lines),
}
}
fn adjust_scroll_margin(&mut self, visible_lines: usize) {
if visible_lines == 0 {
return;
}
if self.line_count <= visible_lines {
self.scroll_offset = 0;
return;
}
let margin = visible_lines / 2;
if self.selected_line < self.scroll_offset + margin {
self.scroll_offset = self.selected_line.saturating_sub(margin);
}
if self.selected_line + margin >= self.scroll_offset + visible_lines {
self.scroll_offset = self
.selected_line
.saturating_sub(visible_lines.saturating_sub(margin + 1));
}
}
fn adjust_scroll_edge(&mut self, visible_lines: usize) {
if visible_lines == 0 {
return;
}
if self.selected_line < self.scroll_offset {
self.scroll_offset = self.selected_line;
}
if self.selected_line >= self.scroll_offset + visible_lines {
self.scroll_offset = self.selected_line.saturating_sub(visible_lines) + 1;
}
}
pub fn move_down(&mut self) {
if self.line_count > 0 {
self.selected_line = (self.selected_line + 1).min(self.line_count.saturating_sub(1));
}
self.adjust_scroll(self.visible_lines);
}
pub fn move_up(&mut self) {
self.selected_line = self.selected_line.saturating_sub(1);
self.adjust_scroll(self.visible_lines);
}
pub fn page_down(&mut self, step: usize) {
if self.line_count > 0 {
self.selected_line =
(self.selected_line + step).min(self.line_count.saturating_sub(1));
}
self.adjust_scroll(self.visible_lines);
}
pub fn page_up(&mut self, step: usize) {
self.selected_line = self.selected_line.saturating_sub(step);
self.adjust_scroll(self.visible_lines);
}
pub fn jump_to_first(&mut self) {
self.selected_line = 0;
self.scroll_offset = 0;
}
pub fn jump_to_last(&mut self) {
if self.line_count > 0 {
self.selected_line = self.line_count.saturating_sub(1);
}
self.adjust_scroll(self.visible_lines);
}
}
pub struct PrefetchItem<K> {
pub key: K,
pub file_index: usize,
pub filename: String,
pub patch: String,
}
pub struct DiffCacheSnapshot<K: Hash + Eq + Clone> {
pub current: Option<DiffCache>,
pub current_key: Option<K>,
pub store: HashMap<K, DiffCache>,
}
pub struct DiffCacheStore<K: Hash + Eq + Clone + Send + 'static> {
pub current: Option<DiffCache>,
current_key: Option<K>,
pub(crate) store: HashMap<K, DiffCache>,
max_store_entries: usize,
highlight_rx: Option<mpsc::Receiver<(K, DiffCache)>>,
prefetch_rx: Option<mpsc::Receiver<(K, DiffCache)>>,
}
impl<K: Hash + Eq + Clone + Send + 'static> DiffCacheStore<K> {
pub fn new(max_store_entries: usize) -> Self {
Self {
current: None,
current_key: None,
store: HashMap::new(),
max_store_entries,
highlight_rx: None,
prefetch_rx: None,
}
}
pub fn current_key(&self) -> Option<&K> {
self.current_key.as_ref()
}
pub fn try_restore(&mut self, key: &K, expected_hash: Option<u64>) -> bool {
if let Some(cached) = self.store.remove(key) {
if let Some(hash) = expected_hash {
if cached.patch_hash != hash {
return false;
}
}
self.retire_current();
self.current = Some(cached);
self.current_key = Some(key.clone());
true
} else {
false
}
}
pub fn set_current(&mut self, key: K, cache: DiffCache) {
self.retire_current();
self.current = Some(cache);
self.current_key = Some(key);
}
pub fn set_highlight_rx(&mut self, rx: mpsc::Receiver<(K, DiffCache)>) {
self.highlight_rx = Some(rx);
}
pub fn set_prefetch_rx(&mut self, rx: mpsc::Receiver<(K, DiffCache)>) {
self.prefetch_rx = Some(rx);
}
pub fn poll_highlight(&mut self) -> bool {
let Some(ref mut rx) = self.highlight_rx else {
return false;
};
match rx.try_recv() {
Ok((key, cache)) => {
self.highlight_rx = None;
let is_current = self.current_key.as_ref() == Some(&key);
let hash_matches = self
.current
.as_ref()
.map(|c| c.patch_hash == cache.patch_hash)
.unwrap_or(false);
if is_current && hash_matches {
self.current = Some(cache);
true
} else {
if cache.highlighted && self.store.len() < self.max_store_entries {
self.store.insert(key, cache);
}
false
}
}
Err(mpsc::error::TryRecvError::Empty) => false,
Err(mpsc::error::TryRecvError::Disconnected) => {
self.highlight_rx = None;
false
}
}
}
pub fn poll_prefetch(&mut self, distance_fn: impl Fn(&K) -> usize) {
let Some(ref mut rx) = self.prefetch_rx else {
return;
};
loop {
match rx.try_recv() {
Ok((key, cache)) => {
if self
.current
.as_ref()
.is_some_and(|c| c.highlighted)
&& self.current_key.as_ref() == Some(&key)
{
continue;
}
if self.store.contains_key(&key) {
continue;
}
if self.store.len() >= self.max_store_entries {
let evict_key = self
.store
.keys()
.max_by_key(|k| distance_fn(k))
.cloned();
if let Some(k) = evict_key {
self.store.remove(&k);
}
}
self.store.insert(key, cache);
}
Err(mpsc::error::TryRecvError::Empty) => break,
Err(mpsc::error::TryRecvError::Disconnected) => {
self.prefetch_rx = None;
break;
}
}
}
}
pub fn clear(&mut self) {
self.current = None;
self.current_key = None;
self.store.clear();
self.highlight_rx = None;
self.prefetch_rx = None;
}
pub fn clear_store_only(&mut self) {
self.store.clear();
self.prefetch_rx = None;
}
pub fn clear_current(&mut self) {
self.current = None;
self.current_key = None;
self.highlight_rx = None;
}
pub fn drop_prefetch_rx(&mut self) {
self.prefetch_rx = None;
}
pub fn start_prefetch(
&mut self,
items: Vec<PrefetchItem<K>>,
theme: &str,
markdown_rich: bool,
tab_width: u8,
) {
if items.is_empty() {
return;
}
let channel_size = items.len();
let (tx, rx) = mpsc::channel(channel_size);
self.prefetch_rx = Some(rx);
let theme = theme.to_string();
tokio::task::spawn_blocking(move || {
let mut parser_pool = ParserPool::new();
for item in items {
let mut cache = build_diff_cache(
&item.patch,
&item.filename,
&theme,
&mut parser_pool,
markdown_rich,
tab_width,
);
cache.file_index = item.file_index;
if tx.blocking_send((item.key, cache)).is_err() {
break;
}
}
});
}
pub fn invalidate_if(&mut self, pred: impl Fn(&K, &DiffCache) -> bool) {
self.store.retain(|k, v| !pred(k, v));
}
pub fn store_len(&self) -> usize {
self.store.len()
}
pub fn store_contains_key(&self, key: &K) -> bool {
self.store.contains_key(key)
}
pub fn has_prefetch_rx(&self) -> bool {
self.prefetch_rx.is_some()
}
pub fn has_highlight_rx(&self) -> bool {
self.highlight_rx.is_some()
}
pub fn take_snapshot(&mut self) -> DiffCacheSnapshot<K> {
self.highlight_rx = None;
self.prefetch_rx = None;
DiffCacheSnapshot {
current: self.current.take(),
current_key: self.current_key.take(),
store: std::mem::take(&mut self.store),
}
}
pub fn restore_snapshot(&mut self, snapshot: DiffCacheSnapshot<K>) {
self.current = snapshot.current;
self.current_key = snapshot.current_key;
self.store = snapshot.store;
self.highlight_rx = None;
self.prefetch_rx = None;
}
fn retire_current(&mut self) {
if let Some(old) = self.current.take() {
if old.highlighted {
if let Some(key) = self.current_key.take() {
if self.store.len() < self.max_store_entries {
self.store.insert(key, old);
}
}
}
}
self.current_key = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use lasso::Rodeo;
#[test]
fn test_margin_above_viewport() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 2;
s.scroll_offset = 5;
s.line_count = 100;
s.adjust_scroll(20);
assert_eq!(s.scroll_offset, 0);
}
#[test]
fn test_margin_below_viewport() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 30;
s.scroll_offset = 5;
s.line_count = 100;
s.adjust_scroll(20);
assert!(s.scroll_offset > 5);
assert!(s.selected_line >= s.scroll_offset);
assert!(s.selected_line < s.scroll_offset + 20);
}
#[test]
fn test_margin_within_viewport() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 15;
s.scroll_offset = 5;
s.line_count = 100;
s.adjust_scroll(20);
assert!(s.selected_line >= s.scroll_offset);
assert!(s.selected_line < s.scroll_offset + 20);
}
#[test]
fn test_margin_zero_visible() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 10;
s.scroll_offset = 5;
s.line_count = 100;
s.adjust_scroll(0);
assert_eq!(s.scroll_offset, 5);
}
#[test]
fn test_margin_at_last_line() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 50;
s.selected_line = 49;
s.scroll_offset = 0;
s.adjust_scroll(20);
assert!(s.selected_line >= s.scroll_offset);
assert!(s.selected_line < s.scroll_offset + 20);
assert_eq!(s.scroll_offset, 40);
}
#[test]
fn test_margin_single_line() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 1;
s.selected_line = 0;
s.scroll_offset = 0;
s.adjust_scroll(20);
assert_eq!(s.scroll_offset, 0);
assert_eq!(s.selected_line, 0);
}
#[test]
fn test_margin_invariant_all_positions() {
for line_count in [1usize, 5, 10, 20, 50, 100] {
for visible_lines in [1usize, 5, 10, 20, 40] {
for selected_line in 0..line_count {
for initial_scroll in [
0,
selected_line.saturating_sub(visible_lines),
selected_line,
] {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = line_count;
s.selected_line = selected_line;
s.scroll_offset = initial_scroll;
s.adjust_scroll(visible_lines);
assert!(
s.selected_line >= s.scroll_offset,
"cursor above: sel={}, scroll={}, vis={}, count={}, init={}",
s.selected_line, s.scroll_offset, visible_lines, line_count, initial_scroll,
);
assert!(
s.selected_line < s.scroll_offset + visible_lines,
"cursor below: sel={}, scroll={}, vis={}, count={}, init={}",
s.selected_line, s.scroll_offset, visible_lines, line_count, initial_scroll,
);
}
}
}
}
}
#[test]
fn test_margin_sequential_down_no_jump() {
let line_count = 100;
let visible_lines = 20;
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = line_count;
let mut prev_scroll = 0;
for line in 0..line_count {
s.selected_line = line;
s.adjust_scroll(visible_lines);
assert!(
s.selected_line >= s.scroll_offset
&& s.selected_line < s.scroll_offset + visible_lines,
);
assert!(
s.scroll_offset <= prev_scroll + 1,
"scroll jumped at line={}: prev={}, now={}",
line, prev_scroll, s.scroll_offset,
);
prev_scroll = s.scroll_offset;
}
}
#[test]
fn test_margin_sequential_up_no_jump() {
let line_count = 100;
let visible_lines = 20;
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = line_count;
s.selected_line = line_count - 1;
s.scroll_offset = line_count.saturating_sub(visible_lines);
s.adjust_scroll(visible_lines);
let mut prev_scroll = s.scroll_offset;
for line in (0..line_count).rev() {
s.selected_line = line;
s.adjust_scroll(visible_lines);
assert!(
s.selected_line >= s.scroll_offset
&& s.selected_line < s.scroll_offset + visible_lines,
);
assert!(
prev_scroll <= s.scroll_offset + 1,
"scroll jumped at line={}: prev={}, now={}",
line, prev_scroll, s.scroll_offset,
);
prev_scroll = s.scroll_offset;
}
}
#[test]
fn test_margin_file_shorter_than_viewport() {
let visible_lines = 40;
for line_count in [1, 5, 10, 39] {
for line in 0..line_count {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = line_count;
s.selected_line = line;
s.adjust_scroll(visible_lines);
assert_eq!(s.scroll_offset, 0);
}
}
}
#[test]
fn test_edge_scroll_down() {
let mut s = DiffScrollState::new(ScrollMode::Edge);
s.selected_line = 30;
s.scroll_offset = 0;
s.line_count = 100;
s.adjust_scroll(20);
assert_eq!(s.scroll_offset, 11); }
#[test]
fn test_edge_scroll_up() {
let mut s = DiffScrollState::new(ScrollMode::Edge);
s.selected_line = 5;
s.scroll_offset = 10;
s.line_count = 100;
s.adjust_scroll(20);
assert_eq!(s.scroll_offset, 5);
}
#[test]
fn test_edge_zero_visible() {
let mut s = DiffScrollState::new(ScrollMode::Edge);
s.selected_line = 10;
s.scroll_offset = 5;
s.line_count = 100;
s.adjust_scroll(0);
assert_eq!(s.scroll_offset, 5);
}
#[test]
fn test_edge_within_viewport() {
let mut s = DiffScrollState::new(ScrollMode::Edge);
s.selected_line = 15;
s.scroll_offset = 10;
s.line_count = 100;
s.adjust_scroll(20);
assert_eq!(s.scroll_offset, 10);
}
#[test]
fn test_set_line_count_clamp() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 50;
s.scroll_offset = 40;
s.set_line_count(30);
assert_eq!(s.selected_line, 29);
assert_eq!(s.scroll_offset, 29);
}
#[test]
fn test_set_line_count_zero() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 10;
s.scroll_offset = 5;
s.set_line_count(0);
assert_eq!(s.selected_line, 0);
assert_eq!(s.scroll_offset, 0);
}
#[test]
fn test_set_line_count_no_clamp_needed() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.selected_line = 5;
s.scroll_offset = 3;
s.set_line_count(100);
assert_eq!(s.selected_line, 5);
assert_eq!(s.scroll_offset, 3);
}
#[test]
fn test_move_down_boundary() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 5;
s.selected_line = 4;
s.move_down();
assert_eq!(s.selected_line, 4);
s.selected_line = 3;
s.move_down();
assert_eq!(s.selected_line, 4);
}
#[test]
fn test_move_down_zero_lines() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 0;
s.move_down();
assert_eq!(s.selected_line, 0);
}
#[test]
fn test_move_up_boundary() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 5;
s.selected_line = 0;
s.move_up();
assert_eq!(s.selected_line, 0);
s.selected_line = 3;
s.move_up();
assert_eq!(s.selected_line, 2);
}
#[test]
fn test_page_down() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 100;
s.selected_line = 10;
s.page_down(20);
assert_eq!(s.selected_line, 30);
s.selected_line = 90;
s.page_down(20);
assert_eq!(s.selected_line, 99);
}
#[test]
fn test_page_up() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 100;
s.selected_line = 30;
s.page_up(20);
assert_eq!(s.selected_line, 10);
s.selected_line = 5;
s.page_up(20);
assert_eq!(s.selected_line, 0);
}
#[test]
fn test_jump_to_first() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 100;
s.selected_line = 50;
s.scroll_offset = 40;
s.jump_to_first();
assert_eq!(s.selected_line, 0);
assert_eq!(s.scroll_offset, 0);
}
#[test]
fn test_jump_to_last() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 100;
s.selected_line = 0;
s.jump_to_last();
assert_eq!(s.selected_line, 99);
}
#[test]
fn test_jump_to_last_empty() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 0;
s.jump_to_last();
assert_eq!(s.selected_line, 0);
}
#[test]
fn test_reset() {
let mut s = DiffScrollState::new(ScrollMode::Margin);
s.line_count = 100;
s.selected_line = 50;
s.scroll_offset = 30;
s.reset();
assert_eq!(s.selected_line, 0);
assert_eq!(s.scroll_offset, 0);
assert_eq!(s.line_count, 100); }
fn make_cache(highlighted: bool, patch_hash: u64) -> DiffCache {
DiffCache {
file_index: 0,
patch_hash,
lines: vec![],
interner: Rodeo::default(),
highlighted,
markdown_rich: false,
}
}
#[test]
fn test_set_current_retires_highlighted() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(true, 100));
store.set_current(1, make_cache(false, 200));
assert!(store.store_contains_key(&0));
assert_eq!(store.current_key(), Some(&1));
}
#[test]
fn test_set_current_discards_plain() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(false, 100)); store.set_current(1, make_cache(false, 200));
assert!(!store.store_contains_key(&0));
}
#[test]
fn test_try_restore_success() {
let mut store = DiffCacheStore::<usize>::new(50);
store.store.insert(5, make_cache(true, 500));
assert!(store.try_restore(&5, None));
assert_eq!(store.current_key(), Some(&5));
assert!(store.current.as_ref().unwrap().patch_hash == 500);
assert!(!store.store_contains_key(&5));
}
#[test]
fn test_try_restore_with_hash_match() {
let mut store = DiffCacheStore::<usize>::new(50);
store.store.insert(5, make_cache(true, 500));
assert!(store.try_restore(&5, Some(500)));
assert_eq!(store.current_key(), Some(&5));
}
#[test]
fn test_try_restore_with_hash_mismatch() {
let mut store = DiffCacheStore::<usize>::new(50);
store.store.insert(5, make_cache(true, 500));
assert!(!store.try_restore(&5, Some(999)));
assert!(!store.store_contains_key(&5));
assert!(store.current.is_none());
}
#[test]
fn test_try_restore_not_found() {
let mut store = DiffCacheStore::<usize>::new(50);
assert!(!store.try_restore(&5, None));
}
#[test]
fn test_try_restore_retires_current() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(true, 100));
store.store.insert(5, make_cache(true, 500));
store.try_restore(&5, None);
assert!(store.store_contains_key(&0));
assert_eq!(store.current_key(), Some(&5));
}
#[test]
fn test_poll_highlight_updates_current() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(3, make_cache(false, 300));
let (tx, rx) = mpsc::channel(1);
store.set_highlight_rx(rx);
tx.try_send((3, make_cache(true, 300))).unwrap();
assert!(store.poll_highlight());
assert!(store.current.as_ref().unwrap().highlighted);
}
#[test]
fn test_poll_highlight_stale_key() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(3, make_cache(false, 300));
let (tx, rx) = mpsc::channel(1);
store.set_highlight_rx(rx);
tx.try_send((99, make_cache(true, 300))).unwrap();
assert!(!store.poll_highlight());
assert!(store.store_contains_key(&99));
}
#[test]
fn test_poll_highlight_stale_hash() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(3, make_cache(false, 300));
let (tx, rx) = mpsc::channel(1);
store.set_highlight_rx(rx);
tx.try_send((3, make_cache(true, 999))).unwrap();
assert!(!store.poll_highlight());
}
#[test]
fn test_poll_prefetch_with_eviction() {
let mut store = DiffCacheStore::<usize>::new(3);
store.store.insert(0, make_cache(true, 100));
store.store.insert(1, make_cache(true, 200));
store.store.insert(2, make_cache(true, 300));
let (tx, rx) = mpsc::channel(1);
store.set_prefetch_rx(rx);
tx.try_send((5, make_cache(true, 500))).unwrap();
drop(tx);
store.poll_prefetch(|k| k.abs_diff(5));
assert!(store.store_contains_key(&5));
assert_eq!(store.store_len(), 3); assert!(!store.store_contains_key(&0));
}
#[test]
fn test_poll_prefetch_skips_current() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(3, make_cache(true, 300));
let (tx, rx) = mpsc::channel(1);
store.set_prefetch_rx(rx);
tx.try_send((3, make_cache(true, 300))).unwrap();
drop(tx);
store.poll_prefetch(|k| *k);
assert!(!store.store_contains_key(&3));
}
#[test]
fn test_poll_prefetch_skips_existing() {
let mut store = DiffCacheStore::<usize>::new(50);
store.store.insert(3, make_cache(true, 300));
let (tx, rx) = mpsc::channel(1);
store.set_prefetch_rx(rx);
tx.try_send((3, make_cache(true, 999))).unwrap();
drop(tx);
store.poll_prefetch(|k| *k);
assert_eq!(store.store.get(&3).unwrap().patch_hash, 300);
}
#[test]
fn test_invalidate_if() {
let mut store = DiffCacheStore::<usize>::new(50);
store.store.insert(0, make_cache(true, 100));
store.store.insert(1, {
let mut c = make_cache(true, 200);
c.markdown_rich = true;
c
});
store.store.insert(2, make_cache(true, 300));
store.invalidate_if(|_, c| c.markdown_rich);
assert_eq!(store.store_len(), 2);
assert!(!store.store_contains_key(&1));
}
#[test]
fn test_clear() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(true, 100));
store.store.insert(1, make_cache(true, 200));
store.clear();
assert!(store.current.is_none());
assert!(store.current_key.is_none());
assert_eq!(store.store_len(), 0);
}
#[test]
fn test_clear_current() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(true, 100));
store.store.insert(1, make_cache(true, 200));
store.clear_current();
assert!(store.current.is_none());
assert!(store.current_key.is_none());
assert!(store.store_contains_key(&1));
}
#[test]
fn test_clear_store_only() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(0, make_cache(true, 100));
store.store.insert(1, make_cache(true, 200));
store.store.insert(2, make_cache(true, 300));
store.clear_store_only();
assert!(store.current.is_some());
assert_eq!(store.current_key, Some(0));
assert_eq!(store.store_len(), 0);
assert!(!store.store_contains_key(&1));
assert!(!store.store_contains_key(&2));
}
#[test]
fn test_take_snapshot_and_restore() {
let mut store = DiffCacheStore::<usize>::new(50);
store.set_current(3, make_cache(true, 300));
store.store.insert(5, make_cache(true, 500));
let snapshot = store.take_snapshot();
assert!(store.current.is_none());
assert_eq!(store.store_len(), 0);
assert!(snapshot.current.is_some());
assert_eq!(snapshot.current_key, Some(3));
assert!(snapshot.store.contains_key(&5));
store.restore_snapshot(snapshot);
assert_eq!(store.current_key(), Some(&3));
assert!(store.store_contains_key(&5));
}
#[test]
fn test_store_retirement_limit() {
let mut store = DiffCacheStore::<usize>::new(2);
store.set_current(0, make_cache(true, 100));
store.set_current(1, make_cache(true, 200));
assert!(store.store_contains_key(&0));
store.set_current(2, make_cache(true, 300));
assert!(store.store_contains_key(&1));
assert_eq!(store.store_len(), 2);
store.set_current(3, make_cache(true, 400));
assert!(!store.store_contains_key(&2));
assert_eq!(store.store_len(), 2);
}
}