diff --git a/src/cmds/js/vitest_cmd.rs b/src/cmds/js/vitest_cmd.rs index 1f59b28de9..fd6014c044 100644 --- a/src/cmds/js/vitest_cmd.rs +++ b/src/cmds/js/vitest_cmd.rs @@ -5,7 +5,6 @@ use regex::Regex; use serde::Deserialize; use std::sync::LazyLock; -use crate::Commands; use crate::core::stream::exec_capture; use crate::core::tracking; use crate::core::utils::{package_manager_exec, strip_ansi}; @@ -199,44 +198,75 @@ fn extract_failures_regex(output: &str) -> Vec { failures } -pub fn run_test(command: &Commands, args: &[String], verbose: u8) -> Result { - let timer = tracking::TimedExecution::start(); - let mut passthrough_requested = false; - - let (framework, mut cmd) = match command { - Commands::Vitest { .. } => { - let framework = "vitest"; - let mut cmd = package_manager_exec(framework); - let effective_args = build_vitest_effective_args(args); - passthrough_requested = effective_args.passthrough; - cmd.args(effective_args.args); - (framework, cmd) - } - Commands::Jest { .. } => { - let framework = "jest"; - let mut cmd = package_manager_exec(framework); - cmd - // Force non-watch mode - .arg("--no-watch") - // Enable JSON structured output - .arg("--json"); - (framework, cmd) - } - _ => unreachable!(), - }; +pub fn run_vitest(args: &[String], verbose: u8) -> Result { + run_framework_test(vitest_invocation(args), verbose) +} - if !matches!(command, Commands::Vitest { .. }) { - for arg in args { - if arg == "run" - || arg.starts_with("--json") - || arg.starts_with("--reporter") - || arg.starts_with("--watch") - { - continue; - } - cmd.arg(arg); +pub fn run_jest(args: &[String], verbose: u8) -> Result { + run_framework_test(jest_invocation(args), verbose) +} + +/// What `rtk vitest` or `rtk jest` spawns: the tool, the arguments it gets, +/// and whether the user's own reporter output is shown instead of parsed. +struct TestInvocation { + framework: &'static str, + args: Vec, + passthrough: bool, +} + +fn vitest_invocation(args: &[String]) -> TestInvocation { + let passthrough = has_explicit_vitest_reporter(args); + let mut effective = vec!["run".to_string()]; + + if !passthrough { + effective.push("--reporter=json".to_string()); + } + + for arg in args { + if should_skip_vitest_arg(arg) { + continue; } + effective.push(arg.clone()); + } + + TestInvocation { + framework: "vitest", + args: effective, + passthrough, } +} + +fn jest_invocation(args: &[String]) -> TestInvocation { + let mut effective = vec![ + // Force non-watch mode + "--no-watch".to_string(), + // Enable JSON structured output + "--json".to_string(), + ]; + effective.extend( + args.iter() + .filter(|arg| !should_skip_jest_arg(arg)) + .cloned(), + ); + TestInvocation { + framework: "jest", + args: effective, + passthrough: false, + } +} + +fn should_skip_jest_arg(arg: &str) -> bool { + arg == "run" + || arg.starts_with("--json") + || arg.starts_with("--reporter") + || arg.starts_with("--watch") +} + +fn run_framework_test(invocation: TestInvocation, verbose: u8) -> Result { + let timer = tracking::TimedExecution::start(); + let framework = invocation.framework; + let mut cmd = package_manager_exec(framework); + cmd.args(&invocation.args); let result = exec_capture(&mut cmd).context(format!("Failed to run {}", framework))?; let combined = result.combined(); @@ -245,7 +275,7 @@ pub fn run_test(command: &Commands, args: &[String], verbose: u8) -> Result framework, &result.stdout, &combined, - passthrough_requested, + invocation.passthrough, verbose, ); let tee_label = format!("{}_run", framework); @@ -266,11 +296,6 @@ pub fn run_test(command: &Commands, args: &[String], verbose: u8) -> Result Ok(0) } -struct EffectiveVitestArgs { - args: Vec, - passthrough: bool, -} - struct FormattedTestOutput { text: String, truncated: bool, @@ -292,27 +317,6 @@ impl FormattedTestOutput { } } -fn build_vitest_effective_args(args: &[String]) -> EffectiveVitestArgs { - let passthrough = has_explicit_vitest_reporter(args); - let mut effective = vec!["run".to_string()]; - - if !passthrough { - effective.push("--reporter=json".to_string()); - } - - for arg in args { - if should_skip_vitest_arg(arg) { - continue; - } - effective.push(arg.clone()); - } - - EffectiveVitestArgs { - args: effective, - passthrough, - } -} - fn has_explicit_vitest_reporter(args: &[String]) -> bool { args.iter() .any(|arg| arg == "--reporter" || arg.starts_with("--reporter=")) @@ -418,6 +422,43 @@ mod tests { values.iter().map(|value| value.to_string()).collect() } + #[test] + fn test_vitest_invocation_runs_vitest() { + let invocation = vitest_invocation(&args(&["src/a.test.ts"])); + + assert_eq!(invocation.framework, "vitest"); + } + + #[test] + fn test_jest_invocation_runs_jest_once_with_json_output() { + let invocation = jest_invocation(&args(&["src/a.test.js"])); + + assert_eq!(invocation.framework, "jest"); + assert_eq!( + invocation.args, + args(&["--no-watch", "--json", "src/a.test.js"]) + ); + assert!(!invocation.passthrough); + } + + #[test] + fn test_jest_invocation_drops_run_json_reporter_and_watch_args() { + let invocation = jest_invocation(&args(&[ + "run", + "--json", + "--reporters=default", + "--watch", + "-t", + "adds", + "src/a.test.js", + ])); + + assert_eq!( + invocation.args, + args(&["--no-watch", "--json", "-t", "adds", "src/a.test.js"]) + ); + } + #[test] fn test_vitest_parser_json() { let json = r#"{ @@ -525,8 +566,7 @@ Scope: all 6 workspace projects #[test] fn test_vitest_effective_args_inject_json_reporter_by_default() { - let effective = - build_vitest_effective_args(&args(&["run", "constants.test.ts", "--watch"])); + let effective = vitest_invocation(&args(&["run", "constants.test.ts", "--watch"])); assert!(!effective.passthrough); assert_eq!( @@ -537,8 +577,7 @@ Scope: all 6 workspace projects #[test] fn test_vitest_effective_args_preserve_explicit_reporter_equals() { - let effective = - build_vitest_effective_args(&args(&["constants.test.ts", "--reporter=verbose"])); + let effective = vitest_invocation(&args(&["constants.test.ts", "--reporter=verbose"])); assert!(effective.passthrough); assert_eq!( @@ -549,7 +588,7 @@ Scope: all 6 workspace projects #[test] fn test_vitest_effective_args_preserve_explicit_reporter_value() { - let effective = build_vitest_effective_args(&args(&[ + let effective = vitest_invocation(&args(&[ "run", "constants.test.ts", "--reporter", diff --git a/src/core/tracking.rs b/src/core/tracking.rs index e084f9b826..687f0a51e6 100644 --- a/src/core/tracking.rs +++ b/src/core/tracking.rs @@ -1814,7 +1814,7 @@ pub fn estimate_tokens_from_len(len: usize) -> usize { /// /// # Examples /// -/// ```no_run +/// ```ignore /// use rtk::tracking::TimedExecution; /// /// let timer = TimedExecution::start(); diff --git a/src/core/utils.rs b/src/core/utils.rs index 5dd8679a83..f63d1bcdcc 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -188,7 +188,7 @@ pub fn format_cpt(cpt: f64) -> String { /// ``` /// use rtk::utils::join_with_overflow; /// let items = vec!["a".to_string(), "b".to_string()]; -/// assert_eq!(join_with_overflow(&items, 5, 3, "items"), "a\nb\n... +2 more items"); +/// assert_eq!(join_with_overflow(&items, 5, 3, "items"), "a\nb\n… +2 more items"); /// assert_eq!(join_with_overflow(&items, 2, 3, "items"), "a\nb"); /// ``` pub fn join_with_overflow(items: &[String], total: usize, max: usize, label: &str) -> String { diff --git a/src/main.rs b/src/main.rs index 0604e6d027..5d3e598c5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2740,9 +2740,8 @@ fn run_cli() -> Result { 0 } - Commands::Jest { ref args } | Commands::Vitest { ref args } => { - vitest_cmd::run_test(&cli.command, args, cli.verbose)? - } + Commands::Vitest { ref args } => vitest_cmd::run_vitest(args, cli.verbose)?, + Commands::Jest { ref args } => vitest_cmd::run_jest(args, cli.verbose)?, Commands::Ctest { args } => ctest_cmd::run(&args, cli.verbose)?, @@ -3444,6 +3443,7 @@ fn is_operational_command(cmd: &Commands) -> bool { | Commands::Rg { .. } | Commands::AstGrep { .. } | Commands::Wget { .. } + | Commands::Jest { .. } | Commands::Vitest { .. } | Commands::Ctest { .. } | Commands::Prisma { .. } @@ -3489,6 +3489,15 @@ mod tests { use clap::Parser; use std::cell::Cell; + #[test] + fn test_jest_and_vitest_get_the_hook_integrity_check() { + for framework in ["jest", "vitest"] { + let cli = Cli::try_parse_from(["rtk", framework, "src/a.test.js"]) + .expect("rtk parses"); + assert!(is_operational_command(&cli.command), "{framework}"); + } + } + #[test] fn test_git_commit_single_message() { let cli = Cli::try_parse_from(["rtk", "git", "commit", "-m", "fix: typo"]).unwrap();