cli_boxes/
lib.rs

1//! # CLI Boxes
2//!
3//! [![Crates.io](https://img.shields.io/crates/v/cli-boxes.svg)](https://crates.io/crates/cli-boxes)
4//! [![Documentation](https://docs.rs/cli-boxes/badge.svg)](https://docs.rs/cli-boxes)
5//! [![License](https://img.shields.io/crates/l/cli-boxes.svg)](https://github.com/sabry-awad97/boxen-rs)
6//!
7//! A high-performance Rust library providing Unicode box-drawing characters for creating beautiful CLI interfaces.
8//! Perfect for terminal applications, CLI tools, and text-based user interfaces.
9//!
10//! ## 🎯 Features
11//!
12//! - **9 Beautiful Box Styles**: From elegant single-line to decorative arrows
13//! - **Unicode & ASCII Support**: Full Unicode compliance with ASCII fallback
14//! - **Zero-Cost Abstractions**: Compile-time constants and zero-allocation parsing
15//! - **Type Safety**: Strong typing prevents incorrect usage
16//! - **Ergonomic Builder API**: Fluent interface for custom box creation
17//! - **String Parsing**: Parse styles from configuration files or user input
18//! - **Serde Integration**: Optional serialization support (feature-gated)
19//! - **Comprehensive Testing**: 100% test coverage with extensive edge case handling
20//!
21//! ## 📦 Box Styles
22//!
23//! | Style | Preview | Use Case |
24//! |-------|---------|----------|
25//! | `SINGLE` | `┌─┐│┘─└│` | General purpose, clean appearance |
26//! | `DOUBLE` | `╔═╗║╝═╚║` | Emphasis, important content |
27//! | `ROUND` | `╭─╮│╯─╰│` | Modern, soft appearance |
28//! | `BOLD` | `┏━┓┃┛━┗┃` | Strong emphasis, headers |
29//! | `SINGLE_DOUBLE` | `╓─╖║╜─╙║` | Mixed style, unique look |
30//! | `DOUBLE_SINGLE` | `╒═╕│╛═╘│` | Alternative mixed style |
31//! | `CLASSIC` | `+─+|+─+|` | ASCII compatibility, legacy systems |
32//! | `ARROW` | `↘↓↙←↖↑↗→` | Decorative, special effects |
33//! | `NONE` | `        ` | Invisible borders, spacing |
34//!
35//! ## 🚀 Quick Start
36//!
37//! Add to your `Cargo.toml`:
38//! ```toml
39//! [dependencies]
40//! cli-boxes = "0.1.0"
41//! ```
42//!
43//! ### Basic Usage
44//!
45//! ```rust
46//! use cli_boxes::{BoxChars, BorderStyle};
47//!
48//! // Create a simple box
49//! let box_chars = BoxChars::SINGLE;
50//! println!("{}{}{}",
51//!     box_chars.top_left,
52//!     box_chars.top.to_string().repeat(10),
53//!     box_chars.top_right
54//! );
55//! println!("{}          {}", box_chars.left, box_chars.right);
56//! println!("{}{}{}",
57//!     box_chars.bottom_left,
58//!     box_chars.bottom.to_string().repeat(10),
59//!     box_chars.bottom_right
60//! );
61//! // Output:
62//! // ┌──────────┐
63//! // │          │
64//! // └──────────┘
65//! ```
66//!
67//! ### Advanced Usage with Builder Pattern
68//!
69//! ```rust
70//! use cli_boxes::BoxChars;
71//!
72//! // Create custom box with builder pattern
73//! let custom = BoxChars::builder()
74//!     .corners('●')
75//!     .horizontal('═')
76//!     .vertical('║')
77//!     .top_left('╔')  // Override specific corner
78//!     .build();
79//!
80//! // Asymmetric design
81//! let fancy = BoxChars::builder()
82//!     .top_left('╭').top('─').top_right('╮')
83//!     .left('│').right('│')
84//!     .bottom_left('└').bottom('─').bottom_right('┘')
85//!     .build();
86//! ```
87//!
88//! ### Dynamic Style Selection
89//!
90//! ```rust
91//! use cli_boxes::{BorderStyle, BoxChars};
92//!
93//! // Parse from configuration
94//! let style: BorderStyle = "double".parse().unwrap();
95//! let box_chars = style.chars();
96//!
97//! // Iterate through all styles
98//! for style in BorderStyle::all() {
99//!     let chars = style.chars();
100//!     println!("{}: {}", style, chars);
101//! }
102//! ```
103//!
104//! ## 🎨 Real-World Examples
105//!
106//! ### Creating a Status Box
107//!
108//! ```rust
109//! use cli_boxes::BoxChars;
110//!
111//! fn create_status_box(message: &str, width: usize) -> String {
112//!     let box_chars = BoxChars::DOUBLE;
113//!     let padding = width.saturating_sub(message.len() + 2);
114//!     let left_pad = padding / 2;
115//!     let right_pad = padding - left_pad;
116//!     
117//!     format!(
118//!         "{}{}{}\n{}{}{}{}{}\n{}{}{}",
119//!         box_chars.top_left,
120//!         box_chars.top.to_string().repeat(width),
121//!         box_chars.top_right,
122//!         box_chars.left,
123//!         " ".repeat(left_pad),
124//!         message,
125//!         " ".repeat(right_pad),
126//!         box_chars.right,
127//!         box_chars.bottom_left,
128//!         box_chars.bottom.to_string().repeat(width),
129//!         box_chars.bottom_right
130//!     )
131//! }
132//! ```
133//!
134//! ### Configuration-Driven Boxes
135//!
136//! ```rust
137//! use cli_boxes::{BorderStyle, BoxChars};
138//! use std::collections::HashMap;
139//!
140//! fn get_box_for_level(level: &str) -> BoxChars {
141//!     let styles: HashMap<&str, BorderStyle> = [
142//!         ("info", BorderStyle::Single),
143//!         ("warning", BorderStyle::Bold),
144//!         ("error", BorderStyle::Double),
145//!         ("success", BorderStyle::Round),
146//!     ].iter().cloned().collect();
147//!     
148//!     styles.get(level)
149//!         .map(|s| s.chars())
150//!         .unwrap_or(BoxChars::CLASSIC)
151//! }
152//! ```
153//!
154//! ## ⚡ Performance
155//!
156//! This library is designed for maximum performance:
157//!
158//! - **Zero Allocations**: String parsing uses byte-level comparison without heap allocation
159//! - **Compile-Time Constants**: All predefined styles are `const` and computed at compile time
160//! - **Minimal Dependencies**: Only `strum` for enum iteration, optional `serde` for serialization
161//! - **Small Memory Footprint**: `BoxChars` is only 32 bytes (8 × 4-byte chars)
162//!
163//! ## 🔧 Optional Features
164//!
165//! ### Serde Support
166//!
167//! Enable serialization support:
168//! ```toml
169//! [dependencies]
170//! cli-boxes = { version = "0.1.0", features = ["serde"] }
171//! ```
172//!
173//! ```rust
174//! # #[cfg(feature = "serde")]
175//! # {
176//! use cli_boxes::{BoxChars, BorderStyle};
177//! use serde_json;
178//!
179//! let style = BorderStyle::Double;
180//! let json = serde_json::to_string(&style).unwrap();
181//! let deserialized: BorderStyle = serde_json::from_str(&json).unwrap();
182//! # }
183//! ```
184//!
185//! ## 🛡️ Error Handling
186//!
187//! The library provides helpful error messages for invalid input:
188//!
189//! ```rust
190//! use cli_boxes::BorderStyle;
191//!
192//! match "invalid_style".parse::<BorderStyle>() {
193//!     Ok(style) => println!("Parsed: {}", style),
194//!     Err(e) => println!("{}", e),
195//!     // Output: Invalid border style: 'invalid_style'.
196//!     // Did you mean one of: none, single, double, round, bold,
197//!     // single_double, double_single, classic, arrow?
198//! }
199//! ```
200//!
201//! ## 🔗 Related Crates
202//!
203//! - [`tui`](https://crates.io/crates/tui) - Terminal user interface library
204//! - [`crossterm`](https://crates.io/crates/crossterm) - Cross-platform terminal manipulation
205//! - [`console`](https://crates.io/crates/console) - Terminal and console abstraction
206//!
207//! ## 📄 License
208//!
209//! This project is licensed under either of
210//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
211//! - MIT License ([LICENSE-MIT](LICENSE-MIT))
212//!
213//! at your option.
214
215#![cfg_attr(docsrs, feature(doc_cfg))]
216#![deny(missing_docs)]
217#![warn(clippy::all)]
218#![warn(clippy::pedantic)]
219#![warn(clippy::nursery)]
220#![warn(clippy::cargo)]
221#![doc(html_root_url = "https://docs.rs/cli-boxes/0.1.0")]
222#![doc(
223    html_logo_url = "https://raw.githubusercontent.com/sabry-awad97/boxen-rs/main/assets/logo.png"
224)]
225#![doc(
226    html_favicon_url = "https://raw.githubusercontent.com/sabry-awad97/boxen-rs/main/assets/favicon.ico"
227)]
228
229#[cfg(feature = "serde")]
230use serde::{Deserialize, Serialize};
231use std::fmt;
232use std::str::FromStr;
233use strum::{EnumIter, IntoEnumIterator};
234
235/// A collection of Unicode characters used for drawing boxes in CLI applications.
236///
237/// This struct contains all the necessary characters to draw a complete box:
238/// corners, horizontal lines, and vertical lines. Each field represents a specific
239/// position in the box structure.
240///
241/// # Box Structure
242///
243/// ```text
244/// top_left ──── top ──── top_right
245///    │                      │
246///   left                  right
247///    │                      │
248/// bottom_left ── bottom ── bottom_right
249/// ```
250///
251/// # Memory Layout
252///
253/// `BoxChars` is a compact struct containing 8 `char` values (32 bytes total on most platforms).
254/// All predefined constants are evaluated at compile time for zero runtime cost.
255///
256/// # Thread Safety
257///
258/// `BoxChars` implements `Send` and `Sync`, making it safe to use across threads.
259/// All operations are immutable after construction.
260///
261/// # Examples
262///
263/// ## Using Predefined Styles
264///
265/// ```rust
266/// use cli_boxes::BoxChars;
267///
268/// // Use predefined single-line box characters
269/// let single = BoxChars::SINGLE;
270/// assert_eq!(single.top_left, '┌');
271/// assert_eq!(single.top, '─');
272/// assert_eq!(single.top_right, '┐');
273///
274/// // Double-line for emphasis
275/// let double = BoxChars::DOUBLE;
276/// assert_eq!(double.top_left, '╔');
277/// ```
278///
279/// ## Custom Box Creation
280///
281/// ```rust
282/// use cli_boxes::BoxChars;
283///
284/// // Direct constructor
285/// let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
286///
287/// // Builder pattern (recommended for readability)
288/// let builder_box = BoxChars::builder()
289///     .corners('*')
290///     .horizontal('-')
291///     .vertical('|')
292///     .build();
293///
294/// assert_eq!(custom, builder_box);
295/// ```
296///
297/// ## Drawing a Complete Box
298///
299/// ```rust
300/// use cli_boxes::BoxChars;
301///
302/// fn draw_box(chars: &BoxChars, width: usize, height: usize) -> String {
303///     let mut result = String::new();
304///     
305///     // Top border
306///     result.push(chars.top_left);
307///     result.push_str(&chars.top.to_string().repeat(width.saturating_sub(2)));
308///     result.push(chars.top_right);
309///     result.push('\n');
310///     
311///     // Side borders
312///     for _ in 0..height.saturating_sub(2) {
313///         result.push(chars.left);
314///         result.push_str(&" ".repeat(width.saturating_sub(2)));
315///         result.push(chars.right);
316///         result.push('\n');
317///     }
318///     
319///     // Bottom border
320///     result.push(chars.bottom_left);
321///     result.push_str(&chars.bottom.to_string().repeat(width.saturating_sub(2)));
322///     result.push(chars.bottom_right);
323///     
324///     result
325/// }
326///
327/// let box_str = draw_box(&BoxChars::SINGLE, 5, 3);
328/// println!("{}", box_str);
329/// // Output:
330/// // ┌───┐
331/// // │   │
332/// // └───┘
333/// ```
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
336#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
337pub struct BoxChars {
338    /// Top-left corner character
339    pub top_left: char,
340    /// Top border character (repeated horizontally)
341    pub top: char,
342    /// Top-right corner character
343    pub top_right: char,
344    /// Right border character (repeated vertically)
345    pub right: char,
346    /// Bottom-right corner character
347    pub bottom_right: char,
348    /// Bottom border character (repeated horizontally)
349    pub bottom: char,
350    /// Bottom-left corner character
351    pub bottom_left: char,
352    /// Left border character (repeated vertically)
353    pub left: char,
354}
355
356/// Macro to reduce boilerplate when defining box character constants.
357macro_rules! box_chars {
358    ($name:ident, $tl:literal, $t:literal, $tr:literal, $r:literal, $br:literal, $b:literal, $bl:literal, $l:literal, $doc:literal) => {
359        #[doc = $doc]
360        pub const $name: Self = Self {
361            top_left: $tl,
362            top: $t,
363            top_right: $tr,
364            right: $r,
365            bottom_right: $br,
366            bottom: $b,
367            bottom_left: $bl,
368            left: $l,
369        };
370    };
371}
372
373impl BoxChars {
374    /// Creates a new `BoxChars` with the specified characters.
375    ///
376    /// # Arguments
377    ///
378    /// * `top_left` - Top-left corner character
379    /// * `top` - Top border character
380    /// * `top_right` - Top-right corner character
381    /// * `right` - Right border character
382    /// * `bottom_right` - Bottom-right corner character
383    /// * `bottom` - Bottom border character
384    /// * `bottom_left` - Bottom-left corner character
385    /// * `left` - Left border character
386    ///
387    /// # Examples
388    ///
389    /// ```rust
390    /// use cli_boxes::BoxChars;
391    ///
392    /// let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
393    /// assert_eq!(custom.top_left, '*');
394    /// assert_eq!(custom.top, '-');
395    /// ```
396    #[must_use]
397    #[allow(clippy::too_many_arguments)]
398    pub const fn new(
399        top_left: char,
400        top: char,
401        top_right: char,
402        right: char,
403        bottom_right: char,
404        bottom: char,
405        bottom_left: char,
406        left: char,
407    ) -> Self {
408        Self {
409            top_left,
410            top,
411            top_right,
412            right,
413            bottom_right,
414            bottom,
415            bottom_left,
416            left,
417        }
418    }
419
420    /// Creates a builder for constructing custom box characters.
421    ///
422    /// # Examples
423    ///
424    /// ```rust
425    /// use cli_boxes::BoxChars;
426    ///
427    /// let custom = BoxChars::builder()
428    ///     .corners('*')
429    ///     .horizontal('-')
430    ///     .vertical('|')
431    ///     .build();
432    /// ```
433    #[must_use]
434    pub fn builder() -> BoxCharsBuilder {
435        BoxCharsBuilder::default()
436    }
437
438    box_chars!(
439        NONE,
440        ' ',
441        ' ',
442        ' ',
443        ' ',
444        ' ',
445        ' ',
446        ' ',
447        ' ',
448        "Creates a box with no visible characters (all spaces).\n\nThis is useful when you want to maintain box structure without visible borders, or as a fallback when no box styling is desired."
449    );
450
451    box_chars!(
452        SINGLE,
453        '┌',
454        '─',
455        '┐',
456        '│',
457        '┘',
458        '─',
459        '└',
460        '│',
461        "Creates a box using single-line Unicode box-drawing characters.\n\nThis is the most commonly used box style, providing clean and professional-looking borders that work well in most terminal environments.\n\nCharacters used: `┌─┐│┘─└│`"
462    );
463
464    box_chars!(
465        DOUBLE,
466        '╔',
467        '═',
468        '╗',
469        '║',
470        '╝',
471        '═',
472        '╚',
473        '║',
474        "Creates a box using double-line Unicode box-drawing characters.\n\nThis style provides a bold, prominent appearance that's excellent for highlighting important content or creating emphasis in CLI applications.\n\nCharacters used: `╔═╗║╝═╚║`"
475    );
476
477    box_chars!(
478        ROUND,
479        '╭',
480        '─',
481        '╮',
482        '│',
483        '╯',
484        '─',
485        '╰',
486        '│',
487        "Creates a box using rounded corner Unicode box-drawing characters.\n\nThis style provides a softer, more modern appearance with curved corners that's aesthetically pleasing and less harsh than sharp corners.\n\nCharacters used: `╭─╮│╯─╰│`"
488    );
489
490    box_chars!(
491        BOLD,
492        '┏',
493        '━',
494        '┓',
495        '┃',
496        '┛',
497        '━',
498        '┗',
499        '┃',
500        "Creates a box using bold/thick line Unicode box-drawing characters.\n\nThis style provides maximum visual impact with thick, bold lines that command attention and create strong visual separation.\n\nCharacters used: `┏━┓┃┛━┗┃`"
501    );
502
503    box_chars!(
504        SINGLE_DOUBLE,
505        '╓',
506        '─',
507        '╖',
508        '║',
509        '╜',
510        '─',
511        '╙',
512        '║',
513        "Creates a box using single horizontal, double vertical box-drawing characters.\n\nThis mixed style combines single-line horizontal borders with double-line vertical borders, creating a unique visual effect.\n\nCharacters used: `╓─╖║╜─╙║`"
514    );
515
516    box_chars!(
517        DOUBLE_SINGLE,
518        '╒',
519        '═',
520        '╕',
521        '│',
522        '╛',
523        '═',
524        '╘',
525        '│',
526        "Creates a box using double horizontal, single vertical box-drawing characters.\n\nThis mixed style combines double-line horizontal borders with single-line vertical borders, creating an alternative visual effect to `SINGLE_DOUBLE`.\n\nCharacters used: `╒═╕│╛═╘│`"
527    );
528
529    box_chars!(
530        CLASSIC,
531        '+',
532        '-',
533        '+',
534        '|',
535        '+',
536        '-',
537        '+',
538        '|',
539        "Creates a box using classic ASCII characters for maximum compatibility.\n\nThis style uses only basic ASCII characters, ensuring compatibility with all terminal environments, including those that don't support Unicode.\n\nCharacters used: `+-+|+-+|`"
540    );
541
542    box_chars!(
543        ARROW,
544        '↘',
545        '↓',
546        '↙',
547        '←',
548        '↖',
549        '↑',
550        '↗',
551        '→',
552        "Creates a decorative box using arrow Unicode characters.\n\nThis unique style uses directional arrows to create an unconventional but eye-catching border effect. Best used for special emphasis or creative CLI applications.\n\nCharacters used: `↘↓↙←↖↑↗→`"
553    );
554}
555
556impl Default for BoxChars {
557    /// Returns the default box characters (single-line style).
558    ///
559    /// # Examples
560    ///
561    /// ```rust
562    /// use cli_boxes::BoxChars;
563    ///
564    /// let default_box = BoxChars::default();
565    /// assert_eq!(default_box, BoxChars::SINGLE);
566    /// ```
567    fn default() -> Self {
568        Self::SINGLE
569    }
570}
571
572impl fmt::Display for BoxChars {
573    /// Formats the box characters as a string showing all 8 characters in order.
574    ///
575    /// The format is: `top_left`, `top`, `top_right`, `right`, `bottom_right`, `bottom`, `bottom_left`, `left`
576    ///
577    /// # Examples
578    ///
579    /// ```rust
580    /// use cli_boxes::BoxChars;
581    ///
582    /// let single = BoxChars::SINGLE;
583    /// println!("{}", single); // Outputs: ┌─┐│┘─└│
584    /// ```
585    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586        write!(
587            f,
588            "{}{}{}{}{}{}{}{}",
589            self.top_left,
590            self.top,
591            self.top_right,
592            self.right,
593            self.bottom_right,
594            self.bottom,
595            self.bottom_left,
596            self.left
597        )
598    }
599}
600
601/// Builder for creating custom `BoxChars` with a fluent, type-safe API.
602///
603/// The builder pattern provides an ergonomic way to construct custom box characters
604/// with method chaining. All methods are `const fn` where possible for compile-time
605/// evaluation. The builder starts with invisible characters (`NONE`) and allows
606/// selective customization.
607///
608/// # Design Patterns
609///
610/// ## Uniform Styling
611/// Use the convenience methods for consistent appearance:
612/// - `corners()` - Sets all four corners to the same character
613/// - `horizontal()` - Sets top and bottom borders  
614/// - `vertical()` - Sets left and right borders
615///
616/// ## Selective Overrides
617/// Start with uniform styling, then override specific positions:
618///
619/// ```rust
620/// use cli_boxes::BoxChars;
621///
622/// let mixed = BoxChars::builder()
623///     .corners('●')           // Set all corners
624///     .horizontal('═')        // Set horizontal borders
625///     .vertical('║')          // Set vertical borders
626///     .top_left('╔')         // Override just top-left
627///     .build();
628/// ```
629///
630/// ## Asymmetric Designs
631/// Create unique, asymmetric box styles:
632///
633/// ```rust
634/// use cli_boxes::BoxChars;
635///
636/// let asymmetric = BoxChars::builder()
637///     .top_left('╭').top('─').top_right('╮')
638///     .left('│').right('│')
639///     .bottom_left('└').bottom('─').bottom_right('┘')
640///     .build();
641/// ```
642///
643/// ## Theme-Based Construction
644///
645/// ```rust
646/// use cli_boxes::BoxChars;
647///
648/// fn create_theme_box(theme: &str) -> BoxChars {
649///     let mut builder = BoxChars::builder();
650///     
651///     match theme {
652///         "minimal" => builder.corners('+').horizontal('-').vertical('|'),
653///         "fancy" => builder.corners('●').horizontal('═').vertical('║'),
654///         "retro" => builder.corners('*').horizontal('=').vertical(':'),
655///         _ => builder.corners('?').horizontal('?').vertical('?'),
656///     }.build()
657/// }
658/// ```
659///
660/// # Performance
661///
662/// The builder uses `const fn` methods where possible, allowing compile-time
663/// evaluation when used with constant inputs. The final `build()` call is
664/// zero-cost, simply returning the constructed `BoxChars` struct.
665#[derive(Debug, Clone)]
666pub struct BoxCharsBuilder {
667    chars: BoxChars,
668}
669
670impl Default for BoxCharsBuilder {
671    fn default() -> Self {
672        Self {
673            chars: BoxChars::NONE,
674        }
675    }
676}
677
678impl BoxCharsBuilder {
679    /// Sets all corner characters to the same value.
680    #[must_use]
681    pub const fn corners(mut self, char: char) -> Self {
682        self.chars.top_left = char;
683        self.chars.top_right = char;
684        self.chars.bottom_left = char;
685        self.chars.bottom_right = char;
686        self
687    }
688
689    /// Sets both horizontal border characters (top and bottom) to the same value.
690    #[must_use]
691    pub const fn horizontal(mut self, char: char) -> Self {
692        self.chars.top = char;
693        self.chars.bottom = char;
694        self
695    }
696
697    /// Sets both vertical border characters (left and right) to the same value.
698    #[must_use]
699    pub const fn vertical(mut self, char: char) -> Self {
700        self.chars.left = char;
701        self.chars.right = char;
702        self
703    }
704
705    /// Sets the top-left corner character.
706    #[must_use]
707    pub const fn top_left(mut self, char: char) -> Self {
708        self.chars.top_left = char;
709        self
710    }
711
712    /// Sets the top border character.
713    #[must_use]
714    pub const fn top(mut self, char: char) -> Self {
715        self.chars.top = char;
716        self
717    }
718
719    /// Sets the top-right corner character.
720    #[must_use]
721    pub const fn top_right(mut self, char: char) -> Self {
722        self.chars.top_right = char;
723        self
724    }
725
726    /// Sets the right border character.
727    #[must_use]
728    pub const fn right(mut self, char: char) -> Self {
729        self.chars.right = char;
730        self
731    }
732
733    /// Sets the bottom-right corner character.
734    #[must_use]
735    pub const fn bottom_right(mut self, char: char) -> Self {
736        self.chars.bottom_right = char;
737        self
738    }
739
740    /// Sets the bottom border character.
741    #[must_use]
742    pub const fn bottom(mut self, char: char) -> Self {
743        self.chars.bottom = char;
744        self
745    }
746
747    /// Sets the bottom-left corner character.
748    #[must_use]
749    pub const fn bottom_left(mut self, char: char) -> Self {
750        self.chars.bottom_left = char;
751        self
752    }
753
754    /// Sets the left border character.
755    #[must_use]
756    pub const fn left(mut self, char: char) -> Self {
757        self.chars.left = char;
758        self
759    }
760
761    /// Builds and returns the final `BoxChars`.
762    #[must_use]
763    pub const fn build(self) -> BoxChars {
764        self.chars
765    }
766}
767
768/// Available box drawing styles with semantic meaning and use cases.
769///
770/// Each style provides a different visual appearance optimized for specific use cases.
771/// This enum provides a convenient, type-safe way to select box styles without
772/// directly referencing character constants.
773///
774/// # Style Guidelines
775///
776/// - **Single**: Default choice for most applications, clean and readable
777/// - **Double**: Use for emphasis, headers, or important content sections  
778/// - **Round**: Modern appearance, good for user-friendly interfaces
779/// - **Bold**: Maximum emphasis, warnings, or critical information
780/// - **Mixed Styles**: Unique visual effects, decorative purposes
781/// - **Classic**: ASCII-only environments, legacy system compatibility
782/// - **Arrow**: Special effects, directional indicators, creative designs
783/// - **None**: Invisible spacing, layout without visible borders
784///
785/// # Performance Notes
786///
787/// All enum variants are zero-cost abstractions that compile to direct character constants.
788/// String parsing is optimized for performance with zero heap allocations.
789///
790/// # Examples
791///
792/// ## Basic Usage
793///
794/// ```rust
795/// use cli_boxes::{BorderStyle, BoxChars};
796///
797/// // Convert enum to box characters
798/// let box_chars = BoxChars::from(BorderStyle::Single);
799/// assert_eq!(box_chars.top_left, '┌');
800///
801/// // Using the convenience method
802/// let double_chars = BorderStyle::Double.chars();
803/// assert_eq!(double_chars.top_left, '╔');
804/// ```
805///
806/// ## Dynamic Style Selection
807///
808/// ```rust
809/// use cli_boxes::BorderStyle;
810///
811/// fn get_style_for_severity(level: u8) -> BorderStyle {
812///     match level {
813///         0 => BorderStyle::None,
814///         1 => BorderStyle::Single,
815///         2 => BorderStyle::Bold,
816///         3 => BorderStyle::Double,
817///         _ => BorderStyle::Classic,
818///     }
819/// }
820///
821/// let style = get_style_for_severity(2);
822/// let chars = style.chars();
823/// ```
824///
825/// ## Iteration and Discovery
826///
827/// ```rust
828/// use cli_boxes::BorderStyle;
829///
830/// // Display all available styles
831/// for style in BorderStyle::all() {
832///     let chars = style.chars();
833///     println!("{:12} -> {}", style.to_string(), chars);
834/// }
835/// ```
836///
837/// ## String Parsing with Error Handling
838///
839/// ```rust
840/// use cli_boxes::BorderStyle;
841/// use std::str::FromStr;
842///
843/// // Parse from configuration files or user input
844/// let styles = ["single", "double", "round", "invalid"];
845///
846/// for style_str in &styles {
847///     match style_str.parse::<BorderStyle>() {
848///         Ok(style) => println!("✓ Parsed '{}' as {:?}", style_str, style),
849///         Err(e) => println!("✗ Error: {}", e),
850///     }
851/// }
852/// ```
853#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
854pub enum BorderStyle {
855    /// No box characters (all spaces)
856    None,
857    /// Single-line box drawing characters: ┌─┐│┘─└│
858    Single,
859    /// Double-line box drawing characters: ╔═╗║╝═╚║
860    Double,
861    /// Rounded corner box drawing characters: ╭─╮│╯─╰│
862    Round,
863    /// Bold/thick line box drawing characters: ┏━┓┃┛━┗┃
864    Bold,
865    /// Single horizontal, double vertical: ╓─╖║╜─╙║
866    SingleDouble,
867    /// Double horizontal, single vertical: ╒═╕│╛═╘│
868    DoubleSingle,
869    /// ASCII-compatible classic box characters: +─+|+─+|
870    Classic,
871    /// Arrow-based decorative box characters: ↘↓↙←↖↑↗→
872    Arrow,
873}
874
875impl From<BorderStyle> for BoxChars {
876    /// Converts a `BorderStyle` enum variant to its corresponding `BoxChars`.
877    ///
878    /// # Examples
879    ///
880    /// ```rust
881    /// use cli_boxes::{BorderStyle, BoxChars};
882    ///
883    /// let single_chars = BoxChars::from(BorderStyle::Single);
884    /// assert_eq!(single_chars, BoxChars::SINGLE);
885    ///
886    /// let double_chars = BoxChars::from(BorderStyle::Double);
887    /// assert_eq!(double_chars, BoxChars::DOUBLE);
888    /// ```
889    fn from(style: BorderStyle) -> Self {
890        match style {
891            BorderStyle::None => Self::NONE,
892            BorderStyle::Single => Self::SINGLE,
893            BorderStyle::Double => Self::DOUBLE,
894            BorderStyle::Round => Self::ROUND,
895            BorderStyle::Bold => Self::BOLD,
896            BorderStyle::SingleDouble => Self::SINGLE_DOUBLE,
897            BorderStyle::DoubleSingle => Self::DOUBLE_SINGLE,
898            BorderStyle::Classic => Self::CLASSIC,
899            BorderStyle::Arrow => Self::ARROW,
900        }
901    }
902}
903
904impl BorderStyle {
905    /// Returns the `BoxChars` associated with this border style.
906    ///
907    /// This is a convenience method that's equivalent to `BoxChars::from(style)`.
908    ///
909    /// # Examples
910    ///
911    /// ```rust
912    /// use cli_boxes::BorderStyle;
913    ///
914    /// let style = BorderStyle::Bold;
915    /// let chars = style.chars();
916    /// assert_eq!(chars.top, '━');
917    /// ```
918    #[must_use]
919    pub fn chars(self) -> BoxChars {
920        BoxChars::from(self)
921    }
922
923    /// Returns an iterator over all available border styles.
924    ///
925    /// This is a convenience method that uses the `EnumIter` trait.
926    ///
927    /// # Examples
928    ///
929    /// ```rust
930    /// use cli_boxes::BorderStyle;
931    ///
932    /// for style in BorderStyle::all() {
933    ///     println!("Style: {:?}", style);
934    /// }
935    /// ```
936    pub fn all() -> impl Iterator<Item = Self> {
937        Self::iter()
938    }
939}
940
941impl fmt::Display for BorderStyle {
942    /// Formats the border style as a lowercase string.
943    ///
944    /// # Examples
945    ///
946    /// ```rust
947    /// use cli_boxes::BorderStyle;
948    ///
949    /// assert_eq!(BorderStyle::Single.to_string(), "single");
950    /// assert_eq!(BorderStyle::DoubleSingle.to_string(), "double_single");
951    /// assert_eq!(BorderStyle::Arrow.to_string(), "arrow");
952    /// ```
953    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
954        let s = match self {
955            Self::None => "none",
956            Self::Single => "single",
957            Self::Double => "double",
958            Self::Round => "round",
959            Self::Bold => "bold",
960            Self::SingleDouble => "single_double",
961            Self::DoubleSingle => "double_single",
962            Self::Classic => "classic",
963            Self::Arrow => "arrow",
964        };
965        write!(f, "{s}")
966    }
967}
968
969impl FromStr for BorderStyle {
970    type Err = ParseBorderStyleError;
971
972    /// Parses a string into a `BorderStyle`.
973    ///
974    /// The parsing is case-insensitive and accepts both `snake_case` and `kebab-case`.
975    /// This implementation is optimized to avoid heap allocations.
976    ///
977    /// # Examples
978    ///
979    /// ```rust
980    /// use std::str::FromStr;
981    /// use cli_boxes::BorderStyle;
982    ///
983    /// assert_eq!("single".parse::<BorderStyle>().unwrap(), BorderStyle::Single);
984    /// assert_eq!("DOUBLE".parse::<BorderStyle>().unwrap(), BorderStyle::Double);
985    /// assert_eq!("single-double".parse::<BorderStyle>().unwrap(), BorderStyle::SingleDouble);
986    /// assert_eq!("double_single".parse::<BorderStyle>().unwrap(), BorderStyle::DoubleSingle);
987    ///
988    /// // Error case
989    /// assert!("invalid".parse::<BorderStyle>().is_err());
990    /// ```
991    fn from_str(s: &str) -> Result<Self, Self::Err> {
992        // Helper function to match strings case-insensitively with hyphen/underscore normalization
993        let matches = |target: &str| -> bool {
994            if s.len() != target.len() {
995                return false;
996            }
997            s.bytes().zip(target.bytes()).all(|(a, b)| {
998                let a_norm = if a == b'-' {
999                    b'_'
1000                } else {
1001                    a.to_ascii_lowercase()
1002                };
1003                a_norm == b
1004            })
1005        };
1006
1007        if matches("none") {
1008            Ok(Self::None)
1009        } else if matches("single") {
1010            Ok(Self::Single)
1011        } else if matches("double") {
1012            Ok(Self::Double)
1013        } else if matches("round") {
1014            Ok(Self::Round)
1015        } else if matches("bold") {
1016            Ok(Self::Bold)
1017        } else if matches("single_double") {
1018            Ok(Self::SingleDouble)
1019        } else if matches("double_single") {
1020            Ok(Self::DoubleSingle)
1021        } else if matches("classic") {
1022            Ok(Self::Classic)
1023        } else if matches("arrow") {
1024            Ok(Self::Arrow)
1025        } else {
1026            Err(ParseBorderStyleError::InvalidStyle(s.to_string()))
1027        }
1028    }
1029}
1030
1031/// Error type for parsing `BorderStyle` from string.
1032#[derive(Debug, Clone, PartialEq, Eq)]
1033pub enum ParseBorderStyleError {
1034    /// The provided string does not match any known border style.
1035    InvalidStyle(String),
1036}
1037
1038impl fmt::Display for ParseBorderStyleError {
1039    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1040        match self {
1041            Self::InvalidStyle(style) => {
1042                let suggestions = BorderStyle::all()
1043                    .map(|s| s.to_string())
1044                    .collect::<Vec<_>>()
1045                    .join(", ");
1046
1047                write!(
1048                    f,
1049                    "Invalid border style: '{style}'. Did you mean one of: {suggestions}?"
1050                )
1051            }
1052        }
1053    }
1054}
1055
1056impl std::error::Error for ParseBorderStyleError {}
1057
1058#[cfg(test)]
1059mod tests {
1060    use super::*;
1061    use std::collections::HashSet;
1062
1063    #[test]
1064    fn test_box_chars_constants() {
1065        // Test that all constants have the expected characters
1066        assert_eq!(BoxChars::NONE.top_left, ' ');
1067        assert_eq!(BoxChars::SINGLE.top_left, '┌');
1068        assert_eq!(BoxChars::DOUBLE.top_left, '╔');
1069        assert_eq!(BoxChars::ROUND.top_left, '╭');
1070        assert_eq!(BoxChars::BOLD.top_left, '┏');
1071        assert_eq!(BoxChars::SINGLE_DOUBLE.top_left, '╓');
1072        assert_eq!(BoxChars::DOUBLE_SINGLE.top_left, '╒');
1073        assert_eq!(BoxChars::CLASSIC.top_left, '+');
1074        assert_eq!(BoxChars::ARROW.top_left, '↘');
1075    }
1076
1077    #[test]
1078    fn test_box_chars_new() {
1079        let custom = BoxChars::new('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
1080        assert_eq!(custom.top_left, 'a');
1081        assert_eq!(custom.top, 'b');
1082        assert_eq!(custom.top_right, 'c');
1083        assert_eq!(custom.right, 'd');
1084        assert_eq!(custom.bottom_right, 'e');
1085        assert_eq!(custom.bottom, 'f');
1086        assert_eq!(custom.bottom_left, 'g');
1087        assert_eq!(custom.left, 'h');
1088    }
1089
1090    #[test]
1091    fn test_box_chars_default() {
1092        let default = BoxChars::default();
1093        assert_eq!(default, BoxChars::SINGLE);
1094    }
1095
1096    #[test]
1097    fn test_box_chars_display() {
1098        let single = BoxChars::SINGLE;
1099        assert_eq!(single.to_string(), "┌─┐│┘─└│");
1100
1101        let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
1102        assert_eq!(custom.to_string(), "*-*|*-*|");
1103    }
1104
1105    #[test]
1106    fn test_box_chars_traits() {
1107        let single = BoxChars::SINGLE;
1108        let single_copy = single;
1109
1110        // Test Copy trait
1111        assert_eq!(single, single_copy);
1112
1113        // Test Hash trait
1114        let mut set = HashSet::new();
1115        set.insert(single);
1116        assert!(set.contains(&single));
1117
1118        // Test Debug trait
1119        let debug_str = format!("{single:?}");
1120        assert!(debug_str.contains("BoxChars"));
1121    }
1122
1123    #[test]
1124    fn test_builder_pattern() {
1125        let custom = BoxChars::builder()
1126            .corners('*')
1127            .horizontal('-')
1128            .vertical('|')
1129            .build();
1130
1131        assert_eq!(custom.top_left, '*');
1132        assert_eq!(custom.top_right, '*');
1133        assert_eq!(custom.bottom_left, '*');
1134        assert_eq!(custom.bottom_right, '*');
1135        assert_eq!(custom.top, '-');
1136        assert_eq!(custom.bottom, '-');
1137        assert_eq!(custom.left, '|');
1138        assert_eq!(custom.right, '|');
1139    }
1140
1141    #[test]
1142    fn test_builder_individual_methods() {
1143        let custom = BoxChars::builder()
1144            .top_left('a')
1145            .top('b')
1146            .top_right('c')
1147            .right('d')
1148            .bottom_right('e')
1149            .bottom('f')
1150            .bottom_left('g')
1151            .left('h')
1152            .build();
1153
1154        assert_eq!(custom.top_left, 'a');
1155        assert_eq!(custom.top, 'b');
1156        assert_eq!(custom.top_right, 'c');
1157        assert_eq!(custom.right, 'd');
1158        assert_eq!(custom.bottom_right, 'e');
1159        assert_eq!(custom.bottom, 'f');
1160        assert_eq!(custom.bottom_left, 'g');
1161        assert_eq!(custom.left, 'h');
1162    }
1163
1164    #[test]
1165    fn test_builder_chaining() {
1166        let result = BoxChars::builder()
1167            .corners('●')
1168            .horizontal('═')
1169            .vertical('║')
1170            .top_left('╔') // Override corner
1171            .build();
1172
1173        assert_eq!(result.top_left, '╔'); // Overridden
1174        assert_eq!(result.top_right, '●'); // From corners
1175        assert_eq!(result.top, '═'); // From horizontal
1176        assert_eq!(result.left, '║'); // From vertical
1177    }
1178
1179    #[test]
1180    fn test_builder_default() {
1181        let default_builder = BoxCharsBuilder::default();
1182        let result = default_builder.build();
1183        assert_eq!(result, BoxChars::NONE);
1184    }
1185
1186    #[test]
1187    fn test_border_style_enum() {
1188        // Test all variants exist
1189        let styles = [
1190            BorderStyle::None,
1191            BorderStyle::Single,
1192            BorderStyle::Double,
1193            BorderStyle::Round,
1194            BorderStyle::Bold,
1195            BorderStyle::SingleDouble,
1196            BorderStyle::DoubleSingle,
1197            BorderStyle::Classic,
1198            BorderStyle::Arrow,
1199        ];
1200
1201        assert_eq!(styles.len(), 9);
1202    }
1203
1204    #[test]
1205    fn test_border_style_to_box_chars() {
1206        assert_eq!(BoxChars::from(BorderStyle::None), BoxChars::NONE);
1207        assert_eq!(BoxChars::from(BorderStyle::Single), BoxChars::SINGLE);
1208        assert_eq!(BoxChars::from(BorderStyle::Double), BoxChars::DOUBLE);
1209        assert_eq!(BoxChars::from(BorderStyle::Round), BoxChars::ROUND);
1210        assert_eq!(BoxChars::from(BorderStyle::Bold), BoxChars::BOLD);
1211        assert_eq!(
1212            BoxChars::from(BorderStyle::SingleDouble),
1213            BoxChars::SINGLE_DOUBLE
1214        );
1215        assert_eq!(
1216            BoxChars::from(BorderStyle::DoubleSingle),
1217            BoxChars::DOUBLE_SINGLE
1218        );
1219        assert_eq!(BoxChars::from(BorderStyle::Classic), BoxChars::CLASSIC);
1220        assert_eq!(BoxChars::from(BorderStyle::Arrow), BoxChars::ARROW);
1221    }
1222
1223    #[test]
1224    fn test_border_style_chars_method() {
1225        let style = BorderStyle::Bold;
1226        let chars = style.chars();
1227        assert_eq!(chars, BoxChars::BOLD);
1228        assert_eq!(chars.top, '━');
1229    }
1230
1231    #[test]
1232    fn test_border_style_display() {
1233        assert_eq!(BorderStyle::None.to_string(), "none");
1234        assert_eq!(BorderStyle::Single.to_string(), "single");
1235        assert_eq!(BorderStyle::Double.to_string(), "double");
1236        assert_eq!(BorderStyle::Round.to_string(), "round");
1237        assert_eq!(BorderStyle::Bold.to_string(), "bold");
1238        assert_eq!(BorderStyle::SingleDouble.to_string(), "single_double");
1239        assert_eq!(BorderStyle::DoubleSingle.to_string(), "double_single");
1240        assert_eq!(BorderStyle::Classic.to_string(), "classic");
1241        assert_eq!(BorderStyle::Arrow.to_string(), "arrow");
1242    }
1243
1244    #[test]
1245    fn test_border_style_from_str() {
1246        // Test basic parsing
1247        assert_eq!("none".parse::<BorderStyle>().unwrap(), BorderStyle::None);
1248        assert_eq!(
1249            "single".parse::<BorderStyle>().unwrap(),
1250            BorderStyle::Single
1251        );
1252        assert_eq!(
1253            "double".parse::<BorderStyle>().unwrap(),
1254            BorderStyle::Double
1255        );
1256        assert_eq!("round".parse::<BorderStyle>().unwrap(), BorderStyle::Round);
1257        assert_eq!("bold".parse::<BorderStyle>().unwrap(), BorderStyle::Bold);
1258        assert_eq!(
1259            "single_double".parse::<BorderStyle>().unwrap(),
1260            BorderStyle::SingleDouble
1261        );
1262        assert_eq!(
1263            "double_single".parse::<BorderStyle>().unwrap(),
1264            BorderStyle::DoubleSingle
1265        );
1266        assert_eq!(
1267            "classic".parse::<BorderStyle>().unwrap(),
1268            BorderStyle::Classic
1269        );
1270        assert_eq!("arrow".parse::<BorderStyle>().unwrap(), BorderStyle::Arrow);
1271    }
1272
1273    #[test]
1274    fn test_border_style_from_conversion() {
1275        assert_eq!(BoxChars::from(BorderStyle::Single), BoxChars::SINGLE);
1276        assert_eq!(BoxChars::from(BorderStyle::Double), BoxChars::DOUBLE);
1277        assert_eq!(BoxChars::from(BorderStyle::Round), BoxChars::ROUND);
1278        assert_eq!(BoxChars::from(BorderStyle::Bold), BoxChars::BOLD);
1279        assert_eq!(
1280            BoxChars::from(BorderStyle::SingleDouble),
1281            BoxChars::SINGLE_DOUBLE
1282        );
1283        assert_eq!(
1284            BoxChars::from(BorderStyle::DoubleSingle),
1285            BoxChars::DOUBLE_SINGLE
1286        );
1287        assert_eq!(BoxChars::from(BorderStyle::Classic), BoxChars::CLASSIC);
1288        assert_eq!(BoxChars::from(BorderStyle::Arrow), BoxChars::ARROW);
1289        assert_eq!(BoxChars::from(BorderStyle::None), BoxChars::NONE);
1290    }
1291
1292    #[test]
1293    fn test_border_style_all() {
1294        let all_styles: Vec<_> = BorderStyle::all().collect();
1295        assert_eq!(all_styles.len(), 9);
1296        assert!(all_styles.contains(&BorderStyle::Single));
1297        assert!(all_styles.contains(&BorderStyle::Double));
1298    }
1299
1300    #[test]
1301    fn test_border_style_from_str_case_insensitive() {
1302        assert_eq!(
1303            "SINGLE".parse::<BorderStyle>().unwrap(),
1304            BorderStyle::Single
1305        );
1306        assert_eq!(
1307            "Double".parse::<BorderStyle>().unwrap(),
1308            BorderStyle::Double
1309        );
1310        assert_eq!("ROUND".parse::<BorderStyle>().unwrap(), BorderStyle::Round);
1311        assert_eq!("Bold".parse::<BorderStyle>().unwrap(), BorderStyle::Bold);
1312    }
1313
1314    #[test]
1315    fn test_border_style_from_str_kebab_case() {
1316        assert_eq!(
1317            "single-double".parse::<BorderStyle>().unwrap(),
1318            BorderStyle::SingleDouble
1319        );
1320        assert_eq!(
1321            "double-single".parse::<BorderStyle>().unwrap(),
1322            BorderStyle::DoubleSingle
1323        );
1324        assert_eq!(
1325            "SINGLE-DOUBLE".parse::<BorderStyle>().unwrap(),
1326            BorderStyle::SingleDouble
1327        );
1328        assert_eq!(
1329            "Double-Single".parse::<BorderStyle>().unwrap(),
1330            BorderStyle::DoubleSingle
1331        );
1332    }
1333
1334    #[test]
1335    fn test_border_style_from_str_invalid() {
1336        assert!("invalid".parse::<BorderStyle>().is_err());
1337        assert!("".parse::<BorderStyle>().is_err());
1338        assert!("singleee".parse::<BorderStyle>().is_err());
1339        assert!("single_".parse::<BorderStyle>().is_err());
1340    }
1341
1342    #[test]
1343    fn test_parse_border_style_error() {
1344        let error = "invalid".parse::<BorderStyle>().unwrap_err();
1345        assert_eq!(
1346            error,
1347            ParseBorderStyleError::InvalidStyle("invalid".to_string())
1348        );
1349
1350        let error_display = error.to_string();
1351        assert!(error_display.contains("Invalid border style: 'invalid'"));
1352        assert!(error_display.contains("Did you mean one of:"));
1353        assert!(error_display.contains("single"));
1354        assert!(error_display.contains("double"));
1355    }
1356
1357    #[test]
1358    fn test_parse_border_style_error_traits() {
1359        let error = ParseBorderStyleError::InvalidStyle("test".to_string());
1360
1361        // Test Debug
1362        let debug_str = format!("{error:?}");
1363        assert!(debug_str.contains("InvalidStyle"));
1364
1365        // Test Error trait
1366        let _: &dyn std::error::Error = &error;
1367    }
1368
1369    #[test]
1370    fn test_zero_allocation_parsing() {
1371        // This test ensures our optimized parsing doesn't allocate
1372        // We can't directly test allocations in unit tests, but we can test
1373        // that the parsing logic works correctly for edge cases
1374
1375        // Test exact length matching
1376        assert!("single".parse::<BorderStyle>().is_ok());
1377        assert!("singlee".parse::<BorderStyle>().is_err()); // Too long
1378        assert!("singl".parse::<BorderStyle>().is_err()); // Too short
1379
1380        // Test case normalization
1381        assert_eq!(
1382            "SINGLE".parse::<BorderStyle>().unwrap(),
1383            BorderStyle::Single
1384        );
1385        assert_eq!(
1386            "single".parse::<BorderStyle>().unwrap(),
1387            BorderStyle::Single
1388        );
1389        assert_eq!(
1390            "Single".parse::<BorderStyle>().unwrap(),
1391            BorderStyle::Single
1392        );
1393
1394        // Test hyphen/underscore normalization
1395        assert_eq!(
1396            "single-double".parse::<BorderStyle>().unwrap(),
1397            BorderStyle::SingleDouble
1398        );
1399        assert_eq!(
1400            "single_double".parse::<BorderStyle>().unwrap(),
1401            BorderStyle::SingleDouble
1402        );
1403    }
1404
1405    #[test]
1406    fn test_comprehensive_style_coverage() {
1407        // Ensure all BorderStyle variants have corresponding BoxChars
1408        for style in BorderStyle::all() {
1409            let chars = BoxChars::from(style);
1410            let chars_method = style.chars();
1411            assert_eq!(chars, chars_method);
1412
1413            // Ensure the conversion is reversible through string representation
1414            let style_str = style.to_string();
1415            let parsed_style: BorderStyle = style_str.parse().unwrap();
1416            assert_eq!(style, parsed_style);
1417        }
1418    }
1419
1420    #[test]
1421    fn test_unicode_characters() {
1422        // Test that Unicode characters are correctly stored and retrieved
1423        let single = BoxChars::SINGLE;
1424        assert_eq!(single.top_left as u32, 0x250C); // ┌
1425        assert_eq!(single.top as u32, 0x2500); // ─
1426
1427        let double = BoxChars::DOUBLE;
1428        assert_eq!(double.top_left as u32, 0x2554); // ╔
1429        assert_eq!(double.top as u32, 0x2550); // ═
1430
1431        let round = BoxChars::ROUND;
1432        assert_eq!(round.top_left as u32, 0x256D); // ╭
1433        assert_eq!(round.bottom_right as u32, 0x256F); // ╯
1434    }
1435
1436    #[cfg(feature = "serde")]
1437    #[test]
1438    fn test_serde_serialization() {
1439        use serde_json;
1440
1441        let single = BoxChars::SINGLE;
1442        let serialized = serde_json::to_string(&single).unwrap();
1443        let deserialized: BoxChars = serde_json::from_str(&serialized).unwrap();
1444        assert_eq!(single, deserialized);
1445
1446        // Test camelCase field names
1447        assert!(serialized.contains("topLeft"));
1448        assert!(serialized.contains("topRight"));
1449        assert!(serialized.contains("bottomLeft"));
1450        assert!(serialized.contains("bottomRight"));
1451    }
1452
1453    #[test]
1454    fn test_edge_cases() {
1455        // Test empty builder
1456        let empty = BoxChars::builder().build();
1457        assert_eq!(empty, BoxChars::NONE);
1458
1459        // Test builder method chaining order doesn't matter
1460        let box1 = BoxChars::builder()
1461            .corners('*')
1462            .horizontal('-')
1463            .vertical('|')
1464            .build();
1465        let box2 = BoxChars::builder()
1466            .vertical('|')
1467            .corners('*')
1468            .horizontal('-')
1469            .build();
1470        assert_eq!(box1, box2);
1471
1472        // Test that builder methods override previous values
1473        let overridden = BoxChars::builder().corners('*').corners('#').build();
1474        assert_eq!(overridden.top_left, '#');
1475        assert_eq!(overridden.top_right, '#');
1476    }
1477
1478    #[test]
1479    fn test_memory_layout() {
1480        use std::mem;
1481
1482        // BoxChars should be small and efficiently packed
1483        assert_eq!(mem::size_of::<BoxChars>(), mem::size_of::<char>() * 8);
1484
1485        // BorderStyle should be small (single byte enum)
1486        assert!(mem::size_of::<BorderStyle>() <= mem::size_of::<u8>());
1487
1488        // Test alignment
1489        assert_eq!(mem::align_of::<BoxChars>(), mem::align_of::<char>());
1490    }
1491}