aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/bindings')
-rw-r--r--src/bun.js/bindings/JSBufferList.cpp2
-rw-r--r--src/bun.js/bindings/JSMockFunction.cpp711
-rw-r--r--src/bun.js/bindings/JSMockFunction.h34
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp2
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h1
6 files changed, 490 insertions, 261 deletions
diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp
index 409d50df6..a8cefa710 100644
--- a/src/bun.js/bindings/JSBufferList.cpp
+++ b/src/bun.js/bindings/JSBufferList.cpp
@@ -445,4 +445,4 @@ void JSBufferListConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject*
const ClassInfo JSBufferListConstructor::s_info = { "BufferList"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBufferListConstructor) };
-} // namespace Zig \ No newline at end of file
+} // namespace Zig
diff --git a/src/bun.js/bindings/JSMockFunction.cpp b/src/bun.js/bindings/JSMockFunction.cpp
index fcd80102b..b7c2659b4 100644
--- a/src/bun.js/bindings/JSMockFunction.cpp
+++ b/src/bun.js/bindings/JSMockFunction.cpp
@@ -22,9 +22,31 @@
namespace Bun {
+/**
+ * intended to be used in an if statement as an abstraction over this double if statement
+ *
+ * if(jsValue) {
+ * if(auto value = jsDynamicCast(jsValue)) {
+ * ...
+ * }
+ * }
+ *
+ * the reason this is needed is because jsDynamicCast will segfault if given a zero JSValue
+ */
+template<typename To>
+inline To tryJSDynamicCast(JSValue from)
+{
+ if (UNLIKELY(!from))
+ return nullptr;
+ if (UNLIKELY(!from.isCell()))
+ return nullptr;
+ return jsDynamicCast<To>(from.asCell());
+}
+
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionCall);
JSC_DECLARE_CUSTOM_GETTER(jsMockFunctionGetter_protoImpl);
JSC_DECLARE_CUSTOM_GETTER(jsMockFunctionGetter_mock);
+JSC_DECLARE_HOST_FUNCTION(jsMockFunctionGetter_mockGetLastCall);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionGetMockImplementation);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionGetMockName);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockClear);
@@ -40,8 +62,10 @@ JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockResolvedValue);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockResolvedValueOnce);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockRejectedValue);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockRejectedValueOnce);
+JSC_DECLARE_HOST_FUNCTION(jsMockFunctionWithImplementationCleanup);
JSC_DECLARE_HOST_FUNCTION(jsMockFunctionWithImplementation);
-JSC_DECLARE_HOST_FUNCTION(jsMockFunctionMockImplementationOnce);
+
+uint64_t JSMockModule::s_nextInvocationId = 0;
// This is taken from JSWeakSet
// We only want to hold onto the list of active spies which haven't already been collected
@@ -79,9 +103,7 @@ class JSMockImplementation final : public JSNonFinalObject {
public:
enum class Kind : uint8_t {
Call,
- Promise,
ReturnValue,
- ThrowValue,
ReturnThis,
};
@@ -112,7 +134,14 @@ public:
static constexpr unsigned numberOfInternalFields = 2;
- mutable JSC::WriteBarrier<Unknown> internalFields[2];
+ // either a function or a return value, depends on kind
+ mutable JSC::WriteBarrier<Unknown> underlyingValue;
+
+ // a combination of a pointer to the next implementation and a flag indicating if this is a once implementation
+ // - undefined - no next value
+ // - jsNumber(1) - no next value + is a once implementation
+ // - JSMockImplementation - next value + is a once implementation
+ mutable JSC::WriteBarrier<Unknown> nextValueOrSentinel;
DECLARE_EXPORT_INFO;
DECLARE_VISIT_CHILDREN;
@@ -121,11 +150,7 @@ public:
bool isOnce()
{
- auto secondField = internalFields[1].get();
- if (secondField.isNumber() && secondField.asInt32() == 1) {
- return true;
- }
- return jsDynamicCast<JSMockImplementation*>(secondField.asCell());
+ return !nextValueOrSentinel.get().isUndefined();
}
JSMockImplementation(JSC::VM& vm, JSC::Structure* structure, Kind kind)
@@ -137,8 +162,8 @@ public:
void finishCreation(JSC::VM& vm, JSC::JSValue first, JSC::JSValue second)
{
Base::finishCreation(vm);
- this->internalFields[0].set(vm, this, first);
- this->internalFields[1].set(vm, this, second);
+ this->underlyingValue.set(vm, this, first);
+ this->nextValueOrSentinel.set(vm, this, second);
}
};
@@ -149,32 +174,17 @@ void JSMockImplementation::visitChildrenImpl(JSCell* cell, Visitor& visitor)
ASSERT_GC_OBJECT_INHERITS(fn, info());
Base::visitChildren(fn, visitor);
- visitor.append(fn->internalFields[0]);
- visitor.append(fn->internalFields[1]);
+ visitor.append(fn->underlyingValue);
+ visitor.append(fn->nextValueOrSentinel);
}
DEFINE_VISIT_CHILDREN(JSMockImplementation);
enum class CallbackKind : uint8_t {
- Wrapper,
Call,
GetterSetter,
};
-static NativeFunction jsMockFunctionForCallbackKind(CallbackKind kind)
-{
- switch (kind) {
- // return jsMockFunctionGetterSetter;
- case CallbackKind::Wrapper:
- return jsMockFunctionMockImplementation;
- case CallbackKind::GetterSetter:
- case CallbackKind::Call:
- return jsMockFunctionCall;
- default:
- RELEASE_ASSERT_NOT_REACHED();
- }
-}
-
const ClassInfo JSMockImplementation::s_info = { "MockImpl"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMockImplementation) };
class JSMockFunction : public JSC::InternalFunction {
@@ -197,12 +207,20 @@ public:
DECLARE_VISIT_CHILDREN;
JSC::LazyProperty<JSMockFunction, JSObject> mock;
+ // three pointers to implementation objects
+ // head of the list, this one is run next
mutable JSC::WriteBarrier<JSC::Unknown> implementation;
+ // this contains the non-once implementation. there is only ever one of these
+ mutable JSC::WriteBarrier<JSC::Unknown> fallbackImplmentation;
+ // the last once implementation
+ mutable JSC::WriteBarrier<JSC::Unknown> tail;
+ // original implementation from spy. separate from `implementation` so restoration always works
+ mutable JSC::WriteBarrier<JSC::Unknown> spyOriginal;
mutable JSC::WriteBarrier<JSC::JSArray> calls;
mutable JSC::WriteBarrier<JSC::JSArray> contexts;
+ mutable JSC::WriteBarrier<JSC::JSArray> invocationCallOrder;
mutable JSC::WriteBarrier<JSC::JSArray> instances;
mutable JSC::WriteBarrier<JSC::JSArray> returnValues;
- mutable JSC::WriteBarrier<JSC::Unknown> tail;
JSC::Weak<JSObject> spyTarget;
JSC::Identifier spyIdentifier;
@@ -214,6 +232,28 @@ public:
this->putDirect(vm, vm.propertyNames->name, jsString(vm, name), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly);
}
+ void copyNameAndLength(JSC::VM& vm, JSGlobalObject* global, JSC::JSValue value)
+ {
+ auto catcher = DECLARE_CATCH_SCOPE(vm);
+ WTF::String nameToUse;
+ if (auto* fn = jsDynamicCast<JSFunction*>(value)) {
+ nameToUse = fn->name(vm);
+ JSValue lengthJSValue = fn->get(global, vm.propertyNames->length);
+ if (lengthJSValue.isNumber()) {
+ this->putDirect(vm, vm.propertyNames->length, (lengthJSValue), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly);
+ }
+ } else if (auto* fn = jsDynamicCast<InternalFunction*>(value)) {
+ nameToUse = fn->name();
+ } else {
+ nameToUse = "mockConstructor"_s;
+ }
+ this->setName(nameToUse);
+
+ if (catcher.exception()) {
+ catcher.clearException();
+ }
+ }
+
void initMock()
{
mock.initLater(
@@ -226,11 +266,12 @@ public:
object->putDirectOffset(init.vm, 1, mock->getContexts());
object->putDirectOffset(init.vm, 2, mock->getInstances());
object->putDirectOffset(init.vm, 3, mock->getReturnValues());
+ object->putDirectOffset(init.vm, 4, mock->getInvocationCallOrder());
init.set(object);
});
}
- void reset()
+ void clear()
{
this->calls.clear();
this->instances.clear();
@@ -242,12 +283,22 @@ public:
}
}
+ void reset()
+ {
+ this->clear();
+ this->implementation.clear();
+ this->fallbackImplmentation.clear();
+ this->tail.clear();
+ }
+
void clearSpy()
{
+ this->reset();
+
if (auto* target = this->spyTarget.get()) {
- JSValue implValue = jsUndefined();
- if (auto* impl = jsDynamicCast<JSMockImplementation*>(this->implementation.get())) {
- implValue = impl->internalFields[0].get();
+ JSValue implValue = this->spyOriginal.get();
+ if (!implValue) {
+ implValue = jsUndefined();
}
// Reset the spy back to the original value.
@@ -295,6 +346,15 @@ public:
}
return val;
}
+ JSArray* getInvocationCallOrder() const
+ {
+ JSArray* val = invocationCallOrder.get();
+ if (!val) {
+ val = JSC::constructEmptyArray(globalObject(), nullptr, 0);
+ this->invocationCallOrder.set(vm(), this, val);
+ }
+ return val;
+ }
template<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
@@ -310,7 +370,7 @@ public:
}
JSMockFunction(JSC::VM& vm, JSC::Structure* structure, CallbackKind wrapKind)
- : Base(vm, structure, jsMockFunctionForCallbackKind(wrapKind), jsMockFunctionForCallbackKind(wrapKind))
+ : Base(vm, structure, jsMockFunctionCall, jsMockFunctionCall)
{
initMock();
}
@@ -324,41 +384,56 @@ void JSMockFunction::visitChildrenImpl(JSCell* cell, Visitor& visitor)
Base::visitChildren(fn, visitor);
visitor.append(fn->implementation);
+ visitor.append(fn->tail);
+ visitor.append(fn->fallbackImplmentation);
visitor.append(fn->calls);
visitor.append(fn->contexts);
visitor.append(fn->instances);
visitor.append(fn->returnValues);
- visitor.append(fn->tail);
+ visitor.append(fn->invocationCallOrder);
fn->mock.visit(visitor);
}
DEFINE_VISIT_CHILDREN(JSMockFunction);
-static void pushImplInternal(JSMockFunction* fn, JSGlobalObject* jsGlobalObject, JSMockImplementation::Kind kind, JSValue value, bool isOnce)
+static void pushImpl(JSMockFunction* fn, JSGlobalObject* jsGlobalObject, JSMockImplementation::Kind kind, JSValue value)
{
Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(jsGlobalObject);
auto& vm = globalObject->vm();
- JSMockImplementation* impl = JSMockImplementation::create(globalObject, globalObject->mockModule.mockImplementationStructure.getInitializedOnMainThread(globalObject), kind, value, isOnce);
- JSValue currentTail = fn->tail.get();
- JSValue currentImpl = fn->implementation.get();
- if (currentTail) {
- if (auto* current = jsDynamicCast<JSMockImplementation*>(currentTail)) {
- current->internalFields[1].set(vm, current, impl);
- }
+
+ if (auto* current = tryJSDynamicCast<JSMockImplementation*>(fn->fallbackImplmentation.get())) {
+ current->underlyingValue.set(vm, current, value);
+ current->kind = kind;
+ return;
}
- fn->tail.set(vm, fn, impl);
- if (!currentImpl || !currentImpl.inherits<JSMockImplementation>()) {
+
+ JSMockImplementation* impl = JSMockImplementation::create(globalObject, globalObject->mockModule.mockImplementationStructure.getInitializedOnMainThread(globalObject), kind, value, false);
+ fn->fallbackImplmentation.set(vm, fn, impl);
+ if (auto* tail = tryJSDynamicCast<JSMockImplementation*>(fn->tail.get())) {
+ tail->nextValueOrSentinel.set(vm, tail, impl);
+ } else {
fn->implementation.set(vm, fn, impl);
}
}
-static void pushImpl(JSMockFunction* fn, JSGlobalObject* globalObject, JSMockImplementation::Kind kind, JSValue value)
+static void pushImplOnce(JSMockFunction* fn, JSGlobalObject* jsGlobalObject, JSMockImplementation::Kind kind, JSValue value)
{
- pushImplInternal(fn, globalObject, kind, value, false);
-}
+ Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(jsGlobalObject);
+ auto& vm = globalObject->vm();
-static void pushImplOnce(JSMockFunction* fn, JSGlobalObject* globalObject, JSMockImplementation::Kind kind, JSValue value)
-{
- pushImplInternal(fn, globalObject, kind, value, true);
+ JSMockImplementation* impl = JSMockImplementation::create(globalObject, globalObject->mockModule.mockImplementationStructure.getInitializedOnMainThread(globalObject), kind, value, true);
+
+ if (!fn->implementation.get()) {
+ fn->implementation.set(vm, fn, impl);
+ }
+ if (auto* tail = tryJSDynamicCast<JSMockImplementation*>(fn->tail.get())) {
+ tail->nextValueOrSentinel.set(vm, tail, impl);
+ } else {
+ fn->implementation.set(vm, fn, impl);
+ }
+ if (auto fallback = fn->fallbackImplmentation.get()) {
+ impl->nextValueOrSentinel.set(vm, impl, fallback);
+ }
+ fn->tail.set(vm, fn, impl);
}
class JSMockFunctionPrototype final : public JSC::JSNonFinalObject {
@@ -409,8 +484,8 @@ static const HashTableValue JSMockFunctionPrototypeTableValues[] = {
{ "mockReturnValueOnce"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockReturnValueOnce, 1 } },
{ "mockResolvedValue"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockResolvedValue, 1 } },
{ "mockResolvedValueOnce"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockResolvedValueOnce, 1 } },
- { "mockRejectedValueOnce"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockRejectedValue, 1 } },
- { "mockRejectedValue"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockRejectedValueOnce, 1 } },
+ { "mockRejectedValue"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockRejectedValue, 1 } },
+ { "mockRejectedValueOnce"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMockFunctionMockRejectedValueOnce, 1 } },
};
const ClassInfo JSMockFunction::s_info = { "Mock"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMockFunction) };
@@ -446,7 +521,6 @@ extern "C" void JSMock__resetSpies(Zig::GlobalObject* globalObject)
continue;
auto* spyObject = jsCast<JSMockFunction*>(spy);
- spyObject->reset();
spyObject->clearSpy();
}
globalObject->mockModule.activeSpies.clear();
@@ -508,20 +582,7 @@ extern "C" EncodedJSValue JSMock__spyOn(JSC::JSGlobalObject* lexicalGlobalObject
if (hasValue)
attributes = slot.attributes();
- {
- auto catcher = DECLARE_CATCH_SCOPE(vm);
- WTF::String nameToUse;
- if (auto* fn = jsDynamicCast<JSFunction*>(value)) {
- nameToUse = fn->name(vm);
- } else if (auto* fn = jsDynamicCast<InternalFunction*>(value)) {
- nameToUse = fn->name();
- }
- if (nameToUse.length()) {
- mock->setName(nameToUse);
- }
- if (catcher.exception())
- catcher.clearException();
- }
+ mock->copyNameAndLength(vm, globalObject, value);
attributes |= PropertyAttribute::Function;
object->putDirect(vm, propertyKey, mock, attributes);
@@ -534,11 +595,14 @@ extern "C" EncodedJSValue JSMock__spyOn(JSC::JSGlobalObject* lexicalGlobalObject
attributes |= PropertyAttribute::Accessor;
object->putDirect(vm, propertyKey, JSC::GetterSetter::create(vm, globalObject, mock, mock), attributes);
+ // mock->setName(propertyKey.publicName());
RETURN_IF_EXCEPTION(scope, {});
pushImpl(mock, globalObject, JSMockImplementation::Kind::ReturnValue, value);
}
+ mock->spyOriginal.set(vm, mock, value);
+
if (!globalObject->mockModule.activeSpies) {
ActiveSpySet* activeSpies = ActiveSpySet::create(vm, globalObject->mockModule.activeSpySetStructure.getInitializedOnMainThread(globalObject));
globalObject->mockModule.activeSpies.set(vm, activeSpies);
@@ -561,7 +625,6 @@ JSMockModule JSMockModule::create(JSC::JSGlobalObject* globalObject)
mock.mockFunctionStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
auto* prototype = JSMockFunctionPrototype::create(init.vm, init.owner, JSMockFunctionPrototype::createStructure(init.vm, init.owner, init.owner->functionPrototype()));
-
init.set(JSMockFunction::createStructure(init.vm, init.owner, prototype));
});
mock.mockResultStructure.initLater(
@@ -602,10 +665,25 @@ JSMockModule JSMockModule::create(JSC::JSGlobalObject* globalObject)
mock.mockObjectStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(init.owner);
- JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(
+
+ auto* prototype = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
+ // `putDirectCustomAccessor` doesn't pass the `this` value as expected. unfortunatly we
+ // need to use a JSFunction for the getter and assign it via `putDirectAccessor` instead.
+ prototype->putDirectAccessor(
globalObject,
- globalObject->objectPrototype(),
- 4);
+ JSC::Identifier::fromString(init.vm, "lastCall"_s),
+ JSC::GetterSetter::create(
+ init.vm,
+ globalObject,
+ JSC::JSFunction::create(init.vm, init.owner, 0, "lastCall"_s, jsMockFunctionGetter_mockGetLastCall, ImplementationVisibility::Public),
+ jsUndefined()),
+ JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
+
+ JSC::Structure* structure
+ = globalObject->structureCache().emptyObjectStructureForPrototype(
+ globalObject,
+ prototype,
+ 5);
JSC::PropertyOffset offset;
structure = structure->addPropertyTransition(
init.vm,
@@ -631,9 +709,23 @@ JSMockModule JSMockModule::create(JSC::JSGlobalObject* globalObject)
JSC::Identifier::fromString(init.vm, "results"_s),
JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly,
offset);
+ structure = structure->addPropertyTransition(
+ init.vm,
+ structure,
+ JSC::Identifier::fromString(init.vm, "invocationCallOrder"_s),
+ JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly,
+ offset);
init.set(structure);
});
+ mock.withImplementationCleanupFunction.initLater(
+ [](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSFunction>::Initializer& init) {
+ init.set(JSC::JSFunction::create(init.vm, init.owner, 2, String(), jsMockFunctionWithImplementationCleanup, ImplementationVisibility::Public));
+ });
+ mock.mockWithImplementationCleanupDataStructure.initLater(
+ [](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
+ init.set(Bun::MockWithImplementationCleanupData::createStructure(init.vm, init.owner, init.owner->objectPrototype()));
+ });
return mock;
}
@@ -705,8 +797,8 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
1);
calls->initializeIndex(object, 0, argumentsArray);
+ fn->calls.set(vm, fn, calls);
}
- fn->calls.set(vm, fn, calls);
JSC::JSArray* contexts = fn->contexts.get();
if (contexts) {
@@ -718,39 +810,51 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
1);
contexts->initializeIndex(object, 0, thisValue);
+ fn->contexts.set(vm, fn, contexts);
}
- fn->contexts.set(vm, fn, contexts);
- JSValue implementationValue = fn->implementation.get();
- if (!implementationValue)
- implementationValue = jsUndefined();
+ auto invocationId = JSMockModule::nextInvocationId();
+ JSC::JSArray* invocationCallOrder = fn->invocationCallOrder.get();
+ if (invocationCallOrder) {
+ invocationCallOrder->push(globalObject, jsNumber(invocationId));
+ } else {
+ JSC::ObjectInitializationScope object(vm);
+ invocationCallOrder = JSC::JSArray::tryCreateUninitializedRestricted(
+ object,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ 1);
+ invocationCallOrder->initializeIndex(object, 0, jsNumber(invocationId));
+ fn->invocationCallOrder.set(vm, fn, invocationCallOrder);
+ }
- if (auto* impl = jsDynamicCast<JSMockImplementation*>(implementationValue)) {
- if (JSValue nextValue = impl->internalFields[1].get()) {
- if (nextValue.inherits<JSMockImplementation>() || (nextValue.isInt32() && nextValue.asInt32() == 1)) {
- fn->implementation.set(vm, fn, nextValue);
- }
+ unsigned int returnValueIndex = 0;
+ auto setReturnValue = [&](JSC::JSValue value) -> void {
+ if (auto* returnValuesArray = fn->returnValues.get()) {
+ returnValuesArray->push(globalObject, value);
+ returnValueIndex = returnValuesArray->length() - 1;
+ } else {
+ JSC::ObjectInitializationScope object(vm);
+ returnValuesArray = JSC::JSArray::tryCreateUninitializedRestricted(
+ object,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ 1);
+ returnValuesArray->initializeIndex(object, 0, value);
+ fn->returnValues.set(vm, fn, returnValuesArray);
}
+ };
- unsigned int returnValueIndex = 0;
- auto setReturnValue = [&](JSC::JSValue value) -> void {
- if (auto* returnValuesArray = fn->returnValues.get()) {
- returnValuesArray->push(globalObject, value);
- returnValueIndex = returnValuesArray->length() - 1;
- } else {
- JSC::ObjectInitializationScope object(vm);
- returnValuesArray = JSC::JSArray::tryCreateUninitializedRestricted(
- object,
- globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
- 1);
- returnValuesArray->initializeIndex(object, 0, value);
- fn->returnValues.set(vm, fn, returnValuesArray);
+ if (auto* impl = tryJSDynamicCast<JSMockImplementation*>(fn->implementation.get())) {
+ if (impl->isOnce()) {
+ auto next = impl->nextValueOrSentinel.get();
+ fn->implementation.set(vm, fn, next);
+ if (next.isNumber() || !jsDynamicCast<JSMockImplementation*>(next)->isOnce()) {
+ fn->tail.clear();
}
- };
+ }
switch (impl->kind) {
case JSMockImplementation::Kind::Call: {
- JSValue result = impl->internalFields[0].get();
+ JSValue result = impl->underlyingValue.get();
JSC::CallData callData = JSC::getCallData(result);
if (UNLIKELY(callData.type == JSC::CallData::Type::None)) {
throwTypeError(globalObject, scope, "Expected mock implementation to be callable"_s);
@@ -783,9 +887,8 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje
return JSValue::encode(returnValue);
}
- case JSMockImplementation::Kind::ReturnValue:
- case JSMockImplementation::Kind::Promise: {
- JSValue returnValue = impl->internalFields[0].get();
+ case JSMockImplementation::Kind::ReturnValue: {
+ JSValue returnValue = impl->underlyingValue.get();
setReturnValue(createMockResult(vm, globalObject, "return"_s, returnValue));
return JSValue::encode(returnValue);
}
@@ -799,6 +902,7 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje
}
}
+ setReturnValue(createMockResult(vm, globalObject, "return"_s, jsUndefined()));
return JSValue::encode(jsUndefined());
}
@@ -820,10 +924,9 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionGetMockImplementation, (JSC::JSGlobalObje
throwTypeError(globalObject, scope, "Expected Mock"_s);
}
- JSValue impl = thisObject->implementation.get();
- if (auto* implementation = jsDynamicCast<JSMockImplementation*>(impl)) {
+ if (auto* implementation = tryJSDynamicCast<JSMockImplementation*>(thisObject->implementation.get())) {
if (implementation->kind == JSMockImplementation::Kind::Call) {
- RELEASE_AND_RETURN(scope, JSValue::encode(implementation->internalFields[0].get()));
+ RELEASE_AND_RETURN(scope, JSValue::encode(implementation->underlyingValue.get()));
}
}
@@ -851,41 +954,59 @@ JSC_DEFINE_CUSTOM_GETTER(jsMockFunctionGetter_protoImpl, (JSC::JSGlobalObject *
return {};
}
- if (auto implValue = thisObject->implementation.get()) {
- if (auto* impl = jsDynamicCast<JSMockImplementation*>(implValue)) {
- if (impl->kind == JSMockImplementation::Kind::Call) {
- return JSValue::encode(impl->internalFields[0].get());
- }
-
- return JSValue::encode(jsUndefined());
+ if (auto* impl = tryJSDynamicCast<JSMockImplementation*>(thisObject->implementation.get())) {
+ if (impl->kind == JSMockImplementation::Kind::Call) {
+ return JSValue::encode(impl->underlyingValue.get());
}
}
return JSValue::encode(jsUndefined());
}
-#define HANDLE_STATIC_CALL \
- if (!thisObject->implementation.get()) { \
- thisObject = JSMockFunction::create( \
- vm, \
- globalObject, \
- reinterpret_cast<Zig::GlobalObject*>(globalObject)->mockModule.mockFunctionStructure.getInitializedOnMainThread(globalObject), \
- CallbackKind::Call); \
+JSC_DEFINE_HOST_FUNCTION(jsMockFunctionConstructor, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callframe))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto thisObject_ = callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict());
+ JSMockFunction* thisObject = JSMockFunction::create(
+ vm,
+ globalObject,
+ globalObject->mockModule.mockFunctionStructure.getInitializedOnMainThread(globalObject));
+
+ if (UNLIKELY(!thisObject)) {
+ throwOutOfMemoryError(globalObject, scope);
+ return {};
+ }
+
+ if (callframe->argumentCount() > 0) {
+ JSValue value = callframe->argument(0);
+ if (value.isCallable()) {
+ thisObject->copyNameAndLength(vm, lexicalGlobalObject, value);
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Call, value);
+ } else {
+ // jest doesn't support doing `jest.fn(10)`, but we support it.
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, value);
+ thisObject->setName("mockConstructor"_s);
+ }
+ } else {
+ thisObject->setName("mockConstructor"_s);
}
+ return JSValue::encode(thisObject);
+}
+
extern "C" EncodedJSValue JSMockFunction__createObject(Zig::GlobalObject* globalObject)
{
- return JSValue::encode(
- JSMockFunction::create(globalObject->vm(), globalObject, globalObject->mockModule.mockFunctionStructure.getInitializedOnMainThread(globalObject), CallbackKind::Wrapper));
+ auto& vm = globalObject->vm();
+ return JSValue::encode(JSC::JSFunction::create(vm, globalObject, 0, "mock"_s, jsMockFunctionConstructor, ImplementationVisibility::Public));
}
-
extern "C" EncodedJSValue JSMockFunction__getCalls(EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);
- if (value) {
- if (auto* mock = jsDynamicCast<JSMockFunction*>(value)) {
- return JSValue::encode(mock->getCalls());
- }
+ if (auto* mock = tryJSDynamicCast<JSMockFunction*>(value)) {
+ return JSValue::encode(mock->getCalls());
}
return JSValue::encode({});
@@ -893,10 +1014,8 @@ extern "C" EncodedJSValue JSMockFunction__getCalls(EncodedJSValue encodedValue)
extern "C" EncodedJSValue JSMockFunction__getReturns(EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);
- if (value) {
- if (auto* mock = jsDynamicCast<JSMockFunction*>(value)) {
- return JSValue::encode(mock->getReturnValues());
- }
+ if (auto* mock = tryJSDynamicCast<JSMockFunction*>(value)) {
+ return JSValue::encode(mock->getReturnValues());
}
return JSValue::encode({});
@@ -911,19 +1030,16 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionGetMockName, (JSC::JSGlobalObject * globa
throwTypeError(globalObject, scope, "Expected Mock"_s);
}
- JSValue implValue = thisObject->implementation.get();
- if (!implValue) {
- implValue = jsUndefined();
- }
-
- if (auto* impl = jsDynamicCast<JSMockImplementation*>(implValue)) {
+ if (auto* impl = tryJSDynamicCast<JSMockImplementation*>(thisObject->implementation.get())) {
if (impl->kind == JSMockImplementation::Kind::Call) {
- JSObject* object = impl->internalFields[0].get().asCell()->getObject();
- if (auto nameValue = object->getIfPropertyExists(globalObject, PropertyName(vm.propertyNames->name))) {
- RELEASE_AND_RETURN(scope, JSValue::encode(nameValue));
- }
+ if (JSValue underlyingValue = impl->underlyingValue.get()) {
+ JSObject* object = underlyingValue.asCell()->getObject();
+ if (auto nameValue = object->getIfPropertyExists(globalObject, PropertyName(vm.propertyNames->name))) {
+ RELEASE_AND_RETURN(scope, JSValue::encode(nameValue));
+ }
- RETURN_IF_EXCEPTION(scope, {});
+ RETURN_IF_EXCEPTION(scope, {});
+ }
}
}
@@ -938,7 +1054,7 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockClear, (JSC::JSGlobalObject * globalO
throwTypeError(globalObject, scope, "Expected Mock"_s);
}
- thisObject->reset();
+ thisObject->clear();
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
@@ -964,6 +1080,8 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockRestore, (JSC::JSGlobalObject * globa
throwTypeError(globalObject, scope, "Expected Mock"_s);
}
+ thisObject->clearSpy();
+
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockImplementation, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callframe))
@@ -971,37 +1089,20 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockImplementation, (JSC::JSGlobalObject
auto& vm = lexicalGlobalObject->vm();
auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
- JSMockFunction* thisObject = JSMockFunction::create(
- vm,
- globalObject,
- globalObject->mockModule.mockFunctionStructure.getInitializedOnMainThread(globalObject));
+ JSMockFunction* thisObject = jsDynamicCast<JSMockFunction*>(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
if (UNLIKELY(!thisObject)) {
throwOutOfMemoryError(globalObject, scope);
return {};
}
- if (callframe->argumentCount() > 0) {
- JSValue value = callframe->argument(0);
- if (value.isCallable()) {
- {
- auto catcher = DECLARE_CATCH_SCOPE(vm);
- WTF::String nameToUse;
- if (auto* fn = jsDynamicCast<JSFunction*>(value)) {
- nameToUse = fn->name(vm);
- } else if (auto* fn = jsDynamicCast<InternalFunction*>(value)) {
- nameToUse = fn->name();
- }
- if (nameToUse.length()) {
- thisObject->setName(nameToUse);
- }
- if (catcher.exception())
- catcher.clearException();
- }
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Call, value);
- } else {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, value);
- }
+ JSValue value = callframe->argument(0);
+
+ // This check is for a jest edge case, truthy values will throw but not immediatly, and falsy values return undefined.
+ if (value.toBoolean(globalObject)) {
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Call, value);
+ } else {
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, jsUndefined());
}
return JSValue::encode(thisObject);
@@ -1018,59 +1119,16 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockImplementationOnce, (JSC::JSGlobalObj
return {};
}
- HANDLE_STATIC_CALL;
-
- if (callframe->argumentCount() > 0) {
- JSValue value = callframe->argument(0);
- if (value.isCallable()) {
- if (!thisObject->implementation) {
- auto catcher = DECLARE_CATCH_SCOPE(vm);
- WTF::String nameToUse;
- if (auto* fn = jsDynamicCast<JSFunction*>(value)) {
- nameToUse = fn->name(vm);
- } else if (auto* fn = jsDynamicCast<InternalFunction*>(value)) {
- nameToUse = fn->name();
- }
- if (nameToUse.length()) {
- thisObject->setName(nameToUse);
- }
- if (catcher.exception())
- catcher.clearException();
- }
-
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Call, value);
- } else {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, value);
- }
- }
-
- return JSValue::encode(thisObject);
-}
-
-JSC_DEFINE_HOST_FUNCTION(jsMockFunctionWithImplementation, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
-{
- JSMockFunction* thisObject = jsDynamicCast<JSMockFunction*>(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
- auto& vm = globalObject->vm();
- auto scope = DECLARE_THROW_SCOPE(vm);
- if (UNLIKELY(!thisObject)) {
- throwTypeError(globalObject, scope, "Expected Mock"_s);
- RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
- }
-
- HANDLE_STATIC_CALL;
-
- JSValue arg = callframe->argument(0);
+ JSValue value = callframe->argument(0);
- if (callframe->argumentCount() < 1 || arg.isEmpty() || arg.isUndefined()) {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, jsUndefined());
- } else if (arg.isCallable()) {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Call, arg);
+ // This check is for a jest edge case, truthy values will throw but not immediatly, and falsy values return undefined.
+ if (value.toBoolean(globalObject)) {
+ pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::Call, value);
} else {
- throwTypeError(globalObject, scope, "Expected a function or undefined"_s);
- RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
+ pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, jsUndefined());
}
- RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
+ return JSValue::encode(thisObject);
}
JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockName, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
{
@@ -1082,8 +1140,6 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockName, (JSC::JSGlobalObject * globalOb
return {};
}
- HANDLE_STATIC_CALL;
-
if (callframe->argumentCount() > 0) {
auto* newName = callframe->argument(0).toStringOrNull(globalObject);
if (UNLIKELY(!newName)) {
@@ -1105,7 +1161,6 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockReturnThis, (JSC::JSGlobalObject * gl
throwTypeError(globalObject, scope, "Expected Mock"_s);
}
- HANDLE_STATIC_CALL;
pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnThis, jsUndefined());
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
@@ -1117,13 +1172,10 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockReturnValue, (JSC::JSGlobalObject * g
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- if (callframe->argumentCount() < 1) {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, jsUndefined());
- } else {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, callframe->argument(0));
- }
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, callframe->argument(0));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
@@ -1134,35 +1186,24 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockReturnValueOnce, (JSC::JSGlobalObject
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- HANDLE_STATIC_CALL;
- if (callframe->argumentCount() < 1) {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, jsUndefined());
- } else {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, callframe->argument(0));
- }
+ pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, callframe->argument(0));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
-JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockResolvedValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callframe))
+JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockResolvedValue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
{
- auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
JSMockFunction* thisObject = jsDynamicCast<JSMockFunction*>(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
auto& vm = globalObject->vm();
-
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- HANDLE_STATIC_CALL;
-
- if (callframe->argumentCount() < 1) {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::resolvedPromise(globalObject, jsUndefined()));
- } else {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::resolvedPromise(globalObject, callframe->argument(0)));
- }
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, JSC::JSPromise::resolvedPromise(globalObject, callframe->argument(0)));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
@@ -1173,15 +1214,10 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockResolvedValueOnce, (JSC::JSGlobalObje
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- HANDLE_STATIC_CALL;
-
- if (callframe->argumentCount() < 1) {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::resolvedPromise(globalObject, jsUndefined()));
- } else {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::resolvedPromise(globalObject, callframe->argument(0)));
- }
+ pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, JSC::JSPromise::resolvedPromise(globalObject, callframe->argument(0)));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
@@ -1192,15 +1228,10 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockRejectedValue, (JSC::JSGlobalObject *
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- HANDLE_STATIC_CALL;
-
- if (callframe->argumentCount() < 1) {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::rejectedPromise(globalObject, jsUndefined()));
- } else {
- pushImpl(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::rejectedPromise(globalObject, callframe->argument(0)));
- }
+ pushImpl(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, JSC::JSPromise::rejectedPromise(globalObject, callframe->argument(0)));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
}
@@ -1211,16 +1242,178 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockRejectedValueOnce, (JSC::JSGlobalObje
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisObject)) {
throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
}
- HANDLE_STATIC_CALL;
+ pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::ReturnValue, JSC::JSPromise::rejectedPromise(globalObject, callframe->argument(0)));
- if (callframe->argumentCount() < 1) {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::rejectedPromise(globalObject, jsUndefined()));
- } else {
- pushImplOnce(thisObject, globalObject, JSMockImplementation::Kind::Promise, JSC::JSPromise::resolvedPromise(globalObject, callframe->argument(0)));
+ RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
+}
+JSC_DEFINE_HOST_FUNCTION(jsMockFunctionGetter_mockGetLastCall, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ JSValue thisObject = callframe->thisValue();
+ if (UNLIKELY(!thisObject.isObject())) {
+ return JSValue::encode(jsUndefined());
}
+ JSValue callsValue = thisObject.get(globalObject, Identifier::fromString(globalObject->vm(), "calls"_s));
- RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
+ if (auto callsArray = jsDynamicCast<JSC::JSArray*>(callsValue)) {
+ auto len = callsArray->length();
+ if (len > 0) {
+ return JSValue::encode(callsArray->getIndex(globalObject, len - 1));
+ }
+ }
+ return JSValue::encode(jsUndefined());
+}
+
+const JSC::ClassInfo MockWithImplementationCleanupData::s_info = { "MockWithImplementationCleanupData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(MockWithImplementationCleanupData) };
+
+template<typename, JSC::SubspaceAccess mode>
+JSC::GCClient::IsoSubspace* MockWithImplementationCleanupData::subspaceFor(JSC::VM& vm)
+{
+ return WebCore::subspaceForImpl<MockWithImplementationCleanupData, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForMockWithImplementationCleanupData.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMockWithImplementationCleanupData = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForMockWithImplementationCleanupData.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForMockWithImplementationCleanupData = std::forward<decltype(space)>(space); });
+}
+
+MockWithImplementationCleanupData* MockWithImplementationCleanupData::create(VM& vm, Structure* structure)
+{
+ MockWithImplementationCleanupData* mod = new (NotNull, allocateCell<MockWithImplementationCleanupData>(vm)) MockWithImplementationCleanupData(vm, structure);
+ return mod;
+}
+Structure* MockWithImplementationCleanupData::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+ return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
+}
+
+MockWithImplementationCleanupData::MockWithImplementationCleanupData(VM& vm, Structure* structure)
+ : Base(vm, structure)
+{
+}
+
+void MockWithImplementationCleanupData::finishCreation(VM& vm, JSMockFunction* fn, JSValue impl, JSValue tail, JSValue fallback)
+{
+ Base::finishCreation(vm);
+ this->internalField(0).set(vm, this, fn);
+ this->internalField(1).set(vm, this, impl);
+ this->internalField(2).set(vm, this, tail);
+ this->internalField(3).set(vm, this, fallback);
+}
+
+template<typename Visitor>
+void MockWithImplementationCleanupData::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ auto* thisObject = jsCast<MockWithImplementationCleanupData*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+}
+
+DEFINE_VISIT_CHILDREN(MockWithImplementationCleanupData);
+
+MockWithImplementationCleanupData* MockWithImplementationCleanupData::create(JSC::JSGlobalObject* globalObject, JSMockFunction* fn, JSValue impl, JSValue tail, JSValue fallback)
+{
+ auto* obj = create(globalObject->vm(), reinterpret_cast<Zig::GlobalObject*>(globalObject)->mockModule.mockWithImplementationCleanupDataStructure.getInitializedOnMainThread(globalObject));
+ obj->finishCreation(globalObject->vm(), fn, impl, tail, fallback);
+ return obj;
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsMockFunctionWithImplementationCleanup, (JSC::JSGlobalObject * jsGlobalObject, JSC::CallFrame* callframe))
+{
+ auto& vm = jsGlobalObject->vm();
+ auto count = callframe->argumentCount();
+ auto ctx = jsDynamicCast<MockWithImplementationCleanupData*>(callframe->argument(1));
+ if (!ctx) {
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto fn = jsDynamicCast<JSMockFunction*>(ctx->internalField(0).get());
+ fn->implementation.set(vm, fn, ctx->internalField(1).get());
+ fn->tail.set(vm, fn, ctx->internalField(2).get());
+ fn->fallbackImplmentation.set(vm, fn, ctx->internalField(3).get());
+
+ return JSValue::encode(jsUndefined());
+}
+JSC_DEFINE_HOST_FUNCTION(jsMockFunctionWithImplementation, (JSC::JSGlobalObject * jsGlobalObject, JSC::CallFrame* callframe))
+{
+ Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(jsGlobalObject);
+
+ JSMockFunction* thisObject = jsDynamicCast<JSMockFunction*>(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
+ auto& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!thisObject)) {
+ throwTypeError(globalObject, scope, "Expected Mock"_s);
+ return {};
+ }
+
+ JSValue tempImplValue = callframe->argument(0);
+ JSValue callback = callframe->argument(1);
+ JSC::CallData callData = JSC::getCallData(callback);
+ if (UNLIKELY(callData.type == JSC::CallData::Type::None)) {
+ throwTypeError(globalObject, scope, "Expected mock implementation to be callable"_s);
+ return {};
+ }
+
+ auto lastImpl = thisObject->implementation.get();
+ auto lastTail = thisObject->tail.get();
+ auto lastFallback = thisObject->fallbackImplmentation.get();
+
+ JSMockImplementation* impl = JSMockImplementation::create(
+ globalObject,
+ globalObject->mockModule.mockImplementationStructure.getInitializedOnMainThread(globalObject),
+ JSMockImplementation::Kind::Call,
+ tempImplValue,
+ false);
+
+ thisObject->implementation.set(vm, thisObject, impl);
+ thisObject->fallbackImplmentation.clear();
+ thisObject->tail.clear();
+
+ MarkedArgumentBuffer args;
+ NakedPtr<Exception> exception;
+ JSValue returnValue = call(globalObject, callback, callData, jsUndefined(), args, exception);
+
+ if (auto promise = tryJSDynamicCast<JSC::JSPromise*>(returnValue)) {
+ auto capability = JSC::JSPromise::createNewPromiseCapability(globalObject, globalObject->promiseConstructor());
+ auto ctx = MockWithImplementationCleanupData::create(globalObject, thisObject, lastImpl, lastTail, lastFallback);
+
+ JSFunction* cleanup = globalObject->mockModule.withImplementationCleanupFunction.getInitializedOnMainThread(globalObject);
+ JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction();
+ auto callData = JSC::getCallData(performPromiseThenFunction);
+ MarkedArgumentBuffer arguments;
+ arguments.append(promise);
+ arguments.append(cleanup);
+ arguments.append(cleanup);
+ arguments.append(capability);
+ arguments.append(ctx);
+ ASSERT(!arguments.hasOverflowed());
+ call(globalObject, performPromiseThenFunction, callData, jsUndefined(), arguments);
+
+ return JSC::JSValue::encode(promise);
+ }
+
+ thisObject->implementation.set(vm, thisObject, lastImpl);
+ thisObject->tail.set(vm, thisObject, lastImpl);
+ thisObject->fallbackImplmentation.set(vm, thisObject, lastFallback);
+
+ return JSC::JSValue::encode(jsUndefined());
+}
+} // namespace Bun
+
+namespace JSC {
+
+template<unsigned passedNumberOfInternalFields>
+template<typename Visitor>
+void JSInternalFieldObjectImpl<passedNumberOfInternalFields>::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ auto* thisObject = jsCast<JSInternalFieldObjectImpl*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.appendValues(thisObject->m_internalFields, numberOfInternalFields);
}
-} \ No newline at end of file
+
+DEFINE_VISIT_CHILDREN_WITH_MODIFIER(template<unsigned passedNumberOfInternalFields>, JSInternalFieldObjectImpl<passedNumberOfInternalFields>);
+
+} // namespace JSC
diff --git a/src/bun.js/bindings/JSMockFunction.h b/src/bun.js/bindings/JSMockFunction.h
index 288ca2e89..93c8bb015 100644
--- a/src/bun.js/bindings/JSMockFunction.h
+++ b/src/bun.js/bindings/JSMockFunction.h
@@ -16,15 +16,47 @@ class JSMockFunction;
class JSMockModule final {
public:
+ static uint64_t s_nextInvocationId;
+ static uint64_t nextInvocationId() { return ++s_nextInvocationId; }
+
LazyProperty<JSC::JSGlobalObject, Structure> mockFunctionStructure;
LazyProperty<JSC::JSGlobalObject, Structure> mockResultStructure;
LazyProperty<JSC::JSGlobalObject, Structure> mockImplementationStructure;
LazyProperty<JSC::JSGlobalObject, Structure> mockObjectStructure;
LazyProperty<JSC::JSGlobalObject, Structure> activeSpySetStructure;
+ LazyProperty<JSC::JSGlobalObject, JSFunction> withImplementationCleanupFunction;
+ LazyProperty<JSC::JSGlobalObject, JSC::Structure> mockWithImplementationCleanupDataStructure;
static JSMockModule create(JSC::JSGlobalObject*);
JSC::Strong<Unknown> activeSpies;
};
-} \ No newline at end of file
+class MockWithImplementationCleanupData : public JSC::JSInternalFieldObjectImpl<4> {
+public:
+ using Base = JSC::JSInternalFieldObjectImpl<4>;
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
+
+ JS_EXPORT_PRIVATE static MockWithImplementationCleanupData* create(VM&, Structure*);
+ static MockWithImplementationCleanupData* create(JSC::JSGlobalObject* globalObject, JSMockFunction* fn, JSValue impl, JSValue tail, JSValue fallback);
+ static MockWithImplementationCleanupData* createWithInitialValues(VM&, Structure*);
+ static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+ static std::array<JSValue, numberOfInternalFields> initialValues()
+ {
+ return { {
+ jsUndefined(),
+ jsUndefined(),
+ jsUndefined(),
+ jsUndefined(),
+ } };
+ }
+
+ DECLARE_EXPORT_INFO;
+ DECLARE_VISIT_CHILDREN;
+
+ MockWithImplementationCleanupData(JSC::VM&, JSC::Structure*);
+ void finishCreation(JSC::VM&, JSMockFunction* fn, JSValue impl, JSValue tail, JSValue fallback);
+};
+}
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 5589d2add..e49b94687 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -4042,6 +4042,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->mockModule.mockImplementationStructure.visit(visitor);
thisObject->mockModule.mockObjectStructure.visit(visitor);
thisObject->mockModule.activeSpySetStructure.visit(visitor);
+ thisObject->mockModule.mockWithImplementationCleanupDataStructure.visit(visitor);
+ thisObject->mockModule.withImplementationCleanupFunction.visit(visitor);
for (auto& barrier : thisObject->m_thenables) {
visitor.append(barrier);
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index d595dc866..3997c1d88 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -36,6 +36,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCommonJSModuleRecord;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockImplementation;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockFunction;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index b4a5e9d55..4feca1754 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -36,6 +36,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForCommonJSModuleRecord;
std::unique_ptr<IsoSubspace> m_subspaceForJSMockImplementation;
std::unique_ptr<IsoSubspace> m_subspaceForJSMockFunction;
+ std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/