Compare commits

...

2 Commits

Author SHA1 Message Date
pfg
dc89d0cc0e pass test-test.test.ts 2024-12-10 13:18:58 -08:00
pfg
e93b0c4646 Adds support to mark a test file as --todo with {todo: "true"} 2024-12-06 19:40:35 -08:00
11 changed files with 86 additions and 27 deletions

View File

@@ -59,6 +59,8 @@ $ echo $?
1 # this is the exit code of the previous command
```
To mark a file to always run todo tests, use `import { test } from "bun:test" with { todo: true }` in the file.
---
See also:

View File

@@ -123,6 +123,8 @@ my.test.ts:
With this flag, failing todo tests will not cause an error, but todo tests which pass will be marked as failing so you can remove the todo mark or
fix the test.
To mark a file to always run todo tests, use `import { test } from "bun:test" with { todo: true }` in the file.
## `test.only`
To run a particular test or suite of tests use `test.only()` or `describe.only()`. Once declared, running `bun test --only` will only execute tests/suites that have been marked with `.only()`. Running `bun test` without the `--only` option with `test.only()` declared will result in all tests in the given suite being executed _up to_ the test with `.only()`. `describe.only()` functions the same in both execution scenarios.

View File

@@ -64,7 +64,7 @@ pub const TestRunner = struct {
files: File.List = .{},
index: File.Map = File.Map{},
only: bool = false,
run_todo: bool = false,
default_run_todo: bool = false,
last_file: u64 = 0,
bail: u32 = 0,
@@ -229,7 +229,7 @@ pub const TestRunner = struct {
return start;
}
pub fn getOrPutFile(this: *TestRunner, file_path: string) *DescribeScope {
pub fn getOrPutFile(this: *TestRunner, file_path: string, override_run_todo: bool) *DescribeScope {
const entry = this.index.getOrPut(this.allocator, @as(u32, @truncate(bun.hash(file_path)))) catch unreachable;
if (entry.found_existing) {
return this.files.items(.module_scope)[entry.value_ptr.*];
@@ -239,6 +239,7 @@ pub const TestRunner = struct {
scope.* = DescribeScope{
.file_id = file_id,
.test_id_start = @as(Test.ID, @truncate(this.tests.len)),
.run_todo = override_run_todo or this.default_run_todo,
};
this.files.append(this.allocator, .{ .module_scope = scope, .source = logger.Source.initEmptyFile(file_path) }) catch unreachable;
entry.value_ptr.* = file_id;
@@ -530,6 +531,9 @@ pub const Jest = struct {
}
var str = arguments[0].toSlice(globalObject, bun.default_allocator);
defer str.deinit();
const todo = if (arguments.len >= 2) arguments[1].asBoolean() else false;
const slice = str.slice();
if (!std.fs.path.isAbsolute(slice)) {
@@ -537,7 +541,7 @@ pub const Jest = struct {
}
const filepath = Fs.FileSystem.instance.filename_store.append([]const u8, slice) catch unreachable;
var scope = runner.?.getOrPutFile(filepath);
var scope = runner.?.getOrPutFile(filepath, todo);
scope.push();
return Bun__Jest__testModuleObject(globalObject);
@@ -830,6 +834,7 @@ pub const DescribeScope = struct {
done: bool = false,
skip_count: u32 = 0,
tag: Tag = .pass,
run_todo: bool,
fn isWithinOnlyScope(this: *const DescribeScope) bool {
if (this.tag == .only) return true;
@@ -1629,7 +1634,7 @@ pub const TestRunnerTask = struct {
}
}
describe.onTestComplete(globalThis, test_id, result == .skip or (!Jest.runner.?.test_options.run_todo and result == .todo));
describe.onTestComplete(globalThis, test_id, result == .skip or (!describe.run_todo and result == .todo));
Jest.runner.?.runNextTest();
}
@@ -1765,7 +1770,7 @@ inline fn createScope(
}
var is_skip = tag == .skip or
(tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or
(tag == .todo and (function == .zero or !parent.run_todo)) or
(tag != .only and Jest.runner.?.only and parent.tag != .only);
if (is_test) {
@@ -1830,6 +1835,7 @@ inline fn createScope(
.parent = parent,
.file_id = parent.file_id,
.tag = tag_to_use,
.run_todo = parent.run_todo,
};
return scope.run(globalThis, function, &.{});
@@ -2071,7 +2077,7 @@ fn eachBind(globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSVa
}
var is_skip = tag == .skip or
(tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or
(tag == .todo and (function == .zero or !parent.run_todo)) or
(tag != .only and Jest.runner.?.only and parent.tag != .only);
if (Jest.runner.?.filter_regex) |regex| {
@@ -2108,6 +2114,7 @@ fn eachBind(globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSVa
.parent = parent,
.file_id = parent.file_id,
.tag = tag,
.run_todo = parent.run_todo,
};
const ret = scope.run(globalThis, function, function_args);

View File

@@ -585,7 +585,7 @@ pub const Arguments = struct {
ctx.test_options.test_filter_regex = regex;
}
ctx.test_options.update_snapshots = args.flag("--update-snapshots");
ctx.test_options.run_todo = args.flag("--todo");
ctx.test_options.default_run_todo = args.flag("--todo");
ctx.test_options.only = args.flag("--only");
}
@@ -1376,7 +1376,7 @@ pub const Command = struct {
default_timeout_ms: u32 = 5 * std.time.ms_per_s,
update_snapshots: bool = false,
repeat_count: u32 = 0,
run_todo: bool = false,
default_run_todo: bool = false,
only: bool = false,
bail: u32 = 0,
coverage: TestCommand.CodeCoverageOptions = .{},

View File

@@ -1210,7 +1210,7 @@ pub const TestCommand = struct {
.log = ctx.log,
.callback = undefined,
.default_timeout_ms = ctx.test_options.default_timeout_ms,
.run_todo = ctx.test_options.run_todo,
.default_run_todo = ctx.test_options.default_run_todo,
.only = ctx.test_options.only,
.bail = ctx.test_options.bail,
.filter_regex = ctx.test_options.test_filter_regex,

View File

@@ -158,6 +158,8 @@ pub const ImportRecord = struct {
/// Used to prevent running resolve plugins multiple times for the same path
print_namespace_in_path: bool = false,
jest_run_todo: bool = false,
wrap_with_to_esm: bool = false,
wrap_with_to_commonjs: bool = false,
@@ -174,6 +176,7 @@ pub const ImportRecord = struct {
bun,
/// An import to 'bun:test'
bun_test,
bun_test_todo,
/// A builtin module, such as `node:fs` or `bun:sqlite`
builtin,
/// An import to the internal runtime

2
src/js/private.d.ts vendored
View File

@@ -105,7 +105,7 @@ declare module "bun" {
var TOML: {
parse(contents: string): any;
};
function jest(path: string): typeof import("bun:test");
function jest(path: string, todo?: boolean): typeof import("bun:test");
var main: string;
var tty: Array<{ hasColors: boolean }>;
var FFI: any;

View File

@@ -7599,6 +7599,7 @@ pub const Part = struct {
react_fast_refresh,
dirname_filename,
bun_test,
bun_test_todo,
dead_due_to_inlining,
commonjs_named_export,
import_to_convert_from_require,

View File

@@ -8957,6 +8957,7 @@ fn NewParser_(
stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text);
p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import;
p.import_records.items[stmt.import_record_index].jest_run_todo = path.import_tag == .bun_test_todo;
if (stmt.star_name_loc) |star| {
const name = p.loadNameFromRef(stmt.namespace_ref);
@@ -12164,6 +12165,7 @@ fn NewParser_(
type,
embed,
bunBakeGraph,
todo,
};
var has_seen_embed_true = false;
@@ -12232,6 +12234,11 @@ fn NewParser_(
try p.lexer.addRangeError(p.lexer.range(), "'bunBakeGraph' can only be set to 'ssr'", .{}, true);
}
},
.todo => {
if (strings.eqlComptime(string_literal_text, "true")) {
path.import_tag = .bun_test_todo;
}
},
}
}

View File

@@ -998,12 +998,15 @@ fn NewPrinter(
fn printBunJestImportStatement(p: *Printer, import: S.Import) void {
comptime bun.assert(is_bun_platform);
switch (p.options.module_type) {
.cjs => {
printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(__filename)"), "globalThis.Bun.jest(__filename)");
const record = p.import_records[import.import_record_index];
switch (record.jest_run_todo) {
true => switch (p.options.module_type) {
.cjs => printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(__filename, true)"), "globalThis.Bun.jest(__filename, true)"),
else => printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(import.meta.path, true)"), "globalThis.Bun.jest(import.meta.path, true)"),
},
else => {
printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(import.meta.path)"), "globalThis.Bun.jest(import.meta.path)");
false => switch (p.options.module_type) {
.cjs => printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(__filename)"), "globalThis.Bun.jest(__filename)"),
else => printInternalBunImport(p, import, @TypeOf("globalThis.Bun.jest(import.meta.path)"), "globalThis.Bun.jest(import.meta.path)"),
},
}
}
@@ -1681,19 +1684,18 @@ fn NewPrinter(
return;
},
.bun_test => {
if (record.kind == .dynamic) {
if (module_type == .cjs) {
p.print("Promise.resolve(globalThis.Bun.jest(__filename))");
} else {
p.print("Promise.resolve(globalThis.Bun.jest(import.meta.path))");
}
} else if (record.kind == .require) {
if (module_type == .cjs) {
p.print("globalThis.Bun.jest(__filename)");
} else {
p.print("globalThis.Bun.jest(import.meta.path)");
}
if (record.kind == .dynamic) p.print("Promise.resolve(");
p.print("globalThis.Bun.jest(");
switch (module_type) {
.cjs => p.print("__filename"),
else => p.print("import.meta.path"),
}
if (record.jest_run_todo) p.print(", true");
p.print(")");
if (record.kind == .dynamic) p.print(")");
return;
},
else => {},

View File

@@ -0,0 +1,35 @@
import { test, expect } from "bun:test";
import { bunExe, bunEnv, tempDirWithFiles } from "harness";
for (const mode of ["none", "--todo", "withTodo"]) {
for (const shouldPass of [false, true]) {
test("todo tests with " + mode + " that " + (shouldPass ? "passes" : "fails"), () => {
const files = tempDirWithFiles("todo", {
"my.test.ts": /*js*/ `
import { test, expect } from "bun:test"${mode === "withTodo" ? ` with { todo: "true" }` : ""};
test.todo("unimplemented feature", () => {
${shouldPass ? "expect(true).toBe(true);" : "expect(true).toBe(false);"}
});
`,
});
const resultShouldFail = shouldPass && mode !== "none";
const result = Bun.spawnSync({
cmd: [bunExe(), "test", ...(mode === "--todo" ? ["--todo"] : []), "my.test.ts"],
cwd: files,
env: bunEnv,
stdio: ["inherit", "inherit", resultShouldFail ? "pipe" : "inherit"],
});
if (resultShouldFail) {
expect(result.stderr!.toString()).toContain("1 fail");
expect(result.stderr!.toString()).toContain(
"^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.",
);
expect(result.exitCode).toBe(1);
} else {
expect(result.exitCode).toBe(0);
}
});
}
}