pub mod av;
pub mod channel;
pub mod common;
pub mod credits;
pub mod length;
pub mod programme;
pub mod ratings;
pub mod tv;
pub mod utils;
pub mod writer;
pub use av::{Audio, PreviouslyShown, SubtitleType, Subtitles, Video};
pub use channel::{Channel, Url};
pub use common::{EmptyTag, Icon, NameAndLang, TagWithOnlyText, ValueAndLang};
pub use credits::{Actor, Credits, EpisodeNum};
pub use length::{Length, Units};
pub use programme::Programme;
pub use ratings::{Rating, Review, StarRating};
pub use tv::Tv;
pub use writer::{TvWriter, WriteError};
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Timelike, Utc};
#[test]
fn test_programme_date_parsing() {
let prog = Programme {
start: "20231027205000 +0200".to_string(),
stop: Some("20231027223000 +0200".to_string()),
..Default::default()
};
let start = prog.start_datetime().expect("Should parse start date");
let stop = prog.stop_datetime().expect("Should parse stop date");
assert_eq!(start.hour(), 18);
assert_eq!(start.minute(), 50);
assert_eq!(stop.hour(), 20);
assert_eq!(stop.minute(), 30);
}
#[test]
fn test_programme_is_live() {
let now = Utc::now();
let start = now - chrono::Duration::minutes(30);
let stop = now + chrono::Duration::minutes(30);
let prog = Programme {
start: start.format("%Y%m%d%H%M%S +0000").to_string(),
stop: Some(stop.format("%Y%m%d%H%M%S +0000").to_string()),
..Default::default()
};
assert!(prog.is_live(), "Programme should be live");
}
#[test]
fn test_generate_hash_stability() {
let prog1 = Programme {
channel: "TF1.fr".to_string(),
start: "20231027205000".to_string(),
titles: vec![ValueAndLang {
value: "Journal".to_string(),
lang: None,
}],
..Default::default()
};
let prog2 = Programme {
channel: "TF1.fr".to_string(),
start: "20231027205000".to_string(),
titles: vec![ValueAndLang {
value: "Journal".to_string(),
lang: None,
}],
..Default::default()
};
assert_eq!(
prog1.generate_hash(),
prog2.generate_hash(),
"Hash should be the same with same metadata."
);
}
#[test]
fn test_get_title_with_lang() {
let prog = Programme {
titles: vec![
ValueAndLang {
value: "News".to_string(),
lang: Some("en".to_string()),
},
ValueAndLang {
value: "Journal".to_string(),
lang: Some("fr".to_string()),
},
],
..Default::default()
};
assert_eq!(prog.get_title(Some("fr")), Some("Journal"));
assert_eq!(prog.get_title(Some("en")), Some("News"));
assert_eq!(prog.get_title(None), Some("News")); }
#[test]
fn test_tv_find_methods() {
let mut tv = Tv::default();
tv.channels.push(Channel {
id: "arte.tv".to_string(),
display_names: vec![NameAndLang {
name: "Arte".to_string(),
lang: None,
}],
..Default::default()
});
tv.programmes.push(Programme {
channel: "arte.tv".to_string(),
start: "20231027205000".to_string(),
..Default::default()
});
let chan = tv.get_channel("arte.tv");
assert!(chan.is_some());
assert_eq!(chan.unwrap().id, "arte.tv");
let progs = tv.find_by_channel("arte.tv");
assert_eq!(progs.len(), 1);
assert_eq!(progs[0].channel, "arte.tv");
}
#[test]
fn test_tv_cleanse() {
let mut prog = Programme {
channel: "TF1".to_string(),
categories: vec![NameAndLang {
name: "Sport".to_string(),
lang: None,
}],
credits: Some(Credits::default()),
..Default::default()
};
prog.cleanse(&["categories".to_string(), "credits".to_string()]);
assert!(prog.categories.is_empty());
assert!(prog.credits.is_none());
}
#[test]
fn test_length_to_seconds_from_seconds() {
let l = Length {
length: 3661,
units: Units::Seconds,
};
assert_eq!(l.to_seconds(), 3661);
}
#[test]
fn test_length_to_seconds_from_minutes() {
let l = Length {
length: 90,
units: Units::Minutes,
};
assert_eq!(l.to_seconds(), 5400);
}
#[test]
fn test_length_to_seconds_from_hours() {
let l = Length {
length: 2,
units: Units::Hours,
};
assert_eq!(l.to_seconds(), 7200);
}
#[test]
fn test_length_to_minutes_from_seconds() {
let l = Length {
length: 90,
units: Units::Seconds,
};
assert_eq!(l.to_minutes(), 1);
}
#[test]
fn test_length_to_minutes_from_minutes() {
let l = Length {
length: 120,
units: Units::Minutes,
};
assert_eq!(l.to_minutes(), 120);
}
#[test]
fn test_length_to_minutes_from_hours() {
let l = Length {
length: 2,
units: Units::Hours,
};
assert_eq!(l.to_minutes(), 120);
}
#[test]
fn test_subtitle_type_deserialize_teletext() {
let xml = r#"<subtitles type="teletext"/>"#;
let s: Subtitles = quick_xml::de::from_str(xml).unwrap();
assert_eq!(s.r#type, Some(SubtitleType::Teletext));
}
#[test]
fn test_subtitle_type_deserialize_onscreen() {
let xml = r#"<subtitles type="onscreen"/>"#;
let s: Subtitles = quick_xml::de::from_str(xml).unwrap();
assert_eq!(s.r#type, Some(SubtitleType::Onscreen));
}
#[test]
fn test_subtitle_type_deserialize_deaf_signed() {
let xml = r#"<subtitles type="deaf-signed"/>"#;
let s: Subtitles = quick_xml::de::from_str(xml).unwrap();
assert_eq!(s.r#type, Some(SubtitleType::DeafSigned));
}
#[test]
fn test_subtitle_type_serialize_roundtrip() {
let original = Subtitles {
r#type: Some(SubtitleType::DeafSigned),
language: None,
};
let xml = quick_xml::se::to_string_with_root("subtitles", &original).unwrap();
assert!(
xml.contains("deaf-signed"),
"expected 'deaf-signed' in: {xml}"
);
let roundtripped: Subtitles = quick_xml::de::from_str(&xml).unwrap();
assert_eq!(roundtripped.r#type, original.r#type);
}
#[test]
fn test_subtitle_type_absent_is_none() {
let xml = r#"<subtitles/>"#;
let s: Subtitles = quick_xml::de::from_str(xml).unwrap();
assert_eq!(s.r#type, None);
}
#[test]
fn test_video_present_true() {
let xml = r#"<video><present>yes</present></video>"#;
let v: Video = quick_xml::de::from_str(xml).unwrap();
assert_eq!(v.present, Some(true));
}
#[test]
fn test_video_colour_false() {
let xml = r#"<video><colour>no</colour></video>"#;
let v: Video = quick_xml::de::from_str(xml).unwrap();
assert_eq!(v.colour, Some(false));
}
#[test]
fn test_video_present_absent_is_none() {
let xml = r#"<video/>"#;
let v: Video = quick_xml::de::from_str(xml).unwrap();
assert_eq!(v.present, None);
assert_eq!(v.colour, None);
}
#[test]
fn test_video_bool_serialize_roundtrip() {
let original = Video {
present: Some(true),
colour: Some(false),
..Default::default()
};
let xml = quick_xml::se::to_string_with_root("video", &original).unwrap();
assert!(xml.contains("<present>yes</present>"), "xml: {xml}");
assert!(xml.contains("<colour>no</colour>"), "xml: {xml}");
let rt: Video = quick_xml::de::from_str(&xml).unwrap();
assert_eq!(rt.present, Some(true));
assert_eq!(rt.colour, Some(false));
}
#[test]
fn test_audio_present_true() {
let xml = r#"<audio><present>yes</present></audio>"#;
let a: Audio = quick_xml::de::from_str(xml).unwrap();
assert_eq!(a.present, Some(true));
}
#[test]
fn test_audio_present_false() {
let xml = r#"<audio><present>no</present></audio>"#;
let a: Audio = quick_xml::de::from_str(xml).unwrap();
assert_eq!(a.present, Some(false));
}
#[test]
fn test_audio_present_absent_is_none() {
let xml = r#"<audio/>"#;
let a: Audio = quick_xml::de::from_str(xml).unwrap();
assert_eq!(a.present, None);
}
#[test]
fn test_audio_present_serialize_roundtrip() {
let original = Audio {
present: Some(true),
stereo: None,
};
let xml = quick_xml::se::to_string_with_root("audio", &original).unwrap();
assert!(xml.contains("<present>yes</present>"), "xml: {xml}");
let rt: Audio = quick_xml::de::from_str(&xml).unwrap();
assert_eq!(rt.present, Some(true));
}
}