diff options
author | 2022-11-28 23:15:12 -0800 | |
---|---|---|
committer | 2022-11-28 23:15:12 -0800 | |
commit | 56884b5f1dd60a2673ef765a5556bd37d00c1292 (patch) | |
tree | 8ab86ac1015bcaf91c5d33851022a325d3e1fcb6 | |
parent | 887496bcf9bc3e87ca18637f4cd059eecc324102 (diff) | |
download | bun-56884b5f1dd60a2673ef765a5556bd37d00c1292.tar.gz bun-56884b5f1dd60a2673ef765a5556bd37d00c1292.tar.zst bun-56884b5f1dd60a2673ef765a5556bd37d00c1292.zip |
toHaveProperty and tests (#1558)
* toHaveProperty and tests
* emoji tests
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 119 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 2 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 72 | ||||
-rw-r--r-- | test/bun.js/test-test.test.ts | 445 |
8 files changed, 532 insertions, 118 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index f9cffcdce..6a6caab67 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -6065,7 +6065,7 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "toHaveLastReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveLastReturnedWithCallback, 1 } }, { "toHaveLength"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveLengthCallback, 1 } }, { "toHaveNthReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveNthReturnedWithCallback, 1 } }, - { "toHaveProperty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHavePropertyCallback, 1 } }, + { "toHaveProperty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHavePropertyCallback, 2 } }, { "toHaveReturnedTimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedTimesCallback, 1 } }, { "toHaveReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedWithCallback, 1 } }, { "toMatch"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchCallback, 1 } }, diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index b2b0973db..3ff0227f7 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2609,6 +2609,125 @@ JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, return JSC::JSValue::encode(object->getIfPropertyExists(globalObject, propertyName)); } +JSC__JSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, JSC__JSValue arg1) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + JSValue value = JSValue::decode(JSValue0); + JSValue path = JSValue::decode(arg1); + + if (path.isString()) { + String pathString = path.toWTFString(globalObject); + uint32_t length = pathString.length(); + + if (length == 0) { + JSValue prop = value.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(prop); + } + + // Jest doesn't check for valid dot/bracket notation. It will skip all "[" and "]", and search for + // an empty string for "." when it's the first or last character of the path, or if there are + // two in a row. + + JSValue currProp = value; + uint32_t i = 0; + uint32_t j = 0; + + // if "." is the only character, it will search for an empty string twice. + if (pathString.characterAt(0) == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + } + + while (i < length) { + UChar ic = pathString.characterAt(i); + while (ic == '[' || ic == ']' || ic == '.') { + i += 1; + if (i == length) { + + if (ic == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(currProp); + } + + // nothing found. + if (j == 0) { + return JSValue::encode({}); + } + + return JSValue::encode(currProp); + } + + UChar previous = ic; + ic = pathString.characterAt(i); + if (previous == '.' && ic == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + continue; + } + } + + j = i; + UChar jc = pathString.characterAt(j); + while (!(jc == '[' || jc == ']' || jc == '.')) { + j += 1; + if (j == length) { + // break and search for property + break; + } + jc = pathString.characterAt(j); + } + + PropertyName propName = PropertyName(Identifier::fromString(vm, pathString.substring(i, j - i))); + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, propName); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + + i = j; + } + + return JSValue::encode(currProp); + } + + if (isArray(globalObject, path)) { + // each item in array is property name, ignore dot/bracket notation + JSValue currProp = value; + forEachInArrayLike(globalObject, path.toObject(globalObject), [&](JSValue item) -> bool { + if (!(item.isString() || item.isNumber())) { + currProp = {}; + return false; + } + + JSString* propNameString = item.toString(globalObject); + RETURN_IF_EXCEPTION(scope, false); + PropertyName propName = PropertyName(propNameString->toIdentifier(globalObject)); + RETURN_IF_EXCEPTION(scope, false); + + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, propName); + RETURN_IF_EXCEPTION(scope, false); + if (currProp.isEmpty()) { + return false; + } + + return true; + }); + + return JSValue::encode(currProp); + } + + return JSValue::encode({}); +} + void JSC__JSValue__getSymbolDescription(JSC__JSValue symbolValue_, JSC__JSGlobalObject* arg1, ZigString* arg2) { diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index d28b71b66..ab8286b06 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3390,6 +3390,10 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("getIfPropertyExistsImpl", .{ this, global, ptr, len }); } + pub fn getIfPropertyExistsFromPath(this: JSValue, global: *JSGlobalObject, path: JSValue) JSValue { + return cppFn("getIfPropertyExistsFromPath", .{ this, global, path }); + } + pub fn getSymbolDescription(this: JSValue, global: *JSGlobalObject, str: *ZigString) void { cppFn("getSymbolDescription", .{ this, global, str }); } @@ -3642,7 +3646,7 @@ pub const JSValue = enum(JSValueReprInt) { return this.asNullableVoid().?; } - pub const Extern = [_][]const u8{ "forEachProperty", "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" }; + pub const Extern = [_][]const u8{ "forEachProperty", "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals", "getIfPropertyExistsFromPath" }; }; extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void; diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 9d335ee90..346cfa50e 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1669539205 +//-- AUTOGENERATED FILE -- 1669680738 #pragma once #include <stddef.h> @@ -497,6 +497,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__fromInt64NoTruncate(JSC__JSGlobalObject* arg CPP_DECL JSC__JSValue JSC__JSValue__fromUInt64NoTruncate(JSC__JSGlobalObject* arg0, uint64_t arg1); CPP_DECL void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSValue__getErrorsProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); +CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2); CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, const unsigned char* arg2, uint32_t arg3); CPP_DECL uint64_t JSC__JSValue__getLengthOfArray(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__getNameProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 20f767d73..55e947f02 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -290,6 +290,7 @@ pub extern fn JSC__JSValue__fromInt64NoTruncate(arg0: ?*JSC__JSGlobalObject, arg pub extern fn JSC__JSValue__fromUInt64NoTruncate(arg0: ?*JSC__JSGlobalObject, arg1: u64) JSC__JSValue; pub extern fn JSC__JSValue__getClassName(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]ZigString) void; pub extern fn JSC__JSValue__getErrorsProperty(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject) JSC__JSValue; +pub extern fn JSC__JSValue__getIfPropertyExistsFromPath(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, JSValue2: JSC__JSValue) JSC__JSValue; pub extern fn JSC__JSValue__getIfPropertyExistsImpl(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]const u8, arg3: u32) JSC__JSValue; pub extern fn JSC__JSValue__getLengthOfArray(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject) u64; pub extern fn JSC__JSValue__getNameProperty(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]ZigString) void; diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index ad2a5d6e8..1dcb9ae0a 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -102,7 +102,7 @@ export default [ }, toHaveProperty: { fn: "toHaveProperty", - length: 1, + length: 2, }, toBeCloseTo: { fn: "toBeCloseTo", diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index dafb7dc0c..34265789f 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -676,6 +676,8 @@ pub const Expect = struct { } pub fn toEqual(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: []const JSValue = _arguments.ptr[0.._arguments.len]; @@ -715,7 +717,75 @@ pub const Expect = struct { return .zero; } - pub const toHaveProperty = notImplementedJSCFn; + pub fn toHaveProperty(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { + defer this.postMatch(globalObject); + + const thisValue = callFrame.this(); + const _arguments = callFrame.arguments(2); + const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toHaveProperty() requires at least 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toHaveProperty must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const expected_property_path = arguments[0]; + expected_property_path.ensureStillAlive(); + const expected_value: ?JSValue = if (arguments.len > 1) arguments[1] else null; + if (expected_value) |ev| ev.ensureStillAlive(); + + const value = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + value.ensureStillAlive(); + + if (!expected_property_path.isString() and !expected_property_path.isIterable(globalObject)) { + globalObject.throw("Expected path must be a string or an array", .{}); + return .zero; + } + + const not = this.op.contains(.not); + var path_string = ZigString.Empty; + expected_property_path.toZigString(&path_string, globalObject); + + const expected_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); + + var pass = !expected_property.isEmpty(); + + if (pass and expected_value != null) { + pass = expected_property.deepEquals(expected_value.?, globalObject); + } + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + if (not) { + if (!expected_property.isEmpty() and expected_value != null) { + globalObject.throw("Expected property \"{any}\" to not be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); + } else { + globalObject.throw("Expected \"{any}\" to not have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + } + } else { + if (!expected_property.isEmpty() and expected_value != null) { + globalObject.throw("Expected property \"{any}\" to be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); + } else { + globalObject.throw("Expected \"{any}\" to have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + } + } + + return .zero; + } + pub const toHaveBeenCalledTimes = notImplementedJSCFn; pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; diff --git a/test/bun.js/test-test.test.ts b/test/bun.js/test-test.test.ts index 79bac9e74..b71f30ff2 100644 --- a/test/bun.js/test-test.test.ts +++ b/test/bun.js/test-test.test.ts @@ -648,7 +648,7 @@ test("properties with the same circularity are equal", () => { expect(c).toEqual(d); }); -test("arrays", () => { +test("toEqual() - arrays", () => { expect([1, 2, 3]).toEqual([1, 2, 3]); expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]); }); @@ -899,118 +899,337 @@ test("testing Bun.deepEquals() using isEqual()", () => { expect(-Infinity).toEqual(-1 / 0); }); -// test("toHaveProperty()", () => { -// const a = new Array(["a", "b", "c"]); -// // expect(a).toHaveProperty("0.1", "b"); -// const b = new Array("a", "b", "c"); -// expect({ a: { b: { c: 1 } } }).toHaveProperty(b); -// const c = { -// a: { b: 1 }, -// "a.b": 2, -// }; -// const d = new Array("a.b"); -// expect(c).toHaveProperty(d, 2); -// const houseForSale = { -// bath: true, -// bedrooms: 4, -// kitchen: { -// amenities: ["oven", "stove", "washer"], -// area: 20, -// wallColor: "white", -// "nice.oven": true, -// }, -// livingroom: { -// amenities: [ -// { -// couch: [ -// ["large", { dimensions: [20, 20] }], -// ["small", { dimensions: [10, 10] }], -// ], -// }, -// ], -// }, -// sunroom: "yes", -// "ceiling.height": 20, -// "nono.nooooo": 3, -// nono: { nooooo: 5 }, -// }; -// expect(houseForSale).toHaveProperty("nono.nooooo"); -// expect(houseForSale).toHaveProperty(["nono", "nooooo"]); -// expect(houseForSale).toHaveProperty(["nono.nooooo"]); -// expect(houseForSale).not.toHaveProperty("."); -// expect(houseForSale).not.toHaveProperty("]"); -// expect(houseForSale).not.toHaveProperty("["); -// expect(houseForSale).not.toHaveProperty("[]"); -// expect(houseForSale).not.toHaveProperty("[[]]"); -// expect(houseForSale).not.toHaveProperty("[["); -// expect(houseForSale).not.toHaveProperty("]]"); -// expect(houseForSale).not.toHaveProperty("[]]"); -// expect(houseForSale).not.toHaveProperty("[[]"); -// expect(houseForSale).not.toHaveProperty(".]"); -// expect(houseForSale).not.toHaveProperty(".["); -// expect(houseForSale).not.toHaveProperty("[."); -// expect(houseForSale).not.toHaveProperty("]."); -// expect(houseForSale).not.toHaveProperty("].["); -// expect(houseForSale).not.toHaveProperty("].]"); -// expect(houseForSale).not.toHaveProperty("[.]"); -// expect(houseForSale).not.toHaveProperty("[.["); -// expect(houseForSale).toHaveProperty("bath"); -// expect(houseForSale).not.toHaveProperty("jacuzzi"); -// // // expect(houseForSale).toHaveProperty("jacuzzi"); -// // // // expect(houseForSale).not.toHaveProperty("bath"); -// expect(houseForSale).toHaveProperty("bath", true); -// expect(houseForSale).not.toHaveProperty("bath", false); -// // // // expect(houseForSale).toHaveProperty("bath", false); -// // // // expect(houseForSale).not.toHaveProperty("bath", true); -// expect(houseForSale).toHaveProperty("bedrooms", 4); -// expect(houseForSale).toHaveProperty(["sunroom"], "yes"); -// expect(houseForSale).toHaveProperty("kitchen.area", 20); -// expect(houseForSale).toHaveProperty("kitchen.amenities", [ -// "oven", -// "stove", -// "washer", -// ]); -// expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 21); -// expect(houseForSale).toHaveProperty(["kitchen", "area"], 20); -// expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 29); -// expect(houseForSale).toHaveProperty( -// ["kitchen", "amenities"], -// ["oven", "stove", "washer"], -// ); -// expect(houseForSale).toHaveProperty("kitchen.amenities[2]", "washer"); -// expect(houseForSale).toHaveProperty(["kitchen", "amenities", 1], "stove"); -// expect(houseForSale).toHaveProperty(["kitchen", "amenities", 0], "oven"); -// expect(houseForSale).toHaveProperty( -// "livingroom.amenities[0].couch[0][1].dimensions[0]", -// 20, -// ); -// expect(houseForSale).toHaveProperty(["kitchen", "nice.oven"]); -// expect(houseForSale).not.toHaveProperty(["kitchen", "open"]); -// expect(houseForSale).toHaveProperty(["ceiling.height"], 20); -// expect({ a: { b: 1 } }).toHaveProperty("a.b"); -// expect({ a: [2, 3, 4] }).toHaveProperty("a.0"); -// expect({ a: [2, 3, 4] }).toHaveProperty("a.1"); -// expect({ a: [2, 3, 4] }).toHaveProperty("a.2"); -// expect({ a: [2, 3, 4] }).toHaveProperty("a[1]"); -// expect([2, 3, 4]).toHaveProperty("1"); -// expect([2, 3, 4]).toHaveProperty("[1]"); -// expect([2, [6, 9], 4]).toHaveProperty("1.1"); -// expect([2, [6, 9], 4]).toHaveProperty("1[1]"); -// expect([2, [6, 9], 4]).toHaveProperty("[1].1"); -// expect([2, [6, 9], 4]).toHaveProperty("[1][1]"); -// expect([2, [6, 9], 4]).toHaveProperty([0], 2); -// expect({ a: { b: 1 } }).toHaveProperty("a.b"); -// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a.2.1.b"); -// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a"); -// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1].b"); -// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1]"); -// expect({ a: [1, 2, [3, { b: 1 }]] }).not.toHaveProperty("a[2][1].c"); -// expect("test").toHaveProperty("length"); -// expect({}).toHaveProperty("constructor"); -// expect({}).toHaveProperty("constructor.name"); -// expect({}).toHaveProperty("constructor.name", "Object"); -// expect(new Date()).toHaveProperty("getTime"); -// }); +test("toHaveProperty() - emojis", () => { + expect({ "π": "thumbs up" }).toHaveProperty("π", "thumbs up"); + expect({ "π©βπ©βπ§βπ§": "family" }).toHaveProperty("π©βπ©βπ§βπ§", "family"); + expect({ "πΆβπ«οΈ": "fog" }).toHaveProperty("πΆβπ«οΈ", "fog"); + expect({ "π©ββ€οΈβπ¨": "couple" }).toHaveProperty("π©ββ€οΈβπ¨", "couple"); + expect({ "π©ββ€οΈβπ¨βπ¨βπ§βπ§": "family" }).toHaveProperty("π©ββ€οΈβπ¨βπ¨βπ§βπ§", "family"); + expect({ "π©ββ€οΈβπ¨βπ¨βπ§": "family" }).toHaveProperty("π©ββ€οΈβπ¨βπ¨βπ§", "family"); + expect({ "π©ββ€οΈβπ¨βπ¨βπ§": "family" }).not.toHaveProperty("π©ββ€οΈβπ¨βπ¨βπ§βπ§", "family"); + + // emojis in array + expect(["π", "π"]).toHaveProperty("0", "π"); + expect(["π", "π"]).toHaveProperty("1", "π"); + expect(["π", "π"]).not.toHaveProperty("0", "π"); + expect(["π", "π"]).not.toHaveProperty("1", "π"); + expect(["π©ββ€οΈβπ¨βπ¨βπ§βπ§"]).toHaveProperty("0", "π©ββ€οΈβπ¨βπ¨βπ§βπ§"); + expect(["π©ββ€οΈβπ¨βπ¨βπ§βπ§"]).toHaveProperty([0], "π©ββ€οΈβπ¨βπ¨βπ§βπ§"); + expect(["πΆβπ«οΈ"]).toHaveProperty([0], "πΆβπ«οΈ"); +}); + +test("toHaveProperty() - dot and bracket notation edge cases", () => { + expect({ a: 1 }).not.toHaveProperty("."); + expect({ a: 1 }).not.toHaveProperty("]"); + expect({ a: 1 }).not.toHaveProperty("["); + expect({ a: 1 }).not.toHaveProperty("[]"); + expect({ a: 1 }).not.toHaveProperty("[[]]"); + expect({ a: 1 }).not.toHaveProperty("[["); + expect({ a: 1 }).not.toHaveProperty("]]"); + expect({ a: 1 }).not.toHaveProperty("[]]"); + expect({ a: 1 }).not.toHaveProperty("[[]"); + expect({ a: 1 }).not.toHaveProperty(".]"); + expect({ a: 1 }).not.toHaveProperty(".["); + expect({ "": 1 }).toHaveProperty("[.", 1); + expect({ a: 1 }).not.toHaveProperty("[."); + expect({ a: 1 }).not.toHaveProperty("]."); + expect({ a: 1 }).not.toHaveProperty("].["); + expect({ a: 1 }).not.toHaveProperty("].]"); + expect({ a: 1 }).not.toHaveProperty("[.]"); + expect({ a: 1 }).not.toHaveProperty("[.["); + + expect([1]).toHaveProperty("[0]", 1); + expect([1]).toHaveProperty("[0][", 1); + expect([1]).toHaveProperty("[0]]", 1); + expect([1]).toHaveProperty("[0][[", 1); + expect([1]).toHaveProperty("[][[[0]", 1); + expect([1]).toHaveProperty("[][[[]][[][][.0", 1); + expect([1]).toHaveProperty("[][[[]][[][][.[][[][[[][][0", 1); + expect([1]).not.toHaveProperty("......1.............", 1); + expect([1]).not.toHaveProperty("......0.............", 1); + expect([1]).not.toHaveProperty(".0", 1); + expect([1]).not.toHaveProperty("0.", 1); + expect([{ "": 1 }]).toHaveProperty("0.", 1); + expect({ "": { "": 1 } }).toHaveProperty(".", 1); + expect({ "": { "": { "": 1 } } }).toHaveProperty("..", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty(".", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty("...", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty("....", 1); + expect([1]).toHaveProperty("0.[[[][][]][[[][[]]]]", 1); + expect([1]).not.toHaveProperty("[0].", 1); + expect([1]).toHaveProperty("0", 1); + expect([1]).toHaveProperty("[].0", 1); + expect([1]).toHaveProperty("[.0", 1); + expect([1]).toHaveProperty("].0", 1); + expect([1]).toHaveProperty("0[]][[[]", 1); + expect([1]).toHaveProperty("[[]][[[][][0", 1); + expect([1]).toHaveProperty("0", 1); + expect([1]).toHaveProperty("0.[", 1); + expect([1]).not.toHaveProperty("0........[", 1); + expect([1]).not.toHaveProperty("0..[", 1); + expect([1]).not.toHaveProperty(".0", 1); + expect([1]).toHaveProperty("[].0", 1); + expect([1]).not.toHaveProperty("[]..0", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]]]].].].0", 1); + expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].].", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].]", 1); + expect([1]).not.toHaveProperty("[.][.[[..]]]]].[.[].].]]0]]].].]", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].0.]]]]].].]", 1); + expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].0.]]] ]].].]", 1); + expect([1]).not.toHaveProperty("0 ", 1); + expect([1]).not.toHaveProperty(" 0 ", 1); + expect([1]).not.toHaveProperty(" 0[] ", 1); + expect([1]).not.toHaveProperty(" 0] ", 1); + expect([1]).not.toHaveProperty(" .[0]", 1); + + expect({ "": 1 }).not.toHaveProperty(".", 1); + expect({ "": 1 }).not.toHaveProperty("]", 1); + expect({ "": 1 }).not.toHaveProperty("[", 1); + expect({ "": 1 }).toHaveProperty("", 1); + + expect({ "": 1 }).not.toHaveProperty("..", 1); + expect({ "": { "": 1 } }).not.toHaveProperty("..", 1); + expect([{ "": 1 }]).toHaveProperty("0.", 1); + expect([{ "": 1 }]).not.toHaveProperty(".0.", 1); + expect({ "": [1] }).toHaveProperty(".0", 1); + expect({ "": [1] }).not.toHaveProperty("..0", 1); + expect([{ "": 1 }]).not.toHaveProperty("0..", 1); + expect([{ "": { "": 1 } }]).toHaveProperty("0..", 1); + + expect([1]).not.toHaveProperty("[0].", 1); + expect([1]).not.toHaveProperty("[0][0]", 1); + expect({ a: [1] }).toHaveProperty("a[[[[[[[[[0]]]", 1); + expect({ "[[[": 0 }).not.toHaveProperty("[[[", 0); +}); + +test("toHaveProperty() - with string or array", () => { + const a = new Array(["a", "b", "c"]); + expect(a).toHaveProperty("0.1", "b"); + const b = new Array("a", "b", "c"); + expect({ a: { b: { c: 1 } } }).toHaveProperty(b); + const c = { + a: { b: 1 }, + "a.b": 2, + }; + const d = new Array("a.b"); + expect(c).toHaveProperty(d, 2); + const houseForSale = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ["oven", "stove", "washer"], + area: 20, + wallColor: "white", + "nice.oven": true, + }, + livingroom: { + amenities: [ + { + couch: [ + ["large", { dimensions: [20, 20] }], + ["small", { dimensions: [10, 10] }], + ], + }, + ], + }, + sunroom: "yes", + "ceiling.height": 20, + "entrance.window": 3, + entrance: { window: 5 }, + }; + expect(houseForSale).toHaveProperty("entrance.window", 5); + expect(houseForSale).toHaveProperty(["entrance", "window"], 5); + expect(houseForSale).toHaveProperty(["entrance.window"], 3); + expect(houseForSale).toHaveProperty("bath"); + expect(houseForSale).not.toHaveProperty("jacuzzi"); + // expect(houseForSale).toHaveProperty("jacuzzi"); + // expect(houseForSale).not.toHaveProperty("bath"); + expect(houseForSale).toHaveProperty("bath", true); + expect(houseForSale).not.toHaveProperty("bath", false); + // expect(houseForSale).toHaveProperty("bath", false); + // expect(houseForSale).not.toHaveProperty("bath", true); + expect(houseForSale).toHaveProperty("bedrooms", 4); + expect(houseForSale).toHaveProperty(["sunroom"], "yes"); + expect(houseForSale).toHaveProperty("kitchen.area", 20); + expect(houseForSale).toHaveProperty("kitchen.amenities", [ + "oven", + "stove", + "washer", + ]); + expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 21); + expect(houseForSale).toHaveProperty(["kitchen", "area"], 20); + expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 29); + expect(houseForSale).toHaveProperty( + ["kitchen", "amenities"], + ["oven", "stove", "washer"], + ); + expect(houseForSale).toHaveProperty("kitchen.amenities[2]", "washer"); + expect(houseForSale).toHaveProperty(["kitchen", "amenities", 1], "stove"); + expect(houseForSale).toHaveProperty(["kitchen", "amenities", 0], "oven"); + expect(houseForSale).toHaveProperty( + "livingroom.amenities[0].couch[0][1].dimensions[0]", + 20, + ); + expect(houseForSale).toHaveProperty(["kitchen", "nice.oven"]); + expect(houseForSale).not.toHaveProperty(["kitchen", "open"]); + expect(houseForSale).toHaveProperty(["ceiling.height"], 20); + expect({ a: { b: 1 } }).toHaveProperty("a.b"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.0"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.1"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.2"); + expect({ a: [2, 3, 4] }).toHaveProperty("a[1]"); + expect([2, 3, 4]).toHaveProperty("1"); + expect([2, 3, 4]).toHaveProperty("[1]"); + expect([2, [6, 9], 4]).toHaveProperty("1.1"); + expect([2, [6, 9], 4]).toHaveProperty("1[1]"); + expect([2, [6, 9], 4]).toHaveProperty("[1].1"); + expect([2, [6, 9], 4]).toHaveProperty("[1][1]"); + expect([2, [6, 9], 4]).toHaveProperty([0], 2); + expect({ a: { b: 1 } }).toHaveProperty("a.b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a.2.1.b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1].b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1]"); + expect({ a: [1, 2, [3, { b: 1 }]] }).not.toHaveProperty("a[2][1].c"); + expect("test").toHaveProperty("length"); + expect({}).toHaveProperty("constructor"); + expect({}).toHaveProperty("constructor.name"); + expect({}).toHaveProperty("constructor.name", "Object"); + expect(new Date()).toHaveProperty("getTime"); +}); + +test("toHaveProperty() - all", () => { + expect({ a: 1 }).toHaveProperty("a"); + expect({ a: 1 }).toHaveProperty("a", 1); + expect({ a: 1 }).not.toHaveProperty("b"); + expect({ a: 1 }).not.toHaveProperty("a", 2); + + // test with object with property "a" with all types of values (including undefined) + expect({ a: undefined }).toHaveProperty("a"); + expect({ a: null }).toHaveProperty("a"); + expect({ a: 0 }).toHaveProperty("a"); + expect({ a: false }).toHaveProperty("a"); + expect({ a: "" }).toHaveProperty("a"); + expect({ a: {} }).toHaveProperty("a"); + expect({ a: [] }).toHaveProperty("a"); + expect({ a: () => {} }).toHaveProperty("a"); + + // test with object with property "a" with all types of values (including undefined) + expect({ a: undefined }).toHaveProperty("a", undefined); + expect({ a: null }).toHaveProperty("a", null); + expect({ a: 0 }).toHaveProperty("a", 0); + expect({ a: false }).toHaveProperty("a", false); + expect({ a: "" }).toHaveProperty("a", ""); + expect({ a: {} }).toHaveProperty("a", {}); + expect({ a: [] }).toHaveProperty("a", []); + expect({ a: () => {} }).not.toHaveProperty("a", () => {}); + + // test with object with property "a" with all types of values (including undefined) + + expect({ a: undefined }).not.toHaveProperty("a", null); + expect({ a: null }).not.toHaveProperty("a", undefined); + expect({ a: 0 }).not.toHaveProperty("a", null); + expect({ a: false }).not.toHaveProperty("a", null); + expect({ a: "" }).not.toHaveProperty("a", null); + expect({ a: {} }).not.toHaveProperty("a", null); + expect({ a: [] }).not.toHaveProperty("a", null); + expect({ a: () => {} }).not.toHaveProperty("a", null); + + expect({ a: undefined }).not.toHaveProperty("a", 0); + expect({ a: null }).not.toHaveProperty("a", 0); + expect({ a: 0 }).not.toHaveProperty("a", 1); + expect({ a: false }).not.toHaveProperty("a", 0); + expect({ a: "" }).not.toHaveProperty("a", 0); + expect({ a: {} }).not.toHaveProperty("a", 0); + expect({ a: [] }).not.toHaveProperty("a", 0); + expect({ a: () => {} }).not.toHaveProperty("a", 0); + + expect({ a: undefined }).not.toHaveProperty("a", false); + expect({ a: null }).not.toHaveProperty("a", false); + expect({ a: 0 }).not.toHaveProperty("a", false); + expect({ a: false }).not.toHaveProperty("a", true); + expect({ a: "" }).not.toHaveProperty("a", false); + expect({ a: {} }).not.toHaveProperty("a", false); + expect({ a: [] }).not.toHaveProperty("a", false); + expect({ a: () => {} }).not.toHaveProperty("a", false); + + expect({ a: undefined }).not.toHaveProperty("a", ""); + expect({ a: null }).not.toHaveProperty("a", ""); + expect({ a: 0 }).not.toHaveProperty("a", ""); + expect({ a: false }).not.toHaveProperty("a", ""); + expect({ a: "" }).not.toHaveProperty("a", "a"); + expect({ a: {} }).not.toHaveProperty("a", ""); + expect({ a: [] }).not.toHaveProperty("a", ""); + expect({ a: () => {} }).not.toHaveProperty("a", ""); + + expect({ a: undefined }).not.toHaveProperty("a", {}); + expect({ a: null }).not.toHaveProperty("a", {}); + expect({ a: 0 }).not.toHaveProperty("a", {}); + expect({ a: false }).not.toHaveProperty("a", {}); + expect({ a: "" }).not.toHaveProperty("a", {}); + expect({ a: {} }).not.toHaveProperty("a", { a: 1 }); + expect({ a: [] }).not.toHaveProperty("a", {}); + expect({ a: () => {} }).not.toHaveProperty("a", {}); + + // test object with property "a" with value set, map, string + expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([3, 2, 1])); + expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty( + "a", + new Map([{ c: 3 }, { b: 2 }, { a: 1 }]), + ); + expect({ a: new String("a") }).toHaveProperty("a", new String("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", "a"); + expect({ a: new String("a") }).not.toHaveProperty("a", "b"); + expect({ a: new String("a") }).not.toHaveProperty("a", new String("b")); + expect({ a: new String("a") }).not.toHaveProperty("a", new Number(1)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(true)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(false)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Object()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Function()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Date()); + expect({ a: new String("a") }).not.toHaveProperty("a", new RegExp()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Error()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Promise(() => {})); + expect({ a: new String("a") }).not.toHaveProperty("a", new WeakSet()); + expect({ a: new String("a") }).not.toHaveProperty("a", new WeakMap()); + expect({ a: new String("a") }).not.toHaveProperty("a", Symbol("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int8Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint8Array()); + expect({ a: new String("a") }).not.toHaveProperty( + "a", + new Uint8ClampedArray(), + ); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int16Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint16Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Float32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Float64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new BigInt64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new BigUint64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new ArrayBuffer()); + expect({ a: new String("a") }).not.toHaveProperty( + "a", + new SharedArrayBuffer(), + ); + expect({ a: new String("a") }).not.toHaveProperty( + "a", + new DataView(new ArrayBuffer(1)), + ); + + // test property equality with sets, maps, objects, arrays, and String + expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([1, 2, 3])); + expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty( + "a", + new Map([{ a: 1 }, { b: 2 }, { c: 3 }]), + ); + expect({ a: { a: 1, b: 2, c: 3 } }).toHaveProperty("a", { a: 1, b: 2, c: 3 }); + expect({ a: [1, 2, 3] }).toHaveProperty("a", [1, 2, 3]); + expect({ a: "a" }).toHaveProperty("a", "a"); + expect({ a: new String("a") }).toHaveProperty("a", new String("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", "a"); +}); test("toBe()", () => { const a = 1; |