1use std::{fmt, slice::Iter, sync::LazyLock};
2
3use regex::Regex;
4
5use crate::{
6 HasSpan, Parser, Span,
7 attributes::Attrlist,
8 blocks::{
9 Block, ContentModel, IsBlock, metadata::BlockMetadata, parse_utils::parse_blocks_until,
10 },
11 content::{Content, SubstitutionGroup},
12 document::RefType,
13 internal::debug::DebugSliceReference,
14 span::MatchedItem,
15 strings::CowStr,
16 warnings::{Warning, WarningType},
17};
18
19#[derive(Clone, Eq, PartialEq)]
24pub struct SectionBlock<'src> {
25 level: usize,
26 section_title: Content<'src>,
27 blocks: Vec<Block<'src>>,
28 source: Span<'src>,
29 title_source: Option<Span<'src>>,
30 title: Option<String>,
31 anchor: Option<Span<'src>>,
32 anchor_reftext: Option<Span<'src>>,
33 attrlist: Option<Attrlist<'src>>,
34 section_type: SectionType,
35 section_id: Option<String>,
36 section_number: Option<SectionNumber>,
37}
38
39impl<'src> SectionBlock<'src> {
40 pub(crate) fn parse(
41 metadata: &BlockMetadata<'src>,
42 parser: &mut Parser,
43 warnings: &mut Vec<Warning<'src>>,
44 ) -> Option<MatchedItem<'src, Self>> {
45 let discrete = metadata.is_discrete();
46
47 let source = metadata.block_start.discard_empty_lines();
48 let level_and_title = parse_title_line(source, warnings)?;
49
50 let sectids = parser.is_attribute_set("sectids");
53
54 let level = level_and_title.item.0;
55
56 let section_type = if discrete {
59 SectionType::Discrete
60 } else if level == 1 {
61 let section_type = if let Some(ref attrlist) = metadata.attrlist
62 && let Some(block_style) = attrlist.block_style()
63 && block_style == "appendix"
64 {
65 SectionType::Appendix
66 } else {
67 SectionType::Normal
68 };
69 parser.topmost_section_type = section_type;
70 section_type
71 } else {
72 parser.topmost_section_type
73 };
74
75 let section_number =
78 if parser.is_attribute_set("sectnums") && level <= parser.sectnumlevels && !discrete {
79 Some(parser.assign_section_number(level))
80 } else {
81 None
82 };
83
84 let mut most_recent_level = level;
85
86 let mut maw_blocks = parse_blocks_until(
87 level_and_title.after,
88 |i| discrete || peer_or_ancestor_section(*i, level, &mut most_recent_level, warnings),
89 parser,
90 );
91
92 let blocks = maw_blocks.item;
93 let source = metadata.source.trim_remainder(blocks.after);
94
95 let mut section_title = Content::from(level_and_title.item.1);
96 SubstitutionGroup::Title.apply(&mut section_title, parser, metadata.attrlist.as_ref());
97
98 let proposed_base_id = generate_section_id(section_title.rendered(), parser);
99
100 let manual_id = metadata
101 .attrlist
102 .as_ref()
103 .and_then(|a| a.id())
104 .or_else(|| metadata.anchor.as_ref().map(|anchor| anchor.data()));
105
106 let reftext = metadata
107 .attrlist
108 .as_ref()
109 .and_then(|a| a.named_attribute("reftext").map(|a| a.value()))
110 .unwrap_or_else(|| section_title.rendered());
111
112 let section_id = if let Some(catalog) = parser.catalog_mut() {
113 if sectids && manual_id.is_none() {
114 Some(catalog.generate_and_register_unique_id(
115 &proposed_base_id,
116 Some(reftext),
117 RefType::Section,
118 ))
119 } else {
120 if let Some(manual_id) = manual_id
121 && catalog
122 .register_ref(manual_id, Some(reftext), RefType::Section)
123 .is_err()
124 {
125 warnings.push(Warning {
126 source: metadata.source.trim_remainder(level_and_title.after),
127 warning: WarningType::DuplicateId(manual_id.to_string()),
128 });
129 }
130
131 None
132 }
133 } else {
134 None
135 };
136
137 if level == 1 && !discrete {
139 parser.topmost_section_type = SectionType::Normal;
140 }
141
142 warnings.append(&mut maw_blocks.warnings);
143
144 Some(MatchedItem {
145 item: Self {
146 level,
147 section_title,
148 blocks: blocks.item,
149 source: source.trim_trailing_whitespace(),
150 title_source: metadata.title_source,
151 title: metadata.title.clone(),
152 anchor: metadata.anchor,
153 anchor_reftext: metadata.anchor_reftext,
154 attrlist: metadata.attrlist.clone(),
155 section_type,
156 section_id,
157 section_number,
158 },
159 after: blocks.after,
160 })
161 }
162
163 pub fn level(&self) -> usize {
173 self.level
174 }
175
176 pub fn section_title_source(&self) -> Span<'src> {
178 self.section_title.original()
179 }
180
181 pub fn section_title(&'src self) -> &'src str {
184 self.section_title.rendered()
185 }
186
187 pub fn section_type(&'src self) -> SectionType {
189 self.section_type
190 }
191
192 #[cfg(test)]
196 pub(crate) fn section_id(&'src self) -> Option<&'src str> {
197 self.section_id.as_deref()
198 }
199
200 pub fn section_number(&'src self) -> Option<&'src SectionNumber> {
202 self.section_number.as_ref()
203 }
204}
205
206impl<'src> IsBlock<'src> for SectionBlock<'src> {
207 fn content_model(&self) -> ContentModel {
208 ContentModel::Compound
209 }
210
211 fn raw_context(&self) -> CowStr<'src> {
212 "section".into()
213 }
214
215 fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
216 self.blocks.iter()
217 }
218
219 fn title_source(&'src self) -> Option<Span<'src>> {
220 self.title_source
221 }
222
223 fn title(&self) -> Option<&str> {
224 self.title.as_deref()
225 }
226
227 fn anchor(&'src self) -> Option<Span<'src>> {
228 self.anchor
229 }
230
231 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
232 self.anchor_reftext
233 }
234
235 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
236 self.attrlist.as_ref()
237 }
238
239 fn id(&'src self) -> Option<&'src str> {
240 self.anchor()
242 .map(|a| a.data())
243 .or_else(|| self.attrlist().and_then(|attrlist| attrlist.id()))
244 .or(self.section_id.as_deref())
246 }
247}
248
249impl<'src> HasSpan<'src> for SectionBlock<'src> {
250 fn span(&self) -> Span<'src> {
251 self.source
252 }
253}
254
255impl std::fmt::Debug for SectionBlock<'_> {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 f.debug_struct("SectionBlock")
258 .field("level", &self.level)
259 .field("section_title", &self.section_title)
260 .field("blocks", &DebugSliceReference(&self.blocks))
261 .field("source", &self.source)
262 .field("title_source", &self.title_source)
263 .field("title", &self.title)
264 .field("anchor", &self.anchor)
265 .field("anchor_reftext", &self.anchor_reftext)
266 .field("attrlist", &self.attrlist)
267 .field("section_type", &self.section_type)
268 .field("section_id", &self.section_id)
269 .field("section_number", &self.section_number)
270 .finish()
271 }
272}
273
274fn parse_title_line<'src>(
275 source: Span<'src>,
276 warnings: &mut Vec<Warning<'src>>,
277) -> Option<MatchedItem<'src, (usize, Span<'src>)>> {
278 let mi = source.take_non_empty_line()?;
279 let mut line = mi.item;
280
281 let mut count = 0;
282
283 if line.starts_with('=') {
284 while let Some(mi) = line.take_prefix("=") {
285 count += 1;
286 line = mi.after;
287 }
288 } else {
289 while let Some(mi) = line.take_prefix("#") {
290 count += 1;
291 line = mi.after;
292 }
293 }
294
295 if count == 1 {
296 warnings.push(Warning {
297 source: source.take_normalized_line().item,
298 warning: WarningType::Level0SectionHeadingNotSupported,
299 });
300
301 return None;
302 }
303
304 if count > 6 {
305 warnings.push(Warning {
306 source: source.take_normalized_line().item,
307 warning: WarningType::SectionHeadingLevelExceedsMaximum(count - 1),
308 });
309
310 return None;
311 }
312
313 let title = line.take_required_whitespace()?;
314
315 Some(MatchedItem {
316 item: (count - 1, title.after),
317 after: mi.after,
318 })
319}
320
321fn peer_or_ancestor_section<'src>(
322 source: Span<'src>,
323 level: usize,
324 most_recent_level: &mut usize,
325 warnings: &mut Vec<Warning<'src>>,
326) -> bool {
327 let mut temp_parser = Parser::default();
331
332 let block_metadata_maw = BlockMetadata::parse(source, &mut temp_parser);
333
334 let block_metadata = block_metadata_maw.item;
335 if block_metadata.is_discrete() {
336 return false;
337 }
338
339 let source_after_metadata = block_metadata.block_start;
340
341 if let Some(mi) = parse_title_line(source_after_metadata, warnings) {
342 let found_level = mi.item.0;
343
344 if found_level > *most_recent_level + 1 {
345 warnings.push(Warning {
346 source: source.take_normalized_line().item,
347 warning: WarningType::SectionHeadingLevelSkipped(*most_recent_level, found_level),
348 });
349 }
350
351 *most_recent_level = found_level;
352
353 mi.item.0 <= level
354 } else {
355 false
356 }
357}
358
359fn generate_section_id(title: &str, parser: &Parser) -> String {
369 let idprefix = parser
370 .attribute_value("idprefix")
371 .as_maybe_str()
372 .unwrap_or_default()
373 .to_owned();
374
375 let idseparator = parser
376 .attribute_value("idseparator")
377 .as_maybe_str()
378 .unwrap_or_default()
379 .to_owned();
380
381 let mut gen_id = title.to_lowercase().to_owned();
382
383 #[allow(clippy::unwrap_used)]
384 static INVALID_SECTION_ID_CHARS: LazyLock<Regex> = LazyLock::new(|| {
385 Regex::new(
386 r"<[^>]+>|<[^&]*>|&(?:[a-z][a-z]+\d{0,2}|#\d{2,5}|#x[\da-f]{2,4});|[^ \w\-.]+",
387 )
388 .unwrap()
389 });
390
391 gen_id = INVALID_SECTION_ID_CHARS
392 .replace_all(&gen_id, "")
393 .to_string();
394
395 let sep = idseparator
397 .chars()
398 .next()
399 .map(|s| s.to_string())
400 .unwrap_or_default();
401
402 gen_id = gen_id.replace([' ', '.', '-'], &sep);
403
404 if !sep.is_empty() {
405 while gen_id.contains(&format!("{}{}", sep, sep)) {
406 gen_id = gen_id.replace(&format!("{}{}", sep, sep), &sep);
407 }
408
409 if gen_id.ends_with(&sep) {
410 gen_id.pop();
411 }
412
413 if idprefix.is_empty() && gen_id.starts_with(&sep) {
414 gen_id = gen_id[sep.len()..].to_string();
415 }
416 }
417
418 format!("{idprefix}{gen_id}")
419}
420
421#[derive(Clone, Copy, Default, Eq, PartialEq)]
427pub enum SectionType {
428 #[default]
430 Normal,
431
432 Appendix,
434
435 Discrete,
438}
439
440impl std::fmt::Debug for SectionType {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 match self {
443 SectionType::Normal => write!(f, "SectionType::Normal"),
444 SectionType::Appendix => write!(f, "SectionType::Appendix"),
445 SectionType::Discrete => write!(f, "SectionType::Discrete"),
446 }
447 }
448}
449
450#[derive(Clone, Default, Eq, PartialEq)]
457pub struct SectionNumber {
458 pub(crate) section_type: SectionType,
459 pub(crate) components: Vec<usize>,
460}
461
462impl SectionNumber {
463 pub(crate) fn assign_next_number(&mut self, level: usize) {
468 self.components.truncate(level);
470
471 if self.components.len() < level {
472 self.components.resize(level, 1);
473 } else if level > 0
474 && let Some(component) = self.components.get_mut(level - 1)
475 {
476 *component += 1;
477 }
478 }
479
480 pub fn components(&self) -> &[usize] {
482 &self.components
483 }
484}
485
486impl fmt::Display for SectionNumber {
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 f.write_str(
489 &self
490 .components
491 .iter()
492 .enumerate()
493 .map(|(index, x)| {
494 if index == 0 && self.section_type == SectionType::Appendix {
495 char::from_u32(b'A' as u32 + (x - 1) as u32)
496 .unwrap_or('?')
497 .to_string()
498 } else {
499 x.to_string()
500 }
501 })
502 .collect::<Vec<String>>()
503 .join("."),
504 )
505 }
506}
507
508impl fmt::Debug for SectionNumber {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 f.debug_struct("SectionNumber")
511 .field("section_type", &self.section_type)
512 .field("components", &DebugSliceReference(&self.components))
513 .finish()
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 #![allow(clippy::panic)]
520 #![allow(clippy::unwrap_used)]
521
522 use pretty_assertions_sorted::assert_eq;
523
524 use crate::{
525 Parser,
526 blocks::{IsBlock, metadata::BlockMetadata, section::SectionType},
527 tests::prelude::*,
528 warnings::WarningType,
529 };
530
531 #[test]
532 fn impl_clone() {
533 let mut parser = Parser::default();
535 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
536
537 let b1 = crate::blocks::SectionBlock::parse(
538 &BlockMetadata::new("== Section Title"),
539 &mut parser,
540 &mut warnings,
541 )
542 .unwrap();
543
544 let b2 = b1.item.clone();
545 assert_eq!(b1.item, b2);
546 }
547
548 #[test]
549 fn err_empty_source() {
550 let mut parser = Parser::default();
551 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
552
553 assert!(
554 crate::blocks::SectionBlock::parse(&BlockMetadata::new(""), &mut parser, &mut warnings)
555 .is_none()
556 );
557 }
558
559 #[test]
560 fn err_only_spaces() {
561 let mut parser = Parser::default();
562 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
563
564 assert!(
565 crate::blocks::SectionBlock::parse(
566 &BlockMetadata::new(" "),
567 &mut parser,
568 &mut warnings
569 )
570 .is_none()
571 );
572 }
573
574 #[test]
575 fn err_not_section() {
576 let mut parser = Parser::default();
577 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
578
579 assert!(
580 crate::blocks::SectionBlock::parse(
581 &BlockMetadata::new("blah blah"),
582 &mut parser,
583 &mut warnings
584 )
585 .is_none()
586 );
587 }
588
589 mod asciidoc_style_headers {
590 use std::ops::Deref;
591
592 use pretty_assertions_sorted::assert_eq;
593
594 use crate::{
595 Parser,
596 blocks::{
597 ContentModel, IsBlock, MediaType, SimpleBlockStyle, metadata::BlockMetadata,
598 section::SectionType,
599 },
600 content::SubstitutionGroup,
601 tests::prelude::*,
602 warnings::WarningType,
603 };
604
605 #[test]
606 fn err_missing_space_before_title() {
607 let mut parser = Parser::default();
608 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
609
610 assert!(
611 crate::blocks::SectionBlock::parse(
612 &BlockMetadata::new("=blah blah"),
613 &mut parser,
614 &mut warnings
615 )
616 .is_none()
617 );
618 }
619
620 #[test]
621 fn simplest_section_block() {
622 let mut parser = Parser::default();
623 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
624
625 let mi = crate::blocks::SectionBlock::parse(
626 &BlockMetadata::new("== Section Title"),
627 &mut parser,
628 &mut warnings,
629 )
630 .unwrap();
631
632 assert_eq!(mi.item.content_model(), ContentModel::Compound);
633 assert_eq!(mi.item.raw_context().deref(), "section");
634 assert_eq!(mi.item.resolved_context().deref(), "section");
635 assert!(mi.item.declared_style().is_none());
636 assert_eq!(mi.item.id().unwrap(), "_section_title");
637 assert!(mi.item.roles().is_empty());
638 assert!(mi.item.options().is_empty());
639 assert!(mi.item.title_source().is_none());
640 assert!(mi.item.title().is_none());
641 assert!(mi.item.anchor().is_none());
642 assert!(mi.item.anchor_reftext().is_none());
643 assert!(mi.item.attrlist().is_none());
644 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
645
646 assert_eq!(
647 mi.item,
648 SectionBlock {
649 level: 1,
650 section_title: Content {
651 original: Span {
652 data: "Section Title",
653 line: 1,
654 col: 4,
655 offset: 3,
656 },
657 rendered: "Section Title",
658 },
659 blocks: &[],
660 source: Span {
661 data: "== Section Title",
662 line: 1,
663 col: 1,
664 offset: 0,
665 },
666 title_source: None,
667 title: None,
668 anchor: None,
669 anchor_reftext: None,
670 attrlist: None,
671 section_type: SectionType::Normal,
672 section_id: Some("_section_title"),
673 section_number: None,
674 }
675 );
676
677 assert_eq!(
678 mi.after,
679 Span {
680 data: "",
681 line: 1,
682 col: 17,
683 offset: 16
684 }
685 );
686 }
687
688 #[test]
689 fn has_child_block() {
690 let mut parser = Parser::default();
691 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
692
693 let mi = crate::blocks::SectionBlock::parse(
694 &BlockMetadata::new("== Section Title\n\nabc"),
695 &mut parser,
696 &mut warnings,
697 )
698 .unwrap();
699
700 assert_eq!(mi.item.content_model(), ContentModel::Compound);
701 assert_eq!(mi.item.raw_context().deref(), "section");
702 assert_eq!(mi.item.resolved_context().deref(), "section");
703 assert!(mi.item.declared_style().is_none());
704 assert_eq!(mi.item.id().unwrap(), "_section_title");
705 assert!(mi.item.roles().is_empty());
706 assert!(mi.item.options().is_empty());
707 assert!(mi.item.title_source().is_none());
708 assert!(mi.item.title().is_none());
709 assert!(mi.item.anchor().is_none());
710 assert!(mi.item.anchor_reftext().is_none());
711 assert!(mi.item.attrlist().is_none());
712 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
713
714 assert_eq!(
715 mi.item,
716 SectionBlock {
717 level: 1,
718 section_title: Content {
719 original: Span {
720 data: "Section Title",
721 line: 1,
722 col: 4,
723 offset: 3,
724 },
725 rendered: "Section Title",
726 },
727 blocks: &[Block::Simple(SimpleBlock {
728 content: Content {
729 original: Span {
730 data: "abc",
731 line: 3,
732 col: 1,
733 offset: 18,
734 },
735 rendered: "abc",
736 },
737 source: Span {
738 data: "abc",
739 line: 3,
740 col: 1,
741 offset: 18,
742 },
743 style: SimpleBlockStyle::Paragraph,
744 title_source: None,
745 title: None,
746 anchor: None,
747 anchor_reftext: None,
748 attrlist: None,
749 })],
750 source: Span {
751 data: "== Section Title\n\nabc",
752 line: 1,
753 col: 1,
754 offset: 0,
755 },
756 title_source: None,
757 title: None,
758 anchor: None,
759 anchor_reftext: None,
760 attrlist: None,
761 section_type: SectionType::Normal,
762 section_id: Some("_section_title"),
763 section_number: None,
764 }
765 );
766
767 assert_eq!(
768 mi.after,
769 Span {
770 data: "",
771 line: 3,
772 col: 4,
773 offset: 21
774 }
775 );
776 }
777
778 #[test]
779 fn has_macro_block_with_extra_blank_line() {
780 let mut parser = Parser::default();
781 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
782
783 let mi = crate::blocks::SectionBlock::parse(
784 &BlockMetadata::new(
785 "== Section Title\n\nimage::bar[alt=Sunset,width=300,height=400]\n\n",
786 ),
787 &mut parser,
788 &mut warnings,
789 )
790 .unwrap();
791
792 assert_eq!(mi.item.content_model(), ContentModel::Compound);
793 assert_eq!(mi.item.raw_context().deref(), "section");
794 assert_eq!(mi.item.resolved_context().deref(), "section");
795 assert!(mi.item.declared_style().is_none());
796 assert_eq!(mi.item.id().unwrap(), "_section_title");
797 assert!(mi.item.roles().is_empty());
798 assert!(mi.item.options().is_empty());
799 assert!(mi.item.title_source().is_none());
800 assert!(mi.item.title().is_none());
801 assert!(mi.item.anchor().is_none());
802 assert!(mi.item.anchor_reftext().is_none());
803 assert!(mi.item.attrlist().is_none());
804 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
805
806 assert_eq!(
807 mi.item,
808 SectionBlock {
809 level: 1,
810 section_title: Content {
811 original: Span {
812 data: "Section Title",
813 line: 1,
814 col: 4,
815 offset: 3,
816 },
817 rendered: "Section Title",
818 },
819 blocks: &[Block::Media(MediaBlock {
820 type_: MediaType::Image,
821 target: Span {
822 data: "bar",
823 line: 3,
824 col: 8,
825 offset: 25,
826 },
827 macro_attrlist: Attrlist {
828 attributes: &[
829 ElementAttribute {
830 name: Some("alt"),
831 shorthand_items: &[],
832 value: "Sunset"
833 },
834 ElementAttribute {
835 name: Some("width"),
836 shorthand_items: &[],
837 value: "300"
838 },
839 ElementAttribute {
840 name: Some("height"),
841 shorthand_items: &[],
842 value: "400"
843 }
844 ],
845 anchor: None,
846 source: Span {
847 data: "alt=Sunset,width=300,height=400",
848 line: 3,
849 col: 12,
850 offset: 29,
851 }
852 },
853 source: Span {
854 data: "image::bar[alt=Sunset,width=300,height=400]",
855 line: 3,
856 col: 1,
857 offset: 18,
858 },
859 title_source: None,
860 title: None,
861 anchor: None,
862 anchor_reftext: None,
863 attrlist: None,
864 })],
865 source: Span {
866 data: "== Section Title\n\nimage::bar[alt=Sunset,width=300,height=400]",
867 line: 1,
868 col: 1,
869 offset: 0,
870 },
871 title_source: None,
872 title: None,
873 anchor: None,
874 anchor_reftext: None,
875 attrlist: None,
876 section_type: SectionType::Normal,
877 section_id: Some("_section_title"),
878 section_number: None,
879 }
880 );
881
882 assert_eq!(
883 mi.after,
884 Span {
885 data: "",
886 line: 5,
887 col: 1,
888 offset: 63
889 }
890 );
891 }
892
893 #[test]
894 fn has_child_block_with_errors() {
895 let mut parser = Parser::default();
896 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
897
898 let mi = crate::blocks::SectionBlock::parse(
899 &BlockMetadata::new(
900 "== Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
901 ),
902 &mut parser,
903 &mut warnings,
904 )
905 .unwrap();
906
907 assert_eq!(mi.item.content_model(), ContentModel::Compound);
908 assert_eq!(mi.item.raw_context().deref(), "section");
909 assert_eq!(mi.item.resolved_context().deref(), "section");
910 assert!(mi.item.declared_style().is_none());
911 assert_eq!(mi.item.id().unwrap(), "_section_title");
912 assert!(mi.item.roles().is_empty());
913 assert!(mi.item.options().is_empty());
914 assert!(mi.item.title_source().is_none());
915 assert!(mi.item.title().is_none());
916 assert!(mi.item.anchor().is_none());
917 assert!(mi.item.anchor_reftext().is_none());
918 assert!(mi.item.attrlist().is_none());
919 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
920
921 assert_eq!(
922 mi.item,
923 SectionBlock {
924 level: 1,
925 section_title: Content {
926 original: Span {
927 data: "Section Title",
928 line: 1,
929 col: 4,
930 offset: 3,
931 },
932 rendered: "Section Title",
933 },
934 blocks: &[Block::Media(MediaBlock {
935 type_: MediaType::Image,
936 target: Span {
937 data: "bar",
938 line: 3,
939 col: 8,
940 offset: 25,
941 },
942 macro_attrlist: Attrlist {
943 attributes: &[
944 ElementAttribute {
945 name: Some("alt"),
946 shorthand_items: &[],
947 value: "Sunset"
948 },
949 ElementAttribute {
950 name: Some("width"),
951 shorthand_items: &[],
952 value: "300"
953 },
954 ElementAttribute {
955 name: Some("height"),
956 shorthand_items: &[],
957 value: "400"
958 }
959 ],
960 anchor: None,
961 source: Span {
962 data: "alt=Sunset,width=300,,height=400",
963 line: 3,
964 col: 12,
965 offset: 29,
966 }
967 },
968 source: Span {
969 data: "image::bar[alt=Sunset,width=300,,height=400]",
970 line: 3,
971 col: 1,
972 offset: 18,
973 },
974 title_source: None,
975 title: None,
976 anchor: None,
977 anchor_reftext: None,
978 attrlist: None,
979 })],
980 source: Span {
981 data: "== Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
982 line: 1,
983 col: 1,
984 offset: 0,
985 },
986 title_source: None,
987 title: None,
988 anchor: None,
989 anchor_reftext: None,
990 attrlist: None,
991 section_type: SectionType::Normal,
992 section_id: Some("_section_title"),
993 section_number: None,
994 }
995 );
996
997 assert_eq!(
998 mi.after,
999 Span {
1000 data: "",
1001 line: 3,
1002 col: 45,
1003 offset: 62
1004 }
1005 );
1006
1007 assert_eq!(
1008 warnings,
1009 vec![Warning {
1010 source: Span {
1011 data: "alt=Sunset,width=300,,height=400",
1012 line: 3,
1013 col: 12,
1014 offset: 29,
1015 },
1016 warning: WarningType::EmptyAttributeValue,
1017 }]
1018 );
1019 }
1020
1021 #[test]
1022 fn dont_stop_at_child_section() {
1023 let mut parser = Parser::default();
1024 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1025
1026 let mi = crate::blocks::SectionBlock::parse(
1027 &BlockMetadata::new("== Section Title\n\nabc\n\n=== Section 2\n\ndef"),
1028 &mut parser,
1029 &mut warnings,
1030 )
1031 .unwrap();
1032
1033 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1034 assert_eq!(mi.item.raw_context().deref(), "section");
1035 assert_eq!(mi.item.resolved_context().deref(), "section");
1036 assert!(mi.item.declared_style().is_none());
1037 assert_eq!(mi.item.id().unwrap(), "_section_title");
1038 assert!(mi.item.roles().is_empty());
1039 assert!(mi.item.options().is_empty());
1040 assert!(mi.item.title_source().is_none());
1041 assert!(mi.item.title().is_none());
1042 assert!(mi.item.anchor().is_none());
1043 assert!(mi.item.anchor_reftext().is_none());
1044 assert!(mi.item.attrlist().is_none());
1045 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1046
1047 assert_eq!(
1048 mi.item,
1049 SectionBlock {
1050 level: 1,
1051 section_title: Content {
1052 original: Span {
1053 data: "Section Title",
1054 line: 1,
1055 col: 4,
1056 offset: 3,
1057 },
1058 rendered: "Section Title",
1059 },
1060 blocks: &[
1061 Block::Simple(SimpleBlock {
1062 content: Content {
1063 original: Span {
1064 data: "abc",
1065 line: 3,
1066 col: 1,
1067 offset: 18,
1068 },
1069 rendered: "abc",
1070 },
1071 source: Span {
1072 data: "abc",
1073 line: 3,
1074 col: 1,
1075 offset: 18,
1076 },
1077 style: SimpleBlockStyle::Paragraph,
1078 title_source: None,
1079 title: None,
1080 anchor: None,
1081 anchor_reftext: None,
1082 attrlist: None,
1083 }),
1084 Block::Section(SectionBlock {
1085 level: 2,
1086 section_title: Content {
1087 original: Span {
1088 data: "Section 2",
1089 line: 5,
1090 col: 5,
1091 offset: 27,
1092 },
1093 rendered: "Section 2",
1094 },
1095 blocks: &[Block::Simple(SimpleBlock {
1096 content: Content {
1097 original: Span {
1098 data: "def",
1099 line: 7,
1100 col: 1,
1101 offset: 38,
1102 },
1103 rendered: "def",
1104 },
1105 source: Span {
1106 data: "def",
1107 line: 7,
1108 col: 1,
1109 offset: 38,
1110 },
1111 style: SimpleBlockStyle::Paragraph,
1112 title_source: None,
1113 title: None,
1114 anchor: None,
1115 anchor_reftext: None,
1116 attrlist: None,
1117 })],
1118 source: Span {
1119 data: "=== Section 2\n\ndef",
1120 line: 5,
1121 col: 1,
1122 offset: 23,
1123 },
1124 title_source: None,
1125 title: None,
1126 anchor: None,
1127 anchor_reftext: None,
1128 attrlist: None,
1129 section_type: SectionType::Normal,
1130 section_id: Some("_section_2"),
1131 section_number: None,
1132 })
1133 ],
1134 source: Span {
1135 data: "== Section Title\n\nabc\n\n=== Section 2\n\ndef",
1136 line: 1,
1137 col: 1,
1138 offset: 0,
1139 },
1140 title_source: None,
1141 title: None,
1142 anchor: None,
1143 anchor_reftext: None,
1144 attrlist: None,
1145 section_type: SectionType::Normal,
1146 section_id: Some("_section_title"),
1147 section_number: None,
1148 }
1149 );
1150
1151 assert_eq!(
1152 mi.after,
1153 Span {
1154 data: "",
1155 line: 7,
1156 col: 4,
1157 offset: 41
1158 }
1159 );
1160 }
1161
1162 #[test]
1163 fn stop_at_peer_section() {
1164 let mut parser = Parser::default();
1165 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1166
1167 let mi = crate::blocks::SectionBlock::parse(
1168 &BlockMetadata::new("== Section Title\n\nabc\n\n== Section 2\n\ndef"),
1169 &mut parser,
1170 &mut warnings,
1171 )
1172 .unwrap();
1173
1174 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1175 assert_eq!(mi.item.raw_context().deref(), "section");
1176 assert_eq!(mi.item.resolved_context().deref(), "section");
1177 assert!(mi.item.declared_style().is_none());
1178 assert_eq!(mi.item.id().unwrap(), "_section_title");
1179 assert!(mi.item.roles().is_empty());
1180 assert!(mi.item.options().is_empty());
1181 assert!(mi.item.title_source().is_none());
1182 assert!(mi.item.title().is_none());
1183 assert!(mi.item.anchor().is_none());
1184 assert!(mi.item.anchor_reftext().is_none());
1185 assert!(mi.item.attrlist().is_none());
1186 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1187
1188 assert_eq!(
1189 mi.item,
1190 SectionBlock {
1191 level: 1,
1192 section_title: Content {
1193 original: Span {
1194 data: "Section Title",
1195 line: 1,
1196 col: 4,
1197 offset: 3,
1198 },
1199 rendered: "Section Title",
1200 },
1201 blocks: &[Block::Simple(SimpleBlock {
1202 content: Content {
1203 original: Span {
1204 data: "abc",
1205 line: 3,
1206 col: 1,
1207 offset: 18,
1208 },
1209 rendered: "abc",
1210 },
1211 source: Span {
1212 data: "abc",
1213 line: 3,
1214 col: 1,
1215 offset: 18,
1216 },
1217 style: SimpleBlockStyle::Paragraph,
1218 title_source: None,
1219 title: None,
1220 anchor: None,
1221 anchor_reftext: None,
1222 attrlist: None,
1223 })],
1224 source: Span {
1225 data: "== Section Title\n\nabc",
1226 line: 1,
1227 col: 1,
1228 offset: 0,
1229 },
1230 title_source: None,
1231 title: None,
1232 anchor: None,
1233 anchor_reftext: None,
1234 attrlist: None,
1235 section_type: SectionType::Normal,
1236 section_id: Some("_section_title"),
1237 section_number: None,
1238 }
1239 );
1240
1241 assert_eq!(
1242 mi.after,
1243 Span {
1244 data: "== Section 2\n\ndef",
1245 line: 5,
1246 col: 1,
1247 offset: 23
1248 }
1249 );
1250 }
1251
1252 #[test]
1253 fn stop_at_ancestor_section() {
1254 let mut parser = Parser::default();
1255 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1256
1257 let mi = crate::blocks::SectionBlock::parse(
1258 &BlockMetadata::new("=== Section Title\n\nabc\n\n== Section 2\n\ndef"),
1259 &mut parser,
1260 &mut warnings,
1261 )
1262 .unwrap();
1263
1264 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1265 assert_eq!(mi.item.raw_context().deref(), "section");
1266 assert_eq!(mi.item.resolved_context().deref(), "section");
1267 assert!(mi.item.declared_style().is_none());
1268 assert_eq!(mi.item.id().unwrap(), "_section_title");
1269 assert!(mi.item.roles().is_empty());
1270 assert!(mi.item.options().is_empty());
1271 assert!(mi.item.title_source().is_none());
1272 assert!(mi.item.title().is_none());
1273 assert!(mi.item.anchor().is_none());
1274 assert!(mi.item.anchor_reftext().is_none());
1275 assert!(mi.item.attrlist().is_none());
1276 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1277
1278 assert_eq!(
1279 mi.item,
1280 SectionBlock {
1281 level: 2,
1282 section_title: Content {
1283 original: Span {
1284 data: "Section Title",
1285 line: 1,
1286 col: 5,
1287 offset: 4,
1288 },
1289 rendered: "Section Title",
1290 },
1291 blocks: &[Block::Simple(SimpleBlock {
1292 content: Content {
1293 original: Span {
1294 data: "abc",
1295 line: 3,
1296 col: 1,
1297 offset: 19,
1298 },
1299 rendered: "abc",
1300 },
1301 source: Span {
1302 data: "abc",
1303 line: 3,
1304 col: 1,
1305 offset: 19,
1306 },
1307 style: SimpleBlockStyle::Paragraph,
1308 title_source: None,
1309 title: None,
1310 anchor: None,
1311 anchor_reftext: None,
1312 attrlist: None,
1313 })],
1314 source: Span {
1315 data: "=== Section Title\n\nabc",
1316 line: 1,
1317 col: 1,
1318 offset: 0,
1319 },
1320 title_source: None,
1321 title: None,
1322 anchor: None,
1323 anchor_reftext: None,
1324 attrlist: None,
1325 section_type: SectionType::Normal,
1326 section_id: Some("_section_title"),
1327 section_number: None,
1328 }
1329 );
1330
1331 assert_eq!(
1332 mi.after,
1333 Span {
1334 data: "== Section 2\n\ndef",
1335 line: 5,
1336 col: 1,
1337 offset: 24
1338 }
1339 );
1340 }
1341
1342 #[test]
1343 fn section_title_with_markup() {
1344 let mut parser = Parser::default();
1345 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1346
1347 let mi = crate::blocks::SectionBlock::parse(
1348 &BlockMetadata::new("== Section with *bold* text"),
1349 &mut parser,
1350 &mut warnings,
1351 )
1352 .unwrap();
1353
1354 assert_eq!(
1355 mi.item.section_title_source(),
1356 Span {
1357 data: "Section with *bold* text",
1358 line: 1,
1359 col: 4,
1360 offset: 3,
1361 }
1362 );
1363
1364 assert_eq!(
1365 mi.item.section_title(),
1366 "Section with <strong>bold</strong> text"
1367 );
1368
1369 assert_eq!(mi.item.section_type(), SectionType::Normal);
1370 assert_eq!(mi.item.id().unwrap(), "_section_with_bold_text");
1371 }
1372
1373 #[test]
1374 fn section_title_with_special_chars() {
1375 let mut parser = Parser::default();
1376 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1377
1378 let mi = crate::blocks::SectionBlock::parse(
1379 &BlockMetadata::new("== Section with <brackets> & ampersands"),
1380 &mut parser,
1381 &mut warnings,
1382 )
1383 .unwrap();
1384
1385 assert_eq!(
1386 mi.item.section_title_source(),
1387 Span {
1388 data: "Section with <brackets> & ampersands",
1389 line: 1,
1390 col: 4,
1391 offset: 3,
1392 }
1393 );
1394
1395 assert_eq!(
1396 mi.item.section_title(),
1397 "Section with <brackets> & ampersands"
1398 );
1399
1400 assert_eq!(mi.item.id().unwrap(), "_section_with_ampersands");
1401 }
1402
1403 #[test]
1404 fn err_level_0_section_heading() {
1405 let mut parser = Parser::default();
1406 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1407
1408 let result = crate::blocks::SectionBlock::parse(
1409 &BlockMetadata::new("= Document Title"),
1410 &mut parser,
1411 &mut warnings,
1412 );
1413
1414 assert!(result.is_none());
1415
1416 assert_eq!(
1417 warnings,
1418 vec![Warning {
1419 source: Span {
1420 data: "= Document Title",
1421 line: 1,
1422 col: 1,
1423 offset: 0,
1424 },
1425 warning: WarningType::Level0SectionHeadingNotSupported,
1426 }]
1427 );
1428 }
1429
1430 #[test]
1431 fn err_section_heading_level_exceeds_maximum() {
1432 let mut parser = Parser::default();
1433 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1434
1435 let result = crate::blocks::SectionBlock::parse(
1436 &BlockMetadata::new("======= Level 6 Section"),
1437 &mut parser,
1438 &mut warnings,
1439 );
1440
1441 assert!(result.is_none());
1442
1443 assert_eq!(
1444 warnings,
1445 vec![Warning {
1446 source: Span {
1447 data: "======= Level 6 Section",
1448 line: 1,
1449 col: 1,
1450 offset: 0,
1451 },
1452 warning: WarningType::SectionHeadingLevelExceedsMaximum(6),
1453 }]
1454 );
1455 }
1456
1457 #[test]
1458 fn valid_maximum_level_5_section() {
1459 let mut parser = Parser::default();
1460 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1461
1462 let mi = crate::blocks::SectionBlock::parse(
1463 &BlockMetadata::new("====== Level 5 Section"),
1464 &mut parser,
1465 &mut warnings,
1466 )
1467 .unwrap();
1468
1469 assert!(warnings.is_empty());
1470
1471 assert_eq!(mi.item.level(), 5);
1472 assert_eq!(mi.item.section_title(), "Level 5 Section");
1473 assert_eq!(mi.item.section_type(), SectionType::Normal);
1474 assert_eq!(mi.item.id().unwrap(), "_level_5_section");
1475 }
1476
1477 #[test]
1478 fn warn_section_level_skipped() {
1479 let mut parser = Parser::default();
1480 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1481
1482 let mi = crate::blocks::SectionBlock::parse(
1483 &BlockMetadata::new("== Level 1\n\n==== Level 3 (skipped level 2)"),
1484 &mut parser,
1485 &mut warnings,
1486 )
1487 .unwrap();
1488
1489 assert_eq!(mi.item.level(), 1);
1490 assert_eq!(mi.item.section_title(), "Level 1");
1491 assert_eq!(mi.item.section_type(), SectionType::Normal);
1492 assert_eq!(mi.item.nested_blocks().len(), 1);
1493 assert_eq!(mi.item.id().unwrap(), "_level_1");
1494
1495 assert_eq!(
1496 warnings,
1497 vec![Warning {
1498 source: Span {
1499 data: "==== Level 3 (skipped level 2)",
1500 line: 3,
1501 col: 1,
1502 offset: 12,
1503 },
1504 warning: WarningType::SectionHeadingLevelSkipped(1, 3),
1505 }]
1506 );
1507 }
1508 }
1509
1510 mod markdown_style_headings {
1511 use std::ops::Deref;
1512
1513 use pretty_assertions_sorted::assert_eq;
1514
1515 use crate::{
1516 Parser,
1517 blocks::{
1518 ContentModel, IsBlock, MediaType, SimpleBlockStyle, metadata::BlockMetadata,
1519 section::SectionType,
1520 },
1521 content::SubstitutionGroup,
1522 tests::prelude::*,
1523 warnings::WarningType,
1524 };
1525
1526 #[test]
1527 fn err_missing_space_before_title() {
1528 let mut parser = Parser::default();
1529 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1530
1531 assert!(
1532 crate::blocks::SectionBlock::parse(
1533 &BlockMetadata::new("#blah blah"),
1534 &mut parser,
1535 &mut warnings
1536 )
1537 .is_none()
1538 );
1539 }
1540
1541 #[test]
1542 fn simplest_section_block() {
1543 let mut parser = Parser::default();
1544 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1545
1546 let mi = crate::blocks::SectionBlock::parse(
1547 &BlockMetadata::new("## Section Title"),
1548 &mut parser,
1549 &mut warnings,
1550 )
1551 .unwrap();
1552
1553 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1554 assert_eq!(mi.item.raw_context().deref(), "section");
1555 assert_eq!(mi.item.resolved_context().deref(), "section");
1556 assert!(mi.item.declared_style().is_none());
1557 assert_eq!(mi.item.id().unwrap(), "_section_title");
1558 assert!(mi.item.roles().is_empty());
1559 assert!(mi.item.options().is_empty());
1560 assert!(mi.item.title_source().is_none());
1561 assert!(mi.item.title().is_none());
1562 assert!(mi.item.anchor().is_none());
1563 assert!(mi.item.anchor_reftext().is_none());
1564 assert!(mi.item.attrlist().is_none());
1565 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1566
1567 assert_eq!(
1568 mi.item,
1569 SectionBlock {
1570 level: 1,
1571 section_title: Content {
1572 original: Span {
1573 data: "Section Title",
1574 line: 1,
1575 col: 4,
1576 offset: 3,
1577 },
1578 rendered: "Section Title",
1579 },
1580 blocks: &[],
1581 source: Span {
1582 data: "## Section Title",
1583 line: 1,
1584 col: 1,
1585 offset: 0,
1586 },
1587 title_source: None,
1588 title: None,
1589 anchor: None,
1590 anchor_reftext: None,
1591 attrlist: None,
1592 section_type: SectionType::Normal,
1593 section_id: Some("_section_title"),
1594 section_number: None,
1595 }
1596 );
1597
1598 assert_eq!(
1599 mi.after,
1600 Span {
1601 data: "",
1602 line: 1,
1603 col: 17,
1604 offset: 16
1605 }
1606 );
1607 }
1608
1609 #[test]
1610 fn has_child_block() {
1611 let mut parser = Parser::default();
1612 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1613
1614 let mi = crate::blocks::SectionBlock::parse(
1615 &BlockMetadata::new("## Section Title\n\nabc"),
1616 &mut parser,
1617 &mut warnings,
1618 )
1619 .unwrap();
1620
1621 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1622 assert_eq!(mi.item.raw_context().deref(), "section");
1623 assert_eq!(mi.item.resolved_context().deref(), "section");
1624 assert!(mi.item.declared_style().is_none());
1625 assert_eq!(mi.item.id().unwrap(), "_section_title");
1626 assert!(mi.item.roles().is_empty());
1627 assert!(mi.item.options().is_empty());
1628 assert!(mi.item.title_source().is_none());
1629 assert!(mi.item.title().is_none());
1630 assert!(mi.item.anchor().is_none());
1631 assert!(mi.item.anchor_reftext().is_none());
1632 assert!(mi.item.attrlist().is_none());
1633 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1634
1635 assert_eq!(
1636 mi.item,
1637 SectionBlock {
1638 level: 1,
1639 section_title: Content {
1640 original: Span {
1641 data: "Section Title",
1642 line: 1,
1643 col: 4,
1644 offset: 3,
1645 },
1646 rendered: "Section Title",
1647 },
1648 blocks: &[Block::Simple(SimpleBlock {
1649 content: Content {
1650 original: Span {
1651 data: "abc",
1652 line: 3,
1653 col: 1,
1654 offset: 18,
1655 },
1656 rendered: "abc",
1657 },
1658 source: Span {
1659 data: "abc",
1660 line: 3,
1661 col: 1,
1662 offset: 18,
1663 },
1664 style: SimpleBlockStyle::Paragraph,
1665 title_source: None,
1666 title: None,
1667 anchor: None,
1668 anchor_reftext: None,
1669 attrlist: None,
1670 })],
1671 source: Span {
1672 data: "## Section Title\n\nabc",
1673 line: 1,
1674 col: 1,
1675 offset: 0,
1676 },
1677 title_source: None,
1678 title: None,
1679 anchor: None,
1680 anchor_reftext: None,
1681 attrlist: None,
1682 section_type: SectionType::Normal,
1683 section_id: Some("_section_title"),
1684 section_number: None,
1685 }
1686 );
1687
1688 assert_eq!(
1689 mi.after,
1690 Span {
1691 data: "",
1692 line: 3,
1693 col: 4,
1694 offset: 21
1695 }
1696 );
1697 }
1698
1699 #[test]
1700 fn has_macro_block_with_extra_blank_line() {
1701 let mut parser = Parser::default();
1702 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1703
1704 let mi = crate::blocks::SectionBlock::parse(
1705 &BlockMetadata::new(
1706 "## Section Title\n\nimage::bar[alt=Sunset,width=300,height=400]\n\n",
1707 ),
1708 &mut parser,
1709 &mut warnings,
1710 )
1711 .unwrap();
1712
1713 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1714 assert_eq!(mi.item.raw_context().deref(), "section");
1715 assert_eq!(mi.item.resolved_context().deref(), "section");
1716 assert!(mi.item.declared_style().is_none());
1717 assert_eq!(mi.item.id().unwrap(), "_section_title");
1718 assert!(mi.item.roles().is_empty());
1719 assert!(mi.item.options().is_empty());
1720 assert!(mi.item.title_source().is_none());
1721 assert!(mi.item.title().is_none());
1722 assert!(mi.item.anchor().is_none());
1723 assert!(mi.item.anchor_reftext().is_none());
1724 assert!(mi.item.attrlist().is_none());
1725 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1726
1727 assert_eq!(
1728 mi.item,
1729 SectionBlock {
1730 level: 1,
1731 section_title: Content {
1732 original: Span {
1733 data: "Section Title",
1734 line: 1,
1735 col: 4,
1736 offset: 3,
1737 },
1738 rendered: "Section Title",
1739 },
1740 blocks: &[Block::Media(MediaBlock {
1741 type_: MediaType::Image,
1742 target: Span {
1743 data: "bar",
1744 line: 3,
1745 col: 8,
1746 offset: 25,
1747 },
1748 macro_attrlist: Attrlist {
1749 attributes: &[
1750 ElementAttribute {
1751 name: Some("alt"),
1752 shorthand_items: &[],
1753 value: "Sunset"
1754 },
1755 ElementAttribute {
1756 name: Some("width"),
1757 shorthand_items: &[],
1758 value: "300"
1759 },
1760 ElementAttribute {
1761 name: Some("height"),
1762 shorthand_items: &[],
1763 value: "400"
1764 }
1765 ],
1766 anchor: None,
1767 source: Span {
1768 data: "alt=Sunset,width=300,height=400",
1769 line: 3,
1770 col: 12,
1771 offset: 29,
1772 }
1773 },
1774 source: Span {
1775 data: "image::bar[alt=Sunset,width=300,height=400]",
1776 line: 3,
1777 col: 1,
1778 offset: 18,
1779 },
1780 title_source: None,
1781 title: None,
1782 anchor: None,
1783 anchor_reftext: None,
1784 attrlist: None,
1785 })],
1786 source: Span {
1787 data: "## Section Title\n\nimage::bar[alt=Sunset,width=300,height=400]",
1788 line: 1,
1789 col: 1,
1790 offset: 0,
1791 },
1792 title_source: None,
1793 title: None,
1794 anchor: None,
1795 anchor_reftext: None,
1796 attrlist: None,
1797 section_type: SectionType::Normal,
1798 section_id: Some("_section_title"),
1799 section_number: None,
1800 }
1801 );
1802
1803 assert_eq!(
1804 mi.after,
1805 Span {
1806 data: "",
1807 line: 5,
1808 col: 1,
1809 offset: 63
1810 }
1811 );
1812 }
1813
1814 #[test]
1815 fn has_child_block_with_errors() {
1816 let mut parser = Parser::default();
1817 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1818
1819 let mi = crate::blocks::SectionBlock::parse(
1820 &BlockMetadata::new(
1821 "## Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
1822 ),
1823 &mut parser,
1824 &mut warnings,
1825 )
1826 .unwrap();
1827
1828 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1829 assert_eq!(mi.item.raw_context().deref(), "section");
1830 assert_eq!(mi.item.resolved_context().deref(), "section");
1831 assert!(mi.item.declared_style().is_none());
1832 assert_eq!(mi.item.id().unwrap(), "_section_title");
1833 assert!(mi.item.roles().is_empty());
1834 assert!(mi.item.options().is_empty());
1835 assert!(mi.item.title_source().is_none());
1836 assert!(mi.item.title().is_none());
1837 assert!(mi.item.anchor().is_none());
1838 assert!(mi.item.anchor_reftext().is_none());
1839 assert!(mi.item.attrlist().is_none());
1840 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1841
1842 assert_eq!(
1843 mi.item,
1844 SectionBlock {
1845 level: 1,
1846 section_title: Content {
1847 original: Span {
1848 data: "Section Title",
1849 line: 1,
1850 col: 4,
1851 offset: 3,
1852 },
1853 rendered: "Section Title",
1854 },
1855 blocks: &[Block::Media(MediaBlock {
1856 type_: MediaType::Image,
1857 target: Span {
1858 data: "bar",
1859 line: 3,
1860 col: 8,
1861 offset: 25,
1862 },
1863 macro_attrlist: Attrlist {
1864 attributes: &[
1865 ElementAttribute {
1866 name: Some("alt"),
1867 shorthand_items: &[],
1868 value: "Sunset"
1869 },
1870 ElementAttribute {
1871 name: Some("width"),
1872 shorthand_items: &[],
1873 value: "300"
1874 },
1875 ElementAttribute {
1876 name: Some("height"),
1877 shorthand_items: &[],
1878 value: "400"
1879 }
1880 ],
1881 anchor: None,
1882 source: Span {
1883 data: "alt=Sunset,width=300,,height=400",
1884 line: 3,
1885 col: 12,
1886 offset: 29,
1887 }
1888 },
1889 source: Span {
1890 data: "image::bar[alt=Sunset,width=300,,height=400]",
1891 line: 3,
1892 col: 1,
1893 offset: 18,
1894 },
1895 title_source: None,
1896 title: None,
1897 anchor: None,
1898 anchor_reftext: None,
1899 attrlist: None,
1900 })],
1901 source: Span {
1902 data: "## Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
1903 line: 1,
1904 col: 1,
1905 offset: 0,
1906 },
1907 title_source: None,
1908 title: None,
1909 anchor: None,
1910 anchor_reftext: None,
1911 attrlist: None,
1912 section_type: SectionType::Normal,
1913 section_id: Some("_section_title"),
1914 section_number: None,
1915 }
1916 );
1917
1918 assert_eq!(
1919 mi.after,
1920 Span {
1921 data: "",
1922 line: 3,
1923 col: 45,
1924 offset: 62
1925 }
1926 );
1927
1928 assert_eq!(
1929 warnings,
1930 vec![Warning {
1931 source: Span {
1932 data: "alt=Sunset,width=300,,height=400",
1933 line: 3,
1934 col: 12,
1935 offset: 29,
1936 },
1937 warning: WarningType::EmptyAttributeValue,
1938 }]
1939 );
1940 }
1941
1942 #[test]
1943 fn dont_stop_at_child_section() {
1944 let mut parser = Parser::default();
1945 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
1946
1947 let mi = crate::blocks::SectionBlock::parse(
1948 &BlockMetadata::new("## Section Title\n\nabc\n\n### Section 2\n\ndef"),
1949 &mut parser,
1950 &mut warnings,
1951 )
1952 .unwrap();
1953
1954 assert_eq!(mi.item.content_model(), ContentModel::Compound);
1955 assert_eq!(mi.item.raw_context().deref(), "section");
1956 assert_eq!(mi.item.resolved_context().deref(), "section");
1957 assert!(mi.item.declared_style().is_none());
1958 assert_eq!(mi.item.id().unwrap(), "_section_title");
1959 assert!(mi.item.roles().is_empty());
1960 assert!(mi.item.options().is_empty());
1961 assert!(mi.item.title_source().is_none());
1962 assert!(mi.item.title().is_none());
1963 assert!(mi.item.anchor().is_none());
1964 assert!(mi.item.anchor_reftext().is_none());
1965 assert!(mi.item.attrlist().is_none());
1966 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1967
1968 assert_eq!(
1969 mi.item,
1970 SectionBlock {
1971 level: 1,
1972 section_title: Content {
1973 original: Span {
1974 data: "Section Title",
1975 line: 1,
1976 col: 4,
1977 offset: 3,
1978 },
1979 rendered: "Section Title",
1980 },
1981 blocks: &[
1982 Block::Simple(SimpleBlock {
1983 content: Content {
1984 original: Span {
1985 data: "abc",
1986 line: 3,
1987 col: 1,
1988 offset: 18,
1989 },
1990 rendered: "abc",
1991 },
1992 source: Span {
1993 data: "abc",
1994 line: 3,
1995 col: 1,
1996 offset: 18,
1997 },
1998 style: SimpleBlockStyle::Paragraph,
1999 title_source: None,
2000 title: None,
2001 anchor: None,
2002 anchor_reftext: None,
2003 attrlist: None,
2004 }),
2005 Block::Section(SectionBlock {
2006 level: 2,
2007 section_title: Content {
2008 original: Span {
2009 data: "Section 2",
2010 line: 5,
2011 col: 5,
2012 offset: 27,
2013 },
2014 rendered: "Section 2",
2015 },
2016 blocks: &[Block::Simple(SimpleBlock {
2017 content: Content {
2018 original: Span {
2019 data: "def",
2020 line: 7,
2021 col: 1,
2022 offset: 38,
2023 },
2024 rendered: "def",
2025 },
2026 source: Span {
2027 data: "def",
2028 line: 7,
2029 col: 1,
2030 offset: 38,
2031 },
2032 style: SimpleBlockStyle::Paragraph,
2033 title_source: None,
2034 title: None,
2035 anchor: None,
2036 anchor_reftext: None,
2037 attrlist: None,
2038 })],
2039 source: Span {
2040 data: "### Section 2\n\ndef",
2041 line: 5,
2042 col: 1,
2043 offset: 23,
2044 },
2045 title_source: None,
2046 title: None,
2047 anchor: None,
2048 anchor_reftext: None,
2049 attrlist: None,
2050 section_type: SectionType::Normal,
2051 section_id: Some("_section_2"),
2052 section_number: None,
2053 })
2054 ],
2055 source: Span {
2056 data: "## Section Title\n\nabc\n\n### Section 2\n\ndef",
2057 line: 1,
2058 col: 1,
2059 offset: 0,
2060 },
2061 title_source: None,
2062 title: None,
2063 anchor: None,
2064 anchor_reftext: None,
2065 attrlist: None,
2066 section_type: SectionType::Normal,
2067 section_id: Some("_section_title"),
2068 section_number: None,
2069 }
2070 );
2071
2072 assert_eq!(
2073 mi.after,
2074 Span {
2075 data: "",
2076 line: 7,
2077 col: 4,
2078 offset: 41
2079 }
2080 );
2081 }
2082
2083 #[test]
2084 fn stop_at_peer_section() {
2085 let mut parser = Parser::default();
2086 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2087
2088 let mi = crate::blocks::SectionBlock::parse(
2089 &BlockMetadata::new("## Section Title\n\nabc\n\n## Section 2\n\ndef"),
2090 &mut parser,
2091 &mut warnings,
2092 )
2093 .unwrap();
2094
2095 assert_eq!(mi.item.content_model(), ContentModel::Compound);
2096 assert_eq!(mi.item.raw_context().deref(), "section");
2097 assert_eq!(mi.item.resolved_context().deref(), "section");
2098 assert!(mi.item.declared_style().is_none());
2099 assert_eq!(mi.item.id().unwrap(), "_section_title");
2100 assert!(mi.item.roles().is_empty());
2101 assert!(mi.item.options().is_empty());
2102 assert!(mi.item.title_source().is_none());
2103 assert!(mi.item.title().is_none());
2104 assert!(mi.item.anchor().is_none());
2105 assert!(mi.item.anchor_reftext().is_none());
2106 assert!(mi.item.attrlist().is_none());
2107 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
2108
2109 assert_eq!(
2110 mi.item,
2111 SectionBlock {
2112 level: 1,
2113 section_title: Content {
2114 original: Span {
2115 data: "Section Title",
2116 line: 1,
2117 col: 4,
2118 offset: 3,
2119 },
2120 rendered: "Section Title",
2121 },
2122 blocks: &[Block::Simple(SimpleBlock {
2123 content: Content {
2124 original: Span {
2125 data: "abc",
2126 line: 3,
2127 col: 1,
2128 offset: 18,
2129 },
2130 rendered: "abc",
2131 },
2132 source: Span {
2133 data: "abc",
2134 line: 3,
2135 col: 1,
2136 offset: 18,
2137 },
2138 style: SimpleBlockStyle::Paragraph,
2139 title_source: None,
2140 title: None,
2141 anchor: None,
2142 anchor_reftext: None,
2143 attrlist: None,
2144 })],
2145 source: Span {
2146 data: "## Section Title\n\nabc",
2147 line: 1,
2148 col: 1,
2149 offset: 0,
2150 },
2151 title_source: None,
2152 title: None,
2153 anchor: None,
2154 anchor_reftext: None,
2155 attrlist: None,
2156 section_type: SectionType::Normal,
2157 section_id: Some("_section_title"),
2158 section_number: None,
2159 }
2160 );
2161
2162 assert_eq!(
2163 mi.after,
2164 Span {
2165 data: "## Section 2\n\ndef",
2166 line: 5,
2167 col: 1,
2168 offset: 23
2169 }
2170 );
2171 }
2172
2173 #[test]
2174 fn stop_at_ancestor_section() {
2175 let mut parser = Parser::default();
2176 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2177
2178 let mi = crate::blocks::SectionBlock::parse(
2179 &BlockMetadata::new("### Section Title\n\nabc\n\n## Section 2\n\ndef"),
2180 &mut parser,
2181 &mut warnings,
2182 )
2183 .unwrap();
2184
2185 assert_eq!(mi.item.content_model(), ContentModel::Compound);
2186 assert_eq!(mi.item.raw_context().deref(), "section");
2187 assert_eq!(mi.item.resolved_context().deref(), "section");
2188 assert!(mi.item.declared_style().is_none());
2189 assert_eq!(mi.item.id().unwrap(), "_section_title");
2190 assert!(mi.item.roles().is_empty());
2191 assert!(mi.item.options().is_empty());
2192 assert!(mi.item.title_source().is_none());
2193 assert!(mi.item.title().is_none());
2194 assert!(mi.item.anchor().is_none());
2195 assert!(mi.item.anchor_reftext().is_none());
2196 assert!(mi.item.attrlist().is_none());
2197 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
2198
2199 assert_eq!(
2200 mi.item,
2201 SectionBlock {
2202 level: 2,
2203 section_title: Content {
2204 original: Span {
2205 data: "Section Title",
2206 line: 1,
2207 col: 5,
2208 offset: 4,
2209 },
2210 rendered: "Section Title",
2211 },
2212 blocks: &[Block::Simple(SimpleBlock {
2213 content: Content {
2214 original: Span {
2215 data: "abc",
2216 line: 3,
2217 col: 1,
2218 offset: 19,
2219 },
2220 rendered: "abc",
2221 },
2222 source: Span {
2223 data: "abc",
2224 line: 3,
2225 col: 1,
2226 offset: 19,
2227 },
2228 style: SimpleBlockStyle::Paragraph,
2229 title_source: None,
2230 title: None,
2231 anchor: None,
2232 anchor_reftext: None,
2233 attrlist: None,
2234 })],
2235 source: Span {
2236 data: "### Section Title\n\nabc",
2237 line: 1,
2238 col: 1,
2239 offset: 0,
2240 },
2241 title_source: None,
2242 title: None,
2243 anchor: None,
2244 anchor_reftext: None,
2245 attrlist: None,
2246 section_type: SectionType::Normal,
2247 section_id: Some("_section_title"),
2248 section_number: None,
2249 }
2250 );
2251
2252 assert_eq!(
2253 mi.after,
2254 Span {
2255 data: "## Section 2\n\ndef",
2256 line: 5,
2257 col: 1,
2258 offset: 24
2259 }
2260 );
2261 }
2262
2263 #[test]
2264 fn section_title_with_markup() {
2265 let mut parser = Parser::default();
2266 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2267
2268 let mi = crate::blocks::SectionBlock::parse(
2269 &BlockMetadata::new("## Section with *bold* text"),
2270 &mut parser,
2271 &mut warnings,
2272 )
2273 .unwrap();
2274
2275 assert_eq!(
2276 mi.item.section_title_source(),
2277 Span {
2278 data: "Section with *bold* text",
2279 line: 1,
2280 col: 4,
2281 offset: 3,
2282 }
2283 );
2284
2285 assert_eq!(
2286 mi.item.section_title(),
2287 "Section with <strong>bold</strong> text"
2288 );
2289
2290 assert_eq!(mi.item.section_type(), SectionType::Normal);
2291 assert_eq!(mi.item.id().unwrap(), "_section_with_bold_text");
2292 }
2293
2294 #[test]
2295 fn section_title_with_special_chars() {
2296 let mut parser = Parser::default();
2297 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2298
2299 let mi = crate::blocks::SectionBlock::parse(
2300 &BlockMetadata::new("## Section with <brackets> & ampersands"),
2301 &mut parser,
2302 &mut warnings,
2303 )
2304 .unwrap();
2305
2306 assert_eq!(
2307 mi.item.section_title_source(),
2308 Span {
2309 data: "Section with <brackets> & ampersands",
2310 line: 1,
2311 col: 4,
2312 offset: 3,
2313 }
2314 );
2315
2316 assert_eq!(
2317 mi.item.section_title(),
2318 "Section with <brackets> & ampersands"
2319 );
2320
2321 assert_eq!(mi.item.section_type(), SectionType::Normal);
2322 }
2323
2324 #[test]
2325 fn err_level_0_section_heading() {
2326 let mut parser = Parser::default();
2327 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2328
2329 let result = crate::blocks::SectionBlock::parse(
2330 &BlockMetadata::new("# Document Title"),
2331 &mut parser,
2332 &mut warnings,
2333 );
2334
2335 assert!(result.is_none());
2336
2337 assert_eq!(
2338 warnings,
2339 vec![Warning {
2340 source: Span {
2341 data: "# Document Title",
2342 line: 1,
2343 col: 1,
2344 offset: 0,
2345 },
2346 warning: WarningType::Level0SectionHeadingNotSupported,
2347 }]
2348 );
2349 }
2350
2351 #[test]
2352 fn err_section_heading_level_exceeds_maximum() {
2353 let mut parser = Parser::default();
2354 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2355
2356 let result = crate::blocks::SectionBlock::parse(
2357 &BlockMetadata::new("####### Level 6 Section"),
2358 &mut parser,
2359 &mut warnings,
2360 );
2361
2362 assert!(result.is_none());
2363
2364 assert_eq!(
2365 warnings,
2366 vec![Warning {
2367 source: Span {
2368 data: "####### Level 6 Section",
2369 line: 1,
2370 col: 1,
2371 offset: 0,
2372 },
2373 warning: WarningType::SectionHeadingLevelExceedsMaximum(6),
2374 }]
2375 );
2376 }
2377
2378 #[test]
2379 fn valid_maximum_level_5_section() {
2380 let mut parser = Parser::default();
2381 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2382
2383 let mi = crate::blocks::SectionBlock::parse(
2384 &BlockMetadata::new("###### Level 5 Section"),
2385 &mut parser,
2386 &mut warnings,
2387 )
2388 .unwrap();
2389
2390 assert!(warnings.is_empty());
2391
2392 assert_eq!(mi.item.level(), 5);
2393 assert_eq!(mi.item.section_title(), "Level 5 Section");
2394 assert_eq!(mi.item.section_type(), SectionType::Normal);
2395 assert_eq!(mi.item.id().unwrap(), "_level_5_section");
2396 }
2397
2398 #[test]
2399 fn warn_section_level_skipped() {
2400 let mut parser = Parser::default();
2401 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2402
2403 let mi = crate::blocks::SectionBlock::parse(
2404 &BlockMetadata::new("## Level 1\n\n#### Level 3 (skipped level 2)"),
2405 &mut parser,
2406 &mut warnings,
2407 )
2408 .unwrap();
2409
2410 assert_eq!(mi.item.level(), 1);
2411 assert_eq!(mi.item.section_title(), "Level 1");
2412 assert_eq!(mi.item.section_type(), SectionType::Normal);
2413 assert_eq!(mi.item.nested_blocks().len(), 1);
2414 assert_eq!(mi.item.id().unwrap(), "_level_1");
2415
2416 assert_eq!(
2417 warnings,
2418 vec![Warning {
2419 source: Span {
2420 data: "#### Level 3 (skipped level 2)",
2421 line: 3,
2422 col: 1,
2423 offset: 12,
2424 },
2425 warning: WarningType::SectionHeadingLevelSkipped(1, 3),
2426 }]
2427 );
2428 }
2429 }
2430
2431 #[test]
2432 fn warn_multiple_section_levels_skipped() {
2433 let mut parser = Parser::default();
2434 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2435
2436 let mi = crate::blocks::SectionBlock::parse(
2437 &BlockMetadata::new("== Level 1\n\n===== Level 4 (skipped levels 2 and 3)"),
2438 &mut parser,
2439 &mut warnings,
2440 )
2441 .unwrap();
2442
2443 assert_eq!(mi.item.level(), 1);
2444 assert_eq!(mi.item.section_title(), "Level 1");
2445 assert_eq!(mi.item.section_type(), SectionType::Normal);
2446 assert_eq!(mi.item.nested_blocks().len(), 1);
2447 assert_eq!(mi.item.id().unwrap(), "_level_1");
2448
2449 assert_eq!(
2450 warnings,
2451 vec![Warning {
2452 source: Span {
2453 data: "===== Level 4 (skipped levels 2 and 3)",
2454 line: 3,
2455 col: 1,
2456 offset: 12,
2457 },
2458 warning: WarningType::SectionHeadingLevelSkipped(1, 4),
2459 }]
2460 );
2461 }
2462
2463 #[test]
2464 fn no_warning_for_consecutive_section_levels() {
2465 let mut parser = Parser::default();
2466 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2467
2468 let mi = crate::blocks::SectionBlock::parse(
2469 &BlockMetadata::new("== Level 1\n\n=== Level 2 (no skip)"),
2470 &mut parser,
2471 &mut warnings,
2472 )
2473 .unwrap();
2474
2475 assert_eq!(mi.item.level(), 1);
2476 assert_eq!(mi.item.section_title(), "Level 1");
2477 assert_eq!(mi.item.section_type(), SectionType::Normal);
2478 assert_eq!(mi.item.nested_blocks().len(), 1);
2479 assert_eq!(mi.item.id().unwrap(), "_level_1");
2480
2481 assert!(warnings.is_empty());
2482 }
2483
2484 #[test]
2485 fn section_id_generation_basic() {
2486 let input = "== Section One";
2487 let mut parser = Parser::default();
2488 let document = parser.parse(input);
2489
2490 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2491 assert_eq!(section.id(), Some("_section_one"));
2492 } else {
2493 panic!("Expected section block");
2494 }
2495 }
2496
2497 #[test]
2498 fn section_id_generation_with_special_characters() {
2499 let input = "== We're back! & Company";
2500 let mut parser = Parser::default();
2501 let document = parser.parse(input);
2502
2503 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2504 assert_eq!(section.id(), Some("_were_back_company"));
2505 } else {
2506 panic!("Expected section block");
2507 }
2508 }
2509
2510 #[test]
2511 fn section_id_generation_with_entities() {
2512 let input = "== Ben & Jerry "Ice Cream"";
2513 let mut parser = Parser::default();
2514 let document = parser.parse(input);
2515
2516 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2517 assert_eq!(section.id(), Some("_ben_jerry_ice_cream"));
2518 } else {
2519 panic!("Expected section block");
2520 }
2521 }
2522
2523 #[test]
2524 fn section_id_generation_disabled_when_sectids_unset() {
2525 let input = ":!sectids:\n\n== Section One";
2526 let mut parser = Parser::default();
2527 let document = parser.parse(input);
2528
2529 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2530 assert_eq!(section.id(), None);
2531 } else {
2532 panic!("Expected section block");
2533 }
2534 }
2535
2536 #[test]
2537 fn section_id_generation_with_custom_prefix() {
2538 let input = ":idprefix: id_\n\n== Section One";
2539 let mut parser = Parser::default();
2540 let document = parser.parse(input);
2541
2542 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2543 assert_eq!(section.id(), Some("id_section_one"));
2544 } else {
2545 panic!("Expected section block");
2546 }
2547 }
2548
2549 #[test]
2550 fn section_id_generation_with_custom_separator() {
2551 let input = ":idseparator: -\n\n== Section One";
2552 let mut parser = Parser::default();
2553 let document = parser.parse(input);
2554
2555 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2556 assert_eq!(section.id(), Some("_section-one"));
2557 } else {
2558 panic!("Expected section block");
2559 }
2560 }
2561
2562 #[test]
2563 fn section_id_generation_with_empty_prefix() {
2564 let input = ":idprefix:\n\n== Section One";
2565 let mut parser = Parser::default();
2566 let document = parser.parse(input);
2567
2568 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2569 assert_eq!(section.id(), Some("section_one"));
2570 } else {
2571 panic!("Expected section block");
2572 }
2573 }
2574
2575 #[test]
2576 fn section_id_generation_removes_trailing_separator() {
2577 let input = ":idseparator: -\n\n== Section Title-";
2578 let mut parser = Parser::default();
2579 let document = parser.parse(input);
2580
2581 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2582 assert_eq!(section.id(), Some("_section-title"));
2583 } else {
2584 panic!("Expected section block");
2585 }
2586 }
2587
2588 #[test]
2589 fn section_id_generation_removes_leading_separator_when_prefix_empty() {
2590 let input = ":idprefix:\n:idseparator: -\n\n== -Section Title";
2591 let mut parser = Parser::default();
2592 let document = parser.parse(input);
2593
2594 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2595 assert_eq!(section.id(), Some("section-title"));
2596 } else {
2597 panic!("Expected section block");
2598 }
2599 }
2600
2601 #[test]
2602 fn section_id_generation_handles_multiple_trailing_separators() {
2603 let input = ":idseparator: _\n\n== Title with Multiple Dots...";
2604 let mut parser = Parser::default();
2605 let document = parser.parse(input);
2606
2607 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2608 assert_eq!(section.id(), Some("_title_with_multiple_dots"));
2609 } else {
2610 panic!("Expected section block");
2611 }
2612 }
2613
2614 #[test]
2615 fn warn_duplicate_manual_section_id() {
2616 let input = "[#my_id]\n== First Section\n\n[#my_id]\n== Second Section";
2617 let mut parser = Parser::default();
2618 let document = parser.parse(input);
2619
2620 let mut warnings = document.warnings();
2621
2622 assert_eq!(
2623 warnings.next().unwrap(),
2624 Warning {
2625 source: Span {
2626 data: "[#my_id]\n== Second Section",
2627 line: 4,
2628 col: 1,
2629 offset: 27,
2630 },
2631 warning: WarningType::DuplicateId("my_id".to_owned()),
2632 }
2633 );
2634
2635 assert!(warnings.next().is_none());
2636 }
2637
2638 #[test]
2639 fn section_with_custom_reftext_attribute() {
2640 let input = "[reftext=\"Custom Reference Text\"]\n== Section Title";
2641 let mut parser = Parser::default();
2642 let document = parser.parse(input);
2643
2644 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2645 assert_eq!(section.id(), Some("_section_title"));
2646 } else {
2647 panic!("Expected section block");
2648 }
2649
2650 let catalog = document.catalog();
2651 let entry = catalog.get_ref("_section_title");
2652 assert!(entry.is_some());
2653 assert_eq!(
2654 entry.unwrap().reftext,
2655 Some("Custom Reference Text".to_string())
2656 );
2657 }
2658
2659 #[test]
2660 fn section_without_reftext_uses_title() {
2661 let input = "== Section Title";
2662 let mut parser = Parser::default();
2663 let document = parser.parse(input);
2664
2665 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
2666 assert_eq!(section.id(), Some("_section_title"));
2667 } else {
2668 panic!("Expected section block");
2669 }
2670
2671 let catalog = document.catalog();
2672 let entry = catalog.get_ref("_section_title");
2673 assert!(entry.is_some());
2674 assert_eq!(entry.unwrap().reftext, Some("Section Title".to_string()));
2675 }
2676
2677 mod section_numbering {
2678 use crate::{
2679 Parser,
2680 blocks::{Block, IsBlock},
2681 };
2682
2683 #[test]
2684 fn single_section_with_sectnums() {
2685 let input = ":sectnums:\n\n== First Section";
2686 let mut parser = Parser::default();
2687 let document = parser.parse(input);
2688
2689 if let Some(Block::Section(section)) = document.nested_blocks().next() {
2690 let section_number = section.section_number();
2691 assert!(section_number.is_some());
2692 assert_eq!(section_number.unwrap().to_string(), "1");
2693 assert_eq!(section_number.unwrap().components(), [1]);
2694 } else {
2695 panic!("Expected section block");
2696 }
2697 }
2698
2699 #[test]
2700 fn multiple_level_1_sections() {
2701 let input = ":sectnums:\n\n== First Section\n\n== Second Section\n\n== Third Section";
2702 let mut parser = Parser::default();
2703 let document = parser.parse(input);
2704
2705 let mut sections = document.nested_blocks().filter_map(|block| {
2706 if let Block::Section(section) = block {
2707 Some(section)
2708 } else {
2709 None
2710 }
2711 });
2712
2713 let first = sections.next().unwrap();
2714 assert_eq!(first.section_number().unwrap().to_string(), "1");
2715
2716 let second = sections.next().unwrap();
2717 assert_eq!(second.section_number().unwrap().to_string(), "2");
2718
2719 let third = sections.next().unwrap();
2720 assert_eq!(third.section_number().unwrap().to_string(), "3");
2721 }
2722
2723 #[test]
2724 fn nested_sections() {
2725 let input = ":sectnums:\n\n== Level 1\n\n=== Level 2\n\n==== Level 3";
2726 let document = Parser::default().parse(input);
2727
2728 if let Some(Block::Section(level1)) = document.nested_blocks().next() {
2729 assert_eq!(level1.section_number().unwrap().to_string(), "1");
2730
2731 if let Some(Block::Section(level2)) = level1.nested_blocks().next() {
2732 assert_eq!(level2.section_number().unwrap().to_string(), "1.1");
2733
2734 if let Some(Block::Section(level3)) = level2.nested_blocks().next() {
2735 assert_eq!(level3.section_number().unwrap().to_string(), "1.1.1");
2736 } else {
2737 panic!("Expected level 3 section");
2738 }
2739 } else {
2740 panic!("Expected level 2 section");
2741 }
2742 } else {
2743 panic!("Expected level 1 section");
2744 }
2745 }
2746
2747 #[test]
2748 fn mixed_section_levels() {
2749 let input = ":sectnums:\n\n== First\n\n=== First.One\n\n=== First.Two\n\n== Second\n\n=== Second.One";
2750 let document = Parser::default().parse(input);
2751
2752 let mut sections = document.nested_blocks().filter_map(|block| {
2753 if let Block::Section(section) = block {
2754 Some(section)
2755 } else {
2756 None
2757 }
2758 });
2759
2760 let first = sections.next().unwrap();
2761 assert_eq!(first.section_number().unwrap().to_string(), "1");
2762
2763 let first_one = first
2764 .nested_blocks()
2765 .filter_map(|block| {
2766 if let Block::Section(section) = block {
2767 Some(section)
2768 } else {
2769 None
2770 }
2771 })
2772 .next()
2773 .unwrap();
2774 assert_eq!(first_one.section_number().unwrap().to_string(), "1.1");
2775
2776 let first_two = first
2777 .nested_blocks()
2778 .filter_map(|block| {
2779 if let Block::Section(section) = block {
2780 Some(section)
2781 } else {
2782 None
2783 }
2784 })
2785 .nth(1)
2786 .unwrap();
2787 assert_eq!(first_two.section_number().unwrap().to_string(), "1.2");
2788
2789 let second = sections.next().unwrap();
2790 assert_eq!(second.section_number().unwrap().to_string(), "2");
2791
2792 let second_one = second
2793 .nested_blocks()
2794 .filter_map(|block| {
2795 if let Block::Section(section) = block {
2796 Some(section)
2797 } else {
2798 None
2799 }
2800 })
2801 .next()
2802 .unwrap();
2803 assert_eq!(second_one.section_number().unwrap().to_string(), "2.1");
2804 }
2805
2806 #[test]
2807 fn sectnums_disabled() {
2808 let input = "== First Section\n\n== Second Section";
2809 let mut parser = Parser::default();
2810 let document = parser.parse(input);
2811
2812 for block in document.nested_blocks() {
2813 if let Block::Section(section) = block {
2814 assert!(section.section_number().is_none());
2815 }
2816 }
2817 }
2818
2819 #[test]
2820 fn sectnums_explicitly_unset() {
2821 let input = ":!sectnums:\n\n== First Section\n\n== Second Section";
2822 let mut parser = Parser::default();
2823 let document = parser.parse(input);
2824
2825 for block in document.nested_blocks() {
2826 if let Block::Section(section) = block {
2827 assert!(section.section_number().is_none());
2828 }
2829 }
2830 }
2831
2832 #[test]
2833 fn deep_nesting() {
2834 let input = ":sectnums:\n:sectnumlevels: 5\n\n== Level 1\n\n=== Level 2\n\n==== Level 3\n\n===== Level 4\n\n====== Level 5";
2835 let document = Parser::default().parse(input);
2836
2837 if let Some(Block::Section(l1)) = document.nested_blocks().next() {
2838 assert_eq!(l1.section_number().unwrap().to_string(), "1");
2839
2840 if let Some(Block::Section(l2)) = l1.nested_blocks().next() {
2841 assert_eq!(l2.section_number().unwrap().to_string(), "1.1");
2842
2843 if let Some(Block::Section(l3)) = l2.nested_blocks().next() {
2844 assert_eq!(l3.section_number().unwrap().to_string(), "1.1.1");
2845
2846 if let Some(Block::Section(l4)) = l3.nested_blocks().next() {
2847 assert_eq!(l4.section_number().unwrap().to_string(), "1.1.1.1");
2848
2849 if let Some(Block::Section(l5)) = l4.nested_blocks().next() {
2850 assert_eq!(l5.section_number().unwrap().to_string(), "1.1.1.1.1");
2851 } else {
2852 panic!("Expected level 5 section");
2853 }
2854 } else {
2855 panic!("Expected level 4 section");
2856 }
2857 } else {
2858 panic!("Expected level 3 section");
2859 }
2860 } else {
2861 panic!("Expected level 2 section");
2862 }
2863 } else {
2864 panic!("Expected level 1 section");
2865 }
2866 }
2867 }
2868
2869 #[test]
2870 fn impl_debug() {
2871 let mut parser = Parser::default();
2872 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
2873
2874 let section = crate::blocks::SectionBlock::parse(
2875 &BlockMetadata::new("== Section Title"),
2876 &mut parser,
2877 &mut warnings,
2878 )
2879 .unwrap()
2880 .item;
2881
2882 assert_eq!(
2883 format!("{section:#?}"),
2884 r#"SectionBlock {
2885 level: 1,
2886 section_title: Content {
2887 original: Span {
2888 data: "Section Title",
2889 line: 1,
2890 col: 4,
2891 offset: 3,
2892 },
2893 rendered: "Section Title",
2894 },
2895 blocks: &[],
2896 source: Span {
2897 data: "== Section Title",
2898 line: 1,
2899 col: 1,
2900 offset: 0,
2901 },
2902 title_source: None,
2903 title: None,
2904 anchor: None,
2905 anchor_reftext: None,
2906 attrlist: None,
2907 section_type: SectionType::Normal,
2908 section_id: Some(
2909 "_section_title",
2910 ),
2911 section_number: None,
2912}"#
2913 );
2914 }
2915
2916 mod section_type {
2917 use crate::blocks::section::SectionType;
2918
2919 #[test]
2920 fn impl_debug() {
2921 let st = SectionType::Normal;
2922 assert_eq!(format!("{st:?}"), "SectionType::Normal");
2923
2924 let st = SectionType::Appendix;
2925 assert_eq!(format!("{st:?}"), "SectionType::Appendix");
2926
2927 let st = SectionType::Discrete;
2928 assert_eq!(format!("{st:?}"), "SectionType::Discrete");
2929 }
2930 }
2931
2932 mod section_number {
2933 mod assign_next_number {
2934 use crate::blocks::section::SectionNumber;
2935
2936 #[test]
2937 fn default() {
2938 let sn = SectionNumber::default();
2939 assert_eq!(sn.components(), []);
2940 assert_eq!(sn.to_string(), "");
2941 assert_eq!(
2942 format!("{sn:?}"),
2943 "SectionNumber { section_type: SectionType::Normal, components: &[] }"
2944 );
2945 }
2946
2947 #[test]
2948 fn level_1() {
2949 let mut sn = SectionNumber::default();
2950 sn.assign_next_number(1);
2951 assert_eq!(sn.components(), [1]);
2952 assert_eq!(sn.to_string(), "1");
2953 assert_eq!(
2954 format!("{sn:?}"),
2955 "SectionNumber { section_type: SectionType::Normal, components: &[1] }"
2956 );
2957 }
2958
2959 #[test]
2960 fn level_3() {
2961 let mut sn = SectionNumber::default();
2962 sn.assign_next_number(3);
2963 assert_eq!(sn.components(), [1, 1, 1]);
2964 assert_eq!(sn.to_string(), "1.1.1");
2965 assert_eq!(
2966 format!("{sn:?}"),
2967 "SectionNumber { section_type: SectionType::Normal, components: &[1, 1, 1] }"
2968 );
2969 }
2970
2971 #[test]
2972 fn level_3_then_1() {
2973 let mut sn = SectionNumber::default();
2974 sn.assign_next_number(3);
2975 sn.assign_next_number(1);
2976 assert_eq!(sn.components(), [2]);
2977 assert_eq!(sn.to_string(), "2");
2978 assert_eq!(
2979 format!("{sn:?}"),
2980 "SectionNumber { section_type: SectionType::Normal, components: &[2] }"
2981 );
2982 }
2983
2984 #[test]
2985 fn level_3_then_1_then_2() {
2986 let mut sn = SectionNumber::default();
2987 sn.assign_next_number(3);
2988 sn.assign_next_number(1);
2989 sn.assign_next_number(2);
2990 assert_eq!(sn.components(), [2, 1]);
2991 assert_eq!(sn.to_string(), "2.1");
2992 assert_eq!(
2993 format!("{sn:?}"),
2994 "SectionNumber { section_type: SectionType::Normal, components: &[2, 1] }"
2995 );
2996 }
2997 }
2998
2999 mod assign_next_number_appendix {
3000 use crate::blocks::{SectionType, section::SectionNumber};
3001
3002 #[test]
3003 fn default() {
3004 let sn = SectionNumber {
3005 section_type: SectionType::Appendix,
3006 components: vec![],
3007 };
3008 assert_eq!(sn.components(), []);
3009 assert_eq!(sn.to_string(), "");
3010 assert_eq!(
3011 format!("{sn:?}"),
3012 "SectionNumber { section_type: SectionType::Appendix, components: &[] }"
3013 );
3014 }
3015
3016 #[test]
3017 fn level_1() {
3018 let mut sn = SectionNumber {
3019 section_type: SectionType::Appendix,
3020 components: vec![],
3021 };
3022 sn.assign_next_number(1);
3023 assert_eq!(sn.components(), [1]);
3024 assert_eq!(sn.to_string(), "A");
3025 assert_eq!(
3026 format!("{sn:?}"),
3027 "SectionNumber { section_type: SectionType::Appendix, components: &[1] }"
3028 );
3029 }
3030
3031 #[test]
3032 fn level_3() {
3033 let mut sn = SectionNumber {
3034 section_type: SectionType::Appendix,
3035 components: vec![],
3036 };
3037 sn.assign_next_number(3);
3038 assert_eq!(sn.components(), [1, 1, 1]);
3039 assert_eq!(sn.to_string(), "A.1.1");
3040 assert_eq!(
3041 format!("{sn:?}"),
3042 "SectionNumber { section_type: SectionType::Appendix, components: &[1, 1, 1] }"
3043 );
3044 }
3045
3046 #[test]
3047 fn level_3_then_1() {
3048 let mut sn = SectionNumber {
3049 section_type: SectionType::Appendix,
3050 components: vec![],
3051 };
3052 sn.assign_next_number(3);
3053 sn.assign_next_number(1);
3054 assert_eq!(sn.components(), [2]);
3055 assert_eq!(sn.to_string(), "B");
3056 assert_eq!(
3057 format!("{sn:?}"),
3058 "SectionNumber { section_type: SectionType::Appendix, components: &[2] }"
3059 );
3060 }
3061
3062 #[test]
3063 fn level_3_then_1_then_2() {
3064 let mut sn = SectionNumber {
3065 section_type: SectionType::Appendix,
3066 components: vec![],
3067 };
3068 sn.assign_next_number(3);
3069 sn.assign_next_number(1);
3070 sn.assign_next_number(2);
3071 assert_eq!(sn.components(), [2, 1]);
3072 assert_eq!(sn.to_string(), "B.1");
3073 assert_eq!(
3074 format!("{sn:?}"),
3075 "SectionNumber { section_type: SectionType::Appendix, components: &[2, 1] }"
3076 );
3077 }
3078 }
3079 }
3080
3081 mod discrete_headings {
3082 use std::ops::Deref;
3083
3084 use pretty_assertions_sorted::assert_eq;
3085
3086 use crate::{
3087 HasSpan, Parser,
3088 blocks::{ContentModel, IsBlock, metadata::BlockMetadata, section::SectionType},
3089 content::SubstitutionGroup,
3090 tests::prelude::*,
3091 };
3092
3093 #[test]
3094 fn basic_case() {
3095 let mut parser = Parser::default();
3096 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3097
3098 let mi = crate::blocks::SectionBlock::parse(
3099 &BlockMetadata::new("[discrete]\n== Discrete Heading"),
3100 &mut parser,
3101 &mut warnings,
3102 )
3103 .unwrap();
3104
3105 assert_eq!(mi.item.content_model(), ContentModel::Compound);
3106 assert_eq!(mi.item.raw_context().deref(), "section");
3107 assert_eq!(mi.item.level(), 1);
3108 assert_eq!(mi.item.section_title(), "Discrete Heading");
3109 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3110 assert!(mi.item.nested_blocks().next().is_none());
3111 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
3112 assert!(mi.item.title().is_none());
3113 assert!(mi.item.anchor().is_none());
3114 assert!(mi.item.attrlist().is_some());
3115 assert_eq!(mi.item.section_number(), None);
3116 assert!(warnings.is_empty());
3117
3118 assert_eq!(
3119 mi.item.section_title_source(),
3120 Span {
3121 data: "Discrete Heading",
3122 line: 2,
3123 col: 4,
3124 offset: 14,
3125 }
3126 );
3127
3128 assert_eq!(
3129 mi.item.span(),
3130 Span {
3131 data: "[discrete]\n== Discrete Heading",
3132 line: 1,
3133 col: 1,
3134 offset: 0,
3135 }
3136 );
3137
3138 assert_eq!(
3139 mi.after,
3140 Span {
3141 data: "",
3142 line: 2,
3143 col: 20,
3144 offset: 30,
3145 }
3146 );
3147 }
3148
3149 #[test]
3150 fn float_style() {
3151 let mut parser = Parser::default();
3152 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3153
3154 let mi = crate::blocks::SectionBlock::parse(
3155 &BlockMetadata::new("[float]\n== Floating Heading"),
3156 &mut parser,
3157 &mut warnings,
3158 )
3159 .unwrap();
3160
3161 assert_eq!(mi.item.level(), 1);
3162 assert_eq!(mi.item.section_title(), "Floating Heading");
3163 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3164 assert!(mi.item.nested_blocks().next().is_none());
3165 assert!(warnings.is_empty());
3166 }
3167
3168 #[test]
3169 fn has_no_child_blocks() {
3170 let mut parser = Parser::default();
3171 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3172
3173 let mi = crate::blocks::SectionBlock::parse(
3174 &BlockMetadata::new("[discrete]\n== Discrete Heading\n\nThis is a paragraph."),
3175 &mut parser,
3176 &mut warnings,
3177 )
3178 .unwrap();
3179
3180 assert_eq!(mi.item.level(), 1);
3181 assert_eq!(mi.item.section_title(), "Discrete Heading");
3182 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3183
3184 assert!(mi.item.nested_blocks().next().is_none());
3186
3187 assert_eq!(
3189 mi.after,
3190 Span {
3191 data: "This is a paragraph.",
3192 line: 4,
3193 col: 1,
3194 offset: 32,
3195 }
3196 );
3197
3198 assert!(warnings.is_empty());
3199 }
3200
3201 #[test]
3202 fn not_in_section_hierarchy() {
3203 let input = "== Section 1\n\n[discrete]\n=== Discrete\n\n=== Section 1.1";
3204 let mut parser = Parser::default();
3205 let document = parser.parse(input);
3206
3207 let mut blocks = document.nested_blocks();
3208
3209 if let Some(crate::blocks::Block::Section(section)) = blocks.next() {
3211 assert_eq!(section.section_title(), "Section 1");
3212 assert_eq!(section.level(), 1);
3213 assert_eq!(section.section_type(), SectionType::Normal);
3214
3215 let mut children = section.nested_blocks();
3216
3217 if let Some(crate::blocks::Block::Section(discrete)) = children.next() {
3219 assert_eq!(discrete.section_title(), "Discrete");
3220 assert_eq!(discrete.level(), 2);
3221 assert_eq!(discrete.section_type(), SectionType::Discrete);
3222 assert!(discrete.nested_blocks().next().is_none());
3223 } else {
3224 panic!("Expected discrete heading block");
3225 }
3226
3227 if let Some(crate::blocks::Block::Section(subsection)) = children.next() {
3229 assert_eq!(subsection.section_title(), "Section 1.1");
3230 assert_eq!(subsection.level(), 2);
3231 assert_eq!(subsection.section_type(), SectionType::Normal);
3232 } else {
3233 panic!("Expected subsection block");
3234 }
3235 } else {
3236 panic!("Expected section block");
3237 }
3238 }
3239
3240 #[test]
3241 fn has_auto_id() {
3242 let input = "[discrete]\n== Discrete Heading";
3243 let mut parser = Parser::default();
3244 let document = parser.parse(input);
3245
3246 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
3247 assert_eq!(section.id(), Some("_discrete_heading"));
3249 } else {
3250 panic!("Expected section block");
3251 }
3252 }
3253
3254 #[test]
3255 fn with_manual_id() {
3256 let input = "[discrete#my-id]\n== Discrete Heading";
3257 let mut parser = Parser::default();
3258 let document = parser.parse(input);
3259
3260 if let Some(crate::blocks::Block::Section(section)) = document.nested_blocks().next() {
3261 assert_eq!(section.id(), Some("my-id"));
3263 } else {
3264 panic!("Expected section block");
3265 }
3266 }
3267
3268 #[test]
3269 fn no_section_number() {
3270 let input = ":sectnums:\n\n== Section 1\n\n[discrete]\n=== Discrete\n\n=== Section 1.1";
3271 let mut parser = Parser::default();
3272 let document = parser.parse(input);
3273
3274 let mut blocks = document.nested_blocks();
3275
3276 if let Some(crate::blocks::Block::Section(section)) = blocks.next() {
3277 assert_eq!(section.section_title(), "Section 1");
3278 assert!(section.section_number().is_some());
3279
3280 let mut children = section.nested_blocks();
3281
3282 if let Some(crate::blocks::Block::Section(discrete)) = children.next() {
3284 assert_eq!(discrete.section_title(), "Discrete");
3285 assert_eq!(discrete.section_number(), None);
3286 } else {
3287 panic!("Expected discrete heading block");
3288 }
3289
3290 if let Some(crate::blocks::Block::Section(subsection)) = children.next() {
3292 assert_eq!(subsection.section_title(), "Section 1.1");
3293 assert!(subsection.section_number().is_some());
3294 } else {
3295 panic!("Expected subsection block");
3296 }
3297 } else {
3298 panic!("Expected section block");
3299 }
3300 }
3301
3302 #[test]
3303 fn title_can_have_markup() {
3304 let mut parser = Parser::default();
3305 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3306
3307 let mi = crate::blocks::SectionBlock::parse(
3308 &BlockMetadata::new("[discrete]\n== Discrete with *bold* text"),
3309 &mut parser,
3310 &mut warnings,
3311 )
3312 .unwrap();
3313
3314 assert_eq!(
3315 mi.item.section_title(),
3316 "Discrete with <strong>bold</strong> text"
3317 );
3318 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3319 assert!(warnings.is_empty());
3320 }
3321
3322 #[test]
3323 fn level_2() {
3324 let mut parser = Parser::default();
3325 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3326
3327 let mi = crate::blocks::SectionBlock::parse(
3328 &BlockMetadata::new("[discrete]\n=== Level 2 Discrete"),
3329 &mut parser,
3330 &mut warnings,
3331 )
3332 .unwrap();
3333
3334 assert_eq!(mi.item.level(), 2);
3335 assert_eq!(mi.item.section_title(), "Level 2 Discrete");
3336 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3337 assert!(warnings.is_empty());
3338 }
3339
3340 #[test]
3341 fn level_5() {
3342 let mut parser = Parser::default();
3343 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3344
3345 let mi = crate::blocks::SectionBlock::parse(
3346 &BlockMetadata::new("[discrete]\n====== Level 5 Discrete"),
3347 &mut parser,
3348 &mut warnings,
3349 )
3350 .unwrap();
3351
3352 assert_eq!(mi.item.level(), 5);
3353 assert_eq!(mi.item.section_title(), "Level 5 Discrete");
3354 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3355 assert!(warnings.is_empty());
3356 }
3357
3358 #[test]
3359 fn markdown_style() {
3360 let mut parser = Parser::default();
3361 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3362
3363 let mi = crate::blocks::SectionBlock::parse(
3364 &BlockMetadata::new("[discrete]\n## Discrete Heading"),
3365 &mut parser,
3366 &mut warnings,
3367 )
3368 .unwrap();
3369
3370 assert_eq!(mi.item.level(), 1);
3371 assert_eq!(mi.item.section_title(), "Discrete Heading");
3372 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3373 assert!(warnings.is_empty());
3374 }
3375
3376 #[test]
3377 fn with_block_title() {
3378 let mut parser = Parser::default();
3379 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3380
3381 let mi = crate::blocks::SectionBlock::parse(
3382 &BlockMetadata::new(".Block Title\n[discrete]\n== Discrete Heading"),
3383 &mut parser,
3384 &mut warnings,
3385 )
3386 .unwrap();
3387
3388 assert_eq!(mi.item.level(), 1);
3389 assert_eq!(mi.item.section_title(), "Discrete Heading");
3390 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3391 assert_eq!(mi.item.title(), Some("Block Title"));
3392 assert!(warnings.is_empty());
3393 }
3394
3395 #[test]
3396 fn with_anchor() {
3397 let mut parser = Parser::default();
3398 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3399
3400 let mi = crate::blocks::SectionBlock::parse(
3401 &BlockMetadata::new("[[my_anchor]]\n[discrete]\n== Discrete Heading"),
3402 &mut parser,
3403 &mut warnings,
3404 )
3405 .unwrap();
3406
3407 assert_eq!(mi.item.level(), 1);
3408 assert_eq!(mi.item.section_title(), "Discrete Heading");
3409 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3410 assert_eq!(mi.item.id(), Some("my_anchor"));
3411 assert!(warnings.is_empty());
3412 }
3413
3414 #[test]
3415 fn doesnt_include_subsequent_blocks() {
3416 let mut parser = Parser::default();
3417 let mut warnings: Vec<crate::warnings::Warning<'_>> = vec![];
3418
3419 let mi = crate::blocks::SectionBlock::parse(
3420 &BlockMetadata::new(
3421 "[discrete]\n== Discrete Heading\n\nparagraph\n\n== Next Section",
3422 ),
3423 &mut parser,
3424 &mut warnings,
3425 )
3426 .unwrap();
3427
3428 assert_eq!(mi.item.level(), 1);
3429 assert_eq!(mi.item.section_title(), "Discrete Heading");
3430 assert_eq!(mi.item.section_type(), SectionType::Discrete);
3431
3432 assert!(mi.item.nested_blocks().next().is_none());
3434
3435 assert!(mi.after.data().contains("paragraph"));
3437 assert!(mi.after.data().contains("== Next Section"));
3438
3439 assert!(warnings.is_empty());
3440 }
3441 }
3442}