#![cfg(feature = "typescript")]
pub mod common;
use facet::Facet;
use facet_generate::generation::{
CodeGeneratorConfig, Encoding, SourceInstaller,
typescript::{self, InstallTarget},
};
use facet_generate::reflection::RegistryBuilder;
use regex::Regex;
use serde_json::Value;
use std::{
collections::BTreeMap,
fs::File,
path::Path,
process::{Command, Stdio},
};
use tempfile::tempdir;
fn test_typescript_code_compiles_with_config(
dir_path: &Path,
config: &CodeGeneratorConfig,
) -> std::path::PathBuf {
let registry = common::get_registry();
make_output_file(dir_path);
let mut installer = typescript::Installer::new("testing", dir_path, InstallTarget::Deno);
installer.install_serde_runtime().unwrap();
assert_deno_info(dir_path.join("serde/mod.ts").as_path());
let source_path = dir_path.join("testing").join("test.ts");
let mut source = File::create(&source_path).unwrap();
let generator = typescript::CodeGenerator::new(config, InstallTarget::Deno);
generator.output(&mut source, ®istry).unwrap();
assert_deno_info(&source_path);
dir_path.join("testing")
}
fn assert_deno_info(ts_path: &Path) {
let output = Command::new("deno")
.arg("info")
.arg(ts_path)
.stderr(Stdio::inherit())
.output()
.expect("deno info failed, is deno installed? brew install deno");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
!is_error_output(stdout.as_str()),
"deno info detected an error\n{stdout}"
);
}
fn is_error_output(output: &str) -> bool {
let re = Regex::new(r"\berror\b").unwrap();
re.is_match(output)
}
fn make_output_file(dir: &Path) {
std::fs::create_dir_all(dir.join("testing")).unwrap_or(());
}
#[test]
fn test_is_error_output() {
let table = vec![
(
"file:///var/folders/l0/x592_pjj18n6r2m0nqn05vmc0000gn/T/.tmp5NPlE2/serde/mod.ts (176B)",
false,
),
(
"https://deno.land/std@0.85.0/node/_errors.ts (60.89KB)",
false,
),
(
"file:///var/folders/l0/x592_pjj18n6r2m0nqn05vmc0000gn/T/.tmpG1an6c/something/noSerializer.ts (error)",
true,
),
];
for (input, expectation) in table {
assert_eq!(is_error_output(input), expectation);
}
}
#[test]
fn test_typescript_code_compiles_with_bincode() {
let dir = tempdir().unwrap();
let config = CodeGeneratorConfig::new("testing".to_string()).with_encoding(Encoding::Bincode);
test_typescript_code_compiles_with_config(dir.path(), &config);
}
#[test]
fn test_typescript_code_compiles_with_comments() {
#[derive(Facet)]
struct CommentedType {
value: String,
}
let dir = tempdir().unwrap();
let registry = RegistryBuilder::new()
.add_type::<CommentedType>()
.unwrap()
.build()
.unwrap();
let config = CodeGeneratorConfig::new("testing".to_string());
let source_path = dir.path().join("testing");
std::fs::create_dir_all(&source_path).unwrap();
let source_file = source_path.join("test.ts");
let mut file = File::create(&source_file).unwrap();
let generator = typescript::CodeGenerator::new(&config, InstallTarget::Deno);
generator.output(&mut file, ®istry).unwrap();
assert_deno_info(&source_file);
let content = std::fs::read_to_string(&source_file).unwrap();
assert!(
content.contains("/// Some\n/// comments\n"),
"Doc comments should be present in output:\n{content}"
);
}
#[test]
fn test_typescript_code_compiles_with_external_definitions() {
let dir = tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("external")).unwrap_or(());
std::fs::write(
dir.path().join("external/mod.ts"),
"export const CustomType = 5;",
)
.unwrap();
let mut external_definitions = BTreeMap::new();
external_definitions.insert(String::from("external"), vec![String::from("CustomType")]);
let config = CodeGeneratorConfig::new("testing".to_string())
.with_external_definitions(external_definitions);
test_typescript_code_compiles_with_config(dir.path(), &config);
}
#[test]
fn test_typescript_manifest_generation() {
let dir = tempdir().unwrap();
let installer =
typescript::Installer::new("my-typescript-package", dir.path(), InstallTarget::Deno);
installer.install_manifest("my-typescript-package").unwrap();
let manifest_path = dir.path().join("package.json");
assert!(manifest_path.exists());
let manifest_content = std::fs::read_to_string(&manifest_path).unwrap();
let manifest: Value = serde_json::from_str(&manifest_content).unwrap();
assert_eq!(manifest["name"], "my-typescript-package");
assert_eq!(manifest["version"], "0.1.0");
assert_eq!(manifest["devDependencies"]["typescript"], "^5.8.3");
}
#[test]
fn test_typescript_code_generation_without_extensions() {
let dir = tempdir().unwrap();
let registry = common::get_registry();
let config = CodeGeneratorConfig::new("testing".to_string()).with_encoding(Encoding::Bincode);
let mut installer = typescript::Installer::new("testing", dir.path(), InstallTarget::Node);
installer.install_module(&config, ®istry).unwrap();
installer.install_serde_runtime().unwrap();
installer.install_bincode_runtime().unwrap();
let module_path = dir.path().join("testing.ts");
assert!(module_path.exists());
let content = std::fs::read_to_string(&module_path).unwrap();
assert!(content.contains(r#"from "./serde""#));
assert!(!content.contains(r#"from "./serde/mod.ts""#));
let serde_index = dir.path().join("serde").join("index.ts");
assert!(serde_index.exists());
let serde_content = std::fs::read_to_string(&serde_index).unwrap();
assert!(serde_content.contains("from \"./types\""));
assert!(!serde_content.contains("from \"./types.ts\""));
let binary_deserializer = dir.path().join("serde").join("binaryDeserializer.ts");
assert!(binary_deserializer.exists());
let binary_deserializer_content = std::fs::read_to_string(&binary_deserializer).unwrap();
assert!(binary_deserializer_content.contains("from \"./deserializer\""));
assert!(!binary_deserializer_content.contains("from \"./deserializer.ts\""));
}
#[test]
fn test_typescript_code_generation_with_extensions() {
let dir = tempdir().unwrap();
let registry = common::get_registry();
let config = CodeGeneratorConfig::new("testing".to_string()).with_encoding(Encoding::Bincode);
let mut installer = typescript::Installer::new("testing", dir.path(), InstallTarget::Deno);
installer.install_module(&config, ®istry).unwrap();
installer.install_serde_runtime().unwrap();
installer.install_bincode_runtime().unwrap();
let module_path = dir.path().join("testing").join("mod.ts");
assert!(module_path.exists());
let content = std::fs::read_to_string(&module_path).unwrap();
assert!(content.contains(r#"from "./serde/mod.ts""#));
let serde_mod = dir.path().join("serde").join("mod.ts");
assert!(serde_mod.exists());
let serde_content = std::fs::read_to_string(&serde_mod).unwrap();
assert!(serde_content.contains("from \"./types.ts\""));
}