1use crate::{
2 Parser,
3 attributes::Attrlist,
4 content::{Content, Passthroughs, SubstitutionStep},
5};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum SubstitutionGroup {
14 Normal,
18
19 Title,
22
23 Header,
33
34 Verbatim,
38
39 Pass,
47
48 None,
51
52 AttributeEntryValue,
56
57 Custom(Vec<SubstitutionStep>),
67}
68
69impl SubstitutionGroup {
70 pub(crate) fn from_custom_string(start_from: Option<&Self>, mut custom: &str) -> Option<Self> {
75 custom = custom.trim();
76
77 if custom == "none" {
78 return Some(Self::None);
79 }
80
81 if custom == "n" || custom == "normal" {
82 return Some(Self::Normal);
83 }
84
85 if custom == "v" || custom == "verbatim" {
86 return Some(Self::Verbatim);
87 }
88
89 let mut steps: Vec<SubstitutionStep> = vec![];
90
91 for (count, mut step) in custom.split(",").enumerate() {
92 step = step.trim();
93
94 if step == "n" || step == "normal" {
95 steps = vec![
96 SubstitutionStep::SpecialCharacters,
97 SubstitutionStep::Quotes,
98 SubstitutionStep::AttributeReferences,
99 SubstitutionStep::CharacterReplacements,
100 SubstitutionStep::Macros,
101 SubstitutionStep::PostReplacement,
102 ];
103 continue;
104 }
105
106 if step == "v" || step == "verbatim" {
107 steps = vec![SubstitutionStep::SpecialCharacters];
108 continue;
109 }
110
111 let append = if step.starts_with('+') {
112 step = &step[1..];
113 true
114 } else {
115 false
116 };
117
118 let prepend = if !append && step.ends_with('+') {
119 step = &step[0..step.len() - 1];
120 true
121 } else {
122 false
123 };
124
125 let subtract = if !append && !prepend && step.starts_with('-') {
126 step = &step[1..];
127 true
128 } else {
129 false
130 };
131
132 if count == 0
133 && let Some(start_from) = start_from
134 && (append || prepend || subtract)
135 {
136 steps = start_from.steps().to_owned();
137 }
138
139 let step = match step {
140 "c" | "specialcharacters" | "specialchars" => SubstitutionStep::SpecialCharacters,
141 "q" | "quotes" => SubstitutionStep::Quotes,
142 "a" | "attributes" => SubstitutionStep::AttributeReferences,
143 "r" | "replacements" => SubstitutionStep::CharacterReplacements,
144 "m" | "macros" => SubstitutionStep::Macros,
145 "p" | "post_replacements" => SubstitutionStep::PostReplacement,
146 _ => {
147 return None;
148 }
149 };
150
151 if prepend {
152 steps.insert(0, step);
153 } else if append {
154 steps.push(step);
155 } else if subtract {
156 steps.retain(|s| s != &step);
157 } else {
158 steps.push(step);
159 }
160 }
161
162 Some(Self::Custom(steps))
163 }
164
165 pub(crate) fn apply(
166 &self,
167 content: &mut Content<'_>,
168 parser: &Parser,
169 attrlist: Option<&Attrlist>,
170 ) {
171 let steps = self.steps();
172
173 let passthroughs: Option<Passthroughs> =
174 if steps.contains(&SubstitutionStep::Macros) || self == &Self::Header {
175 Some(Passthroughs::extract_from(content))
176 } else {
177 None
178 };
179
180 for step in steps {
181 step.apply(content, parser, attrlist);
182 }
183
184 if let Some(passthroughs) = passthroughs {
185 passthroughs.restore_to(content, parser);
186 }
187 }
188
189 pub(crate) fn override_via_attrlist(&self, attrlist: Option<&Attrlist>) -> Self {
190 let mut result = self.clone();
191
192 if let Some(attrlist) = attrlist {
193 if let Some(block_style) = attrlist.nth_attribute(1).and_then(|a| a.block_style()) {
194 result = match block_style {
195 "pass" => SubstitutionGroup::None,
197 _ => result,
198 };
199 }
200
201 if let Some(sub_group) = attrlist
202 .named_attribute("subs")
203 .map(|attr| attr.value())
204 .and_then(|s| Self::from_custom_string(Some(self), s))
205 {
206 result = sub_group;
207 }
208 }
209
210 result
211 }
212
213 fn steps(&self) -> &[SubstitutionStep] {
214 match self {
215 Self::Normal | Self::Title => &[
216 SubstitutionStep::SpecialCharacters,
217 SubstitutionStep::Quotes,
218 SubstitutionStep::AttributeReferences,
219 SubstitutionStep::CharacterReplacements,
220 SubstitutionStep::Macros,
221 SubstitutionStep::PostReplacement,
222 ],
223
224 Self::Header | Self::AttributeEntryValue => &[
225 SubstitutionStep::SpecialCharacters,
226 SubstitutionStep::AttributeReferences,
227 ],
228
229 Self::Verbatim => &[SubstitutionStep::SpecialCharacters],
230
231 Self::Pass | Self::None => &[],
232
233 Self::Custom(steps) => steps,
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 #![allow(clippy::unwrap_used)]
241
242 mod from_custom_string {
243 use pretty_assertions_sorted::assert_eq;
244
245 use crate::{
246 Parser,
247 content::{Content, SubstitutionGroup, SubstitutionStep},
248 strings::CowStr,
249 };
250
251 #[test]
252 fn empty() {
253 assert_eq!(SubstitutionGroup::from_custom_string(None, ""), None);
254 }
255
256 #[test]
257 fn none() {
258 assert_eq!(
259 SubstitutionGroup::from_custom_string(None, "none"),
260 Some(SubstitutionGroup::None)
261 );
262
263 assert_eq!(SubstitutionGroup::from_custom_string(None, "nermal"), None);
264 }
265
266 #[test]
267 fn normal() {
268 assert_eq!(
269 SubstitutionGroup::from_custom_string(None, "n"),
270 Some(SubstitutionGroup::Normal)
271 );
272
273 assert_eq!(
274 SubstitutionGroup::from_custom_string(None, "normal"),
275 Some(SubstitutionGroup::Normal)
276 );
277
278 assert_eq!(SubstitutionGroup::from_custom_string(None, "nermal"), None);
279 }
280
281 #[test]
282 fn verbatim() {
283 assert_eq!(
284 SubstitutionGroup::from_custom_string(None, "v"),
285 Some(SubstitutionGroup::Verbatim)
286 );
287
288 assert_eq!(
289 SubstitutionGroup::from_custom_string(None, "verbatim"),
290 Some(SubstitutionGroup::Verbatim)
291 );
292
293 assert_eq!(
294 SubstitutionGroup::from_custom_string(None, "verboten"),
295 None
296 );
297 }
298
299 #[test]
300 fn special_chars() {
301 assert_eq!(
302 SubstitutionGroup::from_custom_string(None, "c"),
303 Some(SubstitutionGroup::Custom(vec![
304 SubstitutionStep::SpecialCharacters
305 ]))
306 );
307
308 assert_eq!(
309 SubstitutionGroup::from_custom_string(None, "specialchars"),
310 Some(SubstitutionGroup::Custom(vec![
311 SubstitutionStep::SpecialCharacters
312 ]))
313 );
314 }
315
316 #[test]
317 fn quotes() {
318 assert_eq!(
319 SubstitutionGroup::from_custom_string(None, "q"),
320 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]))
321 );
322
323 assert_eq!(
324 SubstitutionGroup::from_custom_string(None, "quotes"),
325 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]))
326 );
327 }
328
329 #[test]
330 fn attributes() {
331 assert_eq!(
332 SubstitutionGroup::from_custom_string(None, "a"),
333 Some(SubstitutionGroup::Custom(vec![
334 SubstitutionStep::AttributeReferences
335 ]))
336 );
337
338 assert_eq!(
339 SubstitutionGroup::from_custom_string(None, "attributes"),
340 Some(SubstitutionGroup::Custom(vec![
341 SubstitutionStep::AttributeReferences
342 ]))
343 );
344 }
345
346 #[test]
347 fn replacements() {
348 assert_eq!(
349 SubstitutionGroup::from_custom_string(None, "r"),
350 Some(SubstitutionGroup::Custom(vec![
351 SubstitutionStep::CharacterReplacements
352 ]))
353 );
354
355 assert_eq!(
356 SubstitutionGroup::from_custom_string(None, "replacements"),
357 Some(SubstitutionGroup::Custom(vec![
358 SubstitutionStep::CharacterReplacements
359 ]))
360 );
361 }
362
363 #[test]
364 fn macros() {
365 assert_eq!(
366 SubstitutionGroup::from_custom_string(None, "m"),
367 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Macros]))
368 );
369
370 assert_eq!(
371 SubstitutionGroup::from_custom_string(None, "macros"),
372 Some(SubstitutionGroup::Custom(vec![SubstitutionStep::Macros]))
373 );
374 }
375
376 #[test]
377 fn post_replacements() {
378 assert_eq!(
379 SubstitutionGroup::from_custom_string(None, "p"),
380 Some(SubstitutionGroup::Custom(vec![
381 SubstitutionStep::PostReplacement
382 ]))
383 );
384
385 assert_eq!(
386 SubstitutionGroup::from_custom_string(None, "post_replacements"),
387 Some(SubstitutionGroup::Custom(vec![
388 SubstitutionStep::PostReplacement
389 ]))
390 );
391 }
392
393 #[test]
394 fn multiple() {
395 assert_eq!(
396 SubstitutionGroup::from_custom_string(None, "q,a"),
397 Some(SubstitutionGroup::Custom(vec![
398 SubstitutionStep::Quotes,
399 SubstitutionStep::AttributeReferences
400 ]))
401 );
402
403 assert_eq!(
404 SubstitutionGroup::from_custom_string(None, "q, a"),
405 Some(SubstitutionGroup::Custom(vec![
406 SubstitutionStep::Quotes,
407 SubstitutionStep::AttributeReferences
408 ]))
409 );
410
411 assert_eq!(
412 SubstitutionGroup::from_custom_string(None, "quotes,attributes"),
413 Some(SubstitutionGroup::Custom(vec![
414 SubstitutionStep::Quotes,
415 SubstitutionStep::AttributeReferences
416 ]))
417 );
418
419 assert_eq!(
420 SubstitutionGroup::from_custom_string(None, "x,bogus,no such step"),
421 None
422 );
423 }
424
425 #[test]
426 fn subtraction() {
427 assert_eq!(
428 SubstitutionGroup::from_custom_string(None, "n,-r"),
429 Some(SubstitutionGroup::Custom(vec![
430 SubstitutionStep::SpecialCharacters,
431 SubstitutionStep::Quotes,
432 SubstitutionStep::AttributeReferences,
433 SubstitutionStep::Macros,
434 SubstitutionStep::PostReplacement,
435 ]))
436 );
437
438 assert_eq!(
439 SubstitutionGroup::from_custom_string(None, "n,-r,-r,-m"),
440 Some(SubstitutionGroup::Custom(vec![
441 SubstitutionStep::SpecialCharacters,
442 SubstitutionStep::Quotes,
443 SubstitutionStep::AttributeReferences,
444 SubstitutionStep::PostReplacement,
445 ]))
446 );
447
448 assert_eq!(
449 SubstitutionGroup::from_custom_string(None, "v,-r"),
450 Some(SubstitutionGroup::Custom(vec![
451 SubstitutionStep::SpecialCharacters,
452 ]))
453 );
454
455 assert_eq!(
456 SubstitutionGroup::from_custom_string(None, "v,-c"),
457 Some(SubstitutionGroup::Custom(vec![]))
458 );
459 }
460
461 #[test]
462 fn addition() {
463 assert_eq!(
464 SubstitutionGroup::from_custom_string(None, "n,r"),
465 Some(SubstitutionGroup::Custom(vec![
466 SubstitutionStep::SpecialCharacters,
467 SubstitutionStep::Quotes,
468 SubstitutionStep::AttributeReferences,
469 SubstitutionStep::CharacterReplacements,
470 SubstitutionStep::Macros,
471 SubstitutionStep::PostReplacement,
472 SubstitutionStep::CharacterReplacements,
473 ]))
474 );
475
476 assert_eq!(
477 SubstitutionGroup::from_custom_string(None, "v,m"),
478 Some(SubstitutionGroup::Custom(vec![
479 SubstitutionStep::SpecialCharacters,
480 SubstitutionStep::Macros,
481 ]))
482 );
483 }
484
485 #[test]
486 fn incremental() {
487 assert_eq!(
488 SubstitutionGroup::from_custom_string(None, "n,r"),
489 Some(SubstitutionGroup::Custom(vec![
490 SubstitutionStep::SpecialCharacters,
491 SubstitutionStep::Quotes,
492 SubstitutionStep::AttributeReferences,
493 SubstitutionStep::CharacterReplacements,
494 SubstitutionStep::Macros,
495 SubstitutionStep::PostReplacement,
496 SubstitutionStep::CharacterReplacements,
497 ]))
498 );
499
500 assert_eq!(
501 SubstitutionGroup::from_custom_string(None, "v,m"),
502 Some(SubstitutionGroup::Custom(vec![
503 SubstitutionStep::SpecialCharacters,
504 SubstitutionStep::Macros,
505 ]))
506 );
507 }
508
509 #[test]
510 fn prepend() {
511 assert_eq!(
512 SubstitutionGroup::from_custom_string(
513 Some(&SubstitutionGroup::Verbatim),
514 "attributes+"
515 ),
516 Some(SubstitutionGroup::Custom(vec![
517 SubstitutionStep::AttributeReferences,
518 SubstitutionStep::SpecialCharacters,
519 ]))
520 );
521
522 assert_eq!(
523 SubstitutionGroup::from_custom_string(None, "attributes+"),
524 Some(SubstitutionGroup::Custom(vec![
525 SubstitutionStep::AttributeReferences,
526 ]))
527 );
528 }
529
530 #[test]
531 fn append() {
532 assert_eq!(
533 SubstitutionGroup::from_custom_string(
534 Some(&SubstitutionGroup::Verbatim),
535 "+attributes"
536 ),
537 Some(SubstitutionGroup::Custom(vec![
538 SubstitutionStep::SpecialCharacters,
539 SubstitutionStep::AttributeReferences,
540 ]))
541 );
542
543 assert_eq!(
544 SubstitutionGroup::from_custom_string(None, "attributes+"),
545 Some(SubstitutionGroup::Custom(vec![
546 SubstitutionStep::AttributeReferences,
547 ]))
548 );
549 }
550
551 #[test]
552 fn subtract() {
553 assert_eq!(
554 SubstitutionGroup::from_custom_string(
555 Some(&SubstitutionGroup::Normal),
556 "-attributes"
557 ),
558 Some(SubstitutionGroup::Custom(vec![
559 SubstitutionStep::SpecialCharacters,
560 SubstitutionStep::Quotes,
561 SubstitutionStep::CharacterReplacements,
562 SubstitutionStep::Macros,
563 SubstitutionStep::PostReplacement,
564 ]))
565 );
566
567 assert_eq!(
568 SubstitutionGroup::from_custom_string(None, "-attributes"),
569 Some(SubstitutionGroup::Custom(vec![]))
570 );
571 }
572
573 #[test]
574 fn custom_group_with_macros_preserves_passthroughs() {
575 let custom_group = SubstitutionGroup::from_custom_string(None, "q,m").unwrap();
576
577 let mut content = Content::from(crate::Span::new(
578 "Text with +++pass<through>+++ icon:github[] content.",
579 ));
580 let p = Parser::default();
581 custom_group.apply(&mut content, &p, None);
582
583 assert!(!content.is_empty());
584 assert_eq!(
585 content.rendered,
586 CowStr::Boxed(
587 "Text with pass<through> <span class=\"icon\">[github]</span> content."
588 .to_string()
589 .into_boxed_str()
590 )
591 );
592 }
593 }
594
595 mod normal {
596 use crate::{
597 Parser,
598 content::{Content, SubstitutionGroup},
599 strings::CowStr,
600 };
601
602 #[test]
603 fn empty() {
604 let mut content = Content::from(crate::Span::default());
605 let p = Parser::default();
606 SubstitutionGroup::Normal.apply(&mut content, &p, None);
607 assert!(content.is_empty());
608 assert_eq!(content.rendered, CowStr::Borrowed(""));
609 }
610
611 #[test]
612 fn basic_non_empty_span() {
613 let mut content = Content::from(crate::Span::new("blah"));
614 let p = Parser::default();
615 SubstitutionGroup::Normal.apply(&mut content, &p, None);
616 assert!(!content.is_empty());
617 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
618 }
619
620 #[test]
621 fn match_lt_and_gt() {
622 let mut content = Content::from(crate::Span::new("bl<ah>"));
623 let p = Parser::default();
624 SubstitutionGroup::Normal.apply(&mut content, &p, None);
625 assert!(!content.is_empty());
626 assert_eq!(
627 content.rendered,
628 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
629 );
630 }
631
632 #[test]
633 fn match_amp() {
634 let mut content = Content::from(crate::Span::new("bl<a&h>"));
635 let p = Parser::default();
636 SubstitutionGroup::Normal.apply(&mut content, &p, None);
637 assert!(!content.is_empty());
638 assert_eq!(
639 content.rendered,
640 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
641 );
642 }
643
644 #[test]
645 fn strong_word() {
646 let mut content = Content::from(crate::Span::new("One *word* is strong."));
647 let p = Parser::default();
648 SubstitutionGroup::Normal.apply(&mut content, &p, None);
649 assert!(!content.is_empty());
650 assert_eq!(
651 content.rendered,
652 CowStr::Boxed(
653 "One <strong>word</strong> is strong."
654 .to_string()
655 .into_boxed_str()
656 )
657 );
658 }
659
660 #[test]
661 fn strong_word_with_special_chars() {
662 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
663 let p = Parser::default();
664 SubstitutionGroup::Normal.apply(&mut content, &p, None);
665 assert!(!content.is_empty());
666 assert_eq!(
667 content.rendered,
668 CowStr::Boxed(
669 "One <strong>wo<r>d</strong> is strong."
670 .to_string()
671 .into_boxed_str()
672 )
673 );
674 }
675
676 #[test]
677 fn marked_string_with_id() {
678 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
679 let p = Parser::default();
680 SubstitutionGroup::Normal.apply(&mut content, &p, None);
681 assert!(!content.is_empty());
682 assert_eq!(
683 content.rendered,
684 CowStr::Boxed(r#"<span id="id">a few words</span>"#.to_string().into_boxed_str())
685 );
686 }
687 }
688
689 mod attribute_entry_value {
690 use crate::{
691 Parser,
692 content::{Content, SubstitutionGroup},
693 parser::ModificationContext,
694 strings::CowStr,
695 };
696
697 #[test]
698 fn empty() {
699 let mut content = Content::from(crate::Span::default());
700 let p = Parser::default();
701 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
702 assert!(content.is_empty());
703 assert_eq!(content.rendered, CowStr::Borrowed(""));
704 }
705
706 #[test]
707 fn basic_non_empty_span() {
708 let mut content = Content::from(crate::Span::new("blah"));
709 let p = Parser::default();
710 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
711 assert!(!content.is_empty());
712 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
713 }
714
715 #[test]
716 fn match_lt_and_gt() {
717 let mut content = Content::from(crate::Span::new("bl<ah>"));
718 let p = Parser::default();
719 SubstitutionGroup::Normal.apply(&mut content, &p, None);
720 assert!(!content.is_empty());
721 assert_eq!(
722 content.rendered,
723 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
724 );
725 }
726
727 #[test]
728 fn match_amp() {
729 let mut content = Content::from(crate::Span::new("bl<a&h>"));
730 let p = Parser::default();
731 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
732 assert!(!content.is_empty());
733 assert_eq!(
734 content.rendered,
735 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
736 );
737 }
738
739 #[test]
740 fn ignores_strong_word() {
741 let mut content = Content::from(crate::Span::new("One *word* is strong."));
742 let p = Parser::default();
743 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
744 assert!(!content.is_empty());
745 assert_eq!(
746 content.rendered,
747 CowStr::Boxed("One *word* is strong.".to_string().into_boxed_str())
748 );
749 }
750
751 #[test]
752 fn special_chars_and_attributes() {
753 let mut content = Content::from(crate::Span::new("bl<ah> {color}"));
754
755 let p = Parser::default().with_intrinsic_attribute(
756 "color",
757 "red",
758 ModificationContext::Anywhere,
759 );
760
761 SubstitutionGroup::AttributeEntryValue.apply(&mut content, &p, None);
762 assert!(!content.is_empty());
763 assert_eq!(
764 content.rendered,
765 CowStr::Boxed("bl<ah> red".to_string().into_boxed_str())
766 );
767 }
768 }
769
770 mod header {
771 use crate::{
772 Parser,
773 content::{Content, SubstitutionGroup},
774 strings::CowStr,
775 };
776
777 #[test]
778 fn empty() {
779 let mut content = Content::from(crate::Span::default());
780 let p = Parser::default();
781 SubstitutionGroup::Header.apply(&mut content, &p, None);
782 assert!(content.is_empty());
783 assert_eq!(content.rendered, CowStr::Borrowed(""));
784 }
785
786 #[test]
787 fn basic_non_empty_span() {
788 let mut content = Content::from(crate::Span::new("blah"));
789 let p = Parser::default();
790 SubstitutionGroup::Header.apply(&mut content, &p, None);
791 assert!(!content.is_empty());
792 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
793 }
794
795 #[test]
796 fn match_lt_and_gt() {
797 let mut content = Content::from(crate::Span::new("bl<ah>"));
798 let p = Parser::default();
799 SubstitutionGroup::Header.apply(&mut content, &p, None);
800 assert!(!content.is_empty());
801 assert_eq!(
802 content.rendered,
803 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
804 );
805 }
806
807 #[test]
808 fn match_amp() {
809 let mut content = Content::from(crate::Span::new("bl<a&h>"));
810 let p = Parser::default();
811 SubstitutionGroup::Header.apply(&mut content, &p, None);
812 assert!(!content.is_empty());
813 assert_eq!(
814 content.rendered,
815 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
816 );
817 }
818
819 #[test]
820 fn ignores_strong_word() {
821 let mut content = Content::from(crate::Span::new("One *word* is strong."));
822 let p = Parser::default();
823 SubstitutionGroup::Header.apply(&mut content, &p, None);
824 assert!(!content.is_empty());
825 assert_eq!(content.rendered, CowStr::Borrowed("One *word* is strong."));
826 }
827
828 #[test]
829 fn ignores_strong_word_with_special_chars() {
830 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
831 let p = Parser::default();
832 SubstitutionGroup::Header.apply(&mut content, &p, None);
833 assert!(!content.is_empty());
834 assert_eq!(
835 content.rendered,
836 CowStr::Boxed("One *wo<r>d* is strong.".to_string().into_boxed_str())
837 );
838 }
839
840 #[test]
841 fn ignores_marked_string_with_id() {
842 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
843 let p = Parser::default();
844 SubstitutionGroup::Header.apply(&mut content, &p, None);
845 assert!(!content.is_empty());
846 assert_eq!(content.rendered, CowStr::Borrowed("[#id]#a few words#"));
847 }
848 }
849
850 mod title {
851 use crate::{
852 Parser,
853 content::{Content, SubstitutionGroup},
854 strings::CowStr,
855 };
856
857 #[test]
858 fn empty() {
859 let mut content = Content::from(crate::Span::default());
860 let p = Parser::default();
861 SubstitutionGroup::Title.apply(&mut content, &p, None);
862 assert!(content.is_empty());
863 assert_eq!(content.rendered, CowStr::Borrowed(""));
864 }
865
866 #[test]
867 fn basic_non_empty_span() {
868 let mut content = Content::from(crate::Span::new("blah"));
869 let p = Parser::default();
870 SubstitutionGroup::Title.apply(&mut content, &p, None);
871 assert!(!content.is_empty());
872 assert_eq!(content.rendered, CowStr::Borrowed("blah"));
873 }
874
875 #[test]
876 fn match_lt_and_gt() {
877 let mut content = Content::from(crate::Span::new("bl<ah>"));
878 let p = Parser::default();
879 SubstitutionGroup::Title.apply(&mut content, &p, None);
880 assert!(!content.is_empty());
881 assert_eq!(
882 content.rendered,
883 CowStr::Boxed("bl<ah>".to_string().into_boxed_str())
884 );
885 }
886
887 #[test]
888 fn match_amp() {
889 let mut content = Content::from(crate::Span::new("bl<a&h>"));
890 let p = Parser::default();
891 SubstitutionGroup::Title.apply(&mut content, &p, None);
892 assert!(!content.is_empty());
893 assert_eq!(
894 content.rendered,
895 CowStr::Boxed("bl<a&h>".to_string().into_boxed_str())
896 );
897 }
898
899 #[test]
900 fn strong_word() {
901 let mut content = Content::from(crate::Span::new("One *word* is strong."));
902 let p = Parser::default();
903 SubstitutionGroup::Title.apply(&mut content, &p, None);
904 assert!(!content.is_empty());
905 assert_eq!(
906 content.rendered,
907 CowStr::Boxed(
908 "One <strong>word</strong> is strong."
909 .to_string()
910 .into_boxed_str()
911 )
912 );
913 }
914
915 #[test]
916 fn strong_word_with_special_chars() {
917 let mut content = Content::from(crate::Span::new("One *wo<r>d* is strong."));
918 let p = Parser::default();
919 SubstitutionGroup::Title.apply(&mut content, &p, None);
920 assert!(!content.is_empty());
921 assert_eq!(
922 content.rendered,
923 CowStr::Boxed(
924 "One <strong>wo<r>d</strong> is strong."
925 .to_string()
926 .into_boxed_str()
927 )
928 );
929 }
930
931 #[test]
932 fn marked_string_with_id() {
933 let mut content = Content::from(crate::Span::new(r#"[#id]#a few words#"#));
934 let p = Parser::default();
935 SubstitutionGroup::Title.apply(&mut content, &p, None);
936 assert!(!content.is_empty());
937 assert_eq!(
938 content.rendered,
939 CowStr::Boxed(r#"<span id="id">a few words</span>"#.to_string().into_boxed_str())
940 );
941 }
942
943 #[test]
944 fn title_behaves_same_as_normal() {
945 let test_input = "One *wo<r>d* is strong with [#id]#marked text#.";
946
947 let mut title_content = Content::from(crate::Span::new(test_input));
948 let mut normal_content = Content::from(crate::Span::new(test_input));
949 let p = Parser::default();
950
951 SubstitutionGroup::Title.apply(&mut title_content, &p, None);
952 SubstitutionGroup::Normal.apply(&mut normal_content, &p, None);
953
954 assert_eq!(title_content.rendered, normal_content.rendered);
956 }
957 }
958}