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