//! W57 depth tests for tokmd-path: exhaustive edge-case, ordering,//! unicode, Windows, and proptest coverage for normalize_slashes//! and normalize_rel_path.use tokmd_path::{normalize_rel_path, normalize_slashes};// ═══════════════════════════════════════════════════════════════════// normalize_slashes — edge cases// ═══════════════════════════════════════════════════════════════════#[test]fn slashes_empty_input() { assert_eq!(normalize_slashes(""), "");}#[test]fn slashes_single_dot() { assert_eq!(normalize_slashes("."), ".");}#[test]fn slashes_double_dot() { assert_eq!(normalize_slashes(".."), "..");}#[test]fn slashes_mixed_separators_complex() { assert_eq!(normalize_slashes(r"a/b\c/d\e/f"), "a/b/c/d/e/f");}#[test]fn slashes_trailing_backslash_preserved_as_forward() { assert_eq!(normalize_slashes(r"dir\sub\"), "dir/sub/");}#[test]fn slashes_trailing_forward_slash_unchanged() { assert_eq!(normalize_slashes("dir/sub/"), "dir/sub/");}#[test]fn slashes_leading_dot_slash() { assert_eq!(normalize_slashes(r".\foo\bar"), "./foo/bar");}#[test]fn slashes_multiple_consecutive_backslashes() { assert_eq!(normalize_slashes(r"a\\\b"), "a///b");}#[test]fn slashes_multiple_consecutive_forward_slashes_unchanged() { assert_eq!(normalize_slashes("a///b"), "a///b");}#[test]fn slashes_unicode_cjk_path() { assert_eq!(normalize_slashes(r"项目\源码\main.rs"), "项目/源码/main.rs");}#[test]fn slashes_unicode_emoji_path() { assert_eq!(normalize_slashes(r"📁\📄"), "📁/📄");}#[test]fn slashes_unicode_arabic_path() { assert_eq!(normalize_slashes(r"مشروع\ملف.rs"), "مشروع/ملف.rs");}#[test]fn slashes_windows_drive_letter_uppercase() { assert_eq!(normalize_slashes(r"C:\Users\me\code"), "C:/Users/me/code");}#[test]fn slashes_windows_drive_letter_lowercase() { assert_eq!(normalize_slashes(r"d:\data\file.txt"), "d:/data/file.txt");}#[test]fn slashes_unc_path_double_backslash() { assert_eq!( normalize_slashes(r"\\server\share\dir\file"), "//server/share/dir/file" );}#[test]fn slashes_unc_path_long() { assert_eq!( normalize_slashes(r"\\?\C:\very\long\path"), "//?/C:/very/long/path" );}#[test]fn slashes_only_backslashes() { assert_eq!(normalize_slashes(r"\\\"), "///");}#[test]fn slashes_only_forward_slashes() { assert_eq!(normalize_slashes("///"), "///");}// ═══════════════════════════════════════════════════════════════════// normalize_rel_path — edge cases// ═══════════════════════════════════════════════════════════════════#[test]fn rel_empty_string_returns_empty() { assert_eq!(normalize_rel_path(""), "");}#[test]fn rel_single_dot_returns_dot() { // "." doesn't start with "./" so stays assert_eq!(normalize_rel_path("."), ".");}#[test]fn rel_double_dot_preserved() { assert_eq!(normalize_rel_path(".."), "..");}#[test]fn rel_dot_slash_dot_dot() { // "./../foo" -> strip "./" -> "../foo" assert_eq!(normalize_rel_path("./../foo"), "../foo");}#[test]fn rel_multiple_dot_slash_prefix() { assert_eq!(normalize_rel_path("./././file.txt"), "file.txt");}#[test]fn rel_dot_slash_then_dot_file() { assert_eq!(normalize_rel_path("./.gitignore"), ".gitignore");}#[test]fn rel_mixed_separators_with_dot_prefix() { assert_eq!(normalize_rel_path(r".\foo/bar\baz"), "foo/bar/baz");}#[test]fn rel_unicode_with_dot_prefix() { assert_eq!( normalize_rel_path(r".\données\résumé.md"), "données/résumé.md" );}#[test]fn rel_windows_drive_not_stripped() { // "C:\foo" doesn't start with "./" so stays (just normalized) assert_eq!(normalize_rel_path(r"C:\foo\bar.rs"), "C:/foo/bar.rs");}#[test]fn rel_consecutive_slashes_after_dot() { // ".//foo" -> strip "./" -> "/foo" assert_eq!(normalize_rel_path(".//foo"), "/foo");}#[test]fn rel_deeply_nested_with_dot_prefix() { assert_eq!( normalize_rel_path("./a/b/c/d/e/f/g/h.rs"), "a/b/c/d/e/f/g/h.rs" );}// ═══════════════════════════════════════════════════════════════════// Forward-slash normalization — exhaustive// ═══════════════════════════════════════════════════════════════════#[test]fn forward_slash_all_common_patterns() { let cases = [ (r"a\b\c", "a/b/c"), (r"\a\b\c", "/a/b/c"), (r"a\b\c\", "a/b/c/"), ("a/b/c", "a/b/c"), (r"a/b\c/d", "a/b/c/d"), (r"\\a\\b", "//a//b"), ("", ""), ("no_sep", "no_sep"), ]; for (input, expected) in &cases { assert_eq!( normalize_slashes(input), *expected, "Failed for input: {input:?}" ); }}#[test]fn forward_slash_never_appears_as_backslash_in_output() { let inputs = [ r"a\b\c\d\e", r"\\server\share", r"C:\Program Files\app", r".\relative\path", r"..\parent\path", r"\\\triple", r"mixed/and\back", r"trailing\", ]; for input in &inputs { let result = normalize_slashes(input); assert!( !result.contains('\\'), "Backslash found in output for {input:?}: {result}" ); }}// ═══════════════════════════════════════════════════════════════════// Path comparison / ordering determinism// ═══════════════════════════════════════════════════════════════════#[test]fn ordering_normalized_paths_are_lexicographically_sorted() { let mut paths: Vec<String> = vec![ r"src\zzz.rs", r"src\aaa.rs", r"lib\bbb.rs", r"crates\foo\bar.rs", ] .into_iter() .map(normalize_slashes) .collect(); paths.sort(); assert_eq!( paths, vec![ "crates/foo/bar.rs", "lib/bbb.rs", "src/aaa.rs", "src/zzz.rs", ] );}#[test]fn ordering_equivalent_paths_sort_identically() { let a = normalize_slashes(r"src\main.rs"); let b = normalize_slashes("src/main.rs"); assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal);}#[test]fn ordering_rel_paths_deterministic_sort() { let mut paths: Vec<String> = vec![r".\src\b.rs", r".\src\a.rs", "./lib/c.rs", r".\tools\d.rs"] .into_iter() .map(normalize_rel_path) .collect(); paths.sort(); assert_eq!( paths, vec!["lib/c.rs", "src/a.rs", "src/b.rs", "tools/d.rs",] );}#[test]fn ordering_repeated_normalization_preserves_order() { let inputs = [r"b\2.rs", r"a\1.rs", r"c\3.rs"]; let mut first: Vec<String> = inputs.iter().map(|p| normalize_slashes(p)).collect(); let mut second: Vec<String> = inputs.iter().map(|p| normalize_slashes(p)).collect(); first.sort(); second.sort(); assert_eq!(first, second);}// ═══════════════════════════════════════════════════════════════════// Cross-function consistency// ═══════════════════════════════════════════════════════════════════#[test]fn rel_path_always_subset_of_slash_normalization() { let inputs = [ "./foo/bar", r".\foo\bar", "plain/path", r"a\b", "././nested", "./", "", ]; for input in &inputs { let slash = normalize_slashes(input); let rel = normalize_rel_path(input); assert!( rel.len() <= slash.len(), "rel ({rel:?}) longer than slashes ({slash:?}) for {input:?}" ); }}#[test]fn rel_output_is_suffix_of_slashes_output() { let inputs = [ "./foo/bar.rs", r".\crates\foo\lib.rs", "src/lib.rs", "././nested/deep/file.rs", ]; for input in &inputs { let slash = normalize_slashes(input); let rel = normalize_rel_path(input); assert!( slash.ends_with(&rel), "rel {rel:?} not suffix of slashes {slash:?} for {input:?}" ); }}#[test]fn idempotency_both_functions() { let inputs = [ r".\foo\bar", r"a\b\c", "./x/y/z", "plain", "", ".", "..", r"C:\drive", r"\\unc\path", ]; for input in &inputs { let s1 = normalize_slashes(input); let s2 = normalize_slashes(&s1); assert_eq!(s1, s2, "normalize_slashes not idempotent for {input:?}"); let r1 = normalize_rel_path(input); let r2 = normalize_rel_path(&r1); assert_eq!(r1, r2, "normalize_rel_path not idempotent for {input:?}"); }}// ═══════════════════════════════════════════════════════════════════// proptest// ═══════════════════════════════════════════════════════════════════mod proptests { use super::*; use proptest::prelude::*; proptest! { #[test] fn slashes_output_never_contains_backslash(path in "\\PC{0,200}") { let result = normalize_slashes(&path); prop_assert!(!result.contains('\\'), "backslash in output for {:?}: {}", path, result); } #[test] fn slashes_idempotent(path in "\\PC{0,200}") { let once = normalize_slashes(&path); let twice = normalize_slashes(&once); prop_assert_eq!(&once, &twice); } #[test] fn slashes_preserves_length(path in "\\PC{0,200}") { let result = normalize_slashes(&path); prop_assert_eq!(result.len(), path.len(), "length changed for {:?}", path); } #[test] fn rel_output_never_contains_backslash(path in "\\PC{0,200}") { let result = normalize_rel_path(&path); prop_assert!(!result.contains('\\'), "backslash in output for {:?}: {}", path, result); } #[test] fn rel_idempotent(path in "\\PC{0,200}") { let once = normalize_rel_path(&path); let twice = normalize_rel_path(&once); prop_assert_eq!(&once, &twice); } #[test] fn rel_output_no_leading_dot_slash(path in "\\PC{0,200}") { let result = normalize_rel_path(&path); prop_assert!(!result.starts_with("./"), "leading ./ in output for {:?}: {}", path, result); } #[test] fn slashes_forward_slash_count_gte_input(path in "\\PC{0,200}") { let input_fwd = path.chars().filter(|c| *c == '/').count(); let input_back = path.chars().filter(|c| *c == '\\').count(); let output_fwd = normalize_slashes(&path).chars().filter(|c| *c == '/').count(); prop_assert!(output_fwd >= input_fwd, "forward slashes decreased for {:?}", path); prop_assert_eq!(output_fwd, input_fwd + input_back, "forward slashes != fwd + back for {:?}", path); } }}