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