aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jacques <25390037+jecquas@users.noreply.github.com> 2023-08-10 22:29:53 +0200
committerGravatar GitHub <noreply@github.com> 2023-08-10 13:29:53 -0700
commite65535cc054f8bfff98648f0605537a9d734e225 (patch)
tree05391656703829a2cb1b25b4873adb0aca957a97
parent74f9fabd018e09e10ed8e8a8b82035f1e19c2162 (diff)
downloadbun-e65535cc054f8bfff98648f0605537a9d734e225.tar.gz
bun-e65535cc054f8bfff98648f0605537a9d734e225.tar.zst
bun-e65535cc054f8bfff98648f0605537a9d734e225.zip
bun test: format description of test.each (#4092)
* bun test: format description * add tests for tests * only --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
-rw-r--r--src/bun.js/test/jest.zig95
-rw-r--r--test/cli/test/bun-test.test.ts215
-rw-r--r--test/js/bun/test/jest-each.test.ts14
3 files changed, 311 insertions, 13 deletions
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 9169c994b..6813af74a 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -1773,6 +1773,83 @@ pub fn printGithubAnnotation(exception: *JSC.ZigException) void {
Output.flush();
}
+fn consumeArg(
+ globalThis: *JSC.JSGlobalObject,
+ should_write: bool,
+ str_idx: *usize,
+ args_idx: *usize,
+ array_list: *std.ArrayListUnmanaged(u8),
+ arg: *const JSC.JSValue,
+ fallback: []const u8,
+) !void {
+ const allocator = getAllocator(globalThis);
+ if (should_write) {
+ const owned_slice = try arg.*.toBunString(globalThis).toOwnedSlice(allocator);
+ defer allocator.free(owned_slice);
+ try array_list.*.appendSlice(allocator, owned_slice);
+ } else {
+ try array_list.appendSlice(allocator, fallback);
+ }
+ str_idx.* += 1;
+ args_idx.* += 1;
+}
+
+// Generate test label by positionally injecting parameters with printf formatting
+fn formatLabel(globalThis: *JSC.JSGlobalObject, label: string, function_args: []JSC.JSValue, test_idx: usize) !string {
+ const allocator = getAllocator(globalThis);
+ var idx: usize = 0;
+ var args_idx: usize = 0;
+ var list = try std.ArrayListUnmanaged(u8).initCapacity(allocator, label.len);
+
+ while (idx < label.len) {
+ const char = label[idx];
+ if (char == '%' and (idx + 1 < label.len) and !(args_idx >= function_args.len)) {
+ const current_arg = function_args[args_idx];
+
+ switch (label[idx + 1]) {
+ 's' => {
+ try consumeArg(globalThis, current_arg.jsType().isString(), &idx, &args_idx, &list, &current_arg, "%s");
+ },
+ 'i' => {
+ try consumeArg(globalThis, current_arg.isAnyInt(), &idx, &args_idx, &list, &current_arg, "%i");
+ },
+ 'd' => {
+ try consumeArg(globalThis, current_arg.isNumber(), &idx, &args_idx, &list, &current_arg, "%d");
+ },
+ 'f' => {
+ try consumeArg(globalThis, current_arg.isNumber(), &idx, &args_idx, &list, &current_arg, "%f");
+ },
+ 'j', 'o' => {
+ var str = bun.String.empty;
+ defer str.deref();
+ current_arg.jsonStringify(globalThis, 0, &str);
+ const owned_slice = try str.toOwnedSlice(allocator);
+ defer allocator.free(owned_slice);
+ try list.appendSlice(allocator, owned_slice);
+ idx += 1;
+ args_idx += 1;
+ },
+ '#' => {
+ const test_index_str = try std.fmt.allocPrint(allocator, "{d}", .{test_idx});
+ defer allocator.free(test_index_str);
+ try list.appendSlice(allocator, test_index_str);
+ idx += 1;
+ },
+ '%' => {
+ try list.append(allocator, '%');
+ idx += 1;
+ },
+ else => {
+ // ignore unrecognized fmt
+ },
+ }
+ } else try list.append(allocator, char);
+ idx += 1;
+ }
+
+ return list.toOwnedSlice(allocator);
+}
+
pub const EachData = struct { strong: JSC.Strong, is_test: bool };
fn eachBind(
@@ -1846,13 +1923,8 @@ fn eachBind(
var iter = array.arrayIterator(globalThis);
+ var test_idx: usize = 0;
while (iter.next()) |item| {
- // TODO: node:util.format() the label
- const label = if (description.isEmptyOrUndefinedOrNull())
- ""
- else
- (description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice();
-
const func_params_length = function.getLength(globalThis);
const item_is_array = !item.isEmptyOrUndefinedOrNull() and item.jsType().isArray();
var arg_size: usize = 1;
@@ -1887,10 +1959,16 @@ fn eachBind(
function_args[0] = item;
}
+ const label = if (description.isEmptyOrUndefinedOrNull())
+ ""
+ else
+ (description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice();
+ const formattedLabel = formatLabel(globalThis, label, function_args, test_idx) catch return .zero;
+
if (each_data.is_test) {
function.protect();
parent.tests.append(allocator, TestScope{
- .label = label,
+ .label = formattedLabel,
.parent = parent,
.tag = parent.tag,
.func = function,
@@ -1907,7 +1985,7 @@ fn eachBind(
} else {
var scope = allocator.create(DescribeScope) catch unreachable;
scope.* = .{
- .label = label,
+ .label = formattedLabel,
.parent = parent,
.file_id = parent.file_id,
.tag = if (parent.is_skip) parent.tag else .pass,
@@ -1918,6 +1996,7 @@ fn eachBind(
_ = ret;
allocator.free(function_args);
}
+ test_idx += 1;
}
}
diff --git a/test/cli/test/bun-test.test.ts b/test/cli/test/bun-test.test.ts
index d26912b7c..c40a11742 100644
--- a/test/cli/test/bun-test.test.ts
+++ b/test/cli/test/bun-test.test.ts
@@ -478,6 +478,221 @@ describe("bun test", () => {
expect(stderr).toMatch(/::error title=error: Oops!::/);
});
});
+ describe(".each", () => {
+ test("should run tests with test.each", () => {
+ const numbers = [
+ [1, 2, 3],
+ [1, 1, 2],
+ [3, 4, 7],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ numbers.forEach(numbers => {
+ expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
+ });
+ });
+ test("should run tests with describe.each", () => {
+ const numbers = [
+ [1, 2, 3],
+ [1, 1, 2],
+ [3, 4, 7],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect, describe } from "bun:test";
+
+ describe.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {\
+ test("addition", () => {
+ expect(a + b).toBe(e);
+ });
+ });
+ `,
+ });
+ numbers.forEach(numbers => {
+ expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
+ });
+ });
+ test("check formatting for %i", () => {
+ const numbers = [
+ [1, 2, 3],
+ [1, 1, 2],
+ [3, 4, 7],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ numbers.forEach(numbers => {
+ expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
+ });
+ });
+ test("check formatting for %f", () => {
+ const numbers = [
+ [1.4, 2.9, 4.3],
+ [1, 1, 2],
+ [3.1, 4.5, 7.6],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("%f + %f = %d", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ numbers.forEach(numbers => {
+ expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
+ });
+ });
+ test("check formatting for %d", () => {
+ const numbers = [
+ [1.4, 2.9, 4.3],
+ [1, 1, 2],
+ [3.1, 4.5, 7.6],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("%f + %f = %d", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ numbers.forEach(numbers => {
+ expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
+ });
+ });
+ test("check formatting for %s", () => {
+ const strings = ["hello", "world", "foo"];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(strings)})("with a string: %s", (s) => {
+ expect(s).toBeType("string");
+ });
+ `,
+ });
+ strings.forEach(s => {
+ expect(stderr).toContain(`with a string: ${s}`);
+ });
+ });
+ test("check formatting for %j", () => {
+ const input = [
+ {
+ foo: "bar",
+ nested: {
+ again: {
+ a: 2,
+ },
+ },
+ },
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(input)})("with an object: %o", (o) => {
+ expect(o).toBe(o);
+ });
+ `,
+ });
+ expect(stderr).toContain(`with an object: ${JSON.stringify(input[0])}`);
+ });
+ test("check formatting for %o", () => {
+ const input = [
+ {
+ foo: "bar",
+ nested: {
+ again: {
+ a: 2,
+ },
+ },
+ },
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(input)})("with an object: %o", (o) => {
+ expect(o).toBe(o);
+ });
+ `,
+ });
+ expect(stderr).toContain(`with an object: ${JSON.stringify(input[0])}`);
+ });
+ test("check formatting for %#", () => {
+ const numbers = [
+ [1, 2, 3],
+ [1, 1, 2],
+ [3, 4, 7],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("test number %#: %i + %i = %i", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ numbers.forEach((_, idx) => {
+ expect(stderr).toContain(`test number ${idx}:`);
+ });
+ });
+ test("check formatting for %%", () => {
+ const numbers = [
+ [1, 2, 3],
+ [1, 1, 2],
+ [3, 4, 7],
+ ];
+
+ const stderr = runTest({
+ args: [],
+ input: `
+ import { test, expect } from "bun:test";
+
+ test.each(${JSON.stringify(numbers)})("test number %#: %i + %i = %i %%", (a, b, e) => {
+ expect(a + b).toBe(e);
+ });
+ `,
+ });
+ expect(stderr).toContain(`%`);
+ });
+ test.todo("check formatting for %p", () => {});
+ });
});
function createTest(input?: string | string[], filename?: string): string {
diff --git a/test/js/bun/test/jest-each.test.ts b/test/js/bun/test/jest-each.test.ts
index fc4145cbe..bb8bd54b4 100644
--- a/test/js/bun/test/jest-each.test.ts
+++ b/test/js/bun/test/jest-each.test.ts
@@ -11,10 +11,10 @@ describe("jest-each", () => {
expect(it.each).toBeTypeOf("function");
expect(it.each([])).toBeTypeOf("function");
});
- it.each(NUMBERS)("add two numbers", (a, b, e) => {
+ it.each(NUMBERS)("%i + %i = %i", (a, b, e) => {
expect(a + b).toBe(e);
});
- it.each(NUMBERS)("add two numbers with callback", (a, b, e, done) => {
+ it.each(NUMBERS)("with callback: %f + %d = %f", (a, b, e, done) => {
expect(a + b).toBe(e);
expect(done).toBeDefined();
// We cast here because we cannot type done when typing args as ...T
@@ -24,7 +24,7 @@ describe("jest-each", () => {
["a", "b", "ab"],
["c", "d", "cd"],
["e", "f", "ef"],
- ])(`adds two strings`, (a, b, res) => {
+ ])("%s + %s = %s", (a, b, res) => {
expect(typeof a).toBe("string");
expect(typeof b).toBe("string");
expect(typeof res).toBe("string");
@@ -37,13 +37,17 @@ describe("jest-each", () => {
{ a: 2, b: 13, e: 15 },
{ a: 2, b: 123, e: 125 },
{ a: 15, b: 13, e: 28 },
- ])("add two numbers with object", ({ a, b, e }, cb) => {
+ ])("add two numbers with object: %o", ({ a, b, e }, cb) => {
expect(a + b).toBe(e);
cb();
});
+
+ it.each([undefined, null, NaN, Infinity])("stringify %#: %j", (arg, cb) => {
+ cb();
+ });
});
-describe.each(["some", "cool", "strings"])("works with describe", s => {
+describe.each(["some", "cool", "strings"])("works with describe: %s", s => {
it(`has access to params : ${s}`, done => {
expect(s).toBeTypeOf("string");
done();