Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion src/jsc/bindings/JSMockFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ inline To tryJSDynamicCast(JSC::WriteBarrier<WriteBarrierT>& 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);
Expand Down Expand Up @@ -463,7 +464,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();
}
Expand Down Expand Up @@ -976,6 +977,48 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionCall, (JSGlobalObject * lexicalGlobalObje
return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(jsMockFunctionConstruct, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe))
{
Zig::GlobalObject* globalObject = uncheckedDowncast<Zig::GlobalObject>(lexicalGlobalObject);
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSMockFunction* fn = dynamicDowncast<JSMockFunction>(callframe->jsCallee());
if (!fn) [[unlikely]] {
throwTypeError(globalObject, scope, "Expected callee to be mock function"_s);
return {};
}

JSValue newTarget = callframe->newTarget();
JSObject* newTargetObject = newTarget.isObject() ? asObject(newTarget) : fn;
JSValue prototype = newTargetObject->get(globalObject, vm.propertyNames->prototype);
RETURN_IF_EXCEPTION(scope, {});
JSObject* instance = prototype.isObject()
? JSC::constructEmptyObject(globalObject, asObject(prototype))
: JSC::constructEmptyObject(globalObject);
RETURN_IF_EXCEPTION(scope, {});

if (JSC::JSArray* instances = fn->instances.get()) {
instances->push(globalObject, instance);
RETURN_IF_EXCEPTION(scope, {});
} else {
JSC::ObjectInitializationScope object(vm);
instances = JSC::JSArray::tryCreateUninitializedRestricted(
object,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
1);
instances->initializeIndex(object, 0, instance);
fn->instances.set(vm, fn, instances);
}

callframe->setThisValue(instance);
EncodedJSValue encodedResult = jsMockFunctionCall(lexicalGlobalObject, callframe);
RETURN_IF_EXCEPTION(scope, {});
JSValue result = JSValue::decode(encodedResult);
if (result.isObject())
return encodedResult;
return JSValue::encode(instance);
}

void JSMockFunctionPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
Expand Down
28 changes: 28 additions & 0 deletions test/js/bun/test/mock-fn.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,34 @@ describe("mock()", () => {

expect(bar()()).toBe(true);
});

test("Reflect.construct on a mock returns an object and records the instance", () => {
const fn = jest.fn();
const instance = Reflect.construct(fn, [1, 2]);
expect(typeof instance).toBe("object");
expect(instance).not.toBeNull();
expect(fn.mock.instances).toEqual([instance]);
expect(fn.mock.contexts).toEqual([instance]);
expect(fn.mock.calls).toEqual([[1, 2]]);

const fn2 = jest.fn(() => ({ tag: "impl" }));
const result2 = Reflect.construct(fn2, []);
expect(result2).toEqual({ tag: "impl" });
expect(fn2.mock.instances).toHaveLength(1);
expect(fn2.mock.instances[0]).not.toBe(result2);

const fn3 = jest.fn(() => 42);
const result3 = new fn3();
expect(typeof result3).toBe("object");
expect(fn3.mock.instances).toEqual([result3]);

class NewTarget {}
const fn4 = jest.fn();
const result4 = Reflect.construct(fn4, [], NewTarget);
expect(Object.getPrototypeOf(result4)).toBe(NewTarget.prototype);
expect(result4).toBeInstanceOf(NewTarget);
expect(fn4.mock.instances).toEqual([result4]);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
});

describe("resetAllMocks", () => {
Expand Down
Loading