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