use std::path::Path;
use syncable_cli::analyzer::{
AnalysisConfig, DetectedLanguage, TechnologyCategory, framework_detector::detect_frameworks,
};
#[test]
fn test_javascript_framework_detection_with_file_indicators() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("18.0.0".to_string()),
confidence: 0.9,
files: vec![
std::path::PathBuf::from("next.config.js"),
std::path::PathBuf::from("pages/index.js"),
],
main_dependencies: vec![
"next".to_string(),
"react".to_string(),
"react-dom".to_string(),
],
dev_dependencies: vec!["eslint".to_string()],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let nextjs = technologies.iter().find(|t| t.name == "Next.js");
assert!(nextjs.is_some(), "Next.js should be detected");
let nextjs = nextjs.unwrap();
assert!(matches!(nextjs.category, TechnologyCategory::MetaFramework));
assert!(nextjs.is_primary);
assert!(nextjs.confidence > 0.9);
let react = technologies.iter().find(|t| t.name == "React");
assert!(react.is_some(), "React should be detected");
}
#[test]
fn test_expo_detection_with_config_file() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("18.0.0".to_string()),
confidence: 0.9,
files: vec![
std::path::PathBuf::from("app.json"),
std::path::PathBuf::from("App.js"),
],
main_dependencies: vec![
"expo".to_string(),
"react".to_string(),
"react-native".to_string(),
],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let expo = technologies.iter().find(|t| t.name == "Expo");
assert!(expo.is_some(), "Expo should be detected");
let expo = expo.unwrap();
assert!(matches!(expo.category, TechnologyCategory::MetaFramework));
assert!(expo.is_primary);
assert!(expo.confidence > 0.9); }
#[test]
fn test_tanstack_start_detection_with_config_file() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("18.0.0".to_string()),
confidence: 0.9,
files: vec![std::path::PathBuf::from("app.config.ts")],
main_dependencies: vec![
"@tanstack/react-start".to_string(),
"react".to_string(),
"vinxi".to_string(),
],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let tanstack = technologies.iter().find(|t| t.name == "Tanstack Start");
assert!(tanstack.is_some(), "Tanstack Start should be detected");
let tanstack = tanstack.unwrap();
assert!(matches!(
tanstack.category,
TechnologyCategory::MetaFramework
));
assert!(tanstack.is_primary);
assert!(tanstack.confidence > 0.9); }
#[test]
fn test_react_native_detection_with_config_file() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("18.0.0".to_string()),
confidence: 0.9,
files: vec![std::path::PathBuf::from("react-native.config.js")],
main_dependencies: vec!["react-native".to_string(), "react".to_string()],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let react_native = technologies.iter().find(|t| t.name == "React Native");
assert!(react_native.is_some(), "React Native should be detected");
let react_native = react_native.unwrap();
assert!(matches!(
react_native.category,
TechnologyCategory::FrontendFramework
));
assert!(react_native.is_primary);
assert!(react_native.confidence > 0.9); }
#[test]
fn test_expo_react_native_detection_should_not_detect_nextjs() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("4.0.0".to_string()),
confidence: 0.95,
files: vec![
std::path::PathBuf::from("app.json"),
std::path::PathBuf::from("App.tsx"),
std::path::PathBuf::from("android/build.gradle"),
std::path::PathBuf::from("ios/Podfile"),
],
main_dependencies: vec![
"expo".to_string(),
"react-native".to_string(),
"react".to_string(),
"next".to_string(), ],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let expo = technologies.iter().find(|t| t.name == "Expo");
let nextjs = technologies.iter().find(|t| t.name == "Next.js");
assert!(expo.is_some(), "Should detect Expo");
assert!(expo.unwrap().is_primary, "Expo should be primary");
assert!(
nextjs.is_none(),
"Should not detect Next.js in Expo project"
);
}
#[test]
fn test_encore_backend_detection() {
let language = DetectedLanguage {
name: "TypeScript".to_string(),
version: Some("4.0.0".to_string()),
confidence: 0.95,
files: vec![
std::path::PathBuf::from("main.go"),
std::path::PathBuf::from("service/user.go"),
std::path::PathBuf::from("encore.app"),
],
main_dependencies: vec!["encore.dev".to_string()],
dev_dependencies: vec![],
package_manager: Some("go mod".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let encore = technologies.iter().find(|t| t.name == "Encore");
assert!(encore.is_some(), "Should detect Encore");
assert!(encore.unwrap().is_primary, "Encore should be primary");
}
#[test]
fn test_encore_detection_should_not_detect_nextjs() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("4.0.0".to_string()),
confidence: 0.95,
files: vec![
std::path::PathBuf::from("encore.app"),
std::path::PathBuf::from("service/api.encore.service.ts"),
],
main_dependencies: vec![
"encore.dev".to_string(),
"next".to_string(), ],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let encore = technologies.iter().find(|t| t.name == "Encore");
let nextjs = technologies.iter().find(|t| t.name == "Next.js");
assert!(encore.is_some(), "Should detect Encore");
assert!(encore.unwrap().is_primary, "Encore should be primary");
assert!(
nextjs.is_none(),
"Should not detect Next.js in Encore project"
);
}
#[test]
fn test_false_positive_expo_detection_in_pure_typescript_project() {
let language = DetectedLanguage {
name: "TypeScript".to_string(),
version: Some(">=20.0.0".to_string()),
confidence: 0.92499995,
files: vec![
std::path::PathBuf::from("eslint.config.js"),
std::path::PathBuf::from("src/tools/write-file.ts"),
std::path::PathBuf::from("src/tools/read-file.ts"),
std::path::PathBuf::from("src/tools/insert.ts"),
std::path::PathBuf::from("src/tools/docker.ts"),
std::path::PathBuf::from("src/tools/directoryContext.ts"),
std::path::PathBuf::from("src/tools/ls.ts"),
std::path::PathBuf::from("src/tools/grep.ts"),
std::path::PathBuf::from("src/tools/edit.ts"),
std::path::PathBuf::from("src/tools/index.ts"),
std::path::PathBuf::from("src/tools/read-many-files.ts"),
std::path::PathBuf::from("src/agents/repo-analysis-agent.ts"),
std::path::PathBuf::from("src/agents/docker-agent.ts"),
std::path::PathBuf::from("src/agents/infra-agent.ts"),
std::path::PathBuf::from("src/toolkits/generateFileToolkit.ts"),
std::path::PathBuf::from("src/toolkits/fullRepoToolkit.ts"),
std::path::PathBuf::from("src/toolkits/dockerToolkit.ts"),
std::path::PathBuf::from("src/toolkits/minimalRepoToolkit.ts"),
std::path::PathBuf::from("src/supervisors/tech-lead-supervisor.ts"),
std::path::PathBuf::from("src/supervisors/application-supervisor.ts"),
std::path::PathBuf::from("src/supervisors/devops-supervisor.ts"),
std::path::PathBuf::from("src/index.ts"),
],
main_dependencies: vec![
"@ai-sdk/anthropic".to_string(),
"@types/glob".to_string(),
"@voltagent/cli".to_string(),
"@voltagent/core".to_string(),
"@voltagent/langfuse-exporter".to_string(),
"@voltagent/logger".to_string(),
"@voltagent/vercel-ai".to_string(),
"dockerode".to_string(),
"dotenv".to_string(),
"glob".to_string(),
"tar-fs".to_string(),
"zod".to_string(),
],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
println!("Detected technologies:");
for tech in &technologies {
println!(
" - {} (confidence: {:.2}, primary: {})",
tech.name, tech.confidence, tech.is_primary
);
}
let expo = technologies.iter().find(|t| t.name == "Expo");
if let Some(expo_tech) = expo {
println!("ERROR: Expo incorrectly detected!");
println!(" Confidence: {}", expo_tech.confidence);
println!(" Is primary: {}", expo_tech.is_primary);
panic!(
"Expo should NOT be detected in a pure TypeScript project without Expo dependencies"
);
} else {
println!("SUCCESS: Expo not detected (as expected)");
}
}
#[test]
fn test_legitimate_expo_detection_still_works() {
let language = DetectedLanguage {
name: "JavaScript".to_string(),
version: Some("18.0.0".to_string()),
confidence: 0.9,
files: vec![
std::path::PathBuf::from("app.json"),
std::path::PathBuf::from("App.js"),
],
main_dependencies: vec![
"expo".to_string(),
"react".to_string(),
"react-native".to_string(),
],
dev_dependencies: vec![],
package_manager: Some("npm".to_string()),
};
let config = AnalysisConfig::default();
let project_root = Path::new(".");
let technologies = detect_frameworks(project_root, &[language], &config).unwrap();
let expo = technologies.iter().find(|t| t.name == "Expo");
assert!(
expo.is_some(),
"Expo should be detected with proper dependencies"
);
let expo = expo.unwrap();
assert!(matches!(expo.category, TechnologyCategory::MetaFramework));
assert!(expo.is_primary);
assert!(expo.confidence > 0.9);
println!("SUCCESS: Expo correctly detected with legitimate dependencies");
println!(" Confidence: {}", expo.confidence);
println!(" Is primary: {}", expo.is_primary);
}