Skip to content

Commit ee68d1a

Browse files
Boshenclaude
andcommitted
test: add unit tests for TsconfigFileMatcher pattern matching
Add comprehensive unit tests that directly test pattern matching logic in isolation, using the previously unused fixtures. ## Changes - Create `src/tests/tsconfig_file_matcher.rs` with 10 unit tests - Each test covers a specific pattern type: - `test_globstar_patterns` - Tests **/*.ts (matches at any depth) - `test_wildcard_patterns` - Tests src/*.ts (single level) - `test_character_set_patterns` - Tests [A-Z]*.ts (character ranges) - `test_complex_patterns` - Tests src/**/test/**/*.spec.ts (nested) - `test_monorepo_patterns` - Tests packages/*/src/**/* (monorepo) - `test_files_priority` - Tests files array overrides exclude - `test_outdir_exclude` - Tests automatic outDir exclusion - `test_absolute_patterns` - Tests absolute path handling - `test_configdir_syntax` - Tests ${configDir} template variable - `test_without_baseurl` - Tests default include/exclude - Helper function `create_matcher_from_fixture()` reads tsconfig.json and creates TsconfigFileMatcher with template substitution - All 10 fixtures that were created but unused are now tested - Total: 168 tests passing (10 new unit tests added) ## Benefits - Tests pattern matching in isolation (faster than integration tests) - Clear documentation of all supported pattern types - Better coverage of edge cases - Reuses existing fixture infrastructure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2a6e0a9 commit ee68d1a

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod simple;
2626
mod symlink;
2727
mod tsconfig_discovery;
2828
mod tsconfig_extends;
29+
mod tsconfig_file_matcher;
2930
mod tsconfig_include_exclude;
3031
mod tsconfig_paths;
3132
mod tsconfig_project_references;

src/tests/tsconfig_file_matcher.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
//! Unit tests for TsconfigFileMatcher pattern matching
2+
//!
3+
//! Tests various glob patterns, include/exclude logic, and edge cases
4+
//! using fixtures from tsconfig/cases/
5+
6+
use crate::tsconfig::TsconfigFileMatcher;
7+
use std::path::PathBuf;
8+
9+
/// Helper to create a TsconfigFileMatcher from a fixture directory
10+
fn create_matcher_from_fixture(fixture_name: &str) -> (TsconfigFileMatcher, PathBuf) {
11+
let fixture_dir = super::fixture_root().join("tsconfig/cases").join(fixture_name);
12+
let tsconfig_path = fixture_dir.join("tsconfig.json");
13+
14+
// Read and parse tsconfig.json
15+
let tsconfig_str = std::fs::read_to_string(&tsconfig_path)
16+
.unwrap_or_else(|_| panic!("Failed to read tsconfig.json from {fixture_name}"));
17+
let json: serde_json::Value = serde_json::from_str(&tsconfig_str)
18+
.unwrap_or_else(|_| panic!("Failed to parse tsconfig.json from {fixture_name}"));
19+
20+
// Helper to substitute ${configDir} template variable
21+
#[allow(clippy::option_if_let_else)] // map_or causes borrow checker issues
22+
let substitute_template = |s: String| -> String {
23+
match s.strip_prefix("${configDir}") {
24+
Some(stripped) => fixture_dir.to_str().unwrap().to_string() + stripped,
25+
None => s,
26+
}
27+
};
28+
29+
// Extract fields
30+
let files = json.get("files").and_then(|v| {
31+
v.as_array().map(|arr| {
32+
arr.iter()
33+
.filter_map(|s| s.as_str().map(String::from))
34+
.map(substitute_template)
35+
.collect()
36+
})
37+
});
38+
39+
let include = json.get("include").and_then(|v| {
40+
v.as_array().map(|arr| {
41+
arr.iter()
42+
.filter_map(|s| s.as_str().map(String::from))
43+
.map(substitute_template)
44+
.collect()
45+
})
46+
});
47+
48+
let exclude = json.get("exclude").and_then(|v| {
49+
v.as_array().map(|arr| {
50+
arr.iter()
51+
.filter_map(|s| s.as_str().map(String::from))
52+
.map(substitute_template)
53+
.collect()
54+
})
55+
});
56+
57+
let out_dir = json
58+
.get("compilerOptions")
59+
.and_then(|opts| opts.get("outDir"))
60+
.and_then(|v| v.as_str())
61+
.map(std::path::Path::new);
62+
63+
// Create matcher
64+
let matcher = TsconfigFileMatcher::new(files, include, exclude, out_dir, fixture_dir.clone());
65+
66+
(matcher, fixture_dir)
67+
}
68+
69+
#[test]
70+
fn test_globstar_patterns() {
71+
let (matcher, fixture_dir) = create_matcher_from_fixture("globstar_patterns");
72+
73+
// Test cases: (path, should_match, description)
74+
let test_cases = [
75+
("index.ts", true, "globstar matches root level"),
76+
("src/index.ts", true, "globstar matches one level deep"),
77+
("src/utils/helper.ts", true, "globstar matches two levels deep"),
78+
("src/utils/deep/nested/file.ts", true, "globstar matches deeply nested"),
79+
("index.js", false, "globstar doesn't match .js files"),
80+
("src/index.js", false, "globstar doesn't match .js files in subdirs"),
81+
];
82+
83+
for (file_path, should_match, comment) in test_cases {
84+
let full_path = fixture_dir.join(file_path);
85+
let result = matcher.matches(&full_path);
86+
assert_eq!(result, should_match, "{comment}: path={file_path}");
87+
}
88+
}
89+
90+
#[test]
91+
fn test_wildcard_patterns() {
92+
let (matcher, fixture_dir) = create_matcher_from_fixture("wildcard_patterns");
93+
94+
let test_cases = [
95+
("src/index.ts", true, "wildcard matches files in src/"),
96+
("src/helper.ts", true, "wildcard matches files in src/"),
97+
("src/utils/helper.ts", false, "wildcard doesn't match subdirectories"),
98+
("index.ts", false, "wildcard doesn't match parent directory"),
99+
];
100+
101+
for (file_path, should_match, comment) in test_cases {
102+
let full_path = fixture_dir.join(file_path);
103+
let result = matcher.matches(&full_path);
104+
assert_eq!(result, should_match, "{comment}: path={file_path}");
105+
}
106+
}
107+
108+
#[test]
109+
fn test_character_set_patterns() {
110+
let (matcher, fixture_dir) = create_matcher_from_fixture("character_set_patterns");
111+
112+
let test_cases = [
113+
("App.ts", true, "character set matches uppercase start"),
114+
("Button.ts", true, "character set matches uppercase start"),
115+
];
116+
117+
for (file_path, should_match, comment) in test_cases {
118+
let full_path = fixture_dir.join(file_path);
119+
let result = matcher.matches(&full_path);
120+
assert_eq!(result, should_match, "{comment}: path={file_path}");
121+
}
122+
}
123+
124+
#[test]
125+
fn test_complex_patterns() {
126+
let (matcher, fixture_dir) = create_matcher_from_fixture("complex_patterns");
127+
128+
let test_cases = [
129+
("src/test/unit.spec.ts", true, "complex pattern matches src/test/*.spec.ts"),
130+
("src/utils/test/helper.spec.ts", true, "complex pattern matches src/**/test/*.spec.ts"),
131+
("src/deep/nested/test/component.spec.ts", true, "complex pattern matches deeply nested"),
132+
("src/index.ts", false, "file not in test directory"),
133+
("src/utils/helper.ts", false, "file not in test directory"),
134+
("src/test/unit.test.ts", false, "wrong extension (.test.ts not .spec.ts)"),
135+
];
136+
137+
for (file_path, should_match, comment) in test_cases {
138+
let full_path = fixture_dir.join(file_path);
139+
let result = matcher.matches(&full_path);
140+
assert_eq!(result, should_match, "{comment}: path={file_path}");
141+
}
142+
}
143+
144+
#[test]
145+
fn test_monorepo_patterns() {
146+
let (matcher, fixture_dir) = create_matcher_from_fixture("monorepo_patterns");
147+
148+
let test_cases = [
149+
("packages/pkg-a/src/index.ts", true, "monorepo pattern matches pkg-a"),
150+
("packages/pkg-b/src/utils.ts", true, "monorepo pattern matches pkg-b"),
151+
("packages/pkg-c/src/deep/nested/file.ts", true, "monorepo pattern matches nested"),
152+
("packages/pkg-a/dist/index.js", false, "dist not in src directory"),
153+
("shared/utils.ts", false, "shared not in packages/*/src"),
154+
("packages/pkg-a/test.ts", false, "test.ts not in src directory"),
155+
];
156+
157+
for (file_path, should_match, comment) in test_cases {
158+
let full_path = fixture_dir.join(file_path);
159+
let result = matcher.matches(&full_path);
160+
assert_eq!(result, should_match, "{comment}: path={file_path}");
161+
}
162+
}
163+
164+
#[test]
165+
fn test_files_priority() {
166+
let (matcher, fixture_dir) = create_matcher_from_fixture("files_priority");
167+
168+
let test_cases = [
169+
("test.ts", true, "files field overrides exclude"),
170+
("other.ts", false, "file not in files array"),
171+
];
172+
173+
for (file_path, should_match, comment) in test_cases {
174+
let full_path = fixture_dir.join(file_path);
175+
let result = matcher.matches(&full_path);
176+
assert_eq!(result, should_match, "{comment}: path={file_path}");
177+
}
178+
}
179+
180+
#[test]
181+
fn test_outdir_exclude() {
182+
let (matcher, fixture_dir) = create_matcher_from_fixture("outdir_exclude");
183+
184+
let test_cases = [
185+
("src/index.ts", true, "source files included"),
186+
("dist/index.js", false, "outDir automatically excluded"),
187+
("dist/index.d.ts", false, "outDir automatically excluded"),
188+
];
189+
190+
for (file_path, should_match, comment) in test_cases {
191+
let full_path = fixture_dir.join(file_path);
192+
let result = matcher.matches(&full_path);
193+
assert_eq!(result, should_match, "{comment}: path={file_path}");
194+
}
195+
}
196+
197+
#[test]
198+
fn test_absolute_patterns() {
199+
let (matcher, fixture_dir) = create_matcher_from_fixture("absolute_patterns");
200+
201+
let test_cases = [
202+
("src/index.ts", true, "absolute pattern matches"),
203+
("excluded/file.ts", false, "excluded directory"),
204+
];
205+
206+
for (file_path, should_match, comment) in test_cases {
207+
let full_path = fixture_dir.join(file_path);
208+
let result = matcher.matches(&full_path);
209+
assert_eq!(result, should_match, "{comment}: path={file_path}");
210+
}
211+
}
212+
213+
#[test]
214+
fn test_configdir_syntax() {
215+
let (matcher, fixture_dir) = create_matcher_from_fixture("configdir_syntax");
216+
217+
let test_cases = [
218+
("index.ts", true, "${configDir} matches root level .ts files"),
219+
("log.ts", true, "${configDir} matches root level .ts files"),
220+
("dist/output.js", false, "dist excluded"),
221+
("src/index.ts", false, "${configDir}/*.ts doesn't match subdirectories"),
222+
];
223+
224+
for (file_path, should_match, comment) in test_cases {
225+
let full_path = fixture_dir.join(file_path);
226+
let result = matcher.matches(&full_path);
227+
assert_eq!(result, should_match, "{comment}: path={file_path}");
228+
}
229+
}
230+
231+
#[test]
232+
fn test_without_baseurl() {
233+
let (matcher, fixture_dir) = create_matcher_from_fixture("without_baseurl");
234+
235+
let test_cases = [
236+
("index.ts", true, "regular files included"),
237+
("log.ts", true, "regular files included"),
238+
("node_modules/package/index.ts", false, "node_modules excluded by default"),
239+
("bower_components/lib.ts", false, "bower_components excluded by default"),
240+
("jspm_packages/mod.ts", false, "jspm_packages excluded by default"),
241+
("dist/output.js", false, "custom exclude pattern works"),
242+
];
243+
244+
for (file_path, should_match, comment) in test_cases {
245+
let full_path = fixture_dir.join(file_path);
246+
let result = matcher.matches(&full_path);
247+
assert_eq!(result, should_match, "{comment}: path={file_path}");
248+
}
249+
}

0 commit comments

Comments
 (0)