diff --git a/packages/bun-types/test.d.ts b/packages/bun-types/test.d.ts index 009781b4a6..8a15f1a026 100644 --- a/packages/bun-types/test.d.ts +++ b/packages/bun-types/test.d.ts @@ -966,11 +966,23 @@ declare module "bun:test" { */ toContainKey(expected: unknown): void; /** + * Asserts that an `object` contains at least one of the provided keys. * Asserts that an `object` contains all the provided keys. * * The value must be an object * * @example + * expect({ a: 'hello', b: 'world' }).toContainAnyKeys(['a']); + * expect({ a: 'hello', b: 'world' }).toContainAnyKeys(['b']); + * expect({ a: 'hello', b: 'world' }).toContainAnyKeys(['b', 'c']); + * expect({ a: 'hello', b: 'world' }).not.toContainAnyKeys(['c']); + * + * @param expected the expected value + */ + toContainAnyKeys(expected: unknown): void; + + /** + * Asserts that an `object` contains all the provided keys. * expect({ a: 'foo', b: 'bar', c: 'baz' }).toContainKeys(['a', 'b']); * expect({ a: 'foo', b: 'bar', c: 'baz' }).toContainKeys(['a', 'b', 'c']); * expect({ a: 'foo', b: 'bar', c: 'baz' }).not.toContainKeys(['a', 'b', 'e']); diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index e1ec1f1fcc..42a969270f 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -852,6 +852,75 @@ pub const Expect = struct { return .zero; } + pub fn toContainAnyKeys( + this: *Expect, + globalObject: *JSC.JSGlobalObject, + callFrame: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + defer this.postMatch(globalObject); + const thisValue = callFrame.this(); + const arguments_ = callFrame.arguments(1); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toContainAnyKeys() takes 1 argument", .{}); + return .zero; + } + + incrementExpectCallCounter(); + + const expected = arguments[0]; + expected.ensureStillAlive(); + const value: JSValue = this.getValue(globalObject, thisValue, "toContainAnyKeys", "expected") orelse return .zero; + + if (!expected.jsType().isArray()) { + globalObject.throwInvalidArgumentType("toContainAnyKeys", "expected", "array"); + return .zero; + } + + const not = this.flags.not; + var pass = false; + + const count = expected.getLength(globalObject); + + var i: u32 = 0; + + while (i < count) : (i += 1) { + const key = expected.getIndex(globalObject, i); + + if (value.hasOwnProperty(globalObject, key.toString(globalObject).getZigString(globalObject))) { + pass = true; + break; + } + } + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = expected.toFmt(globalObject, &formatter); + if (not) { + const received_fmt = value.toFmt(globalObject, &formatter); + const expected_line = "Expected to not contain: {any}\n\nReceived: {any}\n"; + const fmt = comptime getSignature("toContainAnyKeys", "expected", true) ++ "\n\n" ++ expected_line; + globalObject.throwPretty(fmt, .{ expected_fmt, received_fmt }); + return .zero; + } + + const expected_line = "Expected to contain: {any}\n"; + const received_line = "Received: {any}\n"; + const fmt = comptime getSignature("toContainAnyKeys", "expected", false) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); + return .zero; + } + pub fn toContainEqual( this: *Expect, globalObject: *JSC.JSGlobalObject, diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index 195e58fff7..9d5a5b8b14 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -419,6 +419,9 @@ export default [ fn: "toContainKey", length: 1, }, + toContainAnyKeys: { + fn: "toContainAnyKeys", + }, toContainKeys: { fn: "toContainKeys", length: 1, diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index 3ed691272f..65ec21fcf0 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -2251,6 +2251,15 @@ describe("expect()", () => { expect(o).not.toContainKey({ a: "foo" }); }); + test("toContainAnyKeys", () => { + expect({ a: "hello", b: "world" }).toContainAnyKeys(["a"]); + expect({ a: "hello", b: "world" }).toContainAnyKeys(["a", "c"]); + expect({ 1: "test", 2: "test2" }).toContainAnyKeys([1]); + expect({ a: "hello", b: "world" }).toContainAnyKeys(["b"]); + expect({ a: "hello", b: "world" }).toContainAnyKeys(["b", "c"]); + expect({ a: "hello", b: "world" }).not.toContainAnyKeys(["c"]); + }); + test("toContainKeys", () => { expect({ a: "foo", b: "bar", c: "baz" }).toContainKeys(["a", "b"]); expect({ a: "foo", b: "bar", c: "baz" }).toContainKeys(["a", "b", "c"]);