1use crate::terminal::no_emoji;
4use crate::theme::{Colors, Theme};
5use crate::types::TestResult;
6
7#[must_use]
8pub fn ping_rating(ping_ms: f64) -> &'static str {
9 if ping_ms < 10.0 {
10 "Excellent"
11 } else if ping_ms < 30.0 {
12 "Good"
13 } else if ping_ms < 60.0 {
14 "Fair"
15 } else if ping_ms < 100.0 {
16 "Poor"
17 } else {
18 "Bad"
19 }
20}
21
22#[must_use]
23pub fn speed_rating_mbps(mbps: f64) -> &'static str {
24 if mbps >= 500.0 {
25 "Excellent"
26 } else if mbps >= 200.0 {
27 "Great"
28 } else if mbps >= 100.0 {
29 "Good"
30 } else if mbps >= 50.0 {
31 "Fair"
32 } else if mbps >= 25.0 {
33 "Moderate"
34 } else if mbps >= 10.0 {
35 "Slow"
36 } else {
37 "Very Slow"
38 }
39}
40
41#[must_use]
42pub fn colorize_rating(rating: &str, nc: bool, theme: Theme) -> String {
43 if nc || no_emoji() {
44 format!("[{rating}]")
45 } else {
46 match rating {
47 "Excellent" | "Great" | "Good" => Colors::good(rating, theme),
48 "Fair" | "Moderate" => Colors::warn(rating, theme),
49 "Poor" | "Slow" | "Very Slow" => Colors::bad(rating, theme),
50 _ => rating.to_string(),
51 }
52 }
53}
54
55struct SpeedComponents {
57 value: f64,
58 unit: &'static str,
59}
60
61fn speed_components(bps: f64, bytes: bool) -> SpeedComponents {
63 let divider = if bytes { 8.0 } else { 1.0 };
64 let unit = if bytes { "MB/s" } else { "Mb/s" };
65 let value = bps / divider / 1_000_000.0;
66 SpeedComponents { value, unit }
67}
68
69#[must_use]
70pub fn format_speed_colored(bps: f64, bytes: bool, theme: Theme) -> String {
71 let SpeedComponents { value, unit } = speed_components(bps, bytes);
72 let mbps = bps / 1_000_000.0;
73 let rating = speed_rating_mbps(mbps);
74 let text = format!("{value:.2} {unit}");
75 match rating {
76 "Excellent" | "Great" | "Good" => Colors::good(&text, theme),
77 "Fair" | "Moderate" => Colors::warn(&text, theme),
78 "Poor" | "Slow" | "Very Slow" => Colors::bad(&text, theme),
79 _ => text,
80 }
81}
82
83#[must_use]
84pub fn format_speed_plain(bps: f64, bytes: bool) -> String {
85 let SpeedComponents { value, unit } = speed_components(bps, bytes);
86 format!("{value:.2} {unit}")
87}
88
89#[must_use]
90pub fn format_duration(secs: f64) -> String {
91 if secs < 60.0 {
92 format!("{secs:.1}s")
93 } else {
94 let mins = (secs / 60.0).clamp(0.0, u64::MAX as f64) as u64;
96 let secs = secs % 60.0;
97 format!("{mins}m {secs:.0}s")
98 }
99}
100
101#[must_use]
102pub fn connection_rating(result: &TestResult) -> &'static str {
103 fn score_lower(value: f64, thresholds: [f64; 5]) -> f64 {
105 if value < thresholds[0] {
106 100.0
107 } else if value < thresholds[1] {
108 80.0
109 } else if value < thresholds[2] {
110 60.0
111 } else if value < thresholds[3] {
112 40.0
113 } else {
114 20.0
115 }
116 }
117
118 fn score_higher(mbps: f64, thresholds: [f64; 6]) -> f64 {
120 if mbps >= thresholds[0] {
121 100.0
122 } else if mbps >= thresholds[1] {
123 85.0
124 } else if mbps >= thresholds[2] {
125 70.0
126 } else if mbps >= thresholds[3] {
127 55.0
128 } else if mbps >= thresholds[4] {
129 40.0
130 } else if mbps >= thresholds[5] {
131 25.0
132 } else {
133 10.0
134 }
135 }
136
137 let mut score = 0.0;
138 let mut factors = 0.0;
139
140 if let Some(ping) = result.ping {
142 score += score_lower(ping, [10.0, 30.0, 60.0, 100.0, f64::MAX]);
143 factors += 1.0;
144 }
145
146 if let Some(jitter) = result.jitter {
148 score += score_lower(jitter, [2.0, 5.0, 10.0, 20.0, f64::MAX]);
149 factors += 1.0;
150 }
151
152 if let Some(dl) = result.download {
154 score += score_higher(dl / 1_000_000.0, [500.0, 200.0, 100.0, 50.0, 25.0, 10.0]);
155 factors += 1.0;
156 }
157
158 if let Some(ul) = result.upload {
160 score += score_higher(ul / 1_000_000.0, [500.0, 200.0, 100.0, 50.0, 25.0, 10.0]);
161 factors += 1.0;
162 }
163
164 if factors == 0.0 {
165 return "Unknown";
166 }
167
168 let avg = score / factors;
169 if avg >= 90.0 {
170 "Excellent"
171 } else if avg >= 75.0 {
172 "Great"
173 } else if avg >= 55.0 {
174 "Good"
175 } else if avg >= 40.0 {
176 "Fair"
177 } else if avg >= 25.0 {
178 "Moderate"
179 } else {
180 "Poor"
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum BufferbloatGrade {
187 A,
188 B,
189 C,
190 D,
191 F,
192}
193
194impl BufferbloatGrade {
195 #[must_use]
196 pub fn as_str(&self) -> &'static str {
197 match self {
198 Self::A => "A",
199 Self::B => "B",
200 Self::C => "C",
201 Self::D => "D",
202 Self::F => "F",
203 }
204 }
205}
206
207#[must_use]
209pub fn bufferbloat_grade(load_latency: f64, idle_latency: f64) -> (BufferbloatGrade, f64) {
210 let added = if idle_latency > 0.0 {
211 load_latency - idle_latency
212 } else {
213 load_latency
214 };
215 let grade = if added < 5.0 {
216 BufferbloatGrade::A
217 } else if added < 20.0 {
218 BufferbloatGrade::B
219 } else if added < 50.0 {
220 BufferbloatGrade::C
221 } else if added < 100.0 {
222 BufferbloatGrade::D
223 } else {
224 BufferbloatGrade::F
225 };
226 (grade, added.max(0.0))
227}
228
229#[must_use]
230pub fn bufferbloat_colorized(
231 grade: BufferbloatGrade,
232 added_ms: f64,
233 nc: bool,
234 theme: Theme,
235) -> String {
236 if nc {
237 format!("{} ({added_ms:.0}ms)", grade.as_str())
238 } else {
239 let text = format!("{} ({added_ms:.0}ms added)", grade.as_str());
240 match grade {
241 BufferbloatGrade::A | BufferbloatGrade::B => Colors::good(&text, theme),
242 BufferbloatGrade::C | BufferbloatGrade::D => Colors::warn(&text, theme),
243 BufferbloatGrade::F => Colors::bad(&text, theme),
244 }
245 }
246}
247
248#[must_use]
249pub fn format_overall_rating(result: &TestResult, nc: bool, theme: Theme) -> String {
250 let rating = connection_rating(result);
251 if nc {
252 format!(" Overall: {rating}")
253 } else {
254 let colored = match rating {
255 "Excellent" | "Great" | "Good" => Colors::good(rating, theme),
256 "Fair" | "Moderate" => Colors::warn(rating, theme),
257 _ => Colors::bad(rating, theme),
258 };
259 format!(" {} {colored}", Colors::dimmed("Overall:", theme))
260 }
261}
262
263#[must_use]
264pub fn degradation_str(lat_load: f64, idle_ping: Option<f64>, nc: bool, theme: Theme) -> String {
265 let Some(idle) = idle_ping else {
266 return String::new();
267 };
268 if idle <= 0.0 {
269 return String::new();
270 }
271 let pct = ((lat_load / idle) - 1.0) * 100.0;
272 let text = format!(
273 "+{pct:.0}% ({})",
274 if pct < 25.0 {
275 "minimal"
276 } else if pct < 50.0 {
277 "moderate"
278 } else {
279 "significant"
280 }
281 );
282 if nc {
283 format!(" [{text:>8}]")
284 } else {
285 let colored = if pct < 25.0 {
286 Colors::good(&text, theme)
287 } else if pct < 50.0 {
288 Colors::warn(&text, theme)
289 } else {
290 Colors::bad(&text, theme)
291 };
292 format!(" {colored}")
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::types::{PhaseResult, ServerInfo, TestPhases, TestResult};
300
301 fn make_test_result() -> TestResult {
302 TestResult {
303 status: "ok".to_string(),
304 version: env!("CARGO_PKG_VERSION").to_string(),
305 test_id: None,
306 server: ServerInfo {
307 id: "1".to_string(),
308 name: "Test".to_string(),
309 sponsor: "Test ISP".to_string(),
310 country: "US".to_string(),
311 distance: 10.0,
312 },
313 ping: Some(15.0),
314 jitter: Some(1.5),
315 packet_loss: Some(0.0),
316 download: Some(100_000_000.0),
317 download_peak: Some(120_000_000.0),
318 upload: Some(50_000_000.0),
319 upload_peak: Some(60_000_000.0),
320 latency_download: Some(20.0),
321 latency_upload: Some(18.0),
322 download_samples: None,
323 upload_samples: None,
324 ping_samples: None,
325 timestamp: "2026-01-01T00:00:00Z".to_string(),
326 client_ip: None,
327 client_location: None,
328 download_cv: None,
329 upload_cv: None,
330 download_ci_95: None,
331 upload_ci_95: None,
332 overall_grade: None,
333 download_grade: None,
334 upload_grade: None,
335 connection_rating: None,
336 phases: TestPhases {
337 ping: PhaseResult::completed(),
338 download: PhaseResult::completed(),
339 upload: PhaseResult::completed(),
340 },
341 }
342 }
343
344 #[test]
347 fn test_ping_rating_excellent() {
348 assert_eq!(ping_rating(5.0), "Excellent");
349 assert_eq!(ping_rating(9.9), "Excellent");
350 assert_eq!(ping_rating(0.0), "Excellent");
351 }
352
353 #[test]
354 fn test_ping_rating_good() {
355 assert_eq!(ping_rating(10.0), "Good");
356 assert_eq!(ping_rating(29.9), "Good");
357 assert_eq!(ping_rating(15.0), "Good");
358 }
359
360 #[test]
361 fn test_ping_rating_fair() {
362 assert_eq!(ping_rating(30.0), "Fair");
363 assert_eq!(ping_rating(59.9), "Fair");
364 assert_eq!(ping_rating(45.0), "Fair");
365 }
366
367 #[test]
368 fn test_ping_rating_poor() {
369 assert_eq!(ping_rating(60.0), "Poor");
370 assert_eq!(ping_rating(99.9), "Poor");
371 assert_eq!(ping_rating(80.0), "Poor");
372 }
373
374 #[test]
375 fn test_ping_rating_bad() {
376 assert_eq!(ping_rating(100.0), "Bad");
377 assert_eq!(ping_rating(500.0), "Bad");
378 assert_eq!(ping_rating(10000.0), "Bad");
379 }
380
381 #[test]
384 fn test_speed_rating_excellent() {
385 assert_eq!(speed_rating_mbps(500.0), "Excellent");
386 assert_eq!(speed_rating_mbps(1000.0), "Excellent");
387 }
388
389 #[test]
390 fn test_speed_rating_great() {
391 assert_eq!(speed_rating_mbps(200.0), "Great");
392 assert_eq!(speed_rating_mbps(499.9), "Great");
393 assert_eq!(speed_rating_mbps(300.0), "Great");
394 }
395
396 #[test]
397 fn test_speed_rating_good() {
398 assert_eq!(speed_rating_mbps(100.0), "Good");
399 assert_eq!(speed_rating_mbps(199.9), "Good");
400 assert_eq!(speed_rating_mbps(150.0), "Good");
401 }
402
403 #[test]
404 fn test_speed_rating_fair() {
405 assert_eq!(speed_rating_mbps(50.0), "Fair");
406 assert_eq!(speed_rating_mbps(99.9), "Fair");
407 }
408
409 #[test]
410 fn test_speed_rating_moderate() {
411 assert_eq!(speed_rating_mbps(25.0), "Moderate");
412 assert_eq!(speed_rating_mbps(49.9), "Moderate");
413 assert_eq!(speed_rating_mbps(30.0), "Moderate");
414 }
415
416 #[test]
417 fn test_speed_rating_slow() {
418 assert_eq!(speed_rating_mbps(10.0), "Slow");
419 assert_eq!(speed_rating_mbps(24.9), "Slow");
420 assert_eq!(speed_rating_mbps(15.0), "Slow");
421 }
422
423 #[test]
424 fn test_speed_rating_very_slow() {
425 assert_eq!(speed_rating_mbps(0.0), "Very Slow");
426 assert_eq!(speed_rating_mbps(9.9), "Very Slow");
427 assert_eq!(speed_rating_mbps(1.0), "Very Slow");
428 }
429
430 #[test]
433 fn test_colorize_rating_excellent() {
434 let result = colorize_rating("Excellent", true, Theme::Dark);
435 assert!(result.contains("[Excellent]"));
436 }
437
438 #[test]
439 fn test_colorize_rating_great() {
440 let result = colorize_rating("Great", true, Theme::Dark);
441 assert!(result.contains("[Great]"));
442 }
443
444 #[test]
445 fn test_colorize_rating_good() {
446 let result = colorize_rating("Good", true, Theme::Dark);
447 assert!(result.contains("[Good]"));
448 }
449
450 #[test]
451 fn test_colorize_rating_fair() {
452 let result = colorize_rating("Fair", true, Theme::Dark);
453 assert!(result.contains("[Fair]"));
454 }
455
456 #[test]
457 fn test_colorize_rating_moderate() {
458 let result = colorize_rating("Moderate", true, Theme::Dark);
459 assert!(result.contains("[Moderate]"));
460 }
461
462 #[test]
463 fn test_colorize_rating_poor() {
464 let result = colorize_rating("Poor", true, Theme::Dark);
465 assert!(result.contains("[Poor]"));
466 }
467
468 #[test]
469 fn test_colorize_rating_slow() {
470 let result = colorize_rating("Slow", true, Theme::Dark);
471 assert!(result.contains("[Slow]"));
472 }
473
474 #[test]
475 fn test_colorize_rating_very_slow() {
476 let result = colorize_rating("Very Slow", true, Theme::Dark);
477 assert!(result.contains("[Very Slow]"));
478 }
479
480 #[test]
481 fn test_colorize_rating_unknown() {
482 let result = colorize_rating("Unknown", false, Theme::Dark);
484 assert_eq!(result, "Unknown");
485 }
486
487 #[test]
490 fn test_format_speed_colored_mbps_excellent() {
491 let result = format_speed_colored(500_000_000.0, false, Theme::Dark);
492 assert!(result.contains("500.00"));
493 assert!(result.contains("Mb/s"));
494 }
495
496 #[test]
497 fn test_format_speed_colored_bytes_mode() {
498 let result = format_speed_colored(8_000_000.0, true, Theme::Dark);
500 assert!(result.contains("1.00"));
501 assert!(result.contains("MB/s"));
502 }
503
504 #[test]
505 fn test_format_speed_colored_light_theme() {
506 let result = format_speed_colored(100_000_000.0, false, Theme::Light);
507 assert!(result.contains("100.00"));
508 }
509
510 #[test]
511 fn test_format_speed_colored_low_speed() {
512 let result = format_speed_colored(5_000_000.0, false, Theme::Dark);
514 assert!(result.contains("5.00"));
515 }
516
517 #[test]
520 fn test_format_speed_plain_mbps() {
521 let result = format_speed_plain(100_000_000.0, false);
522 assert_eq!(result, "100.00 Mb/s");
523 }
524
525 #[test]
526 fn test_format_speed_plain_bytes() {
527 let result = format_speed_plain(8_000_000.0, true);
529 assert_eq!(result, "1.00 MB/s");
530 }
531
532 #[test]
533 fn test_format_speed_plain_zero() {
534 let result = format_speed_plain(0.0, false);
535 assert_eq!(result, "0.00 Mb/s");
536 }
537
538 #[test]
539 fn test_format_speed_plain_fractional() {
540 let result = format_speed_plain(55_555_555.0, false);
541 assert!(result.contains("55.56"));
542 }
543
544 #[test]
547 fn test_format_duration_seconds() {
548 assert_eq!(format_duration(30.0), "30.0s");
549 assert_eq!(format_duration(59.9), "59.9s");
550 assert_eq!(format_duration(0.5), "0.5s");
551 }
552
553 #[test]
554 fn test_format_duration_minutes() {
555 assert_eq!(format_duration(60.0), "1m 0s");
556 assert_eq!(format_duration(90.0), "1m 30s");
557 assert_eq!(format_duration(125.0), "2m 5s");
558 }
559
560 #[test]
561 fn test_format_duration_many_minutes() {
562 assert_eq!(format_duration(600.0), "10m 0s");
563 assert_eq!(format_duration(3661.0), "61m 1s");
564 }
565
566 #[test]
569 fn test_connection_rating_excellent() {
570 let mut result = make_test_result();
571 result.ping = Some(5.0);
572 result.jitter = Some(1.0);
573 result.download = Some(500_000_000.0);
574 result.upload = Some(250_000_000.0);
575 assert_eq!(connection_rating(&result), "Excellent");
576 }
577
578 #[test]
579 fn test_connection_rating_great() {
580 let mut result = make_test_result();
581 result.ping = Some(20.0);
582 result.jitter = Some(3.0);
583 result.download = Some(200_000_000.0);
584 result.upload = Some(100_000_000.0);
585 assert_eq!(connection_rating(&result), "Great");
586 }
587
588 #[test]
589 fn test_connection_rating_good() {
590 let mut result = make_test_result();
591 result.ping = Some(40.0);
592 result.jitter = Some(8.0);
593 result.download = Some(100_000_000.0);
594 result.upload = Some(50_000_000.0);
595 assert_eq!(connection_rating(&result), "Good");
596 }
597
598 #[test]
599 fn test_connection_rating_fair() {
600 let mut result = make_test_result();
601 result.ping = Some(60.0);
602 result.jitter = Some(15.0);
603 result.download = Some(50_000_000.0);
604 result.upload = Some(25_000_000.0);
605 assert_eq!(connection_rating(&result), "Fair");
606 }
607
608 #[test]
609 fn test_connection_rating_moderate() {
610 let mut result = make_test_result();
611 result.ping = Some(80.0);
612 result.jitter = Some(18.0);
613 result.download = Some(25_000_000.0);
614 result.upload = Some(12_000_000.0);
615 assert_eq!(connection_rating(&result), "Moderate");
616 }
617
618 #[test]
619 fn test_connection_rating_poor() {
620 let mut result = make_test_result();
621 result.ping = Some(150.0);
622 result.jitter = Some(30.0);
623 result.download = Some(5_000_000.0);
624 result.upload = Some(1_000_000.0);
625 assert_eq!(connection_rating(&result), "Poor");
626 }
627
628 #[test]
629 fn test_connection_rating_unknown_no_data() {
630 let mut result = make_test_result();
631 result.ping = None;
632 result.jitter = None;
633 result.download = None;
634 result.upload = None;
635 assert_eq!(connection_rating(&result), "Unknown");
636 }
637
638 #[test]
639 fn test_connection_rating_partial_data() {
640 let mut result = make_test_result();
641 result.ping = Some(10.0);
642 result.jitter = None;
643 result.download = None;
644 result.upload = None;
645 let rating = connection_rating(&result);
647 assert!(!rating.is_empty());
648 }
649
650 #[test]
653 fn test_bufferbloat_grade_a() {
654 let (grade, added) = bufferbloat_grade(10.0, 8.0);
655 assert_eq!(grade, BufferbloatGrade::A);
656 assert!((added - 2.0).abs() < 0.1);
657 }
658
659 #[test]
660 fn test_bufferbloat_grade_b() {
661 let (grade, added) = bufferbloat_grade(30.0, 15.0);
662 assert_eq!(grade, BufferbloatGrade::B);
663 assert!((added - 15.0).abs() < 0.1);
664 }
665
666 #[test]
667 fn test_bufferbloat_grade_c() {
668 let (grade, added) = bufferbloat_grade(60.0, 20.0);
669 assert_eq!(grade, BufferbloatGrade::C);
670 assert!((added - 40.0).abs() < 0.1);
671 }
672
673 #[test]
674 fn test_bufferbloat_grade_d() {
675 let (grade, added) = bufferbloat_grade(120.0, 30.0);
676 assert_eq!(grade, BufferbloatGrade::D);
677 assert!((added - 90.0).abs() < 0.1);
678 }
679
680 #[test]
681 fn test_bufferbloat_grade_f() {
682 let (grade, added) = bufferbloat_grade(200.0, 50.0);
683 assert_eq!(grade, BufferbloatGrade::F);
684 assert!((added - 150.0).abs() < 0.1);
685 }
686
687 #[test]
688 fn test_bufferbloat_grade_zero_idle() {
689 let (grade, added) = bufferbloat_grade(10.0, 0.0);
692 assert_eq!(grade, BufferbloatGrade::B);
693 assert!((added - 10.0).abs() < 0.1);
694 }
695
696 #[test]
697 fn test_bufferbloat_grade_boundaries() {
698 let (grade, _) = bufferbloat_grade(4.99, 0.0);
700 assert_eq!(grade, BufferbloatGrade::A);
701
702 let (grade, _) = bufferbloat_grade(5.0, 0.0);
703 assert_eq!(grade, BufferbloatGrade::B);
704
705 let (grade, _) = bufferbloat_grade(19.99, 0.0);
706 assert_eq!(grade, BufferbloatGrade::B);
707
708 let (grade, _) = bufferbloat_grade(20.0, 0.0);
709 assert_eq!(grade, BufferbloatGrade::C);
710 }
711
712 #[test]
715 fn test_bufferbloat_grade_as_str() {
716 assert_eq!(BufferbloatGrade::A.as_str(), "A");
717 assert_eq!(BufferbloatGrade::B.as_str(), "B");
718 assert_eq!(BufferbloatGrade::C.as_str(), "C");
719 assert_eq!(BufferbloatGrade::D.as_str(), "D");
720 assert_eq!(BufferbloatGrade::F.as_str(), "F");
721 }
722
723 #[test]
726 fn test_bufferbloat_colorized_nc_a() {
727 let result = bufferbloat_colorized(BufferbloatGrade::A, 2.0, true, Theme::Dark);
728 assert!(result.contains("A"));
729 assert!(result.contains("2ms"));
730 }
731
732 #[test]
733 fn test_bufferbloat_colorized_nc_f() {
734 let result = bufferbloat_colorized(BufferbloatGrade::F, 150.0, true, Theme::Dark);
735 assert!(result.contains("F"));
736 assert!(result.contains("150ms"));
737 }
738
739 #[test]
740 fn test_bufferbloat_colorized_colored_a() {
741 let result = bufferbloat_colorized(BufferbloatGrade::A, 2.0, false, Theme::Dark);
742 assert!(result.contains("A"));
743 assert!(result.contains("added"));
744 }
745
746 #[test]
747 fn test_bufferbloat_colorized_colored_b() {
748 let result = bufferbloat_colorized(BufferbloatGrade::B, 15.0, false, Theme::Dark);
749 assert!(result.contains("B"));
750 }
751
752 #[test]
753 fn test_bufferbloat_colorized_colored_c() {
754 let result = bufferbloat_colorized(BufferbloatGrade::C, 40.0, false, Theme::Dark);
755 assert!(result.contains("C"));
756 }
757
758 #[test]
759 fn test_bufferbloat_colorized_colored_d() {
760 let result = bufferbloat_colorized(BufferbloatGrade::D, 90.0, false, Theme::Dark);
761 assert!(result.contains("D"));
762 }
763
764 #[test]
765 fn test_bufferbloat_colorized_colored_f() {
766 let result = bufferbloat_colorized(BufferbloatGrade::F, 150.0, false, Theme::Dark);
767 assert!(result.contains("F"));
768 }
769
770 #[test]
773 fn test_format_overall_rating_nc_excellent() {
774 let result = make_test_result();
775 let output = format_overall_rating(&result, true, Theme::Dark);
776 assert!(output.contains("Overall:"));
777 }
778
779 #[test]
780 fn test_format_overall_rating_colored() {
781 let result = make_test_result();
782 let output = format_overall_rating(&result, false, Theme::Dark);
783 assert!(output.contains("Overall:"));
784 }
785
786 #[test]
787 fn test_format_overall_rating_light_theme() {
788 let result = make_test_result();
789 let output = format_overall_rating(&result, false, Theme::Light);
790 assert!(output.contains("Overall:"));
791 }
792
793 #[test]
796 fn test_degradation_str_minimal() {
797 let result = degradation_str(12.0, Some(10.0), true, Theme::Dark);
799 assert!(result.contains("minimal"));
800 }
801
802 #[test]
803 fn test_degradation_str_moderate() {
804 let result = degradation_str(14.0, Some(10.0), true, Theme::Dark);
806 assert!(result.contains("moderate"));
807 }
808
809 #[test]
810 fn test_degradation_str_significant() {
811 let result = degradation_str(16.0, Some(10.0), true, Theme::Dark);
813 assert!(result.contains("significant"));
814 }
815
816 #[test]
817 fn test_degradation_str_no_idle() {
818 let result = degradation_str(15.0, None, false, Theme::Dark);
819 assert_eq!(result, "");
820 }
821
822 #[test]
823 fn test_degradation_str_zero_idle() {
824 let result = degradation_str(15.0, Some(0.0), false, Theme::Dark);
825 assert_eq!(result, "");
826 }
827
828 #[test]
829 fn test_degradation_str_negative_idle() {
830 let result = degradation_str(15.0, Some(-5.0), false, Theme::Dark);
831 assert_eq!(result, "");
832 }
833
834 #[test]
835 fn test_degradation_str_nc_mode() {
836 let result = degradation_str(12.0, Some(10.0), true, Theme::Dark);
837 assert!(result.contains("["));
838 }
839
840 #[test]
841 fn test_degradation_str_colored_minimal() {
842 let result = degradation_str(12.0, Some(10.0), false, Theme::Dark);
843 assert!(!result.is_empty());
844 }
845
846 #[test]
849 fn test_speed_components_helper() {
850 let result = format_speed_plain(100_000_000.0, false);
852 assert!(result.contains("100.00"));
853 assert!(result.contains("Mb/s"));
854 }
855}