1#![forbid(unsafe_code)]
2
3pub mod color;
25pub mod style;
27pub mod stylesheet;
29pub mod table_theme;
31pub mod theme;
33
34pub use color::{
35 Ansi16,
37 Color,
38 ColorCache,
39 ColorProfile,
40 MonoColor,
41 Rgb,
42 WCAG_AA_LARGE_TEXT,
44 WCAG_AA_NORMAL_TEXT,
45 WCAG_AAA_LARGE_TEXT,
46 WCAG_AAA_NORMAL_TEXT,
47 best_text_color,
49 best_text_color_packed,
50 contrast_ratio,
51 contrast_ratio_packed,
52 meets_wcag_aa,
53 meets_wcag_aa_large_text,
54 meets_wcag_aa_packed,
55 meets_wcag_aaa,
56 relative_luminance,
57 relative_luminance_packed,
58};
59pub use style::{Style, StyleFlags};
60pub use stylesheet::{StyleId, StyleSheet};
61pub use table_theme::{
62 BlendMode, Gradient, StyleMask, TableEffect, TableEffectResolver, TableEffectRule,
63 TableEffectScope, TableEffectTarget, TablePresetId, TableSection, TableTheme,
64 TableThemeDiagnostics, TableThemeSpec,
65};
66pub use theme::{AdaptiveColor, ResolvedTheme, Theme, ThemeBuilder};
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use ftui_render::cell::{CellAttrs, PackedRgba, StyleFlags as CellFlags};
72
73 #[test]
74 fn theme_builder_from_theme_preserves_base_fields() {
75 let base = Theme::builder()
76 .primary(Color::rgb(10, 20, 30))
77 .text(Color::rgb(40, 50, 60))
78 .build();
79
80 let updated = ThemeBuilder::from_theme(base.clone())
81 .text(Color::rgb(70, 80, 90))
82 .build();
83
84 assert_eq!(updated.primary, base.primary);
85 assert_eq!(updated.background, base.background);
86 assert_eq!(updated.text, AdaptiveColor::from(Color::rgb(70, 80, 90)));
87 }
88
89 #[test]
90 fn adaptive_color_resolves_by_mode() {
91 let adaptive = AdaptiveColor::adaptive(Color::rgb(1, 2, 3), Color::rgb(4, 5, 6));
92 assert_eq!(adaptive.resolve(false), Color::rgb(1, 2, 3));
93 assert_eq!(adaptive.resolve(true), Color::rgb(4, 5, 6));
94 }
95
96 #[test]
97 fn packed_rgba_round_trip_channels() {
98 let packed = PackedRgba::rgba(12, 34, 56, 78);
99 assert_eq!(packed.r(), 12);
100 assert_eq!(packed.g(), 34);
101 assert_eq!(packed.b(), 56);
102 assert_eq!(packed.a(), 78);
103
104 let rgb: Rgb = packed.into();
105 assert_eq!(rgb, Rgb::new(12, 34, 56));
106
107 let color: Color = packed.into();
108 assert_eq!(color.to_rgb(), Rgb::new(12, 34, 56));
109 }
110
111 #[test]
112 fn packed_rgba_rgb_defaults_to_opaque() {
113 let packed = PackedRgba::rgb(1, 2, 3);
114 assert_eq!(packed.a(), 255);
115 }
116
117 #[test]
118 fn color_profile_defaults_to_ansi16() {
119 let profile = ColorProfile::detect_from_env(None, None, None);
120 assert_eq!(profile, ColorProfile::Ansi16);
121 }
122
123 #[test]
124 fn style_flags_round_trip_to_cell_flags() {
125 let style_flags = StyleFlags::BOLD
126 .union(StyleFlags::ITALIC)
127 .union(StyleFlags::UNDERLINE)
128 .union(StyleFlags::BLINK);
129
130 let cell_flags: CellFlags = style_flags.into();
131 assert!(cell_flags.contains(CellFlags::BOLD));
132 assert!(cell_flags.contains(CellFlags::ITALIC));
133 assert!(cell_flags.contains(CellFlags::UNDERLINE));
134 assert!(cell_flags.contains(CellFlags::BLINK));
135
136 let round_trip = StyleFlags::from(cell_flags);
137 assert!(round_trip.contains(StyleFlags::BOLD));
138 assert!(round_trip.contains(StyleFlags::ITALIC));
139 assert!(round_trip.contains(StyleFlags::UNDERLINE));
140 assert!(round_trip.contains(StyleFlags::BLINK));
141 }
142
143 #[test]
144 fn extended_underlines_map_to_cell_underline() {
145 let style_flags = StyleFlags::DOUBLE_UNDERLINE.union(StyleFlags::CURLY_UNDERLINE);
146 let cell_flags: CellFlags = style_flags.into();
147 assert!(cell_flags.contains(CellFlags::UNDERLINE));
148 }
149
150 #[test]
151 fn cell_attrs_preserve_link_id_with_flags() {
152 let flags = CellFlags::BOLD | CellFlags::ITALIC | CellFlags::UNDERLINE | CellFlags::BLINK;
153 let attrs = CellAttrs::new(flags, 4242);
154 assert_eq!(attrs.link_id(), 4242);
155 assert!(attrs.has_flag(CellFlags::BOLD));
156 assert!(attrs.has_flag(CellFlags::ITALIC));
157 assert!(attrs.has_flag(CellFlags::UNDERLINE));
158 assert!(attrs.has_flag(CellFlags::BLINK));
159 }
160}