aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Tiramify (A.K. Daniel) <94789999+TiranexDev@users.noreply.github.com> 2023-08-09 07:14:30 +0200
committerGravatar GitHub <noreply@github.com> 2023-08-08 22:14:30 -0700
commit40d00a961ec4aa4398e18bada520eaf5791151cc (patch)
tree6d47891914742498df7becf253575d633386919c
parent1941dbbd71c6d6730ca78b21ef2fd20f51124950 (diff)
downloadbun-40d00a961ec4aa4398e18bada520eaf5791151cc.tar.gz
bun-40d00a961ec4aa4398e18bada520eaf5791151cc.tar.zst
bun-40d00a961ec4aa4398e18bada520eaf5791151cc.zip
feat(bun/test): Implement "toSatisfy" & "toIncludeRepeated" (fwup) (#3651)
* Fix merge issues * oop * make codegen * Fix build issues --------- Co-authored-by: dave caruso <me@paperdave.net>
-rw-r--r--packages/bun-types/bun-test.d.ts17
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp62
-rw-r--r--src/bun.js/bindings/generated_classes.zig6
-rw-r--r--src/bun.js/test/expect.zig200
-rw-r--r--src/bun.js/test/jest.classes.ts8
-rw-r--r--test/js/bun/test/jest-extended.test.js76
6 files changed, 367 insertions, 2 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts
index 680d17d2a..f6e968906 100644
--- a/packages/bun-types/bun-test.d.ts
+++ b/packages/bun-types/bun-test.d.ts
@@ -982,6 +982,23 @@ declare module "bun:test" {
*/
toInclude(expected: string): void;
/**
+ * Asserts that a value includes a `string` {times} times.
+ * @param expected the expected substring
+ * @param times the number of times the substring should occur
+ */
+ toIncludeRepeated(expected: string, times: number): void;
+ /**
+ * Checks whether a value satisfies a custom condition.
+ * @param {Function} predicate - The custom condition to be satisfied. It should be a function that takes a value as an argument (in this case the value from expect) and returns a boolean.
+ * @example
+ * expect(1).toSatisfy((val) => val > 0);
+ * expect("foo").toSatisfy((val) => val === "foo");
+ * expect("bar").not.toSatisfy((val) => val === "bun");
+ * @link https://vitest.dev/api/expect.html#tosatisfy
+ * @link https://jest-extended.jestcommunity.dev/docs/matchers/toSatisfy
+ */
+ toSatisfy(predicate: (value: T) => boolean): void;
+ /**
* Asserts that a value starts with a `string`.
*
* @param expected the string to start with
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index 25aca16c4..c2780bd7f 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -2855,6 +2855,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toHaveReturnedWithCallback);
extern "C" EncodedJSValue ExpectPrototype__toInclude(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toIncludeCallback);
+extern "C" EncodedJSValue ExpectPrototype__toIncludeRepeated(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toIncludeRepeatedCallback);
+
extern "C" EncodedJSValue ExpectPrototype__toMatch(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchCallback);
@@ -2867,6 +2870,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchObjectCallback);
extern "C" EncodedJSValue ExpectPrototype__toMatchSnapshot(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchSnapshotCallback);
+extern "C" EncodedJSValue ExpectPrototype__toSatisfy(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toSatisfyCallback);
+
extern "C" EncodedJSValue ExpectPrototype__toStartWith(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback);
@@ -2939,10 +2945,12 @@ static const HashTableValue JSExpectPrototypeTableValues[] = {
{ "toHaveReturnedTimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedTimesCallback, 1 } },
{ "toHaveReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedWithCallback, 1 } },
{ "toInclude"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toIncludeCallback, 1 } },
+ { "toIncludeRepeated"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toIncludeRepeatedCallback, 2 } },
{ "toMatch"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchCallback, 1 } },
{ "toMatchInlineSnapshot"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchInlineSnapshotCallback, 1 } },
{ "toMatchObject"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchObjectCallback, 1 } },
{ "toMatchSnapshot"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchSnapshotCallback, 1 } },
+ { "toSatisfy"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toSatisfyCallback, 1 } },
{ "toStartWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toStartWithCallback, 1 } },
{ "toStrictEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toStrictEqualCallback, 1 } },
{ "toThrow"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toThrowCallback, 1 } },
@@ -4377,6 +4385,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toIncludeCallback, (JSGlobalObject * l
return ExpectPrototype__toInclude(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
+JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toIncludeRepeatedCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSExpect* thisObject = jsDynamicCast<JSExpect*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return ExpectPrototype__toIncludeRepeated(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
@@ -4485,6 +4520,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchSnapshotCallback, (JSGlobalObje
return ExpectPrototype__toMatchSnapshot(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
+JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toSatisfyCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSExpect* thisObject = jsDynamicCast<JSExpect*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
+ return ExpectPrototype__toSatisfy(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index e8cc701a3..9cf63a2e1 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -991,6 +991,8 @@ pub const JSExpect = struct {
@compileLog("Expected Expect.toHaveReturnedWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toHaveReturnedWith)));
if (@TypeOf(Expect.toInclude) != CallbackType)
@compileLog("Expected Expect.toInclude to be a callback but received " ++ @typeName(@TypeOf(Expect.toInclude)));
+ if (@TypeOf(Expect.toIncludeRepeated) != CallbackType)
+ @compileLog("Expected Expect.toIncludeRepeated to be a callback but received " ++ @typeName(@TypeOf(Expect.toIncludeRepeated)));
if (@TypeOf(Expect.toMatch) != CallbackType)
@compileLog("Expected Expect.toMatch to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatch)));
if (@TypeOf(Expect.toMatchInlineSnapshot) != CallbackType)
@@ -999,6 +1001,8 @@ pub const JSExpect = struct {
@compileLog("Expected Expect.toMatchObject to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatchObject)));
if (@TypeOf(Expect.toMatchSnapshot) != CallbackType)
@compileLog("Expected Expect.toMatchSnapshot to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatchSnapshot)));
+ if (@TypeOf(Expect.toSatisfy) != CallbackType)
+ @compileLog("Expected Expect.toSatisfy to be a callback but received " ++ @typeName(@TypeOf(Expect.toSatisfy)));
if (@TypeOf(Expect.toStartWith) != CallbackType)
@compileLog("Expected Expect.toStartWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toStartWith)));
if (@TypeOf(Expect.toStrictEqual) != CallbackType)
@@ -1111,10 +1115,12 @@ pub const JSExpect = struct {
@export(Expect.toHaveReturnedTimes, .{ .name = "ExpectPrototype__toHaveReturnedTimes" });
@export(Expect.toHaveReturnedWith, .{ .name = "ExpectPrototype__toHaveReturnedWith" });
@export(Expect.toInclude, .{ .name = "ExpectPrototype__toInclude" });
+ @export(Expect.toIncludeRepeated, .{ .name = "ExpectPrototype__toIncludeRepeated" });
@export(Expect.toMatch, .{ .name = "ExpectPrototype__toMatch" });
@export(Expect.toMatchInlineSnapshot, .{ .name = "ExpectPrototype__toMatchInlineSnapshot" });
@export(Expect.toMatchObject, .{ .name = "ExpectPrototype__toMatchObject" });
@export(Expect.toMatchSnapshot, .{ .name = "ExpectPrototype__toMatchSnapshot" });
+ @export(Expect.toSatisfy, .{ .name = "ExpectPrototype__toSatisfy" });
@export(Expect.toStartWith, .{ .name = "ExpectPrototype__toStartWith" });
@export(Expect.toStrictEqual, .{ .name = "ExpectPrototype__toStrictEqual" });
@export(Expect.toThrow, .{ .name = "ExpectPrototype__toThrow" });
diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig
index c56818efc..de3f185d5 100644
--- a/src/bun.js/test/expect.zig
+++ b/src/bun.js/test/expect.zig
@@ -2812,6 +2812,206 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toIncludeRepeated(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue {
+ defer this.postMatch(globalThis);
+
+ const thisValue = callFrame.this();
+ const arguments_ = callFrame.arguments(2);
+ const arguments = arguments_.ptr[0..arguments_.len];
+
+ if (arguments.len < 2) {
+ globalThis.throwInvalidArguments("toIncludeRepeated() requires 2 arguments", .{});
+ return .zero;
+ }
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalThis.throw("toIncludeRepeated() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const substring = arguments[0];
+ substring.ensureStillAlive();
+
+ if (!substring.isString()) {
+ globalThis.throw("toIncludeRepeated() requires the first argument to be a string", .{});
+ return .zero;
+ }
+
+ const count = arguments[1];
+ count.ensureStillAlive();
+
+ if (!count.isAnyInt()) {
+ globalThis.throw("toIncludeRepeated() requires the second argument to be a number", .{});
+ return .zero;
+ }
+
+ const countAsNum = count.toU32();
+
+ const expect_string = Expect.capturedValueGetCached(thisValue) orelse {
+ globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ if (!expect_string.isString()) {
+ globalThis.throw("toIncludeRepeated() requires the expect(value) to be a string", .{});
+ return .zero;
+ }
+
+ const not = this.flags.not;
+ var pass = false;
+
+ const _expectStringAsStr = expect_string.toSliceOrNull(globalThis) orelse return .zero;
+ const _subStringAsStr = substring.toSliceOrNull(globalThis) orelse return .zero;
+
+ defer {
+ _expectStringAsStr.deinit();
+ _subStringAsStr.deinit();
+ }
+
+ var expectStringAsStr = _expectStringAsStr.slice();
+ var subStringAsStr = _subStringAsStr.slice();
+
+ if (subStringAsStr.len == 0) {
+ globalThis.throw("toIncludeRepeated() requires the first argument to be a non-empty string", .{});
+ return .zero;
+ }
+
+ if (countAsNum == 0)
+ pass = !strings.contains(expectStringAsStr, subStringAsStr)
+ else
+ pass = std.mem.containsAtLeast(u8, expectStringAsStr, countAsNum, subStringAsStr);
+
+ if (not) pass = !pass;
+ if (pass) return thisValue;
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true };
+ const expect_string_fmt = expect_string.toFmt(globalThis, &formatter);
+ const substring_fmt = substring.toFmt(globalThis, &formatter);
+ const times_fmt = count.toFmt(globalThis, &formatter);
+
+ const received_line = "Received: <red>{any}<r>\n";
+
+ if (not) {
+ if (countAsNum == 0) {
+ const expected_line = "Expected to include: <green>{any}<r> \n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt });
+ } else if (countAsNum == 1) {
+ const expected_line = "Expected not to include: <green>{any}<r> \n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt });
+ } else {
+ const expected_line = "Expected not to include: <green>{any}<r> <green>{any}<r> times \n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, times_fmt, expect_string_fmt });
+ }
+
+ return .zero;
+ }
+
+ if (countAsNum == 0) {
+ const expected_line = "Expected to not include: <green>{any}<r>\n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt });
+ } else if (countAsNum == 1) {
+ const expected_line = "Expected to include: <green>{any}<r>\n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt });
+ } else {
+ const expected_line = "Expected to include: <green>{any}<r> <green>{any}<r> times \n";
+ const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line;
+ globalThis.throwPretty(fmt, .{ substring_fmt, times_fmt, expect_string_fmt });
+ }
+
+ return .zero;
+ }
+
+ pub fn toSatisfy(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue {
+ defer this.postMatch(globalThis);
+
+ const thisValue = callFrame.this();
+ const arguments_ = callFrame.arguments(1);
+ const arguments = arguments_.ptr[0..arguments_.len];
+
+ if (arguments.len < 1) {
+ globalThis.throwInvalidArguments("toSatisfy() requires 1 argument", .{});
+ return .zero;
+ }
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalThis.throw("toSatisfy() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const predicate = arguments[0];
+ predicate.ensureStillAlive();
+
+ if (!predicate.isCallable(globalThis.vm())) {
+ globalThis.throw("toSatisfy() argument must be a function", .{});
+ return .zero;
+ }
+
+ const value = Expect.capturedValueGetCached(thisValue) orelse {
+ globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+ value.ensureStillAlive();
+
+ const result = predicate.call(globalThis, &.{value});
+
+ if (result.toError()) |err| {
+ var errors: [1]*anyopaque = undefined;
+ var _err = errors[0..errors.len];
+
+ _err[0] = err.asVoid();
+
+ const fmt = ZigString.init("toSatisfy() predicate threw an exception");
+ globalThis.vm().throwError(globalThis, globalThis.createAggregateError(_err.ptr, _err.len, &fmt));
+ return .zero;
+ }
+
+ const not = this.flags.not;
+ const pass = (result.isBoolean() and result.toBoolean()) != not;
+
+ if (pass) return thisValue;
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true };
+
+ if (not) {
+ const signature = comptime getSignature("toSatisfy", "<green>expected<r>", true);
+ const fmt = signature ++ "\n\nExpected: not <green>{any}<r>\n";
+ if (Output.enable_ansi_colors) {
+ globalThis.throw(Output.prettyFmt(fmt, true), .{predicate.toFmt(globalThis, &formatter)});
+ return .zero;
+ }
+ globalThis.throw(Output.prettyFmt(fmt, false), .{predicate.toFmt(globalThis, &formatter)});
+ return .zero;
+ }
+
+ const signature = comptime getSignature("toSatisfy", "<green>expected<r>", false);
+
+ const fmt = signature ++ "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>\n";
+
+ if (Output.enable_ansi_colors) {
+ globalThis.throw(Output.prettyFmt(fmt, true), .{
+ predicate.toFmt(globalThis, &formatter),
+ value.toFmt(globalThis, &formatter),
+ });
+ return .zero;
+ }
+
+ globalThis.throw(Output.prettyFmt(fmt, false), .{
+ predicate.toFmt(globalThis, &formatter),
+ value.toFmt(globalThis, &formatter),
+ });
+
+ return .zero;
+ }
+
pub fn toStartWith(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue {
defer this.postMatch(globalThis);
diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts
index c337ab4ec..d40acbf07 100644
--- a/src/bun.js/test/jest.classes.ts
+++ b/src/bun.js/test/jest.classes.ts
@@ -353,6 +353,14 @@ export default [
fn: "toInclude",
length: 1,
},
+ toIncludeRepeated: {
+ fn: "toIncludeRepeated",
+ length: 2,
+ },
+ toSatisfy: {
+ fn: "toSatisfy",
+ length: 1,
+ },
toStartWith: {
fn: "toStartWith",
length: 1,
diff --git a/test/js/bun/test/jest-extended.test.js b/test/js/bun/test/jest-extended.test.js
index 2cdec75fa..816863218 100644
--- a/test/js/bun/test/jest-extended.test.js
+++ b/test/js/bun/test/jest-extended.test.js
@@ -110,7 +110,37 @@ describe("jest-extended", () => {
expect({}).not.toBeNil();
});
- // test('toSatisfy()')
+ test("toSatisfy()", () => {
+ // Arrow functions
+ const isOdd = value => value % 2 === 1;
+ const hasLetterH = (value) => value.includes("H");
+
+ expect(1).toSatisfy(isOdd);
+ expect("Hello").toSatisfy(hasLetterH);
+
+ // Function expressions
+ function hasBunInAnArray(value) { return value.includes("bun"); }
+
+ expect(["bun", "cheese", "patty"]).toSatisfy(hasBunInAnArray);
+ expect(["cheese", "patty"]).not.toSatisfy(hasBunInAnArray);
+
+ // Inline functions
+ expect([]).toSatisfy((value) => value.length === 0);
+ expect([]).not.toSatisfy(value => value.length > 0);
+
+ // Some other types
+ const fooIsBar = (value) => value?.foo === "bar";
+
+ expect({ foo: "bar" }).toSatisfy(fooIsBar);
+ expect({ foo: "bun" }).not.toSatisfy(fooIsBar);
+ expect({ bar: "foo" }).not.toSatisfy(fooIsBar);
+
+ // Test errors
+ // @ts-expect-error
+ expect(() => expect(1).toSatisfy(() => new Error('Bun!'))).toThrow('predicate threw an exception');
+ // @ts-expect-error
+ expect(() => expect(1).not.toSatisfy(() => new Error('Bun!'))).toThrow('predicate threw an exception');
+ });
// Array
@@ -509,7 +539,49 @@ describe("jest-extended", () => {
expect("bob").not.toInclude("alice");
});
- // test("toIncludeRepeated()")
+ test("toIncludeRepeated()", () => {
+ // 0
+ expect("a").toIncludeRepeated("b", 0)
+ expect("b").not.toIncludeRepeated("b", 0);
+
+ // 1
+ expect("abc").toIncludeRepeated("a", 1);
+ expect("abc").not.toIncludeRepeated("d", 1);
+
+ // Any other number
+ expect("abc abc abc").toIncludeRepeated("abc", 1);
+ expect("abc abc abc").toIncludeRepeated("abc", 2);
+ expect("abc abc abc").toIncludeRepeated("abc", 3);
+ expect("abc abc abc").not.toIncludeRepeated("abc", 4);
+
+ // Emojis/Unicode
+ expect("😘πŸ₯³πŸ˜€πŸ˜˜πŸ₯³").toIncludeRepeated("😘", 1);
+ expect("😘πŸ₯³πŸ˜€πŸ˜˜πŸ₯³").toIncludeRepeated("πŸ₯³", 2);
+ expect("😘πŸ₯³πŸ˜€πŸ˜˜πŸ₯³").not.toIncludeRepeated("😘", 3);
+ expect("😘πŸ₯³πŸ˜€πŸ˜˜πŸ₯³").not.toIncludeRepeated("πŸ˜Άβ€πŸŒ«οΈ", 1);
+
+ // Empty string
+ expect("").not.toIncludeRepeated("a", 1);
+
+ // if toIncludeRepeated() is called with a empty string, it should throw an error or else it segfaults
+ expect(() => expect("a").not.toIncludeRepeated("", 1)).toThrow()
+
+ // Just to make sure it doesn't throw an error
+ expect("").not.toIncludeRepeated("a", 1)
+ expect("").not.toIncludeRepeated("πŸ˜Άβ€πŸŒ«οΈ", 1)
+
+ // Expect them to throw an error
+ const tstErr = (y) => { return expect("").toIncludeRepeated("a", y) };
+
+ expect(() => tstErr(1.23)).toThrow();
+ expect(() => tstErr(Infinity)).toThrow();
+ expect(() => tstErr(NaN)).toThrow();
+ expect(() => tstErr(-0)).toThrow(); // -0 and below (-1, -2, ...)
+ expect(() => tstErr(null)).toThrow();
+ expect(() => tstErr(undefined)).toThrow();
+ expect(() => tstErr({})).toThrow();
+ }) ;
+
// test("toIncludeMultiple()")
// test("toEqualIgnoringWhitespace()")