1use alloc::string::{String, ToString};
8
9use crate::{corety::AzString, props::formatter::PrintAsCssValue};
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(C)]
21pub struct Content {
22 pub inner: AzString,
23}
24
25impl Default for Content {
26 fn default() -> Self {
27 Self {
28 inner: "normal".into(),
29 }
30 }
31}
32
33impl PrintAsCssValue for Content {
34 fn print_as_css_value(&self) -> String {
35 self.inner.as_str().to_string()
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[repr(C)]
42pub struct CounterReset {
43 pub counter_name: AzString,
44 pub value: i32,
45}
46
47impl CounterReset {
48 #[must_use] pub const fn new(counter_name: AzString, value: i32) -> Self {
49 Self {
50 counter_name,
51 value,
52 }
53 }
54
55 #[must_use] pub const fn none() -> Self {
56 Self {
57 counter_name: AzString::from_const_str("none"),
58 value: 0,
59 }
60 }
61
62 #[must_use] pub const fn list_item() -> Self {
63 Self {
64 counter_name: AzString::from_const_str("list-item"),
65 value: 0,
66 }
67 }
68}
69
70impl Default for CounterReset {
71 fn default() -> Self {
72 Self::none()
73 }
74}
75
76impl PrintAsCssValue for CounterReset {
77 fn print_as_css_value(&self) -> String {
78 if self.counter_name.as_str() == "none" {
79 "none".to_string()
80 } else {
81 alloc::format!("{} {}", self.counter_name.as_str(), self.value)
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88#[repr(C)]
89pub struct CounterIncrement {
90 pub counter_name: AzString,
91 pub value: i32,
92}
93
94impl CounterIncrement {
95 #[must_use] pub const fn new(counter_name: AzString, value: i32) -> Self {
96 Self {
97 counter_name,
98 value,
99 }
100 }
101
102 #[must_use] pub const fn none() -> Self {
103 Self {
104 counter_name: AzString::from_const_str("none"),
105 value: 0,
106 }
107 }
108
109 #[must_use] pub const fn list_item() -> Self {
110 Self {
111 counter_name: AzString::from_const_str("list-item"),
112 value: 1,
113 }
114 }
115}
116
117impl Default for CounterIncrement {
118 fn default() -> Self {
119 Self::none()
120 }
121}
122
123impl PrintAsCssValue for CounterIncrement {
124 fn print_as_css_value(&self) -> String {
125 if self.counter_name.as_str() == "none" {
126 "none".to_string()
127 } else {
128 alloc::format!("{} {}", self.counter_name.as_str(), self.value)
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137#[repr(C)]
138pub struct StringSet {
139 pub inner: AzString,
140}
141
142impl Default for StringSet {
143 fn default() -> Self {
144 Self {
145 inner: "none".into(),
146 }
147 }
148}
149
150impl PrintAsCssValue for StringSet {
151 fn print_as_css_value(&self) -> String {
152 self.inner.as_str().to_string()
153 }
154}
155
156impl crate::codegen::format::FormatAsRustCode for Content {
158 fn format_as_rust_code(&self, _tabs: usize) -> String {
159 format!("Content {{ inner: String::from({:?}) }}", self.inner)
160 }
161}
162
163impl crate::codegen::format::FormatAsRustCode for CounterReset {
164 fn format_as_rust_code(&self, _tabs: usize) -> String {
165 alloc::format!(
166 "CounterReset {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
167 self.counter_name.as_str(),
168 self.value
169 )
170 }
171}
172
173impl crate::codegen::format::FormatAsRustCode for CounterIncrement {
174 fn format_as_rust_code(&self, _tabs: usize) -> String {
175 alloc::format!(
176 "CounterIncrement {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
177 self.counter_name.as_str(),
178 self.value
179 )
180 }
181}
182
183impl crate::codegen::format::FormatAsRustCode for StringSet {
184 fn format_as_rust_code(&self, _tabs: usize) -> String {
185 format!("StringSet {{ inner: String::from({:?}) }}", self.inner)
186 }
187}
188
189#[cfg(feature = "parser")]
192pub mod parser {
193 #[allow(clippy::wildcard_imports)] use super::*;
195
196 pub fn parse_content(input: &str) -> Result<Content, ()> {
201 Ok(Content {
202 inner: input.trim().into(),
203 })
204 }
205
206 fn parse_counter_name_value(input: &str, default_value: i32) -> Result<(AzString, i32), ()> {
207 let trimmed = input.trim();
208
209 if trimmed == "none" {
210 return Ok((AzString::from_const_str("none"), 0));
211 }
212
213 let parts: Vec<&str> = trimmed.split_whitespace().collect();
214
215 if parts.is_empty() {
216 return Err(());
217 }
218
219 let counter_name = parts[0].into();
220 let value = if parts.len() > 1 {
221 parts[1].parse::<i32>().map_err(|_| ())?
222 } else {
223 default_value
224 };
225
226 Ok((counter_name, value))
227 }
228
229 pub fn parse_counter_reset(input: &str) -> Result<CounterReset, ()> {
233 let (counter_name, value) = parse_counter_name_value(input, 0)?;
234 Ok(CounterReset::new(counter_name, value))
235 }
236
237 pub fn parse_counter_increment(input: &str) -> Result<CounterIncrement, ()> {
241 let (counter_name, value) = parse_counter_name_value(input, 1)?;
242 Ok(CounterIncrement::new(counter_name, value))
243 }
244
245 pub fn parse_string_set(input: &str) -> Result<StringSet, ()> {
249 Ok(StringSet {
250 inner: input.trim().into(),
251 })
252 }
253}
254
255#[cfg(feature = "parser")]
256pub use parser::*;
257
258#[cfg(all(test, feature = "parser"))]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_simple_content_parser() {
264 assert_eq!(parse_content("'Hello'").unwrap().inner.as_str(), "'Hello'");
265
266 let reset = parse_counter_reset("page 1").unwrap();
268 assert_eq!(reset.counter_name.as_str(), "page");
269 assert_eq!(reset.value, 1);
270
271 let reset = parse_counter_reset("list-item 0").unwrap();
272 assert_eq!(reset.counter_name.as_str(), "list-item");
273 assert_eq!(reset.value, 0);
274
275 let reset = parse_counter_reset("none").unwrap();
276 assert_eq!(reset.counter_name.as_str(), "none");
277
278 let inc = parse_counter_increment("section").unwrap();
280 assert_eq!(inc.counter_name.as_str(), "section");
281 assert_eq!(inc.value, 1); let inc = parse_counter_increment("list-item 2").unwrap();
284 assert_eq!(inc.counter_name.as_str(), "list-item");
285 assert_eq!(inc.value, 2);
286
287 assert_eq!(
288 parse_string_set("chapter-title content()")
289 .unwrap()
290 .inner
291 .as_str(),
292 "chapter-title content()"
293 );
294 }
295}
296
297#[cfg(all(test, feature = "parser"))]
298mod autotest_generated {
299 use std::{
300 collections::hash_map::DefaultHasher,
301 hash::{Hash, Hasher},
302 };
303
304 use super::*;
305 use crate::codegen::format::FormatAsRustCode;
306
307 fn hash_of<T: Hash>(t: &T) -> u64 {
308 let mut h = DefaultHasher::new();
309 t.hash(&mut h);
310 h.finish()
311 }
312
313 fn hostile_corpus() -> Vec<String> {
315 alloc::vec![
316 String::new(),
317 " ".to_string(),
318 "\t\n\r\u{b}\u{c}".to_string(),
319 "\u{a0}".to_string(), "\u{200b}".to_string(), "\0".to_string(), "\0page\0 1\0".to_string(),
323 ";".to_string(),
324 "}{".to_string(),
325 "counter(section) \". \"".to_string(),
326 "-".to_string(),
327 "--".to_string(),
328 "+".to_string(),
329 "e".to_string(),
330 "NaN".to_string(),
331 "inf".to_string(),
332 "-inf".to_string(),
333 "\u{1F600}".to_string(),
334 "a\u{0301}\u{0301}\u{0301}".to_string(), "\u{202e}reversed".to_string(), "page 1 page 2 page 3".to_string(),
337 "page \u{fffd}".to_string(),
338 "\"unterminated".to_string(),
339 "url(".to_string(),
340 ]
341 }
342
343 #[test]
349 fn counter_constructors_preserve_fields_at_i32_extremes() {
350 for value in [i32::MIN, i32::MIN + 1, -1, 0, 1, i32::MAX - 1, i32::MAX] {
351 let r = CounterReset::new(AzString::from_const_str("c"), value);
352 assert_eq!(r.counter_name.as_str(), "c");
353 assert_eq!(r.value, value);
354
355 let i = CounterIncrement::new(AzString::from_const_str("c"), value);
356 assert_eq!(i.counter_name.as_str(), "c");
357 assert_eq!(i.value, value);
358 }
359 }
360
361 #[test]
362 fn counter_constructors_accept_degenerate_names_verbatim() {
363 let huge: String = "x".repeat(100_000);
366 let names = ["", " ", "a b", "\u{1F600}\u{1F600}", "none", huge.as_str()];
367
368 for name in names {
369 let r = CounterReset::new(name.into(), i32::MIN);
370 assert_eq!(r.counter_name.as_str(), name);
371 assert_eq!(r.counter_name.as_str().len(), name.len());
372 assert_eq!(r.value, i32::MIN);
373
374 let i = CounterIncrement::new(name.into(), i32::MAX);
375 assert_eq!(i.counter_name.as_str(), name);
376 assert_eq!(i.counter_name.as_str().len(), name.len());
377 assert_eq!(i.value, i32::MAX);
378 }
379 }
380
381 #[test]
382 fn counter_none_and_list_item_constants() {
383 assert_eq!(CounterReset::none().counter_name.as_str(), "none");
384 assert_eq!(CounterReset::none().value, 0);
385 assert_eq!(CounterReset::default(), CounterReset::none());
386
387 assert_eq!(CounterIncrement::none().counter_name.as_str(), "none");
388 assert_eq!(CounterIncrement::none().value, 0);
389 assert_eq!(CounterIncrement::default(), CounterIncrement::none());
390
391 assert_eq!(CounterReset::list_item().counter_name.as_str(), "list-item");
394 assert_eq!(CounterReset::list_item().value, 0);
395 assert_eq!(
396 CounterIncrement::list_item().counter_name.as_str(),
397 "list-item"
398 );
399 assert_eq!(CounterIncrement::list_item().value, 1);
400 }
401
402 #[test]
403 fn content_and_string_set_defaults() {
404 assert_eq!(Content::default().inner.as_str(), "normal");
405 assert_eq!(StringSet::default().inner.as_str(), "none");
406 }
407
408 #[test]
413 fn content_and_string_set_are_pure_trim_and_never_error() {
414 for input in hostile_corpus() {
419 let c = parse_content(&input).expect("parse_content never errors");
420 assert_eq!(c.inner.as_str(), input.trim());
421
422 let s = parse_string_set(&input).expect("parse_string_set never errors");
423 assert_eq!(s.inner.as_str(), input.trim());
424 }
425
426 assert_eq!(parse_content("").unwrap().inner.as_str(), "");
427 assert_eq!(parse_content(" \t\n ").unwrap().inner.as_str(), "");
428 assert_eq!(parse_string_set("").unwrap().inner.as_str(), "");
429 assert_eq!(parse_content("\u{a0}x\u{a0}").unwrap().inner.as_str(), "x");
431 assert_eq!(
433 parse_content(" \u{200b} ").unwrap().inner.as_str(),
434 "\u{200b}"
435 );
436 }
437
438 #[test]
439 fn content_positive_control_and_inner_junk_is_kept() {
440 assert_eq!(parse_content("'Hi'").unwrap().inner.as_str(), "'Hi'");
441 assert_eq!(parse_content(" 'Hi' ").unwrap().inner.as_str(), "'Hi'");
442 assert_eq!(
444 parse_content("'Hi';garbage").unwrap().inner.as_str(),
445 "'Hi';garbage"
446 );
447 assert_eq!(
448 parse_string_set("chapter content()").unwrap().inner.as_str(),
449 "chapter content()"
450 );
451 }
452
453 #[test]
454 fn content_extremely_long_input_does_not_hang() {
455 let huge = "a".repeat(1_000_000);
456 let padded = alloc::format!(" {huge}\n");
457
458 let c = parse_content(&padded).unwrap();
459 assert_eq!(c.inner.as_str().len(), 1_000_000);
460 assert!(c.inner.as_str().starts_with("aa"));
461
462 let s = parse_string_set(&padded).unwrap();
463 assert_eq!(s.inner.as_str().len(), 1_000_000);
464 }
465
466 #[test]
467 fn content_deeply_nested_input_does_not_stack_overflow() {
468 let nested = alloc::format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
470 let c = parse_content(&nested).unwrap();
471 assert_eq!(c.inner.as_str().len(), 20_000);
472
473 let deep = alloc::format!("{}x{}", "counter(".repeat(5_000), ")".repeat(5_000));
474 assert!(parse_content(&deep).is_ok());
475 assert!(parse_string_set(&deep).is_ok());
476 }
477
478 #[test]
479 fn content_preserves_multibyte_unicode_byte_for_byte() {
480 let input = " \u{1F600}a\u{0301}\u{4e2d}\u{6587} ";
481 let c = parse_content(input).unwrap();
482 assert_eq!(c.inner.as_str(), input.trim());
483 assert_eq!(c.inner.as_str().len(), input.trim().len());
484 assert_eq!(c.inner.as_str().chars().count(), 5);
488 }
489
490 #[test]
491 fn content_boundary_number_strings_are_accepted_as_plain_text() {
492 for n in [
494 "0",
495 "-0",
496 "9223372036854775807",
497 "-9223372036854775808",
498 "1e309",
499 "NaN",
500 "inf",
501 ] {
502 assert_eq!(parse_content(n).unwrap().inner.as_str(), n);
503 assert_eq!(parse_string_set(n).unwrap().inner.as_str(), n);
504 }
505 }
506
507 #[test]
508 fn content_print_parse_roundtrips_for_parsed_values() {
509 for input in hostile_corpus() {
510 let c = parse_content(&input).unwrap();
511 let reparsed = parse_content(&c.print_as_css_value()).unwrap();
512 assert_eq!(reparsed, c, "content round-trip failed for {input:?}");
513
514 let s = parse_string_set(&input).unwrap();
515 let re_s = parse_string_set(&s.print_as_css_value()).unwrap();
516 assert_eq!(re_s, s, "string-set round-trip failed for {input:?}");
517 }
518
519 let d = Content::default();
520 assert_eq!(parse_content(&d.print_as_css_value()).unwrap(), d);
521 let d = StringSet::default();
522 assert_eq!(parse_string_set(&d.print_as_css_value()).unwrap(), d);
523 }
524
525 #[test]
526 fn content_roundtrip_is_lossy_for_untrimmed_handbuilt_values() {
527 let padded = Content {
530 inner: " x ".into(),
531 };
532 assert_eq!(padded.print_as_css_value(), " x ");
533 assert_ne!(parse_content(&padded.print_as_css_value()).unwrap(), padded);
534 assert_eq!(
535 parse_content(&padded.print_as_css_value())
536 .unwrap()
537 .inner
538 .as_str(),
539 "x"
540 );
541 }
542
543 #[test]
550 fn counter_empty_and_whitespace_only_input_is_rejected() {
551 for input in ["", " ", " ", "\t\n", "\r\n\t ", "\u{a0}", "\u{2003}"] {
552 assert!(
553 parse_counter_reset(input).is_err(),
554 "counter-reset accepted whitespace-only {input:?}"
555 );
556 assert!(
557 parse_counter_increment(input).is_err(),
558 "counter-increment accepted whitespace-only {input:?}"
559 );
560 }
561 }
562
563 #[test]
564 fn counter_missing_value_uses_per_property_default() {
565 let r = parse_counter_reset("section").unwrap();
566 assert_eq!(r.counter_name.as_str(), "section");
567 assert_eq!(r.value, 0);
568
569 let i = parse_counter_increment("section").unwrap();
570 assert_eq!(i.counter_name.as_str(), "section");
571 assert_eq!(i.value, 1);
572 }
573
574 #[test]
575 fn counter_none_keyword_is_case_sensitive() {
576 let r = parse_counter_reset("NONE").unwrap();
579 assert_eq!(r.counter_name.as_str(), "NONE");
580 assert_eq!(r.value, 0);
581
582 let i = parse_counter_increment("None").unwrap();
583 assert_eq!(i.counter_name.as_str(), "None");
584 assert_eq!(i.value, 1, "uppercase `None` took the counter-name branch");
585
586 assert_eq!(parse_counter_increment("none").unwrap().value, 0);
588 assert_eq!(parse_counter_increment(" none ").unwrap().value, 0);
589 }
590
591 #[test]
592 fn counter_none_with_value_keeps_value_but_prints_as_bare_none() {
593 let r = parse_counter_reset("none 5").unwrap();
597 assert_eq!(r.counter_name.as_str(), "none");
598 assert_eq!(r.value, 5);
599 assert_eq!(r.print_as_css_value(), "none");
600
601 let reparsed = parse_counter_reset(&r.print_as_css_value()).unwrap();
602 assert_eq!(reparsed.value, 0);
603 assert_ne!(reparsed, r, "value 5 silently vanished across a round-trip");
604 }
605
606 #[test]
607 fn counter_value_at_i32_boundaries_parses_exactly() {
608 assert_eq!(parse_counter_reset("c 2147483647").unwrap().value, i32::MAX);
609 assert_eq!(parse_counter_reset("c -2147483648").unwrap().value, i32::MIN);
610 assert_eq!(parse_counter_reset("c 0").unwrap().value, 0);
611 assert_eq!(parse_counter_reset("c -0").unwrap().value, 0);
612 assert_eq!(parse_counter_reset("c +7").unwrap().value, 7);
613 assert_eq!(parse_counter_reset("c 007").unwrap().value, 7);
614 assert_eq!(
615 parse_counter_increment("c -2147483648").unwrap().value,
616 i32::MIN
617 );
618 }
619
620 #[test]
621 fn counter_value_overflowing_i32_is_rejected_not_wrapped() {
622 for over in [
623 "c 2147483648", "c -2147483649", "c 9223372036854775807", "c -9223372036854775808", "c 340282366920938463463374607431768211456",
628 ] {
629 assert!(
630 parse_counter_reset(over).is_err(),
631 "overflowing value silently accepted: {over:?}"
632 );
633 assert!(parse_counter_increment(over).is_err());
634 }
635 }
636
637 #[test]
638 fn counter_non_integer_values_are_rejected() {
639 for bad in [
640 "c 1.0", "c 1.5", "c 1e3", "c NaN", "c nan", "c inf", "c -inf", "c 0x10", "c 1_000",
641 "c 1,", "c one", "c -", "c +", "c ٣", "c 1", "c 1\u{200b}",
644 ] {
645 assert!(
646 parse_counter_reset(bad).is_err(),
647 "counter-reset accepted non-integer {bad:?}"
648 );
649 assert!(
650 parse_counter_increment(bad).is_err(),
651 "counter-increment accepted non-integer {bad:?}"
652 );
653 }
654 }
655
656 #[test]
657 fn counter_extra_tokens_after_the_first_pair_are_silently_dropped() {
658 let r = parse_counter_reset("a 1 b 2 c 3").unwrap();
661 assert_eq!(r.counter_name.as_str(), "a");
662 assert_eq!(r.value, 1);
663
664 parse_counter_increment("a b").unwrap_err();
667 }
668
669 #[test]
670 fn counter_arbitrary_whitespace_forms_are_normalized() {
671 for input in [
672 "\t page \n 42 \r",
673 "page\u{a0}42", "page 42", "\u{2003}page 42", ] {
677 let r = parse_counter_reset(input).unwrap();
678 assert_eq!(r.counter_name.as_str(), "page", "for {input:?}");
679 assert_eq!(r.value, 42, "for {input:?}");
680 }
681 }
682
683 #[test]
684 fn counter_unicode_names_are_preserved() {
685 let r = parse_counter_reset("\u{7ae0}\u{8282} 3").unwrap();
686 assert_eq!(r.counter_name.as_str(), "\u{7ae0}\u{8282}");
687 assert_eq!(r.value, 3);
688
689 let i = parse_counter_increment("\u{1F600}").unwrap();
690 assert_eq!(i.counter_name.as_str(), "\u{1F600}");
691 assert_eq!(i.value, 1);
692
693 let z = parse_counter_reset("\u{200b}").unwrap();
695 assert_eq!(z.counter_name.as_str(), "\u{200b}");
696 assert_eq!(z.value, 0);
697 }
698
699 #[test]
700 fn counter_hostile_corpus_never_panics() {
701 for input in hostile_corpus() {
702 let _ = parse_counter_reset(&input);
703 let _ = parse_counter_increment(&input);
704 }
705 }
706
707 #[test]
708 fn counter_extremely_long_input_does_not_hang() {
709 let long_name = "n".repeat(1_000_000);
710 let r = parse_counter_reset(&long_name).unwrap();
711 assert_eq!(r.counter_name.as_str().len(), 1_000_000);
712 assert_eq!(r.value, 0);
713
714 let with_value = alloc::format!("{long_name} 5");
715 assert_eq!(parse_counter_increment(&with_value).unwrap().value, 5);
716
717 let long_number = alloc::format!("c {}", "9".repeat(1_000_000));
719 assert!(parse_counter_reset(&long_number).is_err());
720
721 assert!(parse_counter_reset(&" ".repeat(1_000_000)).is_err());
723 }
724
725 #[test]
726 fn counter_deeply_nested_input_does_not_stack_overflow() {
727 let nested = alloc::format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
728 let r = parse_counter_reset(&nested).unwrap();
729 assert_eq!(r.counter_name.as_str().len(), 20_000);
730 assert_eq!(r.value, 0);
731
732 let nested_with_value = alloc::format!("{nested} 1");
733 assert_eq!(parse_counter_increment(&nested_with_value).unwrap().value, 1);
734 }
735
736 #[test]
741 fn counter_print_parse_roundtrips_for_well_formed_values() {
742 for (name, value) in [
743 ("page", 0),
744 ("page", 1),
745 ("section", -1),
746 ("list-item", i32::MAX),
747 ("list-item", i32::MIN),
748 ("\u{7ae0}", 7),
749 ] {
750 let r = CounterReset::new(name.into(), value);
751 assert_eq!(parse_counter_reset(&r.print_as_css_value()).unwrap(), r);
752
753 let i = CounterIncrement::new(name.into(), value);
754 assert_eq!(parse_counter_increment(&i.print_as_css_value()).unwrap(), i);
755 }
756
757 let n = CounterReset::none();
759 assert_eq!(n.print_as_css_value(), "none");
760 assert_eq!(parse_counter_reset(&n.print_as_css_value()).unwrap(), n);
761
762 let li = CounterIncrement::list_item();
763 assert_eq!(li.print_as_css_value(), "list-item 1");
764 assert_eq!(parse_counter_increment(&li.print_as_css_value()).unwrap(), li);
765
766 assert_eq!(CounterReset::list_item().print_as_css_value(), "list-item 0");
767 assert_eq!(CounterIncrement::none().print_as_css_value(), "none");
768 }
769
770 #[test]
771 fn counter_print_of_empty_name_reparses_into_a_different_counter() {
772 let r = CounterReset::new(AzString::from_const_str(""), 5);
775 assert_eq!(r.print_as_css_value(), " 5");
776
777 let reparsed = parse_counter_reset(&r.print_as_css_value()).unwrap();
778 assert_eq!(reparsed.counter_name.as_str(), "5");
779 assert_eq!(reparsed.value, 0);
780 assert_ne!(reparsed, r);
781 }
782
783 #[test]
784 fn counter_print_of_name_containing_space_fails_to_reparse() {
785 let r = CounterReset::new("a b".into(), 5);
788 assert_eq!(r.print_as_css_value(), "a b 5");
789 assert!(parse_counter_reset(&r.print_as_css_value()).is_err());
790
791 let i = CounterIncrement::new("a b".into(), 5);
792 assert!(parse_counter_increment(&i.print_as_css_value()).is_err());
793 }
794
795 #[test]
800 fn counter_ord_is_name_then_value_and_hash_agrees_with_eq() {
801 let a1 = CounterReset::new("a".into(), 1);
802 let a2 = CounterReset::new("a".into(), 2);
803 let b_min = CounterReset::new("b".into(), i32::MIN);
804
805 assert!(a1 < a2, "equal names must order by value");
806 assert!(a2 < b_min, "name must dominate value in the ordering");
807 assert!(CounterReset::new("a".into(), i32::MAX) < b_min);
808
809 let owned = CounterReset::new(String::from("a").into(), 1);
811 assert_eq!(owned, a1);
812 assert_eq!(hash_of(&owned), hash_of(&a1));
813 assert_ne!(a1, a2);
814
815 assert_eq!(
816 hash_of(&Content {
817 inner: "x".into()
818 }),
819 hash_of(&parse_content(" x ").unwrap())
820 );
821 }
822
823 #[test]
824 fn format_as_rust_code_escapes_quotes_and_control_chars() {
825 let c = Content {
826 inner: "a\"b\\c\nd".into(),
827 };
828 assert_eq!(
829 c.format_as_rust_code(0),
830 r#"Content { inner: String::from("a\"b\\c\nd") }"#
831 );
832
833 let s = StringSet {
834 inner: "\"q\"".into(),
835 };
836 assert_eq!(
837 s.format_as_rust_code(0),
838 r#"StringSet { inner: String::from("\"q\"") }"#
839 );
840
841 let r = CounterReset::new("a\"b".into(), i32::MIN);
842 assert_eq!(
843 r.format_as_rust_code(0),
844 r#"CounterReset { counter_name: AzString::from_const_str("a\"b"), value: -2147483648 }"#
845 );
846
847 let i = CounterIncrement::new("".into(), i32::MAX);
848 assert_eq!(
849 i.format_as_rust_code(0),
850 r#"CounterIncrement { counter_name: AzString::from_const_str(""), value: 2147483647 }"#
851 );
852 }
853
854 #[test]
855 fn format_as_rust_code_ignores_the_tab_argument() {
856 let c = Content::default();
857 assert_eq!(c.format_as_rust_code(0), c.format_as_rust_code(usize::MAX));
858
859 let r = CounterReset::list_item();
860 assert_eq!(r.format_as_rust_code(0), r.format_as_rust_code(usize::MAX));
861 }
862}