tui_piechart/title.rs
1//! Title positioning, alignment, and styling configuration for block wrappers.
2//!
3//! This module provides types and functionality for controlling where and how
4//! block titles are positioned, aligned, and styled with different Unicode fonts.
5//!
6//! # Examples
7//!
8//! ## Using the unified Title API
9//!
10//! ```
11//! use tui_piechart::title::{Title, TitleStyle};
12//! use tui_piechart::border_style::BorderStyle;
13//!
14//! // Create a positioned title using fluent API (preserves your text styling)
15//! let styled_text = TitleStyle::Bold.apply("My Chart");
16//! let block = BorderStyle::Rounded.block()
17//! .title(
18//! Title::new(styled_text)
19//! .center()
20//! .bottom()
21//! );
22//! ```
23//!
24//! ## Using individual components (legacy)
25//!
26//! ```
27//! use tui_piechart::title::{TitleAlignment, TitlePosition, TitleStyle, BlockExt};
28//! use tui_piechart::border_style::BorderStyle;
29//!
30//! let title = TitleStyle::Bold.apply("My Chart");
31//! let block = BorderStyle::Rounded.block()
32//! .title(title)
33//! .title_alignment_horizontal(TitleAlignment::Center)
34//! .title_vertical_position(TitlePosition::Bottom);
35//! ```
36
37use ratatui::layout::Alignment;
38use ratatui::text::Line;
39use ratatui::widgets::Block;
40
41/// A builder for positioning block titles with horizontal alignment and vertical placement.
42///
43/// This struct handles only the positioning of titles (alignment and position),
44/// preserving any text styling you've already applied. You can style your text
45/// separately using `TitleStyle` or ratatui's styling features.
46///
47/// # Examples
48///
49/// ```
50/// use tui_piechart::title::{Title, TitleStyle};
51/// use tui_piechart::border_style::BorderStyle;
52///
53/// // Simple title with defaults (center top)
54/// let simple = Title::new("Statistics");
55///
56/// // Style text first, then position it
57/// let styled_text = TitleStyle::Bold.apply("Results");
58/// let title = Title::new(styled_text)
59/// .right()
60/// .bottom();
61///
62/// // Position plain text
63/// let positioned = Title::new("Dashboard")
64/// .center()
65/// .top();
66/// ```
67///
68/// # Method Chaining
69///
70/// All builder methods return `Self`, allowing for fluent method chaining:
71///
72/// ```
73/// use tui_piechart::title::Title;
74///
75/// let title = Title::new("My Chart")
76/// .center() // horizontal alignment
77/// .bottom(); // vertical position
78/// ```
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Title {
81 text: String,
82 alignment: TitleAlignment,
83 position: TitlePosition,
84}
85
86impl Title {
87 /// Creates a new title with the given text and default positioning.
88 ///
89 /// Defaults: Center alignment, Top position
90 ///
91 /// The text is preserved as-is, including any styling you've already applied.
92 #[must_use]
93 pub fn new(text: impl Into<String>) -> Self {
94 Self {
95 text: text.into(),
96 alignment: TitleAlignment::default(),
97 position: TitlePosition::default(),
98 }
99 }
100
101 /// Sets horizontal alignment to Start (left in LTR).
102 #[must_use]
103 pub fn left(mut self) -> Self {
104 self.alignment = TitleAlignment::Start;
105 self
106 }
107
108 /// Sets horizontal alignment to Center.
109 #[must_use]
110 pub fn center(mut self) -> Self {
111 self.alignment = TitleAlignment::Center;
112 self
113 }
114
115 /// Sets horizontal alignment to End (right in LTR).
116 #[must_use]
117 pub fn right(mut self) -> Self {
118 self.alignment = TitleAlignment::End;
119 self
120 }
121
122 /// Sets vertical position to Top.
123 #[must_use]
124 pub fn top(mut self) -> Self {
125 self.position = TitlePosition::Top;
126 self
127 }
128
129 /// Sets vertical position to Bottom.
130 #[must_use]
131 pub fn bottom(mut self) -> Self {
132 self.position = TitlePosition::Bottom;
133 self
134 }
135
136 /// Returns the text as a Line for rendering (preserves original styling).
137 #[must_use]
138 pub fn render(&self) -> Line<'static> {
139 Line::from(self.text.clone())
140 }
141
142 /// Gets the horizontal alignment.
143 #[must_use]
144 pub fn alignment(&self) -> TitleAlignment {
145 self.alignment
146 }
147
148 /// Gets the vertical position.
149 #[must_use]
150 pub fn position(&self) -> TitlePosition {
151 self.position
152 }
153}
154
155impl From<Title> for Line<'static> {
156 fn from(title: Title) -> Self {
157 title.render()
158 }
159}
160
161impl<T: Into<String>> From<T> for Title {
162 fn from(text: T) -> Self {
163 Title::new(text)
164 }
165}
166
167/// Horizontal alignment for block titles.
168///
169/// Controls how the title text is aligned horizontally within the block's top
170/// or bottom border. Supports start (left), center, and end (right) alignment.
171///
172/// # Examples
173///
174/// ```
175/// use tui_piechart::title::{TitleAlignment, BlockExt};
176/// use tui_piechart::border_style::BorderStyle;
177///
178/// let block = BorderStyle::Rounded.block()
179/// .title("Centered Title")
180/// .title_alignment_horizontal(TitleAlignment::Center);
181/// ```
182///
183/// # Text Direction
184///
185/// The alignment is logical rather than physical:
186/// - **Start**: Left in LTR languages, right in RTL languages
187/// - **Center**: Always centered
188/// - **End**: Right in LTR languages, left in RTL languages
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
190pub enum TitleAlignment {
191 /// Start-aligned title (left in LTR, right in RTL)
192 ///
193 /// The title appears at the start of the text direction. For left-to-right
194 /// languages (like English), this means left-aligned.
195 Start,
196
197 /// Center-aligned title (default)
198 ///
199 /// The title appears centered horizontally within the block border.
200 /// This is the default alignment.
201 #[default]
202 Center,
203
204 /// End-aligned title (right in LTR, left in RTL)
205 ///
206 /// The title appears at the end of the text direction. For left-to-right
207 /// languages (like English), this means right-aligned.
208 End,
209}
210
211impl From<TitleAlignment> for Alignment {
212 fn from(alignment: TitleAlignment) -> Self {
213 match alignment {
214 TitleAlignment::Start => Alignment::Left,
215 TitleAlignment::Center => Alignment::Center,
216 TitleAlignment::End => Alignment::Right,
217 }
218 }
219}
220
221/// Vertical position for block titles.
222///
223/// Controls whether the title appears at the top or bottom of the block border.
224///
225/// # Examples
226///
227/// ```
228/// use tui_piechart::title::{TitlePosition, BlockExt};
229/// use tui_piechart::border_style::BorderStyle;
230///
231/// let block = BorderStyle::Rounded.block()
232/// .title("Bottom Title")
233/// .title_vertical_position(TitlePosition::Bottom);
234/// ```
235///
236/// # Combinations
237///
238/// Title position can be combined with horizontal alignment to create
239/// 6 different title placements:
240/// - Top-Start, Top-Center, Top-End
241/// - Bottom-Start, Bottom-Center, Bottom-End
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
243pub enum TitlePosition {
244 /// Title at the top (default)
245 ///
246 /// The title appears in the top border of the block. This is the default
247 /// position and is the most common placement for block titles.
248 #[default]
249 Top,
250
251 /// Title at the bottom
252 ///
253 /// The title appears in the bottom border of the block. Useful when you
254 /// want to place other content at the top or when the title serves as
255 /// a caption rather than a header.
256 Bottom,
257}
258
259/// Font style for block titles using Unicode character variants.
260///
261/// Converts regular ASCII text to different Unicode character sets to achieve
262/// visual font styles in terminal user interfaces. Each style uses specific
263/// Unicode code points that represent the same letters in different typographic styles.
264///
265/// # Examples
266///
267/// ```
268/// use tui_piechart::title::TitleStyle;
269///
270/// let bold = TitleStyle::Bold.apply("Statistics");
271/// let italic = TitleStyle::Italic.apply("Results");
272/// let script = TitleStyle::Script.apply("Elegant");
273/// ```
274///
275/// # Limitations
276///
277/// - Only supports ASCII letters (a-z, A-Z), numbers (0-9), and spaces
278/// - Other characters (punctuation, special symbols) are passed through unchanged
279/// - Terminal font must support the Unicode characters (most modern terminals do)
280/// - Some styles may not render identically across different fonts
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
282pub enum TitleStyle {
283 /// Normal/regular text (default) - no transformation applied
284 #[default]
285 Normal,
286
287 /// Bold text using Unicode Mathematical Bold characters
288 ///
289 /// Converts text to bold Unicode variants. Example: "Hello" → "𝐇𝐞𝐥𝐥𝐨"
290 Bold,
291
292 /// Italic text using Unicode Mathematical Italic characters
293 ///
294 /// Converts text to italic Unicode variants. Example: "Hello" → "𝐻𝑒𝑙𝑙𝑜"
295 Italic,
296
297 /// Bold Italic text using Unicode Mathematical Bold Italic characters
298 ///
299 /// Combines bold and italic styling. Example: "Hello" → "𝑯𝒆𝒍𝒍𝒐"
300 BoldItalic,
301
302 /// Script/cursive text using Unicode Mathematical Script characters
303 ///
304 /// Converts text to flowing script style. Example: "Hello" → "𝐻ℯ𝓁𝓁ℴ"
305 Script,
306
307 /// Bold Script text using Unicode Mathematical Bold Script characters
308 ///
309 /// Script style with bold weight. Example: "Hello" → "𝓗𝓮𝓵𝓵𝓸"
310 BoldScript,
311
312 /// Sans-serif text using Unicode Mathematical Sans-Serif characters
313 ///
314 /// Clean sans-serif style. Example: "Hello" → "𝖧𝖾𝗅𝗅𝗈"
315 SansSerif,
316
317 /// Bold Sans-serif text using Unicode Mathematical Sans-Serif Bold characters
318 ///
319 /// Bold sans-serif style. Example: "Hello" → "𝗛𝗲𝗹𝗹𝗼"
320 BoldSansSerif,
321
322 /// Italic Sans-serif text using Unicode Mathematical Sans-Serif Italic characters
323 ///
324 /// Italic sans-serif style. Example: "Hello" → "𝘏𝘦𝘭𝘭𝘰"
325 ItalicSansSerif,
326
327 /// Monospace text using Unicode Monospace characters
328 ///
329 /// Fixed-width monospace style. Example: "Hello" → "𝙷𝚎𝚕𝚕𝚘"
330 Monospace,
331}
332
333impl TitleStyle {
334 /// Apply this style to the given text.
335 ///
336 /// Converts ASCII letters and numbers to their Unicode equivalents in the
337 /// selected style. Non-ASCII characters and unsupported characters are
338 /// passed through unchanged.
339 ///
340 /// # Examples
341 ///
342 /// ```
343 /// use tui_piechart::title::TitleStyle;
344 ///
345 /// let bold = TitleStyle::Bold.apply("Chart 2024");
346 /// let italic = TitleStyle::Italic.apply("Statistics");
347 /// let script = TitleStyle::Script.apply("Elegant Title");
348 /// ```
349 ///
350 /// # Character Support
351 ///
352 /// - **Letters**: Full support for a-z and A-Z
353 /// - **Numbers**: Support varies by style (most support 0-9)
354 /// - **Spaces**: Preserved as-is
355 /// - **Punctuation**: Passed through unchanged
356 #[must_use]
357 pub fn apply(&self, text: &str) -> String {
358 match self {
359 Self::Normal => text.to_string(),
360 Self::Bold => convert_to_bold(text),
361 Self::Italic => convert_to_italic(text),
362 Self::BoldItalic => convert_to_bold_italic(text),
363 Self::Script => convert_to_script(text),
364 Self::BoldScript => convert_to_bold_script(text),
365 Self::SansSerif => convert_to_sans_serif(text),
366 Self::BoldSansSerif => convert_to_bold_sans_serif(text),
367 Self::ItalicSansSerif => convert_to_italic_sans_serif(text),
368 Self::Monospace => convert_to_monospace(text),
369 }
370 }
371}
372
373// Unicode conversion functions - using macro to reduce code duplication
374
375/// Macro to generate Unicode conversion functions.
376///
377/// This macro generates functions that convert ASCII text to Unicode character variants.
378/// It reduces code duplication by handling the repetitive pattern of mapping character
379/// ranges to Unicode code points.
380///
381/// # Parameters
382/// - `$name`: Function name
383/// - `$upper`: Unicode base for uppercase letters (A-Z)
384/// - `$lower`: Unicode base for lowercase letters (a-z)
385/// - `$digit`: Optional Unicode base for digits (0-9)
386macro_rules! unicode_converter {
387 // Version with digit support
388 ($name:ident, $upper:expr, $lower:expr, $digit:expr) => {
389 fn $name(text: &str) -> String {
390 text.chars()
391 .map(|c| match c {
392 'A'..='Z' => char::from_u32($upper + (c as u32 - 'A' as u32)).unwrap(),
393 'a'..='z' => char::from_u32($lower + (c as u32 - 'a' as u32)).unwrap(),
394 '0'..='9' => char::from_u32($digit + (c as u32 - '0' as u32)).unwrap(),
395 _ => c,
396 })
397 .collect()
398 }
399 };
400 // Version without digit support
401 ($name:ident, $upper:expr, $lower:expr) => {
402 fn $name(text: &str) -> String {
403 text.chars()
404 .map(|c| match c {
405 'A'..='Z' => char::from_u32($upper + (c as u32 - 'A' as u32)).unwrap(),
406 'a'..='z' => char::from_u32($lower + (c as u32 - 'a' as u32)).unwrap(),
407 _ => c,
408 })
409 .collect()
410 }
411 };
412}
413
414// Generate all Unicode conversion functions using the macro
415unicode_converter!(convert_to_bold, 0x1D400, 0x1D41A, 0x1D7CE);
416unicode_converter!(convert_to_italic, 0x1D434, 0x1D44E);
417unicode_converter!(convert_to_bold_italic, 0x1D468, 0x1D482);
418unicode_converter!(convert_to_script, 0x1D49C, 0x1D4B6);
419unicode_converter!(convert_to_bold_script, 0x1D4D0, 0x1D4EA);
420unicode_converter!(convert_to_sans_serif, 0x1D5A0, 0x1D5BA, 0x1D7E2);
421unicode_converter!(convert_to_bold_sans_serif, 0x1D5D4, 0x1D5EE, 0x1D7EC);
422unicode_converter!(convert_to_italic_sans_serif, 0x1D608, 0x1D622);
423unicode_converter!(convert_to_monospace, 0x1D670, 0x1D68A, 0x1D7F6);
424
425/// Extension trait for adding title positioning helpers to Block.
426///
427/// This trait provides ergonomic methods for setting title alignment and position
428/// on Ratatui's `Block` type. It allows for method chaining and uses semantic
429/// types instead of raw alignment values.
430///
431/// # Examples
432///
433/// ## Using the unified Title API (recommended)
434///
435/// ```
436/// use tui_piechart::title::{Title, TitleStyle};
437/// use ratatui::widgets::Block;
438///
439/// let styled = TitleStyle::Bold.apply("My Chart");
440/// let block = Block::bordered()
441/// .title(Title::new(styled).center().bottom());
442/// ```
443///
444/// ## Using individual positioning methods (legacy)
445///
446/// ```
447/// use tui_piechart::title::{TitleAlignment, TitlePosition, BlockExt};
448/// use ratatui::widgets::Block;
449///
450/// let block = Block::bordered()
451/// .title("My Chart")
452/// .title_alignment_horizontal(TitleAlignment::Center)
453/// .title_vertical_position(TitlePosition::Bottom);
454/// ```
455pub trait BlockExt<'a>: Sized {
456 /// Apply a unified Title with styling and positioning.
457 ///
458 /// This is the recommended way to add styled and positioned titles.
459 ///
460 /// # Examples
461 ///
462 /// ```
463 /// use tui_piechart::title::{Title, TitleStyle, BlockExt};
464 /// use ratatui::widgets::Block;
465 ///
466 /// let styled = TitleStyle::Bold.apply("Stats");
467 /// let block = Block::bordered()
468 /// .apply_title(Title::new(styled).center().bottom());
469 /// ```
470 #[must_use]
471 fn apply_title(self, title: Title) -> Self;
472 /// Sets the horizontal alignment of the title.
473 ///
474 /// Controls whether the title appears at the start (left), center, or end (right)
475 /// of the block border.
476 ///
477 /// # Examples
478 ///
479 /// ```
480 /// use tui_piechart::title::{TitleAlignment, BlockExt};
481 /// use ratatui::widgets::Block;
482 ///
483 /// let block = Block::bordered()
484 /// .title("My Chart")
485 /// .title_alignment_horizontal(TitleAlignment::Center);
486 /// ```
487 #[must_use]
488 fn title_alignment_horizontal(self, alignment: TitleAlignment) -> Self;
489
490 /// Sets the vertical position of the title.
491 ///
492 /// Controls whether the title appears at the top or bottom of the block border.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// use tui_piechart::title::{TitlePosition, BlockExt};
498 /// use ratatui::widgets::Block;
499 ///
500 /// let block = Block::bordered()
501 /// .title("My Chart")
502 /// .title_vertical_position(TitlePosition::Bottom);
503 /// ```
504 #[must_use]
505 fn title_vertical_position(self, position: TitlePosition) -> Self;
506}
507
508impl<'a> BlockExt<'a> for Block<'a> {
509 fn apply_title(self, title: Title) -> Self {
510 let styled_text = title.render();
511 let alignment = title.alignment();
512 let position = title.position();
513
514 let block = match position {
515 TitlePosition::Top => self.title(styled_text),
516 TitlePosition::Bottom => self.title_bottom(styled_text),
517 };
518
519 block.title_alignment(alignment.into())
520 }
521
522 fn title_alignment_horizontal(self, alignment: TitleAlignment) -> Self {
523 self.title_alignment(alignment.into())
524 }
525
526 fn title_vertical_position(self, position: TitlePosition) -> Self {
527 // `Block::title_position()` and `ratatui::widgets::TitlePosition` were
528 // removed in ratatui 0.30. Position is now set per-title when the title
529 // is added: `Block::title()` places at the top, `Block::title_bottom()`
530 // at the bottom. `apply_title()` already handles this correctly, so
531 // this method is intentionally a no-op to preserve API compatibility.
532 let _ = position;
533 self
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 #[test]
542 fn title_new() {
543 let title = Title::new("Test");
544 assert_eq!(title.text, "Test");
545 assert_eq!(title.alignment, TitleAlignment::Center);
546 assert_eq!(title.position, TitlePosition::Top);
547 }
548
549 #[test]
550 fn title_builder_alignment() {
551 let title = Title::new("Test").left();
552 assert_eq!(title.alignment, TitleAlignment::Start);
553 }
554
555 #[test]
556 fn title_builder_position() {
557 let title = Title::new("Test").bottom();
558 assert_eq!(title.position, TitlePosition::Bottom);
559 }
560
561 #[test]
562 fn title_builder_chaining() {
563 let title = Title::new("Test").center().bottom();
564 assert_eq!(title.alignment, TitleAlignment::Center);
565 assert_eq!(title.position, TitlePosition::Bottom);
566 }
567
568 #[test]
569 fn title_from_string() {
570 let title: Title = "Test".into();
571 assert_eq!(title.text, "Test");
572 }
573
574 #[test]
575 fn title_alignment_default() {
576 assert_eq!(TitleAlignment::default(), TitleAlignment::Center);
577 }
578
579 #[test]
580 fn title_position_default() {
581 assert_eq!(TitlePosition::default(), TitlePosition::Top);
582 }
583
584 #[test]
585 fn title_style_default() {
586 assert_eq!(TitleStyle::default(), TitleStyle::Normal);
587 }
588
589 #[test]
590 fn title_alignment_to_ratatui_alignment() {
591 assert_eq!(Alignment::from(TitleAlignment::Start), Alignment::Left);
592 assert_eq!(Alignment::from(TitleAlignment::Center), Alignment::Center);
593 assert_eq!(Alignment::from(TitleAlignment::End), Alignment::Right);
594 }
595
596 #[test]
597 fn title_alignment_clone() {
598 let align = TitleAlignment::End;
599 let cloned = align;
600 assert_eq!(align, cloned);
601 }
602
603 #[test]
604 fn title_position_clone() {
605 let pos = TitlePosition::Bottom;
606 let cloned = pos;
607 assert_eq!(pos, cloned);
608 }
609
610 #[test]
611 fn title_style_clone() {
612 let style = TitleStyle::Bold;
613 let cloned = style;
614 assert_eq!(style, cloned);
615 }
616
617 #[test]
618 fn title_alignment_debug() {
619 let align = TitleAlignment::Start;
620 let debug = format!("{align:?}");
621 assert_eq!(debug, "Start");
622 }
623
624 #[test]
625 fn title_position_debug() {
626 let pos = TitlePosition::Bottom;
627 let debug = format!("{pos:?}");
628 assert_eq!(debug, "Bottom");
629 }
630
631 #[test]
632 fn title_style_debug() {
633 let style = TitleStyle::Bold;
634 let debug = format!("{style:?}");
635 assert_eq!(debug, "Bold");
636 }
637
638 #[test]
639 fn block_ext_title_alignment() {
640 let block = Block::bordered()
641 .title("Test")
642 .title_alignment_horizontal(TitleAlignment::Center);
643 // If this compiles and doesn't panic, the trait is working
644 assert!(format!("{block:?}").contains("Test"));
645 }
646
647 #[test]
648 fn block_ext_title_position() {
649 let block = Block::bordered()
650 .title("Test")
651 .title_vertical_position(TitlePosition::Bottom);
652 // If this compiles and doesn't panic, the trait is working
653 assert!(format!("{block:?}").contains("Test"));
654 }
655
656 #[test]
657 fn block_ext_method_chaining() {
658 let block = Block::bordered()
659 .title("Test")
660 .title_alignment_horizontal(TitleAlignment::End)
661 .title_vertical_position(TitlePosition::Bottom);
662 // If this compiles and doesn't panic, method chaining works
663 assert!(format!("{block:?}").contains("Test"));
664 }
665
666 #[test]
667 fn title_style_normal() {
668 let text = "Hello World";
669 assert_eq!(TitleStyle::Normal.apply(text), "Hello World");
670 }
671
672 #[test]
673 fn title_style_bold_letters() {
674 let result = TitleStyle::Bold.apply("Hello");
675 assert_ne!(result, "Hello");
676 assert_eq!(result.chars().count(), 5); // Same length
677 }
678
679 #[test]
680 fn title_style_bold_with_numbers() {
681 let result = TitleStyle::Bold.apply("Chart 2024");
682 assert!(result.chars().count() >= 10); // At least same length
683 }
684
685 #[test]
686 fn title_style_italic_letters() {
687 let result = TitleStyle::Italic.apply("Statistics");
688 assert_ne!(result, "Statistics");
689 }
690
691 #[test]
692 fn title_style_preserves_spaces() {
693 let result = TitleStyle::Bold.apply("Hello World");
694 assert!(result.contains(' '));
695 }
696
697 #[test]
698 fn title_style_preserves_punctuation() {
699 let result = TitleStyle::Bold.apply("Hello!");
700 assert!(result.ends_with('!'));
701 }
702
703 #[test]
704 fn title_style_script() {
705 let result = TitleStyle::Script.apply("Test");
706 assert_ne!(result, "Test");
707 }
708
709 #[test]
710 fn title_style_monospace() {
711 let result = TitleStyle::Monospace.apply("Code");
712 assert_ne!(result, "Code");
713 }
714
715 #[test]
716 fn title_style_sans_serif() {
717 let result = TitleStyle::SansSerif.apply("Modern");
718 assert_ne!(result, "Modern");
719 }
720
721 #[test]
722 fn title_style_empty_string() {
723 assert_eq!(TitleStyle::Bold.apply(""), "");
724 assert_eq!(TitleStyle::Italic.apply(""), "");
725 }
726
727 #[test]
728 fn title_style_mixed_case() {
729 let result = TitleStyle::Bold.apply("TeSt");
730 assert_ne!(result, "TeSt");
731 assert_eq!(result.chars().count(), 4);
732 }
733
734 // --- Title builder: left / right / top ---
735
736 #[test]
737 fn title_builder_left() {
738 let title = Title::new("Test").left();
739 assert_eq!(title.alignment, TitleAlignment::Start);
740 }
741
742 #[test]
743 fn title_builder_right() {
744 let title = Title::new("Test").right();
745 assert_eq!(title.alignment, TitleAlignment::End);
746 }
747
748 #[test]
749 fn title_builder_top() {
750 let title = Title::new("Test").bottom().top();
751 assert_eq!(title.position, TitlePosition::Top);
752 }
753
754 // --- Title getters: alignment() / position() / render() ---
755
756 #[test]
757 fn title_alignment_getter() {
758 let title = Title::new("Test").right();
759 assert_eq!(title.alignment(), TitleAlignment::End);
760 }
761
762 #[test]
763 fn title_position_getter() {
764 let title = Title::new("Test").bottom();
765 assert_eq!(title.position(), TitlePosition::Bottom);
766 }
767
768 #[test]
769 fn title_render_returns_line() {
770 let title = Title::new("Hello");
771 let line = title.render();
772 assert_eq!(line.to_string(), "Hello");
773 }
774
775 #[test]
776 fn title_into_line() {
777 let title = Title::new("World");
778 let line: ratatui::text::Line = title.into();
779 assert_eq!(line.to_string(), "World");
780 }
781
782 // --- BlockExt::apply_title ---
783
784 #[test]
785 fn block_ext_apply_title_top_center() {
786 let title = Title::new("My Chart").center().top();
787 let block = Block::bordered().apply_title(title);
788 assert!(format!("{block:?}").contains("My Chart"));
789 }
790
791 #[test]
792 fn block_ext_apply_title_bottom_right() {
793 let title = Title::new("Footer").right().bottom();
794 let block = Block::bordered().apply_title(title);
795 assert!(format!("{block:?}").contains("Footer"));
796 }
797
798 #[test]
799 fn block_ext_apply_title_bottom_left() {
800 let title = Title::new("Left Footer").left().bottom();
801 let block = Block::bordered().apply_title(title);
802 assert!(format!("{block:?}").contains("Left Footer"));
803 }
804
805 // --- TitleStyle remaining variants ---
806
807 #[test]
808 fn title_style_bold_italic() {
809 let result = TitleStyle::BoldItalic.apply("Test");
810 assert_ne!(result, "Test");
811 assert_eq!(result.chars().count(), 4);
812 }
813
814 #[test]
815 fn title_style_bold_script() {
816 let result = TitleStyle::BoldScript.apply("Test");
817 assert_ne!(result, "Test");
818 assert_eq!(result.chars().count(), 4);
819 }
820
821 #[test]
822 fn title_style_bold_sans_serif() {
823 let result = TitleStyle::BoldSansSerif.apply("Test");
824 assert_ne!(result, "Test");
825 assert_eq!(result.chars().count(), 4);
826 }
827
828 #[test]
829 fn title_style_italic_sans_serif() {
830 let result = TitleStyle::ItalicSansSerif.apply("Test");
831 assert_ne!(result, "Test");
832 assert_eq!(result.chars().count(), 4);
833 }
834
835 #[test]
836 fn title_style_bold_script_with_numbers() {
837 // BoldScript has no digit support, digits pass through unchanged
838 let result = TitleStyle::BoldScript.apply("Test 42");
839 assert!(result.contains('4'));
840 assert!(result.contains('2'));
841 }
842
843 #[test]
844 fn title_style_bold_italic_preserves_spaces() {
845 let result = TitleStyle::BoldItalic.apply("A B");
846 assert!(result.contains(' '));
847 }
848
849 #[test]
850 fn block_ext_title_vertical_position_top() {
851 let block = Block::bordered()
852 .title("Test")
853 .title_vertical_position(TitlePosition::Top);
854 assert!(format!("{block:?}").contains("Test"));
855 }
856
857 #[test]
858 fn title_style_all_variants_non_empty() {
859 let text = "ABC";
860 let variants = [
861 TitleStyle::Normal,
862 TitleStyle::Bold,
863 TitleStyle::Italic,
864 TitleStyle::BoldItalic,
865 TitleStyle::Script,
866 TitleStyle::BoldScript,
867 TitleStyle::SansSerif,
868 TitleStyle::BoldSansSerif,
869 TitleStyle::ItalicSansSerif,
870 TitleStyle::Monospace,
871 ];
872 for variant in &variants {
873 let result = variant.apply(text);
874 assert_eq!(result.chars().count(), 3, "{variant:?} changed char count");
875 }
876 }
877}