// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::lsp::analysis::source_range_to_lsp_range; use super::definitions::TestModule; use deno_ast::swc::ast; use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::VisitWith; use deno_ast::SourceRangedForSpanned; use deno_ast::SourceTextInfo; use deno_core::ModuleSpecifier; use lsp::Range; use std::collections::HashMap; use std::collections::HashSet; use tower_lsp::lsp_types as lsp; /// Parse an arrow expression for any test steps and return them. fn visit_arrow( arrow_expr: &ast::ArrowExpr, parent_id: &str, text_info: &SourceTextInfo, test_module: &mut TestModule, ) { if let Some((maybe_test_context, maybe_step_var)) = parse_test_context_param(arrow_expr.params.first()) { let mut collector = TestStepCollector::new( maybe_test_context, maybe_step_var, parent_id, text_info, test_module, ); arrow_expr.body.visit_with(&mut collector); } } /// Parse a function for any test steps and return them. fn visit_fn( function: &ast::Function, parent_id: &str, text_info: &SourceTextInfo, test_module: &mut TestModule, ) { if let Some((maybe_test_context, maybe_step_var)) = parse_test_context_param(function.params.first().map(|p| &p.pat)) { let mut collector = TestStepCollector::new( maybe_test_context, maybe_step_var, parent_id, text_info, test_module, ); function.body.visit_with(&mut collector); } } /// Parse a param of a test function for the test context binding, or any /// destructuring of a `steps` method from the test context. fn parse_test_context_param( param: Option<&ast::Pat>, ) -> Option<(Option, Option)> { let mut maybe_test_context = None; let mut maybe_step_var = None; match param { // handles `(testContext)` Some(ast::Pat::Ident(binding_ident)) => { maybe_test_context = Some(binding_ident.id.sym.to_string()); } Some(ast::Pat::Object(object_pattern)) => { for prop in &object_pattern.props { match prop { ast::ObjectPatProp::KeyValue(key_value_pat_prop) => { match &key_value_pat_prop.key { // handles `({ step: s })` ast::PropName::Ident(ident) => { if ident.sym.eq("step") { if let ast::Pat::Ident(ident) = key_value_pat_prop.value.as_ref() { maybe_step_var = Some(ident.id.sym.to_string()); } break; } } // handles `({ "step": s })` ast::PropName::Str(string) => { if string.value.eq("step") { if let ast::Pat::Ident(ident) = key_value_pat_prop.value.as_ref() { maybe_step_var = Some(ident.id.sym.to_string()); } break; } } _ => (), } } // handles `({ step = something })` ast::ObjectPatProp::Assign(assign_pat_prop) if assign_pat_prop.key.sym.eq("step") => { maybe_step_var = Some("step".to_string()); break; } // handles `({ ...ctx })` ast::ObjectPatProp::Rest(rest_pat) => { if let ast::Pat::Ident(ident) = rest_pat.arg.as_ref() { maybe_test_context = Some(ident.id.sym.to_string()); } break; } _ => (), } } } _ => return None, } if maybe_test_context.is_none() && maybe_step_var.is_none() { None } else { Some((maybe_test_context, maybe_step_var)) } } /// Check a call expression of a test or test step to determine the name of the /// test or test step as well as any sub steps. fn visit_call_expr( node: &ast::CallExpr, fns: Option<&HashMap>, range: Range, parent_id: Option<&str>, text_info: &SourceTextInfo, test_module: &mut TestModule, ) { if let Some(expr) = node.args.first().map(|es| es.expr.as_ref()) { match expr { ast::Expr::Object(obj_lit) => { let mut maybe_name = None; for prop in &obj_lit.props { let ast::PropOrSpread::Prop(prop) = prop else { continue; }; let ast::Prop::KeyValue(key_value_prop) = prop.as_ref() else { continue; }; let ast::PropName::Ident(ast::Ident { sym, .. }) = &key_value_prop.key else { continue; }; if sym == "name" { match key_value_prop.value.as_ref() { // matches string literals (e.g. "test name" or // 'test name') ast::Expr::Lit(ast::Lit::Str(lit_str)) => { maybe_name = Some(lit_str.value.to_string()); } // matches template literals with only a single quasis // (e.g. `test name`) ast::Expr::Tpl(tpl) => { if tpl.quasis.len() == 1 { maybe_name = Some(tpl.quasis[0].raw.to_string()); } } _ => {} } break; } } let name = match maybe_name { Some(n) => n, None => return, }; let (id, _) = test_module.register( name, Some(range), false, parent_id.map(str::to_owned), ); for prop in &obj_lit.props { let ast::PropOrSpread::Prop(prop) = prop else { continue; }; match prop.as_ref() { ast::Prop::KeyValue(key_value_prop) => { let ast::PropName::Ident(ast::Ident { sym, .. }) = &key_value_prop.key else { continue; }; if sym == "fn" { match key_value_prop.value.as_ref() { ast::Expr::Arrow(arrow_expr) => { visit_arrow(arrow_expr, &id, text_info, test_module); } ast::Expr::Fn(fn_expr) => { visit_fn(&fn_expr.function, &id, text_info, test_module); } _ => {} } break; } } ast::Prop::Method(method_prop) => { let ast::PropName::Ident(ast::Ident { sym, .. }) = &method_prop.key else { continue; }; if sym == "fn" { visit_fn(&method_prop.function, &id, text_info, test_module); break; } } _ => {} } } } ast::Expr::Fn(fn_expr) => { if let Some(ast::Ident { sym, .. }) = fn_expr.ident.as_ref() { let name = sym.to_string(); let (id, _) = test_module.register( name, Some(range), false, parent_id.map(str::to_owned), ); visit_fn(&fn_expr.function, &id, text_info, test_module); } } ast::Expr::Lit(ast::Lit::Str(lit_str)) => { let name = lit_str.value.to_string(); let (id, _) = test_module.register( name, Some(range), false, parent_id.map(str::to_owned), ); match node.args.get(1).map(|es| es.expr.as_ref()) { Some(ast::Expr::Fn(fn_expr)) => { visit_fn(&fn_expr.function, &id, text_info, test_module); } Some(ast::Expr::Arrow(arrow_expr)) => { visit_arrow(arrow_expr, &id, text_info, test_module); } _ => {} } } ast::Expr::Tpl(tpl) => { if tpl.quasis.len() == 1 { let name = tpl.quasis[0].raw.to_string(); let (id, _) = test_module.register( name, Some(range), false, parent_id.map(str::to_owned), ); match node.args.get(1).map(|es| es.expr.as_ref()) { Some(ast::Expr::Fn(fn_expr)) => { visit_fn(&fn_expr.function, &id, text_info, test_module); } Some(ast::Expr::Arrow(arrow_expr)) => { visit_arrow(arrow_expr, &id, text_info, test_module); } _ => {} } } } ast::Expr::Ident(ident) => { let name = ident.sym.to_string(); if let Some(fn_expr) = fns.and_then(|fns| fns.get(&name)) { let (parent_id, _) = test_module.register( name, Some(range), false, parent_id.map(str::to_owned), ); visit_fn(fn_expr, &parent_id, text_info, test_module); } } _ => { if parent_id.is_none() { let node_range = node.range(); let indexes = text_info.line_and_column_display(node_range.start); test_module.register( format!("Test {}:{}", indexes.line_number, indexes.column_number), Some(range), false, None, ); } } } } } /// A structure which can be used to walk a branch of AST determining if the /// branch contains any testing steps. struct TestStepCollector<'a> { maybe_test_context: Option, vars: HashSet, parent_id: &'a str, text_info: &'a SourceTextInfo, test_module: &'a mut TestModule, } impl<'a> TestStepCollector<'a> { fn new( maybe_test_context: Option, maybe_step_var: Option, parent_id: &'a str, text_info: &'a SourceTextInfo, test_module: &'a mut TestModule, ) -> Self { let mut vars = HashSet::new(); if let Some(var) = maybe_step_var { vars.insert(var); } Self { maybe_test_context, vars, parent_id, text_info, test_module, } } } impl Visit for TestStepCollector<'_> { fn visit_call_expr(&mut self, node: &ast::CallExpr) { if let ast::Callee::Expr(callee_expr) = &node.callee { match callee_expr.as_ref() { // Identify calls to identified variables ast::Expr::Ident(ident) => { if self.vars.contains(&ident.sym.to_string()) { visit_call_expr( node, None, source_range_to_lsp_range(&ident.range(), self.text_info), Some(self.parent_id), self.text_info, self.test_module, ); } } // Identify calls to `test.step()` ast::Expr::Member(member_expr) => { if let Some(test_context) = &self.maybe_test_context { if let ast::MemberProp::Ident(ns_prop_ident) = &member_expr.prop { if ns_prop_ident.sym.eq("step") { if let ast::Expr::Ident(ident) = member_expr.obj.as_ref() { if ident.sym == *test_context { visit_call_expr( node, None, source_range_to_lsp_range( &ns_prop_ident.range(), self.text_info, ), Some(self.parent_id), self.text_info, self.test_module, ); } } } } } } _ => (), } } } fn visit_var_decl(&mut self, node: &ast::VarDecl) { if let Some(test_context) = &self.maybe_test_context { for decl in &node.decls { let Some(init) = &decl.init else { continue; }; match init.as_ref() { // Identify destructured assignments of `step` from test context ast::Expr::Ident(ident) => { if ident.sym != *test_context { continue; } let ast::Pat::Object(object_pat) = &decl.name else { continue; }; for prop in &object_pat.props { match prop { ast::ObjectPatProp::Assign(prop) => { if prop.key.sym.eq("step") { self.vars.insert(prop.key.sym.to_string()); } } ast::ObjectPatProp::KeyValue(prop) => { if let ast::PropName::Ident(key_ident) = &prop.key { if key_ident.sym.eq("step") { if let ast::Pat::Ident(value_ident) = &prop.value.as_ref() { self.vars.insert(value_ident.id.sym.to_string()); } } } } _ => (), } } } // Identify variable assignments where the init is test context // `.step` ast::Expr::Member(member_expr) => { let ast::Expr::Ident(obj_ident) = member_expr.obj.as_ref() else { continue; }; if obj_ident.sym != *test_context { continue; } let ast::MemberProp::Ident(prop_ident) = &member_expr.prop else { continue; }; if prop_ident.sym.eq("step") { if let ast::Pat::Ident(binding_ident) = &decl.name { self.vars.insert(binding_ident.id.sym.to_string()); } } } _ => (), } } } } } /// Walk an AST and determine if it contains any `Deno.test` tests. pub struct TestCollector { test_module: TestModule, vars: HashSet, fns: HashMap, text_info: SourceTextInfo, } impl TestCollector { pub fn new( specifier: ModuleSpecifier, script_version: String, text_info: SourceTextInfo, ) -> Self { Self { test_module: TestModule::new(specifier, script_version), vars: HashSet::new(), fns: HashMap::new(), text_info, } } /// Move out the test definitions pub fn take(self) -> TestModule { self.test_module } } impl Visit for TestCollector { fn visit_call_expr(&mut self, node: &ast::CallExpr) { fn visit_if_deno_test( collector: &mut TestCollector, node: &ast::CallExpr, range: &deno_ast::SourceRange, ns_prop_ident: &ast::Ident, member_expr: &ast::MemberExpr, ) { if ns_prop_ident.sym == "test" { let ast::Expr::Ident(ident) = member_expr.obj.as_ref() else { return; }; if ident.sym != "Deno" { return; } visit_call_expr( node, Some(&collector.fns), source_range_to_lsp_range(range, &collector.text_info), None, &collector.text_info, &mut collector.test_module, ); } } let ast::Callee::Expr(callee_expr) = &node.callee else { return; }; match callee_expr.as_ref() { ast::Expr::Ident(ident) => { if self.vars.contains(&ident.sym.to_string()) { visit_call_expr( node, Some(&self.fns), source_range_to_lsp_range(&ident.range(), &self.text_info), None, &self.text_info, &mut self.test_module, ); } } ast::Expr::Member(member_expr) => { let ast::MemberProp::Ident(ns_prop_ident) = &member_expr.prop else { return; }; let ns_prop_ident_name = ns_prop_ident.sym.to_string(); visit_if_deno_test( self, node, &ns_prop_ident.range(), ns_prop_ident, member_expr, ); if ns_prop_ident_name == "ignore" || ns_prop_ident_name == "only" { let ast::Expr::Member(child_member_expr) = member_expr.obj.as_ref() else { return; }; let ast::MemberProp::Ident(child_ns_prop_ident) = &child_member_expr.prop else { return; }; visit_if_deno_test( self, node, &ns_prop_ident.range(), child_ns_prop_ident, child_member_expr, ); } } _ => (), } } fn visit_var_decl(&mut self, node: &ast::VarDecl) { for decl in &node.decls { let Some(init) = &decl.init else { continue }; match init.as_ref() { // Identify destructured assignments of `test` from `Deno` ast::Expr::Ident(ident) => { if ident.sym != "Deno" { continue; } let ast::Pat::Object(object_pat) = &decl.name else { continue; }; for prop in &object_pat.props { match prop { ast::ObjectPatProp::Assign(prop) => { let name = prop.key.sym.to_string(); if name == "test" { self.vars.insert(name); } } ast::ObjectPatProp::KeyValue(prop) => { let ast::PropName::Ident(key_ident) = &prop.key else { continue; }; if key_ident.sym == "test" { if let ast::Pat::Ident(value_ident) = &prop.value.as_ref() { self.vars.insert(value_ident.id.sym.to_string()); } } } _ => (), } } } // Identify variable assignments where the init is `Deno.test` ast::Expr::Member(member_expr) => { let ast::Expr::Ident(obj_ident) = member_expr.obj.as_ref() else { continue; }; if obj_ident.sym != "Deno" { continue; }; let ast::MemberProp::Ident(prop_ident) = &member_expr.prop else { continue; }; if prop_ident.sym != "test" { continue; } if let ast::Pat::Ident(binding_ident) = &decl.name { self.vars.insert(binding_ident.id.sym.to_string()); } } _ => (), } } } fn visit_fn_decl(&mut self, n: &ast::FnDecl) { self .fns .insert(n.ident.sym.to_string(), *n.function.clone()); } } #[cfg(test)] pub mod tests { use crate::lsp::testing::definitions::TestDefinition; use super::*; use deno_core::resolve_url; use lsp::Position; pub fn new_range(l1: u32, c1: u32, l2: u32, c2: u32) -> Range { Range::new(Position::new(l1, c1), Position::new(l2, c2)) } fn collect(source: &str) -> TestModule { let specifier = resolve_url("file:///a/example.ts").unwrap(); let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { specifier: specifier.to_string(), text_info: deno_ast::SourceTextInfo::new(source.into()), media_type: deno_ast::MediaType::TypeScript, capture_tokens: true, scope_analysis: true, maybe_syntax: None, }) .unwrap(); let text_info = parsed_module.text_info().clone(); let mut collector = TestCollector::new(specifier, "1".to_string(), text_info); parsed_module.module().visit_with(&mut collector); collector.take() } #[test] fn test_test_collector_test() { let test_module = collect( r#" Deno.test("test", () => {}); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), name: "test".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_test_tpl() { let test_module = collect( r#" Deno.test(`test`, () => {}); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), name: "test".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_a() { let test_module = collect( r#" Deno.test({ name: "test", async fn(t) { await t.step("step", ({ step }) => { await step({ name: "sub step", fn() {} }) }); } }); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![ ( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string(), name: "test".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: vec!["704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string()].into_iter().collect(), } ), ( "704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string(), TestDefinition { id: "704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string(), name: "step".to_string(), range: Some(new_range(4, 18, 4, 22)), is_dynamic: false, parent_id: Some("4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string()), step_ids: vec!["0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string()].into_iter().collect(), } ), ( "0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string(), TestDefinition { id: "0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string(), name: "sub step".to_string(), range: Some(new_range(5, 18, 5, 22)), is_dynamic: false, parent_id: Some("704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string()), step_ids: Default::default(), } ), ].into_iter().collect(), } ); } #[test] fn test_test_collector_a_tpl() { let test_module = collect( r#" Deno.test({ name: `test`, async fn(t) { await t.step(`step`, ({ step }) => { await step({ name: `sub step`, fn() {} }) }); } }); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![ ( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string(), name: "test".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: vec!["704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string()].into_iter().collect(), } ), ( "704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string(), TestDefinition { id: "704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string(), name: "step".to_string(), range: Some(new_range(4, 18, 4, 22)), is_dynamic: false, parent_id: Some("4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9".to_string()), step_ids: vec!["0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string()].into_iter().collect(), } ), ( "0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string(), TestDefinition { id: "0d006a4ec0abaa9cc1d18256b1ccd2677a4c882ff5cb807123890f7528ab1e8d".to_string(), name: "sub step".to_string(), range: Some(new_range(5, 18, 5, 22)), is_dynamic: false, parent_id: Some("704d24083fd4a3e1bd204faa20827dc594334812245e5d45dda222b3edc60a0c".to_string()), step_ids: Default::default(), } ), ].into_iter().collect(), } ); } #[test] fn test_test_collector_destructure() { let test_module = collect( r#" const { test } = Deno; test("test", () => {}); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), name: "test".to_string(), range: Some(new_range(2, 6, 2, 10)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_destructure_rebind_step() { let test_module = collect( r#" Deno.test(async function useFnName({ step: s }) { await s("step", () => {}); }); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![ ( "86b4c821900e38fc89f24bceb0e45193608ab3f9d2a6019c7b6a5aceff5d7df2".to_string(), TestDefinition { id: "86b4c821900e38fc89f24bceb0e45193608ab3f9d2a6019c7b6a5aceff5d7df2".to_string(), name: "useFnName".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: vec!["dac8a169b8f8c6babf11122557ea545de2733bfafed594d044b22bc6863a0856".to_string()].into_iter().collect(), } ), ( "dac8a169b8f8c6babf11122557ea545de2733bfafed594d044b22bc6863a0856".to_string(), TestDefinition { id: "dac8a169b8f8c6babf11122557ea545de2733bfafed594d044b22bc6863a0856".to_string(), name: "step".to_string(), range: Some(new_range(2, 14, 2, 15)), is_dynamic: false, parent_id: Some("86b4c821900e38fc89f24bceb0e45193608ab3f9d2a6019c7b6a5aceff5d7df2".to_string()), step_ids: Default::default(), } ), ].into_iter().collect(), } ); } #[test] fn test_test_collector_rebind() { let test_module = collect( r#" const t = Deno.test; t("test", () => {}); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), name: "test".to_string(), range: Some(new_range(2, 6, 2, 7)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_separate_test_function_with_string_name() { let test_module = collect( r#" function someFunction() {} Deno.test("test", someFunction); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), TestDefinition { id: "4ebb361c93f76a0f1bac300638675609f1cf481e6f3b9006c3c98604b3a184e9" .to_string(), name: "test".to_string(), range: Some(new_range(2, 11, 2, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_function_only() { let test_module = collect( r#" Deno.test(async function someFunction() {}); Deno.test.ignore(function foo() {}); Deno.test.only(function bar() {}); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![ ( "87f28e06f5ddadd90a74a93b84df2e31b9edced8301b0ad4c8fbab8d806ec99d".to_string(), TestDefinition { id: "87f28e06f5ddadd90a74a93b84df2e31b9edced8301b0ad4c8fbab8d806ec99d".to_string(), name: "foo".to_string(), range: Some(new_range(2, 16, 2, 22)), is_dynamic: false, parent_id: None, step_ids: Default::default(), }, ), ( "e0f6a73647b763f82176c98a019e54200b799a32007f9859fb782aaa9e308568".to_string(), TestDefinition { id: "e0f6a73647b763f82176c98a019e54200b799a32007f9859fb782aaa9e308568".to_string(), name: "someFunction".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ), ( "e1bd61cdaf5e64863d3d85baffe3e43bd57cdb8dc0b5d6a9e03ade18b7f68d47".to_string(), TestDefinition { id: "e1bd61cdaf5e64863d3d85baffe3e43bd57cdb8dc0b5d6a9e03ade18b7f68d47".to_string(), name: "bar".to_string(), range: Some(new_range(3, 16, 3, 20)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ) ] .into_iter() .collect(), } ); } #[test] fn test_test_collector_separate_test_function() { let test_module = collect( r#" async function someFunction() {} Deno.test(someFunction); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "e0f6a73647b763f82176c98a019e54200b799a32007f9859fb782aaa9e308568" .to_string(), TestDefinition { id: "e0f6a73647b763f82176c98a019e54200b799a32007f9859fb782aaa9e308568" .to_string(), name: "someFunction".to_string(), range: Some(new_range(2, 11, 2, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } #[test] fn test_test_collector_unknown_test() { let test_module = collect( r#" const someFunction = () => ({ name: "test", fn: () => {} }); Deno.test(someFunction()); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![( "6d05d6dc35548b86a1e70acaf24a5bc2dd35db686b35b685ad5931d201b4a918" .to_string(), TestDefinition { id: "6d05d6dc35548b86a1e70acaf24a5bc2dd35db686b35b685ad5931d201b4a918" .to_string(), name: "Test 3:7".to_string(), range: Some(new_range(2, 11, 2, 15)), is_dynamic: false, parent_id: None, step_ids: Default::default(), } ),] .into_iter() .collect(), } ); } // Regression test for https://github.com/denoland/vscode_deno/issues/656. #[test] fn test_test_collector_nested_steps_same_name_and_level() { let test_module = collect( r#" Deno.test("1", async (t) => { await t.step("step 1", async (t) => { await t.step("nested step", () => {}); }); await t.step("step 2", async (t) => { await t.step("nested step", () => {}); }); }); "#, ); assert_eq!( &test_module, &TestModule { specifier: test_module.specifier.clone(), script_version: test_module.script_version.clone(), defs: vec![ ( "3799fc549a32532145ffc8532b0cd943e025bbc19a02e2cde9be94f87bceb829".to_string(), TestDefinition { id: "3799fc549a32532145ffc8532b0cd943e025bbc19a02e2cde9be94f87bceb829".to_string(), name: "1".to_string(), range: Some(new_range(1, 11, 1, 15)), is_dynamic: false, parent_id: None, step_ids: vec![ "e714fc695c0895327bf7148a934c3303ad515af029a14906be46f80340c6d7e3".to_string(), "ec6b03d3dd3dde78d2d11ed981d3386083aeca701510cc049189d74bd79f8587".to_string(), ].into_iter().collect() } ), ( "e714fc695c0895327bf7148a934c3303ad515af029a14906be46f80340c6d7e3".to_string(), TestDefinition { id: "e714fc695c0895327bf7148a934c3303ad515af029a14906be46f80340c6d7e3".to_string(), name: "step 1".to_string(), range: Some(new_range(2, 16, 2, 20)), is_dynamic: false, parent_id: Some("3799fc549a32532145ffc8532b0cd943e025bbc19a02e2cde9be94f87bceb829".to_string()), step_ids: vec!["d874949e18dfc297e15c52ff13f13b4e6ae911ec1818b2c761e3313bc018a3ab".to_string()].into_iter().collect() } ), ( "d874949e18dfc297e15c52ff13f13b4e6ae911ec1818b2c761e3313bc018a3ab".to_string(), TestDefinition { id: "d874949e18dfc297e15c52ff13f13b4e6ae911ec1818b2c761e3313bc018a3ab".to_string(), name: "nested step".to_string(), range: Some(new_range(3, 18, 3, 22)), is_dynamic: false, parent_id: Some("e714fc695c0895327bf7148a934c3303ad515af029a14906be46f80340c6d7e3".to_string()), step_ids: Default::default(), } ), ( "ec6b03d3dd3dde78d2d11ed981d3386083aeca701510cc049189d74bd79f8587".to_string(), TestDefinition { id: "ec6b03d3dd3dde78d2d11ed981d3386083aeca701510cc049189d74bd79f8587".to_string(), name: "step 2".to_string(), range: Some(new_range(5, 16, 5, 20)), is_dynamic: false, parent_id: Some("3799fc549a32532145ffc8532b0cd943e025bbc19a02e2cde9be94f87bceb829".to_string()), step_ids: vec!["96729f1f1608e50160b0bf11946719384b4021fd1d26b14eff7765034b3d2684".to_string()].into_iter().collect() } ), ( "96729f1f1608e50160b0bf11946719384b4021fd1d26b14eff7765034b3d2684".to_string(), TestDefinition { id: "96729f1f1608e50160b0bf11946719384b4021fd1d26b14eff7765034b3d2684".to_string(), name: "nested step".to_string(), range: Some(new_range(6, 18, 6, 22)), is_dynamic: false, parent_id: Some("ec6b03d3dd3dde78d2d11ed981d3386083aeca701510cc049189d74bd79f8587".to_string()), step_ids: Default::default(), } ), ].into_iter().collect(), } ); } }