1use crate::{
2 HasSpan, Parser, Span,
3 attributes::{Attrlist, AttrlistContext},
4 blocks::{ChildBlocks, ContentModel, IsBlock, caption, metadata::BlockMetadata},
5 content::{Content, substitute_attributes_in_macro_target},
6 span::MatchedItem,
7 strings::CowStr,
8 warnings::{MatchAndWarnings, Warning, WarningType},
9};
10
11#[derive(Clone, Debug, Eq, Hash, PartialEq)]
13pub struct MediaBlock<'src> {
14 type_: MediaType,
15 target: Span<'src>,
16 resolved_target: CowStr<'src>,
17 macro_attrlist: Attrlist<'src>,
18 source: Span<'src>,
19 title_source: Option<Span<'src>>,
20 title: Option<Content<'src>>,
21 caption: Option<String>,
22 number: Option<usize>,
23 anchor: Option<Span<'src>>,
24 anchor_reftext: Option<Span<'src>>,
25 attrlist: Option<Attrlist<'src>>,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub(crate) enum TargetResolution {
32 Keep,
34
35 Drop,
38}
39
40#[derive(Clone, Copy, Eq, Hash, PartialEq)]
42pub enum MediaType {
43 Image,
45
46 Video,
48
49 Audio,
51}
52
53impl std::fmt::Debug for MediaType {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 MediaType::Image => write!(f, "MediaType::Image"),
57 MediaType::Video => write!(f, "MediaType::Video"),
58 MediaType::Audio => write!(f, "MediaType::Audio"),
59 }
60 }
61}
62
63impl<'src> MediaBlock<'src> {
64 pub fn child_blocks(&'src self) -> ChildBlocks<'src> {
71 ChildBlocks::empty()
72 }
73
74 pub(crate) fn title_content_mut(&mut self) -> Option<&mut Content<'src>> {
82 self.title.as_mut()
83 }
84
85 pub(crate) fn parse(
86 metadata: &BlockMetadata<'src>,
87 parser: &mut Parser,
88 ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
89 let line = metadata.block_start.take_normalized_line();
90
91 if !line.item.ends_with(']') {
93 return MatchAndWarnings {
94 item: None,
95 warnings: vec![],
96 };
97 }
98
99 let Some(name) = line.item.take_block_macro_name() else {
100 return MatchAndWarnings {
101 item: None,
102 warnings: vec![],
103 };
104 };
105
106 let type_ = match name.item.data() {
107 "image" => MediaType::Image,
108 "video" => MediaType::Video,
109 "audio" => MediaType::Audio,
110 _ => {
111 return MatchAndWarnings {
112 item: None,
113 warnings: vec![],
114 };
115 }
116 };
117
118 let Some(colons) = name.after.take_prefix("::") else {
119 return MatchAndWarnings {
120 item: None,
121 warnings: vec![Warning::new(name.after, WarningType::MacroMissingSeparator)],
122 };
123 };
124
125 let target = colons.after.take_while(|c| c != '[');
127
128 if target.item.is_empty() {
129 return MatchAndWarnings {
130 item: None,
131 warnings: vec![Warning::new(
132 target.after,
133 WarningType::MediaMacroMissingTarget,
134 )],
135 };
136 }
137
138 let Some(open_brace) = target.after.take_prefix("[") else {
139 return MatchAndWarnings {
140 item: None,
141 warnings: vec![Warning::new(
142 target.after,
143 WarningType::MacroMissingAttributeList,
144 )],
145 };
146 };
147
148 let attrlist = open_brace.after.slice(0..open_brace.after.len() - 1);
149
150 let macro_attrlist = Attrlist::parse(attrlist, parser, AttrlistContext::Inline);
153
154 let source: Span = metadata.source.trim_remainder(line.after);
155 let source = source.slice(0..source.trim().len());
156
157 MatchAndWarnings {
158 item: Some(MatchedItem {
159 item: Self {
160 type_,
161 target: target.item,
162
163 resolved_target: target.item.data().into(),
168 macro_attrlist: macro_attrlist.item.item,
169 source,
170 title_source: metadata.title_source,
171 title: metadata.title.clone(),
172
173 caption: None,
180 number: None,
181 anchor: metadata.anchor,
182 anchor_reftext: metadata.anchor_reftext,
183 attrlist: metadata.attrlist.clone(),
184 },
185
186 after: line.after.discard_empty_lines(),
187 }),
188 warnings: macro_attrlist.warnings,
189 }
190 }
191
192 pub fn type_(&self) -> MediaType {
194 self.type_
195 }
196
197 pub fn target(&'src self) -> Option<&'src Span<'src>> {
203 Some(&self.target)
204 }
205
206 pub fn resolved_target(&self) -> &str {
214 self.resolved_target.as_ref()
215 }
216
217 pub(crate) fn resolve_target(&mut self, parser: &Parser) -> TargetResolution {
229 match substitute_attributes_in_macro_target(self.target, parser) {
230 Some(resolved) => {
231 self.resolved_target = resolved;
232 TargetResolution::Keep
233 }
234 None => TargetResolution::Drop,
235 }
236 }
237
238 pub(crate) fn assign_caption(&mut self, parser: &mut Parser) {
254 if self.type_ != MediaType::Image {
255 return;
256 }
257
258 let explicit_caption = self
259 .macro_attrlist
260 .named_attribute("caption")
261 .or_else(|| {
262 self.attrlist
263 .as_ref()
264 .and_then(|attrlist| attrlist.named_attribute("caption"))
265 })
266 .map(|attr| attr.value().to_string());
267
268 let caption = caption::assign_caption(
269 parser,
270 "figure",
271 self.title.is_some(),
272 explicit_caption.as_deref(),
273 );
274 self.number = caption.as_ref().and_then(|c| c.number);
275 self.caption = caption.map(|c| c.prefix);
276 }
277
278 pub fn macro_attrlist(&'src self) -> &'src Attrlist<'src> {
288 &self.macro_attrlist
289 }
290}
291
292impl<'src> IsBlock<'src> for MediaBlock<'src> {
293 fn content_model(&self) -> ContentModel {
294 ContentModel::Empty
295 }
296
297 fn raw_context(&self) -> CowStr<'src> {
298 match self.type_ {
299 MediaType::Audio => "audio",
300 MediaType::Image => "image",
301 MediaType::Video => "video",
302 }
303 .into()
304 }
305
306 fn title_source(&'src self) -> Option<Span<'src>> {
307 self.title_source
308 }
309
310 fn title(&self) -> Option<&str> {
311 self.title.as_ref().map(Content::rendered_str)
312 }
313
314 fn caption(&self) -> Option<&str> {
315 self.caption.as_deref()
316 }
317
318 fn number(&self) -> Option<usize> {
319 self.number
320 }
321
322 fn id(&'src self) -> Option<&'src str> {
323 self.anchor()
329 .map(|a| a.data())
330 .or_else(|| self.attrlist().and_then(|attrlist| attrlist.id()))
331 .or_else(|| self.macro_attrlist.id())
332 }
333
334 fn anchor(&'src self) -> Option<Span<'src>> {
335 self.anchor
336 }
337
338 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
339 self.anchor_reftext
340 }
341
342 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
343 self.attrlist.as_ref()
344 }
345}
346
347impl<'src> HasSpan<'src> for MediaBlock<'src> {
348 fn span(&self) -> Span<'src> {
349 self.source
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 #![allow(clippy::unwrap_used)]
356
357 use std::ops::Deref;
358
359 use crate::{
360 blocks::{ContentModel, MediaType, metadata::BlockMetadata},
361 tests::prelude::*,
362 };
363
364 #[test]
365 fn impl_clone() {
366 let mut parser = Parser::default();
368
369 let b1 =
370 crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::foo.jpg[]"), &mut parser)
371 .unwrap_if_no_warnings()
372 .unwrap()
373 .item;
374
375 let b2 = b1.clone();
376 assert_eq!(b1, b2);
377 }
378
379 #[test]
380 fn err_empty_source() {
381 let mut parser = Parser::default();
382 assert!(
383 crate::blocks::MediaBlock::parse(&BlockMetadata::new(""), &mut parser)
384 .unwrap_if_no_warnings()
385 .is_none()
386 );
387 }
388
389 #[test]
390 fn err_only_spaces() {
391 let mut parser = Parser::default();
392 assert!(
393 crate::blocks::MediaBlock::parse(&BlockMetadata::new(" "), &mut parser)
394 .unwrap_if_no_warnings()
395 .is_none()
396 );
397 }
398
399 #[test]
400 fn err_macro_name_not_word_char() {
401 let mut parser = Parser::default();
404 let maw = crate::blocks::MediaBlock::parse(
405 &BlockMetadata::new("#xyz::bar[blah,blap]"),
406 &mut parser,
407 );
408
409 assert!(maw.item.is_none());
410 assert!(maw.warnings.is_empty());
411 }
412
413 #[test]
414 fn err_missing_double_colon() {
415 let mut parser = Parser::default();
416 let maw = crate::blocks::MediaBlock::parse(
417 &BlockMetadata::new("image:bar[blah,blap]"),
418 &mut parser,
419 );
420
421 assert!(maw.item.is_none());
422
423 assert_eq!(
424 maw.warnings,
425 vec![Warning {
426 source: Span {
427 data: ":bar[blah,blap]",
428 line: 1,
429 col: 6,
430 offset: 5,
431 },
432 warning: WarningType::MacroMissingSeparator,
433 }]
434 );
435 }
436
437 #[test]
438 fn err_missing_macro_attrlist() {
439 let mut parser = Parser::default();
440 let maw = crate::blocks::MediaBlock::parse(
441 &BlockMetadata::new("image::barblah,blap]"),
442 &mut parser,
443 );
444
445 assert!(maw.item.is_none());
446
447 assert_eq!(
448 maw.warnings,
449 vec![Warning {
450 source: Span {
451 data: "",
452 line: 1,
453 col: 21,
454 offset: 20,
455 },
456 warning: WarningType::MacroMissingAttributeList,
457 }]
458 );
459 }
460
461 #[test]
462 fn err_unknown_type() {
463 let mut parser = Parser::default();
464 assert!(
465 crate::blocks::MediaBlock::parse(&BlockMetadata::new("imagex::bar[]"), &mut parser)
466 .unwrap_if_no_warnings()
467 .is_none()
468 );
469 }
470
471 #[test]
472 fn err_no_attr_list() {
473 let mut parser = Parser::default();
474 assert!(
475 crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::bar"), &mut parser)
476 .unwrap_if_no_warnings()
477 .is_none()
478 );
479 }
480
481 #[test]
482 fn err_attr_list_not_closed() {
483 let mut parser = Parser::default();
484 assert!(
485 crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::bar[blah"), &mut parser)
486 .unwrap_if_no_warnings()
487 .is_none()
488 );
489 }
490
491 #[test]
492 fn err_unexpected_after_attr_list() {
493 let mut parser = Parser::default();
494 assert!(
495 crate::blocks::MediaBlock::parse(
496 &BlockMetadata::new("image::bar[blah]bonus"),
497 &mut parser
498 )
499 .unwrap_if_no_warnings()
500 .is_none()
501 );
502 }
503
504 #[test]
505 fn simplest_block_macro() {
506 let mut parser = Parser::default();
507
508 let mi = crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::[]"), &mut parser);
509 assert!(mi.item.is_none());
510
511 assert_eq!(
512 mi.warnings,
513 vec![Warning {
514 source: Span {
515 data: "[]",
516 line: 1,
517 col: 8,
518 offset: 7,
519 },
520 warning: WarningType::MediaMacroMissingTarget,
521 }]
522 );
523 }
524
525 #[test]
526 fn has_target() {
527 let mut parser = Parser::default();
528
529 let mi = crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::bar[]"), &mut parser)
530 .unwrap_if_no_warnings()
531 .unwrap();
532
533 assert_eq!(
534 mi.item,
535 MediaBlock {
536 type_: MediaType::Image,
537 target: Span {
538 data: "bar",
539 line: 1,
540 col: 8,
541 offset: 7,
542 },
543 macro_attrlist: Attrlist {
544 attributes: &[],
545 anchor: None,
546 source: Span {
547 data: "",
548 line: 1,
549 col: 12,
550 offset: 11,
551 }
552 },
553 source: Span {
554 data: "image::bar[]",
555 line: 1,
556 col: 1,
557 offset: 0,
558 },
559 title_source: None,
560 title: None,
561 caption: None,
562 number: None,
563 anchor: None,
564 anchor_reftext: None,
565 attrlist: None,
566 }
567 );
568
569 assert_eq!(
570 mi.after,
571 Span {
572 data: "",
573 line: 1,
574 col: 13,
575 offset: 12
576 }
577 );
578
579 assert_eq!(mi.item.content_model(), ContentModel::Empty);
580 assert_eq!(mi.item.raw_context().deref(), "image");
581 assert!(mi.item.child_blocks().next().is_none());
582 assert!(mi.item.title_source().is_none());
583 assert!(mi.item.title().is_none());
584 assert!(mi.item.anchor().is_none());
585 assert!(mi.item.anchor_reftext().is_none());
586 assert!(mi.item.attrlist().is_none());
587 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
588 }
589
590 #[test]
591 fn has_target_and_attrlist() {
592 let mut parser = Parser::default();
593
594 let mi =
595 crate::blocks::MediaBlock::parse(&BlockMetadata::new("image::bar[blah]"), &mut parser)
596 .unwrap_if_no_warnings()
597 .unwrap();
598
599 assert_eq!(
600 mi.item,
601 MediaBlock {
602 type_: MediaType::Image,
603 target: Span {
604 data: "bar",
605 line: 1,
606 col: 8,
607 offset: 7,
608 },
609 macro_attrlist: Attrlist {
610 attributes: &[ElementAttribute {
611 name: None,
612 shorthand_items: &["blah"],
613 value: "blah"
614 }],
615 anchor: None,
616 source: Span {
617 data: "blah",
618 line: 1,
619 col: 12,
620 offset: 11,
621 }
622 },
623 source: Span {
624 data: "image::bar[blah]",
625 line: 1,
626 col: 1,
627 offset: 0,
628 },
629 title_source: None,
630 title: None,
631 caption: None,
632 number: None,
633 anchor: None,
634 anchor_reftext: None,
635 attrlist: None,
636 }
637 );
638
639 assert_eq!(
640 mi.after,
641 Span {
642 data: "",
643 line: 1,
644 col: 17,
645 offset: 16
646 }
647 );
648 }
649
650 #[test]
651 fn audio() {
652 let mut parser = Parser::default();
653
654 let mi = crate::blocks::MediaBlock::parse(&BlockMetadata::new("audio::bar[]"), &mut parser)
655 .unwrap_if_no_warnings()
656 .unwrap();
657
658 assert_eq!(
659 mi.item,
660 MediaBlock {
661 type_: MediaType::Audio,
662 target: Span {
663 data: "bar",
664 line: 1,
665 col: 8,
666 offset: 7,
667 },
668 macro_attrlist: Attrlist {
669 attributes: &[],
670 anchor: None,
671 source: Span {
672 data: "",
673 line: 1,
674 col: 12,
675 offset: 11,
676 }
677 },
678 source: Span {
679 data: "audio::bar[]",
680 line: 1,
681 col: 1,
682 offset: 0,
683 },
684 title_source: None,
685 title: None,
686 caption: None,
687 number: None,
688 anchor: None,
689 anchor_reftext: None,
690 attrlist: None,
691 }
692 );
693
694 assert_eq!(
695 mi.after,
696 Span {
697 data: "",
698 line: 1,
699 col: 13,
700 offset: 12
701 }
702 );
703
704 assert_eq!(mi.item.content_model(), ContentModel::Empty);
705 assert_eq!(mi.item.raw_context().deref(), "audio");
706 assert!(mi.item.child_blocks().next().is_none());
707 assert!(mi.item.title_source().is_none());
708 assert!(mi.item.title().is_none());
709 assert!(mi.item.anchor().is_none());
710 assert!(mi.item.anchor_reftext().is_none());
711 assert!(mi.item.attrlist().is_none());
712 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
713 }
714
715 #[test]
716 fn video() {
717 let mut parser = Parser::default();
718
719 let mi = crate::blocks::MediaBlock::parse(&BlockMetadata::new("video::bar[]"), &mut parser)
720 .unwrap_if_no_warnings()
721 .unwrap();
722
723 assert_eq!(
724 mi.item,
725 MediaBlock {
726 type_: MediaType::Video,
727 target: Span {
728 data: "bar",
729 line: 1,
730 col: 8,
731 offset: 7,
732 },
733 macro_attrlist: Attrlist {
734 attributes: &[],
735 anchor: None,
736 source: Span {
737 data: "",
738 line: 1,
739 col: 12,
740 offset: 11,
741 }
742 },
743 source: Span {
744 data: "video::bar[]",
745 line: 1,
746 col: 1,
747 offset: 0,
748 },
749 title_source: None,
750 title: None,
751 caption: None,
752 number: None,
753 anchor: None,
754 anchor_reftext: None,
755 attrlist: None,
756 }
757 );
758
759 assert_eq!(
760 mi.after,
761 Span {
762 data: "",
763 line: 1,
764 col: 13,
765 offset: 12
766 }
767 );
768
769 assert_eq!(mi.item.content_model(), ContentModel::Empty);
770 assert_eq!(mi.item.raw_context().deref(), "video");
771 assert!(mi.item.child_blocks().next().is_none());
772 assert!(mi.item.title_source().is_none());
773 assert!(mi.item.title().is_none());
774 assert!(mi.item.anchor().is_none());
775 assert!(mi.item.anchor_reftext().is_none());
776 assert!(mi.item.attrlist().is_none());
777 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
778 }
779
780 #[test]
781 fn err_duplicate_comma() {
782 let mut parser = Parser::default();
783 let maw = crate::blocks::MediaBlock::parse(
784 &BlockMetadata::new("image::bar[blah,,blap]"),
785 &mut parser,
786 );
787
788 let mi = maw.item.unwrap().clone();
789
790 assert_eq!(
791 mi.item,
792 MediaBlock {
793 type_: MediaType::Image,
794 target: Span {
795 data: "bar",
796 line: 1,
797 col: 8,
798 offset: 7,
799 },
800 macro_attrlist: Attrlist {
801 attributes: &[
802 ElementAttribute {
803 name: None,
804 shorthand_items: &["blah"],
805 value: "blah"
806 },
807 ElementAttribute {
808 name: None,
809 shorthand_items: &[],
810 value: "blap"
811 }
812 ],
813 anchor: None,
814 source: Span {
815 data: "blah,,blap",
816 line: 1,
817 col: 12,
818 offset: 11,
819 }
820 },
821 source: Span {
822 data: "image::bar[blah,,blap]",
823 line: 1,
824 col: 1,
825 offset: 0,
826 },
827 title_source: None,
828 title: None,
829 caption: None,
830 number: None,
831 anchor: None,
832 anchor_reftext: None,
833 attrlist: None,
834 }
835 );
836
837 assert_eq!(
838 mi.after,
839 Span {
840 data: "",
841 line: 1,
842 col: 23,
843 offset: 22
844 }
845 );
846
847 assert_eq!(
848 maw.warnings,
849 vec![Warning {
850 source: Span {
851 data: "blah,,blap",
852 line: 1,
853 col: 12,
854 offset: 11,
855 },
856 warning: WarningType::EmptyAttributeValue,
857 }]
858 );
859 }
860
861 mod target_resolution {
862 #![allow(clippy::indexing_slicing)]
863
864 use crate::{
865 blocks::{MediaBlock, media::TargetResolution, metadata::BlockMetadata},
866 parser::ModificationContext,
867 tests::prelude::*,
868 warnings::WarningType,
869 };
870
871 fn resolve<'i>(input: &'i str, parser: &mut Parser) -> Option<MediaBlock<'i>> {
875 let mut block = MediaBlock::parse(&BlockMetadata::new(input), parser)
876 .unwrap_if_no_warnings()
877 .unwrap()
878 .item;
879
880 match block.resolve_target(parser) {
881 TargetResolution::Keep => Some(block),
882 TargetResolution::Drop => None,
883 }
884 }
885
886 fn parser_with_mode(mode: &str) -> Parser {
887 Parser::default().with_intrinsic_attribute(
888 "attribute-missing",
889 mode,
890 ModificationContext::Anywhere,
891 )
892 }
893
894 #[test]
895 fn target_without_reference_is_unchanged() {
896 let mut p = Parser::default();
898 let block = resolve("image::foo.png[]", &mut p).unwrap();
899 assert_eq!(block.resolved_target(), "foo.png");
900 }
901
902 #[test]
903 fn resolves_a_defined_reference() {
904 let mut p = Parser::default().with_intrinsic_attribute(
905 "name",
906 "bar",
907 ModificationContext::Anywhere,
908 );
909 let block = resolve("image::pre-{name}-post.png[]", &mut p).unwrap();
910 assert_eq!(block.resolved_target(), "pre-bar-post.png");
911 }
912
913 #[test]
914 fn skip_leaves_a_missing_reference_in_place() {
915 let mut p = Parser::default();
917 let block = resolve("image::a{missing}b.png[]", &mut p).unwrap();
918 assert_eq!(block.resolved_target(), "a{missing}b.png");
919 assert!(p.take_substitution_warnings().is_empty());
920 }
921
922 #[test]
923 fn drop_removes_only_the_missing_reference() {
924 let mut p = parser_with_mode("drop");
925 let block = resolve("image::a{missing}b.png[]", &mut p).unwrap();
926 assert_eq!(block.resolved_target(), "ab.png");
927 }
928
929 #[test]
930 fn warn_leaves_the_reference_and_records_a_warning() {
931 let mut p = parser_with_mode("warn");
932 let block = resolve("image::a{missing}b.png[]", &mut p).unwrap();
933 assert_eq!(block.resolved_target(), "a{missing}b.png");
934
935 let warnings = p.take_substitution_warnings();
936 assert_eq!(warnings.len(), 1);
937 assert_eq!(
938 warnings[0].warning,
939 WarningType::SkippingReferenceToMissingAttribute("missing".to_string())
940 );
941 }
942
943 #[test]
944 fn drop_line_drops_the_whole_block() {
945 let mut p = parser_with_mode("drop-line");
946 assert!(resolve("image::a{missing}b.png[]", &mut p).is_none());
947
948 let warnings = p.take_substitution_warnings();
951 assert_eq!(warnings.len(), 1);
952 assert_eq!(
953 warnings[0].warning,
954 WarningType::SkippingReferenceToMissingAttribute("missing".to_string())
955 );
956 }
957
958 #[test]
959 fn drop_line_keeps_a_block_whose_reference_resolves() {
960 let mut p = parser_with_mode("drop-line").with_intrinsic_attribute(
961 "name",
962 "bar",
963 ModificationContext::Anywhere,
964 );
965 let block = resolve("image::{name}.png[]", &mut p).unwrap();
966 assert_eq!(block.resolved_target(), "bar.png");
967 }
968
969 #[test]
970 fn drop_line_drops_a_top_level_block_but_keeps_following_blocks() {
971 let doc = Parser::default().parse(
974 ":attribute-missing: drop-line\n\nimage::{unresolved}[]\n\nparagraph after\n",
975 );
976
977 assert_css(&doc, ".imageblock", 0);
978 assert_css(&doc, ".paragraph", 1);
979 }
980
981 #[test]
982 fn drop_line_drops_a_top_level_audio_block() {
983 let doc = Parser::default().parse(
986 ":attribute-missing: drop-line\n\naudio::{unresolved}[]\n\nparagraph after\n",
987 );
988
989 assert_css(&doc, ".audioblock", 0);
990 assert_css(&doc, ".paragraph", 1);
991 }
992
993 #[test]
994 fn escaped_missing_reference_never_drops_the_block() {
995 let mut p = parser_with_mode("drop-line");
1000 let block = resolve("image::a\\{missing}b.png[]", &mut p).unwrap();
1001 assert_eq!(block.resolved_target(), "a{missing}b.png");
1002 assert!(p.take_substitution_warnings().is_empty());
1003 }
1004 }
1005
1006 mod media_type {
1007 mod impl_debug {
1008 use crate::blocks::MediaType;
1009
1010 #[test]
1011 fn image() {
1012 let media_type = MediaType::Image;
1013 let debug_output = format!("{:?}", media_type);
1014 assert_eq!(debug_output, "MediaType::Image");
1015 }
1016
1017 #[test]
1018 fn video() {
1019 let media_type = MediaType::Video;
1020 let debug_output = format!("{:?}", media_type);
1021 assert_eq!(debug_output, "MediaType::Video");
1022 }
1023
1024 #[test]
1025 fn audio() {
1026 let media_type = MediaType::Audio;
1027 let debug_output = format!("{:?}", media_type);
1028 assert_eq!(debug_output, "MediaType::Audio");
1029 }
1030 }
1031 }
1032}