1use crate::profiles::UserProfile;
13use crate::terminal;
14use crate::theme::{Colors, Theme};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18pub enum LetterGrade {
19 APlus,
20 A,
21 AMinus,
22 BPlus,
23 B,
24 BMinus,
25 CPlus,
26 C,
27 CMinus,
28 D,
29 F,
30}
31
32impl LetterGrade {
33 #[must_use]
35 pub fn as_str(&self) -> &'static str {
36 match self {
37 Self::APlus => "A+",
38 Self::A => "A",
39 Self::AMinus => "A-",
40 Self::BPlus => "B+",
41 Self::B => "B",
42 Self::BMinus => "B-",
43 Self::CPlus => "C+",
44 Self::C => "C",
45 Self::CMinus => "C-",
46 Self::D => "D",
47 Self::F => "F",
48 }
49 }
50
51 #[must_use]
53 pub fn color_str(&self, nc: bool, theme: Theme) -> String {
54 let s = self.as_str();
55 if nc {
56 return format!("[{s}]");
57 }
58 match self {
59 Self::APlus | Self::A | Self::AMinus | Self::BPlus | Self::B => Colors::good(s, theme),
60 Self::BMinus | Self::CPlus | Self::C | Self::CMinus | Self::D => Colors::warn(s, theme),
61 Self::F => Colors::bad(s, theme),
62 }
63 }
64
65 #[must_use]
67 pub fn emoji(&self) -> &'static str {
68 if terminal::no_emoji() {
69 return "";
70 }
71 match self {
72 Self::APlus | Self::A | Self::AMinus => "⚡",
73 Self::BPlus | Self::B => "✅",
74 Self::BMinus | Self::CPlus | Self::C => "⚠️",
75 Self::CMinus | Self::D => "❌",
76 Self::F => "🚫",
77 }
78 }
79
80 #[must_use]
82 pub fn score(&self) -> f64 {
83 match self {
84 Self::APlus => 100.0,
85 Self::A => 95.0,
86 Self::AMinus => 90.0,
87 Self::BPlus => 85.0,
88 Self::B => 80.0,
89 Self::BMinus => 75.0,
90 Self::CPlus => 70.0,
91 Self::C => 65.0,
92 Self::CMinus => 60.0,
93 Self::D => 50.0,
94 Self::F => 25.0,
95 }
96 }
97
98 #[must_use]
100 pub fn description(&self) -> &'static str {
101 match self {
102 Self::APlus => "Exceptional",
103 Self::A => "Excellent",
104 Self::AMinus => "Very Good",
105 Self::BPlus => "Good+",
106 Self::B => "Good",
107 Self::BMinus => "Above Average",
108 Self::CPlus => "Average+",
109 Self::C => "Average",
110 Self::CMinus => "Below Average",
111 Self::D => "Poor",
112 Self::F => "Unacceptable",
113 }
114 }
115}
116
117#[must_use]
120pub fn grade_ping(ping_ms: f64, profile: UserProfile) -> LetterGrade {
121 let excellent = profile.excellent_ping_threshold();
122 let good = excellent * 3.0;
123 let average = excellent * 6.0;
124
125 if ping_ms <= excellent * 0.5 {
126 LetterGrade::APlus
127 } else if ping_ms <= excellent {
128 LetterGrade::A
129 } else if ping_ms <= excellent * 1.5 {
130 LetterGrade::AMinus
131 } else if ping_ms <= good {
132 LetterGrade::B
133 } else if ping_ms <= good * 1.5 {
134 LetterGrade::C
135 } else if ping_ms <= average {
136 LetterGrade::D
137 } else {
138 LetterGrade::F
139 }
140}
141
142#[must_use]
144pub fn grade_jitter(jitter_ms: f64, profile: UserProfile) -> LetterGrade {
145 let excellent = profile.excellent_jitter_threshold();
146 let good = excellent * 3.0;
147 let average = excellent * 8.0;
148
149 if jitter_ms <= excellent * 0.5 {
150 LetterGrade::APlus
151 } else if jitter_ms <= excellent {
152 LetterGrade::A
153 } else if jitter_ms <= excellent * 2.0 {
154 LetterGrade::B
155 } else if jitter_ms <= good {
156 LetterGrade::C
157 } else if jitter_ms <= average {
158 LetterGrade::D
159 } else {
160 LetterGrade::F
161 }
162}
163
164#[must_use]
166pub fn grade_download(speed_mbps: f64, profile: UserProfile) -> LetterGrade {
167 let excellent = profile.excellent_speed_threshold();
168 let good = excellent * 0.4;
169 let average = excellent * 0.15;
170
171 if speed_mbps >= excellent * 2.0 {
172 LetterGrade::APlus
173 } else if speed_mbps >= excellent {
174 LetterGrade::A
175 } else if speed_mbps >= excellent * 0.75 {
176 LetterGrade::B
177 } else if speed_mbps >= good {
178 LetterGrade::C
179 } else if speed_mbps >= average {
180 LetterGrade::D
181 } else {
182 LetterGrade::F
183 }
184}
185
186#[must_use]
188pub fn grade_upload(speed_mbps: f64, profile: UserProfile) -> LetterGrade {
189 let excellent = profile.excellent_speed_threshold() * 0.5;
191 let good = excellent * 0.4;
192 let average = excellent * 0.15;
193
194 if speed_mbps >= excellent * 2.0 {
195 LetterGrade::APlus
196 } else if speed_mbps >= excellent {
197 LetterGrade::A
198 } else if speed_mbps >= excellent * 0.75 {
199 LetterGrade::B
200 } else if speed_mbps >= good {
201 LetterGrade::C
202 } else if speed_mbps >= average {
203 LetterGrade::D
204 } else {
205 LetterGrade::F
206 }
207}
208
209#[must_use]
211pub fn grade_bufferbloat(added_latency_ms: f64) -> LetterGrade {
212 if added_latency_ms < 3.0 {
213 LetterGrade::APlus
214 } else if added_latency_ms < 5.0 {
215 LetterGrade::A
216 } else if added_latency_ms < 10.0 {
217 LetterGrade::AMinus
218 } else if added_latency_ms < 20.0 {
219 LetterGrade::BPlus
220 } else if added_latency_ms < 30.0 {
221 LetterGrade::B
222 } else if added_latency_ms < 50.0 {
223 LetterGrade::C
224 } else if added_latency_ms < 100.0 {
225 LetterGrade::D
226 } else {
227 LetterGrade::F
228 }
229}
230
231#[must_use]
233pub fn grade_stability(cv_pct: f64) -> LetterGrade {
234 if cv_pct < 3.0 {
235 LetterGrade::APlus
236 } else if cv_pct < 5.0 {
237 LetterGrade::A
238 } else if cv_pct < 8.0 {
239 LetterGrade::B
240 } else if cv_pct < 15.0 {
241 LetterGrade::C
242 } else if cv_pct < 25.0 {
243 LetterGrade::D
244 } else {
245 LetterGrade::F
246 }
247}
248
249#[must_use]
251pub fn grade_overall(
252 ping: Option<f64>,
253 jitter: Option<f64>,
254 download_bps: Option<f64>,
255 upload_bps: Option<f64>,
256 profile: UserProfile,
257) -> LetterGrade {
258 let (ping_w, jitter_w, dl_w, ul_w) = profile.scoring_weights();
259 let mut total_score = 0.0;
260 let mut total_weight = 0.0;
261
262 if let Some(p) = ping {
263 total_score += grade_ping(p, profile).score() * ping_w;
264 total_weight += ping_w;
265 }
266 if let Some(j) = jitter {
267 total_score += grade_jitter(j, profile).score() * jitter_w;
268 total_weight += jitter_w;
269 }
270 if let Some(dl) = download_bps {
271 total_score += grade_download(dl / 1_000_000.0, profile).score() * dl_w;
272 total_weight += dl_w;
273 }
274 if let Some(ul) = upload_bps {
275 total_score += grade_upload(ul / 1_000_000.0, profile).score() * ul_w;
276 total_weight += ul_w;
277 }
278
279 if total_weight == 0.0 {
280 return LetterGrade::F;
281 }
282
283 let avg_score = total_score / total_weight;
284 score_to_grade(avg_score)
285}
286
287#[must_use]
289pub fn score_to_grade(score: f64) -> LetterGrade {
290 if score >= 97.0 {
291 LetterGrade::APlus
292 } else if score >= 93.0 {
293 LetterGrade::A
294 } else if score >= 90.0 {
295 LetterGrade::AMinus
296 } else if score >= 87.0 {
297 LetterGrade::BPlus
298 } else if score >= 80.0 {
299 LetterGrade::B
300 } else if score >= 77.0 {
301 LetterGrade::BMinus
302 } else if score >= 70.0 {
303 LetterGrade::CPlus
304 } else if score >= 65.0 {
305 LetterGrade::C
306 } else if score >= 60.0 {
307 LetterGrade::CMinus
308 } else if score >= 50.0 {
309 LetterGrade::D
310 } else {
311 LetterGrade::F
312 }
313}
314
315#[must_use]
317pub fn format_grade_line(
318 label: &str,
319 grade: LetterGrade,
320 value: Option<&str>,
321 nc: bool,
322 theme: Theme,
323) -> String {
324 let emoji = grade.emoji();
325 let grade_display = grade.color_str(nc, theme);
326 let value_str = value.map(|v| format!(" ({v})")).unwrap_or_default();
327
328 if nc || terminal::no_emoji() {
329 format!(" {label:>14}: {grade_display}{value_str}")
330 } else {
331 format!(" {label:>14}: {emoji} {grade_display}{value_str}")
332 }
333}
334
335#[must_use]
336pub fn grade_badge(grade: LetterGrade, nc: bool, theme: Theme) -> String {
337 let emoji = grade.emoji();
338 let grade_display = grade.color_str(nc, theme);
339 if nc {
340 format!("[{grade_display}]")
341 } else if terminal::no_emoji() {
342 grade_display.clone()
343 } else {
344 format!("{emoji} {grade_display}")
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::theme::Theme;
352
353 #[test]
356 fn test_letter_grade_ordering() {
357 assert!(LetterGrade::APlus.score() > LetterGrade::A.score());
360 assert!(LetterGrade::A.score() > LetterGrade::AMinus.score());
361 assert!(LetterGrade::B.score() > LetterGrade::C.score());
362 assert!(LetterGrade::F.score() < LetterGrade::D.score());
363 }
364
365 #[test]
366 fn test_letter_grade_as_str_all_variants() {
367 assert_eq!(LetterGrade::APlus.as_str(), "A+");
368 assert_eq!(LetterGrade::A.as_str(), "A");
369 assert_eq!(LetterGrade::AMinus.as_str(), "A-");
370 assert_eq!(LetterGrade::BPlus.as_str(), "B+");
371 assert_eq!(LetterGrade::B.as_str(), "B");
372 assert_eq!(LetterGrade::BMinus.as_str(), "B-");
373 assert_eq!(LetterGrade::CPlus.as_str(), "C+");
374 assert_eq!(LetterGrade::C.as_str(), "C");
375 assert_eq!(LetterGrade::CMinus.as_str(), "C-");
376 assert_eq!(LetterGrade::D.as_str(), "D");
377 assert_eq!(LetterGrade::F.as_str(), "F");
378 }
379
380 #[test]
381 fn test_letter_grade_score_all_variants() {
382 assert_eq!(LetterGrade::APlus.score(), 100.0);
383 assert_eq!(LetterGrade::A.score(), 95.0);
384 assert_eq!(LetterGrade::AMinus.score(), 90.0);
385 assert_eq!(LetterGrade::BPlus.score(), 85.0);
386 assert_eq!(LetterGrade::B.score(), 80.0);
387 assert_eq!(LetterGrade::BMinus.score(), 75.0);
388 assert_eq!(LetterGrade::CPlus.score(), 70.0);
389 assert_eq!(LetterGrade::C.score(), 65.0);
390 assert_eq!(LetterGrade::CMinus.score(), 60.0);
391 assert_eq!(LetterGrade::D.score(), 50.0);
392 assert_eq!(LetterGrade::F.score(), 25.0);
393 }
394
395 #[test]
396 fn test_letter_grade_description_all_variants() {
397 assert_eq!(LetterGrade::APlus.description(), "Exceptional");
398 assert_eq!(LetterGrade::A.description(), "Excellent");
399 assert_eq!(LetterGrade::AMinus.description(), "Very Good");
400 assert_eq!(LetterGrade::BPlus.description(), "Good+");
401 assert_eq!(LetterGrade::B.description(), "Good");
402 assert_eq!(LetterGrade::BMinus.description(), "Above Average");
403 assert_eq!(LetterGrade::CPlus.description(), "Average+");
404 assert_eq!(LetterGrade::C.description(), "Average");
405 assert_eq!(LetterGrade::CMinus.description(), "Below Average");
406 assert_eq!(LetterGrade::D.description(), "Poor");
407 assert_eq!(LetterGrade::F.description(), "Unacceptable");
408 }
409
410 #[test]
411 fn test_letter_grade_color_str_nc_mode() {
412 assert_eq!(LetterGrade::A.color_str(true, Theme::Dark), "[A]");
414 assert_eq!(LetterGrade::F.color_str(true, Theme::Dark), "[F]");
415 assert_eq!(LetterGrade::BPlus.color_str(true, Theme::Dark), "[B+]");
416 }
417
418 #[test]
419 fn test_letter_grade_color_str_all_grades() {
420 for grade in &[
422 LetterGrade::APlus,
423 LetterGrade::A,
424 LetterGrade::AMinus,
425 LetterGrade::BPlus,
426 LetterGrade::B,
427 LetterGrade::BMinus,
428 LetterGrade::CPlus,
429 LetterGrade::C,
430 LetterGrade::CMinus,
431 LetterGrade::D,
432 LetterGrade::F,
433 ] {
434 let colored = grade.color_str(false, Theme::Dark);
435 assert!(
436 !colored.is_empty(),
437 "Grade {:?} should have color output",
438 grade
439 );
440 assert!(
441 colored.contains(grade.as_str()),
442 "Colored output should contain grade string"
443 );
444 }
445 }
446
447 #[test]
448 fn test_letter_grade_color_str_different_themes() {
449 let grade = LetterGrade::A;
450 let dark = grade.color_str(false, Theme::Dark);
452 let light = grade.color_str(false, Theme::Light);
453 assert!(!dark.is_empty());
454 assert!(!light.is_empty());
455 assert!(dark.contains("A"));
457 assert!(light.contains("A"));
458 }
459
460 #[test]
463 fn test_grade_ping_excellent() {
464 let p = UserProfile::PowerUser;
465 assert_eq!(grade_ping(3.0, p), LetterGrade::APlus);
468 assert_eq!(grade_ping(5.0, p), LetterGrade::APlus);
469 assert_eq!(grade_ping(6.0, p), LetterGrade::A);
471 assert_eq!(grade_ping(10.0, p), LetterGrade::A);
472 assert_eq!(grade_ping(11.0, p), LetterGrade::AMinus);
474 assert_eq!(grade_ping(15.0, p), LetterGrade::AMinus);
475 }
476
477 #[test]
478 fn test_grade_ping_good_to_fail() {
479 let p = UserProfile::PowerUser;
480 assert_eq!(grade_ping(20.0, p), LetterGrade::B);
482 assert_eq!(grade_ping(30.0, p), LetterGrade::B);
483 assert_eq!(grade_ping(40.0, p), LetterGrade::C);
485 assert_eq!(grade_ping(45.0, p), LetterGrade::C);
486 assert_eq!(grade_ping(50.0, p), LetterGrade::D);
488 assert_eq!(grade_ping(60.0, p), LetterGrade::D);
489 assert_eq!(grade_ping(61.0, p), LetterGrade::F);
491 assert_eq!(grade_ping(500.0, p), LetterGrade::F);
492 }
493
494 #[test]
495 fn test_grade_ping_gamer_profile() {
496 let g = UserProfile::Gamer;
497 assert_eq!(grade_ping(2.0, g), LetterGrade::APlus);
500 assert_eq!(grade_ping(3.0, g), LetterGrade::A);
502 assert_eq!(grade_ping(5.0, g), LetterGrade::A);
503 assert_eq!(grade_ping(7.0, g), LetterGrade::AMinus);
505 assert_eq!(grade_ping(10.0, g), LetterGrade::B);
507 assert_eq!(grade_ping(100.0, g), LetterGrade::F);
509 }
510
511 #[test]
512 fn test_grade_ping_streamer_profile() {
513 let s = UserProfile::Streamer;
514 assert_eq!(grade_ping(10.0, s), LetterGrade::APlus);
517 assert_eq!(grade_ping(30.0, s), LetterGrade::A);
518 assert_eq!(grade_ping(200.0, s), LetterGrade::F);
519 }
520
521 #[test]
524 fn test_grade_jitter_excellent() {
525 let p = UserProfile::PowerUser;
526 assert_eq!(grade_jitter(0.5, p), LetterGrade::APlus);
529 assert_eq!(grade_jitter(1.0, p), LetterGrade::APlus);
530 assert_eq!(grade_jitter(1.5, p), LetterGrade::A);
532 assert_eq!(grade_jitter(2.0, p), LetterGrade::A);
533 assert_eq!(grade_jitter(3.0, p), LetterGrade::B);
535 assert_eq!(grade_jitter(4.0, p), LetterGrade::B);
536 }
537
538 #[test]
539 fn test_grade_jitter_good_to_fail() {
540 let p = UserProfile::PowerUser;
541 assert_eq!(grade_jitter(5.0, p), LetterGrade::C);
543 assert_eq!(grade_jitter(10.0, p), LetterGrade::D);
545 assert_eq!(grade_jitter(16.0, p), LetterGrade::D);
546 assert_eq!(grade_jitter(50.0, p), LetterGrade::F);
548 }
549
550 #[test]
553 fn test_grade_download_excellent() {
554 let p = UserProfile::PowerUser;
555 assert_eq!(grade_download(1000.0, p), LetterGrade::APlus);
558 assert_eq!(grade_download(500.0, p), LetterGrade::A);
559 assert_eq!(grade_download(400.0, p), LetterGrade::B);
561 assert_eq!(grade_download(375.0, p), LetterGrade::B);
562 }
563
564 #[test]
565 fn test_grade_download_good_to_fail() {
566 let p = UserProfile::PowerUser;
567 assert_eq!(grade_download(200.0, p), LetterGrade::C);
569 assert_eq!(grade_download(75.0, p), LetterGrade::D);
571 assert_eq!(grade_download(50.0, p), LetterGrade::F);
573 assert_eq!(grade_download(1.0, p), LetterGrade::F);
574 }
575
576 #[test]
577 fn test_grade_download_streamer_profile() {
578 let s = UserProfile::Streamer;
579 assert_eq!(grade_download(400.0, s), LetterGrade::APlus);
583 assert_eq!(grade_download(200.0, s), LetterGrade::A);
584 assert_eq!(grade_download(100.0, s), LetterGrade::C);
586 assert_eq!(grade_download(80.0, s), LetterGrade::C);
588 assert_eq!(grade_download(40.0, s), LetterGrade::D);
589 assert_eq!(grade_download(10.0, s), LetterGrade::F);
590 }
591
592 #[test]
593 fn test_grade_download_gamer_profile() {
594 let g = UserProfile::Gamer;
595 assert_eq!(grade_download(200.0, g), LetterGrade::APlus);
597 assert_eq!(grade_download(100.0, g), LetterGrade::A);
598 assert_eq!(grade_download(50.0, g), LetterGrade::C);
599 }
600
601 #[test]
604 fn test_grade_upload_excellent() {
605 let p = UserProfile::PowerUser;
606 assert_eq!(grade_upload(500.0, p), LetterGrade::APlus);
608 assert_eq!(grade_upload(250.0, p), LetterGrade::A);
609 assert_eq!(grade_upload(200.0, p), LetterGrade::B);
611 }
612
613 #[test]
614 fn test_grade_upload_good_to_fail() {
615 let p = UserProfile::PowerUser;
616 assert_eq!(grade_upload(100.0, p), LetterGrade::C);
618 assert_eq!(grade_upload(40.0, p), LetterGrade::D);
620 assert_eq!(grade_upload(1.0, p), LetterGrade::F);
622 }
623
624 #[test]
627 fn test_grade_bufferbloat_all_levels() {
628 assert_eq!(grade_bufferbloat(0.0), LetterGrade::APlus);
630 assert_eq!(grade_bufferbloat(2.0), LetterGrade::APlus);
631 assert_eq!(grade_bufferbloat(3.0), LetterGrade::A);
633 assert_eq!(grade_bufferbloat(4.0), LetterGrade::A);
634 assert_eq!(grade_bufferbloat(5.0), LetterGrade::AMinus);
636 assert_eq!(grade_bufferbloat(8.0), LetterGrade::AMinus);
637 assert_eq!(grade_bufferbloat(15.0), LetterGrade::BPlus);
639 assert_eq!(grade_bufferbloat(25.0), LetterGrade::B);
641 assert_eq!(grade_bufferbloat(40.0), LetterGrade::C);
643 assert_eq!(grade_bufferbloat(75.0), LetterGrade::D);
645 assert_eq!(grade_bufferbloat(100.0), LetterGrade::F);
647 assert_eq!(grade_bufferbloat(200.0), LetterGrade::F);
648 }
649
650 #[test]
651 fn test_grade_bufferbloat_boundary_cases() {
652 assert_eq!(grade_bufferbloat(2.999), LetterGrade::APlus);
654 assert_eq!(grade_bufferbloat(3.001), LetterGrade::A);
655 assert_eq!(grade_bufferbloat(4.999), LetterGrade::A);
656 assert_eq!(grade_bufferbloat(5.001), LetterGrade::AMinus);
657 assert_eq!(grade_bufferbloat(9.999), LetterGrade::AMinus);
658 assert_eq!(grade_bufferbloat(10.001), LetterGrade::BPlus);
659 }
660
661 #[test]
664 fn test_grade_stability_all_levels() {
665 assert_eq!(grade_stability(0.0), LetterGrade::APlus);
667 assert_eq!(grade_stability(2.0), LetterGrade::APlus);
668 assert_eq!(grade_stability(3.0), LetterGrade::A);
669 assert_eq!(grade_stability(4.0), LetterGrade::A);
670 assert_eq!(grade_stability(5.0), LetterGrade::B);
671 assert_eq!(grade_stability(7.0), LetterGrade::B);
672 assert_eq!(grade_stability(10.0), LetterGrade::C);
673 assert_eq!(grade_stability(20.0), LetterGrade::D);
674 assert_eq!(grade_stability(50.0), LetterGrade::F);
675 }
676
677 #[test]
680 fn test_score_to_grade_all_levels() {
681 assert_eq!(score_to_grade(97.0), LetterGrade::APlus);
683 assert_eq!(score_to_grade(100.0), LetterGrade::APlus);
684 assert_eq!(score_to_grade(93.0), LetterGrade::A);
686 assert_eq!(score_to_grade(96.99), LetterGrade::A);
687 assert_eq!(score_to_grade(90.0), LetterGrade::AMinus);
689 assert_eq!(score_to_grade(92.99), LetterGrade::AMinus);
690 assert_eq!(score_to_grade(87.0), LetterGrade::BPlus);
692 assert_eq!(score_to_grade(89.99), LetterGrade::BPlus);
693 assert_eq!(score_to_grade(80.0), LetterGrade::B);
695 assert_eq!(score_to_grade(86.99), LetterGrade::B);
696 assert_eq!(score_to_grade(77.0), LetterGrade::BMinus);
698 assert_eq!(score_to_grade(79.99), LetterGrade::BMinus);
699 assert_eq!(score_to_grade(70.0), LetterGrade::CPlus);
701 assert_eq!(score_to_grade(76.99), LetterGrade::CPlus);
702 assert_eq!(score_to_grade(65.0), LetterGrade::C);
704 assert_eq!(score_to_grade(69.99), LetterGrade::C);
705 assert_eq!(score_to_grade(60.0), LetterGrade::CMinus);
707 assert_eq!(score_to_grade(64.99), LetterGrade::CMinus);
708 assert_eq!(score_to_grade(50.0), LetterGrade::D);
710 assert_eq!(score_to_grade(59.99), LetterGrade::D);
711 assert_eq!(score_to_grade(49.99), LetterGrade::F);
713 assert_eq!(score_to_grade(0.0), LetterGrade::F);
714 }
715
716 #[test]
717 fn test_score_to_grade_boundary_cases() {
718 assert_eq!(score_to_grade(96.99), LetterGrade::A); assert_eq!(score_to_grade(92.99), LetterGrade::AMinus); assert_eq!(score_to_grade(86.99), LetterGrade::B); assert_eq!(score_to_grade(79.99), LetterGrade::BMinus); assert_eq!(score_to_grade(76.99), LetterGrade::CPlus); assert_eq!(score_to_grade(69.99), LetterGrade::C); assert_eq!(score_to_grade(64.99), LetterGrade::CMinus); assert_eq!(score_to_grade(59.99), LetterGrade::D); }
728
729 #[test]
732 fn test_grade_overall_all_none() {
733 let p = UserProfile::PowerUser;
734 let grade = grade_overall(None, None, None, None, p);
736 assert_eq!(grade, LetterGrade::F);
737 }
738
739 #[test]
740 fn test_grade_overall_excellent() {
741 let p = UserProfile::PowerUser;
742 let grade = grade_overall(
743 Some(5.0), Some(1.0), Some(600_000_000.0), Some(300_000_000.0), p,
748 );
749 assert!(grade.score() >= LetterGrade::A.score());
750 }
751
752 #[test]
753 fn test_grade_overall_poor() {
754 let p = UserProfile::PowerUser;
755 let grade = grade_overall(
756 Some(200.0), Some(50.0), Some(5_000_000.0), Some(1_000_000.0), p,
761 );
762 assert!(grade.score() <= LetterGrade::F.score());
763 }
764
765 #[test]
766 fn test_grade_overall_partial_data() {
767 let p = UserProfile::PowerUser;
768 let grade = grade_overall(Some(5.0), None, Some(600_000_000.0), None, p);
770 assert!(grade.score() >= LetterGrade::A.score());
771
772 let grade = grade_overall(None, None, Some(600_000_000.0), None, p);
774 assert!(grade.score() >= LetterGrade::A.score());
775
776 let grade = grade_overall(Some(5.0), None, None, None, p);
778 assert!(grade.score() >= LetterGrade::A.score());
779
780 let grade = grade_overall(None, Some(1.0), None, None, p);
782 assert!(grade.score() >= LetterGrade::A.score());
783 }
784
785 #[test]
786 fn test_grade_overall_mixed_quality() {
787 let p = UserProfile::PowerUser;
788 let grade = grade_overall(Some(5.0), None, Some(5_000_000.0), None, p);
790 assert!(grade.score() <= LetterGrade::D.score());
792 assert!(grade.score() >= LetterGrade::F.score());
793 }
794
795 #[test]
798 fn test_format_grade_line_with_value() {
799 let grade = LetterGrade::A;
800 let line = format_grade_line("Ping", grade, Some("5.2 ms"), false, Theme::Dark);
801 assert!(line.contains("Ping"));
802 assert!(line.contains("5.2 ms"));
803 assert!(line.contains("A"));
804 }
805
806 #[test]
807 fn test_format_grade_line_without_value() {
808 let grade = LetterGrade::B;
809 let line = format_grade_line("Jitter", grade, None, false, Theme::Dark);
810 assert!(line.contains("Jitter"));
811 assert!(line.contains("B"));
812 assert!(!line.contains("()"));
814 }
815
816 #[test]
817 fn test_format_grade_line_nc_mode() {
818 let grade = LetterGrade::C;
819 let line = format_grade_line("Download", grade, Some("100 Mbps"), true, Theme::Dark);
820 assert!(line.contains("["));
822 assert!(line.contains("]"));
823 }
824
825 #[test]
826 fn test_format_grade_line_all_grade_levels() {
827 let metrics = ["Ping", "Jitter", "Download", "Upload", "Bufferbloat"];
829 for grade in &[
830 LetterGrade::APlus,
831 LetterGrade::A,
832 LetterGrade::B,
833 LetterGrade::C,
834 LetterGrade::D,
835 LetterGrade::F,
836 ] {
837 for metric in &metrics {
838 let line = format_grade_line(metric, *grade, Some("value"), false, Theme::Dark);
839 assert!(!line.is_empty());
840 }
841 }
842 }
843
844 #[test]
847 fn test_grade_badge_nc_mode() {
848 let badge = grade_badge(LetterGrade::A, true, Theme::Dark);
849 assert!(badge.starts_with('['));
851 assert!(badge.ends_with(']'));
852 assert!(badge.contains('A'));
853 }
854
855 #[test]
856 fn test_grade_badge_colored_mode() {
857 let badge = grade_badge(LetterGrade::B, false, Theme::Dark);
858 assert!(badge.contains('B'));
859 assert!(!badge.is_empty());
860 }
861
862 #[test]
863 fn test_grade_badge_all_grades() {
864 for grade in &[
865 LetterGrade::APlus,
866 LetterGrade::A,
867 LetterGrade::B,
868 LetterGrade::C,
869 LetterGrade::F,
870 ] {
871 let badge = grade_badge(*grade, false, Theme::Dark);
872 assert!(!badge.is_empty());
873 assert!(badge.contains(grade.as_str()));
874 }
875 }
876
877 #[test]
880 fn test_letter_grade_is_copy() {
881 let grade = LetterGrade::BPlus;
882 let _copied = grade; assert_eq!(grade, _copied);
884 }
885
886 #[test]
889 fn test_letter_grade_debug() {
890 let grade = LetterGrade::C;
891 let debug_str = format!("{:?}", grade);
892 assert!(debug_str.contains("C"));
893 }
894
895 #[test]
898 fn test_letter_grade_partial_eq() {
899 assert_eq!(LetterGrade::A, LetterGrade::A);
900 assert_ne!(LetterGrade::A, LetterGrade::B);
901 assert_eq!(LetterGrade::APlus, LetterGrade::APlus);
902 }
903
904 #[test]
907 fn test_letter_grade_ord() {
908 assert!(LetterGrade::APlus < LetterGrade::A);
909 assert!(LetterGrade::A < LetterGrade::B);
910 assert!(LetterGrade::B < LetterGrade::C);
911 assert!(LetterGrade::C < LetterGrade::D);
912 assert!(LetterGrade::D < LetterGrade::F);
913 }
914}