1use std::io::Write;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum Color {
5 Named(u8),
6 RGB(u8, u8, u8),
7}
8
9impl Color {
10 pub const BLACK: Color = Color::Named(0);
11 pub const RED: Color = Color::Named(1);
12 pub const GREEN: Color = Color::Named(2);
13 pub const YELLOW: Color = Color::Named(3);
14 pub const BLUE: Color = Color::Named(4);
15 pub const MAGENTA: Color = Color::Named(5);
16 pub const CYAN: Color = Color::Named(6);
17 pub const WHITE: Color = Color::Named(7);
18 pub const BRIGHT_BLACK: Color = Color::Named(8);
19 pub const BRIGHT_RED: Color = Color::Named(9);
20 pub const BRIGHT_GREEN: Color = Color::Named(10);
21 pub const BRIGHT_YELLOW: Color = Color::Named(11);
22 pub const BRIGHT_BLUE: Color = Color::Named(12);
23 pub const BRIGHT_MAGENTA: Color = Color::Named(13);
24 pub const BRIGHT_CYAN: Color = Color::Named(14);
25 pub const BRIGHT_WHITE: Color = Color::Named(15);
26
27 fn to_fg_code(&self) -> String {
28 match self {
29 Color::Named(n) => format!("38;5;{}", n),
30 Color::RGB(r, g, b) => format!("38;2;{};{};{}", r, g, b),
31 }
32 }
33
34 fn to_bg_code(&self) -> String {
35 match self {
36 Color::Named(n) => format!("48;5;{}", n),
37 Color::RGB(r, g, b) => format!("48;2;{};{};{}", r, g, b),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Attribute {
44 Bold,
45 Dim,
46 Italic,
47 Underline,
48 Blink,
49 Reverse,
50 Hidden,
51 Strikethrough,
52}
53
54impl Attribute {
55 fn to_code(&self) -> &'static str {
56 match self {
57 Attribute::Bold => "1",
58 Attribute::Dim => "2",
59 Attribute::Italic => "3",
60 Attribute::Underline => "4",
61 Attribute::Blink => "5",
62 Attribute::Reverse => "7",
63 Attribute::Hidden => "8",
64 Attribute::Strikethrough => "9",
65 }
66 }
67}
68
69#[derive(Clone, Default)]
70pub struct Style {
71 foreground: Option<Color>,
72 background: Option<Color>,
73 attributes: Vec<Attribute>,
74}
75
76impl Style {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn fg(mut self, color: Color) -> Self {
82 self.foreground = Some(color);
83 self
84 }
85
86 pub fn bg(mut self, color: Color) -> Self {
87 self.background = Some(color);
88 self
89 }
90
91 pub fn attr(mut self, attribute: Attribute) -> Self {
92 self.attributes.push(attribute);
93 self
94 }
95
96 pub fn bold(self) -> Self {
97 self.attr(Attribute::Bold)
98 }
99
100 pub fn dim(self) -> Self {
101 self.attr(Attribute::Dim)
102 }
103
104 pub fn italic(self) -> Self {
105 self.attr(Attribute::Italic)
106 }
107
108 pub fn underline(self) -> Self {
109 self.attr(Attribute::Underline)
110 }
111
112 pub fn blink(self) -> Self {
113 self.attr(Attribute::Blink)
114 }
115
116 pub fn reverse(self) -> Self {
117 self.attr(Attribute::Reverse)
118 }
119
120 pub fn hidden(self) -> Self {
121 self.attr(Attribute::Hidden)
122 }
123
124 pub fn strikethrough(self) -> Self {
125 self.attr(Attribute::Strikethrough)
126 }
127
128 #[cfg(not(feature = "no-color"))]
129 fn to_ansi_start(&self) -> String {
130 let mut codes: Vec<String> = Vec::new();
131
132 for attr in &self.attributes {
133 codes.push(attr.to_code().into());
134 }
135
136 if let Some(fg) = &self.foreground {
137 codes.push(fg.to_fg_code());
138 }
139
140 if let Some(bg) = &self.background {
141 codes.push(bg.to_bg_code());
142 }
143
144 if codes.is_empty() {
145 String::new()
146 } else {
147 format!("\x1b[{}m", codes.join(";"))
148 }
149 }
150
151 #[cfg(feature = "no-color")]
152 fn to_ansi_start(&self) -> String {
153 String::new()
154 }
155}
156
157#[derive(Clone)]
158pub struct Console {
159 text: String,
160 style: Style,
161}
162
163impl Console {
164 pub fn new<T: Into<String>>(text: T) -> Self {
165 Console {
166 text: text.into(),
167 style: Style::default(),
168 }
169 }
170
171 pub fn new_with_style<T: Into<String>>(text: T, style: Style) -> Self {
172 Console {
173 text: text.into(),
174 style,
175 }
176 }
177
178 pub fn with_text<T: Into<String>>(&self, text: T) -> Self {
179 Console {
180 text: text.into(),
181 style: self.style.clone(),
182 }
183 }
184
185 pub fn fg(self, color: Color) -> Self {
187 Console {
188 style: self.style.fg(color),
189 ..self
190 }
191 }
192
193 pub fn bg(self, color: Color) -> Self {
194 Console {
195 style: self.style.bg(color),
196 ..self
197 }
198 }
199
200 pub fn fg_rgb(self, r: u8, g: u8, b: u8) -> Self {
201 self.fg(Color::RGB(r, g, b))
202 }
203
204 pub fn bg_rgb(self, r: u8, g: u8, b: u8) -> Self {
205 self.bg(Color::RGB(r, g, b))
206 }
207
208 pub fn black(self) -> Self {
210 self.fg(Color::BLACK)
211 }
212
213 pub fn red(self) -> Self {
214 self.fg(Color::RED)
215 }
216
217 pub fn green(self) -> Self {
218 self.fg(Color::GREEN)
219 }
220
221 pub fn yellow(self) -> Self {
222 self.fg(Color::YELLOW)
223 }
224
225 pub fn blue(self) -> Self {
226 self.fg(Color::BLUE)
227 }
228 pub fn magenta(self) -> Self {
229 self.fg(Color::MAGENTA)
230 }
231
232 pub fn cyan(self) -> Self {
233 self.fg(Color::CYAN)
234 }
235
236 pub fn white(self) -> Self {
237 self.fg(Color::WHITE)
238 }
239
240 pub fn bright_black(self) -> Self {
241 self.fg(Color::BRIGHT_BLACK)
242 }
243
244 pub fn bright_red(self) -> Self {
245 self.fg(Color::BRIGHT_RED)
246 }
247
248 pub fn bright_green(self) -> Self {
249 self.fg(Color::BRIGHT_GREEN)
250 }
251
252 pub fn bright_yellow(self) -> Self {
253 self.fg(Color::BRIGHT_YELLOW)
254 }
255
256 pub fn bright_blue(self) -> Self {
257 self.fg(Color::BRIGHT_BLUE)
258 }
259
260 pub fn bright_magenta(self) -> Self {
261 self.fg(Color::BRIGHT_MAGENTA)
262 }
263
264 pub fn bright_cyan(self) -> Self {
265 self.fg(Color::BRIGHT_CYAN)
266 }
267
268 pub fn bright_white(self) -> Self {
269 self.fg(Color::BRIGHT_WHITE)
270 }
271
272 pub fn on_black(self) -> Self {
274 self.bg(Color::BLACK)
275 }
276
277 pub fn on_red(self) -> Self {
278 self.bg(Color::RED)
279 }
280
281 pub fn on_green(self) -> Self {
282 self.bg(Color::GREEN)
283 }
284
285 pub fn on_yellow(self) -> Self {
286 self.bg(Color::YELLOW)
287 }
288
289 pub fn on_blue(self) -> Self {
290 self.bg(Color::BLUE)
291 }
292
293 pub fn on_magenta(self) -> Self {
294 self.bg(Color::MAGENTA)
295 }
296
297 pub fn on_cyan(self) -> Self {
298 self.bg(Color::CYAN)
299 }
300
301 pub fn on_white(self) -> Self {
302 self.bg(Color::WHITE)
303 }
304
305 pub fn on_bright_black(self) -> Self {
306 self.bg(Color::BRIGHT_BLACK)
307 }
308
309 pub fn on_bright_red(self) -> Self {
310 self.bg(Color::BRIGHT_RED)
311 }
312
313 pub fn on_bright_green(self) -> Self {
314 self.bg(Color::BRIGHT_GREEN)
315 }
316
317 pub fn on_bright_yellow(self) -> Self {
318 self.bg(Color::BRIGHT_YELLOW)
319 }
320
321 pub fn on_bright_blue(self) -> Self {
322 self.bg(Color::BRIGHT_BLUE)
323 }
324
325 pub fn on_bright_magenta(self) -> Self {
326 self.bg(Color::BRIGHT_MAGENTA)
327 }
328
329 pub fn on_bright_cyan(self) -> Self {
330 self.bg(Color::BRIGHT_CYAN)
331 }
332
333 pub fn on_bright_white(self) -> Self {
334 self.bg(Color::BRIGHT_WHITE)
335 }
336
337 pub fn attr(self, attribute: Attribute) -> Self {
339 Console {
340 style: self.style.attr(attribute),
341 ..self
342 }
343 }
344
345 pub fn bold(self) -> Self {
346 Console {
347 style: self.style.bold(),
348 ..self
349 }
350 }
351
352 pub fn dim(self) -> Self {
353 Console {
354 style: self.style.dim(),
355 ..self
356 }
357 }
358
359 pub fn italic(self) -> Self {
360 Console {
361 style: self.style.italic(),
362 ..self
363 }
364 }
365
366 pub fn underline(self) -> Self {
367 Console {
368 style: self.style.underline(),
369 ..self
370 }
371 }
372
373 pub fn blink(self) -> Self {
374 Console {
375 style: self.style.blink(),
376 ..self
377 }
378 }
379
380 pub fn reverse(self) -> Self {
381 Console {
382 style: self.style.reverse(),
383 ..self
384 }
385 }
386
387 pub fn hidden(self) -> Self {
388 Console {
389 style: self.style.hidden(),
390 ..self
391 }
392 }
393
394 pub fn strikethrough(self) -> Self {
395 Console {
396 style: self.style.strikethrough(),
397 ..self
398 }
399 }
400
401 pub fn print(&self) {
403 let mut stdout = std::io::stdout();
404 self.write_to(&mut stdout).unwrap();
405 }
406
407 pub fn println(&self) {
408 let mut stdout = std::io::stdout();
409 self.write_to(&mut stdout).unwrap();
410 writeln!(stdout).unwrap();
411 }
412
413 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
414 let ansi_code = self.style.to_ansi_start();
415 if !ansi_code.is_empty() {
416 write!(writer, "{}", ansi_code)?;
417 }
418 write!(writer, "{}", self.text)?;
419 if !ansi_code.is_empty() {
420 write!(writer, "\x1b[0m")?;
421 }
422 Ok(())
423 }
424
425 pub fn to_string(&self) -> String {
426 format!("{}", self)
427 }
428}
429
430impl std::fmt::Display for Console {
431 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
432 let ansi_code = self.style.to_ansi_start();
433 if !ansi_code.is_empty() {
434 write!(f, "{}", ansi_code)?;
435 }
436 write!(f, "{}", self.text)?;
437 if !ansi_code.is_empty() {
438 write!(f, "\x1b[0m")?;
439 }
440 Ok(())
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn test_color_constants() {
450 assert_eq!(Color::BLACK, Color::Named(0));
451 assert_eq!(Color::RED, Color::Named(1));
452 assert_eq!(Color::GREEN, Color::Named(2));
453 assert_eq!(Color::YELLOW, Color::Named(3));
454 assert_eq!(Color::BLUE, Color::Named(4));
455 assert_eq!(Color::MAGENTA, Color::Named(5));
456 assert_eq!(Color::CYAN, Color::Named(6));
457 assert_eq!(Color::WHITE, Color::Named(7));
458 assert_eq!(Color::BRIGHT_BLACK, Color::Named(8));
459 assert_eq!(Color::BRIGHT_RED, Color::Named(9));
460 assert_eq!(Color::BRIGHT_GREEN, Color::Named(10));
461 assert_eq!(Color::BRIGHT_YELLOW, Color::Named(11));
462 assert_eq!(Color::BRIGHT_BLUE, Color::Named(12));
463 assert_eq!(Color::BRIGHT_MAGENTA, Color::Named(13));
464 assert_eq!(Color::BRIGHT_CYAN, Color::Named(14));
465 assert_eq!(Color::BRIGHT_WHITE, Color::Named(15));
466 }
467
468 #[test]
469 fn test_color_codes() {
470 let color = Color::RED;
472 assert_eq!(color.to_fg_code(), "38;5;1");
473 assert_eq!(color.to_bg_code(), "48;5;1");
474
475 let rgb = Color::RGB(255, 128, 0);
477 assert_eq!(rgb.to_fg_code(), "38;2;255;128;0");
478 assert_eq!(rgb.to_bg_code(), "48;2;255;128;0");
479
480 let black = Color::BLACK;
482 assert_eq!(black.to_fg_code(), "38;5;0");
483
484 let white = Color::BRIGHT_WHITE;
485 assert_eq!(white.to_fg_code(), "38;5;15");
486 }
487
488 #[test]
489 fn test_attribute_codes() {
490 assert_eq!(Attribute::Bold.to_code(), "1");
491 assert_eq!(Attribute::Dim.to_code(), "2");
492 assert_eq!(Attribute::Italic.to_code(), "3");
493 assert_eq!(Attribute::Underline.to_code(), "4");
494 assert_eq!(Attribute::Blink.to_code(), "5");
495 assert_eq!(Attribute::Reverse.to_code(), "7");
496 assert_eq!(Attribute::Hidden.to_code(), "8");
497 assert_eq!(Attribute::Strikethrough.to_code(), "9");
498 }
499
500 #[test]
501 fn test_style_builder() {
502 let style = Style::new()
503 .fg(Color::RED)
504 .bg(Color::BLUE)
505 .bold()
506 .underline();
507
508 #[cfg(not(feature = "no-color"))]
509 {
510 let ansi = style.to_ansi_start();
511 assert!(ansi.contains("1")); assert!(ansi.contains("4")); assert!(ansi.contains("38;5;1")); assert!(ansi.contains("48;5;4")); }
516
517 #[cfg(feature = "no-color")]
518 assert_eq!(style.to_ansi_start(), "");
519 }
520
521 #[test]
522 fn test_style_empty() {
523 let style = Style::new();
524 #[cfg(not(feature = "no-color"))]
525 assert_eq!(style.to_ansi_start(), "");
526 #[cfg(feature = "no-color")]
527 assert_eq!(style.to_ansi_start(), "");
528 }
529
530 #[test]
531 fn test_style_only_foreground() {
532 let style = Style::new().fg(Color::GREEN);
533
534 #[cfg(not(feature = "no-color"))]
535 assert_eq!(style.to_ansi_start(), "\x1b[38;5;2m");
536 #[cfg(feature = "no-color")]
537 assert_eq!(style.to_ansi_start(), "");
538 }
539
540 #[test]
541 fn test_style_only_background() {
542 let style = Style::new().bg(Color::YELLOW);
543
544 #[cfg(not(feature = "no-color"))]
545 assert_eq!(style.to_ansi_start(), "\x1b[48;5;3m");
546 #[cfg(feature = "no-color")]
547 assert_eq!(style.to_ansi_start(), "");
548 }
549
550 #[test]
551 fn test_style_only_attributes() {
552 let style = Style::new().bold().italic();
553
554 #[cfg(not(feature = "no-color"))]
555 {
556 let ansi = style.to_ansi_start();
557 assert!(ansi.contains("1"));
558 assert!(ansi.contains("3"));
559 }
560 #[cfg(feature = "no-color")]
561 assert_eq!(style.to_ansi_start(), "");
562 }
563
564 #[test]
565 fn test_style_rgb_colors() {
566 let style = Style::new()
567 .fg(Color::RGB(255, 0, 0))
568 .bg(Color::RGB(0, 255, 0));
569
570 #[cfg(not(feature = "no-color"))]
571 {
572 let ansi = style.to_ansi_start();
573 assert!(ansi.contains("38;2;255;0;0"));
574 assert!(ansi.contains("48;2;0;255;0"));
575 }
576 #[cfg(feature = "no-color")]
577 assert_eq!(style.to_ansi_start(), "");
578 }
579
580 #[test]
581 fn test_console_creation() {
582 let console = Console::new("test");
583 assert_eq!(console.text, "test");
584
585 let console_from_str = Console::new("test");
586 assert_eq!(console_from_str.text, "test");
587 }
588
589 #[test]
590 fn test_console_with_text() {
591 let base = Console::new("").red().bold();
592 let derived = base.with_text("new text");
593
594 assert_eq!(derived.text, "new text");
595 assert_eq!(derived.style.foreground, base.style.foreground);
597 assert_eq!(derived.style.attributes, base.style.attributes);
598 }
599
600 #[test]
601 fn test_console_color_methods() {
602 let console = Console::new("test").red();
603 assert_eq!(console.style.foreground, Some(Color::RED));
604
605 let console = Console::new("test").on_blue();
606 assert_eq!(console.style.background, Some(Color::BLUE));
607
608 let console = Console::new("test").fg_rgb(255, 0, 0);
609 assert_eq!(console.style.foreground, Some(Color::RGB(255, 0, 0)));
610
611 let console = Console::new("test").bg_rgb(0, 255, 0);
612 assert_eq!(console.style.background, Some(Color::RGB(0, 255, 0)));
613 }
614
615 #[test]
616 fn test_console_attribute_methods() {
617 let console = Console::new("test").bold();
618 assert!(console.style.attributes.contains(&Attribute::Bold));
619
620 let console = Console::new("test").italic().underline();
621 assert!(console.style.attributes.contains(&Attribute::Italic));
622 assert!(console.style.attributes.contains(&Attribute::Underline));
623
624 let console = Console::new("test")
625 .bold()
626 .dim()
627 .italic()
628 .underline()
629 .blink()
630 .reverse()
631 .hidden()
632 .strikethrough();
633
634 let attrs = &console.style.attributes;
635 assert!(attrs.contains(&Attribute::Bold));
636 assert!(attrs.contains(&Attribute::Dim));
637 assert!(attrs.contains(&Attribute::Italic));
638 assert!(attrs.contains(&Attribute::Underline));
639 assert!(attrs.contains(&Attribute::Blink));
640 assert!(attrs.contains(&Attribute::Reverse));
641 assert!(attrs.contains(&Attribute::Hidden));
642 assert!(attrs.contains(&Attribute::Strikethrough));
643 }
644
645 #[test]
646 fn test_console_display_trait() {
647 let console = Console::new("hello").red().bold();
648 let output = format!("{}", console);
649
650 #[cfg(not(feature = "no-color"))]
651 {
652 assert!(output.starts_with("\x1b["));
653 assert!(output.contains("hello"));
654 assert!(output.ends_with("\x1b[0m"));
655 }
656 #[cfg(feature = "no-color")]
657 assert_eq!(output, "hello");
658 }
659
660 #[test]
661 fn test_console_to_string() {
662 let console = Console::new("test").blue().underline();
663 let string = console.to_string();
664
665 #[cfg(not(feature = "no-color"))]
666 {
667 assert!(string.contains("test"));
668 assert!(string.contains("38;5;4")); assert!(string.contains("4")); }
671 #[cfg(feature = "no-color")]
672 assert_eq!(string, "test");
673 }
674
675 #[test]
676 fn test_console_complex_styling() {
677 let console = Console::new("complex")
678 .fg_rgb(128, 64, 255)
679 .on_bright_white()
680 .bold()
681 .italic()
682 .underline();
683
684 let output = console.to_string();
685
686 #[cfg(not(feature = "no-color"))]
687 {
688 assert!(output.contains("complex"));
689 assert!(output.contains("38;2;128;64;255"));
690 assert!(output.contains("48;5;15"));
691 assert!(output.contains("1"));
692 assert!(output.contains("3"));
693 assert!(output.contains("4"));
694 }
695 #[cfg(feature = "no-color")]
696 assert_eq!(output, "complex");
697 }
698
699 #[test]
700 fn test_console_method_chaining() {
701 let console = Console::new("test").red().on_white().bold().underline();
703
704 assert_eq!(console.style.foreground, Some(Color::RED));
705 assert_eq!(console.style.background, Some(Color::WHITE));
706 assert!(console.style.attributes.contains(&Attribute::Bold));
707 assert!(console.style.attributes.contains(&Attribute::Underline));
708 }
709
710 #[test]
711 fn test_console_clone() {
712 let original = Console::new("original").red().bold();
713 let cloned = original.clone();
714
715 assert_eq!(original.text, cloned.text);
716 assert_eq!(original.style.foreground, cloned.style.foreground);
717 assert_eq!(original.style.background, cloned.style.background);
718 assert_eq!(original.style.attributes, cloned.style.attributes);
719 }
720
721 #[test]
722 fn test_color_and_attribute_copy() {
723 let color = Color::RED;
725 let attr = Attribute::Bold;
726
727 let style1 = Style::new().fg(color).attr(attr);
728 let style2 = Style::new().fg(color).attr(attr);
729
730 assert_eq!(style1.foreground, style2.foreground);
731 assert_eq!(style1.attributes, style2.attributes);
732 }
733
734 #[test]
735 fn test_all_named_colors() {
736 let colors = [
738 (Console::new("").black(), Color::BLACK),
739 (Console::new("").red(), Color::RED),
740 (Console::new("").green(), Color::GREEN),
741 (Console::new("").yellow(), Color::YELLOW),
742 (Console::new("").blue(), Color::BLUE),
743 (Console::new("").magenta(), Color::MAGENTA),
744 (Console::new("").cyan(), Color::CYAN),
745 (Console::new("").white(), Color::WHITE),
746 (Console::new("").bright_black(), Color::BRIGHT_BLACK),
747 (Console::new("").bright_red(), Color::BRIGHT_RED),
748 (Console::new("").bright_green(), Color::BRIGHT_GREEN),
749 (Console::new("").bright_yellow(), Color::BRIGHT_YELLOW),
750 (Console::new("").bright_blue(), Color::BRIGHT_BLUE),
751 (Console::new("").bright_magenta(), Color::BRIGHT_MAGENTA),
752 (Console::new("").bright_cyan(), Color::BRIGHT_CYAN),
753 (Console::new("").bright_white(), Color::BRIGHT_WHITE),
754 ];
755
756 for (console, expected_color) in colors {
757 assert_eq!(console.style.foreground, Some(expected_color));
758 }
759 }
760
761 #[test]
762 fn test_all_background_colors() {
763 let backgrounds = [
765 (Console::new("").on_black(), Color::BLACK),
766 (Console::new("").on_red(), Color::RED),
767 (Console::new("").on_green(), Color::GREEN),
768 (Console::new("").on_yellow(), Color::YELLOW),
769 (Console::new("").on_blue(), Color::BLUE),
770 (Console::new("").on_magenta(), Color::MAGENTA),
771 (Console::new("").on_cyan(), Color::CYAN),
772 (Console::new("").on_white(), Color::WHITE),
773 (Console::new("").on_bright_black(), Color::BRIGHT_BLACK),
774 (Console::new("").on_bright_red(), Color::BRIGHT_RED),
775 (Console::new("").on_bright_green(), Color::BRIGHT_GREEN),
776 (Console::new("").on_bright_yellow(), Color::BRIGHT_YELLOW),
777 (Console::new("").on_bright_blue(), Color::BRIGHT_BLUE),
778 (Console::new("").on_bright_magenta(), Color::BRIGHT_MAGENTA),
779 (Console::new("").on_bright_cyan(), Color::BRIGHT_CYAN),
780 (Console::new("").on_bright_white(), Color::BRIGHT_WHITE),
781 ];
782
783 for (console, expected_color) in backgrounds {
784 assert_eq!(console.style.background, Some(expected_color));
785 }
786 }
787
788 #[test]
789 fn test_console_write_to() {
790 let console = Console::new("write test").green();
791 let mut buffer = Vec::new();
792
793 console.write_to(&mut buffer).unwrap();
794 let output = String::from_utf8(buffer).unwrap();
795
796 #[cfg(not(feature = "no-color"))]
797 {
798 assert!(output.contains("write test"));
799 assert!(output.contains("38;5;2")); assert!(output.ends_with("\x1b[0m"));
801 }
802 #[cfg(feature = "no-color")]
803 assert_eq!(output, "write test");
804 }
805
806 #[test]
807 fn test_console_empty_text() {
808 let console = Console::new("").red().bold();
809 let output = console.to_string();
810
811 #[cfg(not(feature = "no-color"))]
812 {
813 assert!(output.starts_with("\x1b["));
815 assert!(output.ends_with("\x1b[0m"));
816 }
817 #[cfg(feature = "no-color")]
818 assert_eq!(output, "");
819 }
820
821 #[test]
822 fn test_multiple_identical_attributes() {
823 let console = Console::new("test").bold().bold().bold();
826
827 let bold_count = console
829 .style
830 .attributes
831 .iter()
832 .filter(|&&attr| attr == Attribute::Bold)
833 .count();
834 assert_eq!(bold_count, 3);
835 }
836
837 #[test]
838 fn test_ansi_code_ordering() {
839 let style = Style::new()
842 .bg(Color::BLUE)
843 .fg(Color::RED)
844 .underline()
845 .bold();
846
847 #[cfg(not(feature = "no-color"))]
848 {
849 let ansi = style.to_ansi_start();
850 let expected_parts = ["1", "4", "38;5;1", "48;5;4"];
852 let ansi_without_prefix = ansi.trim_start_matches("\x1b[").trim_end_matches('m');
853 let parts: Vec<&str> = ansi_without_prefix.split(';').collect();
854
855 for expected in expected_parts {
858 assert!(
859 ansi.contains(expected),
860 "ANSI code should contain {}",
861 expected
862 );
863 }
864 }
865 }
866
867 #[test]
868 fn test_console_builder() {
869 let console = Console::new("test").red().on_white().bold().underline();
870
871 let output = console.to_string();
872
873 #[cfg(not(feature = "no-color"))]
874 {
875 assert!(output.contains("test"));
876 assert!(output.contains("\x1b["));
877 assert!(output.contains("0m"));
878 }
879
880 #[cfg(feature = "no-color")]
881 assert_eq!(output, "test");
882 }
883 #[test]
884 fn test_basic_colors() {
885 let console = Console::new("Hello, world!").red().bold();
886 insta::assert_yaml_snapshot!(console.to_string());
887 }
888
889 #[test]
890 fn test_rgb_colors() {
891 let console = Console::new("RGB Text")
892 .fg_rgb(255, 0, 128)
893 .bg_rgb(0, 255, 0)
894 .underline();
895 insta::assert_yaml_snapshot!(console.to_string());
896 }
897
898 #[test]
899 fn test_complex_styling() {
900 let console = Console::new("Complex Style")
901 .bright_red()
902 .on_bright_white()
903 .bold()
904 .italic()
905 .blink();
906 insta::assert_yaml_snapshot!(console.to_string());
907 }
908 #[test]
909 fn test_style_combinations() {
910 let styles = vec![
911 Console::new("Error style").red().bold(),
912 Console::new("Warning style").yellow().italic(),
913 Console::new("Success style").green().bold(),
914 ];
915
916 let outputs: Vec<String> = styles.iter().map(|c| c.to_string()).collect();
917 insta::assert_yaml_snapshot!(outputs);
918 }
919
920 #[test]
921 fn test_reusable_style() {
922 let error_style = Console::new("").red().bold();
923 let messages = vec![
924 error_style.with_text("Error: File not found").to_string(),
925 error_style
926 .with_text("Error: Permission denied")
927 .to_string(),
928 ];
929
930 insta::assert_yaml_snapshot!(messages);
931 }
932}