JavaScript: Add runnable tests (#12118)

Remco Smits created

https://github.com/zed-industries/zed/assets/62463826/2912c940-bd00-483d-9ce7-df1a2539560a


Release Notes:

- Added runnable tests for JavaScript & Typescript files.
- Added task to run selected javascript code.

Change summary

crates/languages/src/javascript/outline.scm   | 11 +++++++
crates/languages/src/javascript/runnables.scm | 14 ++++++++++
crates/languages/src/lib.rs                   |  7 +++-
crates/languages/src/typescript.rs            | 29 +++++++++++++++++++++
crates/languages/src/typescript/outline.scm   | 11 +++++++
crates/languages/src/typescript/runnables.scm | 14 ++++++++++
6 files changed, 84 insertions(+), 2 deletions(-)

Detailed changes

crates/languages/src/javascript/outline.scm 🔗

@@ -60,3 +60,14 @@
         (accessibility_modifier)
     ]* @context
     name: (_) @name) @item
+
+; Add support for (node:test, bun:test and Jest) runnable
+(call_expression
+    function: (_) @context
+    (#any-of? @context "it" "test" "describe")
+    arguments: (
+        arguments . (string
+            (string_fragment) @name
+        )
+    )
+) @item

crates/languages/src/javascript/runnables.scm 🔗

@@ -0,0 +1,14 @@
+; Add support for (node:test, bun:test and Jest) runnable
+; Function expression that has `it`, `test` or `describe` as the function name
+(
+    (call_expression
+        function: (_) @_name
+        (#any-of? @_name "it" "test" "describe")
+        arguments: (
+            arguments . (string
+                (string_fragment) @run
+            )
+        )
+    ) @js-test
+    (#set! tag js-test)
+)

crates/languages/src/lib.rs 🔗

@@ -7,6 +7,7 @@ use rust_embed::RustEmbed;
 use settings::SettingsStore;
 use smol::stream::StreamExt;
 use std::{str, sync::Arc};
+use typescript::typescript_task_context;
 use util::{asset_str, ResultExt};
 
 use crate::{
@@ -146,13 +147,15 @@ pub fn init(
         "typescript",
         vec![Arc::new(typescript::TypeScriptLspAdapter::new(
             node_runtime.clone()
-        ))]
+        ))],
+        typescript_task_context()
     );
     language!(
         "javascript",
         vec![Arc::new(typescript::TypeScriptLspAdapter::new(
             node_runtime.clone()
-        ))]
+        ))],
+        typescript_task_context()
     );
     language!(
         "jsdoc",

crates/languages/src/typescript.rs 🔗

@@ -9,6 +9,7 @@ use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
 use lsp::{CodeActionKind, LanguageServerBinary};
 use node_runtime::NodeRuntime;
 use project::project_settings::ProjectSettings;
+use project::ContextProviderWithTasks;
 use serde_json::{json, Value};
 use settings::Settings;
 use smol::{fs, io::BufReader, stream::StreamExt};
@@ -18,8 +19,36 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
+use task::{TaskTemplate, TaskTemplates, VariableName};
 use util::{fs::remove_matching, maybe, ResultExt};
 
+pub(super) fn typescript_task_context() -> ContextProviderWithTasks {
+    ContextProviderWithTasks::new(TaskTemplates(vec![
+        TaskTemplate {
+            label: "jest file test".to_owned(),
+            command: "npx jest".to_owned(),
+            args: vec![VariableName::File.template_value()],
+            ..TaskTemplate::default()
+        },
+        TaskTemplate {
+            label: "jest test $ZED_SYMBOL".to_owned(),
+            command: "npx jest".to_owned(),
+            args: vec![
+                VariableName::Symbol.template_value(),
+                VariableName::File.template_value(),
+            ],
+            tags: vec!["ts-test".into(), "js-test".into()],
+            ..TaskTemplate::default()
+        },
+        TaskTemplate {
+            label: "execute selection $ZED_SELECTED_TEXT".to_owned(),
+            command: "node".to_owned(),
+            args: vec!["-e".into(), VariableName::SelectedText.template_value()],
+            ..TaskTemplate::default()
+        },
+    ]))
+}
+
 fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
     vec![server_path.into(), "--stdio".into()]
 }

crates/languages/src/typescript/outline.scm 🔗

@@ -63,3 +63,14 @@
         (accessibility_modifier)
     ]* @context
     name: (_) @name) @item
+
+; Add support for (node:test, bun:test and Jest) runnable
+(call_expression
+    function: (_) @context
+    (#any-of? @context "it" "test" "describe")
+    arguments: (
+        arguments . (string
+            (string_fragment) @name
+        )
+    )
+) @item

crates/languages/src/typescript/runnables.scm 🔗

@@ -0,0 +1,14 @@
+; Add support for (node:test, bun:test and Jest) runnable
+; Function expression that has `it`, `test` or `describe` as the function name
+(
+    (call_expression
+        function: (_) @_name
+        (#any-of? @_name "it" "test" "describe")
+        arguments: (
+            arguments . (string
+                (string_fragment) @run
+            )
+        )
+    ) @ts-test
+    (#set! tag ts-test)
+)