Files
Marcos Paulo e8c8c06d1e
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: smart folder model, media time fragments, link card URL guard (MiniMax); specs for 10 more gaps (Codex) and the registry with bloat measurements
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 14:41:26 -03:00

107 lines
3.2 KiB
Rust

//! Integration tests for the media timestamp parser (`[[note.mp3#t=04:12]]`).
use notescraft_markdown::{MEDIA_EXTS, TimeFragment, format_time, is_media_ext, parse_time_fragment};
/// Start offset of a subpath, if it is a time fragment.
fn start(subpath: &str) -> Option<u64> {
parse_time_fragment(subpath).map(|frag| frag.start_ms)
}
#[test]
fn parses_the_documented_forms() {
assert_eq!(start("t=04:12"), Some(252_000));
assert_eq!(start("t=4:12"), Some(252_000));
assert_eq!(start("t=252"), Some(252_000));
assert_eq!(start("t=1:02:03"), Some(3_723_000));
assert_eq!(start("t=0:04:12.5"), Some(252_500));
assert_eq!(start("t=npt:10.5"), Some(10_500));
assert_eq!(start("t=10.5"), Some(10_500));
assert_eq!(start("t=0"), Some(0));
}
#[test]
fn parses_a_range() {
assert_eq!(parse_time_fragment("t=10,20"), Some(TimeFragment { start_ms: 10_000, end_ms: Some(20_000) }));
assert_eq!(parse_time_fragment("t=npt:10.5,20"), Some(TimeFragment { start_ms: 10_500, end_ms: Some(20_000) }));
assert_eq!(parse_time_fragment("t=10"), Some(TimeFragment { start_ms: 10_000, end_ms: None }));
}
#[test]
fn rejects_a_range_that_does_not_move_forward() {
assert_eq!(parse_time_fragment("t=20,10"), None);
assert_eq!(parse_time_fragment("t=10,10"), None);
}
#[test]
fn rejects_invalid_subpaths() {
for bad in [
"t=",
"t=-1",
"t=1e3",
"t=1.2345",
"t=99:99",
"t=1:75",
"t=360001",
"t= 10",
"t=10 ",
"t=10&x=1",
"t=10,20,30",
"t=10:",
"t=:10",
"t=1.2.3",
"t=10.",
"t=.5",
"page=3",
"T=10",
"",
] {
assert_eq!(parse_time_fragment(bad), None, "{bad} is not a time fragment");
}
assert_eq!(parse_time_fragment("t=\u{661}\u{662}"), None, "non-ASCII digits are not digits");
}
#[test]
fn rejects_subpaths_over_the_byte_limit() {
let long = format!("t={}", "0".repeat(63));
assert_eq!(long.len(), 65);
assert_eq!(parse_time_fragment(&long), None);
}
#[test]
fn accepts_the_total_limit() {
assert_eq!(start("t=100:00:00"), Some(360_000_000));
assert_eq!(start("t=100:00:00.001"), None);
assert_eq!(start("t=6000:00"), Some(360_000_000));
assert_eq!(start("t=6000:00.001"), None);
}
#[test]
fn formats_times() {
assert_eq!(format_time(0), "0:00");
assert_eq!(format_time(252_000), "4:12");
assert_eq!(format_time(3_723_000), "1:02:03");
assert_eq!(format_time(3_600_000), "1:00:00");
assert_eq!(format_time(59_999), "0:59", "seconds are truncated, not rounded");
assert_eq!(format_time(3_723_999), "1:02:03");
}
#[test]
fn formats_and_parses_round_trip() {
for step in 0..1000_u64 {
let ms = step * 1000;
let text = format!("t={}", format_time(ms));
assert_eq!(start(&text), Some(ms), "{text} does not round trip");
}
}
#[test]
fn media_extensions_are_case_insensitive() {
assert!(is_media_ext("MP3"));
assert!(is_media_ext("mp4"));
assert!(is_media_ext("MKV"));
assert!(!is_media_ext("md"));
assert!(!is_media_ext(""));
assert!(MEDIA_EXTS.contains(&"mp3"));
assert!(MEDIA_EXTS.contains(&"mp4"));
}