1use crate::theme::Gradient;
15use presentar_core::{
16 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Constraints, Event,
17 LayoutResult, Point, Rect, Size, TextStyle, TypeId, Widget,
18};
19use std::any::Any;
20use std::time::Duration;
21
22#[derive(Debug, Clone)]
31pub struct PerCoreSparklineGrid {
32 pub core_histories: Vec<Vec<f64>>,
34 gradient: Gradient,
36 columns: Option<usize>,
38 show_labels: bool,
40 bounds: Rect,
42}
43
44impl Default for PerCoreSparklineGrid {
45 fn default() -> Self {
46 Self::new(vec![])
47 }
48}
49
50impl PerCoreSparklineGrid {
51 #[must_use]
53 pub fn new(core_histories: Vec<Vec<f64>>) -> Self {
54 Self {
55 core_histories,
56 gradient: Gradient::from_hex(&["#7aa2f7", "#e0af68", "#f7768e"]),
57 columns: None,
58 show_labels: true,
59 bounds: Rect::default(),
60 }
61 }
62
63 #[must_use]
65 pub fn with_columns(mut self, cols: usize) -> Self {
66 self.columns = Some(cols);
67 self
68 }
69
70 #[must_use]
72 pub fn with_gradient(mut self, gradient: Gradient) -> Self {
73 self.gradient = gradient;
74 self
75 }
76
77 #[must_use]
79 pub fn without_labels(mut self) -> Self {
80 self.show_labels = false;
81 self
82 }
83
84 pub fn set_histories(&mut self, histories: Vec<Vec<f64>>) {
86 self.core_histories = histories;
87 }
88
89 fn optimal_grid(&self, max_width: usize, cell_width: usize) -> (usize, usize) {
91 let count = self.core_histories.len();
92 if count == 0 {
93 return (0, 0);
94 }
95
96 let max_cols = (max_width / cell_width).max(1);
97 let cols = self.columns.unwrap_or_else(|| {
98 let ideal = (count as f64).sqrt().ceil() as usize;
100 ideal.min(max_cols).min(4).max(1)
101 });
102
103 let rows = count.div_ceil(cols);
104 (cols, rows)
105 }
106}
107
108impl Brick for PerCoreSparklineGrid {
109 fn brick_name(&self) -> &'static str {
110 "per_core_sparkline_grid"
111 }
112
113 fn assertions(&self) -> &[BrickAssertion] {
114 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
115 ASSERTIONS
116 }
117
118 fn budget(&self) -> BrickBudget {
119 BrickBudget::uniform(16)
120 }
121
122 fn verify(&self) -> BrickVerification {
123 BrickVerification {
124 passed: self.assertions().to_vec(),
125 failed: vec![],
126 verification_time: Duration::from_micros(10),
127 }
128 }
129
130 fn to_html(&self) -> String {
131 String::new()
132 }
133 fn to_css(&self) -> String {
134 String::new()
135 }
136}
137
138impl Widget for PerCoreSparklineGrid {
139 fn type_id(&self) -> TypeId {
140 TypeId::of::<Self>()
141 }
142
143 fn measure(&self, constraints: Constraints) -> Size {
144 let cell_width = 20; let cell_height = 2; let (cols, rows) = self.optimal_grid(constraints.max_width as usize, cell_width);
147
148 let width = (cols * cell_width) as f32;
149 let height = (rows * cell_height) as f32;
150
151 constraints.constrain(Size::new(width, height))
152 }
153
154 fn layout(&mut self, bounds: Rect) -> LayoutResult {
155 self.bounds = bounds;
156 LayoutResult {
157 size: Size::new(bounds.width, bounds.height),
158 }
159 }
160
161 fn paint(&self, canvas: &mut dyn Canvas) {
162 if self.core_histories.is_empty() {
163 return;
164 }
165
166 let cell_width = 20_usize;
167 let cell_height = 2_usize;
168 let (cols, _) = self.optimal_grid(self.bounds.width as usize, cell_width);
169 if cols == 0 {
170 return;
171 }
172
173 for (i, history) in self.core_histories.iter().enumerate() {
174 let col = i % cols;
175 let row = i / cols;
176
177 let x = self.bounds.x + (col * cell_width) as f32;
178 let y = self.bounds.y + (row * cell_height) as f32;
179
180 let current = history.last().copied().unwrap_or(0.0);
182 let color = self.gradient.for_percent(current);
183
184 if self.show_labels {
186 let label = format!("{i:2}");
187 canvas.draw_text(
188 &label,
189 Point::new(x, y),
190 &TextStyle {
191 color,
192 ..Default::default()
193 },
194 );
195 }
196
197 let sparkline_x = x + if self.show_labels { 3.0 } else { 0.0 };
199 let sparkline_width = (cell_width as f32 - 5.0).max(8.0);
200
201 let chars: String = history
203 .iter()
204 .rev()
205 .take(sparkline_width as usize)
206 .rev()
207 .map(|&v| {
208 let idx = ((v / 100.0) * 7.0).round() as usize;
209 ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][idx.min(7)]
210 })
211 .collect();
212
213 canvas.draw_text(
214 &chars,
215 Point::new(sparkline_x, y),
216 &TextStyle {
217 color,
218 ..Default::default()
219 },
220 );
221
222 let pct_str = format!("{current:3.0}%");
224 canvas.draw_text(
225 &pct_str,
226 Point::new(sparkline_x + sparkline_width, y),
227 &TextStyle {
228 color,
229 ..Default::default()
230 },
231 );
232 }
233 }
234
235 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
236 None
237 }
238
239 fn children(&self) -> &[Box<dyn Widget>] {
240 &[]
241 }
242
243 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
244 &mut []
245 }
246}
247
248#[derive(Debug, Clone)]
254pub struct CpuStateBreakdown {
255 pub core_states: Vec<CpuCoreState>,
257 bounds: Rect,
259}
260
261#[derive(Debug, Clone, Default)]
263pub struct CpuCoreState {
264 pub user: f64,
265 pub system: f64,
266 pub idle: f64,
267 pub iowait: f64,
268 pub irq: f64,
269 pub softirq: f64,
270}
271
272impl CpuCoreState {
273 pub fn new(user: f64, system: f64, idle: f64, iowait: f64, irq: f64, softirq: f64) -> Self {
275 Self {
276 user,
277 system,
278 idle,
279 iowait,
280 irq,
281 softirq,
282 }
283 }
284}
285
286impl Default for CpuStateBreakdown {
287 fn default() -> Self {
288 Self::new(vec![])
289 }
290}
291
292impl CpuStateBreakdown {
293 #[must_use]
295 pub fn new(core_states: Vec<CpuCoreState>) -> Self {
296 Self {
297 core_states,
298 bounds: Rect::default(),
299 }
300 }
301
302 pub fn set_states(&mut self, states: Vec<CpuCoreState>) {
304 self.core_states = states;
305 }
306}
307
308fn state_colors() -> [Color; 6] {
310 [
311 Color::rgb(0.478, 0.635, 0.969), Color::rgb(0.878, 0.686, 0.408), Color::rgb(0.969, 0.463, 0.557), Color::rgb(0.733, 0.604, 0.969), Color::rgb(0.620, 0.808, 0.416), Color::rgb(0.337, 0.373, 0.537), ]
318}
319
320impl Brick for CpuStateBreakdown {
321 fn brick_name(&self) -> &'static str {
322 "cpu_state_breakdown"
323 }
324
325 fn assertions(&self) -> &[BrickAssertion] {
326 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
327 ASSERTIONS
328 }
329
330 fn budget(&self) -> BrickBudget {
331 BrickBudget::uniform(16)
332 }
333
334 fn verify(&self) -> BrickVerification {
335 BrickVerification {
336 passed: self.assertions().to_vec(),
337 failed: vec![],
338 verification_time: Duration::from_micros(10),
339 }
340 }
341
342 fn to_html(&self) -> String {
343 String::new()
344 }
345 fn to_css(&self) -> String {
346 String::new()
347 }
348}
349
350impl Widget for CpuStateBreakdown {
351 fn type_id(&self) -> TypeId {
352 TypeId::of::<Self>()
353 }
354
355 fn measure(&self, constraints: Constraints) -> Size {
356 let height = self.core_states.len().min(constraints.max_height as usize) as f32;
357 constraints.constrain(Size::new(constraints.max_width, height))
358 }
359
360 fn layout(&mut self, bounds: Rect) -> LayoutResult {
361 self.bounds = bounds;
362 LayoutResult {
363 size: Size::new(bounds.width, bounds.height),
364 }
365 }
366
367 fn paint(&self, canvas: &mut dyn Canvas) {
368 let bar_width = (self.bounds.width as usize).saturating_sub(20); for (i, state) in self.core_states.iter().enumerate() {
371 let y = self.bounds.y + i as f32;
372 if y >= self.bounds.y + self.bounds.height {
373 break;
374 }
375
376 let label = format!("{i:2}:");
378 canvas.draw_text(&label, Point::new(self.bounds.x, y), &TextStyle::default());
379
380 let total =
382 state.user + state.system + state.idle + state.iowait + state.irq + state.softirq;
383 if total <= 0.0 {
384 continue;
385 }
386
387 let mut x = self.bounds.x + 4.0;
388 let colors = state_colors();
389 let segments = [
390 (state.user, colors[0], '█'), (state.system, colors[1], '▓'), (state.iowait, colors[2], '░'), (state.irq, colors[3], '▒'), (state.softirq, colors[4], '░'), (state.idle, colors[5], '·'), ];
397
398 for (pct, color, ch) in segments {
399 let width = ((pct / total) * bar_width as f64).round() as usize;
400 if width > 0 {
401 let bar: String = std::iter::repeat_n(ch, width).collect();
402 canvas.draw_text(
403 &bar,
404 Point::new(x, y),
405 &TextStyle {
406 color,
407 ..Default::default()
408 },
409 );
410 x += width as f32;
411 }
412 }
413
414 let summary = format!(
416 " u:{:2.0} s:{:2.0} w:{:2.0}",
417 state.user, state.system, state.iowait
418 );
419 canvas.draw_text(&summary, Point::new(x + 1.0, y), &TextStyle::default());
420 }
421 }
422
423 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
424 None
425 }
426
427 fn children(&self) -> &[Box<dyn Widget>] {
428 &[]
429 }
430
431 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
432 &mut []
433 }
434}
435
436#[derive(Debug, Clone)]
442pub struct TopProcessesMini {
443 pub processes: Vec<TopProcess>,
445 bounds: Rect,
447}
448
449#[derive(Debug, Clone)]
451pub struct TopProcess {
452 pub pid: u32,
453 pub cpu_percent: f32,
454 pub name: String,
455}
456
457impl TopProcess {
458 pub fn new(pid: u32, cpu_percent: f32, name: impl Into<String>) -> Self {
459 Self {
460 pid,
461 cpu_percent,
462 name: name.into(),
463 }
464 }
465}
466
467impl Default for TopProcessesMini {
468 fn default() -> Self {
469 Self::new(vec![])
470 }
471}
472
473impl TopProcessesMini {
474 #[must_use]
476 pub fn new(processes: Vec<TopProcess>) -> Self {
477 Self {
478 processes,
479 bounds: Rect::default(),
480 }
481 }
482
483 pub fn set_processes(&mut self, processes: Vec<TopProcess>) {
485 self.processes = processes;
486 }
487}
488
489impl Brick for TopProcessesMini {
490 fn brick_name(&self) -> &'static str {
491 "top_processes_mini"
492 }
493
494 fn assertions(&self) -> &[BrickAssertion] {
495 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
496 ASSERTIONS
497 }
498
499 fn budget(&self) -> BrickBudget {
500 BrickBudget::uniform(16)
501 }
502
503 fn verify(&self) -> BrickVerification {
504 BrickVerification {
505 passed: self.assertions().to_vec(),
506 failed: vec![],
507 verification_time: Duration::from_micros(10),
508 }
509 }
510
511 fn to_html(&self) -> String {
512 String::new()
513 }
514 fn to_css(&self) -> String {
515 String::new()
516 }
517}
518
519impl Widget for TopProcessesMini {
520 fn type_id(&self) -> TypeId {
521 TypeId::of::<Self>()
522 }
523
524 fn measure(&self, constraints: Constraints) -> Size {
525 let height = (self.processes.len().min(5) + 1) as f32; constraints.constrain(Size::new(constraints.max_width, height))
527 }
528
529 fn layout(&mut self, bounds: Rect) -> LayoutResult {
530 self.bounds = bounds;
531 LayoutResult {
532 size: Size::new(bounds.width, bounds.height),
533 }
534 }
535
536 fn paint(&self, canvas: &mut dyn Canvas) {
537 let header = " PID %CPU Command";
539 canvas.draw_text(
540 header,
541 Point::new(self.bounds.x, self.bounds.y),
542 &TextStyle {
543 color: Color::rgb(0.663, 0.694, 0.839), ..Default::default()
545 },
546 );
547
548 let gradient = Gradient::from_hex(&["#7aa2f7", "#e0af68", "#f7768e"]);
550
551 for (i, proc) in self.processes.iter().take(5).enumerate() {
552 let y = self.bounds.y + (i + 1) as f32;
553 if y >= self.bounds.y + self.bounds.height {
554 break;
555 }
556
557 let color = gradient.for_percent(proc.cpu_percent as f64);
558 let max_name_len = (self.bounds.width as usize).saturating_sub(15);
559 let name = if proc.name.len() > max_name_len {
560 format!("{}...", &proc.name[..max_name_len.saturating_sub(3)])
561 } else {
562 proc.name.clone()
563 };
564
565 let line = format!("{:5} {:5.1} {}", proc.pid, proc.cpu_percent, name);
566 canvas.draw_text(
567 &line,
568 Point::new(self.bounds.x, y),
569 &TextStyle {
570 color,
571 ..Default::default()
572 },
573 );
574 }
575 }
576
577 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
578 None
579 }
580
581 fn children(&self) -> &[Box<dyn Widget>] {
582 &[]
583 }
584
585 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
586 &mut []
587 }
588}
589
590#[derive(Debug, Clone)]
596pub struct FreqTempHeatmap {
597 pub frequencies: Vec<u32>,
599 pub max_frequencies: Vec<u32>,
601 pub temperatures: Option<Vec<f32>>,
603 bounds: Rect,
605}
606
607impl Default for FreqTempHeatmap {
608 fn default() -> Self {
609 Self::new(vec![], vec![])
610 }
611}
612
613impl FreqTempHeatmap {
614 #[must_use]
616 pub fn new(frequencies: Vec<u32>, max_frequencies: Vec<u32>) -> Self {
617 Self {
618 frequencies,
619 max_frequencies,
620 temperatures: None,
621 bounds: Rect::default(),
622 }
623 }
624
625 #[must_use]
627 pub fn with_temperatures(mut self, temps: Vec<f32>) -> Self {
628 self.temperatures = Some(temps);
629 self
630 }
631
632 pub fn set_data(&mut self, freqs: Vec<u32>, max_freqs: Vec<u32>, temps: Option<Vec<f32>>) {
634 self.frequencies = freqs;
635 self.max_frequencies = max_freqs;
636 self.temperatures = temps;
637 }
638}
639
640impl Brick for FreqTempHeatmap {
641 fn brick_name(&self) -> &'static str {
642 "freq_temp_heatmap"
643 }
644
645 fn assertions(&self) -> &[BrickAssertion] {
646 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
647 ASSERTIONS
648 }
649
650 fn budget(&self) -> BrickBudget {
651 BrickBudget::uniform(16)
652 }
653
654 fn verify(&self) -> BrickVerification {
655 BrickVerification {
656 passed: self.assertions().to_vec(),
657 failed: vec![],
658 verification_time: Duration::from_micros(10),
659 }
660 }
661
662 fn to_html(&self) -> String {
663 String::new()
664 }
665 fn to_css(&self) -> String {
666 String::new()
667 }
668}
669
670impl Widget for FreqTempHeatmap {
671 fn type_id(&self) -> TypeId {
672 TypeId::of::<Self>()
673 }
674
675 fn measure(&self, constraints: Constraints) -> Size {
676 let has_temps = self.temperatures.is_some();
677 let height = if has_temps { 4.0 } else { 2.0 }; constraints.constrain(Size::new(constraints.max_width, height))
679 }
680
681 fn layout(&mut self, bounds: Rect) -> LayoutResult {
682 self.bounds = bounds;
683 LayoutResult {
684 size: Size::new(bounds.width, bounds.height),
685 }
686 }
687
688 fn paint(&self, canvas: &mut dyn Canvas) {
689 if self.frequencies.is_empty() {
690 return;
691 }
692
693 let freq_gradient = Gradient::from_hex(&["#565f89", "#7aa2f7", "#9ece6a"]); let temp_gradient = Gradient::from_hex(&["#9ece6a", "#e0af68", "#f7768e"]); let cell_width = 4_usize;
698 let cores_per_row = (self.bounds.width as usize / cell_width).max(1);
699
700 canvas.draw_text(
702 "Freq:",
703 Point::new(self.bounds.x, self.bounds.y),
704 &TextStyle::default(),
705 );
706 for (i, (&freq, &max_freq)) in self
707 .frequencies
708 .iter()
709 .zip(self.max_frequencies.iter())
710 .enumerate()
711 {
712 let col = i % cores_per_row;
713 let row = i / cores_per_row;
714 let x = self.bounds.x + 6.0 + (col * cell_width) as f32;
715 let y = self.bounds.y + row as f32;
716
717 let pct = if max_freq > 0 {
718 (freq as f64 / max_freq as f64) * 100.0
719 } else {
720 0.0
721 };
722 let color = freq_gradient.for_percent(pct);
723 let level = ((pct / 100.0) * 7.0).round() as usize;
724 let ch = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][level.min(7)];
725
726 canvas.draw_text(
727 &ch.to_string(),
728 Point::new(x, y),
729 &TextStyle {
730 color,
731 ..Default::default()
732 },
733 );
734 }
735
736 if let Some(ref temps) = self.temperatures {
738 let temp_y = self.bounds.y + 2.0;
739 canvas.draw_text(
740 "Temp:",
741 Point::new(self.bounds.x, temp_y),
742 &TextStyle::default(),
743 );
744
745 for (i, &temp) in temps.iter().enumerate() {
746 let col = i % cores_per_row;
747 let row = i / cores_per_row;
748 let x = self.bounds.x + 6.0 + (col * cell_width) as f32;
749 let y = temp_y + row as f32;
750
751 let pct = ((temp - 30.0) / 70.0 * 100.0).clamp(0.0, 100.0) as f64;
753 let color = temp_gradient.for_percent(pct);
754 let level = ((pct / 100.0) * 7.0).round() as usize;
755 let ch = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][level.min(7)];
756
757 canvas.draw_text(
758 &ch.to_string(),
759 Point::new(x, y),
760 &TextStyle {
761 color,
762 ..Default::default()
763 },
764 );
765 }
766 }
767 }
768
769 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
770 None
771 }
772
773 fn children(&self) -> &[Box<dyn Widget>] {
774 &[]
775 }
776
777 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
778 &mut []
779 }
780}
781
782#[derive(Debug, Clone)]
788pub struct LoadAverageTimeline {
789 pub load_1m: Vec<f64>,
791 pub load_5m: Vec<f64>,
793 pub load_15m: Vec<f64>,
795 pub core_count: usize,
797 bounds: Rect,
799}
800
801impl Default for LoadAverageTimeline {
802 fn default() -> Self {
803 Self::new(1)
804 }
805}
806
807impl LoadAverageTimeline {
808 #[must_use]
810 pub fn new(core_count: usize) -> Self {
811 Self {
812 load_1m: Vec::new(),
813 load_5m: Vec::new(),
814 load_15m: Vec::new(),
815 core_count: core_count.max(1),
816 bounds: Rect::default(),
817 }
818 }
819
820 pub fn push(&mut self, load_1m: f64, load_5m: f64, load_15m: f64) {
822 const MAX_HISTORY: usize = 60;
823
824 self.load_1m.push(load_1m);
825 self.load_5m.push(load_5m);
826 self.load_15m.push(load_15m);
827
828 if self.load_1m.len() > MAX_HISTORY {
829 self.load_1m.remove(0);
830 }
831 if self.load_5m.len() > MAX_HISTORY {
832 self.load_5m.remove(0);
833 }
834 if self.load_15m.len() > MAX_HISTORY {
835 self.load_15m.remove(0);
836 }
837 }
838
839 pub fn set_core_count(&mut self, count: usize) {
841 self.core_count = count.max(1);
842 }
843}
844
845impl Brick for LoadAverageTimeline {
846 fn brick_name(&self) -> &'static str {
847 "load_average_timeline"
848 }
849
850 fn assertions(&self) -> &[BrickAssertion] {
851 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
852 ASSERTIONS
853 }
854
855 fn budget(&self) -> BrickBudget {
856 BrickBudget::uniform(16)
857 }
858
859 fn verify(&self) -> BrickVerification {
860 BrickVerification {
861 passed: self.assertions().to_vec(),
862 failed: vec![],
863 verification_time: Duration::from_micros(10),
864 }
865 }
866
867 fn to_html(&self) -> String {
868 String::new()
869 }
870 fn to_css(&self) -> String {
871 String::new()
872 }
873}
874
875impl Widget for LoadAverageTimeline {
876 fn type_id(&self) -> TypeId {
877 TypeId::of::<Self>()
878 }
879
880 fn measure(&self, constraints: Constraints) -> Size {
881 constraints.constrain(Size::new(constraints.max_width, 3.0))
882 }
883
884 fn layout(&mut self, bounds: Rect) -> LayoutResult {
885 self.bounds = bounds;
886 LayoutResult {
887 size: Size::new(bounds.width, bounds.height),
888 }
889 }
890
891 fn paint(&self, canvas: &mut dyn Canvas) {
892 let gradient = Gradient::from_hex(&["#9ece6a", "#e0af68", "#f7768e"]); let sparkline_width = (self.bounds.width as usize).saturating_sub(20);
894
895 let loads = [
896 (" 1m:", &self.load_1m),
897 (" 5m:", &self.load_5m),
898 ("15m:", &self.load_15m),
899 ];
900
901 for (row, (label, history)) in loads.iter().enumerate() {
902 let y = self.bounds.y + row as f32;
903 if y >= self.bounds.y + self.bounds.height {
904 break;
905 }
906
907 canvas.draw_text(label, Point::new(self.bounds.x, y), &TextStyle::default());
909
910 if history.is_empty() {
911 continue;
912 }
913
914 let current = history.last().copied().unwrap_or(0.0);
916 let load_pct = (current / self.core_count as f64 * 100.0).min(200.0);
917 let color = gradient.for_percent(load_pct.min(100.0));
918
919 let chars: String = history
921 .iter()
922 .rev()
923 .take(sparkline_width)
924 .rev()
925 .map(|&v| {
926 let pct = (v / self.core_count as f64 * 100.0).min(200.0);
927 let idx = ((pct / 200.0) * 7.0).round() as usize;
928 ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][idx.min(7)]
929 })
930 .collect();
931
932 canvas.draw_text(
933 &chars,
934 Point::new(self.bounds.x + 5.0, y),
935 &TextStyle {
936 color,
937 ..Default::default()
938 },
939 );
940
941 let value_str = format!(" ({current:.2})");
943 canvas.draw_text(
944 &value_str,
945 Point::new(self.bounds.x + 5.0 + sparkline_width as f32, y),
946 &TextStyle {
947 color,
948 ..Default::default()
949 },
950 );
951 }
952 }
953
954 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
955 None
956 }
957
958 fn children(&self) -> &[Box<dyn Widget>] {
959 &[]
960 }
961
962 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
963 &mut []
964 }
965}
966
967#[cfg(test)]
968#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
969mod tests {
970 use super::*;
971 use crate::direct::{CellBuffer, DirectTerminalCanvas};
972
973 #[test]
976 fn test_per_core_sparkline_grid_new() {
977 let grid = PerCoreSparklineGrid::new(vec![vec![10.0, 20.0, 30.0]; 4]);
978 assert_eq!(grid.core_histories.len(), 4);
979 }
980
981 #[test]
982 fn test_per_core_sparkline_grid_default() {
983 let grid = PerCoreSparklineGrid::default();
984 assert!(grid.core_histories.is_empty());
985 }
986
987 #[test]
988 fn test_per_core_sparkline_grid_with_columns() {
989 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]; 8]).with_columns(4);
990 assert_eq!(grid.columns, Some(4));
991 }
992
993 #[test]
994 fn test_per_core_sparkline_grid_with_gradient() {
995 let custom_gradient = Gradient::from_hex(&["#ff0000", "#00ff00"]);
996 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]; 4]).with_gradient(custom_gradient);
997 assert!(!grid.core_histories.is_empty());
999 }
1000
1001 #[test]
1002 fn test_per_core_sparkline_grid_without_labels() {
1003 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]; 4]).without_labels();
1004 assert!(!grid.show_labels);
1005 }
1006
1007 #[test]
1008 fn test_per_core_sparkline_grid_set_histories() {
1009 let mut grid = PerCoreSparklineGrid::new(vec![]);
1010 assert!(grid.core_histories.is_empty());
1011 grid.set_histories(vec![vec![10.0, 20.0], vec![30.0, 40.0]]);
1012 assert_eq!(grid.core_histories.len(), 2);
1013 }
1014
1015 #[test]
1016 fn test_per_core_sparkline_grid_optimal_grid_empty() {
1017 let grid = PerCoreSparklineGrid::new(vec![]);
1018 let (cols, rows) = grid.optimal_grid(80, 20);
1019 assert_eq!((cols, rows), (0, 0));
1020 }
1021
1022 #[test]
1023 fn test_per_core_sparkline_grid_optimal_grid_with_explicit_cols() {
1024 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]; 8]).with_columns(2);
1025 let (cols, rows) = grid.optimal_grid(80, 20);
1026 assert_eq!(cols, 2);
1027 assert_eq!(rows, 4);
1028 }
1029
1030 #[test]
1031 fn test_per_core_sparkline_grid_paint() {
1032 let mut grid = PerCoreSparklineGrid::new(vec![
1033 vec![10.0, 20.0, 30.0, 40.0, 50.0],
1034 vec![60.0, 70.0, 80.0, 90.0, 100.0],
1035 vec![50.0, 50.0, 50.0, 50.0, 50.0],
1036 vec![5.0, 10.0, 15.0, 20.0, 25.0],
1037 ]);
1038 let mut buffer = CellBuffer::new(80, 20);
1039 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1040 grid.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1041 grid.paint(&mut canvas);
1042 }
1043
1044 #[test]
1045 fn test_per_core_sparkline_grid_paint_empty() {
1046 let mut grid = PerCoreSparklineGrid::new(vec![]);
1047 let mut buffer = CellBuffer::new(80, 20);
1048 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1049 grid.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1050 grid.paint(&mut canvas); }
1052
1053 #[test]
1054 fn test_per_core_sparkline_grid_paint_without_labels() {
1055 let mut grid = PerCoreSparklineGrid::new(vec![vec![50.0; 10]; 4]).without_labels();
1056 let mut buffer = CellBuffer::new(80, 20);
1057 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1058 grid.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1059 grid.paint(&mut canvas);
1060 }
1061
1062 #[test]
1063 fn test_per_core_sparkline_grid_paint_with_empty_history() {
1064 let mut grid = PerCoreSparklineGrid::new(vec![vec![], vec![]]);
1065 let mut buffer = CellBuffer::new(80, 20);
1066 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1067 grid.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1068 grid.paint(&mut canvas);
1069 }
1070
1071 #[test]
1072 fn test_per_core_sparkline_grid_measure() {
1073 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]; 4]);
1074 let size = grid.measure(Constraints {
1075 min_width: 0.0,
1076 max_width: 80.0,
1077 min_height: 0.0,
1078 max_height: 40.0,
1079 });
1080 assert!(size.width > 0.0);
1081 }
1082
1083 #[test]
1084 fn test_per_core_sparkline_grid_measure_empty() {
1085 let grid = PerCoreSparklineGrid::new(vec![]);
1086 let size = grid.measure(Constraints {
1087 min_width: 0.0,
1088 max_width: 80.0,
1089 min_height: 0.0,
1090 max_height: 40.0,
1091 });
1092 assert!((size.width - 0.0).abs() < f32::EPSILON);
1093 }
1094
1095 #[test]
1096 fn test_per_core_sparkline_grid_event() {
1097 let mut grid = PerCoreSparklineGrid::default();
1098 let event = Event::FocusIn;
1099 assert!(grid.event(&event).is_none());
1100 }
1101
1102 #[test]
1103 fn test_per_core_sparkline_grid_children() {
1104 let grid = PerCoreSparklineGrid::default();
1105 assert!(grid.children().is_empty());
1106 }
1107
1108 #[test]
1109 fn test_per_core_sparkline_grid_children_mut() {
1110 let mut grid = PerCoreSparklineGrid::default();
1111 assert!(grid.children_mut().is_empty());
1112 }
1113
1114 #[test]
1115 fn test_per_core_sparkline_grid_to_html_css() {
1116 let grid = PerCoreSparklineGrid::default();
1117 assert!(grid.to_html().is_empty());
1118 assert!(grid.to_css().is_empty());
1119 }
1120
1121 #[test]
1122 fn test_per_core_sparkline_grid_budget() {
1123 let grid = PerCoreSparklineGrid::default();
1124 let budget = grid.budget();
1125 assert!(budget.measure_ms > 0);
1126 }
1127
1128 #[test]
1129 fn test_per_core_sparkline_grid_type_id() {
1130 let grid = PerCoreSparklineGrid::default();
1131 let type_id = Widget::type_id(&grid);
1132 assert_eq!(type_id, TypeId::of::<PerCoreSparklineGrid>());
1133 }
1134
1135 #[test]
1136 fn test_per_core_sparkline_grid_clone() {
1137 let grid = PerCoreSparklineGrid::new(vec![vec![10.0, 20.0]]);
1138 let cloned = grid;
1139 assert_eq!(cloned.core_histories.len(), 1);
1140 }
1141
1142 #[test]
1143 fn test_per_core_sparkline_grid_debug() {
1144 let grid = PerCoreSparklineGrid::new(vec![vec![10.0]]);
1145 let debug = format!("{:?}", grid);
1146 assert!(debug.contains("PerCoreSparklineGrid"));
1147 }
1148
1149 #[test]
1152 fn test_cpu_state_breakdown_new() {
1153 let breakdown =
1154 CpuStateBreakdown::new(vec![CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0)]);
1155 assert_eq!(breakdown.core_states.len(), 1);
1156 }
1157
1158 #[test]
1159 fn test_cpu_state_breakdown_default() {
1160 let breakdown = CpuStateBreakdown::default();
1161 assert!(breakdown.core_states.is_empty());
1162 }
1163
1164 #[test]
1165 fn test_cpu_state_breakdown_set_states() {
1166 let mut breakdown = CpuStateBreakdown::default();
1167 breakdown.set_states(vec![
1168 CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0),
1169 CpuCoreState::new(30.0, 20.0, 45.0, 5.0, 0.0, 0.0),
1170 ]);
1171 assert_eq!(breakdown.core_states.len(), 2);
1172 }
1173
1174 #[test]
1175 fn test_cpu_state_breakdown_paint() {
1176 let mut breakdown = CpuStateBreakdown::new(vec![
1177 CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0),
1178 CpuCoreState::new(70.0, 15.0, 10.0, 5.0, 0.0, 0.0),
1179 CpuCoreState::new(30.0, 5.0, 60.0, 5.0, 0.0, 0.0),
1180 ]);
1181 let mut buffer = CellBuffer::new(80, 20);
1182 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1183 breakdown.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1184 breakdown.paint(&mut canvas);
1185 }
1186
1187 #[test]
1188 fn test_cpu_state_breakdown_paint_empty() {
1189 let mut breakdown = CpuStateBreakdown::default();
1190 let mut buffer = CellBuffer::new(80, 20);
1191 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1192 breakdown.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1193 breakdown.paint(&mut canvas);
1194 }
1195
1196 #[test]
1197 fn test_cpu_state_breakdown_paint_zero_total() {
1198 let mut breakdown =
1199 CpuStateBreakdown::new(vec![CpuCoreState::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]);
1200 let mut buffer = CellBuffer::new(80, 20);
1201 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1202 breakdown.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1203 breakdown.paint(&mut canvas);
1204 }
1205
1206 #[test]
1207 fn test_cpu_state_breakdown_paint_with_irq() {
1208 let mut breakdown =
1209 CpuStateBreakdown::new(vec![CpuCoreState::new(30.0, 10.0, 40.0, 5.0, 10.0, 5.0)]);
1210 let mut buffer = CellBuffer::new(80, 20);
1211 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1212 breakdown.layout(Rect::new(0.0, 0.0, 80.0, 20.0));
1213 breakdown.paint(&mut canvas);
1214 }
1215
1216 #[test]
1217 fn test_cpu_state_breakdown_measure() {
1218 let breakdown =
1219 CpuStateBreakdown::new(vec![CpuCoreState::default(), CpuCoreState::default()]);
1220 let size = breakdown.measure(Constraints {
1221 min_width: 0.0,
1222 max_width: 80.0,
1223 min_height: 0.0,
1224 max_height: 40.0,
1225 });
1226 assert!(size.height >= 2.0);
1227 }
1228
1229 #[test]
1230 fn test_cpu_state_breakdown_event() {
1231 let mut breakdown = CpuStateBreakdown::default();
1232 assert!(breakdown.event(&Event::FocusIn).is_none());
1233 }
1234
1235 #[test]
1236 fn test_cpu_state_breakdown_children() {
1237 let breakdown = CpuStateBreakdown::default();
1238 assert!(breakdown.children().is_empty());
1239 }
1240
1241 #[test]
1242 fn test_cpu_state_breakdown_to_html_css() {
1243 let breakdown = CpuStateBreakdown::default();
1244 assert!(breakdown.to_html().is_empty());
1245 assert!(breakdown.to_css().is_empty());
1246 }
1247
1248 #[test]
1249 fn test_cpu_state_breakdown_type_id() {
1250 let breakdown = CpuStateBreakdown::default();
1251 assert_eq!(
1252 Widget::type_id(&breakdown),
1253 TypeId::of::<CpuStateBreakdown>()
1254 );
1255 }
1256
1257 #[test]
1258 fn test_cpu_state_breakdown_clone() {
1259 let breakdown = CpuStateBreakdown::new(vec![CpuCoreState::default()]);
1260 let cloned = breakdown;
1261 assert_eq!(cloned.core_states.len(), 1);
1262 }
1263
1264 #[test]
1265 fn test_cpu_state_breakdown_debug() {
1266 let breakdown = CpuStateBreakdown::default();
1267 let debug = format!("{:?}", breakdown);
1268 assert!(debug.contains("CpuStateBreakdown"));
1269 }
1270
1271 #[test]
1274 fn test_cpu_core_state_fields() {
1275 let state = CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0);
1276 assert!((state.user - 50.0).abs() < 0.001);
1277 assert!((state.system - 10.0).abs() < 0.001);
1278 assert!((state.idle - 35.0).abs() < 0.001);
1279 assert!((state.iowait - 5.0).abs() < 0.001);
1280 }
1281
1282 #[test]
1283 fn test_cpu_core_state_default() {
1284 let state = CpuCoreState::default();
1285 assert!((state.user - 0.0).abs() < 0.001);
1286 assert!((state.system - 0.0).abs() < 0.001);
1287 assert!((state.idle - 0.0).abs() < 0.001);
1288 assert!((state.iowait - 0.0).abs() < 0.001);
1289 assert!((state.irq - 0.0).abs() < 0.001);
1290 assert!((state.softirq - 0.0).abs() < 0.001);
1291 }
1292
1293 #[test]
1294 fn test_cpu_core_state_clone() {
1295 let state = CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0);
1296 let cloned = state;
1297 assert!((cloned.user - 50.0).abs() < 0.001);
1298 }
1299
1300 #[test]
1301 fn test_cpu_core_state_debug() {
1302 let state = CpuCoreState::new(50.0, 10.0, 35.0, 5.0, 0.0, 0.0);
1303 let debug = format!("{:?}", state);
1304 assert!(debug.contains("CpuCoreState"));
1305 assert!(debug.contains("50"));
1306 }
1307
1308 #[test]
1311 fn test_top_processes_mini_new() {
1312 let top = TopProcessesMini::new(vec![TopProcess::new(1234, 45.2, "firefox")]);
1313 assert_eq!(top.processes.len(), 1);
1314 }
1315
1316 #[test]
1317 fn test_top_processes_mini_default() {
1318 let top = TopProcessesMini::default();
1319 assert!(top.processes.is_empty());
1320 }
1321
1322 #[test]
1323 fn test_top_processes_mini_set_processes() {
1324 let mut top = TopProcessesMini::default();
1325 top.set_processes(vec![
1326 TopProcess::new(1234, 45.2, "firefox"),
1327 TopProcess::new(5678, 30.0, "chrome"),
1328 ]);
1329 assert_eq!(top.processes.len(), 2);
1330 }
1331
1332 #[test]
1333 fn test_top_processes_mini_paint() {
1334 let mut top = TopProcessesMini::new(vec![
1335 TopProcess::new(1234, 45.2, "firefox"),
1336 TopProcess::new(5678, 30.0, "chrome"),
1337 TopProcess::new(9012, 15.5, "vscode"),
1338 ]);
1339 let mut buffer = CellBuffer::new(40, 10);
1340 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1341 top.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
1342 top.paint(&mut canvas);
1343 }
1344
1345 #[test]
1346 fn test_top_processes_mini_paint_empty() {
1347 let mut top = TopProcessesMini::default();
1348 let mut buffer = CellBuffer::new(40, 10);
1349 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1350 top.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
1351 top.paint(&mut canvas);
1352 }
1353
1354 #[test]
1355 fn test_top_processes_mini_paint_long_name_truncation() {
1356 let mut top = TopProcessesMini::new(vec![TopProcess::new(
1357 1234,
1358 45.2,
1359 "this_is_a_very_long_process_name_that_should_be_truncated",
1360 )]);
1361 let mut buffer = CellBuffer::new(30, 10);
1362 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1363 top.layout(Rect::new(0.0, 0.0, 30.0, 10.0));
1364 top.paint(&mut canvas);
1365 }
1366
1367 #[test]
1368 fn test_top_processes_mini_paint_more_than_five() {
1369 let mut top = TopProcessesMini::new(vec![
1370 TopProcess::new(1, 90.0, "proc1"),
1371 TopProcess::new(2, 80.0, "proc2"),
1372 TopProcess::new(3, 70.0, "proc3"),
1373 TopProcess::new(4, 60.0, "proc4"),
1374 TopProcess::new(5, 50.0, "proc5"),
1375 TopProcess::new(6, 40.0, "proc6"),
1376 TopProcess::new(7, 30.0, "proc7"),
1377 ]);
1378 let mut buffer = CellBuffer::new(40, 10);
1379 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1380 top.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
1381 top.paint(&mut canvas); }
1383
1384 #[test]
1385 fn test_top_processes_mini_measure() {
1386 let top = TopProcessesMini::new(vec![
1387 TopProcess::new(1234, 45.2, "firefox"),
1388 TopProcess::new(5678, 30.0, "chrome"),
1389 ]);
1390 let size = top.measure(Constraints {
1391 min_width: 0.0,
1392 max_width: 40.0,
1393 min_height: 0.0,
1394 max_height: 20.0,
1395 });
1396 assert_eq!(size.height, 3.0); }
1398
1399 #[test]
1400 fn test_top_processes_mini_event() {
1401 let mut top = TopProcessesMini::default();
1402 assert!(top.event(&Event::FocusIn).is_none());
1403 }
1404
1405 #[test]
1406 fn test_top_processes_mini_children() {
1407 let top = TopProcessesMini::default();
1408 assert!(top.children().is_empty());
1409 }
1410
1411 #[test]
1412 fn test_top_processes_mini_to_html_css() {
1413 let top = TopProcessesMini::default();
1414 assert!(top.to_html().is_empty());
1415 assert!(top.to_css().is_empty());
1416 }
1417
1418 #[test]
1419 fn test_top_processes_mini_type_id() {
1420 let top = TopProcessesMini::default();
1421 assert_eq!(Widget::type_id(&top), TypeId::of::<TopProcessesMini>());
1422 }
1423
1424 #[test]
1425 fn test_top_processes_mini_clone() {
1426 let top = TopProcessesMini::new(vec![TopProcess::new(1234, 45.2, "firefox")]);
1427 let cloned = top;
1428 assert_eq!(cloned.processes.len(), 1);
1429 }
1430
1431 #[test]
1432 fn test_top_processes_mini_debug() {
1433 let top = TopProcessesMini::default();
1434 let debug = format!("{:?}", top);
1435 assert!(debug.contains("TopProcessesMini"));
1436 }
1437
1438 #[test]
1441 fn test_top_process_creation() {
1442 let proc = TopProcess::new(1234, 45.2, "firefox");
1443 assert_eq!(proc.pid, 1234);
1444 assert!((proc.cpu_percent - 45.2).abs() < 0.001);
1445 assert_eq!(proc.name, "firefox");
1446 }
1447
1448 #[test]
1449 fn test_top_process_clone() {
1450 let proc = TopProcess::new(1234, 45.2, "firefox");
1451 let cloned = proc;
1452 assert_eq!(cloned.pid, 1234);
1453 assert_eq!(cloned.name, "firefox");
1454 }
1455
1456 #[test]
1457 fn test_top_process_debug() {
1458 let proc = TopProcess::new(1234, 45.2, "firefox");
1459 let debug = format!("{:?}", proc);
1460 assert!(debug.contains("TopProcess"));
1461 assert!(debug.contains("1234"));
1462 }
1463
1464 #[test]
1467 fn test_freq_temp_heatmap_new() {
1468 let heatmap = FreqTempHeatmap::new(vec![3600, 3200], vec![4000, 4000]);
1469 assert_eq!(heatmap.frequencies.len(), 2);
1470 }
1471
1472 #[test]
1473 fn test_freq_temp_heatmap_default() {
1474 let heatmap = FreqTempHeatmap::default();
1475 assert!(heatmap.frequencies.is_empty());
1476 }
1477
1478 #[test]
1479 fn test_freq_temp_heatmap_with_temperatures() {
1480 let heatmap = FreqTempHeatmap::new(vec![3600, 3200], vec![4000, 4000])
1481 .with_temperatures(vec![65.0, 70.0]);
1482 assert_eq!(heatmap.temperatures.as_ref().unwrap().len(), 2);
1483 }
1484
1485 #[test]
1486 fn test_freq_temp_heatmap_set_data() {
1487 let mut heatmap = FreqTempHeatmap::default();
1488 heatmap.set_data(vec![3600, 3200], vec![4000, 4000], Some(vec![65.0, 70.0]));
1489 assert_eq!(heatmap.frequencies.len(), 2);
1490 assert!(heatmap.temperatures.is_some());
1491 }
1492
1493 #[test]
1494 fn test_freq_temp_heatmap_set_data_no_temps() {
1495 let mut heatmap = FreqTempHeatmap::default();
1496 heatmap.set_data(vec![3600], vec![4000], None);
1497 assert!(heatmap.temperatures.is_none());
1498 }
1499
1500 #[test]
1501 fn test_freq_temp_heatmap_paint() {
1502 let mut heatmap =
1503 FreqTempHeatmap::new(vec![3600, 3200, 3000, 2800], vec![4000, 4000, 4000, 4000])
1504 .with_temperatures(vec![65.0, 70.0, 75.0, 80.0]);
1505 let mut buffer = CellBuffer::new(60, 15);
1506 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1507 heatmap.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
1508 heatmap.paint(&mut canvas);
1509 }
1510
1511 #[test]
1512 fn test_freq_temp_heatmap_paint_empty() {
1513 let mut heatmap = FreqTempHeatmap::default();
1514 let mut buffer = CellBuffer::new(60, 15);
1515 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1516 heatmap.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
1517 heatmap.paint(&mut canvas);
1518 }
1519
1520 #[test]
1521 fn test_freq_temp_heatmap_paint_no_temps() {
1522 let mut heatmap = FreqTempHeatmap::new(vec![3600, 3200], vec![4000, 4000]);
1523 let mut buffer = CellBuffer::new(60, 15);
1524 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1525 heatmap.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
1526 heatmap.paint(&mut canvas);
1527 }
1528
1529 #[test]
1530 fn test_freq_temp_heatmap_paint_zero_max_freq() {
1531 let mut heatmap = FreqTempHeatmap::new(vec![3600], vec![0]);
1532 let mut buffer = CellBuffer::new(60, 15);
1533 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1534 heatmap.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
1535 heatmap.paint(&mut canvas);
1536 }
1537
1538 #[test]
1539 fn test_freq_temp_heatmap_paint_extreme_temps() {
1540 let mut heatmap =
1541 FreqTempHeatmap::new(vec![3600], vec![4000]).with_temperatures(vec![20.0]); let mut buffer = CellBuffer::new(60, 15);
1543 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1544 heatmap.layout(Rect::new(0.0, 0.0, 60.0, 15.0));
1545 heatmap.paint(&mut canvas);
1546 }
1547
1548 #[test]
1549 fn test_freq_temp_heatmap_measure_no_temps() {
1550 let heatmap = FreqTempHeatmap::new(vec![3600], vec![4000]);
1551 let size = heatmap.measure(Constraints {
1552 min_width: 0.0,
1553 max_width: 60.0,
1554 min_height: 0.0,
1555 max_height: 20.0,
1556 });
1557 assert_eq!(size.height, 2.0);
1558 }
1559
1560 #[test]
1561 fn test_freq_temp_heatmap_measure_with_temps() {
1562 let heatmap = FreqTempHeatmap::new(vec![3600], vec![4000]).with_temperatures(vec![65.0]);
1563 let size = heatmap.measure(Constraints {
1564 min_width: 0.0,
1565 max_width: 60.0,
1566 min_height: 0.0,
1567 max_height: 20.0,
1568 });
1569 assert_eq!(size.height, 4.0);
1570 }
1571
1572 #[test]
1573 fn test_freq_temp_heatmap_event() {
1574 let mut heatmap = FreqTempHeatmap::default();
1575 assert!(heatmap.event(&Event::FocusIn).is_none());
1576 }
1577
1578 #[test]
1579 fn test_freq_temp_heatmap_children() {
1580 let heatmap = FreqTempHeatmap::default();
1581 assert!(heatmap.children().is_empty());
1582 }
1583
1584 #[test]
1585 fn test_freq_temp_heatmap_to_html_css() {
1586 let heatmap = FreqTempHeatmap::default();
1587 assert!(heatmap.to_html().is_empty());
1588 assert!(heatmap.to_css().is_empty());
1589 }
1590
1591 #[test]
1592 fn test_freq_temp_heatmap_type_id() {
1593 let heatmap = FreqTempHeatmap::default();
1594 assert_eq!(Widget::type_id(&heatmap), TypeId::of::<FreqTempHeatmap>());
1595 }
1596
1597 #[test]
1598 fn test_freq_temp_heatmap_clone() {
1599 let heatmap = FreqTempHeatmap::new(vec![3600], vec![4000]);
1600 let cloned = heatmap;
1601 assert_eq!(cloned.frequencies.len(), 1);
1602 }
1603
1604 #[test]
1605 fn test_freq_temp_heatmap_debug() {
1606 let heatmap = FreqTempHeatmap::default();
1607 let debug = format!("{:?}", heatmap);
1608 assert!(debug.contains("FreqTempHeatmap"));
1609 }
1610
1611 #[test]
1614 fn test_load_average_timeline_new() {
1615 let timeline = LoadAverageTimeline::new(8);
1616 assert_eq!(timeline.core_count, 8);
1617 assert!(timeline.load_1m.is_empty());
1618 }
1619
1620 #[test]
1621 fn test_load_average_timeline_new_zero_cores() {
1622 let timeline = LoadAverageTimeline::new(0);
1623 assert_eq!(timeline.core_count, 1); }
1625
1626 #[test]
1627 fn test_load_average_timeline_default() {
1628 let timeline = LoadAverageTimeline::default();
1629 assert_eq!(timeline.core_count, 1);
1630 }
1631
1632 #[test]
1633 fn test_load_average_timeline_push() {
1634 let mut timeline = LoadAverageTimeline::new(8);
1635 timeline.push(1.5, 1.2, 1.0);
1636 timeline.push(2.0, 1.5, 1.1);
1637 assert_eq!(timeline.load_1m.len(), 2);
1638 assert_eq!(timeline.load_5m.len(), 2);
1639 assert_eq!(timeline.load_15m.len(), 2);
1640 }
1641
1642 #[test]
1643 fn test_load_average_timeline_push_exceeds_max_history() {
1644 let mut timeline = LoadAverageTimeline::new(8);
1645 for i in 0..65 {
1647 timeline.push(i as f64, i as f64, i as f64);
1648 }
1649 assert_eq!(timeline.load_1m.len(), 60);
1650 assert_eq!(timeline.load_5m.len(), 60);
1651 assert_eq!(timeline.load_15m.len(), 60);
1652 assert!((timeline.load_1m[0] - 5.0).abs() < f64::EPSILON);
1654 }
1655
1656 #[test]
1657 fn test_load_average_timeline_set_core_count() {
1658 let mut timeline = LoadAverageTimeline::new(8);
1659 timeline.set_core_count(16);
1660 assert_eq!(timeline.core_count, 16);
1661 }
1662
1663 #[test]
1664 fn test_load_average_timeline_set_core_count_zero() {
1665 let mut timeline = LoadAverageTimeline::new(8);
1666 timeline.set_core_count(0);
1667 assert_eq!(timeline.core_count, 1); }
1669
1670 #[test]
1671 fn test_load_average_timeline_paint() {
1672 let mut timeline = LoadAverageTimeline::new(8);
1673 for i in 0..30 {
1674 timeline.push(1.0 + i as f64 * 0.1, 1.0, 0.8);
1675 }
1676 let mut buffer = CellBuffer::new(60, 10);
1677 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1678 timeline.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
1679 timeline.paint(&mut canvas);
1680 }
1681
1682 #[test]
1683 fn test_load_average_timeline_paint_empty() {
1684 let mut timeline = LoadAverageTimeline::new(8);
1685 let mut buffer = CellBuffer::new(60, 10);
1686 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1687 timeline.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
1688 timeline.paint(&mut canvas);
1689 }
1690
1691 #[test]
1692 fn test_load_average_timeline_paint_high_load() {
1693 let mut timeline = LoadAverageTimeline::new(4);
1694 for i in 0..30 {
1696 timeline.push(8.0 + i as f64, 7.0, 6.0);
1697 }
1698 let mut buffer = CellBuffer::new(60, 10);
1699 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1700 timeline.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
1701 timeline.paint(&mut canvas);
1702 }
1703
1704 #[test]
1705 fn test_load_average_timeline_measure() {
1706 let timeline = LoadAverageTimeline::new(8);
1707 let size = timeline.measure(Constraints {
1708 min_width: 0.0,
1709 max_width: 60.0,
1710 min_height: 0.0,
1711 max_height: 20.0,
1712 });
1713 assert_eq!(size.height, 3.0);
1714 }
1715
1716 #[test]
1717 fn test_load_average_timeline_event() {
1718 let mut timeline = LoadAverageTimeline::default();
1719 assert!(timeline.event(&Event::FocusIn).is_none());
1720 }
1721
1722 #[test]
1723 fn test_load_average_timeline_children() {
1724 let timeline = LoadAverageTimeline::default();
1725 assert!(timeline.children().is_empty());
1726 }
1727
1728 #[test]
1729 fn test_load_average_timeline_to_html_css() {
1730 let timeline = LoadAverageTimeline::default();
1731 assert!(timeline.to_html().is_empty());
1732 assert!(timeline.to_css().is_empty());
1733 }
1734
1735 #[test]
1736 fn test_load_average_timeline_type_id() {
1737 let timeline = LoadAverageTimeline::default();
1738 assert_eq!(
1739 Widget::type_id(&timeline),
1740 TypeId::of::<LoadAverageTimeline>()
1741 );
1742 }
1743
1744 #[test]
1745 fn test_load_average_timeline_clone() {
1746 let mut timeline = LoadAverageTimeline::new(8);
1747 timeline.push(1.0, 2.0, 3.0);
1748 let cloned = timeline.clone();
1749 assert_eq!(cloned.load_1m.len(), 1);
1750 }
1751
1752 #[test]
1753 fn test_load_average_timeline_debug() {
1754 let timeline = LoadAverageTimeline::default();
1755 let debug = format!("{:?}", timeline);
1756 assert!(debug.contains("LoadAverageTimeline"));
1757 }
1758
1759 #[test]
1762 fn test_all_widgets_verify() {
1763 assert!(PerCoreSparklineGrid::default().verify().is_valid());
1764 assert!(CpuStateBreakdown::default().verify().is_valid());
1765 assert!(TopProcessesMini::default().verify().is_valid());
1766 assert!(FreqTempHeatmap::default().verify().is_valid());
1767 assert!(LoadAverageTimeline::default().verify().is_valid());
1768 }
1769
1770 #[test]
1771 fn test_all_widgets_brick_names() {
1772 assert_eq!(
1773 PerCoreSparklineGrid::default().brick_name(),
1774 "per_core_sparkline_grid"
1775 );
1776 assert_eq!(
1777 CpuStateBreakdown::default().brick_name(),
1778 "cpu_state_breakdown"
1779 );
1780 assert_eq!(
1781 TopProcessesMini::default().brick_name(),
1782 "top_processes_mini"
1783 );
1784 assert_eq!(FreqTempHeatmap::default().brick_name(), "freq_temp_heatmap");
1785 assert_eq!(
1786 LoadAverageTimeline::default().brick_name(),
1787 "load_average_timeline"
1788 );
1789 }
1790
1791 #[test]
1792 fn test_all_widgets_assertions() {
1793 assert!(!PerCoreSparklineGrid::default().assertions().is_empty());
1794 assert!(!CpuStateBreakdown::default().assertions().is_empty());
1795 assert!(!TopProcessesMini::default().assertions().is_empty());
1796 assert!(!FreqTempHeatmap::default().assertions().is_empty());
1797 assert!(!LoadAverageTimeline::default().assertions().is_empty());
1798 }
1799
1800 #[test]
1801 fn test_all_widgets_budget() {
1802 assert!(PerCoreSparklineGrid::default().budget().measure_ms > 0);
1803 assert!(CpuStateBreakdown::default().budget().measure_ms > 0);
1804 assert!(TopProcessesMini::default().budget().measure_ms > 0);
1805 assert!(FreqTempHeatmap::default().budget().measure_ms > 0);
1806 assert!(LoadAverageTimeline::default().budget().measure_ms > 0);
1807 }
1808
1809 #[test]
1812 fn test_state_colors() {
1813 let colors = state_colors();
1814 assert_eq!(colors.len(), 6);
1815 assert!(colors[0].r > 0.4 && colors[0].r < 0.6);
1817 assert!(colors[5].r < 0.4);
1819 }
1820}