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