diff --git a/src/jsc/bindings/JSMockFunction.cpp b/src/jsc/bindings/JSMockFunction.cpp index 9b39827ad788..7ff80ab74a3c 100644 --- a/src/jsc/bindings/JSMockFunction.cpp +++ b/src/jsc/bindings/JSMockFunction.cpp @@ -86,6 +86,7 @@ inline To tryJSDynamicCast(JSC::WriteBarrier& from) } JSC_DECLARE_HOST_FUNCTION(jsMockFunctionCall); +JSC_DECLARE_HOST_FUNCTION(jsMockFunctionConstruct); JSC_DECLARE_CUSTOM_GETTER(jsMockFunctionGetter_protoImpl); JSC_DECLARE_CUSTOM_GETTER(jsMockFunctionGetter_mock); JSC_DECLARE_HOST_FUNCTION(jsMockFunctionGetter_mockGetLastCall); @@ -462,7 +463,7 @@ class JSMockFunction : public JSC::InternalFunction { } JSMockFunction(JSC::VM& vm, JSC::Structure* structure, CallbackKind wrapKind) - : Base(vm, structure, jsMockFunctionCall, jsMockFunctionCall) + : Base(vm, structure, jsMockFunctionCall, jsMockFunctionConstruct) { initMock(); } @@ -981,6 +982,28 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje return JSValue::encode(jsUndefined()); } +JSC_DEFINE_HOST_FUNCTION(jsMockFunctionConstruct, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe)) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* newTarget = asObject(callframe->newTarget()); + JSGlobalObject* functionGlobalObject = getFunctionRealm(lexicalGlobalObject, newTarget); + RETURN_IF_EXCEPTION(scope, {}); + Structure* structure = InternalFunction::createSubclassStructure(lexicalGlobalObject, newTarget, functionGlobalObject->objectStructureForObjectConstructor()); + RETURN_IF_EXCEPTION(scope, {}); + JSObject* thisObject = constructEmptyObject(vm, structure); + callframe->setThisValue(thisObject); + + EncodedJSValue encodedResult = jsMockFunctionCall(lexicalGlobalObject, callframe); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue result = JSValue::decode(encodedResult); + if (result.isObject()) + return encodedResult; + return JSValue::encode(thisObject); +} + void JSMockFunctionPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); diff --git a/test/js/bun/test/mock-fn.test.js b/test/js/bun/test/mock-fn.test.js index 7f6a244d9806..f682e6de75c9 100644 --- a/test/js/bun/test/mock-fn.test.js +++ b/test/js/bun/test/mock-fn.test.js @@ -794,6 +794,28 @@ describe("mock()", () => { expect(bar()()).toBe(true); }); + + it("Reflect.construct returns an object when the implementation does not", () => { + const noImpl = jest.fn(); + expect(typeof Reflect.construct(noImpl, [])).toBe("object"); + expect(typeof new noImpl()).toBe("object"); + + const primitiveImpl = jest.fn(() => 42); + expect(typeof Reflect.construct(primitiveImpl, [])).toBe("object"); + expect(typeof new primitiveImpl()).toBe("object"); + + const returnsPrimitive = jest.fn().mockReturnValue(42); + expect(typeof Reflect.construct(returnsPrimitive, [])).toBe("object"); + expect(typeof new returnsPrimitive()).toBe("object"); + + const sentinel = {}; + const objectImpl = jest.fn(() => sentinel); + expect(Reflect.construct(objectImpl, [])).toBe(sentinel); + expect(new objectImpl()).toBe(sentinel); + + expect(noImpl()).toBeUndefined(); + expect(primitiveImpl()).toBe(42); + }); }); describe("spyOn", () => {