1use alloc::{
4 boxed::Box,
5 string::{String, ToString},
6 vec::Vec,
7};
8
9use crate::{
10 corety::AzString,
11 codegen::format::FormatAsRustCode,
12 impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_eq, impl_vec_hash, impl_vec_mut,
13 impl_vec_ord, impl_vec_partialeq, impl_vec_partialord,
14 props::{basic::pixel::PixelValue, formatter::PrintAsCssValue},
15};
16
17#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[repr(C)]
22pub struct GridMinMax {
23 pub min: Box<GridTrackSizing>,
24 pub max: Box<GridTrackSizing>,
25}
26
27impl core::fmt::Debug for GridMinMax {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 write!(
30 f,
31 "minmax({}, {})",
32 self.min.print_as_css_value(),
33 self.max.print_as_css_value()
34 )
35 }
36}
37
38#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C, u8)]
41#[derive(Default)]
42pub enum GridTrackSizing {
43 Fixed(PixelValue),
45 Fr(i32),
48 MinContent,
50 MaxContent,
52 #[default]
54 Auto,
55 MinMax(GridMinMax),
57 FitContent(PixelValue),
59}
60
61impl_option!(
62 GridTrackSizing,
63 OptionGridTrackSizing,
64 copy = false,
65 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
66);
67
68impl core::fmt::Debug for GridTrackSizing {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 write!(f, "{}", self.print_as_css_value())
71 }
72}
73
74
75impl PrintAsCssValue for GridTrackSizing {
76 fn print_as_css_value(&self) -> String {
77 match self {
78 Self::Fixed(px) => px.print_as_css_value(),
79 Self::Fr(f) => format!("{f}fr"),
80 Self::MinContent => "min-content".to_string(),
81 Self::MaxContent => "max-content".to_string(),
82 Self::Auto => "auto".to_string(),
83 Self::MinMax(minmax) => {
84 format!(
85 "minmax({}, {})",
86 minmax.min.print_as_css_value(),
87 minmax.max.print_as_css_value()
88 )
89 }
90 Self::FitContent(size) => {
91 format!("fit-content({})", size.print_as_css_value())
92 }
93 }
94 }
95}
96
97impl_vec!(GridTrackSizing, GridTrackSizingVec, GridTrackSizingVecDestructor, GridTrackSizingVecDestructorType, GridTrackSizingVecSlice, OptionGridTrackSizing);
99impl_vec_clone!(
100 GridTrackSizing,
101 GridTrackSizingVec,
102 GridTrackSizingVecDestructor
103);
104impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
105impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
106impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
107impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
108impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
109impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
110impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
111
112#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[repr(C)]
115pub struct GridTemplate {
116 pub tracks: GridTrackSizingVec,
117}
118
119impl core::fmt::Debug for GridTemplate {
120 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121 write!(f, "{}", self.print_as_css_value())
122 }
123}
124
125impl Default for GridTemplate {
126 fn default() -> Self {
127 Self {
128 tracks: GridTrackSizingVec::from_vec(Vec::new()),
129 }
130 }
131}
132
133impl PrintAsCssValue for GridTemplate {
134 fn print_as_css_value(&self) -> String {
135 let tracks_slice = self.tracks.as_ref();
136 if tracks_slice.is_empty() {
137 "none".to_string()
138 } else {
139 tracks_slice
140 .iter()
141 .map(PrintAsCssValue::print_as_css_value)
142 .collect::<Vec<_>>()
143 .join(" ")
144 }
145 }
146}
147
148#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
153#[repr(C)]
154pub struct GridAutoTracks {
155 pub tracks: GridTrackSizingVec,
156}
157
158impl core::fmt::Debug for GridAutoTracks {
159 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160 write!(f, "{}", self.print_as_css_value())
161 }
162}
163
164impl Default for GridAutoTracks {
165 fn default() -> Self {
166 Self {
167 tracks: GridTrackSizingVec::from_vec(Vec::new()),
168 }
169 }
170}
171
172impl PrintAsCssValue for GridAutoTracks {
173 fn print_as_css_value(&self) -> String {
174 let tracks_slice = self.tracks.as_ref();
175 if tracks_slice.is_empty() {
176 "auto".to_string()
177 } else {
178 tracks_slice
179 .iter()
180 .map(PrintAsCssValue::print_as_css_value)
181 .collect::<Vec<_>>()
182 .join(" ")
183 }
184 }
185}
186
187impl From<GridTemplate> for GridAutoTracks {
188 fn from(template: GridTemplate) -> Self {
189 Self {
190 tracks: template.tracks,
191 }
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
199#[repr(C)]
200pub struct NamedGridLine {
201 pub grid_line_name: AzString,
202 pub span_count: i32,
204}
205
206impl NamedGridLine {
207 #[must_use] pub fn create(name: AzString, span: Option<i32>) -> Self {
208 Self {
209 grid_line_name: name,
210 span_count: span.unwrap_or(0),
211 }
212 }
213
214 #[must_use] pub const fn span(&self) -> Option<i32> {
215 if self.span_count == 0 {
216 None
217 } else {
218 Some(self.span_count)
219 }
220 }
221}
222#[allow(variant_size_differences)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
225#[repr(C, u8)]
226#[derive(Default)]
227pub enum GridLine {
228 #[default]
230 Auto,
231 Line(i32),
233 Named(NamedGridLine),
235 Span(i32),
237}
238
239impl core::fmt::Debug for GridLine {
240 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241 write!(f, "{}", self.print_as_css_value())
242 }
243}
244
245
246impl PrintAsCssValue for GridLine {
247 fn print_as_css_value(&self) -> String {
248 match self {
249 Self::Auto => "auto".to_string(),
250 Self::Line(n) => n.to_string(),
251 Self::Named(named) => {
252 if named.span_count == 0 {
253 named.grid_line_name.as_str().to_string()
254 } else {
255 format!("{} {}", named.grid_line_name.as_str(), named.span_count)
256 }
257 }
258 Self::Span(n) => format!("span {n}"),
259 }
260 }
261}
262
263#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
265#[repr(C)]
266pub struct GridPlacement {
267 pub grid_start: GridLine,
268 pub grid_end: GridLine,
269}
270
271impl core::fmt::Debug for GridPlacement {
272 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273 write!(f, "{}", self.print_as_css_value())
274 }
275}
276
277impl Default for GridPlacement {
278 fn default() -> Self {
279 Self {
280 grid_start: GridLine::Auto,
281 grid_end: GridLine::Auto,
282 }
283 }
284}
285
286impl PrintAsCssValue for GridPlacement {
287 fn print_as_css_value(&self) -> String {
288 if self.grid_end == GridLine::Auto {
289 self.grid_start.print_as_css_value()
290 } else {
291 format!(
292 "{} / {}",
293 self.grid_start.print_as_css_value(),
294 self.grid_end.print_as_css_value()
295 )
296 }
297 }
298}
299
300#[cfg(feature = "parser")]
301#[derive(Clone, PartialEq, Eq)]
302pub enum GridParseError<'a> {
303 InvalidValue(&'a str),
304}
305
306#[cfg(feature = "parser")]
307impl_debug_as_display!(GridParseError<'a>);
308#[cfg(feature = "parser")]
309impl_display! { GridParseError<'a>, {
310 InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
311}}
312
313#[cfg(feature = "parser")]
314#[derive(Debug, Clone, PartialEq, Eq)]
315#[repr(C, u8)]
316pub enum GridParseErrorOwned {
317 InvalidValue(AzString),
318}
319
320#[cfg(feature = "parser")]
321impl GridParseError<'_> {
322 #[must_use] pub fn to_contained(&self) -> GridParseErrorOwned {
323 match self {
324 GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue((*s).to_string().into()),
325 }
326 }
327}
328
329#[cfg(feature = "parser")]
330impl GridParseErrorOwned {
331 #[must_use] pub fn to_shared(&self) -> GridParseError<'_> {
332 match self {
333 Self::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
334 }
335 }
336}
337
338#[cfg(feature = "parser")]
339fn split_respecting_parens(input: &str) -> Result<Vec<String>, ()> {
340 let mut parts = Vec::new();
341 let mut current = String::new();
342 let mut paren_depth: i32 = 0;
343
344 for ch in input.chars() {
345 match ch {
346 '(' => { paren_depth += 1; current.push(ch); }
347 ')' => { paren_depth -= 1; if paren_depth < 0 { return Err(()); } current.push(ch); }
348 ' ' if paren_depth == 0 => {
349 if !current.trim().is_empty() {
350 parts.push(current.trim().to_string());
351 current.clear();
352 }
353 }
354 _ => current.push(ch),
355 }
356 }
357 if !current.trim().is_empty() {
358 parts.push(current.trim().to_string());
359 }
360 Ok(parts)
361}
362
363#[cfg(feature = "parser")]
364pub fn parse_grid_template(input: &str) -> Result<GridTemplate, GridParseError<'_>> {
368 use crate::props::basic::pixel::parse_pixel_value;
369
370 let input = input.trim();
371
372 if input == "none" {
373 return Ok(GridTemplate::default());
374 }
375
376 let parts = split_respecting_parens(input)
377 .map_err(|()| GridParseError::InvalidValue(input))?;
378
379 let mut tracks = Vec::new();
380 for part in &parts {
381 parse_grid_track_or_repeat(part, &mut tracks)
382 .map_err(|()| GridParseError::InvalidValue(input))?;
383 }
384
385 Ok(GridTemplate {
386 tracks: GridTrackSizingVec::from_vec(tracks),
387 })
388}
389
390#[cfg(feature = "parser")]
393fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
394 const MAX_GRID_REPEAT_COUNT: usize = 10_000;
396 let input = input.trim();
397
398 if input.starts_with("repeat(") && input.ends_with(')') {
400 let content = &input[7..input.len() - 1];
401 let comma_pos = content.find(',').ok_or(())?;
403 let count_str = content[..comma_pos].trim();
404 let track_list_str = content[comma_pos + 1..].trim();
405
406 let count: usize = count_str.parse().map_err(|_| ())?;
407 if count == 0 || count > MAX_GRID_REPEAT_COUNT {
408 return Err(());
409 }
410
411 let parts = split_respecting_parens(track_list_str)?;
413 let repeat_tracks: Vec<GridTrackSizing> = parts
414 .iter()
415 .map(|p| parse_grid_track_owned(p))
416 .collect::<Result<Vec<_>, _>>()?;
417
418 for _ in 0..count {
420 tracks.extend(repeat_tracks.iter().cloned());
421 }
422 return Ok(());
423 }
424
425 tracks.push(parse_grid_track_owned(input)?);
427 Ok(())
428}
429
430#[cfg(feature = "parser")]
431fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
432 use crate::props::basic::pixel::parse_pixel_value;
433
434 let input = input.trim();
435
436 if input == "auto" {
437 return Ok(GridTrackSizing::Auto);
438 }
439
440 if input == "min-content" {
441 return Ok(GridTrackSizing::MinContent);
442 }
443
444 if input == "max-content" {
445 return Ok(GridTrackSizing::MaxContent);
446 }
447
448 if let Some(num_str) = input.strip_suffix("fr") {
449 const FR_SCALING_FACTOR: f32 = 100.0;
451 let num_str = num_str.trim();
452 if let Ok(num) = num_str.parse::<f32>() {
453 let scaled = num * FR_SCALING_FACTOR;
454 if scaled.is_nan() || scaled < crate::cast::i32_to_f32(i32::MIN) || scaled > crate::cast::i32_to_f32(i32::MAX) {
455 return Err(());
456 }
457 return Ok(GridTrackSizing::Fr(crate::cast::f32_to_i32(scaled)));
458 }
459 return Err(());
460 }
461
462 if input.starts_with("minmax(") && input.ends_with(')') {
463 let content = &input[7..input.len() - 1];
464 let parts: Vec<&str> = content.split(',').collect();
465 if parts.len() == 2 {
466 let min = parse_grid_track_owned(parts[0].trim())?;
467 let max = parse_grid_track_owned(parts[1].trim())?;
468 return Ok(GridTrackSizing::MinMax(GridMinMax {
469 min: Box::new(min),
470 max: Box::new(max),
471 }));
472 }
473 return Err(());
474 }
475
476 if input.starts_with("fit-content(") && input.ends_with(')') {
477 let size_str = &input[12..input.len() - 1].trim();
478 if let Ok(size) = parse_pixel_value(size_str) {
479 return Ok(GridTrackSizing::FitContent(size));
480 }
481 return Err(());
482 }
483
484 if let Ok(px) = parse_pixel_value(input) {
486 return Ok(GridTrackSizing::Fixed(px));
487 }
488
489 Err(())
490}
491
492#[cfg(feature = "parser")]
493pub fn parse_grid_placement(input: &str) -> Result<GridPlacement, GridParseError<'_>> {
497 let input = input.trim();
498
499 if input == "auto" {
500 return Ok(GridPlacement::default());
501 }
502
503 let parts: Vec<&str> = input.split('/').map(str::trim).collect();
505
506 let grid_start =
507 parse_grid_line_owned(parts[0]).map_err(|()| GridParseError::InvalidValue(input))?;
508 let grid_end = if parts.len() > 1 {
509 parse_grid_line_owned(parts[1]).map_err(|()| GridParseError::InvalidValue(input))?
510 } else {
511 GridLine::Auto
512 };
513
514 Ok(GridPlacement {
515 grid_start,
516 grid_end,
517 })
518}
519
520#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
524#[repr(C)]
525#[derive(Default)]
526pub enum LayoutGridAutoFlow {
527 #[default]
528 Row,
529 Column,
530 RowDense,
531 ColumnDense,
532}
533
534
535impl PrintAsCssValue for LayoutGridAutoFlow {
536 fn print_as_css_value(&self) -> String {
537 match self {
538 Self::Row => "row".to_string(),
539 Self::Column => "column".to_string(),
540 Self::RowDense => "row dense".to_string(),
541 Self::ColumnDense => "column dense".to_string(),
542 }
543 }
544}
545
546#[cfg(feature = "parser")]
547#[derive(Clone, PartialEq, Eq)]
548pub enum GridAutoFlowParseError<'a> {
549 InvalidValue(&'a str),
550}
551
552#[cfg(feature = "parser")]
553impl_debug_as_display!(GridAutoFlowParseError<'a>);
554#[cfg(feature = "parser")]
555impl_display! { GridAutoFlowParseError<'a>, {
556 InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
557}}
558
559#[cfg(feature = "parser")]
560#[derive(Debug, Clone, PartialEq, Eq)]
561#[repr(C, u8)]
562pub enum GridAutoFlowParseErrorOwned {
563 InvalidValue(AzString),
564}
565
566#[cfg(feature = "parser")]
567impl GridAutoFlowParseError<'_> {
568 #[must_use] pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
569 match self {
570 GridAutoFlowParseError::InvalidValue(s) => {
571 GridAutoFlowParseErrorOwned::InvalidValue((*s).to_string().into())
572 }
573 }
574 }
575}
576
577#[cfg(feature = "parser")]
578impl GridAutoFlowParseErrorOwned {
579 #[must_use] pub fn to_shared(&self) -> GridAutoFlowParseError<'_> {
580 match self {
581 Self::InvalidValue(s) => {
582 GridAutoFlowParseError::InvalidValue(s.as_str())
583 }
584 }
585 }
586}
587
588#[cfg(feature = "parser")]
589pub fn parse_layout_grid_auto_flow(
593 input: &str,
594) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'_>> {
595 match input.trim() {
596 "row" => Ok(LayoutGridAutoFlow::Row),
597 "column" => Ok(LayoutGridAutoFlow::Column),
598 "row dense" | "dense" => Ok(LayoutGridAutoFlow::RowDense),
599 "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
600 _ => Err(GridAutoFlowParseError::InvalidValue(input)),
601 }
602}
603
604#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
608#[repr(C)]
609#[derive(Default)]
610pub enum LayoutJustifySelf {
611 #[default]
612 Auto,
613 Start,
614 End,
615 Center,
616 Stretch,
617}
618
619
620impl PrintAsCssValue for LayoutJustifySelf {
621 fn print_as_css_value(&self) -> String {
622 match self {
623 Self::Auto => "auto".to_string(),
624 Self::Start => "start".to_string(),
625 Self::End => "end".to_string(),
626 Self::Center => "center".to_string(),
627 Self::Stretch => "stretch".to_string(),
628 }
629 }
630}
631
632#[cfg(feature = "parser")]
633#[derive(Clone, PartialEq, Eq)]
634pub enum JustifySelfParseError<'a> {
635 InvalidValue(&'a str),
636}
637
638#[cfg(feature = "parser")]
639#[derive(Debug, Clone, PartialEq, Eq)]
640#[repr(C, u8)]
641pub enum JustifySelfParseErrorOwned {
642 InvalidValue(AzString),
643}
644
645#[cfg(feature = "parser")]
646impl JustifySelfParseError<'_> {
647 #[must_use] pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
648 match self {
649 JustifySelfParseError::InvalidValue(s) => {
650 JustifySelfParseErrorOwned::InvalidValue((*s).to_string().into())
651 }
652 }
653 }
654}
655
656#[cfg(feature = "parser")]
657impl JustifySelfParseErrorOwned {
658 #[must_use] pub fn to_shared(&self) -> JustifySelfParseError<'_> {
659 match self {
660 Self::InvalidValue(s) => {
661 JustifySelfParseError::InvalidValue(s.as_str())
662 }
663 }
664 }
665}
666
667#[cfg(feature = "parser")]
668impl_debug_as_display!(JustifySelfParseError<'a>);
669#[cfg(feature = "parser")]
670impl_display! { JustifySelfParseError<'a>, {
671 InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
672}}
673
674#[cfg(feature = "parser")]
675pub fn parse_layout_justify_self(
679 input: &str,
680) -> Result<LayoutJustifySelf, JustifySelfParseError<'_>> {
681 match input.trim() {
682 "auto" => Ok(LayoutJustifySelf::Auto),
683 "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
684 "end" | "flex-end" => Ok(LayoutJustifySelf::End),
685 "center" => Ok(LayoutJustifySelf::Center),
686 "stretch" => Ok(LayoutJustifySelf::Stretch),
687 _ => Err(JustifySelfParseError::InvalidValue(input)),
688 }
689}
690
691#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
693#[repr(C)]
694#[derive(Default)]
695pub enum LayoutJustifyItems {
696 Start,
697 End,
698 Center,
699 #[default]
700 Stretch,
701}
702
703
704impl PrintAsCssValue for LayoutJustifyItems {
705 fn print_as_css_value(&self) -> String {
706 match self {
707 Self::Start => "start".to_string(),
708 Self::End => "end".to_string(),
709 Self::Center => "center".to_string(),
710 Self::Stretch => "stretch".to_string(),
711 }
712 }
713}
714
715#[cfg(feature = "parser")]
716#[derive(Clone, PartialEq, Eq)]
717pub enum JustifyItemsParseError<'a> {
718 InvalidValue(&'a str),
719}
720
721#[cfg(feature = "parser")]
722#[derive(Debug, Clone, PartialEq, Eq)]
723#[repr(C, u8)]
724pub enum JustifyItemsParseErrorOwned {
725 InvalidValue(AzString),
726}
727
728#[cfg(feature = "parser")]
729impl JustifyItemsParseError<'_> {
730 #[must_use] pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
731 match self {
732 JustifyItemsParseError::InvalidValue(s) => {
733 JustifyItemsParseErrorOwned::InvalidValue((*s).to_string().into())
734 }
735 }
736 }
737}
738
739#[cfg(feature = "parser")]
740impl JustifyItemsParseErrorOwned {
741 #[must_use] pub fn to_shared(&self) -> JustifyItemsParseError<'_> {
742 match self {
743 Self::InvalidValue(s) => {
744 JustifyItemsParseError::InvalidValue(s.as_str())
745 }
746 }
747 }
748}
749
750#[cfg(feature = "parser")]
751impl_debug_as_display!(JustifyItemsParseError<'a>);
752#[cfg(feature = "parser")]
753impl_display! { JustifyItemsParseError<'a>, {
754 InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
755}}
756
757#[cfg(feature = "parser")]
758pub fn parse_layout_justify_items(
762 input: &str,
763) -> Result<LayoutJustifyItems, JustifyItemsParseError<'_>> {
764 match input.trim() {
765 "start" => Ok(LayoutJustifyItems::Start),
766 "end" => Ok(LayoutJustifyItems::End),
767 "center" => Ok(LayoutJustifyItems::Center),
768 "stretch" => Ok(LayoutJustifyItems::Stretch),
769 _ => Err(JustifyItemsParseError::InvalidValue(input)),
770 }
771}
772
773#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
776#[repr(C)]
777pub struct LayoutGap {
778 pub inner: PixelValue,
779}
780
781impl core::fmt::Debug for LayoutGap {
782 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
783 write!(f, "{}", self.inner)
784 }
785}
786
787impl PrintAsCssValue for LayoutGap {
788 fn print_as_css_value(&self) -> String {
789 self.inner.print_as_css_value()
790 }
791}
792
793impl FormatAsRustCode for LayoutGridAutoFlow {
796 fn format_as_rust_code(&self, _tabs: usize) -> String {
797 format!(
798 "LayoutGridAutoFlow::{}",
799 match self {
800 Self::Row => "Row",
801 Self::Column => "Column",
802 Self::RowDense => "RowDense",
803 Self::ColumnDense => "ColumnDense",
804 }
805 )
806 }
807}
808
809impl FormatAsRustCode for LayoutJustifySelf {
810 fn format_as_rust_code(&self, _tabs: usize) -> String {
811 format!(
812 "LayoutJustifySelf::{}",
813 match self {
814 Self::Auto => "Auto",
815 Self::Start => "Start",
816 Self::End => "End",
817 Self::Center => "Center",
818 Self::Stretch => "Stretch",
819 }
820 )
821 }
822}
823
824impl FormatAsRustCode for LayoutJustifyItems {
825 fn format_as_rust_code(&self, _tabs: usize) -> String {
826 format!(
827 "LayoutJustifyItems::{}",
828 match self {
829 Self::Start => "Start",
830 Self::End => "End",
831 Self::Center => "Center",
832 Self::Stretch => "Stretch",
833 }
834 )
835 }
836}
837
838impl FormatAsRustCode for LayoutGap {
839 fn format_as_rust_code(&self, _tabs: usize) -> String {
840 use crate::codegen::format::format_pixel_value;
841 format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
842 }
843}
844
845impl FormatAsRustCode for GridTrackSizing {
846 #[allow(clippy::only_used_in_recursion)]
849 fn format_as_rust_code(&self, tabs: usize) -> String {
850 use crate::codegen::format::format_pixel_value;
851 match self {
852 Self::Fixed(pv) => {
853 format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
854 }
855 Self::Fr(f) => format!("GridTrackSizing::Fr({f})"),
856 Self::MinContent => "GridTrackSizing::MinContent".to_string(),
857 Self::MaxContent => "GridTrackSizing::MaxContent".to_string(),
858 Self::Auto => "GridTrackSizing::Auto".to_string(),
859 Self::MinMax(minmax) => {
860 format!(
861 "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
862 minmax.min.format_as_rust_code(tabs),
863 minmax.max.format_as_rust_code(tabs)
864 )
865 }
866 Self::FitContent(pv) => {
867 format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
868 }
869 }
870 }
871}
872
873impl FormatAsRustCode for GridAutoTracks {
874 fn format_as_rust_code(&self, tabs: usize) -> String {
875 let tracks: Vec<String> = self
876 .tracks
877 .as_ref()
878 .iter()
879 .map(|t| t.format_as_rust_code(tabs))
880 .collect();
881 format!(
882 "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
883 tracks.join(", ")
884 )
885 }
886}
887
888impl FormatAsRustCode for GridTemplateAreas {
889 fn format_as_rust_code(&self, _tabs: usize) -> String {
890 format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
891 }
892}
893
894#[cfg(feature = "parser")]
895pub fn parse_layout_gap(
899 input: &str,
900) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'_>> {
901 crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
902}
903
904#[cfg(feature = "parser")]
905pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
909 let input = input.trim();
910
911 if input == "auto" {
912 return Ok(GridLine::Auto);
913 }
914
915 if let Some(num_str) = input.strip_prefix("span ") {
916 let num_str = num_str.trim();
917 if let Ok(num) = num_str.parse::<i32>() {
918 return Ok(GridLine::Span(num));
919 }
920 return Err(());
921 }
922
923 if let Ok(num) = input.parse::<i32>() {
925 return Ok(GridLine::Line(num));
926 }
927
928 Ok(GridLine::Named(NamedGridLine::create(
930 input.to_string().into(),
931 None,
932 )))
933}
934
935#[cfg(all(test, feature = "parser"))]
936mod tests {
937 use super::*;
938
939 #[test]
941 fn test_parse_grid_template_none() {
942 let result = parse_grid_template("none").unwrap();
943 assert_eq!(result.tracks.len(), 0);
944 }
945
946 #[test]
947 fn test_parse_grid_template_single_px() {
948 let result = parse_grid_template("100px").unwrap();
949 assert_eq!(result.tracks.len(), 1);
950 assert!(matches!(
951 result.tracks.as_ref()[0],
952 GridTrackSizing::Fixed(_)
953 ));
954 }
955
956 #[test]
957 fn test_parse_grid_template_multiple_tracks() {
958 let result = parse_grid_template("100px 200px 1fr").unwrap();
959 assert_eq!(result.tracks.len(), 3);
960 }
961
962 #[test]
963 fn test_parse_grid_template_fr_units() {
964 let result = parse_grid_template("1fr 2fr 1fr").unwrap();
965 assert_eq!(result.tracks.len(), 3);
966 assert!(matches!(
967 result.tracks.as_ref()[0],
968 GridTrackSizing::Fr(100)
969 ));
970 assert!(matches!(
971 result.tracks.as_ref()[1],
972 GridTrackSizing::Fr(200)
973 ));
974 }
975
976 #[test]
977 fn test_parse_grid_template_fractional_fr() {
978 let result = parse_grid_template("0.5fr 1.5fr").unwrap();
979 assert_eq!(result.tracks.len(), 2);
980 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
981 assert!(matches!(
982 result.tracks.as_ref()[1],
983 GridTrackSizing::Fr(150)
984 ));
985 }
986
987 #[test]
988 fn test_parse_grid_template_auto() {
989 let result = parse_grid_template("auto 100px auto").unwrap();
990 assert_eq!(result.tracks.len(), 3);
991 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
992 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
993 }
994
995 #[test]
996 fn test_parse_grid_template_min_max_content() {
997 let result = parse_grid_template("min-content max-content auto").unwrap();
998 assert_eq!(result.tracks.len(), 3);
999 assert!(matches!(
1000 result.tracks.as_ref()[0],
1001 GridTrackSizing::MinContent
1002 ));
1003 assert!(matches!(
1004 result.tracks.as_ref()[1],
1005 GridTrackSizing::MaxContent
1006 ));
1007 }
1008
1009 #[test]
1010 fn test_parse_grid_template_minmax() {
1011 let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
1012 assert_eq!(result.tracks.len(), 1);
1013 assert!(matches!(
1014 result.tracks.as_ref()[0],
1015 GridTrackSizing::MinMax(_)
1016 ));
1017 }
1018
1019 #[test]
1020 fn test_parse_grid_template_minmax_complex() {
1021 let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
1022 assert_eq!(result.tracks.len(), 1);
1023 }
1024
1025 #[test]
1026 fn test_parse_grid_template_fit_content() {
1027 let result = parse_grid_template("fit-content(200px)").unwrap();
1028 assert_eq!(result.tracks.len(), 1);
1029 assert!(matches!(
1030 result.tracks.as_ref()[0],
1031 GridTrackSizing::FitContent(_)
1032 ));
1033 }
1034
1035 #[test]
1036 fn test_parse_grid_template_mixed() {
1037 let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1038 assert_eq!(result.tracks.len(), 4);
1039 }
1040
1041 #[test]
1042 fn test_parse_grid_template_percent() {
1043 let result = parse_grid_template("25% 50% 25%").unwrap();
1044 assert_eq!(result.tracks.len(), 3);
1045 }
1046
1047 #[test]
1048 fn test_parse_grid_template_em_units() {
1049 let result = parse_grid_template("10em 20em 1fr").unwrap();
1050 assert_eq!(result.tracks.len(), 3);
1051 }
1052
1053 #[test]
1055 fn test_parse_grid_placement_auto() {
1056 let result = parse_grid_placement("auto").unwrap();
1057 assert!(matches!(result.grid_start, GridLine::Auto));
1058 assert!(matches!(result.grid_end, GridLine::Auto));
1059 }
1060
1061 #[test]
1062 fn test_parse_grid_placement_line_number() {
1063 let result = parse_grid_placement("1").unwrap();
1064 assert!(matches!(result.grid_start, GridLine::Line(1)));
1065 assert!(matches!(result.grid_end, GridLine::Auto));
1066 }
1067
1068 #[test]
1069 fn test_parse_grid_placement_negative_line() {
1070 let result = parse_grid_placement("-1").unwrap();
1071 assert!(matches!(result.grid_start, GridLine::Line(-1)));
1072 }
1073
1074 #[test]
1075 fn test_parse_grid_placement_span() {
1076 let result = parse_grid_placement("span 2").unwrap();
1077 assert!(matches!(result.grid_start, GridLine::Span(2)));
1078 }
1079
1080 #[test]
1081 fn test_parse_grid_placement_start_end() {
1082 let result = parse_grid_placement("1 / 3").unwrap();
1083 assert!(matches!(result.grid_start, GridLine::Line(1)));
1084 assert!(matches!(result.grid_end, GridLine::Line(3)));
1085 }
1086
1087 #[test]
1088 fn test_parse_grid_placement_span_end() {
1089 let result = parse_grid_placement("1 / span 2").unwrap();
1090 assert!(matches!(result.grid_start, GridLine::Line(1)));
1091 assert!(matches!(result.grid_end, GridLine::Span(2)));
1092 }
1093
1094 #[test]
1095 fn test_parse_grid_placement_named_line() {
1096 let result = parse_grid_placement("header-start").unwrap();
1097 assert!(matches!(result.grid_start, GridLine::Named(_)));
1098 }
1099
1100 #[test]
1101 fn test_parse_grid_placement_named_start_end() {
1102 let result = parse_grid_placement("header-start / header-end").unwrap();
1103 assert!(matches!(result.grid_start, GridLine::Named(_)));
1104 assert!(matches!(result.grid_end, GridLine::Named(_)));
1105 }
1106
1107 #[test]
1109 fn test_parse_grid_template_whitespace() {
1110 let result = parse_grid_template(" 100px 200px ").unwrap();
1111 assert_eq!(result.tracks.len(), 2);
1112 }
1113
1114 #[test]
1115 fn test_parse_grid_placement_whitespace() {
1116 let result = parse_grid_placement(" 1 / 3 ").unwrap();
1117 assert!(matches!(result.grid_start, GridLine::Line(1)));
1118 assert!(matches!(result.grid_end, GridLine::Line(3)));
1119 }
1120
1121 #[test]
1122 fn test_parse_grid_template_zero_fr() {
1123 let result = parse_grid_template("0fr").unwrap();
1124 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1125 }
1126
1127 #[test]
1128 fn test_parse_grid_placement_zero_line() {
1129 let result = parse_grid_placement("0").unwrap();
1130 assert!(matches!(result.grid_start, GridLine::Line(0)));
1131 }
1132
1133 #[test]
1135 fn test_parse_grid_template_repeat_fr() {
1136 let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1137 assert_eq!(result.tracks.len(), 3);
1138 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
1139 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1140 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1141 }
1142
1143 #[test]
1144 fn test_parse_grid_template_repeat_px() {
1145 let result = parse_grid_template("repeat(2, 100px)").unwrap();
1146 assert_eq!(result.tracks.len(), 2);
1147 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1148 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
1149 }
1150
1151 #[test]
1152 fn test_parse_grid_template_repeat_multiple_tracks() {
1153 let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1155 assert_eq!(result.tracks.len(), 4);
1156 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1157 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1158 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
1159 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
1160 }
1161
1162 #[test]
1163 fn test_parse_grid_template_repeat_with_other_tracks() {
1164 let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1166 assert_eq!(result.tracks.len(), 4);
1167 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1168 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1169 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1170 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1171 }
1172
1173 #[test]
1174 fn test_parse_grid_template_repeat_minmax() {
1175 let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1176 assert_eq!(result.tracks.len(), 3);
1177 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
1178 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
1179 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
1180 }
1181}
1182
1183#[repr(C)]
1188#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1189pub struct GridAreaDefinition {
1190 pub name: AzString,
1191 pub row_start: u16,
1192 pub row_end: u16,
1193 pub column_start: u16,
1194 pub column_end: u16,
1195}
1196
1197impl_option!(
1198 GridAreaDefinition,
1199 OptionGridAreaDefinition,
1200 copy = false,
1201 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1202);
1203
1204impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
1205impl_vec_clone!(
1206 GridAreaDefinition,
1207 GridAreaDefinitionVec,
1208 GridAreaDefinitionVecDestructor
1209);
1210impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1211impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1212impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1213impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1214impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1215impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1216impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1217
1218#[repr(C)]
1228#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1229pub struct GridTemplateAreas {
1230 pub areas: GridAreaDefinitionVec,
1231}
1232
1233impl Default for GridTemplateAreas {
1234 fn default() -> Self {
1235 Self { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
1236 }
1237}
1238
1239impl PrintAsCssValue for GridTemplateAreas {
1240 fn print_as_css_value(&self) -> String {
1241 let areas_slice = self.areas.as_ref();
1242 if areas_slice.is_empty() {
1243 return "none".to_string();
1244 }
1245 let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1247 let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1248 let num_rows = (max_row - 1) as usize;
1249 let num_cols = (max_col - 1) as usize;
1250 let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1251 for area in areas_slice {
1252 let row_start = area.row_start as usize - 1;
1253 let row_end = area.row_end as usize - 1;
1254 let col_start = area.column_start as usize - 1;
1255 let col_end = area.column_end as usize - 1;
1256 for row in grid.iter_mut().take(row_end).skip(row_start) {
1257 for cell in row.iter_mut().take(col_end).skip(col_start) {
1258 *cell = area.name.as_str().to_string();
1259 }
1260 }
1261 }
1262 grid.iter()
1263 .map(|row| format!("\"{}\"", row.join(" ")))
1264 .collect::<Vec<_>>()
1265 .join(" ")
1266 }
1267}
1268
1269#[cfg(feature = "parser")]
1277pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1281 use alloc::collections::BTreeMap;
1282 let input = input.trim();
1283 if input == "none" {
1284 return Ok(GridTemplateAreas::default());
1285 }
1286
1287 let mut rows: Vec<Vec<String>> = Vec::new();
1289 let mut i = 0;
1290 let bytes = input.as_bytes();
1291 while i < bytes.len() {
1292 if bytes[i] == b'"' || bytes[i] == b'\'' {
1293 let quote = bytes[i];
1294 i += 1;
1295 let start = i;
1296 while i < bytes.len() && bytes[i] != quote {
1297 i += 1;
1298 }
1299 if i >= bytes.len() {
1300 return Err(());
1301 }
1302 let row_str = &input[start..i];
1303 let cells: Vec<String> = row_str.split_whitespace().map(std::string::ToString::to_string).collect();
1304 if cells.is_empty() {
1305 return Err(());
1306 }
1307 rows.push(cells);
1308 }
1309 i += 1;
1311 }
1312
1313 if rows.is_empty() {
1314 return Err(());
1315 }
1316
1317 let num_cols = rows[0].len();
1319 for row in &rows {
1320 if row.len() != num_cols {
1321 return Err(());
1322 }
1323 }
1324
1325 let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1327
1328 for (row_idx, row) in rows.iter().enumerate() {
1329 for (col_idx, cell) in row.iter().enumerate() {
1330 if cell == "." {
1331 continue; }
1333 let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
1334 entry.0 = entry.0.min(row_idx);
1335 entry.1 = entry.1.max(row_idx);
1336 entry.2 = entry.2.min(col_idx);
1337 entry.3 = entry.3.max(col_idx);
1338 }
1339 }
1340
1341 let mut areas = Vec::new();
1343 for (name, (min_row, max_row, min_col, max_col)) in area_map {
1344 areas.push(GridAreaDefinition {
1345 name: name.into(),
1346 row_start: u16::try_from(min_row + 1).unwrap_or(u16::MAX),
1347 row_end: u16::try_from(max_row + 2).unwrap_or(u16::MAX), column_start: u16::try_from(min_col + 1).unwrap_or(u16::MAX),
1349 column_end: u16::try_from(max_col + 2).unwrap_or(u16::MAX),
1350 });
1351 }
1352
1353 Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
1354}
1355
1356#[cfg(all(test, feature = "parser"))]
1357mod autotest_generated {
1358 use super::*;
1359
1360 #[test]
1371 fn named_grid_line_span_roundtrips_every_nonzero_i32() {
1372 for span in [1_i32, -1, 7, -7, i32::MAX, i32::MIN, i32::MIN + 1] {
1373 let line = NamedGridLine::create("area".to_string().into(), Some(span));
1374 assert_eq!(line.span_count, span);
1375 assert_eq!(line.span(), Some(span), "span {span} must survive create()");
1376 }
1377 }
1378
1379 #[test]
1380 fn named_grid_line_span_of_some_zero_is_lossy() {
1381 let explicit_zero = NamedGridLine::create("a".to_string().into(), Some(0));
1386 let absent = NamedGridLine::create("a".to_string().into(), None);
1387
1388 assert_eq!(explicit_zero.span(), None);
1389 assert_eq!(absent.span(), None);
1390 assert_eq!(explicit_zero, absent, "Some(0) and None collapse to the same value");
1391 }
1392
1393 #[test]
1394 fn named_grid_line_create_accepts_empty_and_unicode_names() {
1395 let empty = NamedGridLine::create(String::new().into(), None);
1396 assert_eq!(empty.grid_line_name.as_str(), "");
1397 assert_eq!(empty.span(), None);
1398
1399 let emoji = NamedGridLine::create("\u{1F600}\u{0301}".to_string().into(), Some(3));
1400 assert_eq!(emoji.grid_line_name.as_str(), "\u{1F600}\u{0301}");
1401 assert_eq!(emoji.span(), Some(3));
1402 }
1403
1404 #[test]
1405 fn named_grid_line_span_on_an_extreme_instance_does_not_panic() {
1406 let huge_name = "x".repeat(100_000);
1407 let line = NamedGridLine::create(huge_name.clone().into(), Some(i32::MIN));
1408 assert_eq!(line.span(), Some(i32::MIN));
1409 assert_eq!(line.grid_line_name.as_str().len(), huge_name.len());
1410 }
1411
1412 #[test]
1417 fn split_respecting_parens_empty_and_whitespace_yield_ok_empty_not_err() {
1418 assert_eq!(split_respecting_parens(""), Ok(Vec::new()));
1422 assert_eq!(split_respecting_parens(" "), Ok(Vec::new()));
1423 assert_eq!(split_respecting_parens(" \t\n "), Ok(Vec::new()));
1424 }
1425
1426 #[test]
1427 fn split_respecting_parens_valid_minimal_and_nested_calls() {
1428 assert_eq!(
1429 split_respecting_parens("100px 1fr"),
1430 Ok(vec!["100px".to_string(), "1fr".to_string()])
1431 );
1432 assert_eq!(
1434 split_respecting_parens("repeat(2, 100px 1fr) auto"),
1435 Ok(vec!["repeat(2, 100px 1fr)".to_string(), "auto".to_string()])
1436 );
1437 assert_eq!(
1438 split_respecting_parens("a(b c)d e"),
1439 Ok(vec!["a(b c)d".to_string(), "e".to_string()])
1440 );
1441 }
1442
1443 #[test]
1444 fn split_respecting_parens_rejects_unbalanced_close_paren() {
1445 assert_eq!(split_respecting_parens(")"), Err(()));
1446 assert_eq!(split_respecting_parens("a)b"), Err(()));
1447 assert_eq!(split_respecting_parens("(a))"), Err(()));
1448 assert_eq!(split_respecting_parens(")("), Err(()));
1449 }
1450
1451 #[test]
1452 fn split_respecting_parens_accepts_unbalanced_open_paren_is_lax() {
1453 assert_eq!(split_respecting_parens("((("), Ok(vec!["(((".to_string()]));
1457 assert_eq!(
1458 split_respecting_parens("repeat(2, 1fr"),
1459 Ok(vec!["repeat(2, 1fr".to_string()])
1460 );
1461 assert!(parse_grid_template("repeat(2, 1fr").is_err());
1462 }
1463
1464 #[test]
1465 fn split_respecting_parens_does_not_treat_tab_or_newline_as_a_separator() {
1466 assert_eq!(
1470 split_respecting_parens("100px\t200px"),
1471 Ok(vec!["100px\t200px".to_string()])
1472 );
1473 assert_eq!(
1474 split_respecting_parens("100px\n200px"),
1475 Ok(vec!["100px\n200px".to_string()])
1476 );
1477 assert!(parse_grid_template("100px\t200px").is_err());
1479 assert!(parse_grid_template("100px\n200px").is_err());
1480 assert_eq!(parse_grid_template("100px 200px").unwrap().tracks.len(), 2);
1482 }
1483
1484 #[test]
1485 fn split_respecting_parens_handles_multibyte_unicode() {
1486 assert_eq!(
1488 split_respecting_parens("\u{1F600} e\u{0301}"),
1489 Ok(vec!["\u{1F600}".to_string(), "e\u{0301}".to_string()])
1490 );
1491 assert_eq!(
1492 split_respecting_parens("\u{1F600}(\u{4E2D} \u{6587})"),
1493 Ok(vec!["\u{1F600}(\u{4E2D} \u{6587})".to_string()])
1494 );
1495 }
1496
1497 #[test]
1498 fn split_respecting_parens_survives_a_million_chars_and_deep_nesting() {
1499 let long = "a".repeat(1_000_000);
1501 let parts = split_respecting_parens(&long).unwrap();
1502 assert_eq!(parts.len(), 1);
1503 assert_eq!(parts[0].len(), 1_000_000);
1504
1505 let many = "1fr ".repeat(50_000);
1507 assert_eq!(split_respecting_parens(&many).unwrap().len(), 50_000);
1508
1509 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
1512 assert_eq!(split_respecting_parens(&nested).unwrap().len(), 1);
1513 assert!(parse_grid_template(&nested).is_err());
1514 }
1515
1516 #[test]
1521 fn parse_grid_track_owned_empty_and_whitespace_are_err() {
1522 assert_eq!(parse_grid_track_owned(""), Err(()));
1523 assert_eq!(parse_grid_track_owned(" "), Err(()));
1524 assert_eq!(parse_grid_track_owned("\t\n"), Err(()));
1525 }
1526
1527 #[test]
1528 fn parse_grid_track_owned_valid_minimal_keywords() {
1529 assert_eq!(parse_grid_track_owned("auto"), Ok(GridTrackSizing::Auto));
1530 assert_eq!(parse_grid_track_owned("min-content"), Ok(GridTrackSizing::MinContent));
1531 assert_eq!(parse_grid_track_owned("max-content"), Ok(GridTrackSizing::MaxContent));
1532 assert_eq!(
1533 parse_grid_track_owned(" auto "),
1534 Ok(GridTrackSizing::Auto),
1535 "leading/trailing whitespace is trimmed"
1536 );
1537 assert_eq!(parse_grid_track_owned("AUTO"), Err(()));
1539 assert_eq!(parse_grid_track_owned("Min-Content"), Err(()));
1540 }
1541
1542 #[test]
1543 fn parse_grid_track_owned_fr_is_scaled_by_100_and_truncates() {
1544 assert_eq!(parse_grid_track_owned("1fr"), Ok(GridTrackSizing::Fr(100)));
1545 assert_eq!(parse_grid_track_owned("0fr"), Ok(GridTrackSizing::Fr(0)));
1546 assert_eq!(parse_grid_track_owned("-0fr"), Ok(GridTrackSizing::Fr(0)));
1547 assert_eq!(parse_grid_track_owned("+1fr"), Ok(GridTrackSizing::Fr(100)));
1548 assert_eq!(parse_grid_track_owned("0.5fr"), Ok(GridTrackSizing::Fr(50)));
1549
1550 assert_eq!(parse_grid_track_owned("0.005fr"), Ok(GridTrackSizing::Fr(0)));
1553 assert_eq!(parse_grid_track_owned("1.999fr"), Ok(GridTrackSizing::Fr(199)));
1554
1555 assert_eq!(parse_grid_track_owned("-1fr"), Ok(GridTrackSizing::Fr(-100)));
1557 }
1558
1559 #[test]
1560 fn parse_grid_track_owned_fr_rejects_nan_inf_and_out_of_range() {
1561 for bad in [
1564 "NaNfr", "nanfr", "inffr", "-inffr", "infinityfr", "1e8fr", "-1e8fr",
1565 "1e30fr", "-1e30fr", "340282350000000000000000000000000000000fr",
1566 ] {
1567 assert_eq!(parse_grid_track_owned(bad), Err(()), "{bad:?} must be rejected");
1568 }
1569
1570 assert_eq!(parse_grid_track_owned("1e7fr"), Ok(GridTrackSizing::Fr(1_000_000_000)));
1572 assert_eq!(parse_grid_track_owned("-1e7fr"), Ok(GridTrackSizing::Fr(-1_000_000_000)));
1573 }
1574
1575 #[test]
1576 fn parse_grid_track_owned_bare_fr_suffix_is_err() {
1577 assert_eq!(parse_grid_track_owned("fr"), Err(()));
1578 assert_eq!(parse_grid_track_owned(" fr"), Err(()));
1579 assert_eq!(parse_grid_track_owned("xfr"), Err(()));
1580 }
1581
1582 #[test]
1583 fn parse_grid_track_owned_minmax_comma_split_is_paren_unaware() {
1584 assert_eq!(
1585 parse_grid_track_owned("minmax(100px, 1fr)"),
1586 Ok(GridTrackSizing::MinMax(GridMinMax {
1587 min: Box::new(GridTrackSizing::Fixed(PixelValue::px(100.0))),
1588 max: Box::new(GridTrackSizing::Fr(100)),
1589 }))
1590 );
1591 assert!(matches!(
1593 parse_grid_track_owned("minmax(fit-content(10px), 2px)"),
1594 Ok(GridTrackSizing::MinMax(_))
1595 ));
1596 assert_eq!(parse_grid_track_owned("minmax(minmax(1px,2px), 3px)"), Err(()));
1601 assert_eq!(parse_grid_track_owned("minmax(1px,2px,3px)"), Err(()));
1602 assert_eq!(parse_grid_track_owned("minmax(1px)"), Err(()));
1603 assert_eq!(parse_grid_track_owned("minmax()"), Err(()));
1604 }
1605
1606 #[test]
1607 fn parse_grid_track_owned_deeply_nested_minmax_cannot_stack_overflow() {
1608 let deep = format!("{}1px{}", "minmax(".repeat(10_000), ")".repeat(10_000));
1612 assert_eq!(parse_grid_track_owned(&deep), Err(()));
1613
1614 let deep_fit = format!("{}1px{}", "fit-content(".repeat(10_000), ")".repeat(10_000));
1615 assert_eq!(parse_grid_track_owned(&deep_fit), Err(()));
1616 }
1617
1618 #[test]
1619 fn parse_grid_track_owned_unclosed_and_truncated_funcs_are_err() {
1620 for bad in [
1624 "minmax(", "minmax", "minmax)", "fit-content(", "fit-content",
1625 "fit-content)", "repeat(", ")", "(", "()", "fit-content()",
1626 ] {
1627 assert_eq!(parse_grid_track_owned(bad), Err(()), "{bad:?}");
1628 }
1629 }
1630
1631 #[test]
1632 fn parse_grid_track_owned_fit_content_and_pixel_fallback() {
1633 assert_eq!(
1634 parse_grid_track_owned("fit-content(200px)"),
1635 Ok(GridTrackSizing::FitContent(PixelValue::px(200.0)))
1636 );
1637 assert_eq!(
1638 parse_grid_track_owned("100px"),
1639 Ok(GridTrackSizing::Fixed(PixelValue::px(100.0)))
1640 );
1641 assert_eq!(
1642 parse_grid_track_owned("25%"),
1643 Ok(GridTrackSizing::Fixed(PixelValue::percent(25.0)))
1644 );
1645 assert_eq!(
1648 parse_grid_track_owned("100"),
1649 Ok(GridTrackSizing::Fixed(PixelValue::px(100.0)))
1650 );
1651 }
1652
1653 #[test]
1654 fn parse_grid_track_owned_pixel_fallback_swallows_nan_and_inf() {
1655 assert_eq!(
1659 parse_grid_track_owned("NaNpx"),
1660 Ok(GridTrackSizing::Fixed(PixelValue::px(0.0))),
1661 "NaN silently becomes 0px"
1662 );
1663 assert_eq!(
1664 parse_grid_track_owned("inf"),
1665 Ok(GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY))),
1666 "inf is accepted and saturates"
1667 );
1668 assert_eq!(
1669 parse_grid_track_owned("1e40px"),
1670 Ok(GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY)))
1671 );
1672 }
1673
1674 #[test]
1675 fn parse_grid_track_owned_garbage_and_long_input_never_panic() {
1676 for bad in [
1677 "!!!", ";;;", "\u{1F600}", "e\u{0301}\u{0301}", "\0", "100px;garbage",
1678 "auto;", "1fr 1fr", "--var(x)", "calc(1px + 1px)",
1679 ] {
1680 assert_eq!(parse_grid_track_owned(bad), Err(()), "{bad:?}");
1681 }
1682 assert_eq!(parse_grid_track_owned(&"a".repeat(1_000_000)), Err(()));
1684 }
1685
1686 #[test]
1691 fn parse_grid_track_or_repeat_valid_minimal() {
1692 let mut tracks = Vec::new();
1693 assert_eq!(parse_grid_track_or_repeat("repeat(2, 1fr)", &mut tracks), Ok(()));
1694 assert_eq!(tracks, vec![GridTrackSizing::Fr(100), GridTrackSizing::Fr(100)]);
1695
1696 assert_eq!(parse_grid_track_or_repeat("auto", &mut tracks), Ok(()));
1698 assert_eq!(tracks.len(), 3);
1699 assert_eq!(tracks[2], GridTrackSizing::Auto);
1700 }
1701
1702 #[test]
1703 fn parse_grid_track_or_repeat_count_is_bounded_at_10_000() {
1704 let mut tracks = Vec::new();
1705 assert_eq!(parse_grid_track_or_repeat("repeat(10000, 1fr)", &mut tracks), Ok(()));
1706 assert_eq!(tracks.len(), 10_000, "the documented maximum is accepted");
1707
1708 for bad in [
1710 "repeat(10001, 1fr)",
1711 "repeat(0, 1fr)",
1712 "repeat(-1, 1fr)",
1713 "repeat(18446744073709551616, 1fr)",
1714 "repeat(99999999999999999999999999, 1fr)",
1715 "repeat(1.5, 1fr)",
1716 "repeat(NaN, 1fr)",
1717 "repeat(inf, 1fr)",
1718 "repeat(auto-fill, 1fr)",
1719 ] {
1720 let mut t = Vec::new();
1721 assert_eq!(parse_grid_track_or_repeat(bad, &mut t), Err(()), "{bad:?}");
1722 }
1723 }
1724
1725 #[test]
1726 fn parse_grid_track_or_repeat_expansion_is_amplified_by_the_track_list_length() {
1727 let mut tracks = Vec::new();
1732 let input = format!("repeat(10000, {})", "1fr ".repeat(10).trim());
1733 assert_eq!(parse_grid_track_or_repeat(&input, &mut tracks), Ok(()));
1734 assert_eq!(tracks.len(), 100_000);
1735 assert!(tracks.iter().all(|t| *t == GridTrackSizing::Fr(100)));
1736 }
1737
1738 #[test]
1739 fn parse_grid_track_or_repeat_with_an_empty_track_list_is_lax() {
1740 let mut tracks = Vec::new();
1743 assert_eq!(parse_grid_track_or_repeat("repeat(2, )", &mut tracks), Ok(()));
1744 assert!(tracks.is_empty());
1745 assert_eq!(parse_grid_template("repeat(2, )").unwrap().tracks.len(), 0);
1746 }
1747
1748 #[test]
1749 fn parse_grid_track_or_repeat_rejects_nested_repeat_and_malformed_shapes() {
1750 for bad in [
1751 "repeat(2, repeat(2, 1fr))", "repeat(2)", "repeat()",
1754 "repeat(2, 1fr", "",
1756 " ",
1757 "\u{1F600}",
1758 "repeat(2, garbage)",
1759 ] {
1760 let mut t = Vec::new();
1761 assert_eq!(parse_grid_track_or_repeat(bad, &mut t), Err(()), "{bad:?}");
1762 }
1763 }
1764
1765 #[test]
1766 fn parse_grid_track_or_repeat_appends_nothing_when_it_fails() {
1767 let mut tracks = vec![GridTrackSizing::Auto];
1772 for bad in [
1773 "repeat(2, 1fr garbage)", "repeat(3, 1fr) trailing", "repeat(0, 1fr)",
1776 "repeat(2, 1fr", "garbage",
1778 ] {
1779 assert_eq!(parse_grid_track_or_repeat(bad, &mut tracks), Err(()), "{bad:?}");
1780 assert_eq!(
1781 tracks,
1782 vec![GridTrackSizing::Auto],
1783 "{bad:?} mutated the caller's Vec despite returning Err"
1784 );
1785 }
1786 }
1787
1788 #[test]
1793 fn parse_grid_template_empty_and_whitespace_are_ok_empty_is_lax() {
1794 for input in ["", " ", "\t\n", " \r\n "] {
1797 let parsed = parse_grid_template(input)
1798 .unwrap_or_else(|e| panic!("{input:?} unexpectedly failed: {e:?}"));
1799 assert_eq!(parsed.tracks.len(), 0, "{input:?}");
1800 assert_eq!(parsed, GridTemplate::default(), "{input:?}");
1801 }
1802 }
1803
1804 #[test]
1805 fn parse_grid_template_none_is_case_sensitive() {
1806 assert_eq!(parse_grid_template("none").unwrap(), GridTemplate::default());
1807 assert_eq!(parse_grid_template(" none ").unwrap(), GridTemplate::default());
1808 assert!(parse_grid_template("NONE").is_err());
1810 assert!(parse_grid_template("None").is_err());
1811 }
1812
1813 #[test]
1814 fn parse_grid_template_garbage_is_err_and_reports_the_trimmed_input() {
1815 let err = parse_grid_template(" !!! garbage ").unwrap_err();
1816 assert_eq!(
1817 err,
1818 GridParseError::InvalidValue("!!! garbage"),
1819 "the error borrows the trimmed input, not the raw one"
1820 );
1821 assert_eq!(format!("{err}"), "Invalid grid value: \"!!! garbage\"");
1822
1823 for bad in [
1824 ")", "a)b", "100px;200px", "1 fr", "100px, 200px", "\u{1F600}",
1825 "100px \u{1F600}", "\0", "calc(100px)",
1826 ] {
1827 assert!(parse_grid_template(bad).is_err(), "{bad:?} must be rejected");
1828 }
1829 }
1830
1831 #[test]
1832 fn parse_grid_template_boundary_numbers_do_not_overflow() {
1833 for bad in ["NaNfr", "inffr", "-inffr", "1e30fr"] {
1835 assert!(parse_grid_template(bad).is_err(), "{bad:?}");
1836 }
1837 assert_eq!(
1839 parse_grid_template("1e40px").unwrap().tracks.as_ref()[0],
1840 GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY))
1841 );
1842 assert!(parse_grid_template("9223372036854775807").is_ok());
1844 assert_eq!(
1845 parse_grid_template("0px -0px 0fr").unwrap().tracks.len(),
1846 3,
1847 "zero and negative-zero are accepted"
1848 );
1849 }
1850
1851 #[test]
1852 fn parse_grid_template_extremely_long_input_terminates() {
1853 let many = "1fr ".repeat(50_000);
1855 assert_eq!(parse_grid_template(&many).unwrap().tracks.len(), 50_000);
1856 assert!(parse_grid_template(&"a".repeat(1_000_000)).is_err());
1858 }
1859
1860 #[test]
1861 fn parse_grid_template_deeply_nested_parens_do_not_stack_overflow() {
1862 let nested = format!("{}1px{}", "(".repeat(10_000), ")".repeat(10_000));
1863 assert!(parse_grid_template(&nested).is_err());
1864 assert!(parse_grid_template(&")".repeat(10_000)).is_err());
1866 }
1867
1868 #[test]
1873 fn parse_grid_line_owned_valid_minimal() {
1874 assert_eq!(parse_grid_line_owned("auto"), Ok(GridLine::Auto));
1875 assert_eq!(parse_grid_line_owned("1"), Ok(GridLine::Line(1)));
1876 assert_eq!(parse_grid_line_owned("-1"), Ok(GridLine::Line(-1)));
1877 assert_eq!(parse_grid_line_owned("+1"), Ok(GridLine::Line(1)));
1878 assert_eq!(parse_grid_line_owned("0"), Ok(GridLine::Line(0)));
1879 assert_eq!(parse_grid_line_owned("-0"), Ok(GridLine::Line(0)));
1880 assert_eq!(parse_grid_line_owned("span 2"), Ok(GridLine::Span(2)));
1881 assert_eq!(parse_grid_line_owned("span 2"), Ok(GridLine::Span(2)));
1882 }
1883
1884 #[test]
1885 fn parse_grid_line_owned_i32_boundaries_saturate_into_a_named_line() {
1886 assert_eq!(parse_grid_line_owned("2147483647"), Ok(GridLine::Line(i32::MAX)));
1887 assert_eq!(parse_grid_line_owned("-2147483648"), Ok(GridLine::Line(i32::MIN)));
1888
1889 assert_eq!(
1894 parse_grid_line_owned("2147483648"),
1895 Ok(GridLine::Named(NamedGridLine::create("2147483648".to_string().into(), None)))
1896 );
1897 assert_eq!(
1898 parse_grid_line_owned("-2147483649"),
1899 Ok(GridLine::Named(NamedGridLine::create("-2147483649".to_string().into(), None)))
1900 );
1901
1902 assert_eq!(parse_grid_line_owned("span 2147483648"), Err(()));
1904 assert_eq!(parse_grid_line_owned("span -2147483649"), Err(()));
1905 assert_eq!(parse_grid_line_owned("span abc"), Err(()));
1906
1907 assert_eq!(
1911 parse_grid_line_owned("span "),
1912 Ok(GridLine::Named(NamedGridLine::create("span".to_string().into(), None)))
1913 );
1914 }
1915
1916 #[test]
1917 fn parse_grid_line_owned_span_accepts_zero_and_negative_is_lax() {
1918 assert_eq!(parse_grid_line_owned("span 0"), Ok(GridLine::Span(0)));
1921 assert_eq!(parse_grid_line_owned("span -5"), Ok(GridLine::Span(-5)));
1922 assert_eq!(parse_grid_line_owned("span -2147483648"), Ok(GridLine::Span(i32::MIN)));
1923 }
1924
1925 #[test]
1926 fn parse_grid_line_owned_never_errs_on_garbage_it_names_it() {
1927 for garbage in ["", " ", "!!!", ";;;", "\u{1F600}", "\0", "span", "100px", "1 2"] {
1932 let parsed = parse_grid_line_owned(garbage)
1933 .unwrap_or_else(|()| panic!("{garbage:?} unexpectedly errored"));
1934 assert!(
1935 matches!(parsed, GridLine::Named(_)),
1936 "{garbage:?} became {parsed:?}, expected a Named catch-all"
1937 );
1938 }
1939 assert_eq!(
1940 parse_grid_line_owned(" "),
1941 Ok(GridLine::Named(NamedGridLine::create(String::new().into(), None))),
1942 "whitespace-only trims to an EMPTY named line"
1943 );
1944 assert!(parse_grid_line_owned(&"x".repeat(100_000)).is_ok());
1946 }
1947
1948 #[test]
1953 fn parse_grid_placement_valid_minimal() {
1954 assert_eq!(parse_grid_placement("auto").unwrap(), GridPlacement::default());
1955 assert_eq!(
1956 parse_grid_placement("1 / 3").unwrap(),
1957 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Line(3) }
1958 );
1959 assert_eq!(
1960 parse_grid_placement("1/3").unwrap(),
1961 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Line(3) },
1962 "the slash does not need surrounding spaces"
1963 );
1964 assert_eq!(
1965 parse_grid_placement(" 1 / span 2 ").unwrap(),
1966 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Span(2) }
1967 );
1968 }
1969
1970 #[test]
1971 fn parse_grid_placement_extra_slash_segments_are_silently_dropped() {
1972 assert_eq!(
1975 parse_grid_placement("1 / 2 / 3").unwrap(),
1976 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Line(2) }
1977 );
1978 assert_eq!(
1979 parse_grid_placement("1 / 2 / 3 / 4 / garbage").unwrap(),
1980 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Line(2) }
1981 );
1982 }
1983
1984 #[test]
1985 fn parse_grid_placement_empty_and_garbage_are_ok_named_is_lax() {
1986 let empty = parse_grid_placement("").unwrap();
1990 assert_eq!(
1991 empty,
1992 GridPlacement {
1993 grid_start: GridLine::Named(NamedGridLine::create(String::new().into(), None)),
1994 grid_end: GridLine::Auto,
1995 },
1996 "empty input should be Err, but yields a Named(\"\") start"
1997 );
1998
1999 for garbage in [" ", "!!!", "\u{1F600}", "/", "///", "\0"] {
2000 assert!(parse_grid_placement(garbage).is_ok(), "{garbage:?}");
2001 }
2002
2003 let err = parse_grid_placement(" span x ").unwrap_err();
2005 assert_eq!(err, GridParseError::InvalidValue("span x"));
2006 assert_eq!(format!("{err}"), "Invalid grid value: \"span x\"");
2007 assert!(parse_grid_placement("1 / span x").is_err());
2008 }
2009
2010 #[test]
2011 fn parse_grid_placement_long_and_nested_input_does_not_panic() {
2012 let long = "a".repeat(200_000);
2013 assert!(parse_grid_placement(&long).is_ok());
2014 assert!(parse_grid_placement(&format!("{long} / {long}")).is_ok());
2015
2016 let nested = format!("{}x{}", "(".repeat(10_000), ")".repeat(10_000));
2018 assert!(parse_grid_placement(&nested).is_ok());
2019 }
2020
2021 #[test]
2026 fn parse_layout_grid_auto_flow_accepts_exactly_five_spellings() {
2027 assert_eq!(parse_layout_grid_auto_flow("row"), Ok(LayoutGridAutoFlow::Row));
2028 assert_eq!(parse_layout_grid_auto_flow("column"), Ok(LayoutGridAutoFlow::Column));
2029 assert_eq!(parse_layout_grid_auto_flow("row dense"), Ok(LayoutGridAutoFlow::RowDense));
2030 assert_eq!(parse_layout_grid_auto_flow("dense"), Ok(LayoutGridAutoFlow::RowDense));
2031 assert_eq!(
2032 parse_layout_grid_auto_flow("column dense"),
2033 Ok(LayoutGridAutoFlow::ColumnDense)
2034 );
2035 assert_eq!(parse_layout_grid_auto_flow(" row "), Ok(LayoutGridAutoFlow::Row));
2036 }
2037
2038 #[test]
2039 fn parse_layout_grid_auto_flow_rejects_everything_else() {
2040 for bad in [
2041 "",
2042 " ",
2043 "\t\n",
2044 "ROW", "Row",
2046 "dense row", "dense column",
2048 "row dense", "row\tdense",
2050 "row dense extra",
2051 "row;",
2052 "\u{1F600}",
2053 "\0",
2054 ] {
2055 assert!(parse_layout_grid_auto_flow(bad).is_err(), "{bad:?} must be rejected");
2056 }
2057 assert!(parse_layout_grid_auto_flow(&"row ".repeat(250_000)).is_err());
2059 }
2060
2061 #[test]
2062 fn parse_layout_grid_auto_flow_error_borrows_the_untrimmed_input() {
2063 let err = parse_layout_grid_auto_flow(" bogus ").unwrap_err();
2067 assert_eq!(err, GridAutoFlowParseError::InvalidValue(" bogus "));
2068 assert_eq!(format!("{err}"), "Invalid grid-auto-flow value: \" bogus \"");
2069 }
2070
2071 #[test]
2076 fn parse_layout_justify_self_accepts_the_flex_aliases() {
2077 assert_eq!(parse_layout_justify_self("auto"), Ok(LayoutJustifySelf::Auto));
2078 assert_eq!(parse_layout_justify_self("start"), Ok(LayoutJustifySelf::Start));
2079 assert_eq!(parse_layout_justify_self("flex-start"), Ok(LayoutJustifySelf::Start));
2080 assert_eq!(parse_layout_justify_self("end"), Ok(LayoutJustifySelf::End));
2081 assert_eq!(parse_layout_justify_self("flex-end"), Ok(LayoutJustifySelf::End));
2082 assert_eq!(parse_layout_justify_self("center"), Ok(LayoutJustifySelf::Center));
2083 assert_eq!(parse_layout_justify_self("stretch"), Ok(LayoutJustifySelf::Stretch));
2084 assert_eq!(parse_layout_justify_self(" center "), Ok(LayoutJustifySelf::Center));
2085 }
2086
2087 #[test]
2088 fn parse_layout_justify_self_rejects_everything_else() {
2089 for bad in [
2090 "", " ", "Start", "START", "space-between", "normal", "left", "right",
2091 "\u{1F600}", "\0", "start end", "flex-start;",
2092 ] {
2093 assert!(parse_layout_justify_self(bad).is_err(), "{bad:?}");
2094 }
2095 let err = parse_layout_justify_self(" bogus ").unwrap_err();
2096 assert_eq!(err, JustifySelfParseError::InvalidValue(" bogus "));
2097 assert_eq!(format!("{err}"), "Invalid justify-self value: \" bogus \"");
2098 assert!(parse_layout_justify_self(&"x".repeat(1_000_000)).is_err());
2099 }
2100
2101 #[test]
2102 fn parse_layout_justify_items_has_no_auto_and_no_flex_aliases() {
2103 assert_eq!(parse_layout_justify_items("start"), Ok(LayoutJustifyItems::Start));
2104 assert_eq!(parse_layout_justify_items("end"), Ok(LayoutJustifyItems::End));
2105 assert_eq!(parse_layout_justify_items("center"), Ok(LayoutJustifyItems::Center));
2106 assert_eq!(parse_layout_justify_items("stretch"), Ok(LayoutJustifyItems::Stretch));
2107
2108 assert!(parse_layout_justify_items("auto").is_err());
2113 assert!(parse_layout_justify_self("auto").is_ok());
2114 assert!(parse_layout_justify_items("flex-start").is_err());
2115 assert!(parse_layout_justify_self("flex-start").is_ok());
2116
2117 for bad in ["", " ", "Start", "\u{1F600}", "\0", "stretch stretch"] {
2118 assert!(parse_layout_justify_items(bad).is_err(), "{bad:?}");
2119 }
2120 let err = parse_layout_justify_items(" bogus ").unwrap_err();
2121 assert_eq!(err, JustifyItemsParseError::InvalidValue(" bogus "));
2122 assert_eq!(format!("{err}"), "Invalid justify-items value: \" bogus \"");
2123 assert!(parse_layout_justify_items(&"x".repeat(1_000_000)).is_err());
2124 }
2125
2126 #[test]
2131 fn parse_layout_gap_valid_minimal_and_units() {
2132 assert_eq!(parse_layout_gap("10px").unwrap().inner, PixelValue::px(10.0));
2133 assert_eq!(parse_layout_gap(" 10px ").unwrap().inner, PixelValue::px(10.0));
2134 assert_eq!(parse_layout_gap("1.5em").unwrap().inner, PixelValue::em(1.5));
2135 assert_eq!(parse_layout_gap("50%").unwrap().inner, PixelValue::percent(50.0));
2136 assert_eq!(parse_layout_gap("0").unwrap().inner, PixelValue::px(0.0));
2137 assert_eq!(parse_layout_gap("-0").unwrap().inner, PixelValue::px(0.0));
2138 }
2139
2140 #[test]
2141 fn parse_layout_gap_empty_and_garbage_are_err() {
2142 assert!(matches!(
2143 parse_layout_gap("").unwrap_err(),
2144 crate::props::basic::pixel::CssPixelValueParseError::EmptyString
2145 ));
2146 assert!(matches!(
2147 parse_layout_gap(" ").unwrap_err(),
2148 crate::props::basic::pixel::CssPixelValueParseError::EmptyString
2149 ));
2150 for bad in ["px", "10pxpx", "!!!", "\u{1F600}", "\0", "auto", "normal"] {
2151 assert!(parse_layout_gap(bad).is_err(), "{bad:?}");
2152 }
2153 }
2154
2155 #[test]
2156 fn parse_layout_gap_accepts_negative_unitless_and_split_units_is_lax() {
2157 assert_eq!(parse_layout_gap("-20px").unwrap().inner, PixelValue::px(-20.0));
2160 assert_eq!(parse_layout_gap("10").unwrap().inner, PixelValue::px(10.0));
2161
2162 assert_eq!(parse_layout_gap("10 px").unwrap().inner, PixelValue::px(10.0));
2166 assert_eq!(parse_layout_gap("10\tpx").unwrap().inner, PixelValue::px(10.0));
2167 assert_eq!(parse_grid_track_owned("1 fr"), Ok(GridTrackSizing::Fr(100)));
2170 assert!(parse_grid_template("1 fr").is_err(), "...but not via the tokenizer");
2171 }
2172
2173 #[test]
2174 fn parse_layout_gap_nan_and_inf_do_not_panic_but_are_swallowed() {
2175 assert_eq!(
2179 parse_layout_gap("NaN").unwrap().inner,
2180 PixelValue::px(0.0),
2181 "NaN silently coerces to 0px"
2182 );
2183 assert_eq!(
2184 parse_layout_gap("inf").unwrap().inner,
2185 PixelValue::px(f32::INFINITY),
2186 "inf saturates instead of erroring"
2187 );
2188 assert_eq!(
2189 parse_layout_gap("-inf").unwrap().inner,
2190 PixelValue::px(f32::NEG_INFINITY)
2191 );
2192 assert_eq!(
2193 parse_layout_gap("1e40px").unwrap().inner,
2194 PixelValue::px(f32::INFINITY),
2195 "an f32 overflow saturates rather than wrapping"
2196 );
2197 assert!(parse_layout_gap(&format!("{}px", "9".repeat(10_000))).is_ok());
2199 }
2200
2201 fn area_named<'a>(areas: &'a GridTemplateAreas, name: &str) -> &'a GridAreaDefinition {
2206 areas
2207 .areas
2208 .as_ref()
2209 .iter()
2210 .find(|a| a.name.as_str() == name)
2211 .unwrap_or_else(|| panic!("no area named {name:?}"))
2212 }
2213
2214 #[test]
2215 fn parse_grid_template_areas_valid_minimal() {
2216 let parsed = parse_grid_template_areas("\"a\"").unwrap();
2217 assert_eq!(parsed.areas.len(), 1);
2218 assert_eq!(
2219 parsed.areas.as_ref()[0],
2220 GridAreaDefinition {
2221 name: "a".to_string().into(),
2222 row_start: 1,
2223 row_end: 2,
2224 column_start: 1,
2225 column_end: 2,
2226 },
2227 "1-based, end line is one past the last cell"
2228 );
2229
2230 assert_eq!(
2231 parse_grid_template_areas("none").unwrap(),
2232 GridTemplateAreas::default()
2233 );
2234 assert_eq!(
2235 parse_grid_template_areas(" none ").unwrap(),
2236 GridTemplateAreas::default()
2237 );
2238 }
2239
2240 #[test]
2241 fn parse_grid_template_areas_computes_bounds_and_sorts_by_name() {
2242 let parsed =
2243 parse_grid_template_areas("\"header header\" \"sidebar main\" \"footer footer\"")
2244 .unwrap();
2245 assert_eq!(parsed.areas.len(), 4);
2246
2247 let names: Vec<&str> = parsed.areas.as_ref().iter().map(|a| a.name.as_str()).collect();
2249 assert_eq!(names, vec!["footer", "header", "main", "sidebar"]);
2250
2251 let header = area_named(&parsed, "header");
2252 assert_eq!((header.row_start, header.row_end), (1, 2));
2253 assert_eq!((header.column_start, header.column_end), (1, 3), "spans both columns");
2254
2255 let sidebar = area_named(&parsed, "sidebar");
2256 assert_eq!((sidebar.row_start, sidebar.row_end), (2, 3));
2257 assert_eq!((sidebar.column_start, sidebar.column_end), (1, 2));
2258
2259 let footer = area_named(&parsed, "footer");
2260 assert_eq!((footer.row_start, footer.row_end), (3, 4));
2261 assert_eq!((footer.column_start, footer.column_end), (1, 3));
2262 }
2263
2264 #[test]
2265 fn parse_grid_template_areas_null_cells_are_skipped() {
2266 let parsed = parse_grid_template_areas("\". a\" \". a\"").unwrap();
2267 assert_eq!(parsed.areas.len(), 1, "'.' never becomes an area");
2268 let a = area_named(&parsed, "a");
2269 assert_eq!((a.row_start, a.row_end), (1, 3));
2270 assert_eq!((a.column_start, a.column_end), (2, 3));
2271
2272 let all_null = parse_grid_template_areas("\". .\" \". .\"").unwrap();
2274 assert_eq!(all_null, GridTemplateAreas::default());
2275 }
2276
2277 #[test]
2278 fn parse_grid_template_areas_rejects_empty_unterminated_and_ragged() {
2279 for bad in [
2280 "", " ",
2282 "\t\n",
2283 "\"\"", "\" \"", "\"abc", "'abc",
2287 "\"a\" \"b", "\"a b\" \"c\"", "\"a\" \"b c\"",
2290 "abc", "\u{1F600}",
2292 ] {
2293 assert_eq!(parse_grid_template_areas(bad), Err(()), "{bad:?} must be rejected");
2294 }
2295 }
2296
2297 #[test]
2298 fn parse_grid_template_areas_ignores_junk_outside_the_quotes_is_lax() {
2299 let parsed = parse_grid_template_areas("garbage \"a\" ;;; \u{1F600}").unwrap();
2302 assert_eq!(parsed.areas.len(), 1);
2303 assert_eq!(area_named(&parsed, "a").row_start, 1);
2304
2305 let mixed = parse_grid_template_areas("'a' \"b\"").unwrap();
2307 assert_eq!(mixed.areas.len(), 2);
2308 let weird = parse_grid_template_areas("'a\"b'").unwrap();
2310 assert_eq!(weird.areas.as_ref()[0].name.as_str(), "a\"b");
2311 }
2312
2313 #[test]
2314 fn parse_grid_template_areas_uses_tabs_and_newlines_as_cell_separators() {
2315 let parsed = parse_grid_template_areas("\"a\tb\" \"c\nd\"").unwrap();
2318 assert_eq!(parsed.areas.len(), 4);
2319 for name in ["a", "b", "c", "d"] {
2320 assert_eq!(area_named(&parsed, name).name.as_str(), name);
2321 }
2322 }
2323
2324 #[test]
2325 fn parse_grid_template_areas_non_rectangular_becomes_a_bounding_box() {
2326 let parsed = parse_grid_template_areas("\"b a b\"").unwrap();
2331 let b = area_named(&parsed, "b");
2332 let a = area_named(&parsed, "a");
2333 assert_eq!((b.column_start, b.column_end), (1, 4), "bounding box, not a rectangle");
2334 assert_eq!((a.column_start, a.column_end), (2, 3));
2335 assert!(
2336 a.column_start >= b.column_start && a.column_end <= b.column_end,
2337 "the two areas overlap, which taffy is never supposed to be handed"
2338 );
2339 }
2340
2341 #[test]
2342 fn parse_grid_template_areas_row_bounds_saturate_past_u16_max() {
2343 let mut input = String::with_capacity(65_600 * 4);
2346 for _ in 0..65_599 {
2347 input.push_str("\"a\" ");
2348 }
2349 input.push_str("\"b\"");
2350
2351 let parsed = parse_grid_template_areas(&input).unwrap();
2352 assert_eq!(parsed.areas.len(), 2);
2353
2354 let a = area_named(&parsed, "a");
2355 assert_eq!(a.row_start, 1);
2356 assert_eq!(
2357 a.row_end,
2358 u16::MAX,
2359 "true end line is 65_600; it saturates and the grid is silently truncated"
2360 );
2361
2362 let b = area_named(&parsed, "b");
2366 assert_eq!(b.row_start, u16::MAX);
2367 assert_eq!(b.row_end, u16::MAX);
2368 assert_eq!(b.row_start, b.row_end, "zero-height area from double saturation");
2369 }
2370
2371 #[test]
2372 fn parse_grid_template_areas_column_bounds_saturate_past_u16_max() {
2373 let mut row = String::with_capacity(70_000 * 2 + 2);
2374 row.push('"');
2375 for _ in 0..70_000 {
2376 row.push_str("a ");
2377 }
2378 row.push('"');
2379
2380 let parsed = parse_grid_template_areas(&row).unwrap();
2381 assert_eq!(parsed.areas.len(), 1);
2382 let a = area_named(&parsed, "a");
2383 assert_eq!(a.column_start, 1);
2384 assert_eq!(a.column_end, u16::MAX, "true end line is 70_001; clamped, not wrapped");
2385 assert_eq!((a.row_start, a.row_end), (1, 2));
2386 }
2387
2388 #[test]
2389 fn parse_grid_template_areas_invariant_end_line_exceeds_start_line() {
2390 for input in [
2394 "\"a\"",
2395 "\"a b\" \"c d\"",
2396 "\"h h h\" \"s m a\" \"f f f\"",
2397 "\". a .\" \"b a c\"",
2398 "'x'",
2399 ] {
2400 let parsed = parse_grid_template_areas(input).unwrap();
2401 for area in parsed.areas.as_ref() {
2402 assert!(area.row_start >= 1, "{input:?}: row_start must be 1-based");
2403 assert!(area.column_start >= 1, "{input:?}: column_start must be 1-based");
2404 assert!(area.row_end > area.row_start, "{input:?}: {area:?}");
2405 assert!(area.column_end > area.column_start, "{input:?}: {area:?}");
2406 }
2407 }
2408 }
2409
2410 #[test]
2415 fn roundtrip_grid_auto_flow_all_variants() {
2416 for value in [
2417 LayoutGridAutoFlow::Row,
2418 LayoutGridAutoFlow::Column,
2419 LayoutGridAutoFlow::RowDense,
2420 LayoutGridAutoFlow::ColumnDense,
2421 ] {
2422 let printed = value.print_as_css_value();
2423 assert_eq!(
2424 parse_layout_grid_auto_flow(&printed),
2425 Ok(value),
2426 "{value:?} printed as {printed:?} must re-parse to itself"
2427 );
2428 }
2429 }
2430
2431 #[test]
2432 fn roundtrip_justify_self_and_justify_items_all_variants() {
2433 for value in [
2434 LayoutJustifySelf::Auto,
2435 LayoutJustifySelf::Start,
2436 LayoutJustifySelf::End,
2437 LayoutJustifySelf::Center,
2438 LayoutJustifySelf::Stretch,
2439 ] {
2440 let printed = value.print_as_css_value();
2441 assert_eq!(parse_layout_justify_self(&printed), Ok(value), "{value:?}");
2442 }
2443 for value in [
2444 LayoutJustifyItems::Start,
2445 LayoutJustifyItems::End,
2446 LayoutJustifyItems::Center,
2447 LayoutJustifyItems::Stretch,
2448 ] {
2449 let printed = value.print_as_css_value();
2450 assert_eq!(parse_layout_justify_items(&printed), Ok(value), "{value:?}");
2451 }
2452 }
2453
2454 #[test]
2455 fn roundtrip_gap_and_fixed_tracks_are_stable() {
2456 for input in ["0px", "10px", "-20px", "1.5em", "2rem", "50%", "10mm", "1in"] {
2457 let gap = parse_layout_gap(input).unwrap();
2458 let printed = gap.print_as_css_value();
2459 assert_eq!(parse_layout_gap(&printed).unwrap(), gap, "gap {input:?} -> {printed:?}");
2460 }
2461 for input in ["auto", "min-content", "max-content", "100px", "25%", "fit-content(2px)"] {
2462 let track = parse_grid_track_owned(input).unwrap();
2463 let printed = track.print_as_css_value();
2464 assert_eq!(
2465 parse_grid_track_owned(&printed),
2466 Ok(track.clone()),
2467 "track {input:?} -> {printed:?}"
2468 );
2469 }
2470 }
2471
2472 #[test]
2473 fn roundtrip_fr_track_inflates_by_100x_every_cycle() {
2474 let once = parse_grid_track_owned("1fr").unwrap();
2481 assert_eq!(once, GridTrackSizing::Fr(100));
2482 assert_eq!(once.print_as_css_value(), "100fr", "should be \"1fr\"");
2483
2484 let twice = parse_grid_track_owned(&once.print_as_css_value()).unwrap();
2485 assert_eq!(twice, GridTrackSizing::Fr(10_000), "100x inflation per cycle");
2486
2487 let thrice = parse_grid_track_owned(&twice.print_as_css_value()).unwrap();
2488 assert_eq!(thrice, GridTrackSizing::Fr(1_000_000));
2489
2490 let four = parse_grid_track_owned(&thrice.print_as_css_value()).unwrap();
2492 assert_eq!(four, GridTrackSizing::Fr(100_000_000));
2493 assert_eq!(
2494 parse_grid_track_owned(&four.print_as_css_value()),
2495 Err(()),
2496 "the fifth cycle exceeds i32 and the declaration is discarded"
2497 );
2498
2499 assert_eq!(
2501 parse_grid_track_owned("minmax(100px, 1fr)").unwrap().print_as_css_value(),
2502 "minmax(100px, 100fr)"
2503 );
2504 assert_eq!(
2505 parse_grid_template("1fr 2fr").unwrap().print_as_css_value(),
2506 "100fr 200fr"
2507 );
2508 }
2509
2510 #[test]
2511 fn roundtrip_grid_template_none_and_repeat_expansion() {
2512 assert_eq!(GridTemplate::default().print_as_css_value(), "none");
2513 assert_eq!(
2514 parse_grid_template(&GridTemplate::default().print_as_css_value()).unwrap(),
2515 GridTemplate::default()
2516 );
2517
2518 let expanded = parse_grid_template("repeat(3, 100px)").unwrap();
2520 assert_eq!(expanded.print_as_css_value(), "100px 100px 100px");
2521 assert_eq!(parse_grid_template(&expanded.print_as_css_value()).unwrap(), expanded);
2522 }
2523
2524 #[test]
2525 fn roundtrip_grid_auto_tracks_empty_prints_auto_but_reparses_to_one_track() {
2526 let empty = GridAutoTracks::default();
2531 assert_eq!(empty.tracks.len(), 0);
2532 assert_eq!(empty.print_as_css_value(), "auto");
2533
2534 let reparsed: GridAutoTracks = parse_grid_template(&empty.print_as_css_value())
2535 .unwrap()
2536 .into();
2537 assert_eq!(reparsed.tracks.len(), 1);
2538 assert_eq!(reparsed.tracks.as_ref()[0], GridTrackSizing::Auto);
2539 assert_ne!(reparsed, empty);
2540 }
2541
2542 #[test]
2543 fn roundtrip_grid_placement_hides_a_trailing_auto_end() {
2544 for input in ["auto", "1", "-1", "span 2", "1 / 3", "1 / span 2", "auto / 3"] {
2547 let placement = parse_grid_placement(input).unwrap();
2548 let printed = placement.print_as_css_value();
2549 assert_eq!(
2550 parse_grid_placement(&printed).unwrap(),
2551 placement,
2552 "{input:?} printed as {printed:?}"
2553 );
2554 }
2555 assert_eq!(
2556 GridPlacement { grid_start: GridLine::Line(1), grid_end: GridLine::Auto }
2557 .print_as_css_value(),
2558 "1"
2559 );
2560 assert_eq!(
2561 GridPlacement { grid_start: GridLine::Auto, grid_end: GridLine::Line(3) }
2562 .print_as_css_value(),
2563 "auto / 3"
2564 );
2565 }
2566
2567 #[test]
2568 fn roundtrip_named_grid_line_with_a_span_is_lossy() {
2569 let spanned = GridLine::Named(NamedGridLine::create("header".to_string().into(), Some(2)));
2576 assert_eq!(spanned.print_as_css_value(), "header 2");
2577
2578 let reparsed = parse_grid_line_owned(&spanned.print_as_css_value()).unwrap();
2579 assert_eq!(
2580 reparsed,
2581 GridLine::Named(NamedGridLine::create("header 2".to_string().into(), None))
2582 );
2583 assert_ne!(reparsed, spanned);
2584
2585 let plain = GridLine::Named(NamedGridLine::create("header".to_string().into(), None));
2587 assert_eq!(plain.print_as_css_value(), "header");
2588 assert_eq!(parse_grid_line_owned(&plain.print_as_css_value()), Ok(plain));
2589 }
2590
2591 #[test]
2592 fn roundtrip_grid_template_areas_rectangular_is_stable() {
2593 for input in [
2594 "\"a\"",
2595 "\"a a\"",
2596 "\"h h\" \"s m\"",
2597 "\"a a\" \"a a\"",
2598 "\". a\" \". a\"",
2599 ] {
2600 let parsed = parse_grid_template_areas(input).unwrap();
2601 let printed = parsed.print_as_css_value();
2602 assert_eq!(
2603 parse_grid_template_areas(&printed).unwrap(),
2604 parsed,
2605 "{input:?} printed as {printed:?}"
2606 );
2607 }
2608 assert_eq!(GridTemplateAreas::default().print_as_css_value(), "none");
2609 assert_eq!(
2610 parse_grid_template_areas(&GridTemplateAreas::default().print_as_css_value()).unwrap(),
2611 GridTemplateAreas::default()
2612 );
2613 }
2614
2615 #[test]
2616 fn roundtrip_grid_template_areas_non_rectangular_loses_a_whole_area() {
2617 let parsed = parse_grid_template_areas("\"b a b\"").unwrap();
2622 assert_eq!(parsed.areas.len(), 2);
2623 assert_eq!(parsed.print_as_css_value(), "\"b b b\"", "the `a` area is erased");
2624
2625 let reparsed = parse_grid_template_areas(&parsed.print_as_css_value()).unwrap();
2626 assert_eq!(reparsed.areas.len(), 1);
2627 assert_ne!(reparsed, parsed);
2628 }
2629
2630 #[test]
2635 fn grid_parse_error_to_contained_and_back_is_identity() {
2636 for payload in ["", " ", "bogus", "\u{1F600}\u{0301}", "\0", "a\nb\tc"] {
2637 let shared = GridParseError::InvalidValue(payload);
2638 let owned = shared.to_contained();
2639 assert_eq!(owned, GridParseErrorOwned::InvalidValue(payload.to_string().into()));
2640 assert_eq!(owned.to_shared(), shared, "{payload:?} must survive the round-trip");
2641 }
2642 let huge = "x".repeat(100_000);
2644 let owned = GridParseError::InvalidValue(&huge).to_contained();
2645 assert_eq!(owned.to_shared(), GridParseError::InvalidValue(huge.as_str()));
2646 }
2647
2648 #[test]
2649 fn grid_auto_flow_parse_error_to_contained_and_back_is_identity() {
2650 for payload in ["", "bogus", "\u{1F600}", "\0"] {
2651 let shared = GridAutoFlowParseError::InvalidValue(payload);
2652 let owned = shared.to_contained();
2653 assert_eq!(
2654 owned,
2655 GridAutoFlowParseErrorOwned::InvalidValue(payload.to_string().into())
2656 );
2657 assert_eq!(owned.to_shared(), shared);
2658 }
2659 let huge = "x".repeat(100_000);
2660 let owned = GridAutoFlowParseError::InvalidValue(&huge).to_contained();
2661 assert_eq!(owned.to_shared(), GridAutoFlowParseError::InvalidValue(huge.as_str()));
2662 }
2663
2664 #[test]
2665 fn justify_self_parse_error_to_contained_and_back_is_identity() {
2666 for payload in ["", "bogus", "\u{1F600}", "\0"] {
2667 let shared = JustifySelfParseError::InvalidValue(payload);
2668 let owned = shared.to_contained();
2669 assert_eq!(
2670 owned,
2671 JustifySelfParseErrorOwned::InvalidValue(payload.to_string().into())
2672 );
2673 assert_eq!(owned.to_shared(), shared);
2674 }
2675 let huge = "x".repeat(100_000);
2676 let owned = JustifySelfParseError::InvalidValue(&huge).to_contained();
2677 assert_eq!(owned.to_shared(), JustifySelfParseError::InvalidValue(huge.as_str()));
2678 }
2679
2680 #[test]
2681 fn justify_items_parse_error_to_contained_and_back_is_identity() {
2682 for payload in ["", "bogus", "\u{1F600}", "\0"] {
2683 let shared = JustifyItemsParseError::InvalidValue(payload);
2684 let owned = shared.to_contained();
2685 assert_eq!(
2686 owned,
2687 JustifyItemsParseErrorOwned::InvalidValue(payload.to_string().into())
2688 );
2689 assert_eq!(owned.to_shared(), shared);
2690 }
2691 let huge = "x".repeat(100_000);
2692 let owned = JustifyItemsParseError::InvalidValue(&huge).to_contained();
2693 assert_eq!(owned.to_shared(), JustifyItemsParseError::InvalidValue(huge.as_str()));
2694 }
2695
2696 #[test]
2697 fn error_types_round_trip_through_a_real_parse_failure() {
2698 let input = String::from(" span x ");
2700 let owned = parse_grid_placement(&input).unwrap_err().to_contained();
2701 drop(input); assert_eq!(owned.to_shared(), GridParseError::InvalidValue("span x"));
2703
2704 let flow = String::from("bogus-flow");
2705 let owned_flow = parse_layout_grid_auto_flow(&flow).unwrap_err().to_contained();
2706 drop(flow);
2707 assert_eq!(
2708 owned_flow.to_shared(),
2709 GridAutoFlowParseError::InvalidValue("bogus-flow")
2710 );
2711 }
2712
2713 #[test]
2718 fn debug_impls_match_print_as_css_value() {
2719 let track = parse_grid_track_owned("minmax(100px, max-content)").unwrap();
2722 assert_eq!(format!("{track:?}"), track.print_as_css_value());
2723
2724 let template = parse_grid_template("100px auto").unwrap();
2725 assert_eq!(format!("{template:?}"), template.print_as_css_value());
2726
2727 let placement = parse_grid_placement("1 / span 2").unwrap();
2728 assert_eq!(format!("{placement:?}"), placement.print_as_css_value());
2729
2730 let minmax = GridMinMax {
2731 min: Box::new(GridTrackSizing::Fixed(PixelValue::px(1.0))),
2732 max: Box::new(GridTrackSizing::Auto),
2733 };
2734 assert_eq!(format!("{minmax:?}"), "minmax(1px, auto)");
2735 }
2736}