From 51668f3cddaecc52c3c973bbf2bf7cc70415ac60 Mon Sep 17 00:00:00 2001 From: Adrien Eppling Date: Fri, 12 Jun 2026 18:27:45 +0200 Subject: [PATCH 1/2] refactor(js): decouple vitest/jest runner from CLI Commands enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run_test(&Commands) → run_vitest/run_jest over a shared helper, dropping the module dependency on the CLI Commands enum and the unreachable!() arm. Also fix two doctests that surface once the crate is bundled as a lib: correct join_with_overflow (Unicode ellipsis) and mark the TimedExecution example ignore. --- src/cmds/js/vitest_cmd.rs | 48 ++++++++++++++++++--------------------- src/core/tracking.rs | 2 +- src/core/utils.rs | 2 +- src/main.rs | 5 ++-- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/cmds/js/vitest_cmd.rs b/src/cmds/js/vitest_cmd.rs index fc80bff696..8fb4d3c403 100644 --- a/src/cmds/js/vitest_cmd.rs +++ b/src/cmds/js/vitest_cmd.rs @@ -11,7 +11,7 @@ use crate::parser::{ emit_degradation_warning, emit_passthrough_warning, extract_json_object, truncate_passthrough, FormatMode, OutputParser, ParseResult, TestFailure, TestResult, TokenFormatter, }; -use crate::Commands; +use std::process::Command; /// Vitest JSON output structures (tool-specific format) #[derive(Debug, Deserialize)] @@ -204,32 +204,28 @@ fn extract_failures_regex(output: &str) -> Vec { failures } -pub fn run_test(command: &Commands, args: &[String], verbose: u8) -> Result { - let timer = tracking::TimedExecution::start(); +pub fn run_vitest(args: &[String], verbose: u8) -> Result { + let mut cmd = package_manager_exec("vitest"); + cmd + // Force non-watch mode + .arg("run") + // Enable JSON structured output + .arg("--reporter=json"); + run_framework_test("vitest", cmd, args, verbose) +} - let (framework, mut cmd) = match command { - Commands::Vitest { .. } => { - let framework = "vitest"; - let mut cmd = package_manager_exec(framework); - cmd - // Force non-watch mode - .arg("run") - // Enable JSON structured output - .arg("--reporter=json"); - (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_jest(args: &[String], verbose: u8) -> Result { + let mut cmd = package_manager_exec("jest"); + cmd + // Force non-watch mode + .arg("--no-watch") + // Enable JSON structured output + .arg("--json"); + run_framework_test("jest", cmd, args, verbose) +} + +fn run_framework_test(framework: &str, mut cmd: Command, args: &[String], verbose: u8) -> Result { + let timer = tracking::TimedExecution::start(); for arg in args { if arg == "run" diff --git a/src/core/tracking.rs b/src/core/tracking.rs index 359e4f6885..7ae772153e 100644 --- a/src/core/tracking.rs +++ b/src/core/tracking.rs @@ -1294,7 +1294,7 @@ pub fn estimate_tokens(text: &str) -> 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 a3bc84fe00..a3bfb05965 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -141,7 +141,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 a5bde544a3..60329c8681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1990,9 +1990,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::Prisma { command } => match command { PrismaCommands::Generate { args } => { From 58d25021e8c3794ee3dbb39bda35d5022ec21703 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Sat, 3 Oct 2026 17:34:05 +0200 Subject: [PATCH 2/2] fix(jest): run the hook integrity check for rtk jest, and pin what vitest and jest spawn rtk jest was missing from is_operational_command, so a tampered legacy hook that stops rtk vitest let rtk jest run. run_vitest and run_jest now build a TestInvocation (framework, arguments, passthrough) that run_framework_test executes, so tests can assert the tool each one runs, its arguments and whether reporter output passes through. Co-Authored-By: Claude Opus 5.5 --- src/cmds/js/vitest_cmd.rs | 155 ++++++++++++++++++++++++-------------- src/main.rs | 10 +++ 2 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/cmds/js/vitest_cmd.rs b/src/cmds/js/vitest_cmd.rs index 437a532f21..fd6014c044 100644 --- a/src/cmds/js/vitest_cmd.rs +++ b/src/cmds/js/vitest_cmd.rs @@ -3,7 +3,6 @@ use anyhow::{Context, Result}; use regex::Regex; use serde::Deserialize; -use std::process::Command; use std::sync::LazyLock; use crate::core::stream::exec_capture; @@ -200,39 +199,74 @@ fn extract_failures_regex(output: &str) -> Vec { } pub fn run_vitest(args: &[String], verbose: u8) -> Result { - let mut cmd = package_manager_exec("vitest"); - let effective_args = build_vitest_effective_args(args); - cmd.args(effective_args.args); - run_framework_test("vitest", cmd, effective_args.passthrough, verbose) + run_framework_test(vitest_invocation(args), verbose) } pub fn run_jest(args: &[String], verbose: u8) -> Result { - let mut cmd = package_manager_exec("jest"); - cmd - // Force non-watch mode - .arg("--no-watch") - // Enable JSON structured output - .arg("--json"); + 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 arg == "run" - || arg.starts_with("--json") - || arg.starts_with("--reporter") - || arg.starts_with("--watch") - { + if should_skip_vitest_arg(arg) { continue; } - cmd.arg(arg); + effective.push(arg.clone()); + } + + TestInvocation { + framework: "vitest", + args: effective, + passthrough, } - run_framework_test("jest", cmd, false, verbose) } -fn run_framework_test( - framework: &str, - mut cmd: Command, - passthrough_requested: bool, - verbose: u8, -) -> Result { +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(); @@ -241,7 +275,7 @@ fn run_framework_test( framework, &result.stdout, &combined, - passthrough_requested, + invocation.passthrough, verbose, ); let tee_label = format!("{}_run", framework); @@ -262,11 +296,6 @@ fn run_framework_test( Ok(0) } -struct EffectiveVitestArgs { - args: Vec, - passthrough: bool, -} - struct FormattedTestOutput { text: String, truncated: bool, @@ -288,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=")) @@ -414,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#"{ @@ -521,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!( @@ -533,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!( @@ -545,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/main.rs b/src/main.rs index c09ea70675..5d3e598c5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3443,6 +3443,7 @@ fn is_operational_command(cmd: &Commands) -> bool { | Commands::Rg { .. } | Commands::AstGrep { .. } | Commands::Wget { .. } + | Commands::Jest { .. } | Commands::Vitest { .. } | Commands::Ctest { .. } | Commands::Prisma { .. } @@ -3488,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();