use std::path::{Path, PathBuf};
use crate::{
ResolveOptions, Resolver, TsConfig, TsconfigDiscovery, TsconfigOptions, TsconfigReferences,
};
#[test]
fn test_extend_tsconfig() {
let f = super::fixture_root().join("tsconfig/cases/extends");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let resolution = resolver.resolve_tsconfig(&f).expect("resolved");
assert_eq!(resolution.files, Some(vec![f.join("files")]));
assert_eq!(resolution.include, Some(vec![f.join("include")]));
assert_eq!(resolution.exclude, Some(vec![f.join("exclude")]));
let compiler_options = &resolution.compiler_options;
assert_eq!(compiler_options.base_url, Some(f.join("src")));
assert_eq!(compiler_options.allow_js, Some(true));
assert_eq!(compiler_options.emit_decorator_metadata, Some(true));
assert_eq!(compiler_options.use_define_for_class_fields, Some(true));
assert_eq!(compiler_options.rewrite_relative_import_extensions, Some(true));
assert_eq!(compiler_options.jsx, Some("react-jsx".to_string()));
assert_eq!(compiler_options.jsx_factory, Some("React.createElement".to_string()));
assert_eq!(compiler_options.jsx_fragment_factory, Some("React.Fragment".to_string()));
assert_eq!(compiler_options.jsx_import_source, Some("react".to_string()));
}
#[test]
fn test_extend_tsconfig_paths() {
let f = super::fixture_root().join("tsconfig/cases/extends-paths-inheritance");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
extensions: vec![".ts".into(), ".js".into()],
..ResolveOptions::default()
});
let resolved_path =
resolver.resolve_file(f.join("src").join("test.ts"), "@/test").map(|f| f.full_path());
assert_eq!(resolved_path, Ok(f.join("src/test.ts")));
}
#[test]
fn test_extend_tsconfig_override_behavior() {
let f = super::fixture_root().join("tsconfig/cases/extends-override");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let resolution = resolver.resolve_tsconfig(&f).expect("resolved");
let compiler_options = &resolution.compiler_options;
assert_eq!(compiler_options.jsx, Some("react".to_string()));
assert_eq!(compiler_options.target, Some("ES2020".to_string()));
}
#[test]
fn test_extend_tsconfig_template_variables() {
let f = super::fixture_root().join("tsconfig/cases/extends-template-vars");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
extensions: vec![".ts".into(), ".js".into()],
..ResolveOptions::default()
});
let resolved_path =
resolver.resolve_file(f.join("src/utils.ts"), "@/utils").map(|f| f.full_path());
assert_eq!(resolved_path, Ok(f.join("src/utils.ts")));
}
#[test]
fn test_extend_tsconfig_missing_file() {
use crate::ResolveError;
let f = super::fixture_root().join("tsconfig/cases");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("nonexistent-tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let result = resolver.resolve_tsconfig(&f);
assert!(matches!(result, Err(ResolveError::TsconfigNotFound(_))));
}
#[test]
fn test_extend_tsconfig_multiple_inheritance() {
let f = super::fixture_root().join("tsconfig/cases/extends-chain");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let resolution = resolver.resolve_tsconfig(&f).expect("resolved");
let compiler_options = &resolution.compiler_options;
assert_eq!(compiler_options.experimental_decorators, Some(true));
assert_eq!(compiler_options.target, Some("ES2022".to_string()));
assert_eq!(compiler_options.module, Some("ESNext".to_string()));
}
#[test]
fn test_extend_tsconfig_preserves_child_settings() {
let f = super::fixture_root().join("tsconfig/cases/extends-preserve-child");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let resolution = resolver.resolve_tsconfig(&f).expect("resolved");
let compiler_options = &resolution.compiler_options;
assert_eq!(compiler_options.jsx, Some("preserve".to_string())); assert_eq!(compiler_options.target, Some("ES2020".to_string())); }
#[test]
fn test_extend_tsconfig_no_override_existing() {
let parent_path = Path::new("/parent/tsconfig.json");
let child_path = Path::new("/child/tsconfig.json");
let parent_config = serde_json::json!({
"compilerOptions": {
"baseUrl": "./src",
"jsx": "react-jsx",
"target": "ES2020"
}
})
.to_string();
let child_config = serde_json::json!({
"compilerOptions": {
"jsx": "preserve" }
})
.to_string();
let parent_tsconfig = TsConfig::parse(true, parent_path, parent_config).unwrap().build();
let mut child_tsconfig = TsConfig::parse(true, child_path, child_config).unwrap();
child_tsconfig.extend_tsconfig(&parent_tsconfig);
let child_built = child_tsconfig.build();
let compiler_options = &child_built.compiler_options;
assert_eq!(compiler_options.jsx, Some("preserve".to_string()));
assert_eq!(compiler_options.target, Some("ES2020".to_string()));
assert!(compiler_options.base_url.is_some());
}
#[test]
fn test_extend_tsconfig_not_found() {
use crate::ResolveError;
let f = super::fixture_root().join("tsconfig/cases/extends-not-found");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Disabled,
})),
..ResolveOptions::default()
});
let result = resolver.resolve_tsconfig(&f);
assert!(
matches!(&result, Err(ResolveError::TsconfigNotFound(_))),
"expected TsconfigNotFound for missing extends target, got {result:?}",
);
}
#[test]
fn test_references_not_found() {
use crate::ResolveError;
let f = super::fixture_root().join("tsconfig/cases/references-not-found");
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
let result = resolver.resolve_tsconfig(&f);
assert!(
matches!(&result, Err(ResolveError::TsconfigNotFound(_))),
"expected TsconfigNotFound for missing references target, got {result:?}",
);
}
struct UnreadableFs {
unreadable_path: PathBuf,
}
impl crate::FileSystem for UnreadableFs {
#[cfg(not(feature = "yarn_pnp"))]
fn new() -> Self {
unreachable!()
}
#[cfg(feature = "yarn_pnp")]
fn new(_yarn_pnp: bool) -> Self {
unreachable!()
}
fn read(&self, path: &Path) -> std::io::Result<Vec<u8>> {
std::fs::read(path)
}
fn read_to_string(&self, path: &Path) -> std::io::Result<String> {
if path == self.unreadable_path {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"permission denied",
));
}
crate::FileSystemOs::read_to_string(path)
}
fn metadata(&self, path: &Path) -> std::io::Result<crate::FileMetadata> {
crate::FileSystemOs::metadata(path)
}
fn symlink_metadata(&self, path: &Path) -> std::io::Result<crate::FileMetadata> {
crate::FileSystemOs::symlink_metadata(path)
}
fn read_link(&self, path: &Path) -> Result<PathBuf, crate::ResolveError> {
crate::FileSystemOs::read_link(path)
}
fn canonicalize(&self, path: &Path) -> std::io::Result<PathBuf> {
crate::FileSystemOs::canonicalize(path)
}
}
#[test]
fn test_extend_tsconfig_unreadable_file() {
use crate::ResolveError;
let f = super::fixture_root().join("tsconfig/cases/extends-unreadable");
let fs = UnreadableFs { unreadable_path: f.join("base.json") };
let resolver = crate::ResolverGeneric::new_with_file_system(
fs,
ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Disabled,
})),
..ResolveOptions::default()
},
);
let result = resolver.resolve_tsconfig(&f);
assert!(
matches!(&result, Err(ResolveError::IOError(_))),
"expected IOError for unreadable extends target, got {result:?}",
);
}
#[test]
fn test_references_unreadable_file() {
use crate::ResolveError;
let f = super::fixture_root().join("tsconfig/cases/references-unreadable");
let fs = UnreadableFs { unreadable_path: f.join("referenced/tsconfig.json") };
let resolver = crate::ResolverGeneric::new_with_file_system(
fs,
ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
},
);
let result = resolver.resolve_tsconfig(&f);
assert!(
matches!(&result, Err(ResolveError::IOError(_))),
"expected IOError for unreadable references target, got {result:?}",
);
}
#[test]
fn test_extend_package() {
let f = super::fixture_root().join("tsconfig/cases");
let data = ["extends-esm", "extends-main"];
let resolver = Resolver::new(ResolveOptions {
tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
config_file: f.join("tsconfig.json"),
references: TsconfigReferences::Auto,
})),
..ResolveOptions::default()
});
for dir in data {
let resolution = resolver.resolve_tsconfig(f.join(dir)).expect("resolved");
let compiler_options = &resolution.compiler_options;
assert_eq!(compiler_options.target, Some("ES2020".to_string()));
}
}