Skip to main content

azul_css/props/layout/
flex.rs

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