mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 12:51:54 +00:00
rename to test.filePatterns
This commit is contained in:
@@ -141,17 +141,17 @@ The root directory to run tests from. Default `.`.
|
||||
root = "./__tests__"
|
||||
```
|
||||
|
||||
### `test.glob`
|
||||
### `test.filePatterns`
|
||||
|
||||
Customize the file patterns used to identify test files. Can be a single string or an array of strings. Patterns are resolved relative to the bunfig.toml directory.
|
||||
Customize the file patterns used to identify test files. Can be a single string or an array of strings.
|
||||
|
||||
```toml
|
||||
[test]
|
||||
# Single pattern
|
||||
glob = "*.mytest.js"
|
||||
filePatterns = "*.mytest.js"
|
||||
|
||||
# Multiple patterns
|
||||
glob = ["*.mytest.js", "*.spec.ts", "**/*.unit.js"]
|
||||
filePatterns = ["*.mytest.js", "*.spec.ts", "**/*.unit.js"]
|
||||
```
|
||||
|
||||
By default, Bun uses these patterns:
|
||||
@@ -163,6 +163,11 @@ By default, Bun uses these patterns:
|
||||
|
||||
When custom patterns are specified, they completely replace the default patterns.
|
||||
|
||||
**Path resolution:**
|
||||
- Patterns starting with `./` or `../` are resolved relative to the directory containing your `bunfig.toml` file
|
||||
- Other glob patterns like `**/*.test.ts` are matched against relative paths from the current working directory
|
||||
- Absolute paths are used as-is
|
||||
|
||||
### `test.preload`
|
||||
|
||||
Same as the top-level `preload` field, but only applies to `bun test`.
|
||||
|
||||
@@ -20,18 +20,18 @@ The `root` option specifies a root directory for test discovery, overriding the
|
||||
root = "src" # Only scan for tests in the src directory
|
||||
```
|
||||
|
||||
#### glob
|
||||
#### filePatterns
|
||||
|
||||
The `glob` option allows you to customize the patterns used to identify test files, overriding the default patterns. You can specify a single string or an array of strings.
|
||||
The `filePatterns` option allows you to customize the patterns used to identify test files, overriding the default patterns. You can specify a single string or an array of strings.
|
||||
|
||||
```toml
|
||||
[test]
|
||||
glob = "*.mytest.js" # Single pattern
|
||||
filePatterns = "*.mytest.js" # Single pattern
|
||||
```
|
||||
|
||||
```toml
|
||||
[test]
|
||||
glob = ["*.mytest.js", "*.spec.ts", "**/*.unit.js"] # Multiple patterns
|
||||
filePatterns = ["*.mytest.js", "*.spec.ts", "**/*.unit.js"] # Multiple patterns
|
||||
```
|
||||
|
||||
By default, `bun test` searches for files matching these patterns:
|
||||
@@ -41,7 +41,12 @@ By default, `bun test` searches for files matching these patterns:
|
||||
- `*.spec.{js|jsx|ts|tsx}`
|
||||
- `*_spec.{js|jsx|ts|tsx}`
|
||||
|
||||
When you specify custom `glob` patterns, these default patterns are completely replaced with your custom ones. The patterns are resolved relative to the directory containing your `bunfig.toml` file.
|
||||
When you specify custom patterns, these default patterns are completely replaced with your custom ones.
|
||||
|
||||
**Path resolution:**
|
||||
- Patterns starting with `./` or `../` are resolved relative to the directory containing your `bunfig.toml` file
|
||||
- Other glob patterns like `**/*.test.ts` are matched against relative paths from the current working directory
|
||||
- Absolute paths are used as-is
|
||||
|
||||
### Reporters
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ pub const Bunfig = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (test_.get("glob")) |expr| brk: {
|
||||
if (test_.get("filePatterns")) |expr| brk: {
|
||||
// Get the directory containing the bunfig file for relative path resolution
|
||||
const bunfig_dir = std.fs.path.dirname(this.source.path.text) orelse "./";
|
||||
|
||||
@@ -361,35 +361,52 @@ pub const Bunfig = struct {
|
||||
.e_string => |str| {
|
||||
const pattern = try str.string(allocator);
|
||||
const patterns = try allocator.alloc(string, 1);
|
||||
// Resolve pattern relative to bunfig.toml directory if it's not absolute
|
||||
// Only resolve relative to bunfig.toml if it starts with ./ or ../
|
||||
if (std.fs.path.isAbsolute(pattern)) {
|
||||
patterns[0] = pattern;
|
||||
} else if (strings.startsWith(pattern, "./") or strings.startsWith(pattern, "../")) {
|
||||
// Resolve any ../ in the path
|
||||
var buffer: bun.PathBuffer = undefined;
|
||||
const resolved = bun.path.joinAbsStringBuf(bunfig_dir, &buffer, &[_][]const u8{pattern}, .auto);
|
||||
patterns[0] = try allocator.dupe(u8, resolved);
|
||||
} else {
|
||||
patterns[0] = try std.fs.path.join(allocator, &[_][]const u8{ bunfig_dir, pattern });
|
||||
// Use pattern as-is for glob patterns like **/*.test.ts
|
||||
patterns[0] = pattern;
|
||||
}
|
||||
this.ctx.test_options.glob_patterns = patterns;
|
||||
this.ctx.test_options.file_patterns = patterns;
|
||||
},
|
||||
.e_array => |arr| {
|
||||
if (arr.items.len == 0) break :brk;
|
||||
// Set empty array to explicitly disable test discovery
|
||||
if (arr.items.len == 0) {
|
||||
const patterns = try allocator.alloc(string, 0);
|
||||
this.ctx.test_options.file_patterns = patterns;
|
||||
break :brk;
|
||||
}
|
||||
|
||||
const patterns = try allocator.alloc(string, arr.items.len);
|
||||
for (arr.items.slice(), 0..) |item, i| {
|
||||
if (item.data != .e_string) {
|
||||
try this.addError(item.loc, "test.glob array must contain only strings");
|
||||
try this.addError(item.loc, "test.filePatterns array must contain only strings");
|
||||
return;
|
||||
}
|
||||
const pattern = try item.data.e_string.string(allocator);
|
||||
// Resolve pattern relative to bunfig.toml directory if it's not absolute
|
||||
// Only resolve relative to bunfig.toml if it starts with ./ or ../
|
||||
if (std.fs.path.isAbsolute(pattern)) {
|
||||
patterns[i] = pattern;
|
||||
} else if (strings.startsWith(pattern, "./") or strings.startsWith(pattern, "../")) {
|
||||
// Resolve any ../ in the path
|
||||
var buffer: bun.PathBuffer = undefined;
|
||||
const resolved = bun.path.joinAbsStringBuf(bunfig_dir, &buffer, &[_][]const u8{pattern}, .auto);
|
||||
patterns[i] = try allocator.dupe(u8, resolved);
|
||||
} else {
|
||||
patterns[i] = try std.fs.path.join(allocator, &[_][]const u8{ bunfig_dir, pattern });
|
||||
// Use pattern as-is for glob patterns like **/*.test.ts
|
||||
patterns[i] = pattern;
|
||||
}
|
||||
}
|
||||
this.ctx.test_options.glob_patterns = patterns;
|
||||
this.ctx.test_options.file_patterns = patterns;
|
||||
},
|
||||
else => {
|
||||
try this.addError(expr.loc, "test.glob must be a string or array of strings");
|
||||
try this.addError(expr.loc, "test.filePatterns must be a string or array of strings");
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
@@ -328,8 +328,8 @@ pub const Command = struct {
|
||||
test_filter_pattern: ?[]const u8 = null,
|
||||
test_filter_regex: ?*RegularExpression = null,
|
||||
|
||||
/// Test file glob patterns. If specified, these override the default test discovery patterns.
|
||||
glob_patterns: ?[]const string = null,
|
||||
/// Test file patterns. If specified, these override the default test discovery patterns.
|
||||
file_patterns: ?[]const string = null,
|
||||
|
||||
file_reporter: ?TestCommand.FileReporter = null,
|
||||
reporter_outfile: ?[]const u8 = null,
|
||||
|
||||
@@ -15,7 +15,7 @@ options: *BundleOptions,
|
||||
has_iterated: bool = false,
|
||||
search_count: usize = 0,
|
||||
/// Custom glob patterns for test files. If set, these override the default patterns.
|
||||
custom_glob_patterns: ?[]const string = null,
|
||||
custom_file_patterns: ?[]const string = null,
|
||||
|
||||
const log = bun.Output.scoped(.jest, .hidden);
|
||||
const Fifo = std.fifo.LinearFifo(ScanEntry, .Dynamic);
|
||||
@@ -34,7 +34,7 @@ pub fn init(
|
||||
alloc: Allocator,
|
||||
transpiler: *Transpiler,
|
||||
initial_results_capacity: usize,
|
||||
custom_glob_patterns: ?[]const string,
|
||||
custom_file_patterns: ?[]const string,
|
||||
) Allocator.Error!Scanner {
|
||||
const results = try std.ArrayListUnmanaged(bun.PathString).initCapacity(
|
||||
alloc,
|
||||
@@ -45,7 +45,7 @@ pub fn init(
|
||||
.options = &transpiler.options,
|
||||
.fs = transpiler.fs,
|
||||
.test_files = results,
|
||||
.custom_glob_patterns = custom_glob_patterns,
|
||||
.custom_file_patterns = custom_file_patterns,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,14 +134,8 @@ pub fn couldBeTestFile(this: *Scanner, name: []const u8, comptime needs_test_suf
|
||||
if (extname.len == 0 or !this.options.loader(extname).isJavaScriptLike()) return false;
|
||||
if (comptime !needs_test_suffix) return true;
|
||||
|
||||
// If custom glob patterns are provided, use them instead of default patterns
|
||||
if (this.custom_glob_patterns) |patterns| {
|
||||
for (patterns) |pattern| {
|
||||
if (bun.glob.match(bun.default_allocator, pattern, name).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (this.custom_file_patterns != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fall back to default test name suffixes
|
||||
@@ -206,18 +200,41 @@ pub fn next(this: *Scanner, entry: *FileSystem.Entry, fd: bun.StoredFileDescript
|
||||
if (!entry.abs_path.isEmpty()) return;
|
||||
|
||||
this.search_count += 1;
|
||||
if (!this.couldBeTestFile(name, true)) return;
|
||||
|
||||
const parts = &[_][]const u8{ entry.dir, entry.base() };
|
||||
const path = this.fs.absBuf(parts, &this.open_dir_buf);
|
||||
if (this.custom_file_patterns) |patterns| {
|
||||
if (patterns.len == 0) return;
|
||||
|
||||
if (!this.doesAbsolutePathMatchFilter(path)) {
|
||||
const parts = &[_][]const u8{ entry.dir, entry.base() };
|
||||
const path = this.fs.absBuf(parts, &this.open_dir_buf);
|
||||
const rel_path = bun.path.relative(this.fs.top_level_dir, path);
|
||||
if (!this.doesPathMatchFilter(rel_path)) return;
|
||||
}
|
||||
|
||||
entry.abs_path = bun.PathString.init(this.fs.filename_store.append(@TypeOf(path), path) catch unreachable);
|
||||
this.test_files.append(this.allocator(), entry.abs_path) catch unreachable;
|
||||
var matches = false;
|
||||
for (patterns) |pattern| {
|
||||
const path_to_match = if (std.fs.path.isAbsolute(pattern)) path else rel_path;
|
||||
if (bun.glob.match(bun.default_allocator, pattern, path_to_match).matches()) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matches) return;
|
||||
|
||||
entry.abs_path = bun.PathString.init(this.fs.filename_store.append(@TypeOf(path), path) catch unreachable);
|
||||
this.test_files.append(this.allocator(), entry.abs_path) catch unreachable;
|
||||
} else {
|
||||
if (!this.couldBeTestFile(name, true)) return;
|
||||
|
||||
const parts = &[_][]const u8{ entry.dir, entry.base() };
|
||||
const path = this.fs.absBuf(parts, &this.open_dir_buf);
|
||||
|
||||
if (!this.doesAbsolutePathMatchFilter(path)) {
|
||||
const rel_path = bun.path.relative(this.fs.top_level_dir, path);
|
||||
if (!this.doesPathMatchFilter(rel_path)) return;
|
||||
}
|
||||
|
||||
entry.abs_path = bun.PathString.init(this.fs.filename_store.append(@TypeOf(path), path) catch unreachable);
|
||||
this.test_files.append(this.allocator(), entry.abs_path) catch unreachable;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1424,7 +1424,7 @@ pub const TestCommand = struct {
|
||||
//
|
||||
try vm.ensureDebugger(false);
|
||||
|
||||
var scanner = Scanner.init(ctx.allocator, &vm.transpiler, ctx.positionals.len, ctx.test_options.glob_patterns) catch bun.outOfMemory();
|
||||
var scanner = Scanner.init(ctx.allocator, &vm.transpiler, ctx.positionals.len, ctx.test_options.file_patterns) catch bun.outOfMemory();
|
||||
defer scanner.deinit();
|
||||
const has_relative_path = for (ctx.positionals) |arg| {
|
||||
if (std.fs.path.isAbsolute(arg) or
|
||||
|
||||
463
test/cli/test/bunfig-test-filepatterns.test.ts
Normal file
463
test/cli/test/bunfig-test-filepatterns.test.ts
Normal file
@@ -0,0 +1,463 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
test("bunfig test.filePatterns with single string pattern", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-single", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "*.mytest.js"
|
||||
`,
|
||||
"example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom glob test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default pattern test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.spec.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("spec test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Test output goes to stderr by default
|
||||
expect(stderr).toContain("1 pass");
|
||||
// Verify only the mytest.js file was run (1 test)
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with array of patterns", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-array", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = ["*.custom.js", "*.mytest.ts"]
|
||||
`,
|
||||
"example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom js test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.mytest.ts": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom ts test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default pattern test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// 2 tests should run (custom.js and mytest.ts)
|
||||
expect(stderr).toContain("2 pass");
|
||||
expect(stderr).toContain("Ran 2 tests");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with nested directories", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-nested", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "**/*.custom.js"
|
||||
`,
|
||||
"src/example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("nested custom test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"lib/utils/helper.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("deeply nested custom test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("root test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// 2 custom tests in nested directories
|
||||
expect(stderr).toContain("2 pass");
|
||||
expect(stderr).toContain("Ran 2 tests");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with relative paths", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-relative", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "tests/*.unit.js"
|
||||
`,
|
||||
"tests/example.unit.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("unit test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"tests/example.integration.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("integration test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Only 1 test from tests/*.unit.js
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns error handling for invalid type", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-invalid", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = 123
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderr).toContain("test.filePatterns must be a string or array of strings");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns error handling for invalid array element", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-invalid-array", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = ["*.test.js", 123]
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderr).toContain("test.filePatterns array must contain only strings");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns fallback to default patterns when not specified", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-fallback", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
# No glob specified
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.spec.ts": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default spec pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("non-matching pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should run default test patterns (test and spec files)
|
||||
expect(stderr).toContain("2 pass");
|
||||
expect(stderr).toContain("Ran 2 tests");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns resolves paths relative to bunfig.toml location", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-cwd", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "mydir/*.mytest.js"
|
||||
`,
|
||||
"mydir/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("relative path test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("root test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Only the nested test file should run
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with ./ relative path resolution", async () => {
|
||||
// Test that ./ patterns are resolved relative to bunfig.toml location
|
||||
const dir = tempDirWithFiles("test-filepatterns-relative-dot", {
|
||||
"project/bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "./tests/*.mytest.js"
|
||||
`,
|
||||
"project/tests/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("relative dot test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"tests/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("should not run", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"project/example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Run from the project directory where bunfig.toml is located
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: `${dir}/project`,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should only run the test in ./tests relative to bunfig.toml location
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test.skip("bunfig test.filePatterns with ../ relative path resolution", async () => {
|
||||
// Note: This test is skipped because bun test doesn't scan parent directories
|
||||
// when running from a subdirectory, which is expected behavior
|
||||
const dir = tempDirWithFiles("test-filepatterns-relative-dotdot", {
|
||||
"project/config/bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = "../tests/*.mytest.js"
|
||||
`,
|
||||
"project/tests/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("relative parent test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"project/config/tests/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("should not run", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"project/config/example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Run from the config directory where bunfig.toml is located
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: `${dir}/project/config`,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should only run the test in ../tests relative to bunfig.toml
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with absolute paths", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-absolute", {
|
||||
"bunfig.toml": "", // Will be written after we know the absolute path
|
||||
"tests/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("absolute path test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Write bunfig.toml with absolute path
|
||||
const absolutePath = `${dir}/tests/*.mytest.js`;
|
||||
await Bun.write(`${dir}/bunfig.toml`, `
|
||||
[test]
|
||||
filePatterns = "${absolutePath}"
|
||||
`);
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should only run the test specified by absolute path
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("Ran 1 test");
|
||||
});
|
||||
|
||||
test("bunfig test.filePatterns with empty array should not match any files", async () => {
|
||||
const dir = tempDirWithFiles("test-filepatterns-empty", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
filePatterns = []
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// When no test files are found, bun test exits with code 1
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderr).toContain("0 test files matching");
|
||||
});
|
||||
@@ -1,313 +0,0 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
test("bunfig test.glob with single string pattern", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-single", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = "*.mytest.js"
|
||||
`,
|
||||
"example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom glob test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default pattern test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.spec.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("spec test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("custom glob test");
|
||||
expect(stdout).not.toContain("default pattern test");
|
||||
expect(stdout).not.toContain("spec test");
|
||||
});
|
||||
|
||||
test("bunfig test.glob with array of patterns", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-array", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = ["*.custom.js", "*.mytest.ts"]
|
||||
`,
|
||||
"example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom js test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.mytest.ts": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("custom ts test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default pattern test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("custom js test");
|
||||
expect(stdout).toContain("custom ts test");
|
||||
expect(stdout).not.toContain("default pattern test");
|
||||
});
|
||||
|
||||
test("bunfig test.glob with nested directories", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-nested", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = "**/*.custom.js"
|
||||
`,
|
||||
"src/example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("nested custom test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"lib/utils/helper.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("deeply nested custom test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("root test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("nested custom test");
|
||||
expect(stdout).toContain("deeply nested custom test");
|
||||
expect(stdout).not.toContain("root test");
|
||||
});
|
||||
|
||||
test("bunfig test.glob with relative paths", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-relative", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = "tests/*.unit.js"
|
||||
`,
|
||||
"tests/example.unit.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("unit test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"tests/example.integration.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("integration test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("unit test");
|
||||
expect(stdout).not.toContain("integration test");
|
||||
expect(stdout).not.toContain("default test");
|
||||
});
|
||||
|
||||
test("bunfig test.glob error handling for invalid type", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-invalid", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = 123
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderr).toContain("test.glob must be a string or array of strings");
|
||||
});
|
||||
|
||||
test("bunfig test.glob error handling for invalid array element", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-invalid-array", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = ["*.test.js", 123]
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderr).toContain("test.glob array must contain only strings");
|
||||
});
|
||||
|
||||
test("bunfig test.glob fallback to default patterns when not specified", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-fallback", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
# No glob specified
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default test pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.spec.ts": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("default spec pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.custom.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("non-matching pattern", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("default test pattern");
|
||||
expect(stdout).toContain("default spec pattern");
|
||||
expect(stdout).not.toContain("non-matching pattern");
|
||||
});
|
||||
|
||||
test("bunfig test.glob resolves paths relative to bunfig.toml location", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-cwd", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = "mydir/*.mytest.js"
|
||||
`,
|
||||
"mydir/example.mytest.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("relative path test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("root test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--reporter", "junit", "--reporter-outfile", "results.xml"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("relative path test");
|
||||
expect(stdout).not.toContain("root test");
|
||||
});
|
||||
|
||||
test("bunfig test.glob with empty array should not match any files", async () => {
|
||||
const dir = tempDirWithFiles("test-glob-empty", {
|
||||
"bunfig.toml": `
|
||||
[test]
|
||||
glob = []
|
||||
`,
|
||||
"example.test.js": `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// When no test files are found, bun test should exit with code 0 but find 0 tests
|
||||
expect(stdout).toContain("0 pass");
|
||||
});
|
||||
Reference in New Issue
Block a user