Skip to main content

azul_css/props/layout/
flex.rs

1//! CSS properties for flexbox layout.
2
3use crate::corety::AzString;
4use alloc::string::{String, ToString};
5use core::num::ParseFloatError;
6
7use crate::{
8    codegen::format::FormatAsRustCode,
9    props::{
10        basic::{
11            error::ParseFloatErrorWithInput,
12            length::{parse_float_value, FloatValue},
13        },
14        formatter::PrintAsCssValue,
15    },
16};
17
18// --- flex-grow ---
19
20/// Represents a `flex-grow` attribute, which dictates what proportion of the
21/// remaining space in the flex container should be assigned to the item.
22/// Default: 0
23#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(C)]
25pub struct LayoutFlexGrow {
26    pub inner: FloatValue,
27}
28
29impl core::fmt::Debug for LayoutFlexGrow {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        write!(f, "{}", self.inner.get())
32    }
33}
34
35impl Default for LayoutFlexGrow {
36    fn default() -> Self {
37        Self {
38            inner: FloatValue::const_new(0),
39        }
40    }
41}
42
43impl PrintAsCssValue for LayoutFlexGrow {
44    fn print_as_css_value(&self) -> String {
45        format!("{}", self.inner)
46    }
47}
48
49impl LayoutFlexGrow {
50    #[must_use]
51    pub fn new(value: isize) -> Self {
52        Self {
53            inner: FloatValue::new(crate::cast::isize_to_f32(value)),
54        }
55    }
56
57    #[must_use]
58    pub const fn const_new(value: isize) -> Self {
59        Self {
60            inner: FloatValue::const_new(value),
61        }
62    }
63
64    #[must_use]
65    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
66        Self {
67            inner: self.inner.interpolate(&other.inner, t),
68        }
69    }
70}
71
72#[cfg(feature = "parser")]
73#[derive(Clone, PartialEq, Eq)]
74pub enum FlexGrowParseError<'a> {
75    ParseFloat(ParseFloatError, &'a str),
76    NegativeValue(&'a str),
77}
78
79#[cfg(feature = "parser")]
80impl_debug_as_display!(FlexGrowParseError<'a>);
81#[cfg(feature = "parser")]
82impl_display! { FlexGrowParseError<'a>, {
83    ParseFloat(e, s) => format!("Invalid flex-grow value: \"{}\". Reason: {}", s, e),
84    NegativeValue(s) => format!("Invalid flex-grow value: \"{}\". Flex-grow cannot be negative", s),
85}}
86
87#[cfg(feature = "parser")]
88#[derive(Debug, Clone, PartialEq, Eq)]
89#[repr(C, u8)]
90pub enum FlexGrowParseErrorOwned {
91    ParseFloat(ParseFloatErrorWithInput),
92    NegativeValue(AzString),
93}
94
95#[cfg(feature = "parser")]
96impl FlexGrowParseError<'_> {
97    #[must_use]
98    pub fn to_contained(&self) -> FlexGrowParseErrorOwned {
99        match self {
100            FlexGrowParseError::ParseFloat(e, s) => {
101                FlexGrowParseErrorOwned::ParseFloat(ParseFloatErrorWithInput {
102                    error: e.clone().into(),
103                    input: (*s).to_string().into(),
104                })
105            }
106            FlexGrowParseError::NegativeValue(s) => {
107                FlexGrowParseErrorOwned::NegativeValue((*s).to_string().into())
108            }
109        }
110    }
111}
112
113#[cfg(feature = "parser")]
114impl FlexGrowParseErrorOwned {
115    #[must_use]
116    pub fn to_shared(&self) -> FlexGrowParseError<'_> {
117        match self {
118            Self::ParseFloat(e) => {
119                FlexGrowParseError::ParseFloat(e.error.to_std(), e.input.as_str())
120            }
121            Self::NegativeValue(s) => FlexGrowParseError::NegativeValue(s.as_str()),
122        }
123    }
124}
125
126#[cfg(feature = "parser")]
127/// # Errors
128///
129/// Returns an error if `input` is not a valid CSS `flex-grow` value.
130pub fn parse_layout_flex_grow(input: &str) -> Result<LayoutFlexGrow, FlexGrowParseError<'_>> {
131    match parse_float_value(input) {
132        Ok(o) => {
133            if o.get() < 0.0 {
134                Err(FlexGrowParseError::NegativeValue(input))
135            } else {
136                Ok(LayoutFlexGrow { inner: o })
137            }
138        }
139        Err(e) => Err(FlexGrowParseError::ParseFloat(e, input)),
140    }
141}
142
143// --- flex-shrink ---
144
145/// Represents a `flex-shrink` attribute, which dictates what proportion of
146/// the negative space in the flex container should be removed from the item.
147/// Default: 1
148#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[repr(C)]
150pub struct LayoutFlexShrink {
151    pub inner: FloatValue,
152}
153
154impl core::fmt::Debug for LayoutFlexShrink {
155    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156        write!(f, "{}", self.inner.get())
157    }
158}
159
160impl Default for LayoutFlexShrink {
161    fn default() -> Self {
162        Self {
163            inner: FloatValue::const_new(1),
164        }
165    }
166}
167
168impl PrintAsCssValue for LayoutFlexShrink {
169    fn print_as_css_value(&self) -> String {
170        format!("{}", self.inner)
171    }
172}
173
174impl LayoutFlexShrink {
175    #[must_use]
176    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
177        Self {
178            inner: self.inner.interpolate(&other.inner, t),
179        }
180    }
181}
182
183#[cfg(feature = "parser")]
184#[derive(Clone, PartialEq, Eq)]
185pub enum FlexShrinkParseError<'a> {
186    ParseFloat(ParseFloatError, &'a str),
187    NegativeValue(&'a str),
188}
189
190#[cfg(feature = "parser")]
191impl_debug_as_display!(FlexShrinkParseError<'a>);
192#[cfg(feature = "parser")]
193impl_display! { FlexShrinkParseError<'a>, {
194    ParseFloat(e, s) => format!("Invalid flex-shrink value: \"{}\". Reason: {}", s, e),
195    NegativeValue(s) => format!("Invalid flex-shrink value: \"{}\". Flex-shrink cannot be negative", s),
196}}
197
198#[cfg(feature = "parser")]
199#[derive(Debug, Clone, PartialEq, Eq)]
200#[repr(C, u8)]
201pub enum FlexShrinkParseErrorOwned {
202    ParseFloat(ParseFloatErrorWithInput),
203    NegativeValue(AzString),
204}
205
206#[cfg(feature = "parser")]
207impl FlexShrinkParseError<'_> {
208    #[must_use]
209    pub fn to_contained(&self) -> FlexShrinkParseErrorOwned {
210        match self {
211            FlexShrinkParseError::ParseFloat(e, s) => {
212                FlexShrinkParseErrorOwned::ParseFloat(ParseFloatErrorWithInput {
213                    error: e.clone().into(),
214                    input: (*s).to_string().into(),
215                })
216            }
217            FlexShrinkParseError::NegativeValue(s) => {
218                FlexShrinkParseErrorOwned::NegativeValue((*s).to_string().into())
219            }
220        }
221    }
222}
223
224#[cfg(feature = "parser")]
225impl FlexShrinkParseErrorOwned {
226    #[must_use]
227    pub fn to_shared(&self) -> FlexShrinkParseError<'_> {
228        match self {
229            Self::ParseFloat(e) => {
230                FlexShrinkParseError::ParseFloat(e.error.to_std(), e.input.as_str())
231            }
232            Self::NegativeValue(s) => FlexShrinkParseError::NegativeValue(s.as_str()),
233        }
234    }
235}
236
237#[cfg(feature = "parser")]
238/// # Errors
239///
240/// Returns an error if `input` is not a valid CSS `flex-shrink` value.
241pub fn parse_layout_flex_shrink(input: &str) -> Result<LayoutFlexShrink, FlexShrinkParseError<'_>> {
242    match parse_float_value(input) {
243        Ok(o) => {
244            if o.get() < 0.0 {
245                Err(FlexShrinkParseError::NegativeValue(input))
246            } else {
247                Ok(LayoutFlexShrink { inner: o })
248            }
249        }
250        Err(e) => Err(FlexShrinkParseError::ParseFloat(e, input)),
251    }
252}
253
254// --- flex-direction ---
255
256/// Represents a `flex-direction` attribute, which establishes the main-axis,
257/// thus defining the direction flex items are placed in the flex container.
258/// Default: `Row`
259#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
260#[repr(C)]
261#[derive(Default)]
262pub enum LayoutFlexDirection {
263    #[default]
264    Row,
265    RowReverse,
266    Column,
267    ColumnReverse,
268}
269
270/// Represents the main or cross axis of a flex container.
271#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[repr(C)]
273pub enum LayoutAxis {
274    Horizontal,
275    Vertical,
276}
277
278impl LayoutFlexDirection {
279    #[must_use]
280    pub const fn get_axis(&self) -> LayoutAxis {
281        match self {
282            Self::Row | Self::RowReverse => LayoutAxis::Horizontal,
283            Self::Column | Self::ColumnReverse => LayoutAxis::Vertical,
284        }
285    }
286
287    #[must_use]
288    pub const fn is_reverse(&self) -> bool {
289        matches!(self, Self::RowReverse | Self::ColumnReverse)
290    }
291}
292
293impl PrintAsCssValue for LayoutFlexDirection {
294    fn print_as_css_value(&self) -> String {
295        String::from(match self {
296            Self::Row => "row",
297            Self::RowReverse => "row-reverse",
298            Self::Column => "column",
299            Self::ColumnReverse => "column-reverse",
300        })
301    }
302}
303
304#[cfg(feature = "parser")]
305#[derive(Clone, PartialEq, Eq)]
306pub enum FlexDirectionParseError<'a> {
307    InvalidValue(&'a str),
308}
309
310#[cfg(feature = "parser")]
311impl_debug_as_display!(FlexDirectionParseError<'a>);
312#[cfg(feature = "parser")]
313impl_display! { FlexDirectionParseError<'a>, {
314    InvalidValue(s) => format!("Invalid flex-direction value: \"{}\"", s),
315}}
316
317#[cfg(feature = "parser")]
318#[derive(Debug, Clone, PartialEq, Eq)]
319#[repr(C, u8)]
320pub enum FlexDirectionParseErrorOwned {
321    InvalidValue(AzString),
322}
323
324#[cfg(feature = "parser")]
325impl FlexDirectionParseError<'_> {
326    #[must_use]
327    pub fn to_contained(&self) -> FlexDirectionParseErrorOwned {
328        match self {
329            Self::InvalidValue(s) => {
330                FlexDirectionParseErrorOwned::InvalidValue((*s).to_string().into())
331            }
332        }
333    }
334}
335
336#[cfg(feature = "parser")]
337impl FlexDirectionParseErrorOwned {
338    #[must_use]
339    pub fn to_shared(&self) -> FlexDirectionParseError<'_> {
340        match self {
341            Self::InvalidValue(s) => FlexDirectionParseError::InvalidValue(s.as_str()),
342        }
343    }
344}
345
346#[cfg(feature = "parser")]
347/// # Errors
348///
349/// Returns an error if `input` is not a valid CSS `flex-direction` value.
350pub fn parse_layout_flex_direction(
351    input: &str,
352) -> Result<LayoutFlexDirection, FlexDirectionParseError<'_>> {
353    match input.trim() {
354        "row" => Ok(LayoutFlexDirection::Row),
355        "row-reverse" => Ok(LayoutFlexDirection::RowReverse),
356        "column" => Ok(LayoutFlexDirection::Column),
357        "column-reverse" => Ok(LayoutFlexDirection::ColumnReverse),
358        _ => Err(FlexDirectionParseError::InvalidValue(input)),
359    }
360}
361
362// --- flex-wrap ---
363
364/// Represents a `flex-wrap` attribute, which determines whether flex items
365/// are forced onto one line or can wrap onto multiple lines.
366/// Default: `NoWrap`
367#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
368#[repr(C)]
369#[derive(Default)]
370pub enum LayoutFlexWrap {
371    Wrap,
372    #[default]
373    NoWrap,
374    WrapReverse,
375}
376
377impl PrintAsCssValue for LayoutFlexWrap {
378    fn print_as_css_value(&self) -> String {
379        String::from(match self {
380            Self::Wrap => "wrap",
381            Self::NoWrap => "nowrap",
382            Self::WrapReverse => "wrap-reverse",
383        })
384    }
385}
386
387#[cfg(feature = "parser")]
388#[derive(Clone, PartialEq, Eq)]
389pub enum FlexWrapParseError<'a> {
390    InvalidValue(&'a str),
391}
392
393#[cfg(feature = "parser")]
394impl_debug_as_display!(FlexWrapParseError<'a>);
395#[cfg(feature = "parser")]
396impl_display! { FlexWrapParseError<'a>, {
397    InvalidValue(s) => format!("Invalid flex-wrap value: \"{}\"", s),
398}}
399
400#[cfg(feature = "parser")]
401#[derive(Debug, Clone, PartialEq, Eq)]
402#[repr(C, u8)]
403pub enum FlexWrapParseErrorOwned {
404    InvalidValue(AzString),
405}
406
407#[cfg(feature = "parser")]
408impl FlexWrapParseError<'_> {
409    #[must_use]
410    pub fn to_contained(&self) -> FlexWrapParseErrorOwned {
411        match self {
412            Self::InvalidValue(s) => FlexWrapParseErrorOwned::InvalidValue((*s).to_string().into()),
413        }
414    }
415}
416
417#[cfg(feature = "parser")]
418impl FlexWrapParseErrorOwned {
419    #[must_use]
420    pub fn to_shared(&self) -> FlexWrapParseError<'_> {
421        match self {
422            Self::InvalidValue(s) => FlexWrapParseError::InvalidValue(s.as_str()),
423        }
424    }
425}
426
427#[cfg(feature = "parser")]
428/// # Errors
429///
430/// Returns an error if `input` is not a valid CSS `flex-wrap` value.
431pub fn parse_layout_flex_wrap(input: &str) -> Result<LayoutFlexWrap, FlexWrapParseError<'_>> {
432    match input.trim() {
433        "wrap" => Ok(LayoutFlexWrap::Wrap),
434        "nowrap" => Ok(LayoutFlexWrap::NoWrap),
435        "wrap-reverse" => Ok(LayoutFlexWrap::WrapReverse),
436        _ => Err(FlexWrapParseError::InvalidValue(input)),
437    }
438}
439
440// --- justify-content ---
441
442/// Represents a `justify-content` attribute, which defines the alignment
443/// along the main axis.
444/// Default: `Start` (flex-start)
445#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
446#[repr(C)]
447#[derive(Default)]
448pub enum LayoutJustifyContent {
449    FlexStart,
450    FlexEnd,
451    #[default]
452    Start,
453    End,
454    Center,
455    SpaceBetween,
456    SpaceAround,
457    SpaceEvenly,
458}
459
460impl PrintAsCssValue for LayoutJustifyContent {
461    fn print_as_css_value(&self) -> String {
462        String::from(match self {
463            Self::Start => "start",
464            Self::End => "end",
465            Self::FlexStart => "flex-start",
466            Self::FlexEnd => "flex-end",
467            Self::Center => "center",
468            Self::SpaceBetween => "space-between",
469            Self::SpaceAround => "space-around",
470            Self::SpaceEvenly => "space-evenly",
471        })
472    }
473}
474
475#[cfg(feature = "parser")]
476#[derive(Clone, PartialEq, Eq)]
477pub enum JustifyContentParseError<'a> {
478    InvalidValue(&'a str),
479}
480
481#[cfg(feature = "parser")]
482impl_debug_as_display!(JustifyContentParseError<'a>);
483#[cfg(feature = "parser")]
484impl_display! { JustifyContentParseError<'a>, {
485    InvalidValue(s) => format!("Invalid justify-content value: \"{}\"", s),
486}}
487
488#[cfg(feature = "parser")]
489#[derive(Debug, Clone, PartialEq, Eq)]
490#[repr(C, u8)]
491pub enum JustifyContentParseErrorOwned {
492    InvalidValue(AzString),
493}
494
495#[cfg(feature = "parser")]
496impl JustifyContentParseError<'_> {
497    #[must_use]
498    pub fn to_contained(&self) -> JustifyContentParseErrorOwned {
499        match self {
500            Self::InvalidValue(s) => {
501                JustifyContentParseErrorOwned::InvalidValue((*s).to_string().into())
502            }
503        }
504    }
505}
506
507#[cfg(feature = "parser")]
508impl JustifyContentParseErrorOwned {
509    #[must_use]
510    pub fn to_shared(&self) -> JustifyContentParseError<'_> {
511        match self {
512            Self::InvalidValue(s) => JustifyContentParseError::InvalidValue(s.as_str()),
513        }
514    }
515}
516
517#[cfg(feature = "parser")]
518/// # Errors
519///
520/// Returns an error if `input` is not a valid CSS `justify-content` value.
521pub fn parse_layout_justify_content(
522    input: &str,
523) -> Result<LayoutJustifyContent, JustifyContentParseError<'_>> {
524    match input.trim() {
525        "flex-start" => Ok(LayoutJustifyContent::FlexStart),
526        "flex-end" => Ok(LayoutJustifyContent::FlexEnd),
527        "start" => Ok(LayoutJustifyContent::Start),
528        "end" => Ok(LayoutJustifyContent::End),
529        "center" => Ok(LayoutJustifyContent::Center),
530        "space-between" => Ok(LayoutJustifyContent::SpaceBetween),
531        "space-around" => Ok(LayoutJustifyContent::SpaceAround),
532        "space-evenly" => Ok(LayoutJustifyContent::SpaceEvenly),
533        _ => Err(JustifyContentParseError::InvalidValue(input)),
534    }
535}
536
537// --- align-items ---
538
539/// Represents an `align-items` attribute, which defines the default behavior for
540/// how flex items are laid out along the cross axis on the current line.
541/// Default: `Stretch`
542#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
543#[repr(C)]
544#[derive(Default)]
545pub enum LayoutAlignItems {
546    #[default]
547    Stretch,
548    Center,
549    Start,
550    End,
551    Baseline,
552}
553
554impl PrintAsCssValue for LayoutAlignItems {
555    fn print_as_css_value(&self) -> String {
556        String::from(match self {
557            Self::Stretch => "stretch",
558            Self::Center => "center",
559            Self::Start => "flex-start",
560            Self::End => "flex-end",
561            Self::Baseline => "baseline",
562        })
563    }
564}
565
566#[cfg(feature = "parser")]
567#[derive(Clone, PartialEq, Eq)]
568pub enum AlignItemsParseError<'a> {
569    InvalidValue(&'a str),
570}
571
572#[cfg(feature = "parser")]
573impl_debug_as_display!(AlignItemsParseError<'a>);
574#[cfg(feature = "parser")]
575impl_display! { AlignItemsParseError<'a>, {
576    InvalidValue(s) => format!("Invalid align-items value: \"{}\"", s),
577}}
578
579#[cfg(feature = "parser")]
580#[derive(Debug, Clone, PartialEq, Eq)]
581#[repr(C, u8)]
582pub enum AlignItemsParseErrorOwned {
583    InvalidValue(AzString),
584}
585
586#[cfg(feature = "parser")]
587impl AlignItemsParseError<'_> {
588    #[must_use]
589    pub fn to_contained(&self) -> AlignItemsParseErrorOwned {
590        match self {
591            Self::InvalidValue(s) => {
592                AlignItemsParseErrorOwned::InvalidValue((*s).to_string().into())
593            }
594        }
595    }
596}
597
598#[cfg(feature = "parser")]
599impl AlignItemsParseErrorOwned {
600    #[must_use]
601    pub fn to_shared(&self) -> AlignItemsParseError<'_> {
602        match self {
603            Self::InvalidValue(s) => AlignItemsParseError::InvalidValue(s.as_str()),
604        }
605    }
606}
607
608#[cfg(feature = "parser")]
609/// # Errors
610///
611/// Returns an error if `input` is not a valid CSS `align-items` value.
612pub fn parse_layout_align_items(input: &str) -> Result<LayoutAlignItems, AlignItemsParseError<'_>> {
613    match input.trim() {
614        "stretch" => Ok(LayoutAlignItems::Stretch),
615        "center" => Ok(LayoutAlignItems::Center),
616        "start" | "flex-start" => Ok(LayoutAlignItems::Start),
617        "end" | "flex-end" => Ok(LayoutAlignItems::End),
618        "baseline" => Ok(LayoutAlignItems::Baseline),
619        _ => Err(AlignItemsParseError::InvalidValue(input)),
620    }
621}
622
623// --- align-content ---
624
625/// Represents an `align-content` attribute, which aligns a flex container's lines
626/// within it when there is extra space in the cross-axis.
627/// Default: `Stretch`
628#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
629#[repr(C)]
630#[derive(Default)]
631pub enum LayoutAlignContent {
632    #[default]
633    Stretch,
634    Center,
635    Start,
636    End,
637    SpaceBetween,
638    SpaceAround,
639}
640
641impl PrintAsCssValue for LayoutAlignContent {
642    fn print_as_css_value(&self) -> String {
643        String::from(match self {
644            Self::Stretch => "stretch",
645            Self::Center => "center",
646            Self::Start => "flex-start",
647            Self::End => "flex-end",
648            Self::SpaceBetween => "space-between",
649            Self::SpaceAround => "space-around",
650        })
651    }
652}
653
654#[cfg(feature = "parser")]
655#[derive(Clone, PartialEq, Eq)]
656pub enum AlignContentParseError<'a> {
657    InvalidValue(&'a str),
658}
659
660#[cfg(feature = "parser")]
661impl_debug_as_display!(AlignContentParseError<'a>);
662#[cfg(feature = "parser")]
663impl_display! { AlignContentParseError<'a>, {
664    InvalidValue(s) => format!("Invalid align-content value: \"{}\"", s),
665}}
666
667#[cfg(feature = "parser")]
668#[derive(Debug, Clone, PartialEq, Eq)]
669#[repr(C, u8)]
670pub enum AlignContentParseErrorOwned {
671    InvalidValue(AzString),
672}
673
674#[cfg(feature = "parser")]
675impl AlignContentParseError<'_> {
676    #[must_use]
677    pub fn to_contained(&self) -> AlignContentParseErrorOwned {
678        match self {
679            Self::InvalidValue(s) => {
680                AlignContentParseErrorOwned::InvalidValue((*s).to_string().into())
681            }
682        }
683    }
684}
685
686#[cfg(feature = "parser")]
687impl AlignContentParseErrorOwned {
688    #[must_use]
689    pub fn to_shared(&self) -> AlignContentParseError<'_> {
690        match self {
691            Self::InvalidValue(s) => AlignContentParseError::InvalidValue(s.as_str()),
692        }
693    }
694}
695
696#[cfg(feature = "parser")]
697/// # Errors
698///
699/// Returns an error if `input` is not a valid CSS `align-content` value.
700pub fn parse_layout_align_content(
701    input: &str,
702) -> Result<LayoutAlignContent, AlignContentParseError<'_>> {
703    match input.trim() {
704        "stretch" => Ok(LayoutAlignContent::Stretch),
705        "center" => Ok(LayoutAlignContent::Center),
706        "start" | "flex-start" => Ok(LayoutAlignContent::Start),
707        "end" | "flex-end" => Ok(LayoutAlignContent::End),
708        "space-between" => Ok(LayoutAlignContent::SpaceBetween),
709        "space-around" => Ok(LayoutAlignContent::SpaceAround),
710        _ => Err(AlignContentParseError::InvalidValue(input)),
711    }
712}
713
714// --- align-self ---
715
716/// Represents an `align-self` attribute, which allows the default alignment
717/// (or the one specified by align-items) to be overridden for individual flex items.
718/// Default: `Auto`
719#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
720#[repr(C)]
721#[derive(Default)]
722pub enum LayoutAlignSelf {
723    #[default]
724    Auto,
725    Stretch,
726    Center,
727    Start,
728    End,
729    Baseline,
730}
731
732impl PrintAsCssValue for LayoutAlignSelf {
733    fn print_as_css_value(&self) -> String {
734        String::from(match self {
735            Self::Auto => "auto",
736            Self::Stretch => "stretch",
737            Self::Center => "center",
738            Self::Start => "flex-start",
739            Self::End => "flex-end",
740            Self::Baseline => "baseline",
741        })
742    }
743}
744
745impl FormatAsRustCode for LayoutAlignSelf {
746    fn format_as_rust_code(&self, _tabs: usize) -> String {
747        format!(
748            "LayoutAlignSelf::{}",
749            match self {
750                Self::Auto => "Auto",
751                Self::Stretch => "Stretch",
752                Self::Center => "Center",
753                Self::Start => "Start",
754                Self::End => "End",
755                Self::Baseline => "Baseline",
756            }
757        )
758    }
759}
760
761#[cfg(feature = "parser")]
762#[derive(Clone, PartialEq, Eq)]
763pub enum AlignSelfParseError<'a> {
764    InvalidValue(&'a str),
765}
766
767#[cfg(feature = "parser")]
768impl_debug_as_display!(AlignSelfParseError<'a>);
769#[cfg(feature = "parser")]
770impl_display! { AlignSelfParseError<'a>, {
771    InvalidValue(s) => format!("Invalid align-self value: \"{}\"", s),
772}}
773
774#[cfg(feature = "parser")]
775#[derive(Debug, Clone, PartialEq, Eq)]
776#[repr(C, u8)]
777pub enum AlignSelfParseErrorOwned {
778    InvalidValue(AzString),
779}
780
781#[cfg(feature = "parser")]
782impl AlignSelfParseError<'_> {
783    #[must_use]
784    pub fn to_contained(&self) -> AlignSelfParseErrorOwned {
785        match self {
786            Self::InvalidValue(s) => {
787                AlignSelfParseErrorOwned::InvalidValue((*s).to_string().into())
788            }
789        }
790    }
791}
792
793#[cfg(feature = "parser")]
794impl AlignSelfParseErrorOwned {
795    #[must_use]
796    pub fn to_shared(&self) -> AlignSelfParseError<'_> {
797        match self {
798            Self::InvalidValue(s) => AlignSelfParseError::InvalidValue(s.as_str()),
799        }
800    }
801}
802
803#[cfg(feature = "parser")]
804/// # Errors
805///
806/// Returns an error if `input` is not a valid CSS `align-self` value.
807pub fn parse_layout_align_self(input: &str) -> Result<LayoutAlignSelf, AlignSelfParseError<'_>> {
808    match input.trim() {
809        "auto" => Ok(LayoutAlignSelf::Auto),
810        "stretch" => Ok(LayoutAlignSelf::Stretch),
811        "center" => Ok(LayoutAlignSelf::Center),
812        "start" | "flex-start" => Ok(LayoutAlignSelf::Start),
813        "end" | "flex-end" => Ok(LayoutAlignSelf::End),
814        "baseline" => Ok(LayoutAlignSelf::Baseline),
815        _ => Err(AlignSelfParseError::InvalidValue(input)),
816    }
817}
818
819// --- flex-basis ---
820#[allow(variant_size_differences)]
821// repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
822/// Represents a `flex-basis` attribute
823#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
824#[repr(C, u8)]
825#[derive(Default)]
826pub enum LayoutFlexBasis {
827    /// auto
828    #[default]
829    Auto,
830    /// Fixed size
831    Exact(crate::props::basic::pixel::PixelValue),
832}
833
834impl core::fmt::Debug for LayoutFlexBasis {
835    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
836        write!(f, "{}", self.print_as_css_value())
837    }
838}
839
840impl PrintAsCssValue for LayoutFlexBasis {
841    fn print_as_css_value(&self) -> String {
842        match self {
843            Self::Auto => "auto".to_string(),
844            Self::Exact(px) => px.print_as_css_value(),
845        }
846    }
847}
848
849impl FormatAsRustCode for LayoutFlexBasis {
850    fn format_as_rust_code(&self, _tabs: usize) -> String {
851        match self {
852            Self::Auto => String::from("LayoutFlexBasis::Auto"),
853            Self::Exact(px) => {
854                format!(
855                    "LayoutFlexBasis::Exact({})",
856                    crate::codegen::format::format_pixel_value(px)
857                )
858            }
859        }
860    }
861}
862
863#[cfg(feature = "parser")]
864#[derive(Clone, PartialEq, Eq)]
865pub enum FlexBasisParseError<'a> {
866    InvalidValue(&'a str),
867}
868
869#[cfg(feature = "parser")]
870impl_debug_as_display!(FlexBasisParseError<'a>);
871#[cfg(feature = "parser")]
872impl_display! { FlexBasisParseError<'a>, {
873    InvalidValue(e) => format!("Invalid flex-basis value: \"{}\"", e),
874}}
875
876#[cfg(feature = "parser")]
877#[derive(Debug, Clone, PartialEq, Eq)]
878#[repr(C, u8)]
879pub enum FlexBasisParseErrorOwned {
880    InvalidValue(AzString),
881}
882
883#[cfg(feature = "parser")]
884impl FlexBasisParseError<'_> {
885    #[must_use]
886    pub fn to_contained(&self) -> FlexBasisParseErrorOwned {
887        match self {
888            FlexBasisParseError::InvalidValue(s) => {
889                FlexBasisParseErrorOwned::InvalidValue((*s).to_string().into())
890            }
891        }
892    }
893}
894
895#[cfg(feature = "parser")]
896impl FlexBasisParseErrorOwned {
897    #[must_use]
898    pub fn to_shared(&self) -> FlexBasisParseError<'_> {
899        match self {
900            Self::InvalidValue(s) => FlexBasisParseError::InvalidValue(s.as_str()),
901        }
902    }
903}
904
905#[cfg(feature = "parser")]
906/// # Errors
907///
908/// Returns an error if `input` is not a valid CSS `flex-basis` value.
909pub fn parse_layout_flex_basis(input: &str) -> Result<LayoutFlexBasis, FlexBasisParseError<'_>> {
910    use crate::props::basic::pixel::parse_pixel_value;
911
912    match input.trim() {
913        "auto" => Ok(LayoutFlexBasis::Auto),
914        s => parse_pixel_value(s)
915            .map(LayoutFlexBasis::Exact)
916            .map_err(|_| FlexBasisParseError::InvalidValue(input)),
917    }
918}
919
920#[cfg(all(test, feature = "parser"))]
921mod tests {
922    // Tests assert that parsed values equal the exact source literals.
923    #![allow(clippy::float_cmp)]
924    use super::*;
925    use crate::props::basic::pixel::PixelValue;
926
927    #[test]
928    fn test_parse_layout_flex_grow() {
929        assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
930        assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
931        assert_eq!(parse_layout_flex_grow("2.5").unwrap().inner.get(), 2.5);
932        assert_eq!(parse_layout_flex_grow("  0.5  ").unwrap().inner.get(), 0.5);
933        assert!(parse_layout_flex_grow("none").is_err());
934        assert!(parse_layout_flex_grow("-1").is_err()); // Negative values are invalid
935    }
936
937    #[test]
938    fn test_parse_layout_flex_shrink() {
939        assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
940        assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
941        assert_eq!(parse_layout_flex_shrink("3.0").unwrap().inner.get(), 3.0);
942        assert_eq!(parse_layout_flex_shrink(" 0.2 ").unwrap().inner.get(), 0.2);
943        assert!(parse_layout_flex_shrink("auto").is_err());
944        assert!(parse_layout_flex_shrink("-1").is_err()); // Negative values are invalid
945    }
946
947    #[test]
948    fn test_parse_layout_flex_direction() {
949        assert_eq!(
950            parse_layout_flex_direction("row").unwrap(),
951            LayoutFlexDirection::Row
952        );
953        assert_eq!(
954            parse_layout_flex_direction("row-reverse").unwrap(),
955            LayoutFlexDirection::RowReverse
956        );
957        assert_eq!(
958            parse_layout_flex_direction("column").unwrap(),
959            LayoutFlexDirection::Column
960        );
961        assert_eq!(
962            parse_layout_flex_direction("column-reverse").unwrap(),
963            LayoutFlexDirection::ColumnReverse
964        );
965        assert_eq!(
966            parse_layout_flex_direction("  row  ").unwrap(),
967            LayoutFlexDirection::Row
968        );
969        assert!(parse_layout_flex_direction("reversed-row").is_err());
970    }
971
972    #[test]
973    fn test_parse_layout_flex_wrap() {
974        assert_eq!(
975            parse_layout_flex_wrap("nowrap").unwrap(),
976            LayoutFlexWrap::NoWrap
977        );
978        assert_eq!(
979            parse_layout_flex_wrap("wrap").unwrap(),
980            LayoutFlexWrap::Wrap
981        );
982        assert_eq!(
983            parse_layout_flex_wrap("wrap-reverse").unwrap(),
984            LayoutFlexWrap::WrapReverse
985        );
986        assert_eq!(
987            parse_layout_flex_wrap("  wrap  ").unwrap(),
988            LayoutFlexWrap::Wrap
989        );
990        assert!(parse_layout_flex_wrap("wrap reverse").is_err());
991    }
992
993    #[test]
994    fn test_parse_layout_justify_content() {
995        assert_eq!(
996            parse_layout_justify_content("flex-start").unwrap(),
997            LayoutJustifyContent::FlexStart
998        );
999        assert_eq!(
1000            parse_layout_justify_content("flex-end").unwrap(),
1001            LayoutJustifyContent::FlexEnd
1002        );
1003        assert_eq!(
1004            parse_layout_justify_content("start").unwrap(),
1005            LayoutJustifyContent::Start
1006        );
1007        assert_eq!(
1008            parse_layout_justify_content("end").unwrap(),
1009            LayoutJustifyContent::End
1010        );
1011        assert_eq!(
1012            parse_layout_justify_content("center").unwrap(),
1013            LayoutJustifyContent::Center
1014        );
1015        assert_eq!(
1016            parse_layout_justify_content("space-between").unwrap(),
1017            LayoutJustifyContent::SpaceBetween
1018        );
1019        assert_eq!(
1020            parse_layout_justify_content("space-around").unwrap(),
1021            LayoutJustifyContent::SpaceAround
1022        );
1023        assert_eq!(
1024            parse_layout_justify_content("space-evenly").unwrap(),
1025            LayoutJustifyContent::SpaceEvenly
1026        );
1027        assert_eq!(
1028            parse_layout_justify_content("  center  ").unwrap(),
1029            LayoutJustifyContent::Center
1030        );
1031    }
1032
1033    #[test]
1034    fn test_parse_layout_align_items() {
1035        assert_eq!(
1036            parse_layout_align_items("stretch").unwrap(),
1037            LayoutAlignItems::Stretch
1038        );
1039        assert_eq!(
1040            parse_layout_align_items("flex-start").unwrap(),
1041            LayoutAlignItems::Start
1042        );
1043        assert_eq!(
1044            parse_layout_align_items("flex-end").unwrap(),
1045            LayoutAlignItems::End
1046        );
1047        assert_eq!(
1048            parse_layout_align_items("start").unwrap(),
1049            LayoutAlignItems::Start
1050        );
1051        assert_eq!(
1052            parse_layout_align_items("end").unwrap(),
1053            LayoutAlignItems::End
1054        );
1055        assert_eq!(
1056            parse_layout_align_items("center").unwrap(),
1057            LayoutAlignItems::Center
1058        );
1059        assert_eq!(
1060            parse_layout_align_items("baseline").unwrap(),
1061            LayoutAlignItems::Baseline
1062        );
1063        assert!(parse_layout_align_items("invalid").is_err());
1064    }
1065
1066    #[test]
1067    fn test_parse_layout_align_content() {
1068        assert_eq!(
1069            parse_layout_align_content("stretch").unwrap(),
1070            LayoutAlignContent::Stretch
1071        );
1072        assert_eq!(
1073            parse_layout_align_content("flex-start").unwrap(),
1074            LayoutAlignContent::Start
1075        );
1076        assert_eq!(
1077            parse_layout_align_content("flex-end").unwrap(),
1078            LayoutAlignContent::End
1079        );
1080        assert_eq!(
1081            parse_layout_align_content("center").unwrap(),
1082            LayoutAlignContent::Center
1083        );
1084        assert_eq!(
1085            parse_layout_align_content("space-between").unwrap(),
1086            LayoutAlignContent::SpaceBetween
1087        );
1088        assert_eq!(
1089            parse_layout_align_content("space-around").unwrap(),
1090            LayoutAlignContent::SpaceAround
1091        );
1092        assert!(parse_layout_align_content("space-evenly").is_err()); // Not valid for align-content
1093    }
1094
1095    #[test]
1096    fn test_parse_layout_flex_basis() {
1097        assert_eq!(
1098            parse_layout_flex_basis("auto").unwrap(),
1099            LayoutFlexBasis::Auto
1100        );
1101        assert_eq!(
1102            parse_layout_flex_basis("200px").unwrap(),
1103            LayoutFlexBasis::Exact(PixelValue::px(200.0))
1104        );
1105        assert_eq!(
1106            parse_layout_flex_basis("50%").unwrap(),
1107            LayoutFlexBasis::Exact(PixelValue::percent(50.0))
1108        );
1109        assert_eq!(
1110            parse_layout_flex_basis("  10em  ").unwrap(),
1111            LayoutFlexBasis::Exact(PixelValue::em(10.0))
1112        );
1113        assert!(parse_layout_flex_basis("none").is_err());
1114        // Liberal parsing accepts unitless numbers (treated as px)
1115        assert_eq!(
1116            parse_layout_flex_basis("200").unwrap(),
1117            LayoutFlexBasis::Exact(PixelValue::px(200.0))
1118        );
1119        assert_eq!(
1120            parse_layout_flex_basis("0").unwrap(),
1121            LayoutFlexBasis::Exact(PixelValue::px(0.0))
1122        );
1123    }
1124}
1125
1126#[cfg(test)]
1127#[allow(clippy::float_cmp)] // fixed-point (1/1000) values are exactly representable in f32
1128mod autotest_generated {
1129    use super::*;
1130    use crate::props::basic::{length::FloatValue, pixel::PixelValue};
1131
1132    // ---------------------------------------------------------------------
1133    // LayoutFlexGrow::new / const_new  (constructor + numeric)
1134    // ---------------------------------------------------------------------
1135
1136    /// `new()` goes through f32 (`isize -> f32 -> *1000 -> isize`) while
1137    /// `const_new()` uses pure integer math (`value * 1000`). For magnitudes
1138    /// where both encodings are exact they must agree bit-for-bit.
1139    #[test]
1140    fn flex_grow_new_agrees_with_const_new_for_exact_ints() {
1141        for v in [-10_000_isize, -1000, -7, -1, 0, 1, 7, 1000, 10_000] {
1142            assert_eq!(
1143                LayoutFlexGrow::new(v).inner.number(),
1144                LayoutFlexGrow::const_new(v).inner.number(),
1145                "new()/const_new() disagree for {v}"
1146            );
1147            assert_eq!(LayoutFlexGrow::new(v).inner.get(), v as f32);
1148        }
1149    }
1150
1151    /// `new()` runs the value through `f32 as isize`, which saturates rather
1152    /// than wrapping or panicking: MIN/MAX must survive without UB or overflow.
1153    #[test]
1154    fn flex_grow_new_saturates_at_isize_extremes() {
1155        let max = LayoutFlexGrow::new(isize::MAX);
1156        let min = LayoutFlexGrow::new(isize::MIN);
1157
1158        assert_eq!(
1159            max.inner.number(),
1160            isize::MAX,
1161            "MAX must saturate, not wrap"
1162        );
1163        assert_eq!(
1164            min.inner.number(),
1165            isize::MIN,
1166            "MIN must saturate, not wrap"
1167        );
1168        assert!(max.inner.get().is_finite());
1169        assert!(min.inner.get().is_finite());
1170        assert!(max.inner.get() > 0.0);
1171        assert!(min.inner.get() < 0.0);
1172    }
1173
1174    /// `const_new()` stores `value * 1000` in an `isize`, so the largest safe
1175    /// input is `isize::MAX / 1000`. Anything above that overflows the
1176    /// multiplication (debug-panic / release-wrap) — this pins the documented
1177    /// safe boundary. See the report note on `const_new` overflow.
1178    #[test]
1179    fn flex_grow_const_new_at_safe_encoding_boundary() {
1180        let hi = isize::MAX / 1000;
1181        let lo = isize::MIN / 1000;
1182
1183        assert_eq!(LayoutFlexGrow::const_new(hi).inner.number(), hi * 1000);
1184        assert_eq!(LayoutFlexGrow::const_new(lo).inner.number(), lo * 1000);
1185        assert!(LayoutFlexGrow::const_new(hi).inner.get().is_finite());
1186        assert!(LayoutFlexGrow::const_new(lo).inner.get().is_finite());
1187    }
1188
1189    /// Zero / negative inputs are stored verbatim: the constructors perform no
1190    /// CSS validation (`flex-grow` may not be negative), only the parser does.
1191    #[test]
1192    fn flex_grow_const_new_zero_and_negative_are_not_clamped() {
1193        const ZERO: LayoutFlexGrow = LayoutFlexGrow::const_new(0);
1194        const NEG: LayoutFlexGrow = LayoutFlexGrow::const_new(-3);
1195
1196        assert_eq!(ZERO.inner.get(), 0.0);
1197        assert_eq!(ZERO.inner.number(), 0);
1198        assert_eq!(NEG.inner.get(), -3.0);
1199        assert_eq!(LayoutFlexGrow::new(-3).inner.get(), -3.0);
1200    }
1201
1202    #[test]
1203    fn flex_grow_and_shrink_defaults_match_css_initial_values() {
1204        assert_eq!(LayoutFlexGrow::default().inner.get(), 0.0);
1205        assert_eq!(LayoutFlexShrink::default().inner.get(), 1.0);
1206        // Ord is derived over the fixed-point isize, so it must track the float.
1207        assert!(LayoutFlexGrow::const_new(1) < LayoutFlexGrow::const_new(2));
1208        assert!(LayoutFlexGrow::const_new(-1) < LayoutFlexGrow::const_new(0));
1209    }
1210
1211    // ---------------------------------------------------------------------
1212    // interpolate()  (numeric: zero / limits / NaN / inf / overflow)
1213    // ---------------------------------------------------------------------
1214
1215    fn grow(v: f32) -> LayoutFlexGrow {
1216        LayoutFlexGrow {
1217            inner: FloatValue::new(v),
1218        }
1219    }
1220
1221    fn shrink(v: f32) -> LayoutFlexShrink {
1222        LayoutFlexShrink {
1223            inner: FloatValue::new(v),
1224        }
1225    }
1226
1227    #[test]
1228    fn flex_grow_interpolate_endpoints_midpoint_and_extrapolation() {
1229        let a = grow(0.0);
1230        let b = grow(10.0);
1231
1232        assert_eq!(a.interpolate(&b, 0.0).inner.get(), 0.0);
1233        assert_eq!(a.interpolate(&b, 1.0).inner.get(), 10.0);
1234        assert_eq!(a.interpolate(&b, 0.5).inner.get(), 5.0);
1235        // t is not clamped: extrapolation past the endpoints is well-defined.
1236        assert_eq!(a.interpolate(&b, 2.0).inner.get(), 20.0);
1237        assert_eq!(a.interpolate(&b, -1.0).inner.get(), -10.0);
1238    }
1239
1240    /// A NaN `t` produces NaN internally; the `f32 as isize` cast maps NaN to 0,
1241    /// so the result is a defined 0.0 rather than a NaN or a panic.
1242    #[test]
1243    fn flex_grow_interpolate_nan_t_collapses_to_zero() {
1244        let a = grow(2.0);
1245        let b = grow(8.0);
1246
1247        let out = a.interpolate(&b, f32::NAN);
1248        assert!(
1249            out.inner.get().is_finite(),
1250            "NaN must not leak into the value"
1251        );
1252        assert_eq!(out.inner.get(), 0.0);
1253        assert_eq!(out.inner.number(), 0);
1254    }
1255
1256    /// Infinite `t` overflows the lerp; the saturating cast must keep the result
1257    /// finite and correctly signed instead of panicking.
1258    #[test]
1259    fn flex_grow_interpolate_infinite_t_saturates_finite() {
1260        let a = grow(0.0);
1261        let b = grow(10.0);
1262
1263        let pos = a.interpolate(&b, f32::INFINITY);
1264        assert!(pos.inner.get().is_finite());
1265        assert!(pos.inner.get() > 0.0);
1266        assert_eq!(pos.inner.number(), isize::MAX);
1267
1268        let neg = a.interpolate(&b, f32::NEG_INFINITY);
1269        assert!(neg.inner.get().is_finite());
1270        assert!(neg.inner.get() < 0.0);
1271        assert_eq!(neg.inner.number(), isize::MIN);
1272
1273        // Degenerate case: equal endpoints => (b - a) * inf == NaN => 0.0.
1274        let same = grow(4.0).interpolate(&grow(4.0), f32::INFINITY);
1275        assert!(same.inner.get().is_finite());
1276        assert_eq!(same.inner.get(), 0.0);
1277    }
1278
1279    /// Interpolating between the saturated extremes must never panic and must
1280    /// always yield a finite, decodable value.
1281    #[test]
1282    fn flex_grow_interpolate_extreme_endpoints_stay_finite() {
1283        let a = LayoutFlexGrow::new(isize::MAX);
1284        let b = LayoutFlexGrow::new(isize::MIN);
1285
1286        for t in [
1287            0.0_f32,
1288            0.5,
1289            1.0,
1290            -1.0,
1291            1e30,
1292            -1e30,
1293            f32::MIN_POSITIVE,
1294            f32::NAN,
1295            f32::INFINITY,
1296            f32::NEG_INFINITY,
1297        ] {
1298            let out = a.interpolate(&b, t);
1299            assert!(
1300                out.inner.get().is_finite(),
1301                "interpolate(MAX, MIN, {t}) produced a non-finite value"
1302            );
1303        }
1304
1305        // t = 0 must return `self` even at the saturation boundary.
1306        assert_eq!(a.interpolate(&b, 0.0).inner.get(), a.inner.get());
1307    }
1308
1309    #[test]
1310    fn flex_shrink_interpolate_endpoints_nan_and_inf() {
1311        let a = shrink(1.0);
1312        let b = shrink(3.0);
1313
1314        assert_eq!(a.interpolate(&b, 0.0).inner.get(), 1.0);
1315        assert_eq!(a.interpolate(&b, 1.0).inner.get(), 3.0);
1316        assert_eq!(a.interpolate(&b, 0.5).inner.get(), 2.0);
1317        assert_eq!(a.interpolate(&b, -2.0).inner.get(), -3.0);
1318
1319        assert_eq!(a.interpolate(&b, f32::NAN).inner.get(), 0.0);
1320        assert!(a.interpolate(&b, f32::INFINITY).inner.get().is_finite());
1321        assert!(a.interpolate(&b, f32::NEG_INFINITY).inner.get().is_finite());
1322
1323        let extreme = LayoutFlexShrink {
1324            inner: FloatValue::new(f32::MAX),
1325        };
1326        assert!(extreme.interpolate(&a, 0.5).inner.get().is_finite());
1327        assert!(extreme.interpolate(&a, f32::NAN).inner.get().is_finite());
1328    }
1329
1330    // ---------------------------------------------------------------------
1331    // LayoutFlexDirection::get_axis / is_reverse  (getter + predicate)
1332    // ---------------------------------------------------------------------
1333
1334    const ALL_DIRECTIONS: [LayoutFlexDirection; 4] = [
1335        LayoutFlexDirection::Row,
1336        LayoutFlexDirection::RowReverse,
1337        LayoutFlexDirection::Column,
1338        LayoutFlexDirection::ColumnReverse,
1339    ];
1340
1341    #[test]
1342    fn flex_direction_get_axis_is_exhaustive_and_reverse_invariant() {
1343        assert_eq!(LayoutFlexDirection::Row.get_axis(), LayoutAxis::Horizontal);
1344        assert_eq!(
1345            LayoutFlexDirection::RowReverse.get_axis(),
1346            LayoutAxis::Horizontal
1347        );
1348        assert_eq!(LayoutFlexDirection::Column.get_axis(), LayoutAxis::Vertical);
1349        assert_eq!(
1350            LayoutFlexDirection::ColumnReverse.get_axis(),
1351            LayoutAxis::Vertical
1352        );
1353
1354        // Invariant: reversing a direction never changes its axis.
1355        assert_eq!(
1356            LayoutFlexDirection::Row.get_axis(),
1357            LayoutFlexDirection::RowReverse.get_axis()
1358        );
1359        assert_eq!(
1360            LayoutFlexDirection::Column.get_axis(),
1361            LayoutFlexDirection::ColumnReverse.get_axis()
1362        );
1363        // Default (`row`) must be the horizontal, non-reversed axis.
1364        assert_eq!(
1365            LayoutFlexDirection::default().get_axis(),
1366            LayoutAxis::Horizontal
1367        );
1368        assert!(!LayoutFlexDirection::default().is_reverse());
1369    }
1370
1371    #[test]
1372    fn flex_direction_is_reverse_exhaustive() {
1373        assert!(!LayoutFlexDirection::Row.is_reverse());
1374        assert!(LayoutFlexDirection::RowReverse.is_reverse());
1375        assert!(!LayoutFlexDirection::Column.is_reverse());
1376        assert!(LayoutFlexDirection::ColumnReverse.is_reverse());
1377
1378        // Every variant answers deterministically, and exactly half are reverse.
1379        let reversed = ALL_DIRECTIONS.iter().filter(|d| d.is_reverse()).count();
1380        assert_eq!(reversed, 2);
1381        // get_axis()/is_reverse() are orthogonal: both axes have a reverse form.
1382        for axis in [LayoutAxis::Horizontal, LayoutAxis::Vertical] {
1383            assert_eq!(
1384                ALL_DIRECTIONS
1385                    .iter()
1386                    .filter(|d| d.get_axis() == axis && d.is_reverse())
1387                    .count(),
1388                1
1389            );
1390        }
1391    }
1392
1393    // =====================================================================
1394    // Parser-gated tests
1395    // =====================================================================
1396
1397    // --- flex-grow / flex-shrink (numeric parsers) -----------------------
1398
1399    #[cfg(feature = "parser")]
1400    #[test]
1401    fn flex_grow_parse_empty_and_whitespace_only_are_err() {
1402        for input in ["", " ", "   ", "\t", "\n", "\r\n", "\t \n "] {
1403            assert!(
1404                parse_layout_flex_grow(input).is_err(),
1405                "empty/whitespace input {input:?} must not parse"
1406            );
1407            assert!(parse_layout_flex_shrink(input).is_err());
1408        }
1409    }
1410
1411    #[cfg(feature = "parser")]
1412    #[test]
1413    fn flex_grow_parse_garbage_and_unicode_never_panics() {
1414        let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
1415        let long_junk = "x".repeat(1_000_000);
1416        let long_digits = "9".repeat(100_000);
1417
1418        let garbage = [
1419            "none",
1420            "auto",
1421            "null",
1422            "1/2",
1423            "0x10",
1424            "1,0",
1425            "--1",
1426            "1-",
1427            "+-1",
1428            "1e",
1429            "e1",
1430            "\u{1F600}",        // emoji
1431            "1\u{1F600}",       // digit + emoji
1432            "e\u{0301}",        // combining acute accent
1433            "\u{0661}\u{0662}", // arabic-indic digits
1434            "1",               // fullwidth digit
1435            "1\u{0}",           // embedded NUL
1436            "\u{200B}1",        // zero-width space
1437            deep_nesting.as_str(),
1438            long_junk.as_str(),
1439        ];
1440
1441        for input in garbage {
1442            assert!(
1443                parse_layout_flex_grow(input).is_err(),
1444                "garbage input {:?} must be rejected",
1445                input.chars().take(8).collect::<String>()
1446            );
1447            assert!(parse_layout_flex_shrink(input).is_err());
1448        }
1449
1450        // 100k digits overflows f32 to +inf; it must saturate, not hang or panic.
1451        let huge = parse_layout_flex_grow(&long_digits).expect("huge finite-overflow input");
1452        assert!(huge.inner.get().is_finite());
1453        assert!(huge.inner.get() >= 0.0);
1454    }
1455
1456    #[cfg(feature = "parser")]
1457    #[test]
1458    fn flex_grow_parse_boundary_numbers() {
1459        // Positive controls.
1460        assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
1461        assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
1462        assert_eq!(parse_layout_flex_grow("+2.5").unwrap().inner.get(), 2.5);
1463        assert_eq!(parse_layout_flex_grow(".5").unwrap().inner.get(), 0.5);
1464
1465        // Signed zero is accepted and normalised to +0.
1466        let neg_zero = parse_layout_flex_grow("-0").unwrap();
1467        assert_eq!(neg_zero.inner.get(), 0.0);
1468        assert_eq!(neg_zero.inner.number(), 0);
1469
1470        // Genuinely negative values are rejected.
1471        assert!(matches!(
1472            parse_layout_flex_grow("-1"),
1473            Err(FlexGrowParseError::NegativeValue("-1"))
1474        ));
1475        assert!(parse_layout_flex_grow("-0.01").is_err());
1476        assert!(parse_layout_flex_grow("-1e10").is_err());
1477
1478        // -inf underflows the fixed point to isize::MIN => still caught as negative.
1479        assert!(matches!(
1480            parse_layout_flex_grow("-inf"),
1481            Err(FlexGrowParseError::NegativeValue(_))
1482        ));
1483
1484        // Sub-quantum negatives (|v| < 0.001) truncate to 0 and are ACCEPTED —
1485        // the negative check runs after the fixed-point quantisation.
1486        let tiny_neg = parse_layout_flex_grow("-0.0001").unwrap();
1487        assert_eq!(tiny_neg.inner.get(), 0.0);
1488        assert_eq!(parse_layout_flex_grow("-1e-30").unwrap().inner.get(), 0.0);
1489
1490        // Values beyond f32 range become +inf, then saturate to a finite maximum.
1491        for input in ["inf", "1e40", "9223372036854775807", "3.5e38"] {
1492            let parsed = parse_layout_flex_grow(input)
1493                .unwrap_or_else(|e| panic!("{input:?} unexpectedly rejected: {e}"));
1494            assert!(
1495                parsed.inner.get().is_finite(),
1496                "{input:?} decoded to a non-finite value"
1497            );
1498            assert!(parsed.inner.get() >= 0.0);
1499        }
1500
1501        // Denormal-scale positives quantise to 0 rather than erroring.
1502        assert_eq!(parse_layout_flex_grow("1e-40").unwrap().inner.get(), 0.0);
1503
1504        // "NaN" is a valid Rust float literal, so it reaches the fixed-point
1505        // cast, which maps NaN -> 0. It is accepted as flex-grow: 0.
1506        let nan = parse_layout_flex_grow("NaN").unwrap();
1507        assert!(nan.inner.get().is_finite());
1508        assert_eq!(nan.inner.get(), 0.0);
1509    }
1510
1511    #[cfg(feature = "parser")]
1512    #[test]
1513    fn flex_shrink_parse_boundary_numbers() {
1514        assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
1515        assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
1516        assert_eq!(parse_layout_flex_shrink("-0").unwrap().inner.get(), 0.0);
1517
1518        assert!(matches!(
1519            parse_layout_flex_shrink("-1"),
1520            Err(FlexShrinkParseError::NegativeValue("-1"))
1521        ));
1522        assert!(parse_layout_flex_shrink("-inf").is_err());
1523
1524        let huge = parse_layout_flex_shrink("1e40").unwrap();
1525        assert!(huge.inner.get().is_finite() && huge.inner.get() > 0.0);
1526        assert_eq!(parse_layout_flex_shrink("NaN").unwrap().inner.get(), 0.0);
1527    }
1528
1529    #[cfg(feature = "parser")]
1530    #[test]
1531    fn flex_grow_parse_leading_trailing_junk() {
1532        // Surrounding whitespace is trimmed.
1533        assert_eq!(parse_layout_flex_grow("  0.5  ").unwrap().inner.get(), 0.5);
1534        assert_eq!(parse_layout_flex_grow("\t2\n").unwrap().inner.get(), 2.0);
1535
1536        // Trailing junk / units / extra tokens are rejected.
1537        for input in ["1;", "1 2", "1px", "1%", "valid;garbage", "1 1 1"] {
1538            assert!(
1539                parse_layout_flex_grow(input).is_err(),
1540                "{input:?} must be rejected"
1541            );
1542        }
1543    }
1544
1545    /// The error must carry the *original* (untrimmed) input, not the trimmed
1546    /// slice — callers rely on it to point back into the source CSS.
1547    #[cfg(feature = "parser")]
1548    #[test]
1549    fn flex_grow_error_preserves_untrimmed_input() {
1550        match parse_layout_flex_grow("  bogus  ") {
1551            Err(FlexGrowParseError::ParseFloat(_, s)) => assert_eq!(s, "  bogus  "),
1552            other => panic!("expected ParseFloat error, got {other:?}"),
1553        }
1554        match parse_layout_flex_shrink(" -2 ") {
1555            Err(FlexShrinkParseError::NegativeValue(s)) => assert_eq!(s, " -2 "),
1556            other => panic!("expected NegativeValue error, got {other:?}"),
1557        }
1558    }
1559
1560    /// Round-trip: print -> parse must reproduce the value for anything that is
1561    /// exactly representable in the 1/1000 fixed point.
1562    #[cfg(feature = "parser")]
1563    #[test]
1564    fn flex_grow_shrink_print_parse_round_trip() {
1565        for v in [0.0_f32, 1.0, 2.5, 0.25, 0.125, 100.0, 12.5] {
1566            let g = grow(v);
1567            let printed = g.print_as_css_value();
1568            assert_eq!(
1569                parse_layout_flex_grow(&printed).unwrap().inner.number(),
1570                g.inner.number(),
1571                "flex-grow round-trip failed for {printed}"
1572            );
1573
1574            let s = shrink(v);
1575            let printed = s.print_as_css_value();
1576            assert_eq!(
1577                parse_layout_flex_shrink(&printed).unwrap().inner.number(),
1578                s.inner.number(),
1579                "flex-shrink round-trip failed for {printed}"
1580            );
1581        }
1582    }
1583
1584    // --- keyword parsers -------------------------------------------------
1585
1586    /// Every enum variant must survive `print_as_css_value() -> parse()`.
1587    #[cfg(feature = "parser")]
1588    #[test]
1589    fn keyword_enums_print_parse_round_trip() {
1590        for d in ALL_DIRECTIONS {
1591            assert_eq!(
1592                parse_layout_flex_direction(&d.print_as_css_value()).unwrap(),
1593                d
1594            );
1595        }
1596        for w in [
1597            LayoutFlexWrap::Wrap,
1598            LayoutFlexWrap::NoWrap,
1599            LayoutFlexWrap::WrapReverse,
1600        ] {
1601            assert_eq!(parse_layout_flex_wrap(&w.print_as_css_value()).unwrap(), w);
1602        }
1603        for j in [
1604            LayoutJustifyContent::FlexStart,
1605            LayoutJustifyContent::FlexEnd,
1606            LayoutJustifyContent::Start,
1607            LayoutJustifyContent::End,
1608            LayoutJustifyContent::Center,
1609            LayoutJustifyContent::SpaceBetween,
1610            LayoutJustifyContent::SpaceAround,
1611            LayoutJustifyContent::SpaceEvenly,
1612        ] {
1613            assert_eq!(
1614                parse_layout_justify_content(&j.print_as_css_value()).unwrap(),
1615                j
1616            );
1617        }
1618        for a in [
1619            LayoutAlignItems::Stretch,
1620            LayoutAlignItems::Center,
1621            LayoutAlignItems::Start,
1622            LayoutAlignItems::End,
1623            LayoutAlignItems::Baseline,
1624        ] {
1625            assert_eq!(
1626                parse_layout_align_items(&a.print_as_css_value()).unwrap(),
1627                a
1628            );
1629        }
1630        for a in [
1631            LayoutAlignContent::Stretch,
1632            LayoutAlignContent::Center,
1633            LayoutAlignContent::Start,
1634            LayoutAlignContent::End,
1635            LayoutAlignContent::SpaceBetween,
1636            LayoutAlignContent::SpaceAround,
1637        ] {
1638            assert_eq!(
1639                parse_layout_align_content(&a.print_as_css_value()).unwrap(),
1640                a
1641            );
1642        }
1643        for a in [
1644            LayoutAlignSelf::Auto,
1645            LayoutAlignSelf::Stretch,
1646            LayoutAlignSelf::Center,
1647            LayoutAlignSelf::Start,
1648            LayoutAlignSelf::End,
1649            LayoutAlignSelf::Baseline,
1650        ] {
1651            assert_eq!(parse_layout_align_self(&a.print_as_css_value()).unwrap(), a);
1652        }
1653    }
1654
1655    #[cfg(feature = "parser")]
1656    #[test]
1657    fn keyword_parsers_reject_empty_whitespace_and_garbage() {
1658        let deep_nesting = "[".repeat(10_000) + &"]".repeat(10_000);
1659        let long_junk = "row".repeat(300_000); // ~900k chars, no hang
1660        let bad = [
1661            "",
1662            " ",
1663            "\t\n",
1664            "0",
1665            "-1",
1666            "NaN",
1667            "inf",
1668            "9223372036854775807",
1669            "\u{1F600}",
1670            "row\u{200B}", // zero-width space is NOT css whitespace
1671            "row\u{0}",    // embedded NUL
1672            "row row",
1673            "row;",
1674            ";row",
1675            "row/**/",
1676            deep_nesting.as_str(),
1677            long_junk.as_str(),
1678        ];
1679
1680        for input in bad {
1681            assert!(parse_layout_flex_direction(input).is_err());
1682            assert!(parse_layout_flex_wrap(input).is_err());
1683            assert!(parse_layout_justify_content(input).is_err());
1684            assert!(parse_layout_align_items(input).is_err());
1685            assert!(parse_layout_align_content(input).is_err());
1686            assert!(parse_layout_align_self(input).is_err());
1687        }
1688    }
1689
1690    /// CSS keywords are ASCII case-insensitive, but these parsers match
1691    /// case-sensitively. Pinned as current behaviour (see report).
1692    #[cfg(feature = "parser")]
1693    #[test]
1694    fn keyword_parsers_are_case_sensitive() {
1695        assert!(parse_layout_flex_direction("ROW").is_err());
1696        assert!(parse_layout_flex_direction("Row").is_err());
1697        assert!(parse_layout_flex_wrap("NoWrap").is_err());
1698        assert!(parse_layout_justify_content("Center").is_err());
1699        assert!(parse_layout_align_items("STRETCH").is_err());
1700        assert!(parse_layout_align_content("Stretch").is_err());
1701        assert!(parse_layout_align_self("AUTO").is_err());
1702
1703        // lowercase positive controls still work
1704        assert_eq!(
1705            parse_layout_flex_direction("row").unwrap(),
1706            LayoutFlexDirection::Row
1707        );
1708        assert_eq!(
1709            parse_layout_align_self("auto").unwrap(),
1710            LayoutAlignSelf::Auto
1711        );
1712    }
1713
1714    /// Keyword errors must echo the original, untrimmed input.
1715    #[cfg(feature = "parser")]
1716    #[test]
1717    fn keyword_errors_preserve_untrimmed_input() {
1718        assert_eq!(
1719            parse_layout_flex_direction("  bogus  ").unwrap_err(),
1720            FlexDirectionParseError::InvalidValue("  bogus  ")
1721        );
1722        assert_eq!(
1723            parse_layout_flex_wrap("\twrap!\n").unwrap_err(),
1724            FlexWrapParseError::InvalidValue("\twrap!\n")
1725        );
1726        assert_eq!(
1727            parse_layout_justify_content("").unwrap_err(),
1728            JustifyContentParseError::InvalidValue("")
1729        );
1730        assert_eq!(
1731            parse_layout_align_items(" nope ").unwrap_err(),
1732            AlignItemsParseError::InvalidValue(" nope ")
1733        );
1734        assert_eq!(
1735            parse_layout_align_content(" nope ").unwrap_err(),
1736            AlignContentParseError::InvalidValue(" nope ")
1737        );
1738        assert_eq!(
1739            parse_layout_align_self(" nope ").unwrap_err(),
1740            AlignSelfParseError::InvalidValue(" nope ")
1741        );
1742    }
1743
1744    /// Aliases: `start`/`flex-start` and `end`/`flex-end` collapse to the same
1745    /// variant for align-*, while justify-content keeps them distinct.
1746    #[cfg(feature = "parser")]
1747    #[test]
1748    fn align_aliases_collapse_but_justify_keeps_them_distinct() {
1749        assert_eq!(
1750            parse_layout_align_items("start").unwrap(),
1751            parse_layout_align_items("flex-start").unwrap()
1752        );
1753        assert_eq!(
1754            parse_layout_align_content("end").unwrap(),
1755            parse_layout_align_content("flex-end").unwrap()
1756        );
1757        assert_eq!(
1758            parse_layout_align_self("start").unwrap(),
1759            parse_layout_align_self("flex-start").unwrap()
1760        );
1761        assert_ne!(
1762            parse_layout_justify_content("start").unwrap(),
1763            parse_layout_justify_content("flex-start").unwrap()
1764        );
1765        // space-evenly exists for justify-content but not for align-content.
1766        assert!(parse_layout_justify_content("space-evenly").is_ok());
1767        assert!(parse_layout_align_content("space-evenly").is_err());
1768        // align-self has `auto`; align-items does not.
1769        assert!(parse_layout_align_self("auto").is_ok());
1770        assert!(parse_layout_align_items("auto").is_err());
1771    }
1772
1773    // --- flex-basis ------------------------------------------------------
1774
1775    #[cfg(feature = "parser")]
1776    #[test]
1777    fn flex_basis_print_parse_round_trip() {
1778        for basis in [
1779            LayoutFlexBasis::Auto,
1780            LayoutFlexBasis::Exact(PixelValue::px(0.0)),
1781            LayoutFlexBasis::Exact(PixelValue::px(200.0)),
1782            LayoutFlexBasis::Exact(PixelValue::px(-5.0)),
1783            LayoutFlexBasis::Exact(PixelValue::percent(50.0)),
1784            LayoutFlexBasis::Exact(PixelValue::em(10.5)),
1785            LayoutFlexBasis::Exact(PixelValue::rem(1.25)),
1786            LayoutFlexBasis::Exact(PixelValue::pt(12.0)),
1787        ] {
1788            let printed = basis.print_as_css_value();
1789            assert_eq!(
1790                parse_layout_flex_basis(&printed).unwrap(),
1791                basis,
1792                "flex-basis round-trip failed for {printed}"
1793            );
1794        }
1795    }
1796
1797    #[cfg(feature = "parser")]
1798    #[test]
1799    fn flex_basis_rejects_empty_units_only_and_garbage() {
1800        let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
1801        let long_junk = "z".repeat(1_000_000);
1802
1803        for input in [
1804            "",
1805            "   ",
1806            "\t\n",
1807            "px", // unit with no value
1808            "%",
1809            "em",
1810            " px ",
1811            "none",
1812            "auto auto",
1813            "200px;",
1814            "200 px extra",
1815            "AUTO", // case-sensitive
1816            "5PX",
1817            "\u{1F600}",
1818            "50px", // fullwidth digits
1819            "200\u{200B}px",
1820            deep_nesting.as_str(),
1821            long_junk.as_str(),
1822        ] {
1823            assert!(
1824                parse_layout_flex_basis(input).is_err(),
1825                "flex-basis {:?} must be rejected",
1826                input.chars().take(10).collect::<String>()
1827            );
1828        }
1829
1830        // The error echoes the original, untrimmed input.
1831        assert_eq!(
1832            parse_layout_flex_basis("  none  ").unwrap_err(),
1833            FlexBasisParseError::InvalidValue("  none  ")
1834        );
1835    }
1836
1837    /// Adversarial numeric flex-basis inputs: NaN/inf reach the fixed-point cast
1838    /// through the unit suffix and must saturate to a finite, defined value.
1839    #[cfg(feature = "parser")]
1840    #[test]
1841    fn flex_basis_nan_and_inf_units_saturate() {
1842        // "NaN" is a valid float literal => NaNpx decodes to 0px, not an error.
1843        assert_eq!(
1844            parse_layout_flex_basis("NaNpx").unwrap(),
1845            LayoutFlexBasis::Exact(PixelValue::px(0.0))
1846        );
1847        // Overflowing magnitudes saturate rather than panicking.
1848        assert_eq!(
1849            parse_layout_flex_basis("infpx").unwrap(),
1850            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1851        );
1852        assert_eq!(
1853            parse_layout_flex_basis("1e40px").unwrap(),
1854            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1855        );
1856        assert_eq!(
1857            parse_layout_flex_basis(&"9".repeat(100_000)).unwrap(),
1858            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1859        );
1860
1861        // Unitless numbers are accepted and treated as px (liberal parsing).
1862        assert_eq!(
1863            parse_layout_flex_basis("-0").unwrap(),
1864            LayoutFlexBasis::Exact(PixelValue::px(0.0))
1865        );
1866        // Negative lengths are accepted even though CSS forbids them (see report).
1867        assert_eq!(
1868            parse_layout_flex_basis("-5px").unwrap(),
1869            LayoutFlexBasis::Exact(PixelValue::px(-5.0))
1870        );
1871        // Whitespace *inside* the token is tolerated (see report).
1872        assert_eq!(
1873            parse_layout_flex_basis("5 px").unwrap(),
1874            LayoutFlexBasis::Exact(PixelValue::px(5.0))
1875        );
1876    }
1877
1878    // ---------------------------------------------------------------------
1879    // Error to_contained() / to_shared()  (getters, borrow <-> owned)
1880    // ---------------------------------------------------------------------
1881
1882    /// `to_contained()` then `to_shared()` must be the identity for every error
1883    /// variant, including empty / unicode / long payloads.
1884    #[cfg(feature = "parser")]
1885    #[test]
1886    fn flex_grow_shrink_error_owned_round_trip() {
1887        let invalid = "x".parse::<f32>().unwrap_err();
1888        let empty = "".parse::<f32>().unwrap_err();
1889        let long = "q".repeat(10_000);
1890
1891        for payload in ["", "abc", "\u{1F600}\u{0301}", "  spaced  ", long.as_str()] {
1892            for err in [
1893                FlexGrowParseError::ParseFloat(invalid.clone(), payload),
1894                FlexGrowParseError::ParseFloat(empty.clone(), payload),
1895                FlexGrowParseError::NegativeValue(payload),
1896            ] {
1897                assert_eq!(err.to_contained().to_shared(), err);
1898            }
1899            for err in [
1900                FlexShrinkParseError::ParseFloat(invalid.clone(), payload),
1901                FlexShrinkParseError::NegativeValue(payload),
1902            ] {
1903                assert_eq!(err.to_contained().to_shared(), err);
1904            }
1905        }
1906    }
1907
1908    #[cfg(feature = "parser")]
1909    #[test]
1910    fn keyword_error_owned_round_trip() {
1911        let long = "k".repeat(10_000);
1912
1913        for payload in ["", " ", "\u{1F600}", "bogus", long.as_str()] {
1914            let d = FlexDirectionParseError::InvalidValue(payload);
1915            assert_eq!(d.to_contained().to_shared(), d);
1916
1917            let w = FlexWrapParseError::InvalidValue(payload);
1918            assert_eq!(w.to_contained().to_shared(), w);
1919
1920            let j = JustifyContentParseError::InvalidValue(payload);
1921            assert_eq!(j.to_contained().to_shared(), j);
1922
1923            let ai = AlignItemsParseError::InvalidValue(payload);
1924            assert_eq!(ai.to_contained().to_shared(), ai);
1925
1926            let ac = AlignContentParseError::InvalidValue(payload);
1927            assert_eq!(ac.to_contained().to_shared(), ac);
1928
1929            let asf = AlignSelfParseError::InvalidValue(payload);
1930            assert_eq!(asf.to_contained().to_shared(), asf);
1931
1932            let b = FlexBasisParseError::InvalidValue(payload);
1933            assert_eq!(b.to_contained().to_shared(), b);
1934        }
1935    }
1936
1937    /// `to_shared()` must not panic on a directly-constructed owned error with a
1938    /// degenerate (empty) payload, and must hand back the exact same string.
1939    #[cfg(feature = "parser")]
1940    #[test]
1941    fn owned_errors_to_shared_on_degenerate_payloads() {
1942        let empty: AzString = String::new().into();
1943
1944        assert_eq!(
1945            FlexDirectionParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1946            FlexDirectionParseError::InvalidValue("")
1947        );
1948        assert_eq!(
1949            FlexWrapParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1950            FlexWrapParseError::InvalidValue("")
1951        );
1952        assert_eq!(
1953            JustifyContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1954            JustifyContentParseError::InvalidValue("")
1955        );
1956        assert_eq!(
1957            AlignItemsParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1958            AlignItemsParseError::InvalidValue("")
1959        );
1960        assert_eq!(
1961            AlignContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1962            AlignContentParseError::InvalidValue("")
1963        );
1964        assert_eq!(
1965            AlignSelfParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1966            AlignSelfParseError::InvalidValue("")
1967        );
1968        assert_eq!(
1969            FlexBasisParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1970            FlexBasisParseError::InvalidValue("")
1971        );
1972        assert_eq!(
1973            FlexGrowParseErrorOwned::NegativeValue(empty.clone()).to_shared(),
1974            FlexGrowParseError::NegativeValue("")
1975        );
1976        assert_eq!(
1977            FlexShrinkParseErrorOwned::NegativeValue(empty).to_shared(),
1978            FlexShrinkParseError::NegativeValue("")
1979        );
1980    }
1981
1982    /// Errors surfaced by the real parsers must convert to owned form and
1983    /// render a non-empty message that names the offending property.
1984    #[cfg(feature = "parser")]
1985    #[test]
1986    fn parser_errors_to_contained_and_display() {
1987        let e = parse_layout_flex_grow("bogus").unwrap_err();
1988        assert_eq!(e.to_contained().to_shared(), e);
1989        assert!(format!("{e}").contains("flex-grow"));
1990
1991        let e = parse_layout_flex_shrink("-1").unwrap_err();
1992        assert_eq!(e.to_contained().to_shared(), e);
1993        assert!(format!("{e}").contains("flex-shrink"));
1994
1995        let e = parse_layout_flex_direction("\u{1F600}").unwrap_err();
1996        assert_eq!(e.to_contained().to_shared(), e);
1997        assert!(format!("{e}").contains("flex-direction"));
1998
1999        let e = parse_layout_flex_wrap("").unwrap_err();
2000        assert_eq!(e.to_contained().to_shared(), e);
2001        assert!(format!("{e}").contains("flex-wrap"));
2002
2003        let e = parse_layout_justify_content("nope").unwrap_err();
2004        assert_eq!(e.to_contained().to_shared(), e);
2005        assert!(format!("{e}").contains("justify-content"));
2006
2007        let e = parse_layout_align_items("nope").unwrap_err();
2008        assert_eq!(e.to_contained().to_shared(), e);
2009        assert!(format!("{e}").contains("align-items"));
2010
2011        let e = parse_layout_align_content("nope").unwrap_err();
2012        assert_eq!(e.to_contained().to_shared(), e);
2013        assert!(format!("{e}").contains("align-content"));
2014
2015        let e = parse_layout_align_self("nope").unwrap_err();
2016        assert_eq!(e.to_contained().to_shared(), e);
2017        assert!(format!("{e}").contains("align-self"));
2018
2019        let e = parse_layout_flex_basis("none").unwrap_err();
2020        assert_eq!(e.to_contained().to_shared(), e);
2021        assert!(format!("{e}").contains("flex-basis"));
2022    }
2023}