use crate::imports::{normalize_import_target, parse_imports, supports_language};
#[test]
fn supports_language_all_five_supported() {
for lang in ["rust", "javascript", "typescript", "python", "go"] {
assert!(supports_language(lang), "{lang} should be supported");
}
}
#[test]
fn supports_language_case_mixed() {
assert!(supports_language("RuSt"));
assert!(supports_language("JAVASCRIPT"));
assert!(supports_language("gO"));
assert!(supports_language("pYtHoN"));
assert!(supports_language("TypeScript"));
}
#[test]
fn supports_language_rejects_similar_names() {
assert!(!supports_language("rust-lang"));
assert!(!supports_language("javascript1"));
assert!(!supports_language("python3"));
assert!(!supports_language("golang"));
assert!(!supports_language("ts"));
assert!(!supports_language("js"));
assert!(!supports_language("py"));
assert!(!supports_language("rs"));
}
#[test]
fn supports_language_rejects_empty_and_whitespace() {
assert!(!supports_language(""));
assert!(!supports_language(" "));
assert!(!supports_language("\t"));
}
#[test]
fn rust_use_with_alias() {
let lines = ["use std::io::Result as IoResult;"];
let imports = parse_imports("rust", &lines);
assert_eq!(imports, vec!["std"]);
}
#[test]
fn rust_use_glob_import() {
let lines = ["use std::prelude::*;"];
let imports = parse_imports("rust", &lines);
assert_eq!(imports, vec!["std"]);
}
#[test]
fn rust_mod_with_block_body_not_captured() {
let lines = ["mod inline {", " fn foo() {}", "}"];
let imports = parse_imports("rust", &lines);
assert_eq!(imports.len(), 1);
assert!(imports[0].starts_with("inline"));
}
#[test]
fn rust_pub_mod_not_captured() {
let lines = ["pub mod public_api;"];
let imports = parse_imports("rust", &lines);
assert!(
imports.is_empty(),
"pub mod should not be captured (doesn't start with 'mod ')"
);
}
#[test]
fn rust_multiple_use_same_crate() {
let lines = [
"use serde::Serialize;",
"use serde::Deserialize;",
"use serde_json::Value;",
];
let imports = parse_imports("rust", &lines);
assert_eq!(imports, vec!["serde", "serde", "serde_json"]);
}
#[test]
fn rust_use_with_leading_whitespace_and_trailing() {
let lines = [" use tokei::Languages; "];
let imports = parse_imports("rust", &lines);
assert_eq!(imports, vec!["tokei"]);
}
#[test]
fn js_import_with_both_default_and_named() {
let lines = [r#"import React, { useState, useEffect } from "react";"#];
let imports = parse_imports("javascript", &lines);
assert_eq!(imports, vec!["react"]);
}
#[test]
fn js_dynamic_import_not_captured() {
let lines = [r#"const mod = await import("./lazy");"#];
let imports = parse_imports("javascript", &lines);
assert!(
imports.is_empty(),
"dynamic import() should not be captured"
);
}
#[test]
fn js_require_resolve_not_captured() {
let lines = [r#"const data = JSON.parse(fs.readFileSync(require.resolve("./data.json")));"#];
let imports = parse_imports("javascript", &lines);
assert!(
imports.is_empty(),
"require.resolve should not match require("
);
}
#[test]
fn js_import_with_no_from_clause() {
let lines = [r#"import "core-js/stable";"#];
let imports = parse_imports("javascript", &lines);
assert_eq!(imports, vec!["core-js/stable"]);
}
#[test]
fn js_empty_require_string_not_captured() {
let lines = [r#"const x = require("");"#];
let imports = parse_imports("javascript", &lines);
assert!(
imports.is_empty(),
"empty quoted string should not produce import"
);
}
#[test]
fn js_import_and_require_on_same_line() {
let lines = [r#"import foo from "bar"; const x = require("baz");"#];
let imports = parse_imports("javascript", &lines);
assert_eq!(imports, vec!["bar", "baz"]);
}
#[test]
fn python_import_with_as_clause() {
let lines = ["import numpy as np"];
let imports = parse_imports("python", &lines);
assert_eq!(imports, vec!["numpy"]);
}
#[test]
fn python_from_import_multiple_names() {
let lines = ["from collections import OrderedDict, defaultdict, Counter"];
let imports = parse_imports("python", &lines);
assert_eq!(imports, vec!["collections"]);
}
#[test]
fn python_from_relative_import() {
let lines = ["from . import utils", "from ..models import User"];
let imports = parse_imports("python", &lines);
assert_eq!(imports, vec![".", "..models"]);
}
#[test]
fn python_ignores_inline_comment_import() {
let lines = ["x = 1 # import os"];
let imports = parse_imports("python", &lines);
assert!(imports.is_empty());
}
#[test]
fn python_conditional_import() {
let lines = [" import sys", " from pathlib import Path"];
let imports = parse_imports("python", &lines);
assert_eq!(imports, vec!["sys", "pathlib"]);
}
#[test]
fn go_block_with_alias_and_blank_import() {
let lines = vec![
"import (",
r#" . "fmt""#,
r#" _ "net/http/pprof""#,
")",
];
let imports = parse_imports("go", &lines);
assert_eq!(imports, vec!["fmt", "net/http/pprof"]);
}
#[test]
fn go_multiple_separate_blocks() {
let lines = vec![
"import (",
r#" "fmt""#,
")",
"",
"import (",
r#" "os""#,
")",
];
let imports = parse_imports("go", &lines);
assert_eq!(imports, vec!["fmt", "os"]);
}
#[test]
fn go_block_with_comment_line_skipped() {
let lines = vec!["import (", " // standard library", r#" "fmt""#, ")"];
let imports = parse_imports("go", &lines);
assert_eq!(imports, vec!["fmt"]);
}
#[test]
fn go_no_import_in_non_import_context() {
let lines = vec![r#"fmt.Println("import something")"#];
let imports = parse_imports("go", &lines);
assert!(imports.is_empty());
}
#[test]
fn normalize_preserves_underscored_crate_names() {
assert_eq!(normalize_import_target("serde_json"), "serde_json");
assert_eq!(normalize_import_target("tokmd_types"), "tokmd_types");
}
#[test]
fn normalize_scoped_npm_package() {
assert_eq!(normalize_import_target("@types/node"), "@types");
assert_eq!(normalize_import_target("@babel/core"), "@babel");
}
#[test]
fn normalize_double_dot_relative() {
assert_eq!(normalize_import_target(".."), "local");
assert_eq!(normalize_import_target("..."), "local");
assert_eq!(normalize_import_target("./"), "local");
assert_eq!(normalize_import_target("../../../deeply/nested"), "local");
}
#[test]
fn normalize_empty_string() {
let result = normalize_import_target("");
assert_eq!(result, "");
}
#[test]
fn normalize_only_whitespace() {
let result = normalize_import_target(" ");
assert_eq!(result, "");
}
#[test]
fn normalize_deeply_nested_go_path() {
assert_eq!(
normalize_import_target("github.com/user/repo/internal/pkg"),
"github"
);
}
#[test]
fn normalize_strips_both_quote_types() {
assert_eq!(normalize_import_target(r#""react""#), "react");
assert_eq!(normalize_import_target("'lodash'"), "lodash");
}
#[test]
fn normalize_mixed_separators() {
assert_eq!(normalize_import_target("a/b:c.d"), "a");
}
#[test]
fn same_module_across_languages() {
let rust = parse_imports("rust", &["use serde::Serialize;"]);
let py = parse_imports("python", &["import serde"]);
assert_eq!(rust, vec!["serde"]);
assert_eq!(py, vec!["serde"]);
}
#[test]
fn whitespace_only_lines_produce_no_imports_any_lang() {
let lines = vec!["", " ", "\t", " \t "];
for lang in ["rust", "python", "javascript", "typescript", "go"] {
let imports = parse_imports(lang, &lines);
assert!(
imports.is_empty(),
"whitespace-only for {lang} should be empty"
);
}
}
#[test]
fn parse_handles_many_lines_without_panic() {
let lines: Vec<String> = (0..10_000)
.map(|i| format!("use crate_{i}::module;"))
.collect();
let refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
let imports = parse_imports("rust", &refs);
assert_eq!(imports.len(), 10_000);
}
#[test]
fn parse_accepts_string_slices() {
let lines: Vec<String> = vec![
"use std::io;".to_string(),
"use serde::Deserialize;".to_string(),
];
let imports = parse_imports("rust", &lines);
assert_eq!(imports, vec!["std", "serde"]);
}