1use alloc::{
4 boxed::Box,
5 string::{String, ToString},
6 vec::Vec,
7};
8
9use crate::{
10 codegen::format::FormatAsRustCode,
11 corety::AzString,
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
74impl PrintAsCssValue for GridTrackSizing {
75 fn print_as_css_value(&self) -> String {
76 match self {
77 Self::Fixed(px) => px.print_as_css_value(),
78 Self::Fr(f) => format!("{f}fr"),
79 Self::MinContent => "min-content".to_string(),
80 Self::MaxContent => "max-content".to_string(),
81 Self::Auto => "auto".to_string(),
82 Self::MinMax(minmax) => {
83 format!(
84 "minmax({}, {})",
85 minmax.min.print_as_css_value(),
86 minmax.max.print_as_css_value()
87 )
88 }
89 Self::FitContent(size) => {
90 format!("fit-content({})", size.print_as_css_value())
91 }
92 }
93 }
94}
95
96impl_vec!(
98 GridTrackSizing,
99 GridTrackSizingVec,
100 GridTrackSizingVecDestructor,
101 GridTrackSizingVecDestructorType,
102 GridTrackSizingVecSlice,
103 OptionGridTrackSizing
104);
105impl_vec_clone!(
106 GridTrackSizing,
107 GridTrackSizingVec,
108 GridTrackSizingVecDestructor
109);
110impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
111impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
112impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
113impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
114impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
115impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
116impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
117
118#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
120#[repr(C)]
121pub struct GridTemplate {
122 pub tracks: GridTrackSizingVec,
123}
124
125impl core::fmt::Debug for GridTemplate {
126 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
127 write!(f, "{}", self.print_as_css_value())
128 }
129}
130
131impl Default for GridTemplate {
132 fn default() -> Self {
133 Self {
134 tracks: GridTrackSizingVec::from_vec(Vec::new()),
135 }
136 }
137}
138
139impl PrintAsCssValue for GridTemplate {
140 fn print_as_css_value(&self) -> String {
141 let tracks_slice = self.tracks.as_ref();
142 if tracks_slice.is_empty() {
143 "none".to_string()
144 } else {
145 tracks_slice
146 .iter()
147 .map(PrintAsCssValue::print_as_css_value)
148 .collect::<Vec<_>>()
149 .join(" ")
150 }
151 }
152}
153
154#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
159#[repr(C)]
160pub struct GridAutoTracks {
161 pub tracks: GridTrackSizingVec,
162}
163
164impl core::fmt::Debug for GridAutoTracks {
165 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166 write!(f, "{}", self.print_as_css_value())
167 }
168}
169
170impl Default for GridAutoTracks {
171 fn default() -> Self {
172 Self {
173 tracks: GridTrackSizingVec::from_vec(Vec::new()),
174 }
175 }
176}
177
178impl PrintAsCssValue for GridAutoTracks {
179 fn print_as_css_value(&self) -> String {
180 let tracks_slice = self.tracks.as_ref();
181 if tracks_slice.is_empty() {
182 "auto".to_string()
183 } else {
184 tracks_slice
185 .iter()
186 .map(PrintAsCssValue::print_as_css_value)
187 .collect::<Vec<_>>()
188 .join(" ")
189 }
190 }
191}
192
193impl From<GridTemplate> for GridAutoTracks {
194 fn from(template: GridTemplate) -> Self {
195 Self {
196 tracks: template.tracks,
197 }
198 }
199}
200
201#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
205#[repr(C)]
206pub struct NamedGridLine {
207 pub grid_line_name: AzString,
208 pub span_count: i32,
210}
211
212impl NamedGridLine {
213 #[must_use]
214 pub fn create(name: AzString, span: Option<i32>) -> Self {
215 Self {
216 grid_line_name: name,
217 span_count: span.unwrap_or(0),
218 }
219 }
220
221 #[must_use]
222 pub const fn span(&self) -> Option<i32> {
223 if self.span_count == 0 {
224 None
225 } else {
226 Some(self.span_count)
227 }
228 }
229}
230#[allow(variant_size_differences)]
231#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
234#[repr(C, u8)]
235#[derive(Default)]
236pub enum GridLine {
237 #[default]
239 Auto,
240 Line(i32),
242 Named(NamedGridLine),
244 Span(i32),
246}
247
248impl core::fmt::Debug for GridLine {
249 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
250 write!(f, "{}", self.print_as_css_value())
251 }
252}
253
254impl PrintAsCssValue for GridLine {
255 fn print_as_css_value(&self) -> String {
256 match self {
257 Self::Auto => "auto".to_string(),
258 Self::Line(n) => n.to_string(),
259 Self::Named(named) => {
260 if named.span_count == 0 {
261 named.grid_line_name.as_str().to_string()
262 } else {
263 format!("{} {}", named.grid_line_name.as_str(), named.span_count)
264 }
265 }
266 Self::Span(n) => format!("span {n}"),
267 }
268 }
269}
270
271#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
273#[repr(C)]
274pub struct GridPlacement {
275 pub grid_start: GridLine,
276 pub grid_end: GridLine,
277}
278
279impl core::fmt::Debug for GridPlacement {
280 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
281 write!(f, "{}", self.print_as_css_value())
282 }
283}
284
285impl Default for GridPlacement {
286 fn default() -> Self {
287 Self {
288 grid_start: GridLine::Auto,
289 grid_end: GridLine::Auto,
290 }
291 }
292}
293
294impl PrintAsCssValue for GridPlacement {
295 fn print_as_css_value(&self) -> String {
296 if self.grid_end == GridLine::Auto {
297 self.grid_start.print_as_css_value()
298 } else {
299 format!(
300 "{} / {}",
301 self.grid_start.print_as_css_value(),
302 self.grid_end.print_as_css_value()
303 )
304 }
305 }
306}
307
308#[cfg(feature = "parser")]
309#[derive(Clone, PartialEq, Eq)]
310pub enum GridParseError<'a> {
311 InvalidValue(&'a str),
312}
313
314#[cfg(feature = "parser")]
315impl_debug_as_display!(GridParseError<'a>);
316#[cfg(feature = "parser")]
317impl_display! { GridParseError<'a>, {
318 InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
319}}
320
321#[cfg(feature = "parser")]
322#[derive(Debug, Clone, PartialEq, Eq)]
323#[repr(C, u8)]
324pub enum GridParseErrorOwned {
325 InvalidValue(AzString),
326}
327
328#[cfg(feature = "parser")]
329impl GridParseError<'_> {
330 #[must_use]
331 pub fn to_contained(&self) -> GridParseErrorOwned {
332 match self {
333 GridParseError::InvalidValue(s) => {
334 GridParseErrorOwned::InvalidValue((*s).to_string().into())
335 }
336 }
337 }
338}
339
340#[cfg(feature = "parser")]
341impl GridParseErrorOwned {
342 #[must_use]
343 pub fn to_shared(&self) -> GridParseError<'_> {
344 match self {
345 Self::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
346 }
347 }
348}
349
350#[cfg(feature = "parser")]
351fn split_respecting_parens(input: &str) -> Result<Vec<String>, ()> {
352 let mut parts = Vec::new();
353 let mut current = String::new();
354 let mut paren_depth: i32 = 0;
355
356 for ch in input.chars() {
357 match ch {
358 '(' => {
359 paren_depth += 1;
360 current.push(ch);
361 }
362 ')' => {
363 paren_depth -= 1;
364 if paren_depth < 0 {
365 return Err(());
366 }
367 current.push(ch);
368 }
369 ' ' if paren_depth == 0 => {
370 if !current.trim().is_empty() {
371 parts.push(current.trim().to_string());
372 current.clear();
373 }
374 }
375 _ => current.push(ch),
376 }
377 }
378 if !current.trim().is_empty() {
379 parts.push(current.trim().to_string());
380 }
381 Ok(parts)
382}
383
384#[cfg(feature = "parser")]
385pub fn parse_grid_template(input: &str) -> Result<GridTemplate, GridParseError<'_>> {
389 use crate::props::basic::pixel::parse_pixel_value;
390
391 let input = input.trim();
392
393 if input == "none" {
394 return Ok(GridTemplate::default());
395 }
396
397 let parts = split_respecting_parens(input).map_err(|()| GridParseError::InvalidValue(input))?;
398
399 let mut tracks = Vec::new();
400 for part in &parts {
401 parse_grid_track_or_repeat(part, &mut tracks)
402 .map_err(|()| GridParseError::InvalidValue(input))?;
403 }
404
405 Ok(GridTemplate {
406 tracks: GridTrackSizingVec::from_vec(tracks),
407 })
408}
409
410#[cfg(feature = "parser")]
413fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
414 const MAX_GRID_REPEAT_COUNT: usize = 10_000;
416 let input = input.trim();
417
418 if input.starts_with("repeat(") && input.ends_with(')') {
420 let content = &input[7..input.len() - 1];
421 let comma_pos = content.find(',').ok_or(())?;
423 let count_str = content[..comma_pos].trim();
424 let track_list_str = content[comma_pos + 1..].trim();
425
426 let count: usize = count_str.parse().map_err(|_| ())?;
427 if count == 0 || count > MAX_GRID_REPEAT_COUNT {
428 return Err(());
429 }
430
431 let parts = split_respecting_parens(track_list_str)?;
433 let repeat_tracks: Vec<GridTrackSizing> = parts
434 .iter()
435 .map(|p| parse_grid_track_owned(p))
436 .collect::<Result<Vec<_>, _>>()?;
437
438 for _ in 0..count {
440 tracks.extend(repeat_tracks.iter().cloned());
441 }
442 return Ok(());
443 }
444
445 tracks.push(parse_grid_track_owned(input)?);
447 Ok(())
448}
449
450#[cfg(feature = "parser")]
451fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
452 use crate::props::basic::pixel::parse_pixel_value;
453
454 let input = input.trim();
455
456 if input == "auto" {
457 return Ok(GridTrackSizing::Auto);
458 }
459
460 if input == "min-content" {
461 return Ok(GridTrackSizing::MinContent);
462 }
463
464 if input == "max-content" {
465 return Ok(GridTrackSizing::MaxContent);
466 }
467
468 if let Some(num_str) = input.strip_suffix("fr") {
469 const FR_SCALING_FACTOR: f32 = 100.0;
471 let num_str = num_str.trim();
472 if let Ok(num) = num_str.parse::<f32>() {
473 let scaled = num * FR_SCALING_FACTOR;
474 if scaled.is_nan()
475 || scaled < crate::cast::i32_to_f32(i32::MIN)
476 || scaled > crate::cast::i32_to_f32(i32::MAX)
477 {
478 return Err(());
479 }
480 return Ok(GridTrackSizing::Fr(crate::cast::f32_to_i32(scaled)));
481 }
482 return Err(());
483 }
484
485 if input.starts_with("minmax(") && input.ends_with(')') {
486 let content = &input[7..input.len() - 1];
487 let parts: Vec<&str> = content.split(',').collect();
488 if parts.len() == 2 {
489 let min = parse_grid_track_owned(parts[0].trim())?;
490 let max = parse_grid_track_owned(parts[1].trim())?;
491 return Ok(GridTrackSizing::MinMax(GridMinMax {
492 min: Box::new(min),
493 max: Box::new(max),
494 }));
495 }
496 return Err(());
497 }
498
499 if input.starts_with("fit-content(") && input.ends_with(')') {
500 let size_str = &input[12..input.len() - 1].trim();
501 if let Ok(size) = parse_pixel_value(size_str) {
502 return Ok(GridTrackSizing::FitContent(size));
503 }
504 return Err(());
505 }
506
507 if let Ok(px) = parse_pixel_value(input) {
509 return Ok(GridTrackSizing::Fixed(px));
510 }
511
512 Err(())
513}
514
515#[cfg(feature = "parser")]
516pub fn parse_grid_placement(input: &str) -> Result<GridPlacement, GridParseError<'_>> {
520 let input = input.trim();
521
522 if input == "auto" {
523 return Ok(GridPlacement::default());
524 }
525
526 let parts: Vec<&str> = input.split('/').map(str::trim).collect();
528
529 let grid_start =
530 parse_grid_line_owned(parts[0]).map_err(|()| GridParseError::InvalidValue(input))?;
531 let grid_end = if parts.len() > 1 {
532 parse_grid_line_owned(parts[1]).map_err(|()| GridParseError::InvalidValue(input))?
533 } else {
534 GridLine::Auto
535 };
536
537 Ok(GridPlacement {
538 grid_start,
539 grid_end,
540 })
541}
542
543#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
547#[repr(C)]
548#[derive(Default)]
549pub enum LayoutGridAutoFlow {
550 #[default]
551 Row,
552 Column,
553 RowDense,
554 ColumnDense,
555}
556
557impl PrintAsCssValue for LayoutGridAutoFlow {
558 fn print_as_css_value(&self) -> String {
559 match self {
560 Self::Row => "row".to_string(),
561 Self::Column => "column".to_string(),
562 Self::RowDense => "row dense".to_string(),
563 Self::ColumnDense => "column dense".to_string(),
564 }
565 }
566}
567
568#[cfg(feature = "parser")]
569#[derive(Clone, PartialEq, Eq)]
570pub enum GridAutoFlowParseError<'a> {
571 InvalidValue(&'a str),
572}
573
574#[cfg(feature = "parser")]
575impl_debug_as_display!(GridAutoFlowParseError<'a>);
576#[cfg(feature = "parser")]
577impl_display! { GridAutoFlowParseError<'a>, {
578 InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
579}}
580
581#[cfg(feature = "parser")]
582#[derive(Debug, Clone, PartialEq, Eq)]
583#[repr(C, u8)]
584pub enum GridAutoFlowParseErrorOwned {
585 InvalidValue(AzString),
586}
587
588#[cfg(feature = "parser")]
589impl GridAutoFlowParseError<'_> {
590 #[must_use]
591 pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
592 match self {
593 GridAutoFlowParseError::InvalidValue(s) => {
594 GridAutoFlowParseErrorOwned::InvalidValue((*s).to_string().into())
595 }
596 }
597 }
598}
599
600#[cfg(feature = "parser")]
601impl GridAutoFlowParseErrorOwned {
602 #[must_use]
603 pub fn to_shared(&self) -> GridAutoFlowParseError<'_> {
604 match self {
605 Self::InvalidValue(s) => GridAutoFlowParseError::InvalidValue(s.as_str()),
606 }
607 }
608}
609
610#[cfg(feature = "parser")]
611pub fn parse_layout_grid_auto_flow(
615 input: &str,
616) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'_>> {
617 match input.trim() {
618 "row" => Ok(LayoutGridAutoFlow::Row),
619 "column" => Ok(LayoutGridAutoFlow::Column),
620 "row dense" | "dense" => Ok(LayoutGridAutoFlow::RowDense),
621 "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
622 _ => Err(GridAutoFlowParseError::InvalidValue(input)),
623 }
624}
625
626#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
630#[repr(C)]
631#[derive(Default)]
632pub enum LayoutJustifySelf {
633 #[default]
634 Auto,
635 Start,
636 End,
637 Center,
638 Stretch,
639}
640
641impl PrintAsCssValue for LayoutJustifySelf {
642 fn print_as_css_value(&self) -> String {
643 match self {
644 Self::Auto => "auto".to_string(),
645 Self::Start => "start".to_string(),
646 Self::End => "end".to_string(),
647 Self::Center => "center".to_string(),
648 Self::Stretch => "stretch".to_string(),
649 }
650 }
651}
652
653#[cfg(feature = "parser")]
654#[derive(Clone, PartialEq, Eq)]
655pub enum JustifySelfParseError<'a> {
656 InvalidValue(&'a str),
657}
658
659#[cfg(feature = "parser")]
660#[derive(Debug, Clone, PartialEq, Eq)]
661#[repr(C, u8)]
662pub enum JustifySelfParseErrorOwned {
663 InvalidValue(AzString),
664}
665
666#[cfg(feature = "parser")]
667impl JustifySelfParseError<'_> {
668 #[must_use]
669 pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
670 match self {
671 JustifySelfParseError::InvalidValue(s) => {
672 JustifySelfParseErrorOwned::InvalidValue((*s).to_string().into())
673 }
674 }
675 }
676}
677
678#[cfg(feature = "parser")]
679impl JustifySelfParseErrorOwned {
680 #[must_use]
681 pub fn to_shared(&self) -> JustifySelfParseError<'_> {
682 match self {
683 Self::InvalidValue(s) => JustifySelfParseError::InvalidValue(s.as_str()),
684 }
685 }
686}
687
688#[cfg(feature = "parser")]
689impl_debug_as_display!(JustifySelfParseError<'a>);
690#[cfg(feature = "parser")]
691impl_display! { JustifySelfParseError<'a>, {
692 InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
693}}
694
695#[cfg(feature = "parser")]
696pub fn parse_layout_justify_self(
700 input: &str,
701) -> Result<LayoutJustifySelf, JustifySelfParseError<'_>> {
702 match input.trim() {
703 "auto" => Ok(LayoutJustifySelf::Auto),
704 "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
705 "end" | "flex-end" => Ok(LayoutJustifySelf::End),
706 "center" => Ok(LayoutJustifySelf::Center),
707 "stretch" => Ok(LayoutJustifySelf::Stretch),
708 _ => Err(JustifySelfParseError::InvalidValue(input)),
709 }
710}
711
712#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
714#[repr(C)]
715#[derive(Default)]
716pub enum LayoutJustifyItems {
717 Start,
718 End,
719 Center,
720 #[default]
721 Stretch,
722}
723
724impl PrintAsCssValue for LayoutJustifyItems {
725 fn print_as_css_value(&self) -> String {
726 match self {
727 Self::Start => "start".to_string(),
728 Self::End => "end".to_string(),
729 Self::Center => "center".to_string(),
730 Self::Stretch => "stretch".to_string(),
731 }
732 }
733}
734
735#[cfg(feature = "parser")]
736#[derive(Clone, PartialEq, Eq)]
737pub enum JustifyItemsParseError<'a> {
738 InvalidValue(&'a str),
739}
740
741#[cfg(feature = "parser")]
742#[derive(Debug, Clone, PartialEq, Eq)]
743#[repr(C, u8)]
744pub enum JustifyItemsParseErrorOwned {
745 InvalidValue(AzString),
746}
747
748#[cfg(feature = "parser")]
749impl JustifyItemsParseError<'_> {
750 #[must_use]
751 pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
752 match self {
753 JustifyItemsParseError::InvalidValue(s) => {
754 JustifyItemsParseErrorOwned::InvalidValue((*s).to_string().into())
755 }
756 }
757 }
758}
759
760#[cfg(feature = "parser")]
761impl JustifyItemsParseErrorOwned {
762 #[must_use]
763 pub fn to_shared(&self) -> JustifyItemsParseError<'_> {
764 match self {
765 Self::InvalidValue(s) => JustifyItemsParseError::InvalidValue(s.as_str()),
766 }
767 }
768}
769
770#[cfg(feature = "parser")]
771impl_debug_as_display!(JustifyItemsParseError<'a>);
772#[cfg(feature = "parser")]
773impl_display! { JustifyItemsParseError<'a>, {
774 InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
775}}
776
777#[cfg(feature = "parser")]
778pub fn parse_layout_justify_items(
782 input: &str,
783) -> Result<LayoutJustifyItems, JustifyItemsParseError<'_>> {
784 match input.trim() {
785 "start" => Ok(LayoutJustifyItems::Start),
786 "end" => Ok(LayoutJustifyItems::End),
787 "center" => Ok(LayoutJustifyItems::Center),
788 "stretch" => Ok(LayoutJustifyItems::Stretch),
789 _ => Err(JustifyItemsParseError::InvalidValue(input)),
790 }
791}
792
793#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
796#[repr(C)]
797pub struct LayoutGap {
798 pub inner: PixelValue,
799}
800
801impl core::fmt::Debug for LayoutGap {
802 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
803 write!(f, "{}", self.inner)
804 }
805}
806
807impl PrintAsCssValue for LayoutGap {
808 fn print_as_css_value(&self) -> String {
809 self.inner.print_as_css_value()
810 }
811}
812
813impl FormatAsRustCode for LayoutGridAutoFlow {
816 fn format_as_rust_code(&self, _tabs: usize) -> String {
817 format!(
818 "LayoutGridAutoFlow::{}",
819 match self {
820 Self::Row => "Row",
821 Self::Column => "Column",
822 Self::RowDense => "RowDense",
823 Self::ColumnDense => "ColumnDense",
824 }
825 )
826 }
827}
828
829impl FormatAsRustCode for LayoutJustifySelf {
830 fn format_as_rust_code(&self, _tabs: usize) -> String {
831 format!(
832 "LayoutJustifySelf::{}",
833 match self {
834 Self::Auto => "Auto",
835 Self::Start => "Start",
836 Self::End => "End",
837 Self::Center => "Center",
838 Self::Stretch => "Stretch",
839 }
840 )
841 }
842}
843
844impl FormatAsRustCode for LayoutJustifyItems {
845 fn format_as_rust_code(&self, _tabs: usize) -> String {
846 format!(
847 "LayoutJustifyItems::{}",
848 match self {
849 Self::Start => "Start",
850 Self::End => "End",
851 Self::Center => "Center",
852 Self::Stretch => "Stretch",
853 }
854 )
855 }
856}
857
858impl FormatAsRustCode for LayoutGap {
859 fn format_as_rust_code(&self, _tabs: usize) -> String {
860 use crate::codegen::format::format_pixel_value;
861 format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
862 }
863}
864
865impl FormatAsRustCode for GridTrackSizing {
866 #[allow(clippy::only_used_in_recursion)]
869 fn format_as_rust_code(&self, tabs: usize) -> String {
870 use crate::codegen::format::format_pixel_value;
871 match self {
872 Self::Fixed(pv) => {
873 format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
874 }
875 Self::Fr(f) => format!("GridTrackSizing::Fr({f})"),
876 Self::MinContent => "GridTrackSizing::MinContent".to_string(),
877 Self::MaxContent => "GridTrackSizing::MaxContent".to_string(),
878 Self::Auto => "GridTrackSizing::Auto".to_string(),
879 Self::MinMax(minmax) => {
880 format!(
881 "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
882 minmax.min.format_as_rust_code(tabs),
883 minmax.max.format_as_rust_code(tabs)
884 )
885 }
886 Self::FitContent(pv) => {
887 format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
888 }
889 }
890 }
891}
892
893impl FormatAsRustCode for GridAutoTracks {
894 fn format_as_rust_code(&self, tabs: usize) -> String {
895 let tracks: Vec<String> = self
896 .tracks
897 .as_ref()
898 .iter()
899 .map(|t| t.format_as_rust_code(tabs))
900 .collect();
901 format!(
902 "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
903 tracks.join(", ")
904 )
905 }
906}
907
908impl FormatAsRustCode for GridTemplateAreas {
909 fn format_as_rust_code(&self, _tabs: usize) -> String {
910 format!(
911 "GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}",
912 self.areas.as_ref()
913 )
914 }
915}
916
917#[cfg(feature = "parser")]
918pub fn parse_layout_gap(
922 input: &str,
923) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'_>> {
924 crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
925}
926
927#[cfg(feature = "parser")]
928pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
932 let input = input.trim();
933
934 if input == "auto" {
935 return Ok(GridLine::Auto);
936 }
937
938 if let Some(num_str) = input.strip_prefix("span ") {
939 let num_str = num_str.trim();
940 if let Ok(num) = num_str.parse::<i32>() {
941 return Ok(GridLine::Span(num));
942 }
943 return Err(());
944 }
945
946 if let Ok(num) = input.parse::<i32>() {
948 return Ok(GridLine::Line(num));
949 }
950
951 Ok(GridLine::Named(NamedGridLine::create(
953 input.to_string().into(),
954 None,
955 )))
956}
957
958#[cfg(all(test, feature = "parser"))]
959mod tests {
960 use super::*;
961
962 #[test]
964 fn test_parse_grid_template_none() {
965 let result = parse_grid_template("none").unwrap();
966 assert_eq!(result.tracks.len(), 0);
967 }
968
969 #[test]
970 fn test_parse_grid_template_single_px() {
971 let result = parse_grid_template("100px").unwrap();
972 assert_eq!(result.tracks.len(), 1);
973 assert!(matches!(
974 result.tracks.as_ref()[0],
975 GridTrackSizing::Fixed(_)
976 ));
977 }
978
979 #[test]
980 fn test_parse_grid_template_multiple_tracks() {
981 let result = parse_grid_template("100px 200px 1fr").unwrap();
982 assert_eq!(result.tracks.len(), 3);
983 }
984
985 #[test]
986 fn test_parse_grid_template_fr_units() {
987 let result = parse_grid_template("1fr 2fr 1fr").unwrap();
988 assert_eq!(result.tracks.len(), 3);
989 assert!(matches!(
990 result.tracks.as_ref()[0],
991 GridTrackSizing::Fr(100)
992 ));
993 assert!(matches!(
994 result.tracks.as_ref()[1],
995 GridTrackSizing::Fr(200)
996 ));
997 }
998
999 #[test]
1000 fn test_parse_grid_template_fractional_fr() {
1001 let result = parse_grid_template("0.5fr 1.5fr").unwrap();
1002 assert_eq!(result.tracks.len(), 2);
1003 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
1004 assert!(matches!(
1005 result.tracks.as_ref()[1],
1006 GridTrackSizing::Fr(150)
1007 ));
1008 }
1009
1010 #[test]
1011 fn test_parse_grid_template_auto() {
1012 let result = parse_grid_template("auto 100px auto").unwrap();
1013 assert_eq!(result.tracks.len(), 3);
1014 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
1015 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
1016 }
1017
1018 #[test]
1019 fn test_parse_grid_template_min_max_content() {
1020 let result = parse_grid_template("min-content max-content auto").unwrap();
1021 assert_eq!(result.tracks.len(), 3);
1022 assert!(matches!(
1023 result.tracks.as_ref()[0],
1024 GridTrackSizing::MinContent
1025 ));
1026 assert!(matches!(
1027 result.tracks.as_ref()[1],
1028 GridTrackSizing::MaxContent
1029 ));
1030 }
1031
1032 #[test]
1033 fn test_parse_grid_template_minmax() {
1034 let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
1035 assert_eq!(result.tracks.len(), 1);
1036 assert!(matches!(
1037 result.tracks.as_ref()[0],
1038 GridTrackSizing::MinMax(_)
1039 ));
1040 }
1041
1042 #[test]
1043 fn test_parse_grid_template_minmax_complex() {
1044 let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
1045 assert_eq!(result.tracks.len(), 1);
1046 }
1047
1048 #[test]
1049 fn test_parse_grid_template_fit_content() {
1050 let result = parse_grid_template("fit-content(200px)").unwrap();
1051 assert_eq!(result.tracks.len(), 1);
1052 assert!(matches!(
1053 result.tracks.as_ref()[0],
1054 GridTrackSizing::FitContent(_)
1055 ));
1056 }
1057
1058 #[test]
1059 fn test_parse_grid_template_mixed() {
1060 let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1061 assert_eq!(result.tracks.len(), 4);
1062 }
1063
1064 #[test]
1065 fn test_parse_grid_template_percent() {
1066 let result = parse_grid_template("25% 50% 25%").unwrap();
1067 assert_eq!(result.tracks.len(), 3);
1068 }
1069
1070 #[test]
1071 fn test_parse_grid_template_em_units() {
1072 let result = parse_grid_template("10em 20em 1fr").unwrap();
1073 assert_eq!(result.tracks.len(), 3);
1074 }
1075
1076 #[test]
1078 fn test_parse_grid_placement_auto() {
1079 let result = parse_grid_placement("auto").unwrap();
1080 assert!(matches!(result.grid_start, GridLine::Auto));
1081 assert!(matches!(result.grid_end, GridLine::Auto));
1082 }
1083
1084 #[test]
1085 fn test_parse_grid_placement_line_number() {
1086 let result = parse_grid_placement("1").unwrap();
1087 assert!(matches!(result.grid_start, GridLine::Line(1)));
1088 assert!(matches!(result.grid_end, GridLine::Auto));
1089 }
1090
1091 #[test]
1092 fn test_parse_grid_placement_negative_line() {
1093 let result = parse_grid_placement("-1").unwrap();
1094 assert!(matches!(result.grid_start, GridLine::Line(-1)));
1095 }
1096
1097 #[test]
1098 fn test_parse_grid_placement_span() {
1099 let result = parse_grid_placement("span 2").unwrap();
1100 assert!(matches!(result.grid_start, GridLine::Span(2)));
1101 }
1102
1103 #[test]
1104 fn test_parse_grid_placement_start_end() {
1105 let result = parse_grid_placement("1 / 3").unwrap();
1106 assert!(matches!(result.grid_start, GridLine::Line(1)));
1107 assert!(matches!(result.grid_end, GridLine::Line(3)));
1108 }
1109
1110 #[test]
1111 fn test_parse_grid_placement_span_end() {
1112 let result = parse_grid_placement("1 / span 2").unwrap();
1113 assert!(matches!(result.grid_start, GridLine::Line(1)));
1114 assert!(matches!(result.grid_end, GridLine::Span(2)));
1115 }
1116
1117 #[test]
1118 fn test_parse_grid_placement_named_line() {
1119 let result = parse_grid_placement("header-start").unwrap();
1120 assert!(matches!(result.grid_start, GridLine::Named(_)));
1121 }
1122
1123 #[test]
1124 fn test_parse_grid_placement_named_start_end() {
1125 let result = parse_grid_placement("header-start / header-end").unwrap();
1126 assert!(matches!(result.grid_start, GridLine::Named(_)));
1127 assert!(matches!(result.grid_end, GridLine::Named(_)));
1128 }
1129
1130 #[test]
1132 fn test_parse_grid_template_whitespace() {
1133 let result = parse_grid_template(" 100px 200px ").unwrap();
1134 assert_eq!(result.tracks.len(), 2);
1135 }
1136
1137 #[test]
1138 fn test_parse_grid_placement_whitespace() {
1139 let result = parse_grid_placement(" 1 / 3 ").unwrap();
1140 assert!(matches!(result.grid_start, GridLine::Line(1)));
1141 assert!(matches!(result.grid_end, GridLine::Line(3)));
1142 }
1143
1144 #[test]
1145 fn test_parse_grid_template_zero_fr() {
1146 let result = parse_grid_template("0fr").unwrap();
1147 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1148 }
1149
1150 #[test]
1151 fn test_parse_grid_placement_zero_line() {
1152 let result = parse_grid_placement("0").unwrap();
1153 assert!(matches!(result.grid_start, GridLine::Line(0)));
1154 }
1155
1156 #[test]
1158 fn test_parse_grid_template_repeat_fr() {
1159 let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1160 assert_eq!(result.tracks.len(), 3);
1161 assert!(matches!(
1162 result.tracks.as_ref()[0],
1163 GridTrackSizing::Fr(100)
1164 ));
1165 assert!(matches!(
1166 result.tracks.as_ref()[1],
1167 GridTrackSizing::Fr(100)
1168 ));
1169 assert!(matches!(
1170 result.tracks.as_ref()[2],
1171 GridTrackSizing::Fr(100)
1172 ));
1173 }
1174
1175 #[test]
1176 fn test_parse_grid_template_repeat_px() {
1177 let result = parse_grid_template("repeat(2, 100px)").unwrap();
1178 assert_eq!(result.tracks.len(), 2);
1179 assert!(matches!(
1180 result.tracks.as_ref()[0],
1181 GridTrackSizing::Fixed(_)
1182 ));
1183 assert!(matches!(
1184 result.tracks.as_ref()[1],
1185 GridTrackSizing::Fixed(_)
1186 ));
1187 }
1188
1189 #[test]
1190 fn test_parse_grid_template_repeat_multiple_tracks() {
1191 let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1193 assert_eq!(result.tracks.len(), 4);
1194 assert!(matches!(
1195 result.tracks.as_ref()[0],
1196 GridTrackSizing::Fixed(_)
1197 ));
1198 assert!(matches!(
1199 result.tracks.as_ref()[1],
1200 GridTrackSizing::Fr(100)
1201 ));
1202 assert!(matches!(
1203 result.tracks.as_ref()[2],
1204 GridTrackSizing::Fixed(_)
1205 ));
1206 assert!(matches!(
1207 result.tracks.as_ref()[3],
1208 GridTrackSizing::Fr(100)
1209 ));
1210 }
1211
1212 #[test]
1213 fn test_parse_grid_template_repeat_with_other_tracks() {
1214 let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1216 assert_eq!(result.tracks.len(), 4);
1217 assert!(matches!(
1218 result.tracks.as_ref()[0],
1219 GridTrackSizing::Fixed(_)
1220 ));
1221 assert!(matches!(
1222 result.tracks.as_ref()[1],
1223 GridTrackSizing::Fr(100)
1224 ));
1225 assert!(matches!(
1226 result.tracks.as_ref()[2],
1227 GridTrackSizing::Fr(100)
1228 ));
1229 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1230 }
1231
1232 #[test]
1233 fn test_parse_grid_template_repeat_minmax() {
1234 let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1235 assert_eq!(result.tracks.len(), 3);
1236 assert!(matches!(
1237 result.tracks.as_ref()[0],
1238 GridTrackSizing::MinMax(_)
1239 ));
1240 assert!(matches!(
1241 result.tracks.as_ref()[1],
1242 GridTrackSizing::MinMax(_)
1243 ));
1244 assert!(matches!(
1245 result.tracks.as_ref()[2],
1246 GridTrackSizing::MinMax(_)
1247 ));
1248 }
1249}
1250
1251#[repr(C)]
1256#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1257pub struct GridAreaDefinition {
1258 pub name: AzString,
1259 pub row_start: u16,
1260 pub row_end: u16,
1261 pub column_start: u16,
1262 pub column_end: u16,
1263}
1264
1265impl_option!(
1266 GridAreaDefinition,
1267 OptionGridAreaDefinition,
1268 copy = false,
1269 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1270);
1271
1272impl_vec!(
1273 GridAreaDefinition,
1274 GridAreaDefinitionVec,
1275 GridAreaDefinitionVecDestructor,
1276 GridAreaDefinitionVecDestructorType,
1277 GridAreaDefinitionVecSlice,
1278 OptionGridAreaDefinition
1279);
1280impl_vec_clone!(
1281 GridAreaDefinition,
1282 GridAreaDefinitionVec,
1283 GridAreaDefinitionVecDestructor
1284);
1285impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1286impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1287impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1288impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1289impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1290impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1291impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1292
1293#[repr(C)]
1303#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1304pub struct GridTemplateAreas {
1305 pub areas: GridAreaDefinitionVec,
1306}
1307
1308impl Default for GridTemplateAreas {
1309 fn default() -> Self {
1310 Self {
1311 areas: GridAreaDefinitionVec::from_vec(Vec::new()),
1312 }
1313 }
1314}
1315
1316impl PrintAsCssValue for GridTemplateAreas {
1317 fn print_as_css_value(&self) -> String {
1318 let areas_slice = self.areas.as_ref();
1319 if areas_slice.is_empty() {
1320 return "none".to_string();
1321 }
1322 let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1324 let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1325 let num_rows = (max_row - 1) as usize;
1326 let num_cols = (max_col - 1) as usize;
1327 let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1328 for area in areas_slice {
1329 let row_start = area.row_start as usize - 1;
1330 let row_end = area.row_end as usize - 1;
1331 let col_start = area.column_start as usize - 1;
1332 let col_end = area.column_end as usize - 1;
1333 for row in grid.iter_mut().take(row_end).skip(row_start) {
1334 for cell in row.iter_mut().take(col_end).skip(col_start) {
1335 *cell = area.name.as_str().to_string();
1336 }
1337 }
1338 }
1339 grid.iter()
1340 .map(|row| format!("\"{}\"", row.join(" ")))
1341 .collect::<Vec<_>>()
1342 .join(" ")
1343 }
1344}
1345
1346#[cfg(feature = "parser")]
1354pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1358 use alloc::collections::BTreeMap;
1359 let input = input.trim();
1360 if input == "none" {
1361 return Ok(GridTemplateAreas::default());
1362 }
1363
1364 let mut rows: Vec<Vec<String>> = Vec::new();
1366 let mut i = 0;
1367 let bytes = input.as_bytes();
1368 while i < bytes.len() {
1369 if bytes[i] == b'"' || bytes[i] == b'\'' {
1370 let quote = bytes[i];
1371 i += 1;
1372 let start = i;
1373 while i < bytes.len() && bytes[i] != quote {
1374 i += 1;
1375 }
1376 if i >= bytes.len() {
1377 return Err(());
1378 }
1379 let row_str = &input[start..i];
1380 let cells: Vec<String> = row_str
1381 .split_whitespace()
1382 .map(std::string::ToString::to_string)
1383 .collect();
1384 if cells.is_empty() {
1385 return Err(());
1386 }
1387 rows.push(cells);
1388 }
1389 i += 1;
1391 }
1392
1393 if rows.is_empty() {
1394 return Err(());
1395 }
1396
1397 let num_cols = rows[0].len();
1399 for row in &rows {
1400 if row.len() != num_cols {
1401 return Err(());
1402 }
1403 }
1404
1405 let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1407
1408 for (row_idx, row) in rows.iter().enumerate() {
1409 for (col_idx, cell) in row.iter().enumerate() {
1410 if cell == "." {
1411 continue; }
1413 let entry = area_map
1414 .entry(cell.clone())
1415 .or_insert((row_idx, row_idx, col_idx, col_idx));
1416 entry.0 = entry.0.min(row_idx);
1417 entry.1 = entry.1.max(row_idx);
1418 entry.2 = entry.2.min(col_idx);
1419 entry.3 = entry.3.max(col_idx);
1420 }
1421 }
1422
1423 let mut areas = Vec::new();
1425 for (name, (min_row, max_row, min_col, max_col)) in area_map {
1426 areas.push(GridAreaDefinition {
1427 name: name.into(),
1428 row_start: u16::try_from(min_row + 1).unwrap_or(u16::MAX),
1429 row_end: u16::try_from(max_row + 2).unwrap_or(u16::MAX), column_start: u16::try_from(min_col + 1).unwrap_or(u16::MAX),
1431 column_end: u16::try_from(max_col + 2).unwrap_or(u16::MAX),
1432 });
1433 }
1434
1435 Ok(GridTemplateAreas {
1436 areas: GridAreaDefinitionVec::from_vec(areas),
1437 })
1438}
1439
1440#[cfg(all(test, feature = "parser"))]
1441mod autotest_generated {
1442 use super::*;
1443
1444 #[test]
1455 fn named_grid_line_span_roundtrips_every_nonzero_i32() {
1456 for span in [1_i32, -1, 7, -7, i32::MAX, i32::MIN, i32::MIN + 1] {
1457 let line = NamedGridLine::create("area".to_string().into(), Some(span));
1458 assert_eq!(line.span_count, span);
1459 assert_eq!(line.span(), Some(span), "span {span} must survive create()");
1460 }
1461 }
1462
1463 #[test]
1464 fn named_grid_line_span_of_some_zero_is_lossy() {
1465 let explicit_zero = NamedGridLine::create("a".to_string().into(), Some(0));
1470 let absent = NamedGridLine::create("a".to_string().into(), None);
1471
1472 assert_eq!(explicit_zero.span(), None);
1473 assert_eq!(absent.span(), None);
1474 assert_eq!(
1475 explicit_zero, absent,
1476 "Some(0) and None collapse to the same value"
1477 );
1478 }
1479
1480 #[test]
1481 fn named_grid_line_create_accepts_empty_and_unicode_names() {
1482 let empty = NamedGridLine::create(String::new().into(), None);
1483 assert_eq!(empty.grid_line_name.as_str(), "");
1484 assert_eq!(empty.span(), None);
1485
1486 let emoji = NamedGridLine::create("\u{1F600}\u{0301}".to_string().into(), Some(3));
1487 assert_eq!(emoji.grid_line_name.as_str(), "\u{1F600}\u{0301}");
1488 assert_eq!(emoji.span(), Some(3));
1489 }
1490
1491 #[test]
1492 fn named_grid_line_span_on_an_extreme_instance_does_not_panic() {
1493 let huge_name = "x".repeat(100_000);
1494 let line = NamedGridLine::create(huge_name.clone().into(), Some(i32::MIN));
1495 assert_eq!(line.span(), Some(i32::MIN));
1496 assert_eq!(line.grid_line_name.as_str().len(), huge_name.len());
1497 }
1498
1499 #[test]
1504 fn split_respecting_parens_empty_and_whitespace_yield_ok_empty_not_err() {
1505 assert_eq!(split_respecting_parens(""), Ok(Vec::new()));
1509 assert_eq!(split_respecting_parens(" "), Ok(Vec::new()));
1510 assert_eq!(split_respecting_parens(" \t\n "), Ok(Vec::new()));
1511 }
1512
1513 #[test]
1514 fn split_respecting_parens_valid_minimal_and_nested_calls() {
1515 assert_eq!(
1516 split_respecting_parens("100px 1fr"),
1517 Ok(vec!["100px".to_string(), "1fr".to_string()])
1518 );
1519 assert_eq!(
1521 split_respecting_parens("repeat(2, 100px 1fr) auto"),
1522 Ok(vec!["repeat(2, 100px 1fr)".to_string(), "auto".to_string()])
1523 );
1524 assert_eq!(
1525 split_respecting_parens("a(b c)d e"),
1526 Ok(vec!["a(b c)d".to_string(), "e".to_string()])
1527 );
1528 }
1529
1530 #[test]
1531 fn split_respecting_parens_rejects_unbalanced_close_paren() {
1532 assert_eq!(split_respecting_parens(")"), Err(()));
1533 assert_eq!(split_respecting_parens("a)b"), Err(()));
1534 assert_eq!(split_respecting_parens("(a))"), Err(()));
1535 assert_eq!(split_respecting_parens(")("), Err(()));
1536 }
1537
1538 #[test]
1539 fn split_respecting_parens_accepts_unbalanced_open_paren_is_lax() {
1540 assert_eq!(split_respecting_parens("((("), Ok(vec!["(((".to_string()]));
1544 assert_eq!(
1545 split_respecting_parens("repeat(2, 1fr"),
1546 Ok(vec!["repeat(2, 1fr".to_string()])
1547 );
1548 assert!(parse_grid_template("repeat(2, 1fr").is_err());
1549 }
1550
1551 #[test]
1552 fn split_respecting_parens_does_not_treat_tab_or_newline_as_a_separator() {
1553 assert_eq!(
1557 split_respecting_parens("100px\t200px"),
1558 Ok(vec!["100px\t200px".to_string()])
1559 );
1560 assert_eq!(
1561 split_respecting_parens("100px\n200px"),
1562 Ok(vec!["100px\n200px".to_string()])
1563 );
1564 assert!(parse_grid_template("100px\t200px").is_err());
1566 assert!(parse_grid_template("100px\n200px").is_err());
1567 assert_eq!(parse_grid_template("100px 200px").unwrap().tracks.len(), 2);
1569 }
1570
1571 #[test]
1572 fn split_respecting_parens_handles_multibyte_unicode() {
1573 assert_eq!(
1575 split_respecting_parens("\u{1F600} e\u{0301}"),
1576 Ok(vec!["\u{1F600}".to_string(), "e\u{0301}".to_string()])
1577 );
1578 assert_eq!(
1579 split_respecting_parens("\u{1F600}(\u{4E2D} \u{6587})"),
1580 Ok(vec!["\u{1F600}(\u{4E2D} \u{6587})".to_string()])
1581 );
1582 }
1583
1584 #[test]
1585 fn split_respecting_parens_survives_a_million_chars_and_deep_nesting() {
1586 let long = "a".repeat(1_000_000);
1588 let parts = split_respecting_parens(&long).unwrap();
1589 assert_eq!(parts.len(), 1);
1590 assert_eq!(parts[0].len(), 1_000_000);
1591
1592 let many = "1fr ".repeat(50_000);
1594 assert_eq!(split_respecting_parens(&many).unwrap().len(), 50_000);
1595
1596 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
1599 assert_eq!(split_respecting_parens(&nested).unwrap().len(), 1);
1600 assert!(parse_grid_template(&nested).is_err());
1601 }
1602
1603 #[test]
1608 fn parse_grid_track_owned_empty_and_whitespace_are_err() {
1609 assert_eq!(parse_grid_track_owned(""), Err(()));
1610 assert_eq!(parse_grid_track_owned(" "), Err(()));
1611 assert_eq!(parse_grid_track_owned("\t\n"), Err(()));
1612 }
1613
1614 #[test]
1615 fn parse_grid_track_owned_valid_minimal_keywords() {
1616 assert_eq!(parse_grid_track_owned("auto"), Ok(GridTrackSizing::Auto));
1617 assert_eq!(
1618 parse_grid_track_owned("min-content"),
1619 Ok(GridTrackSizing::MinContent)
1620 );
1621 assert_eq!(
1622 parse_grid_track_owned("max-content"),
1623 Ok(GridTrackSizing::MaxContent)
1624 );
1625 assert_eq!(
1626 parse_grid_track_owned(" auto "),
1627 Ok(GridTrackSizing::Auto),
1628 "leading/trailing whitespace is trimmed"
1629 );
1630 assert_eq!(parse_grid_track_owned("AUTO"), Err(()));
1632 assert_eq!(parse_grid_track_owned("Min-Content"), Err(()));
1633 }
1634
1635 #[test]
1636 fn parse_grid_track_owned_fr_is_scaled_by_100_and_truncates() {
1637 assert_eq!(parse_grid_track_owned("1fr"), Ok(GridTrackSizing::Fr(100)));
1638 assert_eq!(parse_grid_track_owned("0fr"), Ok(GridTrackSizing::Fr(0)));
1639 assert_eq!(parse_grid_track_owned("-0fr"), Ok(GridTrackSizing::Fr(0)));
1640 assert_eq!(parse_grid_track_owned("+1fr"), Ok(GridTrackSizing::Fr(100)));
1641 assert_eq!(parse_grid_track_owned("0.5fr"), Ok(GridTrackSizing::Fr(50)));
1642
1643 assert_eq!(
1646 parse_grid_track_owned("0.005fr"),
1647 Ok(GridTrackSizing::Fr(0))
1648 );
1649 assert_eq!(
1650 parse_grid_track_owned("1.999fr"),
1651 Ok(GridTrackSizing::Fr(199))
1652 );
1653
1654 assert_eq!(
1656 parse_grid_track_owned("-1fr"),
1657 Ok(GridTrackSizing::Fr(-100))
1658 );
1659 }
1660
1661 #[test]
1662 fn parse_grid_track_owned_fr_rejects_nan_inf_and_out_of_range() {
1663 for bad in [
1666 "NaNfr",
1667 "nanfr",
1668 "inffr",
1669 "-inffr",
1670 "infinityfr",
1671 "1e8fr",
1672 "-1e8fr",
1673 "1e30fr",
1674 "-1e30fr",
1675 "340282350000000000000000000000000000000fr",
1676 ] {
1677 assert_eq!(
1678 parse_grid_track_owned(bad),
1679 Err(()),
1680 "{bad:?} must be rejected"
1681 );
1682 }
1683
1684 assert_eq!(
1686 parse_grid_track_owned("1e7fr"),
1687 Ok(GridTrackSizing::Fr(1_000_000_000))
1688 );
1689 assert_eq!(
1690 parse_grid_track_owned("-1e7fr"),
1691 Ok(GridTrackSizing::Fr(-1_000_000_000))
1692 );
1693 }
1694
1695 #[test]
1696 fn parse_grid_track_owned_bare_fr_suffix_is_err() {
1697 assert_eq!(parse_grid_track_owned("fr"), Err(()));
1698 assert_eq!(parse_grid_track_owned(" fr"), Err(()));
1699 assert_eq!(parse_grid_track_owned("xfr"), Err(()));
1700 }
1701
1702 #[test]
1703 fn parse_grid_track_owned_minmax_comma_split_is_paren_unaware() {
1704 assert_eq!(
1705 parse_grid_track_owned("minmax(100px, 1fr)"),
1706 Ok(GridTrackSizing::MinMax(GridMinMax {
1707 min: Box::new(GridTrackSizing::Fixed(PixelValue::px(100.0))),
1708 max: Box::new(GridTrackSizing::Fr(100)),
1709 }))
1710 );
1711 assert!(matches!(
1713 parse_grid_track_owned("minmax(fit-content(10px), 2px)"),
1714 Ok(GridTrackSizing::MinMax(_))
1715 ));
1716 assert_eq!(
1721 parse_grid_track_owned("minmax(minmax(1px,2px), 3px)"),
1722 Err(())
1723 );
1724 assert_eq!(parse_grid_track_owned("minmax(1px,2px,3px)"), Err(()));
1725 assert_eq!(parse_grid_track_owned("minmax(1px)"), Err(()));
1726 assert_eq!(parse_grid_track_owned("minmax()"), Err(()));
1727 }
1728
1729 #[test]
1730 fn parse_grid_track_owned_deeply_nested_minmax_cannot_stack_overflow() {
1731 let deep = format!("{}1px{}", "minmax(".repeat(10_000), ")".repeat(10_000));
1735 assert_eq!(parse_grid_track_owned(&deep), Err(()));
1736
1737 let deep_fit = format!("{}1px{}", "fit-content(".repeat(10_000), ")".repeat(10_000));
1738 assert_eq!(parse_grid_track_owned(&deep_fit), Err(()));
1739 }
1740
1741 #[test]
1742 fn parse_grid_track_owned_unclosed_and_truncated_funcs_are_err() {
1743 for bad in [
1747 "minmax(",
1748 "minmax",
1749 "minmax)",
1750 "fit-content(",
1751 "fit-content",
1752 "fit-content)",
1753 "repeat(",
1754 ")",
1755 "(",
1756 "()",
1757 "fit-content()",
1758 ] {
1759 assert_eq!(parse_grid_track_owned(bad), Err(()), "{bad:?}");
1760 }
1761 }
1762
1763 #[test]
1764 fn parse_grid_track_owned_fit_content_and_pixel_fallback() {
1765 assert_eq!(
1766 parse_grid_track_owned("fit-content(200px)"),
1767 Ok(GridTrackSizing::FitContent(PixelValue::px(200.0)))
1768 );
1769 assert_eq!(
1770 parse_grid_track_owned("100px"),
1771 Ok(GridTrackSizing::Fixed(PixelValue::px(100.0)))
1772 );
1773 assert_eq!(
1774 parse_grid_track_owned("25%"),
1775 Ok(GridTrackSizing::Fixed(PixelValue::percent(25.0)))
1776 );
1777 assert_eq!(
1780 parse_grid_track_owned("100"),
1781 Ok(GridTrackSizing::Fixed(PixelValue::px(100.0)))
1782 );
1783 }
1784
1785 #[test]
1786 fn parse_grid_track_owned_pixel_fallback_swallows_nan_and_inf() {
1787 assert_eq!(
1791 parse_grid_track_owned("NaNpx"),
1792 Ok(GridTrackSizing::Fixed(PixelValue::px(0.0))),
1793 "NaN silently becomes 0px"
1794 );
1795 assert_eq!(
1796 parse_grid_track_owned("inf"),
1797 Ok(GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY))),
1798 "inf is accepted and saturates"
1799 );
1800 assert_eq!(
1801 parse_grid_track_owned("1e40px"),
1802 Ok(GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY)))
1803 );
1804 }
1805
1806 #[test]
1807 fn parse_grid_track_owned_garbage_and_long_input_never_panic() {
1808 for bad in [
1809 "!!!",
1810 ";;;",
1811 "\u{1F600}",
1812 "e\u{0301}\u{0301}",
1813 "\0",
1814 "100px;garbage",
1815 "auto;",
1816 "1fr 1fr",
1817 "--var(x)",
1818 "calc(1px + 1px)",
1819 ] {
1820 assert_eq!(parse_grid_track_owned(bad), Err(()), "{bad:?}");
1821 }
1822 assert_eq!(parse_grid_track_owned(&"a".repeat(1_000_000)), Err(()));
1824 }
1825
1826 #[test]
1831 fn parse_grid_track_or_repeat_valid_minimal() {
1832 let mut tracks = Vec::new();
1833 assert_eq!(
1834 parse_grid_track_or_repeat("repeat(2, 1fr)", &mut tracks),
1835 Ok(())
1836 );
1837 assert_eq!(
1838 tracks,
1839 vec![GridTrackSizing::Fr(100), GridTrackSizing::Fr(100)]
1840 );
1841
1842 assert_eq!(parse_grid_track_or_repeat("auto", &mut tracks), Ok(()));
1844 assert_eq!(tracks.len(), 3);
1845 assert_eq!(tracks[2], GridTrackSizing::Auto);
1846 }
1847
1848 #[test]
1849 fn parse_grid_track_or_repeat_count_is_bounded_at_10_000() {
1850 let mut tracks = Vec::new();
1851 assert_eq!(
1852 parse_grid_track_or_repeat("repeat(10000, 1fr)", &mut tracks),
1853 Ok(())
1854 );
1855 assert_eq!(tracks.len(), 10_000, "the documented maximum is accepted");
1856
1857 for bad in [
1859 "repeat(10001, 1fr)",
1860 "repeat(0, 1fr)",
1861 "repeat(-1, 1fr)",
1862 "repeat(18446744073709551616, 1fr)",
1863 "repeat(99999999999999999999999999, 1fr)",
1864 "repeat(1.5, 1fr)",
1865 "repeat(NaN, 1fr)",
1866 "repeat(inf, 1fr)",
1867 "repeat(auto-fill, 1fr)",
1868 ] {
1869 let mut t = Vec::new();
1870 assert_eq!(parse_grid_track_or_repeat(bad, &mut t), Err(()), "{bad:?}");
1871 }
1872 }
1873
1874 #[test]
1875 fn parse_grid_track_or_repeat_expansion_is_amplified_by_the_track_list_length() {
1876 let mut tracks = Vec::new();
1881 let input = format!("repeat(10000, {})", "1fr ".repeat(10).trim());
1882 assert_eq!(parse_grid_track_or_repeat(&input, &mut tracks), Ok(()));
1883 assert_eq!(tracks.len(), 100_000);
1884 assert!(tracks.iter().all(|t| *t == GridTrackSizing::Fr(100)));
1885 }
1886
1887 #[test]
1888 fn parse_grid_track_or_repeat_with_an_empty_track_list_is_lax() {
1889 let mut tracks = Vec::new();
1892 assert_eq!(
1893 parse_grid_track_or_repeat("repeat(2, )", &mut tracks),
1894 Ok(())
1895 );
1896 assert!(tracks.is_empty());
1897 assert_eq!(parse_grid_template("repeat(2, )").unwrap().tracks.len(), 0);
1898 }
1899
1900 #[test]
1901 fn parse_grid_track_or_repeat_rejects_nested_repeat_and_malformed_shapes() {
1902 for bad in [
1903 "repeat(2, repeat(2, 1fr))", "repeat(2)", "repeat()",
1906 "repeat(2, 1fr", "",
1908 " ",
1909 "\u{1F600}",
1910 "repeat(2, garbage)",
1911 ] {
1912 let mut t = Vec::new();
1913 assert_eq!(parse_grid_track_or_repeat(bad, &mut t), Err(()), "{bad:?}");
1914 }
1915 }
1916
1917 #[test]
1918 fn parse_grid_track_or_repeat_appends_nothing_when_it_fails() {
1919 let mut tracks = vec![GridTrackSizing::Auto];
1924 for bad in [
1925 "repeat(2, 1fr garbage)", "repeat(3, 1fr) trailing", "repeat(0, 1fr)",
1928 "repeat(2, 1fr", "garbage",
1930 ] {
1931 assert_eq!(
1932 parse_grid_track_or_repeat(bad, &mut tracks),
1933 Err(()),
1934 "{bad:?}"
1935 );
1936 assert_eq!(
1937 tracks,
1938 vec![GridTrackSizing::Auto],
1939 "{bad:?} mutated the caller's Vec despite returning Err"
1940 );
1941 }
1942 }
1943
1944 #[test]
1949 fn parse_grid_template_empty_and_whitespace_are_ok_empty_is_lax() {
1950 for input in ["", " ", "\t\n", " \r\n "] {
1953 let parsed = parse_grid_template(input)
1954 .unwrap_or_else(|e| panic!("{input:?} unexpectedly failed: {e:?}"));
1955 assert_eq!(parsed.tracks.len(), 0, "{input:?}");
1956 assert_eq!(parsed, GridTemplate::default(), "{input:?}");
1957 }
1958 }
1959
1960 #[test]
1961 fn parse_grid_template_none_is_case_sensitive() {
1962 assert_eq!(
1963 parse_grid_template("none").unwrap(),
1964 GridTemplate::default()
1965 );
1966 assert_eq!(
1967 parse_grid_template(" none ").unwrap(),
1968 GridTemplate::default()
1969 );
1970 assert!(parse_grid_template("NONE").is_err());
1972 assert!(parse_grid_template("None").is_err());
1973 }
1974
1975 #[test]
1976 fn parse_grid_template_garbage_is_err_and_reports_the_trimmed_input() {
1977 let err = parse_grid_template(" !!! garbage ").unwrap_err();
1978 assert_eq!(
1979 err,
1980 GridParseError::InvalidValue("!!! garbage"),
1981 "the error borrows the trimmed input, not the raw one"
1982 );
1983 assert_eq!(format!("{err}"), "Invalid grid value: \"!!! garbage\"");
1984
1985 for bad in [
1986 ")",
1987 "a)b",
1988 "100px;200px",
1989 "1 fr",
1990 "100px, 200px",
1991 "\u{1F600}",
1992 "100px \u{1F600}",
1993 "\0",
1994 "calc(100px)",
1995 ] {
1996 assert!(
1997 parse_grid_template(bad).is_err(),
1998 "{bad:?} must be rejected"
1999 );
2000 }
2001 }
2002
2003 #[test]
2004 fn parse_grid_template_boundary_numbers_do_not_overflow() {
2005 for bad in ["NaNfr", "inffr", "-inffr", "1e30fr"] {
2007 assert!(parse_grid_template(bad).is_err(), "{bad:?}");
2008 }
2009 assert_eq!(
2011 parse_grid_template("1e40px").unwrap().tracks.as_ref()[0],
2012 GridTrackSizing::Fixed(PixelValue::px(f32::INFINITY))
2013 );
2014 assert!(parse_grid_template("9223372036854775807").is_ok());
2016 assert_eq!(
2017 parse_grid_template("0px -0px 0fr").unwrap().tracks.len(),
2018 3,
2019 "zero and negative-zero are accepted"
2020 );
2021 }
2022
2023 #[test]
2024 fn parse_grid_template_extremely_long_input_terminates() {
2025 let many = "1fr ".repeat(50_000);
2027 assert_eq!(parse_grid_template(&many).unwrap().tracks.len(), 50_000);
2028 assert!(parse_grid_template(&"a".repeat(1_000_000)).is_err());
2030 }
2031
2032 #[test]
2033 fn parse_grid_template_deeply_nested_parens_do_not_stack_overflow() {
2034 let nested = format!("{}1px{}", "(".repeat(10_000), ")".repeat(10_000));
2035 assert!(parse_grid_template(&nested).is_err());
2036 assert!(parse_grid_template(&")".repeat(10_000)).is_err());
2038 }
2039
2040 #[test]
2045 fn parse_grid_line_owned_valid_minimal() {
2046 assert_eq!(parse_grid_line_owned("auto"), Ok(GridLine::Auto));
2047 assert_eq!(parse_grid_line_owned("1"), Ok(GridLine::Line(1)));
2048 assert_eq!(parse_grid_line_owned("-1"), Ok(GridLine::Line(-1)));
2049 assert_eq!(parse_grid_line_owned("+1"), Ok(GridLine::Line(1)));
2050 assert_eq!(parse_grid_line_owned("0"), Ok(GridLine::Line(0)));
2051 assert_eq!(parse_grid_line_owned("-0"), Ok(GridLine::Line(0)));
2052 assert_eq!(parse_grid_line_owned("span 2"), Ok(GridLine::Span(2)));
2053 assert_eq!(parse_grid_line_owned("span 2"), Ok(GridLine::Span(2)));
2054 }
2055
2056 #[test]
2057 fn parse_grid_line_owned_i32_boundaries_saturate_into_a_named_line() {
2058 assert_eq!(
2059 parse_grid_line_owned("2147483647"),
2060 Ok(GridLine::Line(i32::MAX))
2061 );
2062 assert_eq!(
2063 parse_grid_line_owned("-2147483648"),
2064 Ok(GridLine::Line(i32::MIN))
2065 );
2066
2067 assert_eq!(
2072 parse_grid_line_owned("2147483648"),
2073 Ok(GridLine::Named(NamedGridLine::create(
2074 "2147483648".to_string().into(),
2075 None
2076 )))
2077 );
2078 assert_eq!(
2079 parse_grid_line_owned("-2147483649"),
2080 Ok(GridLine::Named(NamedGridLine::create(
2081 "-2147483649".to_string().into(),
2082 None
2083 )))
2084 );
2085
2086 assert_eq!(parse_grid_line_owned("span 2147483648"), Err(()));
2088 assert_eq!(parse_grid_line_owned("span -2147483649"), Err(()));
2089 assert_eq!(parse_grid_line_owned("span abc"), Err(()));
2090
2091 assert_eq!(
2095 parse_grid_line_owned("span "),
2096 Ok(GridLine::Named(NamedGridLine::create(
2097 "span".to_string().into(),
2098 None
2099 )))
2100 );
2101 }
2102
2103 #[test]
2104 fn parse_grid_line_owned_span_accepts_zero_and_negative_is_lax() {
2105 assert_eq!(parse_grid_line_owned("span 0"), Ok(GridLine::Span(0)));
2108 assert_eq!(parse_grid_line_owned("span -5"), Ok(GridLine::Span(-5)));
2109 assert_eq!(
2110 parse_grid_line_owned("span -2147483648"),
2111 Ok(GridLine::Span(i32::MIN))
2112 );
2113 }
2114
2115 #[test]
2116 fn parse_grid_line_owned_never_errs_on_garbage_it_names_it() {
2117 for garbage in [
2122 "",
2123 " ",
2124 "!!!",
2125 ";;;",
2126 "\u{1F600}",
2127 "\0",
2128 "span",
2129 "100px",
2130 "1 2",
2131 ] {
2132 let parsed = parse_grid_line_owned(garbage)
2133 .unwrap_or_else(|()| panic!("{garbage:?} unexpectedly errored"));
2134 assert!(
2135 matches!(parsed, GridLine::Named(_)),
2136 "{garbage:?} became {parsed:?}, expected a Named catch-all"
2137 );
2138 }
2139 assert_eq!(
2140 parse_grid_line_owned(" "),
2141 Ok(GridLine::Named(NamedGridLine::create(
2142 String::new().into(),
2143 None
2144 ))),
2145 "whitespace-only trims to an EMPTY named line"
2146 );
2147 assert!(parse_grid_line_owned(&"x".repeat(100_000)).is_ok());
2149 }
2150
2151 #[test]
2156 fn parse_grid_placement_valid_minimal() {
2157 assert_eq!(
2158 parse_grid_placement("auto").unwrap(),
2159 GridPlacement::default()
2160 );
2161 assert_eq!(
2162 parse_grid_placement("1 / 3").unwrap(),
2163 GridPlacement {
2164 grid_start: GridLine::Line(1),
2165 grid_end: GridLine::Line(3)
2166 }
2167 );
2168 assert_eq!(
2169 parse_grid_placement("1/3").unwrap(),
2170 GridPlacement {
2171 grid_start: GridLine::Line(1),
2172 grid_end: GridLine::Line(3)
2173 },
2174 "the slash does not need surrounding spaces"
2175 );
2176 assert_eq!(
2177 parse_grid_placement(" 1 / span 2 ").unwrap(),
2178 GridPlacement {
2179 grid_start: GridLine::Line(1),
2180 grid_end: GridLine::Span(2)
2181 }
2182 );
2183 }
2184
2185 #[test]
2186 fn parse_grid_placement_extra_slash_segments_are_silently_dropped() {
2187 assert_eq!(
2190 parse_grid_placement("1 / 2 / 3").unwrap(),
2191 GridPlacement {
2192 grid_start: GridLine::Line(1),
2193 grid_end: GridLine::Line(2)
2194 }
2195 );
2196 assert_eq!(
2197 parse_grid_placement("1 / 2 / 3 / 4 / garbage").unwrap(),
2198 GridPlacement {
2199 grid_start: GridLine::Line(1),
2200 grid_end: GridLine::Line(2)
2201 }
2202 );
2203 }
2204
2205 #[test]
2206 fn parse_grid_placement_empty_and_garbage_are_ok_named_is_lax() {
2207 let empty = parse_grid_placement("").unwrap();
2211 assert_eq!(
2212 empty,
2213 GridPlacement {
2214 grid_start: GridLine::Named(NamedGridLine::create(String::new().into(), None)),
2215 grid_end: GridLine::Auto,
2216 },
2217 "empty input should be Err, but yields a Named(\"\") start"
2218 );
2219
2220 for garbage in [" ", "!!!", "\u{1F600}", "/", "///", "\0"] {
2221 assert!(parse_grid_placement(garbage).is_ok(), "{garbage:?}");
2222 }
2223
2224 let err = parse_grid_placement(" span x ").unwrap_err();
2226 assert_eq!(err, GridParseError::InvalidValue("span x"));
2227 assert_eq!(format!("{err}"), "Invalid grid value: \"span x\"");
2228 assert!(parse_grid_placement("1 / span x").is_err());
2229 }
2230
2231 #[test]
2232 fn parse_grid_placement_long_and_nested_input_does_not_panic() {
2233 let long = "a".repeat(200_000);
2234 assert!(parse_grid_placement(&long).is_ok());
2235 assert!(parse_grid_placement(&format!("{long} / {long}")).is_ok());
2236
2237 let nested = format!("{}x{}", "(".repeat(10_000), ")".repeat(10_000));
2239 assert!(parse_grid_placement(&nested).is_ok());
2240 }
2241
2242 #[test]
2247 fn parse_layout_grid_auto_flow_accepts_exactly_five_spellings() {
2248 assert_eq!(
2249 parse_layout_grid_auto_flow("row"),
2250 Ok(LayoutGridAutoFlow::Row)
2251 );
2252 assert_eq!(
2253 parse_layout_grid_auto_flow("column"),
2254 Ok(LayoutGridAutoFlow::Column)
2255 );
2256 assert_eq!(
2257 parse_layout_grid_auto_flow("row dense"),
2258 Ok(LayoutGridAutoFlow::RowDense)
2259 );
2260 assert_eq!(
2261 parse_layout_grid_auto_flow("dense"),
2262 Ok(LayoutGridAutoFlow::RowDense)
2263 );
2264 assert_eq!(
2265 parse_layout_grid_auto_flow("column dense"),
2266 Ok(LayoutGridAutoFlow::ColumnDense)
2267 );
2268 assert_eq!(
2269 parse_layout_grid_auto_flow(" row "),
2270 Ok(LayoutGridAutoFlow::Row)
2271 );
2272 }
2273
2274 #[test]
2275 fn parse_layout_grid_auto_flow_rejects_everything_else() {
2276 for bad in [
2277 "",
2278 " ",
2279 "\t\n",
2280 "ROW", "Row",
2282 "dense row", "dense column",
2284 "row dense", "row\tdense",
2286 "row dense extra",
2287 "row;",
2288 "\u{1F600}",
2289 "\0",
2290 ] {
2291 assert!(
2292 parse_layout_grid_auto_flow(bad).is_err(),
2293 "{bad:?} must be rejected"
2294 );
2295 }
2296 assert!(parse_layout_grid_auto_flow(&"row ".repeat(250_000)).is_err());
2298 }
2299
2300 #[test]
2301 fn parse_layout_grid_auto_flow_error_borrows_the_untrimmed_input() {
2302 let err = parse_layout_grid_auto_flow(" bogus ").unwrap_err();
2306 assert_eq!(err, GridAutoFlowParseError::InvalidValue(" bogus "));
2307 assert_eq!(
2308 format!("{err}"),
2309 "Invalid grid-auto-flow value: \" bogus \""
2310 );
2311 }
2312
2313 #[test]
2318 fn parse_layout_justify_self_accepts_the_flex_aliases() {
2319 assert_eq!(
2320 parse_layout_justify_self("auto"),
2321 Ok(LayoutJustifySelf::Auto)
2322 );
2323 assert_eq!(
2324 parse_layout_justify_self("start"),
2325 Ok(LayoutJustifySelf::Start)
2326 );
2327 assert_eq!(
2328 parse_layout_justify_self("flex-start"),
2329 Ok(LayoutJustifySelf::Start)
2330 );
2331 assert_eq!(parse_layout_justify_self("end"), Ok(LayoutJustifySelf::End));
2332 assert_eq!(
2333 parse_layout_justify_self("flex-end"),
2334 Ok(LayoutJustifySelf::End)
2335 );
2336 assert_eq!(
2337 parse_layout_justify_self("center"),
2338 Ok(LayoutJustifySelf::Center)
2339 );
2340 assert_eq!(
2341 parse_layout_justify_self("stretch"),
2342 Ok(LayoutJustifySelf::Stretch)
2343 );
2344 assert_eq!(
2345 parse_layout_justify_self(" center "),
2346 Ok(LayoutJustifySelf::Center)
2347 );
2348 }
2349
2350 #[test]
2351 fn parse_layout_justify_self_rejects_everything_else() {
2352 for bad in [
2353 "",
2354 " ",
2355 "Start",
2356 "START",
2357 "space-between",
2358 "normal",
2359 "left",
2360 "right",
2361 "\u{1F600}",
2362 "\0",
2363 "start end",
2364 "flex-start;",
2365 ] {
2366 assert!(parse_layout_justify_self(bad).is_err(), "{bad:?}");
2367 }
2368 let err = parse_layout_justify_self(" bogus ").unwrap_err();
2369 assert_eq!(err, JustifySelfParseError::InvalidValue(" bogus "));
2370 assert_eq!(
2371 format!("{err}"),
2372 "Invalid justify-self value: \" bogus \""
2373 );
2374 assert!(parse_layout_justify_self(&"x".repeat(1_000_000)).is_err());
2375 }
2376
2377 #[test]
2378 fn parse_layout_justify_items_has_no_auto_and_no_flex_aliases() {
2379 assert_eq!(
2380 parse_layout_justify_items("start"),
2381 Ok(LayoutJustifyItems::Start)
2382 );
2383 assert_eq!(
2384 parse_layout_justify_items("end"),
2385 Ok(LayoutJustifyItems::End)
2386 );
2387 assert_eq!(
2388 parse_layout_justify_items("center"),
2389 Ok(LayoutJustifyItems::Center)
2390 );
2391 assert_eq!(
2392 parse_layout_justify_items("stretch"),
2393 Ok(LayoutJustifyItems::Stretch)
2394 );
2395
2396 assert!(parse_layout_justify_items("auto").is_err());
2401 assert!(parse_layout_justify_self("auto").is_ok());
2402 assert!(parse_layout_justify_items("flex-start").is_err());
2403 assert!(parse_layout_justify_self("flex-start").is_ok());
2404
2405 for bad in ["", " ", "Start", "\u{1F600}", "\0", "stretch stretch"] {
2406 assert!(parse_layout_justify_items(bad).is_err(), "{bad:?}");
2407 }
2408 let err = parse_layout_justify_items(" bogus ").unwrap_err();
2409 assert_eq!(err, JustifyItemsParseError::InvalidValue(" bogus "));
2410 assert_eq!(
2411 format!("{err}"),
2412 "Invalid justify-items value: \" bogus \""
2413 );
2414 assert!(parse_layout_justify_items(&"x".repeat(1_000_000)).is_err());
2415 }
2416
2417 #[test]
2422 fn parse_layout_gap_valid_minimal_and_units() {
2423 assert_eq!(
2424 parse_layout_gap("10px").unwrap().inner,
2425 PixelValue::px(10.0)
2426 );
2427 assert_eq!(
2428 parse_layout_gap(" 10px ").unwrap().inner,
2429 PixelValue::px(10.0)
2430 );
2431 assert_eq!(
2432 parse_layout_gap("1.5em").unwrap().inner,
2433 PixelValue::em(1.5)
2434 );
2435 assert_eq!(
2436 parse_layout_gap("50%").unwrap().inner,
2437 PixelValue::percent(50.0)
2438 );
2439 assert_eq!(parse_layout_gap("0").unwrap().inner, PixelValue::px(0.0));
2440 assert_eq!(parse_layout_gap("-0").unwrap().inner, PixelValue::px(0.0));
2441 }
2442
2443 #[test]
2444 fn parse_layout_gap_empty_and_garbage_are_err() {
2445 assert!(matches!(
2446 parse_layout_gap("").unwrap_err(),
2447 crate::props::basic::pixel::CssPixelValueParseError::EmptyString
2448 ));
2449 assert!(matches!(
2450 parse_layout_gap(" ").unwrap_err(),
2451 crate::props::basic::pixel::CssPixelValueParseError::EmptyString
2452 ));
2453 for bad in ["px", "10pxpx", "!!!", "\u{1F600}", "\0", "auto", "normal"] {
2454 assert!(parse_layout_gap(bad).is_err(), "{bad:?}");
2455 }
2456 }
2457
2458 #[test]
2459 fn parse_layout_gap_accepts_negative_unitless_and_split_units_is_lax() {
2460 assert_eq!(
2463 parse_layout_gap("-20px").unwrap().inner,
2464 PixelValue::px(-20.0)
2465 );
2466 assert_eq!(parse_layout_gap("10").unwrap().inner, PixelValue::px(10.0));
2467
2468 assert_eq!(
2472 parse_layout_gap("10 px").unwrap().inner,
2473 PixelValue::px(10.0)
2474 );
2475 assert_eq!(
2476 parse_layout_gap("10\tpx").unwrap().inner,
2477 PixelValue::px(10.0)
2478 );
2479 assert_eq!(parse_grid_track_owned("1 fr"), Ok(GridTrackSizing::Fr(100)));
2482 assert!(
2483 parse_grid_template("1 fr").is_err(),
2484 "...but not via the tokenizer"
2485 );
2486 }
2487
2488 #[test]
2489 fn parse_layout_gap_nan_and_inf_do_not_panic_but_are_swallowed() {
2490 assert_eq!(
2494 parse_layout_gap("NaN").unwrap().inner,
2495 PixelValue::px(0.0),
2496 "NaN silently coerces to 0px"
2497 );
2498 assert_eq!(
2499 parse_layout_gap("inf").unwrap().inner,
2500 PixelValue::px(f32::INFINITY),
2501 "inf saturates instead of erroring"
2502 );
2503 assert_eq!(
2504 parse_layout_gap("-inf").unwrap().inner,
2505 PixelValue::px(f32::NEG_INFINITY)
2506 );
2507 assert_eq!(
2508 parse_layout_gap("1e40px").unwrap().inner,
2509 PixelValue::px(f32::INFINITY),
2510 "an f32 overflow saturates rather than wrapping"
2511 );
2512 assert!(parse_layout_gap(&format!("{}px", "9".repeat(10_000))).is_ok());
2514 }
2515
2516 fn area_named<'a>(areas: &'a GridTemplateAreas, name: &str) -> &'a GridAreaDefinition {
2521 areas
2522 .areas
2523 .as_ref()
2524 .iter()
2525 .find(|a| a.name.as_str() == name)
2526 .unwrap_or_else(|| panic!("no area named {name:?}"))
2527 }
2528
2529 #[test]
2530 fn parse_grid_template_areas_valid_minimal() {
2531 let parsed = parse_grid_template_areas("\"a\"").unwrap();
2532 assert_eq!(parsed.areas.len(), 1);
2533 assert_eq!(
2534 parsed.areas.as_ref()[0],
2535 GridAreaDefinition {
2536 name: "a".to_string().into(),
2537 row_start: 1,
2538 row_end: 2,
2539 column_start: 1,
2540 column_end: 2,
2541 },
2542 "1-based, end line is one past the last cell"
2543 );
2544
2545 assert_eq!(
2546 parse_grid_template_areas("none").unwrap(),
2547 GridTemplateAreas::default()
2548 );
2549 assert_eq!(
2550 parse_grid_template_areas(" none ").unwrap(),
2551 GridTemplateAreas::default()
2552 );
2553 }
2554
2555 #[test]
2556 fn parse_grid_template_areas_computes_bounds_and_sorts_by_name() {
2557 let parsed =
2558 parse_grid_template_areas("\"header header\" \"sidebar main\" \"footer footer\"")
2559 .unwrap();
2560 assert_eq!(parsed.areas.len(), 4);
2561
2562 let names: Vec<&str> = parsed
2564 .areas
2565 .as_ref()
2566 .iter()
2567 .map(|a| a.name.as_str())
2568 .collect();
2569 assert_eq!(names, vec!["footer", "header", "main", "sidebar"]);
2570
2571 let header = area_named(&parsed, "header");
2572 assert_eq!((header.row_start, header.row_end), (1, 2));
2573 assert_eq!(
2574 (header.column_start, header.column_end),
2575 (1, 3),
2576 "spans both columns"
2577 );
2578
2579 let sidebar = area_named(&parsed, "sidebar");
2580 assert_eq!((sidebar.row_start, sidebar.row_end), (2, 3));
2581 assert_eq!((sidebar.column_start, sidebar.column_end), (1, 2));
2582
2583 let footer = area_named(&parsed, "footer");
2584 assert_eq!((footer.row_start, footer.row_end), (3, 4));
2585 assert_eq!((footer.column_start, footer.column_end), (1, 3));
2586 }
2587
2588 #[test]
2589 fn parse_grid_template_areas_null_cells_are_skipped() {
2590 let parsed = parse_grid_template_areas("\". a\" \". a\"").unwrap();
2591 assert_eq!(parsed.areas.len(), 1, "'.' never becomes an area");
2592 let a = area_named(&parsed, "a");
2593 assert_eq!((a.row_start, a.row_end), (1, 3));
2594 assert_eq!((a.column_start, a.column_end), (2, 3));
2595
2596 let all_null = parse_grid_template_areas("\". .\" \". .\"").unwrap();
2598 assert_eq!(all_null, GridTemplateAreas::default());
2599 }
2600
2601 #[test]
2602 fn parse_grid_template_areas_rejects_empty_unterminated_and_ragged() {
2603 for bad in [
2604 "", " ",
2606 "\t\n",
2607 "\"\"", "\" \"", "\"abc", "'abc",
2611 "\"a\" \"b", "\"a b\" \"c\"", "\"a\" \"b c\"",
2614 "abc", "\u{1F600}",
2616 ] {
2617 assert_eq!(
2618 parse_grid_template_areas(bad),
2619 Err(()),
2620 "{bad:?} must be rejected"
2621 );
2622 }
2623 }
2624
2625 #[test]
2626 fn parse_grid_template_areas_ignores_junk_outside_the_quotes_is_lax() {
2627 let parsed = parse_grid_template_areas("garbage \"a\" ;;; \u{1F600}").unwrap();
2630 assert_eq!(parsed.areas.len(), 1);
2631 assert_eq!(area_named(&parsed, "a").row_start, 1);
2632
2633 let mixed = parse_grid_template_areas("'a' \"b\"").unwrap();
2635 assert_eq!(mixed.areas.len(), 2);
2636 let weird = parse_grid_template_areas("'a\"b'").unwrap();
2638 assert_eq!(weird.areas.as_ref()[0].name.as_str(), "a\"b");
2639 }
2640
2641 #[test]
2642 fn parse_grid_template_areas_uses_tabs_and_newlines_as_cell_separators() {
2643 let parsed = parse_grid_template_areas("\"a\tb\" \"c\nd\"").unwrap();
2646 assert_eq!(parsed.areas.len(), 4);
2647 for name in ["a", "b", "c", "d"] {
2648 assert_eq!(area_named(&parsed, name).name.as_str(), name);
2649 }
2650 }
2651
2652 #[test]
2653 fn parse_grid_template_areas_non_rectangular_becomes_a_bounding_box() {
2654 let parsed = parse_grid_template_areas("\"b a b\"").unwrap();
2659 let b = area_named(&parsed, "b");
2660 let a = area_named(&parsed, "a");
2661 assert_eq!(
2662 (b.column_start, b.column_end),
2663 (1, 4),
2664 "bounding box, not a rectangle"
2665 );
2666 assert_eq!((a.column_start, a.column_end), (2, 3));
2667 assert!(
2668 a.column_start >= b.column_start && a.column_end <= b.column_end,
2669 "the two areas overlap, which taffy is never supposed to be handed"
2670 );
2671 }
2672
2673 #[test]
2674 fn parse_grid_template_areas_row_bounds_saturate_past_u16_max() {
2675 let mut input = String::with_capacity(65_600 * 4);
2678 for _ in 0..65_599 {
2679 input.push_str("\"a\" ");
2680 }
2681 input.push_str("\"b\"");
2682
2683 let parsed = parse_grid_template_areas(&input).unwrap();
2684 assert_eq!(parsed.areas.len(), 2);
2685
2686 let a = area_named(&parsed, "a");
2687 assert_eq!(a.row_start, 1);
2688 assert_eq!(
2689 a.row_end,
2690 u16::MAX,
2691 "true end line is 65_600; it saturates and the grid is silently truncated"
2692 );
2693
2694 let b = area_named(&parsed, "b");
2698 assert_eq!(b.row_start, u16::MAX);
2699 assert_eq!(b.row_end, u16::MAX);
2700 assert_eq!(
2701 b.row_start, b.row_end,
2702 "zero-height area from double saturation"
2703 );
2704 }
2705
2706 #[test]
2707 fn parse_grid_template_areas_column_bounds_saturate_past_u16_max() {
2708 let mut row = String::with_capacity(70_000 * 2 + 2);
2709 row.push('"');
2710 for _ in 0..70_000 {
2711 row.push_str("a ");
2712 }
2713 row.push('"');
2714
2715 let parsed = parse_grid_template_areas(&row).unwrap();
2716 assert_eq!(parsed.areas.len(), 1);
2717 let a = area_named(&parsed, "a");
2718 assert_eq!(a.column_start, 1);
2719 assert_eq!(
2720 a.column_end,
2721 u16::MAX,
2722 "true end line is 70_001; clamped, not wrapped"
2723 );
2724 assert_eq!((a.row_start, a.row_end), (1, 2));
2725 }
2726
2727 #[test]
2728 fn parse_grid_template_areas_invariant_end_line_exceeds_start_line() {
2729 for input in [
2733 "\"a\"",
2734 "\"a b\" \"c d\"",
2735 "\"h h h\" \"s m a\" \"f f f\"",
2736 "\". a .\" \"b a c\"",
2737 "'x'",
2738 ] {
2739 let parsed = parse_grid_template_areas(input).unwrap();
2740 for area in parsed.areas.as_ref() {
2741 assert!(area.row_start >= 1, "{input:?}: row_start must be 1-based");
2742 assert!(
2743 area.column_start >= 1,
2744 "{input:?}: column_start must be 1-based"
2745 );
2746 assert!(area.row_end > area.row_start, "{input:?}: {area:?}");
2747 assert!(area.column_end > area.column_start, "{input:?}: {area:?}");
2748 }
2749 }
2750 }
2751
2752 #[test]
2757 fn roundtrip_grid_auto_flow_all_variants() {
2758 for value in [
2759 LayoutGridAutoFlow::Row,
2760 LayoutGridAutoFlow::Column,
2761 LayoutGridAutoFlow::RowDense,
2762 LayoutGridAutoFlow::ColumnDense,
2763 ] {
2764 let printed = value.print_as_css_value();
2765 assert_eq!(
2766 parse_layout_grid_auto_flow(&printed),
2767 Ok(value),
2768 "{value:?} printed as {printed:?} must re-parse to itself"
2769 );
2770 }
2771 }
2772
2773 #[test]
2774 fn roundtrip_justify_self_and_justify_items_all_variants() {
2775 for value in [
2776 LayoutJustifySelf::Auto,
2777 LayoutJustifySelf::Start,
2778 LayoutJustifySelf::End,
2779 LayoutJustifySelf::Center,
2780 LayoutJustifySelf::Stretch,
2781 ] {
2782 let printed = value.print_as_css_value();
2783 assert_eq!(parse_layout_justify_self(&printed), Ok(value), "{value:?}");
2784 }
2785 for value in [
2786 LayoutJustifyItems::Start,
2787 LayoutJustifyItems::End,
2788 LayoutJustifyItems::Center,
2789 LayoutJustifyItems::Stretch,
2790 ] {
2791 let printed = value.print_as_css_value();
2792 assert_eq!(parse_layout_justify_items(&printed), Ok(value), "{value:?}");
2793 }
2794 }
2795
2796 #[test]
2797 fn roundtrip_gap_and_fixed_tracks_are_stable() {
2798 for input in [
2799 "0px", "10px", "-20px", "1.5em", "2rem", "50%", "10mm", "1in",
2800 ] {
2801 let gap = parse_layout_gap(input).unwrap();
2802 let printed = gap.print_as_css_value();
2803 assert_eq!(
2804 parse_layout_gap(&printed).unwrap(),
2805 gap,
2806 "gap {input:?} -> {printed:?}"
2807 );
2808 }
2809 for input in [
2810 "auto",
2811 "min-content",
2812 "max-content",
2813 "100px",
2814 "25%",
2815 "fit-content(2px)",
2816 ] {
2817 let track = parse_grid_track_owned(input).unwrap();
2818 let printed = track.print_as_css_value();
2819 assert_eq!(
2820 parse_grid_track_owned(&printed),
2821 Ok(track.clone()),
2822 "track {input:?} -> {printed:?}"
2823 );
2824 }
2825 }
2826
2827 #[test]
2828 fn roundtrip_fr_track_inflates_by_100x_every_cycle() {
2829 let once = parse_grid_track_owned("1fr").unwrap();
2836 assert_eq!(once, GridTrackSizing::Fr(100));
2837 assert_eq!(once.print_as_css_value(), "100fr", "should be \"1fr\"");
2838
2839 let twice = parse_grid_track_owned(&once.print_as_css_value()).unwrap();
2840 assert_eq!(
2841 twice,
2842 GridTrackSizing::Fr(10_000),
2843 "100x inflation per cycle"
2844 );
2845
2846 let thrice = parse_grid_track_owned(&twice.print_as_css_value()).unwrap();
2847 assert_eq!(thrice, GridTrackSizing::Fr(1_000_000));
2848
2849 let four = parse_grid_track_owned(&thrice.print_as_css_value()).unwrap();
2851 assert_eq!(four, GridTrackSizing::Fr(100_000_000));
2852 assert_eq!(
2853 parse_grid_track_owned(&four.print_as_css_value()),
2854 Err(()),
2855 "the fifth cycle exceeds i32 and the declaration is discarded"
2856 );
2857
2858 assert_eq!(
2860 parse_grid_track_owned("minmax(100px, 1fr)")
2861 .unwrap()
2862 .print_as_css_value(),
2863 "minmax(100px, 100fr)"
2864 );
2865 assert_eq!(
2866 parse_grid_template("1fr 2fr").unwrap().print_as_css_value(),
2867 "100fr 200fr"
2868 );
2869 }
2870
2871 #[test]
2872 fn roundtrip_grid_template_none_and_repeat_expansion() {
2873 assert_eq!(GridTemplate::default().print_as_css_value(), "none");
2874 assert_eq!(
2875 parse_grid_template(&GridTemplate::default().print_as_css_value()).unwrap(),
2876 GridTemplate::default()
2877 );
2878
2879 let expanded = parse_grid_template("repeat(3, 100px)").unwrap();
2881 assert_eq!(expanded.print_as_css_value(), "100px 100px 100px");
2882 assert_eq!(
2883 parse_grid_template(&expanded.print_as_css_value()).unwrap(),
2884 expanded
2885 );
2886 }
2887
2888 #[test]
2889 fn roundtrip_grid_auto_tracks_empty_prints_auto_but_reparses_to_one_track() {
2890 let empty = GridAutoTracks::default();
2895 assert_eq!(empty.tracks.len(), 0);
2896 assert_eq!(empty.print_as_css_value(), "auto");
2897
2898 let reparsed: GridAutoTracks = parse_grid_template(&empty.print_as_css_value())
2899 .unwrap()
2900 .into();
2901 assert_eq!(reparsed.tracks.len(), 1);
2902 assert_eq!(reparsed.tracks.as_ref()[0], GridTrackSizing::Auto);
2903 assert_ne!(reparsed, empty);
2904 }
2905
2906 #[test]
2907 fn roundtrip_grid_placement_hides_a_trailing_auto_end() {
2908 for input in [
2911 "auto",
2912 "1",
2913 "-1",
2914 "span 2",
2915 "1 / 3",
2916 "1 / span 2",
2917 "auto / 3",
2918 ] {
2919 let placement = parse_grid_placement(input).unwrap();
2920 let printed = placement.print_as_css_value();
2921 assert_eq!(
2922 parse_grid_placement(&printed).unwrap(),
2923 placement,
2924 "{input:?} printed as {printed:?}"
2925 );
2926 }
2927 assert_eq!(
2928 GridPlacement {
2929 grid_start: GridLine::Line(1),
2930 grid_end: GridLine::Auto
2931 }
2932 .print_as_css_value(),
2933 "1"
2934 );
2935 assert_eq!(
2936 GridPlacement {
2937 grid_start: GridLine::Auto,
2938 grid_end: GridLine::Line(3)
2939 }
2940 .print_as_css_value(),
2941 "auto / 3"
2942 );
2943 }
2944
2945 #[test]
2946 fn roundtrip_named_grid_line_with_a_span_is_lossy() {
2947 let spanned = GridLine::Named(NamedGridLine::create("header".to_string().into(), Some(2)));
2954 assert_eq!(spanned.print_as_css_value(), "header 2");
2955
2956 let reparsed = parse_grid_line_owned(&spanned.print_as_css_value()).unwrap();
2957 assert_eq!(
2958 reparsed,
2959 GridLine::Named(NamedGridLine::create("header 2".to_string().into(), None))
2960 );
2961 assert_ne!(reparsed, spanned);
2962
2963 let plain = GridLine::Named(NamedGridLine::create("header".to_string().into(), None));
2965 assert_eq!(plain.print_as_css_value(), "header");
2966 assert_eq!(
2967 parse_grid_line_owned(&plain.print_as_css_value()),
2968 Ok(plain)
2969 );
2970 }
2971
2972 #[test]
2973 fn roundtrip_grid_template_areas_rectangular_is_stable() {
2974 for input in [
2975 "\"a\"",
2976 "\"a a\"",
2977 "\"h h\" \"s m\"",
2978 "\"a a\" \"a a\"",
2979 "\". a\" \". a\"",
2980 ] {
2981 let parsed = parse_grid_template_areas(input).unwrap();
2982 let printed = parsed.print_as_css_value();
2983 assert_eq!(
2984 parse_grid_template_areas(&printed).unwrap(),
2985 parsed,
2986 "{input:?} printed as {printed:?}"
2987 );
2988 }
2989 assert_eq!(GridTemplateAreas::default().print_as_css_value(), "none");
2990 assert_eq!(
2991 parse_grid_template_areas(&GridTemplateAreas::default().print_as_css_value()).unwrap(),
2992 GridTemplateAreas::default()
2993 );
2994 }
2995
2996 #[test]
2997 fn roundtrip_grid_template_areas_non_rectangular_loses_a_whole_area() {
2998 let parsed = parse_grid_template_areas("\"b a b\"").unwrap();
3003 assert_eq!(parsed.areas.len(), 2);
3004 assert_eq!(
3005 parsed.print_as_css_value(),
3006 "\"b b b\"",
3007 "the `a` area is erased"
3008 );
3009
3010 let reparsed = parse_grid_template_areas(&parsed.print_as_css_value()).unwrap();
3011 assert_eq!(reparsed.areas.len(), 1);
3012 assert_ne!(reparsed, parsed);
3013 }
3014
3015 #[test]
3020 fn grid_parse_error_to_contained_and_back_is_identity() {
3021 for payload in ["", " ", "bogus", "\u{1F600}\u{0301}", "\0", "a\nb\tc"] {
3022 let shared = GridParseError::InvalidValue(payload);
3023 let owned = shared.to_contained();
3024 assert_eq!(
3025 owned,
3026 GridParseErrorOwned::InvalidValue(payload.to_string().into())
3027 );
3028 assert_eq!(
3029 owned.to_shared(),
3030 shared,
3031 "{payload:?} must survive the round-trip"
3032 );
3033 }
3034 let huge = "x".repeat(100_000);
3036 let owned = GridParseError::InvalidValue(&huge).to_contained();
3037 assert_eq!(
3038 owned.to_shared(),
3039 GridParseError::InvalidValue(huge.as_str())
3040 );
3041 }
3042
3043 #[test]
3044 fn grid_auto_flow_parse_error_to_contained_and_back_is_identity() {
3045 for payload in ["", "bogus", "\u{1F600}", "\0"] {
3046 let shared = GridAutoFlowParseError::InvalidValue(payload);
3047 let owned = shared.to_contained();
3048 assert_eq!(
3049 owned,
3050 GridAutoFlowParseErrorOwned::InvalidValue(payload.to_string().into())
3051 );
3052 assert_eq!(owned.to_shared(), shared);
3053 }
3054 let huge = "x".repeat(100_000);
3055 let owned = GridAutoFlowParseError::InvalidValue(&huge).to_contained();
3056 assert_eq!(
3057 owned.to_shared(),
3058 GridAutoFlowParseError::InvalidValue(huge.as_str())
3059 );
3060 }
3061
3062 #[test]
3063 fn justify_self_parse_error_to_contained_and_back_is_identity() {
3064 for payload in ["", "bogus", "\u{1F600}", "\0"] {
3065 let shared = JustifySelfParseError::InvalidValue(payload);
3066 let owned = shared.to_contained();
3067 assert_eq!(
3068 owned,
3069 JustifySelfParseErrorOwned::InvalidValue(payload.to_string().into())
3070 );
3071 assert_eq!(owned.to_shared(), shared);
3072 }
3073 let huge = "x".repeat(100_000);
3074 let owned = JustifySelfParseError::InvalidValue(&huge).to_contained();
3075 assert_eq!(
3076 owned.to_shared(),
3077 JustifySelfParseError::InvalidValue(huge.as_str())
3078 );
3079 }
3080
3081 #[test]
3082 fn justify_items_parse_error_to_contained_and_back_is_identity() {
3083 for payload in ["", "bogus", "\u{1F600}", "\0"] {
3084 let shared = JustifyItemsParseError::InvalidValue(payload);
3085 let owned = shared.to_contained();
3086 assert_eq!(
3087 owned,
3088 JustifyItemsParseErrorOwned::InvalidValue(payload.to_string().into())
3089 );
3090 assert_eq!(owned.to_shared(), shared);
3091 }
3092 let huge = "x".repeat(100_000);
3093 let owned = JustifyItemsParseError::InvalidValue(&huge).to_contained();
3094 assert_eq!(
3095 owned.to_shared(),
3096 JustifyItemsParseError::InvalidValue(huge.as_str())
3097 );
3098 }
3099
3100 #[test]
3101 fn error_types_round_trip_through_a_real_parse_failure() {
3102 let input = String::from(" span x ");
3104 let owned = parse_grid_placement(&input).unwrap_err().to_contained();
3105 drop(input); assert_eq!(owned.to_shared(), GridParseError::InvalidValue("span x"));
3107
3108 let flow = String::from("bogus-flow");
3109 let owned_flow = parse_layout_grid_auto_flow(&flow)
3110 .unwrap_err()
3111 .to_contained();
3112 drop(flow);
3113 assert_eq!(
3114 owned_flow.to_shared(),
3115 GridAutoFlowParseError::InvalidValue("bogus-flow")
3116 );
3117 }
3118
3119 #[test]
3124 fn debug_impls_match_print_as_css_value() {
3125 let track = parse_grid_track_owned("minmax(100px, max-content)").unwrap();
3128 assert_eq!(format!("{track:?}"), track.print_as_css_value());
3129
3130 let template = parse_grid_template("100px auto").unwrap();
3131 assert_eq!(format!("{template:?}"), template.print_as_css_value());
3132
3133 let placement = parse_grid_placement("1 / span 2").unwrap();
3134 assert_eq!(format!("{placement:?}"), placement.print_as_css_value());
3135
3136 let minmax = GridMinMax {
3137 min: Box::new(GridTrackSizing::Fixed(PixelValue::px(1.0))),
3138 max: Box::new(GridTrackSizing::Auto),
3139 };
3140 assert_eq!(format!("{minmax:?}"), "minmax(1px, auto)");
3141 }
3142}