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