const BINARY: &str = "borp";
const DEFAULT_PATTERN: &str = "**/*.test.{js,mjs,cjs,ts,mts,cts}";
const PATTERN_FLAGS: &[&str] = &["-p", "--pattern"];
const PATTERN_PREFIX: &str = "--pattern=";
const MODULE_VALUE_FLAGS: &[&str] = &["-r", "--reporter"];
const OTHER_VALUE_FLAGS: &[&str] = &[
"-c",
"--concurrency",
"-t",
"--timeout",
"-X",
"--coverage-exclude",
"-i",
"--ignore",
"--lines",
"--branches",
"--functions",
"--statements",
];
pub(super) fn is_borp(binary: &str) -> bool {
binary.rsplit('/').next() == Some(BINARY)
}
struct BorpArgs<'a> {
positionals: Vec<&'a str>,
pattern: Option<&'a str>,
other_values: Vec<&'a str>,
}
impl<'a> BorpArgs<'a> {
fn parse(args: &[&'a str]) -> Self {
let mut parsed = Self {
positionals: Vec::new(),
pattern: None,
other_values: Vec::new(),
};
let mut idx = 0;
while let Some(&token) = args.get(idx) {
idx += 1;
if token == "--" {
parsed
.positionals
.extend(args.get(idx..).unwrap_or_default().iter().copied());
break;
}
if let Some(value) = token.strip_prefix(PATTERN_PREFIX) {
parsed.pattern = Some(value);
} else if PATTERN_FLAGS.contains(&token) {
parsed.pattern = args.get(idx).copied();
idx += 1;
} else if OTHER_VALUE_FLAGS.contains(&token) {
parsed.other_values.extend(args.get(idx).copied());
idx += 1;
} else if MODULE_VALUE_FLAGS.contains(&token) {
idx += 1;
} else if !token.starts_with('-') {
parsed.positionals.push(token);
}
}
parsed
}
fn test_files(&self) -> Vec<String> {
if !self.positionals.is_empty() {
return self
.positionals
.iter()
.filter(|token| !token.starts_with('!'))
.map(|token| (*token).to_string())
.collect();
}
vec![self.pattern.unwrap_or(DEFAULT_PATTERN).to_string()]
}
fn is_unused_value(&self, value: &str) -> bool {
self.other_values.contains(&value)
|| (!self.positionals.is_empty() && self.pattern == Some(value))
}
}
pub(super) fn test_files(args: &[&str]) -> Vec<String> {
BorpArgs::parse(args).test_files()
}
pub(super) fn with_test_files(args: &[&str], mut file_args: Vec<String>) -> Vec<String> {
let parsed = BorpArgs::parse(args);
file_args.retain(|file| !file.starts_with('!') && !parsed.is_unused_value(file));
for file in parsed.test_files() {
if !file_args.contains(&file) {
file_args.push(file);
}
}
file_args
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_binary_matches_by_file_name() {
assert!(is_borp("borp"));
assert!(is_borp("./node_modules/.bin/borp"));
assert!(!is_borp("borpx"));
assert!(!is_borp("node"));
}
#[test]
fn positional_arguments_win_over_the_pattern() {
assert_eq!(
test_files(&["-p", "src/**/*.js", "test/a.test.js", "!test/b.test.js"]),
vec!["test/a.test.js"]
);
assert_eq!(
test_files(&["--", "test/a.test.js"]),
vec!["test/a.test.js"]
);
}
#[test]
fn the_last_pattern_wins() {
assert_eq!(
test_files(&["-p", "a/*.js", "--pattern=b/*.js"]),
vec!["b/*.js"]
);
}
#[test]
fn a_command_without_files_gets_the_default_pattern() {
assert_eq!(
test_files(&["--coverage", "-c", "1", "-r", "spec"]),
vec![DEFAULT_PATTERN]
);
}
}