huskies: merge 588_bug_wizard_generated_script_test_misses_frontend_tests_for_projects_with_a_frontend

This commit is contained in:
dave
2026-04-15 23:53:03 +00:00
parent 4553d7215a
commit 61502f51d9
+178 -7
View File
@@ -199,33 +199,69 @@ pub fn detect_components_toml(root: &Path) -> String {
sections.join("\n") sections.join("\n")
} }
/// Detect the appropriate Node.js test command for a directory containing `package.json`.
///
/// Reads the `package.json` content to identify known test runners (vitest, jest).
/// Falls back to `npm test` or `pnpm test` based on which lock file is present.
fn detect_node_test_cmd(pkg_dir: &Path) -> String {
let has_pnpm = pkg_dir.join("pnpm-lock.yaml").exists();
let content = std::fs::read_to_string(pkg_dir.join("package.json")).unwrap_or_default();
if content.contains("\"vitest\"") {
let pm = if has_pnpm { "pnpm" } else { "npx" };
return format!("{} vitest run", pm);
}
if content.contains("\"jest\"") {
let pm = if has_pnpm { "pnpm" } else { "npx" };
return format!("{} jest", pm);
}
if has_pnpm {
"pnpm test".to_string()
} else {
"npm test".to_string()
}
}
/// Generate `script/test` content for a new project at `root`. /// Generate `script/test` content for a new project at `root`.
/// ///
/// Inspects well-known marker files to identify which tech stacks are present /// Inspects well-known marker files to identify which tech stacks are present
/// and emits the appropriate test commands. Multi-stack projects get combined /// and emits the appropriate test commands. Multi-stack projects get combined
/// commands run sequentially. Falls back to the generic stub when no markers /// commands run sequentially. Falls back to the generic stub when no markers
/// are found so the scaffold is always valid. /// are found so the scaffold is always valid.
///
/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`),
/// the test runner is detected from the `package.json` (vitest, jest, npm, pnpm).
pub fn detect_script_test(root: &Path) -> String { pub fn detect_script_test(root: &Path) -> String {
let mut commands: Vec<&str> = Vec::new(); let mut commands: Vec<String> = Vec::new();
if root.join("Cargo.toml").exists() { if root.join("Cargo.toml").exists() {
commands.push("cargo test"); commands.push("cargo test".to_string());
} }
if root.join("package.json").exists() { if root.join("package.json").exists() {
if root.join("pnpm-lock.yaml").exists() { if root.join("pnpm-lock.yaml").exists() {
commands.push("pnpm test"); commands.push("pnpm test".to_string());
} else { } else {
commands.push("npm test"); commands.push("npm test".to_string());
}
}
// Detect frontend in known subdirectories (e.g. frontend/, client/)
for subdir in &["frontend", "client"] {
let sub_path = root.join(subdir);
if sub_path.join("package.json").exists() {
let cmd = detect_node_test_cmd(&sub_path);
commands.push(format!("(cd {} && {})", subdir, cmd));
} }
} }
if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() { if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() {
commands.push("pytest"); commands.push("pytest".to_string());
} }
if root.join("go.mod").exists() { if root.join("go.mod").exists() {
commands.push("go test ./..."); commands.push("go test ./...".to_string());
} }
if commands.is_empty() { if commands.is_empty() {
@@ -234,7 +270,7 @@ pub fn detect_script_test(root: &Path) -> String {
let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string(); let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string();
for cmd in commands { for cmd in commands {
script.push_str(cmd); script.push_str(&cmd);
script.push('\n'); script.push('\n');
} }
script script
@@ -1170,6 +1206,141 @@ mod tests {
); );
} }
#[test]
fn detect_script_test_frontend_subdir_with_vitest_uses_npx_vitest() {
let dir = tempdir().unwrap();
let frontend = dir.path().join("frontend");
fs::create_dir_all(&frontend).unwrap();
fs::write(
frontend.join("package.json"),
r#"{"devDependencies":{"vitest":"^1.0.0"},"scripts":{"test":"vitest run"}}"#,
)
.unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("vitest run"),
"frontend with vitest should emit vitest run"
);
assert!(
script.contains("cd frontend"),
"should cd into the frontend directory"
);
assert!(
!script.contains("No tests configured"),
"should not use stub when frontend is detected"
);
}
#[test]
fn detect_script_test_frontend_subdir_with_jest_uses_npx_jest() {
let dir = tempdir().unwrap();
let frontend = dir.path().join("frontend");
fs::create_dir_all(&frontend).unwrap();
fs::write(
frontend.join("package.json"),
r#"{"devDependencies":{"jest":"^29.0.0"},"scripts":{"test":"jest"}}"#,
)
.unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("jest"),
"frontend with jest should emit jest"
);
assert!(
script.contains("cd frontend"),
"should cd into the frontend directory"
);
}
#[test]
fn detect_script_test_frontend_subdir_no_known_runner_uses_npm_test() {
let dir = tempdir().unwrap();
let frontend = dir.path().join("frontend");
fs::create_dir_all(&frontend).unwrap();
fs::write(
frontend.join("package.json"),
r#"{"scripts":{"test":"mocha"}}"#,
)
.unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("npm test"),
"frontend without known runner should fall back to npm test"
);
assert!(script.contains("cd frontend"));
}
#[test]
fn detect_script_test_frontend_subdir_pnpm_uses_pnpm_vitest() {
let dir = tempdir().unwrap();
let frontend = dir.path().join("frontend");
fs::create_dir_all(&frontend).unwrap();
fs::write(
frontend.join("package.json"),
r#"{"devDependencies":{"vitest":"^1.0.0"}}"#,
)
.unwrap();
fs::write(frontend.join("pnpm-lock.yaml"), "").unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("pnpm vitest run"),
"pnpm frontend with vitest should use pnpm vitest run"
);
}
#[test]
fn detect_script_test_rust_plus_frontend_subdir_both_included() {
let dir = tempdir().unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"server\"\n",
)
.unwrap();
let frontend = dir.path().join("frontend");
fs::create_dir_all(&frontend).unwrap();
fs::write(
frontend.join("package.json"),
r#"{"devDependencies":{"vitest":"^1.0.0"}}"#,
)
.unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("cargo test"),
"Rust + frontend should include cargo test"
);
assert!(
script.contains("vitest run"),
"Rust + frontend should include vitest run"
);
assert!(
script.contains("cd frontend"),
"Rust + frontend should cd into frontend"
);
}
#[test]
fn detect_script_test_client_subdir_detected() {
let dir = tempdir().unwrap();
let client = dir.path().join("client");
fs::create_dir_all(&client).unwrap();
fs::write(
client.join("package.json"),
r#"{"scripts":{"test":"jest"}}"#,
)
.unwrap();
let script = detect_script_test(dir.path());
assert!(
script.contains("cd client"),
"client/ subdir should also be detected"
);
}
#[test] #[test]
fn detect_script_test_output_starts_with_shebang() { fn detect_script_test_output_starts_with_shebang() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();