use std::path::Path;
use normalize_path::NormalizePath;
use crate::{AliasValue, Resolution, ResolveContext, ResolveError, ResolveOptions, Resolver};
#[tokio::test]
#[cfg(not(target_os = "windows"))] async fn alias() {
use std::path::{Path, PathBuf};
use super::memory_fs::MemoryFS;
use crate::ResolverGeneric;
let f = Path::new("/");
let file_system = MemoryFS::new(&[
("/a/index", ""),
("/a/dir/index", ""),
("/recursive/index", ""),
("/recursive/dir/index", ""),
("/b/index", ""),
("/b/dir/index", ""),
("/c/index", ""),
("/c/dir/index", ""),
("/d/index.js", ""),
("/d/dir/.empty", ""),
("/e/index", ""),
("/e/anotherDir/index", ""),
("/e/dir/file", ""),
("/dashed-name", ""),
]);
let resolver = ResolverGeneric::<MemoryFS>::new_with_file_system(
file_system,
ResolveOptions {
alias: vec![
("aliasA".into(), vec![AliasValue::from("a")]),
("b$".into(), vec![AliasValue::from("a/index")]),
("c$".into(), vec![AliasValue::from("/a/index")]),
(
"multiAlias".into(),
vec![
AliasValue::from("b"),
AliasValue::from("c"),
AliasValue::from("d"),
AliasValue::from("e"),
AliasValue::from("a"),
],
),
("recursive".into(), vec![AliasValue::from("recursive/dir")]),
("/d/dir".into(), vec![AliasValue::from("/c/dir")]),
("/d/index.js".into(), vec![AliasValue::from("/c/index")]),
("#".into(), vec![AliasValue::from("/c/dir")]),
("@".into(), vec![AliasValue::from("/c/dir")]),
("ignored".into(), vec![AliasValue::Ignore]),
(
"alias_query".into(),
vec![AliasValue::from("a?query_after")],
),
(
"alias_fragment".into(),
vec![AliasValue::from("a#fragment_after")],
),
("dash".into(), vec![AliasValue::Ignore]),
(
"@scope/package-name/file$".into(),
vec![AliasValue::from("/c/dir")],
),
],
modules: vec!["/".into()],
..ResolveOptions::default()
},
);
#[rustfmt::skip]
let pass = [
("should resolve a not aliased module 1", "a", "/a/index"),
("should resolve a not aliased module 2", "a/index", "/a/index"),
("should resolve a not aliased module 3", "a/dir", "/a/dir/index"),
("should resolve a not aliased module 4", "a/dir/index", "/a/dir/index"),
("should resolve an aliased module 1", "aliasA", "/a/index"),
("should resolve an aliased module 2", "aliasA/index", "/a/index"),
("should resolve an aliased module 3", "aliasA/dir", "/a/dir/index"),
("should resolve an aliased module 4", "aliasA/dir/index", "/a/dir/index"),
("should resolve '#' alias 1", "#", "/c/dir/index"),
("should resolve '#' alias 2", "#/index", "/c/dir/index"),
("should resolve '@' alias 1", "@", "/c/dir/index"),
("should resolve '@' alias 2", "@/index", "/c/dir/index"),
("should resolve '@' alias 3", "@/", "/c/dir/index"),
("should resolve a recursive aliased module 1", "recursive", "/recursive/dir/index"),
("should resolve a recursive aliased module 2", "recursive/index", "/recursive/dir/index"),
("should resolve a recursive aliased module 3", "recursive/dir", "/recursive/dir/index"),
("should resolve a recursive aliased module 4", "recursive/dir/index", "/recursive/dir/index"),
("should resolve a file aliased module 1", "b", "/a/index"),
("should resolve a file aliased module 2", "c", "/a/index"),
("should resolve a file aliased module with a query 1", "b?query", "/a/index?query"),
("should resolve a file aliased module with a query 2", "c?query", "/a/index?query"),
("should resolve a path in a file aliased module 1", "b/index", "/b/index"),
("should resolve a path in a file aliased module 2", "b/dir", "/b/dir/index"),
("should resolve a path in a file aliased module 3", "b/dir/index", "/b/dir/index"),
("should resolve a path in a file aliased module 4", "c/index", "/c/index"),
("should resolve a path in a file aliased module 5", "c/dir", "/c/dir/index"),
("should resolve a path in a file aliased module 6", "c/dir/index", "/c/dir/index"),
("should resolve a file aliased file 1", "d", "/c/index"),
("should resolve a file aliased file 2", "d/dir/index", "/c/dir/index"),
("should resolve a file in multiple aliased dirs 1", "multiAlias/dir/file", "/e/dir/file"),
("should resolve a file in multiple aliased dirs 2", "multiAlias/anotherDir", "/e/anotherDir/index"),
("should resolve query in alias value", "alias_query?query_before", "/a/index?query_after"),
("should resolve query in alias value", "alias_fragment#fragment_before", "/a/index#fragment_after"),
("should resolve dashed name", "dashed-name", "/dashed-name"),
("should resolve scoped package name with sub dir", "@scope/package-name/file", "/c/dir/index"),
];
for (comment, request, expected) in pass {
let resolved_path = resolver.resolve(f, request).await.map(|r| r.full_path());
assert_eq!(
resolved_path,
Ok(PathBuf::from(expected)),
"{comment} {request}"
);
}
#[rustfmt::skip]
let ignore = [
("should resolve an ignore module", "ignored", ResolveError::Ignored(f.join("ignored")))
];
for (comment, request, expected) in ignore {
let resolution = resolver.resolve(f, request).await;
assert_eq!(resolution, Err(expected), "{comment} {request}");
}
}
#[tokio::test]
async fn infinite_recursion() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
alias: vec![
("./a".into(), vec![AliasValue::from("./b")]),
("./b".into(), vec![AliasValue::from("./a")]),
],
..ResolveOptions::default()
});
let resolution = resolver.resolve(f, "./a").await;
assert_eq!(resolution, Err(ResolveError::Recursion));
}
fn check_slash(path: &Path) {
let s = path.to_string_lossy().to_string();
#[cfg(target_os = "windows")]
{
assert!(!s.contains('/'), "{s}");
assert!(s.contains('\\'), "{s}");
}
#[cfg(not(target_os = "windows"))]
{
assert!(s.contains('/'), "{s}");
assert!(!s.contains('\\'), "{s}");
}
}
#[tokio::test]
async fn absolute_path() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
alias: vec![(
f.join("foo").to_str().unwrap().to_string(),
vec![AliasValue::Ignore],
)],
modules: vec![f.clone().to_str().unwrap().to_string()],
..ResolveOptions::default()
});
let resolution = resolver.resolve(&f, "foo/index").await;
assert_eq!(resolution, Err(ResolveError::Ignored(f.join("foo"))));
}
#[tokio::test]
async fn system_path() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
alias: vec![(
"@app".into(),
vec![AliasValue::from(f.join("alias").to_string_lossy())],
)],
..ResolveOptions::default()
});
let specifiers = ["@app/files/a", "@app/files/a.js"];
for specifier in specifiers {
let path = resolver
.resolve(&f, specifier)
.await
.map(Resolution::into_path_buf)
.unwrap();
assert_eq!(path, f.join("alias/files/a.js"));
check_slash(&path);
}
}
#[tokio::test]
async fn alias_is_full_path() {
let f = super::fixture();
let dir = f.join("foo");
let dir_str = dir.to_string_lossy().to_string();
let resolver = Resolver::new(ResolveOptions {
alias: vec![("@".into(), vec![AliasValue::Path(dir_str.clone())])],
..ResolveOptions::default()
});
let mut ctx = ResolveContext::default();
let specifiers = [
"@/index".to_string(),
"@/index.js".to_string(),
"@////index".to_string(),
dir_str,
];
for specifier in specifiers {
let resolution = resolver.resolve_with_context(&f, &specifier, &mut ctx);
assert_eq!(
resolution.await.map(|r| r.full_path()),
Ok(dir.join("index.js"))
);
}
for path in ctx.file_dependencies {
assert_eq!(path, path.normalize(), "{path:?}");
check_slash(&path);
}
for path in ctx.missing_dependencies {
assert_eq!(path, path.normalize(), "{path:?}");
check_slash(&path);
if let Some(path) = path.parent() {
assert!(!path.is_file(), "{path:?} must not be a file");
}
}
}
#[tokio::test]
async fn all_alias_values_are_not_found() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
alias: vec![(
"m1".to_string(),
vec![AliasValue::Path(
f.join("node_modules")
.join("m2")
.to_string_lossy()
.to_string(),
)],
)],
..ResolveOptions::default()
});
let resolution = resolver.resolve(&f, "m1/a.js").await;
assert_eq!(
resolution,
Err(ResolveError::MatchedAliasNotFound(
"m1/a.js".to_string(),
"m1".to_string(),
))
);
}
#[tokio::test]
async fn alias_fragment() {
let f = super::fixture();
let data = [
(
"handle fragment edge case (no fragment)",
"./no#fragment/#/#",
f.join("no#fragment/#/#.js"),
),
(
"handle fragment edge case (fragment)",
"./no#fragment/#/",
f.join("no.js#fragment/#/"),
),
(
"handle fragment escaping",
"./no\0#fragment/\0#/\0##fragment",
f.join("no#fragment/#/#.js#fragment"),
),
];
for (comment, request, expected) in data {
let resolver = Resolver::new(ResolveOptions {
alias: vec![(
"foo".to_string(),
vec![AliasValue::Path(request.to_string())],
)],
..ResolveOptions::default()
});
let resolved_path = resolver.resolve(&f, "foo").await.map(|r| r.full_path());
assert_eq!(resolved_path, Ok(expected), "{comment} {request}");
}
}
#[tokio::test]
async fn alias_try_fragment_as_path() {
let f = super::fixture();
let resolver = Resolver::new(ResolveOptions {
alias: vec![(
"#".to_string(),
vec![AliasValue::Path(f.join("#").to_string_lossy().to_string())],
)],
..ResolveOptions::default()
});
let resolution = resolver.resolve(&f, "#/a").await.map(|r| r.full_path());
assert_eq!(resolution, Ok(f.join("#").join("a.js")));
}