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