Skip to content

Commit

Permalink
feat: Implemented some jest-extended methods (#9741)
Browse files Browse the repository at this point in the history
Co-authored-by: Ashcon Partovi <[email protected]>
Co-authored-by: Dylan Conway <[email protected]>
  • Loading branch information
3 people authored Jun 9, 2024
1 parent 61cb11d commit 80e4e60
Show file tree
Hide file tree
Showing 8 changed files with 700 additions and 6 deletions.
87 changes: 87 additions & 0 deletions packages/bun-types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,21 @@ declare module "bun:test" {
* @param expected the expected value
*/
toContainKey(expected: unknown): void;
/**
* Asserts that an `object` contains all the provided keys.
*
* The value must be an object
*
* @example
* expect({ a: 'hello', b: 'world' }).toContainAllKeys(['a','b']);
* expect({ a: 'hello', b: 'world' }).toContainAllKeys(['b','a']);
* expect({ 1: 'hello', b: 'world' }).toContainAllKeys([1,'b']);
* expect({ a: 'hello', b: 'world' }).not.toContainAllKeys(['c']);
* expect({ a: 'hello', b: 'world' }).not.toContainAllKeys(['a']);
*
* @param expected the expected value
*/
toContainAllKeys(expected: unknown): void;
/**
* Asserts that an `object` contains at least one of the provided keys.
* Asserts that an `object` contains all the provided keys.
Expand All @@ -971,6 +986,78 @@ declare module "bun:test" {
*/
toContainAnyKeys(expected: unknown): void;

/**
* Asserts that an `object` contain the provided value.
*
* The value must be an object
*
* @example
* const shallow = { hello: "world" };
* const deep = { message: shallow };
* const deepArray = { message: [shallow] };
* const o = { a: "foo", b: [1, "hello", true], c: "baz" };
* expect(shallow).toContainValue("world");
* expect({ foo: false }).toContainValue(false);
* expect(deep).toContainValue({ hello: "world" });
* expect(deepArray).toContainValue([{ hello: "world" }]);
* expect(o).toContainValue("foo", "barr");
* expect(o).toContainValue([1, "hello", true]);
* expect(o).not.toContainValue("qux");
// NOT
* expect(shallow).not.toContainValue("foo");
* expect(deep).not.toContainValue({ foo: "bar" });
* expect(deepArray).not.toContainValue([{ foo: "bar" }]);
*
* @param expected the expected value
*/
toContainValue(expected: unknown): void;

/**
* Asserts that an `object` contain the provided value.
*
* The value must be an object
*
* @example
* const o = { a: 'foo', b: 'bar', c: 'baz' };
* expect(o).toContainValues(['foo']);
* expect(o).toContainValues(['baz', 'bar']);
* expect(o).not.toContainValues(['qux', 'foo']);
* @param expected the expected value
*/
toContainValues(expected: unknown): void;

/**
* Asserts that an `object` contain all the provided values.
*
* The value must be an object
*
* @example
* const o = { a: 'foo', b: 'bar', c: 'baz' };
* expect(o).toContainAllValues(['foo', 'bar', 'baz']);
* expect(o).toContainAllValues(['baz', 'bar', 'foo']);
* expect(o).not.toContainAllValues(['bar', 'foo']);
* @param expected the expected value
*/
toContainAllValues(expected: unknown): void;

/**
* Asserts that an `object` contain any provided value.
*
* The value must be an object
*
* @example
* const o = { a: 'foo', b: 'bar', c: 'baz' };
` * expect(o).toContainAnyValues(['qux', 'foo']);
* expect(o).toContainAnyValues(['qux', 'bar']);
* expect(o).toContainAnyValues(['qux', 'baz']);
* expect(o).not.toContainAnyValues(['qux']);
* @param expected the expected value
*/
toContainAnyValues(expected: unknown): void;

/**
* Asserts that an `object` contains all the provided keys.
* expect({ a: 'foo', b: 'bar', c: 'baz' }).toContainKeys(['a', 'b']);
Expand Down
98 changes: 98 additions & 0 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2546,6 +2546,104 @@ JSC__JSValue JSC__JSValue__keys(JSC__JSGlobalObject* globalObject, JSC__JSValue
RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(globalObject, object, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude)));
}

JSC__JSValue JSC__JSValue__values(JSC__JSGlobalObject* globalObject, JSC__JSValue objectValue)
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

JSC::JSValue targetValue = JSC::JSValue::decode(objectValue);
if (targetValue.isUndefinedOrNull())
return throwVMTypeError(globalObject, scope, "Object.values requires that input parameter not be null or undefined"_s);
JSC::JSObject* target = targetValue.toObject(globalObject);
RETURN_IF_EXCEPTION(scope, { });

if (!target->staticPropertiesReified()) {
target->reifyAllStaticProperties(globalObject);
RETURN_IF_EXCEPTION(scope, { });
}

{
JSC::MarkedArgumentBuffer namedPropertyValues;
bool canUseFastPath = false;
if (!target->canHaveExistingOwnIndexedGetterSetterProperties() && !target->hasNonReifiedStaticProperties()) {
JSC::Structure* targetStructure = target->structure();
if (targetStructure->canPerformFastPropertyEnumerationCommon()) {
canUseFastPath = true;
targetStructure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
if (entry.attributes() & PropertyAttribute::DontEnum)
return true;

if (entry.key()->isSymbol())
return true;

namedPropertyValues.appendWithCrashOnOverflow(target->getDirect(entry.offset()));
return true;
});
}
}

if (canUseFastPath) {
JSC::Structure* arrayStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous);
JSC::MarkedArgumentBuffer indexedPropertyValues;
if (target->canHaveExistingOwnIndexedProperties()) {
target->forEachOwnIndexedProperty<JSC::JSObject::SortMode::Ascending>(globalObject, [&](unsigned, JSC::JSValue value) {
indexedPropertyValues.appendWithCrashOnOverflow(value);
return IterationStatus::Continue;
});
}
RETURN_IF_EXCEPTION(scope, { });

{
JSC::ObjectInitializationScope initializationScope(vm);
JSC::JSArray* result = nullptr;
if (LIKELY(result = JSC::JSArray::tryCreateUninitializedRestricted(initializationScope, nullptr, arrayStructure, indexedPropertyValues.size() + namedPropertyValues.size()))) {
for (unsigned i = 0; i < indexedPropertyValues.size(); ++i)
result->initializeIndex(initializationScope, i, indexedPropertyValues.at(i));
for (unsigned i = 0; i < namedPropertyValues.size(); ++i)
result->initializeIndex(initializationScope, indexedPropertyValues.size() + i, namedPropertyValues.at(i));
return JSC::JSValue::encode(result);
}
}
throwOutOfMemoryError(globalObject, scope);
return { };
}
}

JSC::JSArray* values = JSC::constructEmptyArray(globalObject, nullptr);
RETURN_IF_EXCEPTION(scope, { });

JSC::PropertyNameArray properties(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
target->methodTable()->getOwnPropertyNames(target, globalObject, properties, DontEnumPropertiesMode::Include);
RETURN_IF_EXCEPTION(scope, { });

unsigned index = 0;
auto append = [&] (JSGlobalObject* globalObject, PropertyName propertyName) {
PropertySlot slot(target, PropertySlot::InternalMethodType::GetOwnProperty);
bool hasProperty = target->methodTable()->getOwnPropertySlot(target, globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, void());
if (!hasProperty)
return;
if (slot.attributes() & PropertyAttribute::DontEnum)
return;

JSC::JSValue value;
if (LIKELY(!slot.isTaintedByOpaqueObject()))
value = slot.getValue(globalObject, propertyName);
else
value = target->get(globalObject, propertyName);
RETURN_IF_EXCEPTION(scope, void());

values->putDirectIndex(globalObject, index++, value);
};

for (const auto& propertyName : properties) {
append(globalObject, propertyName);
RETURN_IF_EXCEPTION(scope, { });
}

return JSC::JSValue::encode(values);
}

bool JSC__JSValue__hasOwnProperty(JSC__JSValue jsValue, JSC__JSGlobalObject* globalObject, ZigString key)
{
JSC::VM& vm = globalObject->vm();
Expand Down
20 changes: 14 additions & 6 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3887,8 +3887,8 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("createEmptyArray", .{ global, len });
}

pub fn putRecord(value: JSValue, global: *JSGlobalObject, key: *ZigString, values: [*]ZigString, values_len: usize) void {
return cppFn("putRecord", .{ value, global, key, values, values_len });
pub fn putRecord(value: JSValue, global: *JSGlobalObject, key: *ZigString, values_array: [*]ZigString, values_len: usize) void {
return cppFn("putRecord", .{ value, global, key, values_array, values_len });
}

fn putZigString(value: JSValue, global: *JSGlobalObject, key: *const ZigString, result: JSC.JSValue) void {
Expand Down Expand Up @@ -4307,23 +4307,30 @@ pub const JSValue = enum(JSValueReprInt) {
return String.init(buf.toOwnedSliceLeaky()).toJS(globalThis);
}

pub fn fromEntries(globalThis: *JSGlobalObject, keys_array: [*c]ZigString, values: [*c]ZigString, strings_count: usize, clone: bool) JSValue {
pub fn fromEntries(globalThis: *JSGlobalObject, keys_array: [*c]ZigString, values_array: [*c]ZigString, strings_count: usize, clone: bool) JSValue {
return cppFn("fromEntries", .{
globalThis,
keys_array,
values,
values_array,
strings_count,
clone,
});
}

pub fn keys(globalThis: *JSGlobalObject, value: JSValue) JSValue {
pub fn keys(value: JSValue, globalThis: *JSGlobalObject) JSValue {
return cppFn("keys", .{
globalThis,
value,
});
}

pub fn values(value: JSValue, globalThis: *JSGlobalObject) JSValue {
return cppFn("values", .{
globalThis,
value,
});
}

pub fn hasOwnPropertyValue(this: JSValue, globalThis: *JSGlobalObject, value: JSC.JSValue) bool {
// TODO: add a binding for this
return hasOwnProperty(this, globalThis, value.getZigString(globalThis));
Expand Down Expand Up @@ -4576,7 +4583,7 @@ pub const JSValue = enum(JSValueReprInt) {
return false;
}

return this.jsType().isObject() and keys(globalObject, this).getLength(globalObject) == 0;
return this.jsType().isObject() and keys(this, globalObject).getLength(globalObject) == 0;
}

pub fn isClass(this: JSValue, global: *JSGlobalObject) bool {
Expand Down Expand Up @@ -5536,6 +5543,7 @@ pub const JSValue = enum(JSValueReprInt) {
"jsUndefined",
"jsonStringify",
"keys",
"values",
"kind_",
"parseJSON",
"put",
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/headers.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/bun.js/bindings/headers.zig

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 80e4e60

Please sign in to comment.