diff options
author | 2023-08-10 22:29:53 +0200 | |
---|---|---|
committer | 2023-08-10 13:29:53 -0700 | |
commit | e65535cc054f8bfff98648f0605537a9d734e225 (patch) | |
tree | 05391656703829a2cb1b25b4873adb0aca957a97 | |
parent | 74f9fabd018e09e10ed8e8a8b82035f1e19c2162 (diff) | |
download | bun-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.zig | 95 | ||||
-rw-r--r-- | test/cli/test/bun-test.test.ts | 215 | ||||
-rw-r--r-- | test/js/bun/test/jest-each.test.ts | 14 |
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, ¤t_arg, "%s"); + }, + 'i' => { + try consumeArg(globalThis, current_arg.isAnyInt(), &idx, &args_idx, &list, ¤t_arg, "%i"); + }, + 'd' => { + try consumeArg(globalThis, current_arg.isNumber(), &idx, &args_idx, &list, ¤t_arg, "%d"); + }, + 'f' => { + try consumeArg(globalThis, current_arg.isNumber(), &idx, &args_idx, &list, ¤t_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(); |