1use alloc::string::{String, ToString};
4use crate::corety::AzString;
5
6use crate::props::formatter::PrintAsCssValue;
7
8#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(C)]
13pub enum LayoutDisplay {
14 None,
16 #[default]
18 Block,
19 Inline,
20 InlineBlock,
21
22 Flex,
24 InlineFlex,
25
26 Table,
34 InlineTable,
35 TableRowGroup,
36 TableHeaderGroup,
37 TableFooterGroup,
38 TableRow,
39 TableColumnGroup,
40 TableColumn,
41 TableCell,
42 TableCaption,
43
44 FlowRoot,
45
46 ListItem,
48
49 RunIn,
51 Marker,
52
53 Grid,
55 InlineGrid,
56
57 Contents,
59}
60
61impl LayoutDisplay {
62 #[must_use] pub const fn creates_block_context(&self) -> bool {
64 matches!(
65 self,
66 Self::Block
67 | Self::FlowRoot
68 | Self::Flex
69 | Self::Grid
70 | Self::Table
71 | Self::ListItem
72 )
73 }
74
75 #[must_use] pub const fn creates_flex_context(&self) -> bool {
77 matches!(self, Self::Flex | Self::InlineFlex)
78 }
79
80 #[must_use] pub const fn creates_table_context(&self) -> bool {
83 matches!(self, Self::Table | Self::InlineTable)
84 }
85
86 #[must_use] pub const fn is_layout_internal(&self) -> bool {
90 matches!(
91 self,
92 Self::TableRowGroup
93 | Self::TableHeaderGroup
94 | Self::TableFooterGroup
95 | Self::TableRow
96 | Self::TableColumnGroup
97 | Self::TableColumn
98 | Self::TableCell
99 | Self::TableCaption
100 )
101 }
102
103 #[must_use] pub const fn is_inline_level(&self) -> bool {
108 matches!(
109 self,
110 Self::Inline
111 | Self::InlineBlock
112 | Self::InlineFlex
113 | Self::InlineTable
114 | Self::InlineGrid
115 )
116 }
117
118 #[must_use] pub const fn is_atomic_inline(&self) -> bool {
127 matches!(
128 self,
129 Self::InlineBlock | Self::InlineFlex | Self::InlineTable | Self::InlineGrid
130 )
131 }
132}
133
134impl PrintAsCssValue for LayoutDisplay {
136 fn print_as_css_value(&self) -> String {
137 String::from(match self {
138 Self::None => "none",
139 Self::Block => "block",
140 Self::Inline => "inline",
141 Self::InlineBlock => "inline-block",
142 Self::Flex => "flex",
143 Self::InlineFlex => "inline-flex",
144 Self::Table => "table",
145 Self::InlineTable => "inline-table",
146 Self::TableRowGroup => "table-row-group",
147 Self::TableHeaderGroup => "table-header-group",
148 Self::TableFooterGroup => "table-footer-group",
149 Self::TableRow => "table-row",
150 Self::TableColumnGroup => "table-column-group",
151 Self::TableColumn => "table-column",
152 Self::TableCell => "table-cell",
153 Self::TableCaption => "table-caption",
154 Self::ListItem => "list-item",
155 Self::RunIn => "run-in",
156 Self::Marker => "marker",
157 Self::FlowRoot => "flow-root",
158 Self::Grid => "grid",
159 Self::InlineGrid => "inline-grid",
160 Self::Contents => "contents",
161 })
162 }
163}
164
165#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
167#[repr(C)]
168pub enum LayoutFloat {
169 Left,
170 Right,
171 #[default]
172 None,
173}
174
175impl PrintAsCssValue for LayoutFloat {
176 fn print_as_css_value(&self) -> String {
177 String::from(match self {
178 Self::Left => "left",
179 Self::Right => "right",
180 Self::None => "none",
181 })
182 }
183}
184
185#[cfg(feature = "parser")]
188#[derive(Clone, PartialEq, Eq)]
189pub enum LayoutDisplayParseError<'a> {
190 InvalidValue(&'a str),
191}
192
193#[cfg(feature = "parser")]
194impl_debug_as_display!(LayoutDisplayParseError<'a>);
195
196#[cfg(feature = "parser")]
197impl_display! { LayoutDisplayParseError<'a>, {
198 InvalidValue(val) => format!("Invalid display value: \"{}\"", val),
199}}
200
201#[cfg(feature = "parser")]
202#[derive(Debug, Clone, PartialEq, Eq)]
203#[repr(C, u8)]
204pub enum LayoutDisplayParseErrorOwned {
205 InvalidValue(AzString),
206}
207
208#[cfg(feature = "parser")]
209impl LayoutDisplayParseError<'_> {
210 #[must_use] pub fn to_contained(&self) -> LayoutDisplayParseErrorOwned {
211 match self {
212 Self::InvalidValue(s) => LayoutDisplayParseErrorOwned::InvalidValue((*s).to_string().into()),
213 }
214 }
215}
216
217#[cfg(feature = "parser")]
218impl LayoutDisplayParseErrorOwned {
219 #[must_use] pub fn to_shared(&self) -> LayoutDisplayParseError<'_> {
220 match self {
221 Self::InvalidValue(s) => LayoutDisplayParseError::InvalidValue(s.as_str()),
222 }
223 }
224}
225
226#[cfg(feature = "parser")]
227pub fn parse_layout_display(
231 input: &str,
232) -> Result<LayoutDisplay, LayoutDisplayParseError<'_>> {
233 let input = input.trim();
234 match input {
235 "none" => Ok(LayoutDisplay::None),
236 "block" => Ok(LayoutDisplay::Block),
237 "inline" => Ok(LayoutDisplay::Inline),
238 "inline-block" => Ok(LayoutDisplay::InlineBlock),
240 "flex" => Ok(LayoutDisplay::Flex),
241 "inline-flex" => Ok(LayoutDisplay::InlineFlex),
242 "table" => Ok(LayoutDisplay::Table),
243 "inline-table" => Ok(LayoutDisplay::InlineTable),
244 "table-row-group" => Ok(LayoutDisplay::TableRowGroup),
245 "table-header-group" => Ok(LayoutDisplay::TableHeaderGroup),
246 "table-footer-group" => Ok(LayoutDisplay::TableFooterGroup),
247 "table-row" => Ok(LayoutDisplay::TableRow),
248 "table-column-group" => Ok(LayoutDisplay::TableColumnGroup),
249 "table-column" => Ok(LayoutDisplay::TableColumn),
250 "table-cell" => Ok(LayoutDisplay::TableCell),
251 "table-caption" => Ok(LayoutDisplay::TableCaption),
252 "list-item" => Ok(LayoutDisplay::ListItem),
253 "run-in" => Ok(LayoutDisplay::RunIn),
254 "marker" => Ok(LayoutDisplay::Marker),
255 "grid" => Ok(LayoutDisplay::Grid),
256 "inline-grid" => Ok(LayoutDisplay::InlineGrid),
257 "flow-root" => Ok(LayoutDisplay::FlowRoot),
258 "contents" => Ok(LayoutDisplay::Contents),
259 _ => Err(LayoutDisplayParseError::InvalidValue(input)),
260 }
261}
262
263#[cfg(feature = "parser")]
264#[derive(Clone, PartialEq, Eq)]
265pub enum LayoutFloatParseError<'a> {
266 InvalidValue(&'a str),
267}
268
269#[cfg(feature = "parser")]
270impl_debug_as_display!(LayoutFloatParseError<'a>);
271
272#[cfg(feature = "parser")]
273impl_display! { LayoutFloatParseError<'a>, {
274 InvalidValue(val) => format!("Invalid float value: \"{}\"", val),
275}}
276
277#[cfg(feature = "parser")]
278#[derive(Debug, Clone, PartialEq, Eq)]
279#[repr(C, u8)]
280pub enum LayoutFloatParseErrorOwned {
281 InvalidValue(AzString),
282}
283
284#[cfg(feature = "parser")]
285impl LayoutFloatParseError<'_> {
286 #[must_use] pub fn to_contained(&self) -> LayoutFloatParseErrorOwned {
287 match self {
288 Self::InvalidValue(s) => LayoutFloatParseErrorOwned::InvalidValue((*s).to_string().into()),
289 }
290 }
291}
292
293#[cfg(feature = "parser")]
294impl LayoutFloatParseErrorOwned {
295 #[must_use] pub fn to_shared(&self) -> LayoutFloatParseError<'_> {
296 match self {
297 Self::InvalidValue(s) => LayoutFloatParseError::InvalidValue(s.as_str()),
298 }
299 }
300}
301
302#[cfg(feature = "parser")]
303pub fn parse_layout_float(input: &str) -> Result<LayoutFloat, LayoutFloatParseError<'_>> {
307 let input = input.trim();
308 match input {
309 "left" => Ok(LayoutFloat::Left),
310 "right" => Ok(LayoutFloat::Right),
311 "none" => Ok(LayoutFloat::None),
312 _ => Err(LayoutFloatParseError::InvalidValue(input)),
313 }
314}
315
316#[cfg(all(test, feature = "parser"))]
317mod tests {
318 use super::*;
319
320 #[test]
321 #[allow(clippy::cognitive_complexity)] fn test_parse_layout_display() {
323 assert_eq!(parse_layout_display("block").unwrap(), LayoutDisplay::Block);
324 assert_eq!(
325 parse_layout_display("inline").unwrap(),
326 LayoutDisplay::Inline
327 );
328 assert_eq!(
329 parse_layout_display("inline-block").unwrap(),
330 LayoutDisplay::InlineBlock
331 );
332 assert_eq!(parse_layout_display("flex").unwrap(), LayoutDisplay::Flex);
333 assert_eq!(
334 parse_layout_display("inline-flex").unwrap(),
335 LayoutDisplay::InlineFlex
336 );
337 assert_eq!(parse_layout_display("grid").unwrap(), LayoutDisplay::Grid);
338 assert_eq!(
339 parse_layout_display("inline-grid").unwrap(),
340 LayoutDisplay::InlineGrid
341 );
342 assert_eq!(parse_layout_display("none").unwrap(), LayoutDisplay::None);
343 assert_eq!(
344 parse_layout_display("flow-root").unwrap(),
345 LayoutDisplay::FlowRoot
346 );
347 assert_eq!(
348 parse_layout_display("list-item").unwrap(),
349 LayoutDisplay::ListItem
350 );
351 assert!(parse_layout_display("inherit").is_err());
354 assert!(parse_layout_display("initial").is_err());
355
356 assert_eq!(parse_layout_display("table").unwrap(), LayoutDisplay::Table);
358 assert_eq!(
359 parse_layout_display("inline-table").unwrap(),
360 LayoutDisplay::InlineTable
361 );
362 assert_eq!(
363 parse_layout_display("table-row").unwrap(),
364 LayoutDisplay::TableRow
365 );
366 assert_eq!(
367 parse_layout_display("table-cell").unwrap(),
368 LayoutDisplay::TableCell
369 );
370 assert_eq!(
371 parse_layout_display("table-caption").unwrap(),
372 LayoutDisplay::TableCaption
373 );
374 assert_eq!(
375 parse_layout_display("table-column-group").unwrap(),
376 LayoutDisplay::TableColumnGroup
377 );
378 assert_eq!(
379 parse_layout_display("table-header-group").unwrap(),
380 LayoutDisplay::TableHeaderGroup
381 );
382 assert_eq!(
383 parse_layout_display("table-footer-group").unwrap(),
384 LayoutDisplay::TableFooterGroup
385 );
386 assert_eq!(
387 parse_layout_display("table-row-group").unwrap(),
388 LayoutDisplay::TableRowGroup
389 );
390
391 assert_eq!(
393 parse_layout_display(" inline-flex ").unwrap(),
394 LayoutDisplay::InlineFlex
395 );
396
397 assert!(parse_layout_display("invalid-value").is_err());
399 assert!(parse_layout_display("").is_err());
400 assert!(parse_layout_display("display").is_err());
401 }
402
403 #[test]
404 fn test_parse_layout_float() {
405 assert_eq!(parse_layout_float("left").unwrap(), LayoutFloat::Left);
406 assert_eq!(parse_layout_float("right").unwrap(), LayoutFloat::Right);
407 assert_eq!(parse_layout_float("none").unwrap(), LayoutFloat::None);
408
409 assert_eq!(parse_layout_float(" right ").unwrap(), LayoutFloat::Right);
411
412 assert!(parse_layout_float("center").is_err());
414 assert!(parse_layout_float("").is_err());
415 assert!(parse_layout_float("float-left").is_err());
416 }
417}
418
419#[cfg(all(test, feature = "parser"))]
420mod autotest_generated {
421 use super::*;
422
423 const ALL_DISPLAY: [LayoutDisplay; 23] = [
426 LayoutDisplay::None,
427 LayoutDisplay::Block,
428 LayoutDisplay::Inline,
429 LayoutDisplay::InlineBlock,
430 LayoutDisplay::Flex,
431 LayoutDisplay::InlineFlex,
432 LayoutDisplay::Table,
433 LayoutDisplay::InlineTable,
434 LayoutDisplay::TableRowGroup,
435 LayoutDisplay::TableHeaderGroup,
436 LayoutDisplay::TableFooterGroup,
437 LayoutDisplay::TableRow,
438 LayoutDisplay::TableColumnGroup,
439 LayoutDisplay::TableColumn,
440 LayoutDisplay::TableCell,
441 LayoutDisplay::TableCaption,
442 LayoutDisplay::FlowRoot,
443 LayoutDisplay::ListItem,
444 LayoutDisplay::RunIn,
445 LayoutDisplay::Marker,
446 LayoutDisplay::Grid,
447 LayoutDisplay::InlineGrid,
448 LayoutDisplay::Contents,
449 ];
450
451 const ALL_FLOAT: [LayoutFloat; 3] = [LayoutFloat::Left, LayoutFloat::Right, LayoutFloat::None];
452
453 const fn display_variant_index(d: LayoutDisplay) -> usize {
457 match d {
458 LayoutDisplay::None => 0,
459 LayoutDisplay::Block => 1,
460 LayoutDisplay::Inline => 2,
461 LayoutDisplay::InlineBlock => 3,
462 LayoutDisplay::Flex => 4,
463 LayoutDisplay::InlineFlex => 5,
464 LayoutDisplay::Table => 6,
465 LayoutDisplay::InlineTable => 7,
466 LayoutDisplay::TableRowGroup => 8,
467 LayoutDisplay::TableHeaderGroup => 9,
468 LayoutDisplay::TableFooterGroup => 10,
469 LayoutDisplay::TableRow => 11,
470 LayoutDisplay::TableColumnGroup => 12,
471 LayoutDisplay::TableColumn => 13,
472 LayoutDisplay::TableCell => 14,
473 LayoutDisplay::TableCaption => 15,
474 LayoutDisplay::FlowRoot => 16,
475 LayoutDisplay::ListItem => 17,
476 LayoutDisplay::RunIn => 18,
477 LayoutDisplay::Marker => 19,
478 LayoutDisplay::Grid => 20,
479 LayoutDisplay::InlineGrid => 21,
480 LayoutDisplay::Contents => 22,
481 }
482 }
483
484 #[test]
489 fn all_display_lists_every_variant_exactly_once() {
490 for (i, d) in ALL_DISPLAY.iter().enumerate() {
491 assert_eq!(
492 display_variant_index(*d),
493 i,
494 "ALL_DISPLAY is out of sync at index {i} ({d:?})"
495 );
496 }
497 }
498
499 #[test]
504 fn creates_block_context_matches_an_exact_variant_set() {
505 const EXPECTED: [LayoutDisplay; 6] = [
511 LayoutDisplay::Block,
512 LayoutDisplay::FlowRoot,
513 LayoutDisplay::Flex,
514 LayoutDisplay::Grid,
515 LayoutDisplay::Table,
516 LayoutDisplay::ListItem,
517 ];
518 for d in ALL_DISPLAY {
519 assert_eq!(
520 d.creates_block_context(),
521 EXPECTED.contains(&d),
522 "creates_block_context({d:?})"
523 );
524 }
525 }
526
527 #[test]
528 fn creates_flex_context_matches_an_exact_variant_set() {
529 const EXPECTED: [LayoutDisplay; 2] = [LayoutDisplay::Flex, LayoutDisplay::InlineFlex];
530 for d in ALL_DISPLAY {
531 assert_eq!(
532 d.creates_flex_context(),
533 EXPECTED.contains(&d),
534 "creates_flex_context({d:?})"
535 );
536 }
537 }
538
539 #[test]
540 fn creates_table_context_matches_an_exact_variant_set() {
541 const EXPECTED: [LayoutDisplay; 2] = [LayoutDisplay::Table, LayoutDisplay::InlineTable];
544 for d in ALL_DISPLAY {
545 assert_eq!(
546 d.creates_table_context(),
547 EXPECTED.contains(&d),
548 "creates_table_context({d:?})"
549 );
550 }
551 }
552
553 #[test]
558 fn is_layout_internal_matches_css_display_3_table_internals() {
559 const EXPECTED: [LayoutDisplay; 8] = [
560 LayoutDisplay::TableRowGroup,
561 LayoutDisplay::TableHeaderGroup,
562 LayoutDisplay::TableFooterGroup,
563 LayoutDisplay::TableRow,
564 LayoutDisplay::TableColumnGroup,
565 LayoutDisplay::TableColumn,
566 LayoutDisplay::TableCell,
567 LayoutDisplay::TableCaption,
568 ];
569 for d in ALL_DISPLAY {
570 assert_eq!(
571 d.is_layout_internal(),
572 EXPECTED.contains(&d),
573 "is_layout_internal({d:?})"
574 );
575 }
576 assert!(!LayoutDisplay::Table.is_layout_internal());
578 assert!(!LayoutDisplay::InlineTable.is_layout_internal());
579 }
580
581 #[test]
582 fn is_inline_level_matches_an_exact_variant_set() {
583 const EXPECTED: [LayoutDisplay; 5] = [
584 LayoutDisplay::Inline,
585 LayoutDisplay::InlineBlock,
586 LayoutDisplay::InlineFlex,
587 LayoutDisplay::InlineTable,
588 LayoutDisplay::InlineGrid,
589 ];
590 for d in ALL_DISPLAY {
591 assert_eq!(
592 d.is_inline_level(),
593 EXPECTED.contains(&d),
594 "is_inline_level({d:?})"
595 );
596 }
597 assert!(!LayoutDisplay::Flex.is_inline_level());
599 assert!(!LayoutDisplay::Grid.is_inline_level());
600 assert!(!LayoutDisplay::Table.is_inline_level());
601 assert!(!LayoutDisplay::FlowRoot.is_inline_level());
602 }
603
604 #[test]
605 fn is_atomic_inline_matches_an_exact_variant_set() {
606 const EXPECTED: [LayoutDisplay; 4] = [
610 LayoutDisplay::InlineBlock,
611 LayoutDisplay::InlineFlex,
612 LayoutDisplay::InlineTable,
613 LayoutDisplay::InlineGrid,
614 ];
615 for d in ALL_DISPLAY {
616 assert_eq!(d.is_atomic_inline(), EXPECTED.contains(&d), "is_atomic_inline({d:?})");
617 if d.is_atomic_inline() {
619 assert!(d.is_inline_level(), "atomic-inline must be inline-level ({d:?})");
620 }
621 }
622 assert!(!LayoutDisplay::Inline.is_atomic_inline());
623 assert!(LayoutDisplay::Inline.is_inline_level());
624 }
625
626 #[test]
631 fn flex_and_table_contexts_are_mutually_exclusive() {
632 for d in ALL_DISPLAY {
633 assert!(
634 !(d.creates_flex_context() && d.creates_table_context()),
635 "{d:?} claims to establish both a flex and a table formatting context"
636 );
637 }
638 }
639
640 #[test]
641 fn layout_internal_and_inline_level_are_mutually_exclusive() {
642 for d in ALL_DISPLAY {
643 assert!(
644 !(d.is_layout_internal() && d.is_inline_level()),
645 "{d:?} is both layout-internal and inline-level"
646 );
647 }
648 }
649
650 #[test]
651 fn boxless_displays_establish_and_generate_nothing() {
652 for d in [LayoutDisplay::None, LayoutDisplay::Contents] {
656 assert!(!d.creates_block_context(), "{d:?}");
657 assert!(!d.creates_flex_context(), "{d:?}");
658 assert!(!d.creates_table_context(), "{d:?}");
659 assert!(!d.is_layout_internal(), "{d:?}");
660 assert!(!d.is_inline_level(), "{d:?}");
661 }
662 }
663
664 #[test]
665 fn predicates_on_the_default_instance_do_not_panic() {
666 let d = LayoutDisplay::default();
668 assert_eq!(d, LayoutDisplay::Block);
669 assert!(d.creates_block_context());
670 assert!(!d.creates_flex_context());
671 assert!(!d.creates_table_context());
672 assert!(!d.is_layout_internal());
673 assert!(!d.is_inline_level());
674
675 assert_eq!(LayoutFloat::default(), LayoutFloat::None);
676 }
677
678 #[test]
679 fn predicates_are_pure_and_repeatable() {
680 for d in ALL_DISPLAY {
681 let snapshot = (
682 d.creates_block_context(),
683 d.creates_flex_context(),
684 d.creates_table_context(),
685 d.is_layout_internal(),
686 d.is_inline_level(),
687 );
688 for _ in 0..4 {
689 assert_eq!(
690 (
691 d.creates_block_context(),
692 d.creates_flex_context(),
693 d.creates_table_context(),
694 d.is_layout_internal(),
695 d.is_inline_level(),
696 ),
697 snapshot,
698 "predicates are not deterministic for {d:?}"
699 );
700 }
701 }
702 }
703
704 #[test]
705 fn predicates_are_usable_in_const_context() {
706 const BLOCK_CTX: bool = LayoutDisplay::FlowRoot.creates_block_context();
709 const FLEX_CTX: bool = LayoutDisplay::InlineFlex.creates_flex_context();
710 const TABLE_CTX: bool = LayoutDisplay::InlineTable.creates_table_context();
711 const INTERNAL: bool = LayoutDisplay::TableCell.is_layout_internal();
712 const INLINE: bool = LayoutDisplay::InlineGrid.is_inline_level();
713 const _: () = assert!(BLOCK_CTX && FLEX_CTX && TABLE_CTX && INTERNAL && INLINE);
714 }
715
716 #[test]
721 fn display_round_trips_through_its_css_serialization() {
722 for d in ALL_DISPLAY {
723 let printed = d.print_as_css_value();
724 assert_eq!(
725 parse_layout_display(&printed),
726 Ok(d),
727 "round-trip failed for {d:?} (printed as {printed:?})"
728 );
729 }
730 }
731
732 #[test]
733 fn float_round_trips_through_its_css_serialization() {
734 for f in ALL_FLOAT {
735 let printed = f.print_as_css_value();
736 assert_eq!(
737 parse_layout_float(&printed),
738 Ok(f),
739 "round-trip failed for {f:?} (printed as {printed:?})"
740 );
741 }
742 }
743
744 #[test]
745 fn display_serializations_are_unique_bare_idents() {
746 for (i, a) in ALL_DISPLAY.iter().enumerate() {
747 let printed = a.print_as_css_value();
748 assert!(!printed.is_empty(), "{a:?} serializes to the empty string");
749 assert_eq!(printed.trim(), printed, "{a:?} serializes with padding");
750 assert!(
751 printed
752 .chars()
753 .all(|c| c.is_ascii_lowercase() || c == '-'),
754 "{a:?} serializes to a non-ident {printed:?}"
755 );
756 for b in &ALL_DISPLAY[i + 1..] {
758 assert_ne!(
759 printed,
760 b.print_as_css_value(),
761 "{a:?} and {b:?} share a CSS keyword"
762 );
763 }
764 }
765 }
766
767 #[test]
768 fn parse_then_print_is_idempotent() {
769 for keyword in [
770 "none",
771 "block",
772 "inline",
773 "inline-block",
774 "flex",
775 "inline-flex",
776 "table",
777 "inline-table",
778 "table-row-group",
779 "table-header-group",
780 "table-footer-group",
781 "table-row",
782 "table-column-group",
783 "table-column",
784 "table-cell",
785 "table-caption",
786 "list-item",
787 "run-in",
788 "marker",
789 "grid",
790 "inline-grid",
791 "flow-root",
792 "contents",
793 ] {
794 let parsed = parse_layout_display(keyword)
795 .unwrap_or_else(|e| panic!("{keyword:?} must parse, got {e}"));
796 assert_eq!(parsed.print_as_css_value(), keyword);
797 }
798 for keyword in ["left", "right", "none"] {
799 let parsed = parse_layout_float(keyword)
800 .unwrap_or_else(|e| panic!("{keyword:?} must parse, got {e}"));
801 assert_eq!(parsed.print_as_css_value(), keyword);
802 }
803 }
804
805 #[test]
810 fn parsers_reject_empty_and_whitespace_only_input() {
811 for blank in ["", " ", " ", "\t", "\n", "\r\n", "\t\n\r ", "\u{c}", "\u{b}"] {
812 assert!(
813 parse_layout_display(blank).is_err(),
814 "display accepted blank {blank:?}"
815 );
816 assert!(
817 parse_layout_float(blank).is_err(),
818 "float accepted blank {blank:?}"
819 );
820 }
821 assert_eq!(
823 parse_layout_display(" "),
824 Err(LayoutDisplayParseError::InvalidValue(""))
825 );
826 assert_eq!(
827 parse_layout_float("\t\n"),
828 Err(LayoutFloatParseError::InvalidValue(""))
829 );
830 }
831
832 #[test]
833 fn display_parser_rejects_garbage_without_panicking() {
834 for bad in [
835 "invalid-value",
836 "display",
837 "display: block",
838 "block;",
839 "block ;",
840 "block!important",
841 "block block",
842 "inline block",
843 "inline_block",
844 "inline--block",
845 "-inline-block",
846 "inline-block-",
847 "-webkit-box",
848 "block flow", "inline flow-root",
850 "table-column-groups",
851 "tablerow",
852 "\0",
853 "blo\0ck",
854 "block\0",
855 "inherit",
856 "initial",
857 "unset",
858 "revert",
859 "\\62 lock", "/*block*/",
861 "\"block\"",
862 ] {
863 assert!(
864 parse_layout_display(bad).is_err(),
865 "display accepted garbage {bad:?}"
866 );
867 }
868 }
869
870 #[test]
871 fn float_parser_rejects_garbage_without_panicking() {
872 for bad in [
873 "center",
874 "float-left",
875 "left right",
876 "leftright",
877 "inline-start",
878 "inline-end",
879 "left;",
880 "left!important",
881 "\0",
882 "le\0ft",
883 "inherit",
884 "initial",
885 "footnote",
886 ] {
887 assert!(
888 parse_layout_float(bad).is_err(),
889 "float accepted garbage {bad:?}"
890 );
891 }
892 }
893
894 #[test]
895 fn parsers_are_ascii_case_sensitive() {
896 for bad in ["BLOCK", "Block", "bLoCk", "INLINE-FLEX", "Table-Cell"] {
901 assert!(
902 parse_layout_display(bad).is_err(),
903 "display unexpectedly accepted {bad:?}"
904 );
905 }
906 for bad in ["LEFT", "Left", "RIGHT", "None"] {
907 assert!(
908 parse_layout_float(bad).is_err(),
909 "float unexpectedly accepted {bad:?}"
910 );
911 }
912 }
913
914 #[test]
915 fn parsers_trim_leading_and_trailing_whitespace_but_not_junk() {
916 assert_eq!(
917 parse_layout_display(" inline-flex "),
918 Ok(LayoutDisplay::InlineFlex)
919 );
920 assert_eq!(
921 parse_layout_display("\n\t table-cell \r\n"),
922 Ok(LayoutDisplay::TableCell)
923 );
924 assert_eq!(parse_layout_float("\n right \t"), Ok(LayoutFloat::Right));
925
926 assert!(parse_layout_display("inline - flex").is_err());
928 assert!(parse_layout_display("table-cell;garbage").is_err());
929 assert!(parse_layout_float("right;garbage").is_err());
930 }
931
932 #[test]
933 fn parsers_trim_unicode_whitespace_but_not_zero_width_characters() {
934 assert_eq!(
938 parse_layout_display("\u{a0}block\u{a0}"),
939 Ok(LayoutDisplay::Block)
940 );
941 assert_eq!(
942 parse_layout_display("\u{3000}flex\u{2028}"),
943 Ok(LayoutDisplay::Flex)
944 );
945 assert_eq!(parse_layout_float("\u{a0}left\u{a0}"), Ok(LayoutFloat::Left));
946
947 assert!(parse_layout_display("\u{200b}block").is_err());
950 assert!(parse_layout_display("block\u{feff}").is_err());
951 assert!(parse_layout_float("\u{200b}left").is_err());
952 }
953
954 #[test]
955 fn parsers_survive_non_ascii_and_multibyte_input() {
956 for bad in [
957 "\u{1F600}",
958 "block\u{1F600}",
959 "\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}", "blocke\u{301}", "\u{202E}block", "block", "блок",
964 "块",
965 "\u{0}\u{1}\u{2}",
966 "\u{10FFFF}",
967 ] {
968 assert!(
969 parse_layout_display(bad).is_err(),
970 "display accepted unicode garbage {bad:?}"
971 );
972 assert!(
973 parse_layout_float(bad).is_err(),
974 "float accepted unicode garbage {bad:?}"
975 );
976 }
977 }
978
979 #[test]
980 fn parsers_reject_boundary_numeric_strings() {
981 let numeric = [
982 "0".to_string(),
983 "-0".to_string(),
984 "+0".to_string(),
985 "1".to_string(),
986 "-1".to_string(),
987 i64::MAX.to_string(),
988 i64::MIN.to_string(),
989 u64::MAX.to_string(),
990 format!("{}", f64::MAX),
991 format!("{}", f64::MIN_POSITIVE),
992 "NaN".to_string(),
993 "nan".to_string(),
994 "inf".to_string(),
995 "-inf".to_string(),
996 "infinity".to_string(),
997 "1e400".to_string(),
998 "-1e-400".to_string(),
999 "0x7fffffffffffffff".to_string(),
1000 "99999999999999999999999999999999".to_string(),
1001 ];
1002 for n in &numeric {
1003 assert!(
1004 parse_layout_display(n).is_err(),
1005 "display accepted number {n:?}"
1006 );
1007 assert!(parse_layout_float(n).is_err(), "float accepted number {n:?}");
1008 }
1009 }
1010
1011 #[test]
1012 fn parsers_do_not_hang_on_extremely_long_input() {
1013 const LONG: usize = 1_000_000;
1014
1015 let junk = "x".repeat(LONG);
1016 assert!(parse_layout_display(&junk).is_err());
1017 assert!(parse_layout_float(&junk).is_err());
1018
1019 let near_miss = format!("block{}", "k".repeat(LONG));
1021 assert!(parse_layout_display(&near_miss).is_err());
1022
1023 let blanks = " ".repeat(LONG);
1025 assert!(parse_layout_display(&blanks).is_err());
1026 assert!(parse_layout_float(&blanks).is_err());
1027
1028 let padded = format!("{blanks}table-caption{blanks}");
1030 assert_eq!(
1031 parse_layout_display(&padded),
1032 Ok(LayoutDisplay::TableCaption)
1033 );
1034 let padded_float = format!("{blanks}left{blanks}");
1035 assert_eq!(parse_layout_float(&padded_float), Ok(LayoutFloat::Left));
1036 }
1037
1038 #[test]
1039 fn parsers_do_not_stack_overflow_on_deeply_nested_input() {
1040 const DEPTH: usize = 10_000;
1041 let nested = format!("{}{}", "(".repeat(DEPTH), ")".repeat(DEPTH));
1042 assert!(parse_layout_display(&nested).is_err());
1043 assert!(parse_layout_float(&nested).is_err());
1044
1045 let nested_fn = format!("{}block{}", "calc(".repeat(DEPTH), ")".repeat(DEPTH));
1046 assert!(parse_layout_display(&nested_fn).is_err());
1047 }
1048
1049 #[test]
1054 fn display_error_reports_the_trimmed_input() {
1055 assert_eq!(
1056 parse_layout_display(" bogus "),
1057 Err(LayoutDisplayParseError::InvalidValue("bogus"))
1058 );
1059 assert_eq!(
1061 parse_layout_display("\u{a0}bogus\u{3000}"),
1062 Err(LayoutDisplayParseError::InvalidValue("bogus"))
1063 );
1064 }
1065
1066 #[test]
1067 fn float_error_reports_the_trimmed_input() {
1068 assert_eq!(
1069 parse_layout_float(" bogus "),
1070 Err(LayoutFloatParseError::InvalidValue("bogus"))
1071 );
1072 }
1073
1074 #[test]
1075 fn parse_errors_display_and_debug_identically() {
1076 let d = parse_layout_display("bogus").unwrap_err();
1077 assert_eq!(format!("{d}"), "Invalid display value: \"bogus\"");
1078 assert_eq!(format!("{d:?}"), format!("{d}"));
1079
1080 let f = parse_layout_float("bogus").unwrap_err();
1081 assert_eq!(format!("{f}"), "Invalid float value: \"bogus\"");
1082 assert_eq!(format!("{f:?}"), format!("{f}"));
1083
1084 let empty = parse_layout_display("").unwrap_err();
1086 assert_eq!(format!("{empty}"), "Invalid display value: \"\"");
1087 }
1088
1089 #[test]
1090 fn display_error_to_contained_to_shared_round_trips() {
1091 let long = "x".repeat(4096);
1092 let payloads: [&str; 8] = [
1093 "",
1094 " ",
1095 "bogus",
1096 "\u{1F600}",
1097 "a\0b",
1098 "block block",
1099 "\"quoted\"",
1100 &long,
1101 ];
1102 for payload in payloads {
1103 let shared = LayoutDisplayParseError::InvalidValue(payload);
1104 let owned = shared.to_contained();
1105 assert_eq!(
1106 owned,
1107 LayoutDisplayParseErrorOwned::InvalidValue(payload.to_string().into()),
1108 "to_contained lost the payload {payload:?}"
1109 );
1110 assert_eq!(
1111 owned.to_shared(),
1112 shared,
1113 "to_shared did not restore the payload {payload:?}"
1114 );
1115 let LayoutDisplayParseErrorOwned::InvalidValue(s) = &owned;
1117 assert_eq!(s.as_str(), payload);
1118 }
1119 }
1120
1121 #[test]
1122 fn float_error_to_contained_to_shared_round_trips() {
1123 let long = "y".repeat(4096);
1124 let payloads: [&str; 6] = ["", " ", "bogus", "\u{1F600}", "a\0b", &long];
1125 for payload in payloads {
1126 let shared = LayoutFloatParseError::InvalidValue(payload);
1127 let owned = shared.to_contained();
1128 assert_eq!(
1129 owned,
1130 LayoutFloatParseErrorOwned::InvalidValue(payload.to_string().into()),
1131 "to_contained lost the payload {payload:?}"
1132 );
1133 assert_eq!(
1134 owned.to_shared(),
1135 shared,
1136 "to_shared did not restore the payload {payload:?}"
1137 );
1138 let LayoutFloatParseErrorOwned::InvalidValue(s) = &owned;
1139 assert_eq!(s.as_str(), payload);
1140 }
1141 }
1142
1143 #[test]
1144 fn error_conversions_survive_a_borrowed_owned_borrowed_cycle() {
1145 let owned = {
1148 let input = format!("{}-bogus", "very-long-".repeat(64));
1149 parse_layout_display(&input).unwrap_err().to_contained()
1150 };
1151 let shared = owned.to_shared();
1152 assert_eq!(shared.to_contained(), owned);
1153 assert!(format!("{shared}").starts_with("Invalid display value: \"very-long-"));
1154 }
1155
1156 #[test]
1161 fn valid_minimal_inputs_parse() {
1162 assert_eq!(parse_layout_display("none"), Ok(LayoutDisplay::None));
1163 assert_eq!(parse_layout_display("contents"), Ok(LayoutDisplay::Contents));
1164 assert_eq!(parse_layout_display("run-in"), Ok(LayoutDisplay::RunIn));
1165 assert_eq!(parse_layout_display("marker"), Ok(LayoutDisplay::Marker));
1166 assert_eq!(parse_layout_float("none"), Ok(LayoutFloat::None));
1167 assert_eq!(parse_layout_float("left"), Ok(LayoutFloat::Left));
1168 }
1169}