use super::FileInspector;
use crate::inspectors::FileInspectorOptions;
use lazy_static::lazy_static;
use regex::Regex;
use serde::Serialize;
use serde_json::{json, Map, Value};
use std::path::Path;
#[derive(Serialize)]
struct TestEntry {
path: String,
cases: Vec<String>,
}
pub struct TestInspector {
unit_test_cases: i64,
unit_test_files: Vec<TestEntry>,
e2e_test_cases: i64,
e2e_test_files: Vec<TestEntry>,
}
impl TestInspector {
pub fn new() -> Self {
TestInspector {
unit_test_cases: 0,
unit_test_files: vec![],
e2e_test_cases: 0,
e2e_test_files: vec![],
}
}
pub fn extract_test_names(contents: &str) -> Vec<String> {
lazy_static! {
static ref NAME_REGEX: Regex =
Regex::new(r#"(\b(?:it|test)\b\(['"])(?P<name>.*?)(['"])"#).unwrap();
}
NAME_REGEX
.captures_iter(contents)
.map(|c| c.name("name").unwrap().as_str().to_owned())
.collect()
}
}
impl Default for TestInspector {
fn default() -> Self {
TestInspector::new()
}
}
impl FileInspector for TestInspector {
fn get_module_name(&self) -> &str {
"angular-tests"
}
fn init(&mut self, _working_dir: &Path, _output: &mut Map<String, Value>) {}
fn supports_file(&self, path: &Path) -> bool {
let display_path = path.display().to_string();
path.is_file()
&& (display_path.ends_with(".spec.ts")
|| display_path.ends_with(".test.ts")
|| display_path.ends_with(".e2e.ts"))
}
fn inspect_file(&mut self, options: &FileInspectorOptions, _output: &mut Map<String, Value>) {
let contents = options.read_content();
let test_names = TestInspector::extract_test_names(&contents);
if !test_names.is_empty() {
let workspace_path = options.relative_path.display().to_string();
let total_cases = test_names.len() as i64;
let entry = TestEntry {
path: workspace_path.to_owned(),
cases: test_names,
};
if workspace_path.ends_with(".spec.ts") {
self.unit_test_files.push(entry);
self.unit_test_cases += total_cases
} else if workspace_path.ends_with(".test.ts") || workspace_path.ends_with(".e2e.ts") {
self.e2e_test_files.push(entry);
self.e2e_test_cases += total_cases;
}
}
}
fn finalize(&mut self, output: &mut Map<String, Value>) {
output
.entry("unit_tests")
.or_insert(json!(self.unit_test_files));
output
.entry("e2e_tests")
.or_insert(json!(self.e2e_test_files));
let stats = output
.entry("stats")
.or_insert(json!({}))
.as_object_mut()
.unwrap();
stats.entry("tests").or_insert(json!({
"unit_test": self.unit_test_files.len(),
"unit_test_case": self.unit_test_cases,
"e2e_test": self.e2e_test_files.len(),
"e2e_test_case": self.e2e_test_cases
}));
if !self.unit_test_files.is_empty() {
println!("Unit Tests");
println!(" ├── Cases: {}", self.unit_test_cases);
println!(" └── Files: {}", self.unit_test_files.len());
}
if !self.e2e_test_files.is_empty() {
println!("E2E Tests");
println!(" ├── Cases: {}", self.e2e_test_cases);
println!(" └── Files: {}", self.e2e_test_files.len());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inspectors::utils::test_utils::options_from_file;
use assert_fs::prelude::*;
use assert_fs::NamedTempFile;
#[test]
fn extracts_single_test_name() {
let input = "it('should reset selected nodes from store', () => {";
assert_eq!(
vec!["should reset selected nodes from store"],
TestInspector::extract_test_names(input)
);
}
#[test]
fn extracts_multiple_test_names() {
let input = "\
it('should reset selected nodes from store', () => {\
it('should return false when entry is not shared', () => {
";
assert_eq!(
vec![
"should reset selected nodes from store",
"should return false when entry is not shared"
],
TestInspector::extract_test_names(input)
);
}
#[test]
fn extracts_playwright_test_names() {
let input = "test('Create a rule with condition', async ({ personalFiles, nodesPage })";
assert_eq!(
vec!["Create a rule with condition"],
TestInspector::extract_test_names(input)
);
}
#[test]
fn requires_spec_file_to_exist() {
let path = Path::new("missing/test.spec.ts");
let inspector = TestInspector::new();
assert!(!inspector.supports_file(path));
}
#[test]
fn supports_spec_file() -> Result<(), Box<dyn std::error::Error>> {
let file = NamedTempFile::new("test.spec.ts")?;
file.touch()?;
let inspector: TestInspector = Default::default();
assert!(inspector.supports_file(file.path()));
file.close()?;
Ok(())
}
#[test]
fn writes_default_values_on_finalise() {
let mut inspector: TestInspector = Default::default();
let mut map: Map<String, Value> = Map::new();
inspector.finalize(&mut map);
assert_eq!(
Value::Object(map),
json!({
"unit_tests": [],
"e2e_tests": [],
"stats": {
"tests": {
"unit_test": 0,
"unit_test_case": 0,
"e2e_test": 0,
"e2e_test_case": 0
}
}
})
);
}
#[test]
fn parses_unit_tests() -> Result<(), Box<dyn std::error::Error>> {
let file = NamedTempFile::new("tests.e2e.ts")?;
let content = r#"
describe('test suite', () => {
it('should have default LTR direction value', () => {});
it('should change direction on textOrientation event', () => {});
});
"#;
file.write_str(content)?;
let mut inspector = TestInspector::new();
let mut map: Map<String, Value> = Map::new();
inspector.inspect_file(&options_from_file(&file), &mut map);
inspector.finalize(&mut map);
assert_eq!(
Value::Object(map),
json!({
"unit_tests": [],
"e2e_tests": [
{
"path": "tests.e2e.ts",
"cases": [
"should have default LTR direction value",
"should change direction on textOrientation event"
]
}
],
"stats": {
"tests": {
"unit_test": 0,
"unit_test_case": 0,
"e2e_test": 1,
"e2e_test_case": 2
}
}
})
);
file.close()?;
Ok(())
}
#[test]
fn parses_e2e_tests() -> Result<(), Box<dyn std::error::Error>> {
let file = NamedTempFile::new("tests.spec.ts")?;
let content = r#"
describe('test suite', () => {
it('should have default LTR direction value', () => {});
it('should change direction on textOrientation event', () => {});
});
"#;
file.write_str(content)?;
let mut inspector = TestInspector::new();
let mut map: Map<String, Value> = Map::new();
let options = options_from_file(&file);
inspector.inspect_file(&options, &mut map);
inspector.finalize(&mut map);
assert_eq!(
Value::Object(map),
json!({
"unit_tests": [
{
"path": "tests.spec.ts",
"cases": [
"should have default LTR direction value",
"should change direction on textOrientation event"
]
}
],
"e2e_tests": [],
"stats": {
"tests": {
"unit_test": 1,
"unit_test_case": 2,
"e2e_test": 0,
"e2e_test_case": 0
}
}
})
);
file.close()?;
Ok(())
}
}