use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use crate::console::{Console, ConsoleOptions, Renderable};
use crate::layout::{Layout, LayoutCache};
use crate::segment::Segment;
use crate::style::Style;
use crate::text::Text;
struct CountingRenderable {
call_count: Arc<AtomicUsize>,
fixed_hash: u64,
}
impl CountingRenderable {
fn new(call_count: Arc<AtomicUsize>, fixed_hash: u64) -> Self {
CountingRenderable {
call_count,
fixed_hash,
}
}
}
impl Renderable for CountingRenderable {
fn gilt_console(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
self.call_count.fetch_add(1, Ordering::SeqCst);
vec![Segment::text("hello"), Segment::line()]
}
fn content_hash(&self) -> Option<u64> {
Some(self.fixed_hash)
}
}
struct AlwaysDirty {
call_count: Arc<AtomicUsize>,
}
impl Renderable for AlwaysDirty {
fn gilt_console(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
self.call_count.fetch_add(1, Ordering::SeqCst);
vec![Segment::text("dirty"), Segment::line()]
}
}
#[test]
fn test_content_hash_caches_second_render() {
let console = Console::builder().width(40).height(5).build();
let options = console.options().update_dimensions(40, 5);
let call_count = Arc::new(AtomicUsize::new(0));
let counter_arc = Arc::clone(&call_count);
let child = CountingRenderable::new(call_count, 0xDEAD_BEEF);
let mut layout = Layout::default_layout();
layout.name = Some("main".to_string());
layout.update_renderable(child);
let mut cache = LayoutCache::new();
layout.render_with_cache(&console, &options, &mut cache);
layout.render_with_cache(&console, &options, &mut cache);
let calls = counter_arc.load(Ordering::SeqCst);
assert!(
calls < 2,
"expected cached render to skip second gilt_console call, but got {} calls",
calls
);
assert_eq!(
calls, 1,
"expected exactly 1 gilt_console call (first render only)"
);
}
#[test]
fn test_none_hash_always_rerenders() {
let console = Console::builder().width(40).height(5).build();
let options = console.options().update_dimensions(40, 5);
let call_count = Arc::new(AtomicUsize::new(0));
let counter_arc = Arc::clone(&call_count);
let child = AlwaysDirty { call_count };
let mut layout = Layout::default_layout();
layout.name = Some("dirty_pane".to_string());
layout.update_renderable(child);
let mut cache = LayoutCache::new();
layout.render_with_cache(&console, &options, &mut cache);
layout.render_with_cache(&console, &options, &mut cache);
let calls = counter_arc.load(Ordering::SeqCst);
assert_eq!(
calls, 2,
"None-hash child must re-render every time, got {} calls",
calls
);
}
#[test]
fn test_default_content_hash_is_none() {
struct Minimal;
impl Renderable for Minimal {
fn gilt_console(&self, _: &Console, _: &ConsoleOptions) -> Vec<Segment> {
vec![]
}
}
let m = Minimal;
assert_eq!(m.content_hash(), None, "default content_hash must be None");
}
#[test]
fn test_text_has_content_hash() {
let t = Text::new("hello", Style::null());
assert!(t.content_hash().is_some(), "Text should return Some(hash)");
}
#[test]
fn test_text_content_hash_stable_and_distinct() {
let t1 = Text::new("hello", Style::null());
let t2 = Text::new("hello", Style::null());
let t3 = Text::new("world", Style::null());
assert_eq!(
t1.content_hash(),
t2.content_hash(),
"same content → same hash"
);
assert_ne!(
t1.content_hash(),
t3.content_hash(),
"different content → different hash"
);
}
#[test]
fn test_rule_has_content_hash() {
use crate::rule::Rule;
let r = Rule::new();
assert!(r.content_hash().is_some(), "Rule should return Some(hash)");
}
#[test]
fn test_layout_render_unchanged_smoke() {
let console = Console::builder().width(40).height(5).build();
let options = console.options();
let mut layout = Layout::default_layout();
layout.update("hello".to_string());
layout.name = Some("main".to_string());
let render_map = layout.render(&console, &options);
assert!(render_map.contains_key("main"));
}