typescript: Runners support for bun:test & node:test (#39238)
Closes #21132 Release Notes: - JavaScript/TypeScript: Added support for detecting `node:test` and `bun:test` test runners
This commit is contained in:
@@ -15,6 +15,8 @@ pub struct PackageJsonData {
|
||||
pub mocha_package_path: Option<Arc<Path>>,
|
||||
pub vitest_package_path: Option<Arc<Path>>,
|
||||
pub jasmine_package_path: Option<Arc<Path>>,
|
||||
pub bun_package_path: Option<Arc<Path>>,
|
||||
pub node_package_path: Option<Arc<Path>>,
|
||||
pub scripts: BTreeSet<(Arc<Path>, String)>,
|
||||
pub package_manager: Option<&'static str>,
|
||||
}
|
||||
@@ -35,6 +37,8 @@ impl PackageJsonData {
|
||||
let mut mocha_package_path = None;
|
||||
let mut vitest_package_path = None;
|
||||
let mut jasmine_package_path = None;
|
||||
let mut bun_package_path = None;
|
||||
let mut node_package_path = None;
|
||||
if let Some(Value::Object(dependencies)) = package_json.get("devDependencies") {
|
||||
if dependencies.contains_key("jest") {
|
||||
jest_package_path.get_or_insert_with(|| path.clone());
|
||||
@@ -48,6 +52,12 @@ impl PackageJsonData {
|
||||
if dependencies.contains_key("jasmine") {
|
||||
jasmine_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
if dependencies.contains_key("@types/bun") {
|
||||
bun_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
if dependencies.contains_key("@types/node") {
|
||||
node_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
}
|
||||
if let Some(Value::Object(dev_dependencies)) = package_json.get("dependencies") {
|
||||
if dev_dependencies.contains_key("jest") {
|
||||
@@ -62,6 +72,12 @@ impl PackageJsonData {
|
||||
if dev_dependencies.contains_key("jasmine") {
|
||||
jasmine_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
if dev_dependencies.contains_key("@types/bun") {
|
||||
bun_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
if dev_dependencies.contains_key("@types/node") {
|
||||
node_package_path.get_or_insert_with(|| path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let package_manager = package_json
|
||||
@@ -74,6 +90,8 @@ impl PackageJsonData {
|
||||
Some("yarn")
|
||||
} else if value.starts_with("npm") {
|
||||
Some("npm")
|
||||
} else if value.starts_with("bun") {
|
||||
Some("bun")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -84,6 +102,8 @@ impl PackageJsonData {
|
||||
mocha_package_path,
|
||||
vitest_package_path,
|
||||
jasmine_package_path,
|
||||
bun_package_path,
|
||||
node_package_path,
|
||||
scripts,
|
||||
package_manager,
|
||||
}
|
||||
@@ -100,6 +120,8 @@ impl PackageJsonData {
|
||||
.jasmine_package_path
|
||||
.take()
|
||||
.or(other.jasmine_package_path);
|
||||
self.bun_package_path = self.bun_package_path.take().or(other.bun_package_path);
|
||||
self.node_package_path = self.node_package_path.take().or(other.node_package_path);
|
||||
self.scripts.extend(other.scripts);
|
||||
self.package_manager = self.package_manager.or(other.package_manager);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ const TYPESCRIPT_VITEST_PACKAGE_PATH_VARIABLE: VariableName =
|
||||
const TYPESCRIPT_JASMINE_PACKAGE_PATH_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("TYPESCRIPT_JASMINE_PACKAGE_PATH"));
|
||||
|
||||
const TYPESCRIPT_BUN_PACKAGE_PATH_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("TYPESCRIPT_BUN_PACKAGE_PATH"));
|
||||
|
||||
const TYPESCRIPT_NODE_PACKAGE_PATH_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("TYPESCRIPT_NODE_PACKAGE_PATH"));
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PackageJsonContents(Arc<RwLock<HashMap<PathBuf, PackageJson>>>);
|
||||
|
||||
@@ -220,6 +226,65 @@ impl PackageJsonData {
|
||||
});
|
||||
}
|
||||
|
||||
if self.bun_package_path.is_some() {
|
||||
task_templates.0.push(TaskTemplate {
|
||||
label: format!("{} file test", "bun test".to_owned()),
|
||||
command: "bun".to_owned(),
|
||||
args: vec!["test".to_owned(), VariableName::File.template_value()],
|
||||
cwd: Some(TYPESCRIPT_BUN_PACKAGE_PATH_VARIABLE.template_value()),
|
||||
..TaskTemplate::default()
|
||||
});
|
||||
task_templates.0.push(TaskTemplate {
|
||||
label: format!("bun test {}", VariableName::Symbol.template_value(),),
|
||||
command: "bun".to_owned(),
|
||||
args: vec![
|
||||
"test".to_owned(),
|
||||
"--test-name-pattern".to_owned(),
|
||||
format!("\"{}\"", VariableName::Symbol.template_value()),
|
||||
VariableName::File.template_value(),
|
||||
],
|
||||
tags: vec![
|
||||
"ts-test".to_owned(),
|
||||
"js-test".to_owned(),
|
||||
"tsx-test".to_owned(),
|
||||
],
|
||||
cwd: Some(TYPESCRIPT_BUN_PACKAGE_PATH_VARIABLE.template_value()),
|
||||
..TaskTemplate::default()
|
||||
});
|
||||
}
|
||||
|
||||
if self.node_package_path.is_some() {
|
||||
task_templates.0.push(TaskTemplate {
|
||||
label: format!("{} file test", "node test".to_owned()),
|
||||
command: "node".to_owned(),
|
||||
args: vec!["--test".to_owned(), VariableName::File.template_value()],
|
||||
tags: vec![
|
||||
"ts-test".to_owned(),
|
||||
"js-test".to_owned(),
|
||||
"tsx-test".to_owned(),
|
||||
],
|
||||
cwd: Some(TYPESCRIPT_NODE_PACKAGE_PATH_VARIABLE.template_value()),
|
||||
..TaskTemplate::default()
|
||||
});
|
||||
task_templates.0.push(TaskTemplate {
|
||||
label: format!("node test {}", VariableName::Symbol.template_value()),
|
||||
command: "node".to_owned(),
|
||||
args: vec![
|
||||
"--test".to_owned(),
|
||||
"--test-name-pattern".to_owned(),
|
||||
format!("\"{}\"", VariableName::Symbol.template_value()),
|
||||
VariableName::File.template_value(),
|
||||
],
|
||||
tags: vec![
|
||||
"ts-test".to_owned(),
|
||||
"js-test".to_owned(),
|
||||
"tsx-test".to_owned(),
|
||||
],
|
||||
cwd: Some(TYPESCRIPT_NODE_PACKAGE_PATH_VARIABLE.template_value()),
|
||||
..TaskTemplate::default()
|
||||
});
|
||||
}
|
||||
|
||||
let script_name_counts: HashMap<_, usize> =
|
||||
self.scripts
|
||||
.iter()
|
||||
@@ -493,6 +558,26 @@ impl ContextProvider for TypeScriptContextProvider {
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = package_json_data.bun_package_path {
|
||||
vars.insert(
|
||||
TYPESCRIPT_BUN_PACKAGE_PATH_VARIABLE,
|
||||
path.parent()
|
||||
.unwrap_or(Path::new(""))
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = package_json_data.node_package_path {
|
||||
vars.insert(
|
||||
TYPESCRIPT_NODE_PACKAGE_PATH_VARIABLE,
|
||||
path.parent()
|
||||
.unwrap_or(Path::new(""))
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(vars)
|
||||
@@ -1178,6 +1263,8 @@ mod tests {
|
||||
mocha_package_path: Some(Path::new(path!("/root/package.json")).into()),
|
||||
vitest_package_path: Some(Path::new(path!("/root/sub/package.json")).into()),
|
||||
jasmine_package_path: None,
|
||||
bun_package_path: None,
|
||||
node_package_path: None,
|
||||
scripts: [
|
||||
(
|
||||
Path::new(path!("/root/package.json")).into(),
|
||||
@@ -1231,6 +1318,7 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escaping_name() {
|
||||
let cases = [
|
||||
@@ -1264,4 +1352,110 @@ mod tests {
|
||||
assert_eq!(replace_test_name_parameters(input), expected);
|
||||
}
|
||||
}
|
||||
|
||||
// The order of test runner tasks is based on inferred user preference:
|
||||
// 1. Dedicated test runners (e.g., Jest, Vitest, Mocha, Jasmine) are prioritized.
|
||||
// 2. Bun's built-in test runner (`bun test`) comes next.
|
||||
// 3. Node.js's built-in test runner (`node --test`) is last.
|
||||
// This hierarchy assumes that if a dedicated test framework is installed, it is the
|
||||
// preferred testing mechanism. Between runtime-specific options, `bun test` is
|
||||
// typically preferred over `node --test` when @types/bun is present.
|
||||
#[gpui::test]
|
||||
async fn test_task_ordering_with_multiple_test_runners(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
settings::init(cx);
|
||||
Project::init_settings(cx);
|
||||
language_settings::init(cx);
|
||||
});
|
||||
|
||||
// Test case with all test runners present
|
||||
let package_json_all_runners = json!({
|
||||
"devDependencies": {
|
||||
"@types/bun": "1.0.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"jest": "29.0.0",
|
||||
"mocha": "10.0.0",
|
||||
"vitest": "1.0.0",
|
||||
"jasmine": "5.0.0",
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let fs = FakeFs::new(executor);
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"package.json": package_json_all_runners,
|
||||
"file.js": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let provider = TypeScriptContextProvider::new(fs.clone());
|
||||
|
||||
let package_json_data = cx
|
||||
.update(|cx| {
|
||||
provider.combined_package_json_data(
|
||||
fs.clone(),
|
||||
path!("/root").as_ref(),
|
||||
rel_path("file.js"),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(package_json_data.jest_package_path.is_some());
|
||||
assert!(package_json_data.mocha_package_path.is_some());
|
||||
assert!(package_json_data.vitest_package_path.is_some());
|
||||
assert!(package_json_data.jasmine_package_path.is_some());
|
||||
assert!(package_json_data.bun_package_path.is_some());
|
||||
assert!(package_json_data.node_package_path.is_some());
|
||||
|
||||
let mut task_templates = TaskTemplates::default();
|
||||
package_json_data.fill_task_templates(&mut task_templates);
|
||||
|
||||
let test_tasks: Vec<_> = task_templates
|
||||
.0
|
||||
.iter()
|
||||
.filter(|template| {
|
||||
template.tags.contains(&"ts-test".to_owned())
|
||||
|| template.tags.contains(&"js-test".to_owned())
|
||||
})
|
||||
.map(|template| &template.label)
|
||||
.collect();
|
||||
|
||||
let node_test_index = test_tasks
|
||||
.iter()
|
||||
.position(|label| label.contains("node test"));
|
||||
let jest_test_index = test_tasks.iter().position(|label| label.contains("jest"));
|
||||
let bun_test_index = test_tasks
|
||||
.iter()
|
||||
.position(|label| label.contains("bun test"));
|
||||
|
||||
assert!(
|
||||
node_test_index.is_some(),
|
||||
"Node test tasks should be present"
|
||||
);
|
||||
assert!(
|
||||
jest_test_index.is_some(),
|
||||
"Jest test tasks should be present"
|
||||
);
|
||||
assert!(bun_test_index.is_some(), "Bun test tasks should be present");
|
||||
|
||||
assert!(
|
||||
jest_test_index.unwrap() < bun_test_index.unwrap(),
|
||||
"Jest should come before Bun"
|
||||
);
|
||||
assert!(
|
||||
bun_test_index.unwrap() < node_test_index.unwrap(),
|
||||
"Bun should come before Node"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,10 +179,14 @@ Zed supports debugging JavaScript code out of the box.
|
||||
The following can be debugged without writing additional configuration:
|
||||
|
||||
- Tasks from `package.json`
|
||||
- Tests written using several popular frameworks (Jest, Mocha, Vitest, Jasmine)
|
||||
- Tests written using several popular frameworks (Jest, Mocha, Vitest, Jasmine, Bun, Node)
|
||||
|
||||
Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list of these predefined debug tasks.
|
||||
|
||||
> **Note:** Bun test is automatically detected when `@types/bun` is present in `package.json`.
|
||||
>
|
||||
> **Note:** Node test is automatically detected when `@types/node` is present in `package.json` (requires Node.js 20+).
|
||||
|
||||
As for all languages, configurations from `.vscode/launch.json` are also available for debugging in Zed.
|
||||
|
||||
If your use-case isn't covered by any of these, you can take full control by adding debug configurations to `.zed/debug.json`. See below for example configurations.
|
||||
|
||||
@@ -162,10 +162,14 @@ Zed supports debugging TypeScript code out of the box.
|
||||
The following can be debugged without writing additional configuration:
|
||||
|
||||
- Tasks from `package.json`
|
||||
- Tests written using several popular frameworks (Jest, Mocha, Vitest, Jasmine)
|
||||
- Tests written using several popular frameworks (Jest, Mocha, Vitest, Jasmine, Bun, Node)
|
||||
|
||||
Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list of these predefined debug tasks.
|
||||
|
||||
> **Note:** Bun test is automatically detected when `@types/bun` is present in `package.json`.
|
||||
>
|
||||
> **Note:** Node test is automatically detected when `@types/node` is present in `package.json` (requires Node.js 20+).
|
||||
|
||||
As for all languages, configurations from `.vscode/launch.json` are also available for debugging in Zed.
|
||||
|
||||
If your use-case isn't covered by any of these, you can take full control by adding debug configurations to `.zed/debug.json`. See below for example configurations.
|
||||
|
||||
Reference in New Issue
Block a user