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