aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/bun-types/bun-test.d.ts14
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h6
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h7
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp169
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.h56
-rw-r--r--src/bun.js/bindings/bindings.cpp18
-rw-r--r--src/bun.js/bindings/bindings.zig107
-rw-r--r--src/bun.js/bindings/exports.zig108
-rw-r--r--src/bun.js/bindings/generated_classes.zig82
-rw-r--r--src/bun.js/bindings/generated_classes_list.zig1
-rw-r--r--src/bun.js/bindings/headers.h1
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/javascript.zig1
-rw-r--r--src/bun.js/scripts/generate-classes.ts14
-rw-r--r--src/bun.js/test/jest.classes.ts12
-rw-r--r--src/bun.js/test/jest.zig659
-rw-r--r--src/bun.js/test/pretty_format.zig1963
-rw-r--r--src/bun.js/webcore/blob.zig2
-rw-r--r--src/bun.js/webcore/body.zig4
-rw-r--r--src/bun.js/webcore/request.zig6
-rw-r--r--src/bun.js/webcore/response.zig4
-rw-r--r--src/cli.zig16
-rw-r--r--src/cli/test_command.zig48
-rw-r--r--test/js/bun/test/snapshot-tests/__snapshots__/bun-snapshots.test.ts.snap77
-rw-r--r--test/js/bun/test/snapshot-tests/__snapshots__/existing-snapshots.test.ts.snap71
-rw-r--r--test/js/bun/test/snapshot-tests/bun-snapshots.test.ts64
-rw-r--r--test/js/bun/test/snapshot-tests/existing-snapshots.test.ts13
-rw-r--r--test/js/bun/test/snapshot-tests/generate-jest-snapshots.test.ts45
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/__snapshots__/more.test.ts.snap148
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/__snapshots__/moremore.test.ts.snap96
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap370
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap76
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/more-snapshots/different-directory.test.ts18
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/more.test.ts62
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/moremore.test.ts118
-rw-r--r--test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts161
-rw-r--r--test/js/bun/util/inspect.test.js10
-rw-r--r--test/js/web/console/console-log.expected.txt24
-rw-r--r--test/js/web/url/url.test.ts108
41 files changed, 4635 insertions, 131 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts
index 05206f2a0..75948a75f 100644
--- a/packages/bun-types/bun-test.d.ts
+++ b/packages/bun-types/bun-test.d.ts
@@ -440,7 +440,19 @@ declare module "bun:test" {
* @param expected the expected error, error message, or error pattern
*/
toThrow(expected?: string | Error | ErrorConstructor | RegExp): void;
- };
+ /**
+ * Asserts that a value matches the most recent snapshot.
+ *
+ * @example
+ * expect([1, 2, 3]).toMatchSnapshot();
+ * expect({ a: 1, b: 2 }).toMatchSnapshot({ a: 1 });
+ * expect({ c: new Date() }).toMatchSnapshot({ c: expect.any(Date) });
+ *
+ * @param propertyMatchers Object containing properties to match against the value.
+ * @param hint Hint used to identify the snapshot in the snapshot file.
+ */
+ toMatchSnapshot(propertyMatchers?: Object, hint?: string): void;
+ }
}
declare module "test" {
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
index d642ae40f..f38c22831 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
@@ -2,7 +2,8 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBlob;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBlobConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCryptoHasher;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCryptoHasherConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirent;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirentConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpect;
-std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectAny;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouterConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForListener;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD5;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
index 93cb9bf5e..80ada929c 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
@@ -2,7 +2,8 @@ std::unique_ptr<IsoSubspace> m_subspaceForBlob;
std::unique_ptr<IsoSubspace> m_subspaceForBlobConstructor;std::unique_ptr<IsoSubspace> m_subspaceForCryptoHasher;
std::unique_ptr<IsoSubspace> m_subspaceForCryptoHasherConstructor;std::unique_ptr<IsoSubspace> m_subspaceForDirent;
std::unique_ptr<IsoSubspace> m_subspaceForDirentConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpect;
-std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter;
+std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpectAny;
+std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter;
std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouterConstructor;std::unique_ptr<IsoSubspace> m_subspaceForListener;
std::unique_ptr<IsoSubspace> m_subspaceForMD4;
std::unique_ptr<IsoSubspace> m_subspaceForMD4Constructor;std::unique_ptr<IsoSubspace> m_subspaceForMD5;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
index ab75a9949..ae3e4233a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
@@ -22,6 +22,12 @@ JSC::Structure* JSExpectStructure() { return m_JSExpect.getInitializedOnMainThre
JSC::LazyClassStructure m_JSExpect;
bool hasJSExpectSetterValue { false };
mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectSetterValue;
+JSC::Structure* JSExpectAnyStructure() { return m_JSExpectAny.getInitializedOnMainThread(this); }
+ JSC::JSObject* JSExpectAnyConstructor() { return m_JSExpectAny.constructorInitializedOnMainThread(this); }
+ JSC::JSValue JSExpectAnyPrototype() { return m_JSExpectAny.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_JSExpectAny;
+ bool hasJSExpectAnySetterValue { false };
+ mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectAnySetterValue;
JSC::Structure* JSFileSystemRouterStructure() { return m_JSFileSystemRouter.getInitializedOnMainThread(this); }
JSC::JSObject* JSFileSystemRouterConstructor() { return m_JSFileSystemRouter.constructorInitializedOnMainThread(this); }
JSC::JSValue JSFileSystemRouterPrototype() { return m_JSFileSystemRouter.prototypeInitializedOnMainThread(this); }
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
index aed941a2e..a90753aa8 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
@@ -23,6 +23,12 @@ void GlobalObject::initGeneratedLazyClasses() {
init.setStructure(WebCore::JSExpect::createStructure(init.vm, init.global, init.prototype));
init.setConstructor(WebCore::JSExpect::createConstructor(init.vm, init.global, init.prototype));
});
+ m_JSExpectAny.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::JSExpectAny::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::JSExpectAny::createStructure(init.vm, init.global, init.prototype));
+
+ });
m_JSFileSystemRouter.initLater(
[](LazyClassStructure::Initializer& init) {
init.setPrototype(WebCore::JSFileSystemRouter::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
@@ -163,6 +169,7 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor&
thisObject->m_JSCryptoHasher.visit(visitor); visitor.append(thisObject->m_JSCryptoHasherSetterValue);
thisObject->m_JSDirent.visit(visitor); visitor.append(thisObject->m_JSDirentSetterValue);
thisObject->m_JSExpect.visit(visitor); visitor.append(thisObject->m_JSExpectSetterValue);
+ thisObject->m_JSExpectAny.visit(visitor); visitor.append(thisObject->m_JSExpectAnySetterValue);
thisObject->m_JSFileSystemRouter.visit(visitor); visitor.append(thisObject->m_JSFileSystemRouterSetterValue);
thisObject->m_JSListener.visit(visitor); visitor.append(thisObject->m_JSListenerSetterValue);
thisObject->m_JSMD4.visit(visitor); visitor.append(thisObject->m_JSMD4SetterValue);
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index 716f683e5..a388490b4 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -2342,6 +2342,175 @@ void JSExpect::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
}
DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpect);
+class JSExpectAnyPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static JSExpectAnyPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSExpectAnyPrototype* ptr = new (NotNull, JSC::allocateCell<JSExpectAnyPrototype>(vm)) JSExpectAnyPrototype(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+private:
+ JSExpectAnyPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+};
+
+extern "C" void ExpectAnyClass__finalize(void*);
+extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectAnyClass__call);
+
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSExpectAnyPrototype, JSExpectAnyPrototype::Base);
+
+static const HashTableValue JSExpectAnyPrototypeTableValues[] = {};
+
+const ClassInfo JSExpectAnyPrototype::s_info = { "ExpectAny"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAnyPrototype) };
+
+extern "C" void ExpectAnyPrototype__constructorValueSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
+{
+ auto& vm = globalObject->vm();
+ auto* thisObject = jsCast<JSExpectAny*>(JSValue::decode(thisValue));
+ thisObject->m_constructorValue.set(vm, thisObject, JSValue::decode(value));
+}
+
+extern "C" EncodedJSValue ExpectAnyPrototype__constructorValueGetCachedValue(JSC::EncodedJSValue thisValue)
+{
+ auto* thisObject = jsCast<JSExpectAny*>(JSValue::decode(thisValue));
+ return JSValue::encode(thisObject->m_constructorValue.get());
+}
+
+void JSExpectAnyPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+JSExpectAny::~JSExpectAny()
+{
+ if (m_ctx) {
+ ExpectAnyClass__finalize(m_ctx);
+ }
+}
+void JSExpectAny::destroy(JSCell* cell)
+{
+ static_cast<JSExpectAny*>(cell)->JSExpectAny::~JSExpectAny();
+}
+
+const ClassInfo JSExpectAny::s_info = { "ExpectAny"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAny) };
+
+void JSExpectAny::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+JSExpectAny* JSExpectAny::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx)
+{
+ JSExpectAny* ptr = new (NotNull, JSC::allocateCell<JSExpectAny>(vm)) JSExpectAny(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+extern "C" void* ExpectAny__fromJS(JSC::EncodedJSValue value)
+{
+ JSC::JSValue decodedValue = JSC::JSValue::decode(value);
+ if (decodedValue.isEmpty() || !decodedValue.isCell())
+ return nullptr;
+
+ JSC::JSCell* cell = decodedValue.asCell();
+ JSExpectAny* object = JSC::jsDynamicCast<JSExpectAny*>(cell);
+
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool ExpectAny__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr)
+{
+ JSExpectAny* object = JSC::jsDynamicCast<JSExpectAny*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+extern "C" const size_t ExpectAny__ptrOffset = JSExpectAny::offsetOfWrapped();
+
+void JSExpectAny::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSExpectAny*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* JSExpectAny::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return JSExpectAnyPrototype::create(vm, globalObject, JSExpectAnyPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+extern "C" EncodedJSValue ExpectAny__create(Zig::GlobalObject* globalObject, void* ptr)
+{
+ auto& vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->JSExpectAnyStructure();
+ JSExpectAny* instance = JSExpectAny::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
+
+template<typename Visitor>
+void JSExpectAny::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSExpectAny* thisObject = jsCast<JSExpectAny*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_constructorValue);
+}
+
+DEFINE_VISIT_CHILDREN(JSExpectAny);
+
+template<typename Visitor>
+void JSExpectAny::visitAdditionalChildren(Visitor& visitor)
+{
+ JSExpectAny* thisObject = this;
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ visitor.append(thisObject->m_constructorValue);
+
+ ;
+}
+
+DEFINE_VISIT_ADDITIONAL_CHILDREN(JSExpectAny);
+
+template<typename Visitor>
+void JSExpectAny::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
+{
+ JSExpectAny* thisObject = jsCast<JSExpectAny*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ thisObject->visitAdditionalChildren<Visitor>(visitor);
+}
+
+DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectAny);
class JSFileSystemRouterPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h
index acf1dc140..76420193a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses.h
@@ -236,6 +236,62 @@ public:
mutable JSC::WriteBarrier<JSC::Unknown> m_resultValue;
};
+class JSExpectAny final : public JSC::JSDestructibleObject {
+public:
+ using Base = JSC::JSDestructibleObject;
+ static JSExpectAny* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSExpectAny, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForExpectAny.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForExpectAny = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForExpectAny.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForExpectAny = std::forward<decltype(space)>(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
+ ;
+
+ ~JSExpectAny();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSExpectAny, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+ JSExpectAny(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+
+ DECLARE_VISIT_CHILDREN;
+ template<typename Visitor> void visitAdditionalChildren(Visitor&);
+ DECLARE_VISIT_OUTPUT_CONSTRAINTS;
+
+ mutable JSC::WriteBarrier<JSC::Unknown> m_constructorValue;
+};
+
class JSFileSystemRouter final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 5c9cba7f0..d431a287f 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -3227,13 +3227,21 @@ void JSC__VM__releaseWeakRefs(JSC__VM* arg0)
static auto function_string_view = MAKE_STATIC_STRING_IMPL("Function");
void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2)
{
- JSC::JSCell* cell = JSC::JSValue::decode(JSValue0).asCell();
+ JSValue value = JSValue::decode(JSValue0);
+ JSC::JSCell* cell = value.asCell();
if (cell == nullptr) {
arg2->len = 0;
return;
}
- const char* ptr = cell->className();
+ JSObject* obj = value.toObject(arg1);
+ StringView calculated = StringView(JSObject::calculatedClassName(obj));
+ if (calculated.length() > 0) {
+ *arg2 = Zig::toZigString(calculated);
+ return;
+ }
+
+ const char* ptr = cell->classInfo()->className;
auto view = WTF::StringView(ptr, strlen(ptr));
// Fallback to .name if className is empty
@@ -3784,6 +3792,12 @@ void JSC__JSValue__forEachPropertyOrdered(JSC__JSValue JSValue0, JSC__JSGlobalOb
}
}
+bool JSC__JSValue__isConstructor(JSC__JSValue JSValue0)
+{
+ JSValue value = JSValue::decode(JSValue0);
+ return value.isConstructor();
+}
+
extern "C" JSC__JSValue JSC__JSValue__createRopeString(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
return JSValue::encode(JSC::jsString(globalObject, JSC::JSValue::decode(JSValue0).toString(globalObject), JSC::JSValue::decode(JSValue1).toString(globalObject)));
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 40fc43091..8a63058a5 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -17,6 +17,8 @@ const JSC = @import("bun").JSC;
const Shimmer = JSC.Shimmer;
const FFI = @import("./FFI.zig");
const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator;
+const MutableString = bun.MutableString;
+const JestPrettyFormat = @import("../test/pretty_format.zig").JestPrettyFormat;
pub const JSObject = extern struct {
pub const shim = Shimmer("JSC", "JSObject", @This());
@@ -2442,6 +2444,18 @@ pub const JSGlobalObject = extern struct {
this.vm().throwError(this, this.createErrorInstance(fmt, args));
}
+ pub fn throwPretty(
+ this: *JSGlobalObject,
+ comptime fmt: string,
+ args: anytype,
+ ) void {
+ if (Output.enable_ansi_colors) {
+ this.vm().throwError(this, this.createErrorInstance(Output.prettyFmt(fmt, true), args));
+ } else {
+ this.vm().throwError(this, this.createErrorInstance(Output.prettyFmt(fmt, false), args));
+ }
+ }
+
pub fn queueMicrotask(
this: *JSGlobalObject,
function: JSValue,
@@ -3175,6 +3189,93 @@ pub const JSValue = enum(JSValueReprInt) {
return JSBuffer__bufferFromLength(globalObject, @intCast(i64, len));
}
+ pub fn jestSnapshotPrettyFormat(this: JSValue, out: *MutableString, globalObject: *JSGlobalObject) !void {
+ var buffered_writer = MutableString.BufferedWriter{ .context = out };
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+
+ const fmt_options = JestPrettyFormat.FormatOptions{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .quote_strings = true,
+ };
+
+ JestPrettyFormat.format(
+ .Debug,
+ globalObject,
+ @ptrCast([*]const JSValue, &this),
+ 1,
+ Writer,
+ Writer,
+ writer,
+ fmt_options,
+ );
+
+ try buffered_writer.flush();
+
+ const count: usize = brk: {
+ var total: usize = 0;
+ var remain = out.list.items;
+ while (strings.indexOfChar(remain, '`')) |i| {
+ total += 1;
+ remain = remain[i + 1 ..];
+ }
+ break :brk total;
+ };
+
+ if (count > 0) {
+ var result = try out.allocator.alloc(u8, count + out.list.items.len);
+ var input = out.list.items;
+
+ var input_i: usize = 0;
+ var result_i: usize = 0;
+ while (strings.indexOfChar(input[input_i..], '`')) |i| {
+ bun.copy(u8, result[result_i..], input[input_i .. input_i + i]);
+ result_i += i;
+ result[result_i] = '\\';
+ result[result_i + 1] = '`';
+ result_i += 2;
+ input_i += i + 1;
+ }
+
+ if (result_i != result.len) {
+ bun.copy(u8, result[result_i..], input[input_i..]);
+ }
+
+ out.deinit();
+ out.list.items = result;
+ out.list.capacity = result.len;
+ }
+ }
+
+ pub fn jestPrettyFormat(this: JSValue, out: *MutableString, globalObject: *JSGlobalObject) !void {
+ var buffered_writer = MutableString.BufferedWriter{ .context = out };
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+
+ const fmt_options = JSC.ZigConsoleClient.FormatOptions{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .ordered_properties = true,
+ .quote_strings = true,
+ };
+
+ JSC.ZigConsoleClient.format(
+ .Debug,
+ globalObject,
+ @ptrCast([*]const JSValue, &this),
+ 1,
+ Writer,
+ Writer,
+ writer,
+ fmt_options,
+ );
+
+ try buffered_writer.flush();
+ }
+
extern fn JSBuffer__bufferFromLength(*JSGlobalObject, i64) JSValue;
/// Must come from globally-allocated memory if allocator is not null
@@ -3496,6 +3597,11 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("isClass", .{ this, global });
}
+ pub fn isConstructor(this: JSValue) bool {
+ if (!this.isCell()) return false;
+ return cppFn("isConstructor", .{this});
+ }
+
pub fn getNameProperty(this: JSValue, global: *JSGlobalObject, ret: *ZigString) void {
if (this.isEmptyOrUndefinedOrNull()) {
return;
@@ -4015,6 +4121,7 @@ pub const JSValue = enum(JSValueReprInt) {
"toWTFString",
"toZigException",
"toZigString",
+ "isConstructor",
};
};
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index f9a9a3467..704dd6ae0 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -1426,6 +1426,7 @@ pub const ZigConsoleClient = struct {
JSValue.JSType.Float64Array,
JSValue.JSType.BigInt64Array,
JSValue.JSType.BigUint64Array,
+ .DataView,
=> .TypedArray,
.HeapBigInt => .BigInt,
@@ -1598,11 +1599,11 @@ pub const ZigConsoleClient = struct {
comptime Writer: type,
writer: Writer,
) !void {
- const indent = @min(this.indent, 8);
- var buf = [_]u8{' '} ** 32;
+ const indent = @min(this.indent, 32);
+ var buf = [_]u8{' '} ** 64;
var total_remain: usize = indent;
while (total_remain > 0) {
- const written = @min(16, total_remain);
+ const written = @min(32, total_remain);
try writer.writeAll(buf[0 .. written * 2]);
total_remain -|= written;
}
@@ -1752,14 +1753,14 @@ pub const ZigConsoleClient = struct {
this.addForNewLine(key.len + 1);
writer.print(
- comptime Output.prettyFmt("{}<d>:<r> ", enable_ansi_colors),
+ comptime Output.prettyFmt("\"{}\"<d>:<r> ", enable_ansi_colors),
.{key},
);
} else if (key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned())) {
this.addForNewLine(key.len + 1);
writer.print(
- comptime Output.prettyFmt("{}<d>:<r> ", enable_ansi_colors),
+ comptime Output.prettyFmt("\"{}\"<d>:<r> ", enable_ansi_colors),
.{key},
);
} else if (key.is16Bit()) {
@@ -1941,6 +1942,30 @@ pub const ZigConsoleClient = struct {
writer.print(comptime Output.prettyFmt("<r><yellow>{s}n<r>", enable_ansi_colors), .{out_str});
},
.Double => {
+ if (value.isCell()) {
+ var number_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &number_name);
+
+ var number_value = ZigString.Empty;
+ value.toZigString(&number_value, this.globalThis);
+
+ if (!strings.eqlComptime(number_name.slice(), "Number")) {
+ this.addForNewLine(number_name.len + number_value.len + "[Number ():]".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>[Number ({s}): {s}]<r>", enable_ansi_colors), .{
+ number_name,
+ number_value,
+ });
+ return;
+ }
+
+ this.addForNewLine(number_name.len + number_value.len + 4);
+ writer.print(comptime Output.prettyFmt("<r><yellow>[{s}: {s}]<r>", enable_ansi_colors), .{
+ number_name,
+ number_value,
+ });
+ return;
+ }
+
const num = value.asNumber();
if (std.math.isPositiveInf(num)) {
@@ -1981,7 +2006,6 @@ pub const ZigConsoleClient = struct {
value,
null,
null,
-
Writer,
writer_,
enable_ansi_colors,
@@ -2091,13 +2115,13 @@ pub const ZigConsoleClient = struct {
},
.Private => {
if (value.as(JSC.WebCore.Response)) |response| {
- response.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ response.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.WebCore.Request)) |request| {
- request.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ request.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.WebCore.Blob)) |blob| {
- blob.writeFormat(this, writer_, enable_ansi_colors) catch {};
+ blob.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.DOMFormData) != null) {
const toJSONFunction = value.get(this.globalThis, "toJSON").?;
@@ -2162,6 +2186,7 @@ pub const ZigConsoleClient = struct {
writer.writeAll("\n");
this.writeIndent(Writer, writer_) catch {};
}
+
writer.writeAll("Promise { " ++ comptime Output.prettyFmt("<r><cyan>", enable_ansi_colors));
switch (JSPromise.status(@ptrCast(*JSPromise, value.asObjectRef().?), this.globalThis.vm())) {
@@ -2179,16 +2204,36 @@ pub const ZigConsoleClient = struct {
writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors) ++ " }");
},
.Boolean => {
+ if (value.isCell()) {
+ var bool_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &bool_name);
+ var bool_value = ZigString.Empty;
+ value.toZigString(&bool_value, this.globalThis);
+
+ if (!strings.eqlComptime(bool_name.slice(), "Boolean")) {
+ this.addForNewLine(bool_value.len + bool_name.len + "[Boolean (): ]".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>[Boolean ({s}): {s}]<r>", enable_ansi_colors), .{
+ bool_name,
+ bool_value,
+ });
+ return;
+ }
+ this.addForNewLine(bool_value.len + "[Boolean: ]".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>[Boolean: {s}]<r>", enable_ansi_colors), .{bool_value});
+ return;
+ }
if (value.toBoolean()) {
- this.addForNewLine(5);
+ this.addForNewLine(4);
writer.writeAll(comptime Output.prettyFmt("<r><yellow>true<r>", enable_ansi_colors));
} else {
- this.addForNewLine(4);
+ this.addForNewLine(5);
writer.writeAll(comptime Output.prettyFmt("<r><yellow>false<r>", enable_ansi_colors));
}
},
.GlobalObject => {
- writer.writeAll(comptime Output.prettyFmt("<cyan>[this.globalThis]<r>", enable_ansi_colors));
+ const fmt = "[this.globalThis]";
+ this.addForNewLine(fmt.len);
+ writer.writeAll(comptime Output.prettyFmt("<cyan>" ++ fmt ++ "<r>", enable_ansi_colors));
},
.Map => {
const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
@@ -2198,11 +2243,13 @@ pub const ZigConsoleClient = struct {
this.quote_strings = true;
defer this.quote_strings = prev_quote_strings;
+ const map_name = if (value.jsType() == .JSWeakMap) "WeakMap" else "Map";
+
if (length == 0) {
- return writer.writeAll("Map {}");
+ return writer.print("{s} {{}}", .{map_name});
}
- writer.print("Map({d}) {{\n", .{length});
+ writer.print("{s}({d}) {{\n", .{ map_name, length });
{
this.indent += 1;
defer this.indent -|= 1;
@@ -2224,10 +2271,14 @@ pub const ZigConsoleClient = struct {
defer this.quote_strings = prev_quote_strings;
this.writeIndent(Writer, writer_) catch {};
+
+ const set_name = if (value.jsType() == .JSWeakSet) "WeakSet" else "Set";
+
if (length == 0) {
- return writer.writeAll("Set {}");
+ return writer.print("{s} {{}}", .{set_name});
}
- writer.print("Set({d}) {{\n", .{length});
+
+ writer.print("{s}({d}) {{\n", .{ set_name, length });
{
this.indent += 1;
defer this.indent -|= 1;
@@ -2590,9 +2641,6 @@ pub const ZigConsoleClient = struct {
} else {
if (iter.always_newline) {
this.indent -|= 1;
- }
-
- if (iter.always_newline) {
writer.writeAll("\n");
this.writeIndent(Writer, writer_) catch {};
writer.writeAll("}");
@@ -2605,11 +2653,11 @@ pub const ZigConsoleClient = struct {
},
.TypedArray => {
const arrayBuffer = value.asArrayBuffer(this.globalThis).?;
-
const slice = arrayBuffer.byteSlice();
- writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
writer.print("({d}) [ ", .{arrayBuffer.len});
+
if (slice.len > 0) {
switch (jsType) {
.Int8Array => this.writeTypedArray(
@@ -2668,13 +2716,15 @@ pub const ZigConsoleClient = struct {
@alignCast(std.meta.alignment([]i64), std.mem.bytesAsSlice(i64, slice)),
enable_ansi_colors,
),
- .BigUint64Array => this.writeTypedArray(
- *@TypeOf(writer),
- &writer,
- u64,
- @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice)),
- enable_ansi_colors,
- ),
+ .BigUint64Array => {
+ this.writeTypedArray(
+ *@TypeOf(writer),
+ &writer,
+ u64,
+ @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice)),
+ enable_ansi_colors,
+ );
+ },
// Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer
else => this.writeTypedArray(*@TypeOf(writer), &writer, u8, slice, enable_ansi_colors),
@@ -2687,7 +2737,7 @@ pub const ZigConsoleClient = struct {
}
}
- fn writeTypedArray(this: *ZigConsoleClient.Formatter, comptime Writer: type, writer: Writer, comptime Number: type, slice: []const Number, comptime enable_ansi_colors: bool) void {
+ fn writeTypedArray(this: *ZigConsoleClient.Formatter, comptime WriterWrapped: type, writer: WriterWrapped, comptime Number: type, slice: []const Number, comptime enable_ansi_colors: bool) void {
const fmt_ = if (Number == i64 or Number == u64)
"<r><yellow>{d}n<r>"
else
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 015c740ec..f786e3399 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -616,6 +616,87 @@ pub const JSExpect = struct {
}
}
};
+pub const JSExpectAny = struct {
+ const ExpectAny = Classes.ExpectAny;
+ const GetterType = fn (*ExpectAny, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const GetterTypeWithThisValue = fn (*ExpectAny, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn (*ExpectAny, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const SetterTypeWithThisValue = fn (*ExpectAny, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn (*ExpectAny, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+ /// Return the pointer to the wrapped object.
+ /// If the object does not match the type, return null.
+ pub fn fromJS(value: JSC.JSValue) ?*ExpectAny {
+ JSC.markBinding(@src());
+ return ExpectAny__fromJS(value);
+ }
+
+ extern fn ExpectAnyPrototype__constructorValueSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void;
+
+ extern fn ExpectAnyPrototype__constructorValueGetCachedValue(JSC.JSValue) JSC.JSValue;
+
+ /// `ExpectAny.constructorValue` setter
+ /// This value will be visited by the garbage collector.
+ pub fn constructorValueSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ ExpectAnyPrototype__constructorValueSetCachedValue(thisValue, globalObject, value);
+ }
+
+ /// `ExpectAny.constructorValue` getter
+ /// This value will be visited by the garbage collector.
+ pub fn constructorValueGetCached(thisValue: JSC.JSValue) ?JSC.JSValue {
+ JSC.markBinding(@src());
+ const result = ExpectAnyPrototype__constructorValueGetCachedValue(thisValue);
+ if (result == .zero)
+ return null;
+
+ return result;
+ }
+
+ /// Create a new instance of ExpectAny
+ pub fn toJS(this: *ExpectAny, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ if (comptime Environment.allow_assert) {
+ const value__ = ExpectAny__create(globalObject, this);
+ std.debug.assert(value__.as(ExpectAny).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return ExpectAny__create(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of ExpectAny.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ExpectAny) bool {
+ JSC.markBinding(@src());
+ return ExpectAny__dangerouslySetPtr(value, ptr);
+ }
+
+ /// Detach the ptr from the thisValue
+ pub fn detachPtr(_: *ExpectAny, value: JSC.JSValue) void {
+ JSC.markBinding(@src());
+ std.debug.assert(ExpectAny__dangerouslySetPtr(value, null));
+ }
+
+ extern fn ExpectAny__fromJS(JSC.JSValue) ?*ExpectAny;
+ extern fn ExpectAny__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
+
+ extern fn ExpectAny__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ExpectAny) JSC.JSValue;
+
+ extern fn ExpectAny__dangerouslySetPtr(JSC.JSValue, ?*ExpectAny) bool;
+
+ comptime {
+ if (@TypeOf(ExpectAny.finalize) != (fn (*ExpectAny) callconv(.C) void)) {
+ @compileLog("ExpectAny.finalize is not a finalizer");
+ }
+
+ if (@TypeOf(ExpectAny.call) != StaticCallbackType)
+ @compileLog("Expected ExpectAny.call to be a static callback");
+ if (!JSC.is_bindgen) {
+ @export(ExpectAny.call, .{ .name = "ExpectAnyClass__call" });
+ @export(ExpectAny.finalize, .{ .name = "ExpectAnyClass__finalize" });
+ }
+ }
+};
pub const JSFileSystemRouter = struct {
const FileSystemRouter = Classes.FileSystemRouter;
const GetterType = fn (*FileSystemRouter, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
@@ -3765,6 +3846,7 @@ comptime {
_ = JSCryptoHasher;
_ = JSDirent;
_ = JSExpect;
+ _ = JSExpectAny;
_ = JSFileSystemRouter;
_ = JSListener;
_ = JSMD4;
diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig
index d779a4ff1..3c895e219 100644
--- a/src/bun.js/bindings/generated_classes_list.zig
+++ b/src/bun.js/bindings/generated_classes_list.zig
@@ -20,6 +20,7 @@ pub const Classes = struct {
pub const TLSSocket = JSC.API.TLSSocket;
pub const Listener = JSC.API.Listener;
pub const Expect = JSC.Jest.Expect;
+ pub const ExpectAny = JSC.Jest.ExpectAny;
pub const FileSystemRouter = JSC.API.FileSystemRouter;
pub const MatchedRoute = JSC.API.MatchedRoute;
pub const Dirent = JSC.Node.Dirent;
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 20b7fbedb..8312706e1 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -319,6 +319,7 @@ CPP_DECL bool JSC__JSValue__isBigInt32(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isBoolean(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isCallable(JSC__JSValue JSValue0, JSC__VM* arg1);
CPP_DECL bool JSC__JSValue__isClass(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1);
+CPP_DECL bool JSC__JSValue__isConstructor(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isCustomGetterSetter(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isError(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isException(JSC__JSValue JSValue0, JSC__VM* arg1);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index ce84d1e73..ee0c4fb54 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -232,6 +232,7 @@ pub extern fn JSC__JSValue__isBigInt32(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isBoolean(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isCallable(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
pub extern fn JSC__JSValue__isClass(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) bool;
+pub extern fn JSC__JSValue__isConstructor(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isCustomGetterSetter(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isError(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isException(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index d56d0bdeb..f54dd5fb8 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -1628,7 +1628,6 @@ pub const VirtualMachine = struct {
// If there were multiple errors, it could be contained in an AggregateError.
// In that case, this function becomes recursive.
// In all other cases, we will convert it to a ZigException.
- const errors_property = ZigString.init("errors");
pub fn printErrorlikeObject(
this: *VirtualMachine,
value: JSValue,
diff --git a/src/bun.js/scripts/generate-classes.ts b/src/bun.js/scripts/generate-classes.ts
index d39ea027b..f13132f8f 100644
--- a/src/bun.js/scripts/generate-classes.ts
+++ b/src/bun.js/scripts/generate-classes.ts
@@ -292,9 +292,7 @@ export function generateHashTable(nameToUse, symbolName, typeName, obj, props =
// { "CLOSED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, 3 } },
// };
return `
- static const HashTableValue ${nameToUse}TableValues[] = {
-${rows.join(" ,\n")}
- };
+ static const HashTableValue ${nameToUse}TableValues[] = {${rows.length > 0 ? "\n" + rows.join(" ,\n") + "\n" : ""}};
`;
}
@@ -348,7 +346,11 @@ ${renderFieldsImpl(protoSymbolName, typeName, obj, protoFields, obj.values || []
void ${proto}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
- reifyStaticProperties(vm, ${className(typeName)}::info(), ${proto}TableValues, *this);${specialSymbols}
+ ${
+ Object.keys(protoFields).length > 0
+ ? `reifyStaticProperties(vm, ${className(typeName)}::info(), ${proto}TableValues, *this);`
+ : ""
+ }${specialSymbols}
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
@@ -1144,9 +1146,9 @@ function generateImpl(typeName, obj) {
return [
generatePrototypeHeader(typeName),
!obj.noConstructor ? generateConstructorHeader(typeName).trim() + "\n" : null,
- Object.keys(proto).length > 0 && generatePrototype(typeName, obj).trim(),
+ generatePrototype(typeName, obj).trim(),
!obj.noConstructor ? generateConstructorImpl(typeName, obj).trim() : null,
- Object.keys(proto).length > 0 && generateClassImpl(typeName, obj).trim(),
+ generateClassImpl(typeName, obj).trim(),
]
.filter(Boolean)
.join("\n\n");
diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts
index 78848b287..9182c8cc6 100644
--- a/src/bun.js/test/jest.classes.ts
+++ b/src/bun.js/test/jest.classes.ts
@@ -2,6 +2,18 @@ import { define } from "../scripts/class-definitions";
export default [
define({
+ name: "ExpectAny",
+ construct: false,
+ noConstructor: true,
+ call: true,
+ finalize: true,
+ JSType: "0b11101110",
+ values: ["constructorValue"],
+ configurable: false,
+ klass: {},
+ proto: {},
+ }),
+ define({
name: "Expect",
construct: true,
call: true,
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 2cb86c4f7..d8d4fb246 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -1,5 +1,7 @@
const std = @import("std");
const bun = @import("bun");
+const js_parser = bun.js_parser;
+const js_ast = bun.JSAst;
const Api = @import("../../api/schema.zig").Api;
const RequestContext = @import("../../http.zig").RequestContext;
const MimeType = @import("../../http.zig").MimeType;
@@ -64,12 +66,75 @@ fn notImplementedProp(
}
pub const DiffFormatter = struct {
- received: JSValue,
- expected: JSValue,
+ received_string: ?string = null,
+ expected_string: ?string = null,
+ received: ?JSValue = null,
+ expected: ?JSValue = null,
globalObject: *JSC.JSGlobalObject,
not: bool = false,
pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ if (this.expected_string != null and this.received_string != null) {
+ const received = this.received_string.?;
+ const expected = this.expected_string.?;
+
+ var dmp = DiffMatchPatch.default;
+ dmp.diff_timeout = 200;
+ var diffs = try dmp.diff(default_allocator, received, expected, false);
+ defer diffs.deinit(default_allocator);
+
+ const equal_fmt = "<d>{s}<r>";
+ const delete_fmt = "<red>{s}<r>";
+ const insert_fmt = "<green>{s}<r>";
+
+ try writer.writeAll("Expected: ");
+ for (diffs.items) |df| {
+ switch (df.operation) {
+ .delete => continue,
+ .insert => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text});
+ }
+ },
+ .equal => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text});
+ }
+ },
+ }
+ }
+
+ try writer.writeAll("\nReceived: ");
+ for (diffs.items) |df| {
+ switch (df.operation) {
+ .insert => continue,
+ .delete => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text});
+ }
+ },
+ .equal => {
+ if (Output.enable_ansi_colors) {
+ try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text});
+ } else {
+ try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text});
+ }
+ },
+ }
+ }
+ return;
+ }
+
+ if (this.received == null or this.expected == null) return;
+
+ const received = this.received.?;
+ const expected = this.expected.?;
var received_buf = MutableString.init(default_allocator, 0) catch unreachable;
var expected_buf = MutableString.init(default_allocator, 0) catch unreachable;
defer {
@@ -94,7 +159,7 @@ pub const DiffFormatter = struct {
JSC.ZigConsoleClient.format(
.Debug,
this.globalObject,
- @ptrCast([*]const JSValue, &this.received),
+ @ptrCast([*]const JSValue, &received),
1,
Writer,
Writer,
@@ -131,21 +196,21 @@ pub const DiffFormatter = struct {
return;
}
- switch (this.received.determineDiffMethod(this.expected, this.globalObject)) {
+ switch (received.determineDiffMethod(expected, this.globalObject)) {
.none => {
const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>";
var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = this.globalObject, .quote_strings = true };
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(fmt, true), .{
- this.expected.toFmt(this.globalObject, &formatter),
- this.received.toFmt(this.globalObject, &formatter),
+ expected.toFmt(this.globalObject, &formatter),
+ received.toFmt(this.globalObject, &formatter),
});
return;
}
try writer.print(Output.prettyFmt(fmt, true), .{
- this.expected.toFmt(this.globalObject, &formatter),
- this.received.toFmt(this.globalObject, &formatter),
+ expected.toFmt(this.globalObject, &formatter),
+ received.toFmt(this.globalObject, &formatter),
});
return;
},
@@ -305,6 +370,8 @@ pub const TestRunner = struct {
/// This silences TestNotRunningError when expect() is used to halt a running test.
did_pending_test_fail: bool = false,
+ snapshots: Snapshots,
+
pub const Drainer = JSC.AnyTask.New(TestRunner, drain);
pub fn enqueue(this: *TestRunner, task: *TestRunnerTask) void {
@@ -427,6 +494,274 @@ pub const TestRunner = struct {
};
};
+pub const Snapshots = struct {
+ const file_header = "// Bun Snapshot v1, https://goo.gl/fbAQLP\n";
+ pub const ValuesHashMap = std.HashMap(usize, string, bun.IdentityContext(usize), std.hash_map.default_max_load_percentage);
+
+ allocator: std.mem.Allocator,
+ update_snapshots: bool,
+ total: usize = 0,
+ added: usize = 0,
+ passed: usize = 0,
+ failed: usize = 0,
+
+ file_buf: *std.ArrayList(u8),
+ values: *ValuesHashMap,
+ counts: *bun.StringHashMap(usize),
+ _current_file: ?File = null,
+ snapshot_dir_path: ?string = null,
+
+ const File = struct {
+ id: TestRunner.File.ID,
+ file: std.fs.File,
+ };
+
+ pub fn getOrPut(this: *Snapshots, expect: *Expect, value: JSValue, hint: string, globalObject: *JSC.JSGlobalObject) !?string {
+ switch (try this.getSnapshotFile(expect.scope.file_id)) {
+ .result => {},
+ .err => |err| {
+ return switch (err.syscall) {
+ .mkdir => error.FailedToMakeSnapshotDirectory,
+ .open => error.FailedToOpenSnapshotFile,
+ else => error.SnapshotFailed,
+ };
+ },
+ }
+
+ const snapshot_name = try expect.getSnapshotName(this.allocator, hint);
+ this.total += 1;
+
+ var count_entry = try this.counts.getOrPut(snapshot_name);
+ const counter = brk: {
+ if (count_entry.found_existing) {
+ this.allocator.free(snapshot_name);
+ count_entry.value_ptr.* += 1;
+ break :brk count_entry.value_ptr.*;
+ }
+ count_entry.value_ptr.* = 1;
+ break :brk count_entry.value_ptr.*;
+ };
+
+ const name = count_entry.key_ptr.*;
+
+ var counter_string_buf = [_]u8{0} ** 32;
+ var counter_string = try std.fmt.bufPrint(&counter_string_buf, "{d}", .{counter});
+
+ var name_with_counter = try this.allocator.alloc(u8, name.len + 1 + counter_string.len);
+ defer this.allocator.free(name_with_counter);
+ bun.copy(u8, name_with_counter[0..name.len], name);
+ name_with_counter[name.len] = ' ';
+ bun.copy(u8, name_with_counter[name.len + 1 ..], counter_string);
+
+ const name_hash = std.hash.Wyhash.hash(0, name_with_counter);
+ if (this.values.get(name_hash)) |expected| {
+ return expected;
+ }
+
+ // doesn't exist. append to file bytes and add to hashmap.
+ var pretty_value = try MutableString.init(this.allocator, 0);
+ try value.jestSnapshotPrettyFormat(&pretty_value, globalObject);
+
+ const serialized_length = "\nexports[`".len + name_with_counter.len + "`] = `".len + pretty_value.list.items.len + "`;\n".len;
+ try this.file_buf.ensureUnusedCapacity(serialized_length);
+ this.file_buf.appendSliceAssumeCapacity("\nexports[`");
+ this.file_buf.appendSliceAssumeCapacity(name_with_counter);
+ this.file_buf.appendSliceAssumeCapacity("`] = `");
+ this.file_buf.appendSliceAssumeCapacity(pretty_value.list.items);
+ this.file_buf.appendSliceAssumeCapacity("`;\n");
+
+ this.added += 1;
+ try this.values.put(name_hash, pretty_value.toOwnedSlice());
+ return null;
+ }
+
+ pub fn parseFile(this: *Snapshots) !void {
+ if (this.file_buf.items.len == 0) return;
+
+ const vm = VirtualMachine.get();
+ var opts = js_parser.Parser.Options.init(vm.bundler.options.jsx, .js);
+ var temp_log = logger.Log.init(this.allocator);
+
+ const test_file = Jest.runner.?.files.get(this._current_file.?.id);
+ const test_filename = test_file.source.path.name.filename;
+ const dir_path = test_file.source.path.name.dirWithTrailingSlash();
+
+ var snapshot_file_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ var remain: []u8 = snapshot_file_path_buf[0..bun.MAX_PATH_BYTES];
+ bun.copy(u8, remain, dir_path);
+ remain = remain[dir_path.len..];
+ bun.copy(u8, remain, "__snapshots__/");
+ remain = remain["__snapshots__/".len..];
+ bun.copy(u8, remain, test_filename);
+ remain = remain[test_filename.len..];
+ bun.copy(u8, remain, ".snap");
+ remain = remain[".snap".len..];
+ remain[0] = 0;
+ const snapshot_file_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+
+ const source = logger.Source.initPathString(snapshot_file_path, this.file_buf.items);
+
+ var parser = try js_parser.Parser.init(
+ opts,
+ &temp_log,
+ &source,
+ vm.bundler.options.define,
+ this.allocator,
+ );
+
+ var parse_result = try parser.parse();
+ var ast = if (parse_result.ok) parse_result.ast else return error.ParseError;
+ defer ast.deinit();
+
+ if (ast.exports_ref == null) return;
+ const exports_ref = ast.exports_ref.?;
+
+ // TODO: when common js transform changes, keep this updated or add flag to support this version
+
+ const export_default = brk: {
+ for (ast.parts) |part| {
+ for (part.stmts) |stmt| {
+ if (stmt.data == .s_export_default and stmt.data.s_export_default.value == .expr) {
+ break :brk stmt.data.s_export_default.value.expr;
+ }
+ }
+ }
+
+ return;
+ };
+
+ if (export_default.data == .e_call) {
+ const function_call = export_default.data.e_call;
+ if (function_call.args.len == 2 and function_call.args.ptr[0].data == .e_function) {
+ const arg_function_stmts = function_call.args.ptr[0].data.e_function.func.body.stmts;
+ for (arg_function_stmts) |stmt| {
+ switch (stmt.data) {
+ .s_expr => |expr| {
+ if (expr.value.data == .e_binary and expr.value.data.e_binary.op == .bin_assign) {
+ const left = expr.value.data.e_binary.left;
+ if (left.data == .e_index and left.data.e_index.index.data == .e_string and left.data.e_index.target.data == .e_identifier) {
+ const target: js_ast.E.Identifier = left.data.e_index.target.data.e_identifier;
+ var index: *js_ast.E.String = left.data.e_index.index.data.e_string;
+ if (target.ref.eql(exports_ref) and expr.value.data.e_binary.right.data == .e_string) {
+ const key = index.slice(this.allocator);
+ var value_string = expr.value.data.e_binary.right.data.e_string;
+ const value = value_string.slice(this.allocator);
+ defer {
+ if (!index.isUTF8()) this.allocator.free(key);
+ if (!value_string.isUTF8()) this.allocator.free(value);
+ }
+ const value_clone = try this.allocator.alloc(u8, value.len);
+ bun.copy(u8, value_clone, value);
+ const name_hash = std.hash.Wyhash.hash(0, key);
+ try this.values.put(name_hash, value_clone);
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ }
+ }
+ }
+ }
+
+ pub fn writeSnapshotFile(this: *Snapshots) !void {
+ if (this._current_file) |_file| {
+ var file = _file;
+ file.file.writeAll(this.file_buf.items) catch {
+ return error.FailedToWriteSnapshotFile;
+ };
+ file.file.close();
+ this.file_buf.clearAndFree();
+
+ var value_itr = this.values.valueIterator();
+ while (value_itr.next()) |value| {
+ this.allocator.free(value.*);
+ }
+ this.values.clearAndFree();
+
+ var count_key_itr = this.counts.keyIterator();
+ while (count_key_itr.next()) |key| {
+ this.allocator.free(key.*);
+ }
+ this.counts.clearAndFree();
+ }
+ }
+
+ fn getSnapshotFile(this: *Snapshots, file_id: TestRunner.File.ID) !JSC.Maybe(void) {
+ if (this._current_file == null or this._current_file.?.id != file_id) {
+ try this.writeSnapshotFile();
+
+ const test_file = Jest.runner.?.files.get(file_id);
+ const test_filename = test_file.source.path.name.filename;
+ const dir_path = test_file.source.path.name.dirWithTrailingSlash();
+
+ var snapshot_file_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ var remain: []u8 = snapshot_file_path_buf[0..bun.MAX_PATH_BYTES];
+ bun.copy(u8, remain, dir_path);
+ remain = remain[dir_path.len..];
+ bun.copy(u8, remain, "__snapshots__/");
+ remain = remain["__snapshots__/".len..];
+
+ if (this.snapshot_dir_path == null or !strings.eqlLong(dir_path, this.snapshot_dir_path.?, true)) {
+ remain[0] = 0;
+ const snapshot_dir_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+ switch (JSC.Node.Syscall.mkdir(snapshot_dir_path, 0o777)) {
+ .result => this.snapshot_dir_path = dir_path,
+ .err => |err| {
+ switch (err.getErrno()) {
+ std.os.E.EXIST => this.snapshot_dir_path = dir_path,
+ else => return JSC.Maybe(void){
+ .err = err,
+ },
+ }
+ },
+ }
+ }
+
+ bun.copy(u8, remain, test_filename);
+ remain = remain[test_filename.len..];
+ bun.copy(u8, remain, ".snap");
+ remain = remain[".snap".len..];
+ remain[0] = 0;
+ const snapshot_file_path = snapshot_file_path_buf[0 .. snapshot_file_path_buf.len - remain.len :0];
+
+ var flags: JSC.Node.Mode = std.os.O.CREAT | std.os.O.RDWR;
+ if (this.update_snapshots) flags |= std.os.O.TRUNC;
+ const fd = switch (JSC.Node.Syscall.open(snapshot_file_path, flags, 0o644)) {
+ .result => |_fd| _fd,
+ .err => |err| return JSC.Maybe(void){
+ .err = err,
+ },
+ };
+
+ var file: File = .{
+ .id = file_id,
+ .file = .{ .handle = fd },
+ };
+
+ if (this.update_snapshots) {
+ try this.file_buf.appendSlice(file_header);
+ } else {
+ const length = try file.file.getEndPos();
+ if (length == 0) {
+ try this.file_buf.appendSlice(file_header);
+ } else {
+ const buf = try this.allocator.alloc(u8, length);
+ _ = try file.file.preadAll(buf, 0);
+ try this.file_buf.appendSlice(buf);
+ this.allocator.free(buf);
+ }
+ }
+
+ this._current_file = file;
+ try this.parseFile();
+ }
+
+ return JSC.Maybe(void).success;
+ }
+};
+
pub const Jest = struct {
pub var runner: ?*TestRunner = null;
@@ -465,6 +800,54 @@ pub const Jest = struct {
}
};
+pub const ExpectAny = struct {
+ pub usingnamespace JSC.Codegen.JSExpectAny;
+
+ pub fn finalize(
+ this: *ExpectAny,
+ ) callconv(.C) void {
+ VirtualMachine.get().allocator.destroy(this);
+ }
+
+ pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const _arguments = callFrame.arguments(1);
+ const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
+
+ if (arguments.len == 0) {
+ globalObject.throw("any() expects to be passed a constructor function.", .{});
+ return .zero;
+ }
+
+ const constructor = arguments[0];
+ constructor.ensureStillAlive();
+ if (!constructor.isConstructor()) {
+ const fmt = "<d>expect.<r>any<d>(<r>constructor<d>)<r>\n\nExpected a constructor\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ var any = globalObject.bunVM().allocator.create(ExpectAny) catch unreachable;
+
+ if (Jest.runner.?.pending_test == null) {
+ const err = globalObject.createErrorInstance("expect.any() must be called in a test", .{});
+ err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject));
+ globalObject.throwValue(err);
+ return .zero;
+ }
+
+ any.* = .{};
+ const any_js_value = any.toJS(globalObject);
+ any_js_value.ensureStillAlive();
+ JSC.Jest.ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor);
+ any_js_value.ensureStillAlive();
+
+ var vm = globalObject.bunVM();
+ vm.autoGarbageCollect();
+
+ return any_js_value;
+ }
+};
+
/// https://jestjs.io/docs/expect
// To support async tests, we need to track the test ID
pub const Expect = struct {
@@ -481,6 +864,49 @@ pub const Expect = struct {
pub const Set = std.EnumSet(Op);
};
+ pub fn getSnapshotName(this: *Expect, allocator: std.mem.Allocator, hint: string) ![]const u8 {
+ const test_name = this.scope.tests.items[this.test_id].label;
+
+ var length: usize = 0;
+ var curr_scope: ?*DescribeScope = this.scope;
+ while (curr_scope) |scope| {
+ if (scope.label.len > 0) {
+ length += scope.label.len + 1;
+ }
+ curr_scope = scope.parent;
+ }
+ length += test_name.len;
+ if (hint.len > 0) {
+ length += hint.len + 2;
+ }
+
+ var buf = try allocator.alloc(u8, length);
+
+ var index = buf.len;
+ if (hint.len > 0) {
+ index -= hint.len;
+ bun.copy(u8, buf[index..], hint);
+ index -= test_name.len + 2;
+ bun.copy(u8, buf[index..], test_name);
+ bun.copy(u8, buf[index + test_name.len ..], ": ");
+ } else {
+ index -= test_name.len;
+ bun.copy(u8, buf[index..], test_name);
+ }
+ // copy describe scopes in reverse order
+ curr_scope = this.scope;
+ while (curr_scope) |scope| {
+ if (scope.label.len > 0) {
+ index -= scope.label.len + 1;
+ bun.copy(u8, buf[index..], scope.label);
+ buf[index + scope.label.len] = ' ';
+ }
+ curr_scope = scope.parent;
+ }
+
+ return buf;
+ }
+
pub fn finalize(
this: *Expect,
) callconv(.C) void {
@@ -2080,6 +2506,215 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toMatchSnapshot(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
+ defer this.postMatch(globalObject);
+ const thisValue = callFrame.this();
+ const _arguments = callFrame.arguments(2);
+ const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalObject.throw("toMatchSnapshot() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const not = this.op.contains(.not);
+ if (not) {
+ const signature = comptime getSignature("toMatchSnapshot", "", true);
+ const fmt = signature ++ "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n";
+ globalObject.throwPretty(fmt, .{});
+ }
+
+ var hint_string: ZigString = ZigString.Empty;
+ var property_matchers: ?JSValue = null;
+ switch (arguments.len) {
+ 0 => {},
+ 1 => {
+ if (arguments[0].isString()) {
+ arguments[0].toZigString(&hint_string, globalObject);
+ } else if (arguments[0].isObject()) {
+ property_matchers = arguments[0];
+ }
+ },
+ else => {
+ if (!arguments[0].isObject()) {
+ const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+ const fmt = signature ++ "\n\nMatcher error: Expected <green>properties<r> must be an object\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ property_matchers = arguments[0];
+
+ if (arguments[1].isString()) {
+ arguments[1].toZigString(&hint_string, globalObject);
+ }
+ },
+ }
+
+ var hint = hint_string.toSlice(default_allocator);
+ defer hint.deinit();
+
+ const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ if (!value.isObject() and property_matchers != null) {
+ const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+ const fmt = signature ++ "\n\n<b>Matcher error: <red>received<r> values must be an object when the matcher has <green>properties<r>\n";
+ globalObject.throwPretty(fmt, .{});
+ return .zero;
+ }
+
+ if (property_matchers) |_prop_matchers| {
+ var prop_matchers = _prop_matchers;
+
+ var itr = PropertyMatcherIterator{
+ .received_object = value,
+ .failed = false,
+ };
+
+ prop_matchers.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach);
+
+ if (itr.failed) {
+ // TODO: print diff with properties from propertyMatchers
+ const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false);
+ const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
+ "\n\nReceived: {any}\n";
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ }
+
+ const result = Jest.runner.?.snapshots.getOrPut(this, value, hint.slice(), globalObject) catch |err| {
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ const test_file_path = Jest.runner.?.files.get(this.scope.file_id).source.path.text;
+ switch (err) {
+ error.FailedToOpenSnapshotFile => globalObject.throw("Failed to open snapshot file for test file: {s}", .{test_file_path}),
+ error.FailedToMakeSnapshotDirectory => globalObject.throw("Failed to make snapshot directory for test file: {s}", .{test_file_path}),
+ error.FailedToWriteSnapshotFile => globalObject.throw("Failed write to snapshot file: {s}", .{test_file_path}),
+ error.ParseError => globalObject.throw("Failed to parse snapshot file for: {s}", .{test_file_path}),
+ else => globalObject.throw("Failed to snapshot value: {any}", .{value.toFmt(globalObject, &formatter)}),
+ }
+ return .zero;
+ };
+
+ if (result) |saved_value| {
+ var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable;
+ value.jestSnapshotPrettyFormat(&pretty_value, globalObject) catch {
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ globalObject.throw("Failed to pretty format value: {s}", .{value.toFmt(globalObject, &formatter)});
+ return .zero;
+ };
+ defer pretty_value.deinit();
+
+ if (strings.eqlLong(pretty_value.toOwnedSliceLeaky(), saved_value, true)) {
+ Jest.runner.?.snapshots.passed += 1;
+ return thisValue;
+ }
+
+ Jest.runner.?.snapshots.failed += 1;
+ const signature = comptime getSignature("toMatchSnapshot", "<green>expected<r>", false);
+ const fmt = signature ++ "\n\n{any}\n";
+ const diff_format = DiffFormatter{
+ .received_string = pretty_value.toOwnedSliceLeaky(),
+ .expected_string = saved_value,
+ .globalObject = globalObject,
+ };
+
+ globalObject.throwPretty(fmt, .{diff_format});
+ return .zero;
+ }
+
+ return thisValue;
+ }
+
+ pub const PropertyMatcherIterator = struct {
+ received_object: JSValue,
+ failed: bool,
+ i: usize = 0,
+
+ pub fn forEach(
+ globalObject: *JSGlobalObject,
+ ctx_ptr: ?*anyopaque,
+ key_: [*c]ZigString,
+ value: JSValue,
+ _: bool,
+ ) callconv(.C) void {
+ const key: ZigString = key_.?[0];
+ if (key.eqlComptime("constructor")) return;
+ if (key.eqlComptime("call")) return;
+
+ var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return);
+ defer ctx.i += 1;
+ var received_object: JSValue = ctx.received_object;
+
+ if (received_object.get(globalObject, key.slice())) |received_value| {
+ if (JSC.Jest.ExpectAny.fromJS(value)) |_| {
+ var constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse {
+ globalObject.throw("Internal consistency error: the expect.any(constructor value) was garbage collected but it should not have been!", .{});
+ ctx.failed = true;
+ return;
+ };
+
+ if (received_value.isCell() and received_value.isInstanceOf(globalObject, constructor_value)) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+
+ // check primitives
+ // TODO: check the constructor for primitives by reading it from JSGlobalObject through a binding.
+ var constructor_name = ZigString.Empty;
+ constructor_value.getNameProperty(globalObject, &constructor_name);
+ if (received_value.isNumber() and constructor_name.eqlComptime("Number")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isBoolean() and constructor_name.eqlComptime("Boolean")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isString() and constructor_name.eqlComptime("String")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+ if (received_value.isBigInt() and constructor_name.eqlComptime("BigInt")) {
+ received_object.put(globalObject, &key, value);
+ return;
+ }
+
+ ctx.failed = true;
+ return;
+ }
+
+ if (value.isObject()) {
+ if (received_object.get(globalObject, key.slice())) |new_object| {
+ var itr = PropertyMatcherIterator{
+ .received_object = new_object,
+ .failed = false,
+ };
+ value.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach);
+ if (itr.failed) {
+ ctx.failed = true;
+ }
+ } else {
+ ctx.failed = true;
+ }
+
+ return;
+ }
+
+ if (value.isSameValue(received_value, globalObject)) return;
+ }
+
+ ctx.failed = true;
+ }
+ };
+
pub fn toBeInstanceOf(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
defer this.postMatch(globalObject);
@@ -2098,7 +2733,6 @@ pub const Expect = struct {
}
active_test_expectation_counter.actual += 1;
-
const expected_value = arguments[0];
if (!expected_value.jsType().isFunction()) {
var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
@@ -2156,7 +2790,6 @@ pub const Expect = struct {
pub const toContainEqual = notImplementedJSCFn;
pub const toMatch = notImplementedJSCFn;
pub const toMatchObject = notImplementedJSCFn;
- pub const toMatchSnapshot = notImplementedJSCFn;
pub const toMatchInlineSnapshot = notImplementedJSCFn;
pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn;
pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn;
@@ -2179,9 +2812,12 @@ pub const Expect = struct {
pub const getResolves = notImplementedJSCProp;
pub const getRejects = notImplementedJSCProp;
+ pub fn any(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
+ return ExpectAny.call(globalObject, callFrame);
+ }
+
pub const extend = notImplementedStaticFn;
pub const anything = notImplementedStaticFn;
- pub const any = notImplementedStaticFn;
pub const arrayContaining = notImplementedStaticFn;
pub const assertions = notImplementedStaticFn;
pub const hasAssertions = notImplementedStaticFn;
@@ -2225,6 +2861,7 @@ pub const TestScope = struct {
ran: bool = false,
task: ?*TestRunnerTask = null,
skipped: bool = false,
+ snapshot_count: usize = 0,
pub const Class = NewClass(
void,
diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig
new file mode 100644
index 000000000..dd5814aca
--- /dev/null
+++ b/src/bun.js/test/pretty_format.zig
@@ -0,0 +1,1963 @@
+const std = @import("std");
+const bun = @import("bun");
+const Output = bun.Output;
+const JSC = bun.JSC;
+const JSGlobalObject = JSC.JSGlobalObject;
+const JSValue = JSC.JSValue;
+const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false;
+const default_allocator = bun.default_allocator;
+const CAPI = JSC.C;
+const ZigString = JSC.ZigString;
+const strings = bun.strings;
+const string = bun.string;
+const JSLexer = bun.js_lexer;
+const JSPrinter = bun.js_printer;
+const JSPrivateDataPtr = JSC.JSPrivateDataPtr;
+const JS = @import("../javascript.zig");
+const JSPromise = JSC.JSPromise;
+
+pub const EventType = enum(u8) {
+ Event,
+ MessageEvent,
+ CloseEvent,
+ ErrorEvent,
+ OpenEvent,
+ unknown = 254,
+ _,
+
+ pub const map = bun.ComptimeStringMap(EventType, .{
+ .{ EventType.Event.label(), EventType.Event },
+ .{ EventType.MessageEvent.label(), EventType.MessageEvent },
+ .{ EventType.CloseEvent.label(), EventType.CloseEvent },
+ .{ EventType.ErrorEvent.label(), EventType.ErrorEvent },
+ .{ EventType.OpenEvent.label(), EventType.OpenEvent },
+ });
+
+ pub fn label(this: EventType) string {
+ return switch (this) {
+ .Event => "event",
+ .MessageEvent => "message",
+ .CloseEvent => "close",
+ .ErrorEvent => "error",
+ .OpenEvent => "open",
+ else => "event",
+ };
+ }
+};
+
+pub const JestPrettyFormat = struct {
+ pub const Type = *anyopaque;
+ const Counter = std.AutoHashMapUnmanaged(u64, u32);
+
+ counts: Counter = .{},
+
+ pub const MessageLevel = enum(u32) {
+ Log = 0,
+ Warning = 1,
+ Error = 2,
+ Debug = 3,
+ Info = 4,
+ _,
+ };
+
+ pub const MessageType = enum(u32) {
+ Log = 0,
+ Dir = 1,
+ DirXML = 2,
+ Table = 3,
+ Trace = 4,
+ StartGroup = 5,
+ StartGroupCollapsed = 6,
+ EndGroup = 7,
+ Clear = 8,
+ Assert = 9,
+ Timing = 10,
+ Profile = 11,
+ ProfileEnd = 12,
+ Image = 13,
+ _,
+ };
+
+ pub const FormatOptions = struct {
+ enable_colors: bool,
+ add_newline: bool,
+ flush: bool,
+ quote_strings: bool = false,
+ };
+
+ pub fn format(
+ level: MessageLevel,
+ global: *JSGlobalObject,
+ vals: [*]const JSValue,
+ len: usize,
+ comptime RawWriter: type,
+ comptime Writer: type,
+ writer: Writer,
+ options: FormatOptions,
+ ) void {
+ var fmt: JestPrettyFormat.Formatter = undefined;
+ defer {
+ if (fmt.map_node) |node| {
+ node.data = fmt.map;
+ node.data.clearRetainingCapacity();
+ node.release();
+ }
+ }
+
+ if (len == 1) {
+ fmt = JestPrettyFormat.Formatter{
+ .remaining_values = &[_]JSValue{},
+ .globalThis = global,
+ .quote_strings = options.quote_strings,
+ };
+ const tag = JestPrettyFormat.Formatter.Tag.get(vals[0], global);
+
+ var unbuffered_writer = if (comptime Writer != RawWriter)
+ writer.context.unbuffered_writer.context.writer()
+ else
+ writer;
+
+ if (tag.tag == .String) {
+ if (options.enable_colors) {
+ if (level == .Error) {
+ unbuffered_writer.writeAll(comptime Output.prettyFmt("<r><red>", true)) catch unreachable;
+ }
+ fmt.format(
+ tag,
+ @TypeOf(unbuffered_writer),
+ unbuffered_writer,
+ vals[0],
+ global,
+ true,
+ );
+ if (level == .Error) {
+ unbuffered_writer.writeAll(comptime Output.prettyFmt("<r>", true)) catch unreachable;
+ }
+ } else {
+ fmt.format(
+ tag,
+ @TypeOf(unbuffered_writer),
+ unbuffered_writer,
+ vals[0],
+ global,
+ false,
+ );
+ }
+ if (options.add_newline) _ = unbuffered_writer.write("\n") catch 0;
+ } else {
+ defer {
+ if (comptime Writer != RawWriter) {
+ if (options.flush) writer.context.flush() catch {};
+ }
+ }
+ if (options.enable_colors) {
+ fmt.format(
+ tag,
+ Writer,
+ writer,
+ vals[0],
+ global,
+ true,
+ );
+ } else {
+ fmt.format(
+ tag,
+ Writer,
+ writer,
+ vals[0],
+ global,
+ false,
+ );
+ }
+ if (options.add_newline) _ = writer.write("\n") catch 0;
+ }
+
+ return;
+ }
+
+ defer {
+ if (comptime Writer != RawWriter) {
+ if (options.flush) writer.context.flush() catch {};
+ }
+ }
+
+ var this_value: JSValue = vals[0];
+ fmt = JestPrettyFormat.Formatter{
+ .remaining_values = vals[0..len][1..],
+ .globalThis = global,
+ .quote_strings = options.quote_strings,
+ };
+ var tag: JestPrettyFormat.Formatter.Tag.Result = undefined;
+
+ var any = false;
+ if (options.enable_colors) {
+ if (level == .Error) {
+ writer.writeAll(comptime Output.prettyFmt("<r><red>", true)) catch unreachable;
+ }
+ while (true) {
+ if (any) {
+ _ = writer.write(" ") catch 0;
+ }
+ any = true;
+
+ tag = JestPrettyFormat.Formatter.Tag.get(this_value, global);
+ if (tag.tag == .String and fmt.remaining_values.len > 0) {
+ tag.tag = .StringPossiblyFormatted;
+ }
+
+ fmt.format(tag, Writer, writer, this_value, global, true);
+ if (fmt.remaining_values.len == 0) {
+ break;
+ }
+
+ this_value = fmt.remaining_values[0];
+ fmt.remaining_values = fmt.remaining_values[1..];
+ }
+ if (level == .Error) {
+ writer.writeAll(comptime Output.prettyFmt("<r>", true)) catch unreachable;
+ }
+ } else {
+ while (true) {
+ if (any) {
+ _ = writer.write(" ") catch 0;
+ }
+ any = true;
+ tag = JestPrettyFormat.Formatter.Tag.get(this_value, global);
+ if (tag.tag == .String and fmt.remaining_values.len > 0) {
+ tag.tag = .StringPossiblyFormatted;
+ }
+
+ fmt.format(tag, Writer, writer, this_value, global, false);
+ if (fmt.remaining_values.len == 0)
+ break;
+
+ this_value = fmt.remaining_values[0];
+ fmt.remaining_values = fmt.remaining_values[1..];
+ }
+ }
+
+ if (options.add_newline) _ = writer.write("\n") catch 0;
+ }
+
+ pub const Formatter = struct {
+ remaining_values: []const JSValue = &[_]JSValue{},
+ map: Visited.Map = undefined,
+ map_node: ?*Visited.Pool.Node = null,
+ hide_native: bool = false,
+ globalThis: *JSGlobalObject,
+ indent: u32 = 0,
+ quote_strings: bool = false,
+ failed: bool = false,
+ estimated_line_length: usize = 0,
+ always_newline_scope: bool = false,
+
+ pub fn goodTimeForANewLine(this: *@This()) bool {
+ if (this.estimated_line_length > 80) {
+ this.resetLine();
+ return true;
+ }
+ return false;
+ }
+
+ pub fn resetLine(this: *@This()) void {
+ this.estimated_line_length = this.indent * 2;
+ }
+
+ pub fn addForNewLine(this: *@This(), len: usize) void {
+ this.estimated_line_length +|= len;
+ }
+
+ pub const ZigFormatter = struct {
+ formatter: *JestPrettyFormat.Formatter,
+ global: *JSGlobalObject,
+ value: JSValue,
+
+ pub const WriteError = error{UhOh};
+ pub fn format(self: ZigFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ self.formatter.remaining_values = &[_]JSValue{self.value};
+ defer {
+ self.formatter.remaining_values = &[_]JSValue{};
+ }
+ self.formatter.globalThis = self.global;
+ self.formatter.format(
+ Tag.get(self.value, self.global),
+ @TypeOf(writer),
+ writer,
+ self.value,
+ self.formatter.globalThis,
+ false,
+ );
+ }
+ };
+
+ // For detecting circular references
+ pub const Visited = struct {
+ const ObjectPool = @import("../../pool.zig").ObjectPool;
+ pub const Map = std.AutoHashMap(JSValue.Type, void);
+ pub const Pool = ObjectPool(
+ Map,
+ struct {
+ pub fn init(allocator: std.mem.Allocator) anyerror!Map {
+ return Map.init(allocator);
+ }
+ }.init,
+ true,
+ 16,
+ );
+ };
+
+ pub const Tag = enum {
+ StringPossiblyFormatted,
+ String,
+ Undefined,
+ Double,
+ Integer,
+ Null,
+ Boolean,
+ Array,
+ Object,
+ Function,
+ Class,
+ Error,
+ TypedArray,
+ Map,
+ Set,
+ Symbol,
+ BigInt,
+
+ GlobalObject,
+ Private,
+ Promise,
+
+ JSON,
+ NativeCode,
+ ArrayBuffer,
+
+ JSX,
+ Event,
+
+ pub fn isPrimitive(this: Tag) bool {
+ return switch (this) {
+ .String,
+ .StringPossiblyFormatted,
+ .Undefined,
+ .Double,
+ .Integer,
+ .Null,
+ .Boolean,
+ .Symbol,
+ .BigInt,
+ => true,
+ else => false,
+ };
+ }
+
+ pub inline fn canHaveCircularReferences(tag: Tag) bool {
+ return tag == .Array or tag == .Object or tag == .Map or tag == .Set;
+ }
+
+ const Result = struct {
+ tag: Tag,
+ cell: JSValue.JSType = JSValue.JSType.Cell,
+ };
+
+ pub fn get(value: JSValue, globalThis: *JSGlobalObject) Result {
+ switch (@enumToInt(value)) {
+ 0, 0xa => return Result{
+ .tag = .Undefined,
+ },
+ 0x2 => return Result{
+ .tag = .Null,
+ },
+ else => {},
+ }
+
+ if (value.isInt32()) {
+ return .{
+ .tag = .Integer,
+ };
+ } else if (value.isNumber()) {
+ return .{
+ .tag = .Double,
+ };
+ } else if (value.isBoolean()) {
+ return .{
+ .tag = .Boolean,
+ };
+ }
+
+ if (!value.isCell())
+ return .{
+ .tag = .NativeCode,
+ };
+
+ const js_type = value.jsType();
+
+ if (js_type.isHidden()) return .{
+ .tag = .NativeCode,
+ .cell = js_type,
+ };
+
+ // Cell is the "unknown" type
+ // if we call JSObjectGetPrivate, it can segfault
+ if (js_type == .Cell) {
+ return .{
+ .tag = .NativeCode,
+ .cell = js_type,
+ };
+ }
+
+ if (js_type == .DOMWrapper) {
+ return .{
+ .tag = .Private,
+ .cell = js_type,
+ };
+ }
+
+ if (CAPI.JSObjectGetPrivate(value.asObjectRef()) != null)
+ return .{
+ .tag = .Private,
+ .cell = js_type,
+ };
+
+ // If we check an Object has a method table and it does not
+ // it will crash
+ const callable = js_type != .Object and value.isCallable(globalThis.vm());
+
+ if (value.isClass(globalThis) and !callable) {
+ return .{
+ .tag = .Object,
+ .cell = js_type,
+ };
+ }
+
+ if (callable and js_type == .JSFunction) {
+ return .{
+ .tag = .Function,
+ .cell = js_type,
+ };
+ } else if (callable and js_type == .InternalFunction) {
+ return .{
+ .tag = .Object,
+ .cell = js_type,
+ };
+ }
+
+ if (js_type == .PureForwardingProxy) {
+ return Tag.get(
+ JSC.JSValue.c(JSC.C.JSObjectGetProxyTarget(value.asObjectRef())),
+ globalThis,
+ );
+ }
+
+ // Is this a react element?
+ if (js_type.isObject()) {
+ if (value.get(globalThis, "$$typeof")) |typeof_symbol| {
+ var reactElement = ZigString.init("react.element");
+ var react_fragment = ZigString.init("react.fragment");
+
+ if (JSValue.isSameValue(typeof_symbol, JSValue.symbolFor(globalThis, &reactElement), globalThis) or JSValue.isSameValue(typeof_symbol, JSValue.symbolFor(globalThis, &react_fragment), globalThis)) {
+ return .{ .tag = .JSX, .cell = js_type };
+ }
+ }
+ }
+
+ return .{
+ .tag = switch (js_type) {
+ JSValue.JSType.ErrorInstance => .Error,
+ JSValue.JSType.NumberObject => .Double,
+ JSValue.JSType.DerivedArray, JSValue.JSType.Array => .Array,
+ JSValue.JSType.DerivedStringObject, JSValue.JSType.String, JSValue.JSType.StringObject => .String,
+ JSValue.JSType.RegExpObject => .String,
+ JSValue.JSType.Symbol => .Symbol,
+ JSValue.JSType.BooleanObject => .Boolean,
+ JSValue.JSType.JSFunction => .Function,
+ JSValue.JSType.JSWeakMap, JSValue.JSType.JSMap => .Map,
+ JSValue.JSType.JSWeakSet, JSValue.JSType.JSSet => .Set,
+ JSValue.JSType.JSDate => .JSON,
+ JSValue.JSType.JSPromise => .Promise,
+ JSValue.JSType.Object,
+ JSValue.JSType.FinalObject,
+ .ModuleNamespaceObject,
+ .GlobalObject,
+ => .Object,
+
+ .ArrayBuffer,
+ JSValue.JSType.Int8Array,
+ JSValue.JSType.Uint8Array,
+ JSValue.JSType.Uint8ClampedArray,
+ JSValue.JSType.Int16Array,
+ JSValue.JSType.Uint16Array,
+ JSValue.JSType.Int32Array,
+ JSValue.JSType.Uint32Array,
+ JSValue.JSType.Float32Array,
+ JSValue.JSType.Float64Array,
+ JSValue.JSType.BigInt64Array,
+ JSValue.JSType.BigUint64Array,
+ .DataView,
+ => .TypedArray,
+
+ .HeapBigInt => .BigInt,
+
+ // None of these should ever exist here
+ // But we're going to check anyway
+ .GetterSetter,
+ .CustomGetterSetter,
+ .APIValueWrapper,
+ .NativeExecutable,
+ .ProgramExecutable,
+ .ModuleProgramExecutable,
+ .EvalExecutable,
+ .FunctionExecutable,
+ .UnlinkedFunctionExecutable,
+ .UnlinkedProgramCodeBlock,
+ .UnlinkedModuleProgramCodeBlock,
+ .UnlinkedEvalCodeBlock,
+ .UnlinkedFunctionCodeBlock,
+ .CodeBlock,
+ .JSImmutableButterfly,
+ .JSSourceCode,
+ .JSScriptFetcher,
+ .JSScriptFetchParameters,
+ .JSCallee,
+ .GlobalLexicalEnvironment,
+ .LexicalEnvironment,
+ .ModuleEnvironment,
+ .StrictEvalActivation,
+ .WithScope,
+ => .NativeCode,
+
+ .Event => .Event,
+
+ else => .JSON,
+ },
+ .cell = js_type,
+ };
+ }
+ };
+
+ const CellType = CAPI.CellType;
+ threadlocal var name_buf: [512]u8 = undefined;
+
+ fn writeWithFormatting(
+ this: *JestPrettyFormat.Formatter,
+ comptime Writer: type,
+ writer_: Writer,
+ comptime Slice: type,
+ slice_: Slice,
+ globalThis: *JSGlobalObject,
+ comptime enable_ansi_colors: bool,
+ ) void {
+ var writer = WrappedWriter(Writer){ .ctx = writer_ };
+ var slice = slice_;
+ var i: u32 = 0;
+ var len: u32 = @truncate(u32, slice.len);
+ var any_non_ascii = false;
+ while (i < len) : (i += 1) {
+ switch (slice[i]) {
+ '%' => {
+ i += 1;
+ if (i >= len)
+ break;
+
+ const token = switch (slice[i]) {
+ 's' => Tag.String,
+ 'f' => Tag.Double,
+ 'o' => Tag.Undefined,
+ 'O' => Tag.Object,
+ 'd', 'i' => Tag.Integer,
+ else => continue,
+ };
+
+ // Flush everything up to the %
+ const end = slice[0 .. i - 1];
+ if (!any_non_ascii)
+ writer.writeAll(end)
+ else
+ writer.writeAll(end);
+ any_non_ascii = false;
+ slice = slice[@min(slice.len, i + 1)..];
+ i = 0;
+ len = @truncate(u32, slice.len);
+ const next_value = this.remaining_values[0];
+ this.remaining_values = this.remaining_values[1..];
+ switch (token) {
+ Tag.String => this.printAs(Tag.String, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Double => this.printAs(Tag.Double, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Object => this.printAs(Tag.Object, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+ Tag.Integer => this.printAs(Tag.Integer, Writer, writer_, next_value, next_value.jsType(), enable_ansi_colors),
+
+ // undefined is overloaded to mean the '%o" field
+ Tag.Undefined => this.format(Tag.get(next_value, globalThis), Writer, writer_, next_value, globalThis, enable_ansi_colors),
+
+ else => unreachable,
+ }
+ if (this.remaining_values.len == 0) break;
+ },
+ '\\' => {
+ i += 1;
+ if (i >= len)
+ break;
+ if (slice[i] == '%') i += 2;
+ },
+ 128...255 => {
+ any_non_ascii = true;
+ },
+ else => {},
+ }
+ }
+
+ if (slice.len > 0) writer.writeAll(slice);
+ }
+
+ pub fn WrappedWriter(comptime Writer: type) type {
+ return struct {
+ ctx: Writer,
+ failed: bool = false,
+ estimated_line_length: *usize = undefined,
+
+ pub fn print(self: *@This(), comptime fmt: string, args: anytype) void {
+ self.ctx.print(fmt, args) catch {
+ self.failed = true;
+ };
+ }
+
+ pub fn writeLatin1(self: *@This(), buf: []const u8) void {
+ var remain = buf;
+ while (remain.len > 0) {
+ if (strings.firstNonASCII(remain)) |i| {
+ if (i > 0) {
+ self.ctx.writeAll(remain[0..i]) catch {
+ self.failed = true;
+ return;
+ };
+ }
+ self.ctx.writeAll(&strings.latin1ToCodepointBytesAssumeNotASCII(remain[i])) catch {
+ self.failed = true;
+ };
+ remain = remain[i + 1 ..];
+ } else {
+ break;
+ }
+ }
+
+ self.ctx.writeAll(remain) catch return;
+ }
+
+ pub inline fn writeAll(self: *@This(), buf: []const u8) void {
+ self.ctx.writeAll(buf) catch {
+ self.failed = true;
+ };
+ }
+
+ pub inline fn writeString(self: *@This(), str: ZigString) void {
+ self.print("{}", .{str});
+ }
+
+ pub inline fn write16Bit(self: *@This(), input: []const u16) void {
+ strings.formatUTF16Type([]const u16, input, self.ctx) catch {
+ self.failed = true;
+ };
+ }
+ };
+ }
+
+ pub fn writeIndent(
+ this: *JestPrettyFormat.Formatter,
+ comptime Writer: type,
+ writer: Writer,
+ ) !void {
+ const indent = @min(this.indent, 32);
+ var buf = [_]u8{' '} ** 64;
+ var total_remain: usize = indent;
+ while (total_remain > 0) {
+ const written = @min(32, total_remain);
+ try writer.writeAll(buf[0 .. written * 2]);
+ total_remain -|= written;
+ }
+ }
+
+ pub fn printComma(this: *JestPrettyFormat.Formatter, comptime Writer: type, writer: Writer, comptime enable_ansi_colors: bool) !void {
+ try writer.writeAll(comptime Output.prettyFmt("<r><d>,<r>", enable_ansi_colors));
+ this.estimated_line_length += 1;
+ }
+
+ pub fn MapIterator(comptime Writer: type, comptime enable_ansi_colors: bool) type {
+ return struct {
+ formatter: *JestPrettyFormat.Formatter,
+ writer: Writer,
+ pub fn forEach(_: [*c]JSC.VM, globalObject: [*c]JSGlobalObject, ctx: ?*anyopaque, nextValue: JSValue) callconv(.C) void {
+ var this: *@This() = bun.cast(*@This(), ctx orelse return);
+ const key = JSC.JSObject.getIndex(nextValue, globalObject, 0);
+ const value = JSC.JSObject.getIndex(nextValue, globalObject, 1);
+ this.formatter.writeIndent(Writer, this.writer) catch unreachable;
+ const key_tag = Tag.get(key, globalObject);
+
+ this.formatter.format(
+ key_tag,
+ Writer,
+ this.writer,
+ key,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+ this.writer.writeAll(" => ") catch unreachable;
+ const value_tag = Tag.get(value, globalObject);
+ this.formatter.format(
+ value_tag,
+ Writer,
+ this.writer,
+ value,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+ this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
+ this.writer.writeAll("\n") catch unreachable;
+ }
+ };
+ }
+
+ pub fn SetIterator(comptime Writer: type, comptime enable_ansi_colors: bool) type {
+ return struct {
+ formatter: *JestPrettyFormat.Formatter,
+ writer: Writer,
+ pub fn forEach(_: [*c]JSC.VM, globalObject: [*c]JSGlobalObject, ctx: ?*anyopaque, nextValue: JSValue) callconv(.C) void {
+ var this: *@This() = bun.cast(*@This(), ctx orelse return);
+ this.formatter.writeIndent(Writer, this.writer) catch {};
+ const key_tag = Tag.get(nextValue, globalObject);
+ this.formatter.format(
+ key_tag,
+ Writer,
+ this.writer,
+ nextValue,
+ this.formatter.globalThis,
+ enable_ansi_colors,
+ );
+
+ this.formatter.printComma(Writer, this.writer, enable_ansi_colors) catch unreachable;
+ this.writer.writeAll("\n") catch unreachable;
+ }
+ };
+ }
+
+ pub fn PropertyIterator(comptime Writer: type, comptime enable_ansi_colors_: bool) type {
+ return struct {
+ formatter: *JestPrettyFormat.Formatter,
+ writer: Writer,
+ i: usize = 0,
+ always_newline: bool = false,
+ parent: JSValue,
+ const enable_ansi_colors = enable_ansi_colors_;
+ pub fn handleFirstProperty(this: *@This(), globalThis: *JSC.JSGlobalObject, value: JSValue) void {
+ if (!value.jsType().isFunction() and !value.isClass(globalThis)) {
+ var writer = WrappedWriter(Writer){
+ .ctx = this.writer,
+ .failed = false,
+ };
+ var name_str = ZigString.init("");
+
+ value.getNameProperty(globalThis, &name_str);
+ if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) {
+ writer.print("{} ", .{name_str});
+ } else {
+ value.getPrototype(globalThis).getNameProperty(globalThis, &name_str);
+ if (name_str.len > 0 and !strings.eqlComptime(name_str.slice(), "Object")) {
+ writer.print("{} ", .{name_str});
+ }
+ }
+ }
+
+ this.always_newline = true;
+ this.formatter.estimated_line_length = this.formatter.indent * 2 + 1;
+
+ if (this.formatter.indent == 0) this.writer.writeAll("\n") catch {};
+ var classname = ZigString.Empty;
+ value.getClassName(globalThis, &classname);
+ if (!strings.eqlComptime(classname.slice(), "Object")) {
+ this.writer.print("{} ", .{classname}) catch {};
+ }
+
+ this.writer.writeAll("{\n") catch {};
+ this.formatter.indent += 1;
+ this.formatter.writeIndent(Writer, this.writer) catch {};
+ }
+
+ pub fn forEach(
+ globalThis: *JSGlobalObject,
+ ctx_ptr: ?*anyopaque,
+ key_: [*c]ZigString,
+ value: JSValue,
+ is_symbol: bool,
+ ) callconv(.C) void {
+ const key = key_.?[0];
+ if (key.eqlComptime("constructor")) return;
+ if (key.eqlComptime("call")) return;
+
+ var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return);
+ var this = ctx.formatter;
+ var writer_ = ctx.writer;
+ var writer = WrappedWriter(Writer){
+ .ctx = writer_,
+ .failed = false,
+ };
+
+ const tag = Tag.get(value, globalThis);
+
+ if (tag.cell.isHidden()) return;
+ if (ctx.i == 0) {
+ handleFirstProperty(ctx, globalThis, ctx.parent);
+ } else {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+ }
+
+ defer ctx.i += 1;
+ if (ctx.i > 0) {
+ if (ctx.always_newline or this.always_newline_scope or this.goodTimeForANewLine()) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ this.resetLine();
+ } else {
+ this.estimated_line_length += 1;
+ writer.writeAll(" ");
+ }
+ }
+
+ if (!is_symbol) {
+
+ // TODO: make this one pass?
+ if (!key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice())) {
+ this.addForNewLine(key.len + 1);
+
+ writer.print(
+ comptime Output.prettyFmt("\"{}\"<d>:<r> ", enable_ansi_colors),
+ .{key},
+ );
+ } else if (key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned())) {
+ this.addForNewLine(key.len + 1);
+
+ writer.print(
+ comptime Output.prettyFmt("\"{}\"<d>:<r> ", enable_ansi_colors),
+ .{key},
+ );
+ } else if (key.is16Bit()) {
+ var utf16Slice = key.utf16SliceAligned();
+
+ this.addForNewLine(utf16Slice.len + 2);
+
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
+ }
+
+ writer.writeAll("'");
+
+ while (strings.indexOfAny16(utf16Slice, "\"")) |j| {
+ writer.write16Bit(utf16Slice[0..j]);
+ writer.writeAll("\"");
+ utf16Slice = utf16Slice[j + 1 ..];
+ }
+
+ writer.write16Bit(utf16Slice);
+
+ writer.print(
+ comptime Output.prettyFmt("\"<r><d>:<r> ", enable_ansi_colors),
+ .{},
+ );
+ } else {
+ this.addForNewLine(key.len + 1);
+
+ writer.print(
+ comptime Output.prettyFmt("{s}<d>:<r> ", enable_ansi_colors),
+ .{JSPrinter.formatJSONString(key.slice())},
+ );
+ }
+ } else {
+ this.addForNewLine(1 + "[Symbol()]:".len + key.len);
+ writer.print(
+ comptime Output.prettyFmt("<r><d>[<r><blue>Symbol({any})<r><d>]:<r> ", enable_ansi_colors),
+ .{
+ key,
+ },
+ );
+ }
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
+ }
+ }
+
+ this.format(tag, Writer, ctx.writer, value, globalThis, enable_ansi_colors);
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r>", true));
+ }
+ }
+ }
+ };
+ }
+
+ pub fn printAs(
+ this: *JestPrettyFormat.Formatter,
+ comptime Format: JestPrettyFormat.Formatter.Tag,
+ comptime Writer: type,
+ writer_: Writer,
+ value: JSValue,
+ jsType: JSValue.JSType,
+ comptime enable_ansi_colors: bool,
+ ) void {
+ if (this.failed)
+ return;
+ var writer = WrappedWriter(Writer){ .ctx = writer_, .estimated_line_length = &this.estimated_line_length };
+ defer {
+ if (writer.failed) {
+ this.failed = true;
+ }
+ }
+ if (comptime Format.canHaveCircularReferences()) {
+ if (this.map_node == null) {
+ this.map_node = Visited.Pool.get(default_allocator);
+ this.map_node.?.data.clearRetainingCapacity();
+ this.map = this.map_node.?.data;
+ }
+
+ var entry = this.map.getOrPut(@enumToInt(value)) catch unreachable;
+ if (entry.found_existing) {
+ writer.writeAll(comptime Output.prettyFmt("<r><cyan>[Circular]<r>", enable_ansi_colors));
+ return;
+ }
+ }
+
+ defer {
+ if (comptime Format.canHaveCircularReferences()) {
+ _ = this.map.remove(@enumToInt(value));
+ }
+ }
+
+ switch (comptime Format) {
+ .StringPossiblyFormatted => {
+ var str = value.toSlice(this.globalThis, bun.default_allocator);
+ defer str.deinit();
+ this.addForNewLine(str.len);
+ const slice = str.slice();
+ this.writeWithFormatting(Writer, writer_, @TypeOf(slice), slice, this.globalThis, enable_ansi_colors);
+ },
+ .String => {
+ var str = ZigString.init("");
+ value.toZigString(&str, this.globalThis);
+ this.addForNewLine(str.len);
+
+ if (value.jsType() == .StringObject or value.jsType() == .DerivedStringObject) {
+ if (str.len == 0) {
+ writer.writeAll("String {}");
+ return;
+ }
+ if (this.indent == 0 and str.len > 0) {
+ writer.writeAll("\n");
+ }
+ writer.writeAll("String {\n");
+ this.indent += 1;
+ defer this.indent -|= 1;
+ this.resetLine();
+ this.writeIndent(Writer, writer_) catch unreachable;
+ const length = str.len;
+ for (str.slice(), 0..) |c, i| {
+ writer.print("\"{d}\": \"{c}\",\n", .{ i, c });
+ if (i != length - 1) this.writeIndent(Writer, writer_) catch unreachable;
+ }
+ this.resetLine();
+ writer.writeAll("}\n");
+ return;
+ }
+
+ if (this.quote_strings and jsType != .RegExpObject) {
+ if (str.len == 0) {
+ writer.writeAll("\"\"");
+ return;
+ }
+
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(Output.prettyFmt("<r><green>", true));
+ }
+
+ defer if (comptime enable_ansi_colors)
+ writer.writeAll(Output.prettyFmt("<r>", true));
+
+ if (str.is16Bit()) {
+ this.printAs(.JSON, Writer, writer_, value, .StringObject, enable_ansi_colors);
+ return;
+ }
+
+ var has_newline = false;
+ if (strings.indexOfAny(str.slice(), "\n\r")) |_| {
+ has_newline = true;
+ writer.writeAll("\n");
+ }
+
+ writer.writeAll("\"");
+ var remaining = str.slice();
+ while (strings.indexOfAny(remaining, "\\\r")) |i| {
+ switch (remaining[i]) {
+ '\\' => {
+ writer.print("{s}\\", .{remaining[0 .. i + 1]});
+ remaining = remaining[i + 1 ..];
+ },
+ '\r' => {
+ if (i + 1 < remaining.len and remaining[i + 1] == '\n') {
+ writer.print("{s}", .{remaining[0..i]});
+ } else {
+ writer.print("{s}\n", .{remaining[0..i]});
+ }
+ remaining = remaining[i + 1 ..];
+ },
+ else => unreachable,
+ }
+ }
+
+ writer.writeAll(remaining);
+ writer.writeAll("\"");
+ if (has_newline) writer.writeAll("\n");
+ return;
+ }
+
+ if (jsType == .RegExpObject and enable_ansi_colors) {
+ writer.print(comptime Output.prettyFmt("<r><red>", enable_ansi_colors), .{});
+ }
+
+ if (str.is16Bit()) {
+ // streaming print
+ writer.print("{s}", .{str});
+ } else if (strings.isAllASCII(str.slice())) {
+ // fast path
+ writer.writeAll(str.slice());
+ } else if (str.len > 0) {
+ // slow path
+ var buf = strings.allocateLatin1IntoUTF8(bun.default_allocator, []const u8, str.slice()) catch &[_]u8{};
+ if (buf.len > 0) {
+ defer bun.default_allocator.free(buf);
+ writer.writeAll(buf);
+ }
+ }
+
+ if (jsType == .RegExpObject and enable_ansi_colors) {
+ writer.print(comptime Output.prettyFmt("<r>", enable_ansi_colors), .{});
+ }
+ },
+ .Integer => {
+ const int = value.toInt64();
+ if (int < std.math.maxInt(u32)) {
+ var i = int;
+ const is_negative = i < 0;
+ if (is_negative) {
+ i = -i;
+ }
+ const digits = if (i != 0)
+ bun.fmt.fastDigitCount(@intCast(usize, i)) + @as(usize, @boolToInt(is_negative))
+ else
+ 1;
+ this.addForNewLine(digits);
+ } else {
+ this.addForNewLine(bun.fmt.count("{d}", .{int}));
+ }
+ writer.print(comptime Output.prettyFmt("<r><yellow>{d}<r>", enable_ansi_colors), .{int});
+ },
+ .BigInt => {
+ var out_str = value.getZigString(this.globalThis).slice();
+ this.addForNewLine(out_str.len);
+
+ writer.print(comptime Output.prettyFmt("<r><yellow>{s}n<r>", enable_ansi_colors), .{out_str});
+ },
+ .Double => {
+ if (value.isCell()) {
+ this.printAs(.Object, Writer, writer_, value, .Object, enable_ansi_colors);
+ return;
+ }
+
+ const num = value.asNumber();
+
+ if (std.math.isPositiveInf(num)) {
+ this.addForNewLine("Infinity".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>Infinity<r>", enable_ansi_colors), .{});
+ } else if (std.math.isNegativeInf(num)) {
+ this.addForNewLine("-Infinity".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>-Infinity<r>", enable_ansi_colors), .{});
+ } else if (std.math.isNan(num)) {
+ this.addForNewLine("NaN".len);
+ writer.print(comptime Output.prettyFmt("<r><yellow>NaN<r>", enable_ansi_colors), .{});
+ } else {
+ this.addForNewLine(std.fmt.count("{d}", .{num}));
+ writer.print(comptime Output.prettyFmt("<r><yellow>{d}<r>", enable_ansi_colors), .{num});
+ }
+ },
+ .Undefined => {
+ this.addForNewLine(9);
+ writer.print(comptime Output.prettyFmt("<r><d>undefined<r>", enable_ansi_colors), .{});
+ },
+ .Null => {
+ this.addForNewLine(4);
+ writer.print(comptime Output.prettyFmt("<r><yellow>null<r>", enable_ansi_colors), .{});
+ },
+ .Symbol => {
+ const description = value.getDescription(this.globalThis);
+ this.addForNewLine("Symbol".len);
+
+ if (description.len > 0) {
+ this.addForNewLine(description.len + "()".len);
+ writer.print(comptime Output.prettyFmt("<r><blue>Symbol({any})<r>", enable_ansi_colors), .{description});
+ } else {
+ writer.print(comptime Output.prettyFmt("<r><blue>Symbol<r>", enable_ansi_colors), .{});
+ }
+ },
+ .Error => {
+ var classname = ZigString.Empty;
+ value.getClassName(this.globalThis, &classname);
+ var message_string = ZigString.Empty;
+ if (value.get(this.globalThis, "message")) |message_prop| {
+ message_prop.toZigString(&message_string, this.globalThis);
+ }
+ if (message_string.len == 0) {
+ writer.print("[{s}]", .{classname});
+ return;
+ }
+ writer.print("[{s}: {s}]", .{ classname, message_string });
+ return;
+ },
+ .Class => {
+ var printable = ZigString.init(&name_buf);
+ value.getClassName(this.globalThis, &printable);
+ this.addForNewLine(printable.len);
+
+ if (printable.len == 0) {
+ writer.print(comptime Output.prettyFmt("[class]", enable_ansi_colors), .{});
+ } else {
+ writer.print(comptime Output.prettyFmt("[class <cyan>{}<r>]", enable_ansi_colors), .{printable});
+ }
+ },
+ .Function => {
+ writer.writeAll("[Function]");
+ },
+ .Array => {
+ const len = @truncate(u32, value.getLengthOfArray(this.globalThis));
+ if (len == 0) {
+ writer.writeAll("[]");
+ this.addForNewLine(2);
+ return;
+ }
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+
+ var was_good_time = this.always_newline_scope;
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+
+ this.addForNewLine(2);
+
+ var ref = value.asObjectRef();
+
+ var prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ {
+ const element = JSValue.fromRef(CAPI.JSObjectGetPropertyAtIndex(this.globalThis, ref, 0, null));
+ const tag = Tag.get(element, this.globalThis);
+
+ was_good_time = was_good_time or !tag.tag.isPrimitive() or this.goodTimeForANewLine();
+
+ this.resetLine();
+ writer.writeAll("[");
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ this.addForNewLine(1);
+
+ this.format(tag, Writer, writer_, element, this.globalThis, enable_ansi_colors);
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r>", true));
+ }
+ }
+
+ if (len == 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+ }
+ }
+
+ var i: u32 = 1;
+ while (i < len) : (i += 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+
+ const element = JSValue.fromRef(CAPI.JSObjectGetPropertyAtIndex(this.globalThis, ref, i, null));
+ const tag = Tag.get(element, this.globalThis);
+
+ this.format(tag, Writer, writer_, element, this.globalThis, enable_ansi_colors);
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r>", true));
+ }
+ }
+
+ if (i == len - 1) {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+ }
+ }
+ }
+
+ this.resetLine();
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("]");
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ this.resetLine();
+ this.addForNewLine(1);
+ },
+ .Private => {
+ if (value.as(JSC.WebCore.Response)) |response| {
+ response.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.WebCore.Request)) |request| {
+ request.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.WebCore.Blob)) |blob| {
+ blob.writeFormat(Formatter, this, writer_, enable_ansi_colors) catch {};
+ return;
+ } else if (value.as(JSC.DOMFormData) != null) {
+ const toJSONFunction = value.get(this.globalThis, "toJSON").?;
+
+ this.addForNewLine("FormData (entries) ".len);
+ writer.writeAll(comptime Output.prettyFmt("<r><blue>FormData<r> <d>(entries)<r> ", enable_ansi_colors));
+
+ return this.printAs(
+ .Object,
+ Writer,
+ writer_,
+ toJSONFunction.callWithThis(this.globalThis, value, &.{}),
+ .Object,
+ enable_ansi_colors,
+ );
+ } else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| {
+ this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0))));
+ if (timer.kind == .setInterval) {
+ this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0))));
+ writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>, repeats)<r>", enable_ansi_colors), .{
+ timer.id,
+ });
+ } else {
+ writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{
+ timer.id,
+ });
+ }
+
+ return;
+ } else if (jsType != .DOMWrapper) {
+ if (CAPI.JSObjectGetPrivate(value.asRef())) |private_data_ptr| {
+ const priv_data = JSPrivateDataPtr.from(private_data_ptr);
+ switch (priv_data.tag()) {
+ .BuildError => {
+ const build_error = priv_data.as(JS.BuildError);
+ build_error.msg.writeFormat(writer_, enable_ansi_colors) catch {};
+ return;
+ },
+ .ResolveError => {
+ const resolve_error = priv_data.as(JS.ResolveError);
+ resolve_error.msg.writeFormat(writer_, enable_ansi_colors) catch {};
+ return;
+ },
+ else => {},
+ }
+ }
+
+ if (value.isCallable(this.globalThis.vm())) {
+ return this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors);
+ }
+
+ return this.printAs(.Object, Writer, writer_, value, jsType, enable_ansi_colors);
+ }
+ return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
+ },
+ .NativeCode => {
+ this.addForNewLine("[native code]".len);
+ writer.writeAll("[native code]");
+ },
+ .Promise => {
+ if (this.goodTimeForANewLine()) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ }
+ writer.writeAll("Promise {}");
+ },
+ .Boolean => {
+ if (value.isCell()) {
+ this.printAs(.Object, Writer, writer_, value, .Object, enable_ansi_colors);
+ return;
+ }
+ if (value.toBoolean()) {
+ this.addForNewLine(4);
+ writer.writeAll(comptime Output.prettyFmt("<r><yellow>true<r>", enable_ansi_colors));
+ } else {
+ this.addForNewLine(5);
+ writer.writeAll(comptime Output.prettyFmt("<r><yellow>false<r>", enable_ansi_colors));
+ }
+ },
+ .GlobalObject => {
+ const fmt = "[this.globalThis]";
+ this.addForNewLine(fmt.len);
+ writer.writeAll(comptime Output.prettyFmt("<cyan>" ++ fmt ++ "<r>", enable_ansi_colors));
+ },
+ .Map => {
+ const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
+ const length = length_value.toInt32();
+
+ const prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ const map_name = if (value.jsType() == .JSWeakMap) "WeakMap" else "Map";
+
+ if (length == 0) {
+ return writer.print("{s} {{}}", .{map_name});
+ }
+
+ writer.print("\n{s} {{\n", .{map_name});
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ var iter = MapIterator(Writer, enable_ansi_colors){
+ .formatter = this,
+ .writer = writer_,
+ };
+ value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
+ }
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ writer.writeAll("\n");
+ },
+ .Set => {
+ const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
+ const length = length_value.toInt32();
+
+ const prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ this.writeIndent(Writer, writer_) catch {};
+
+ const set_name = if (value.jsType() == .JSWeakSet) "WeakSet" else "Set";
+
+ if (length == 0) {
+ return writer.print("{s} {{}}", .{set_name});
+ }
+
+ writer.print("\n{s} {{\n", .{set_name});
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ var iter = SetIterator(Writer, enable_ansi_colors){
+ .formatter = this,
+ .writer = writer_,
+ };
+ value.forEach(this.globalThis, &iter, @TypeOf(iter).forEach);
+ }
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ writer.writeAll("\n");
+ },
+ .JSON => {
+ var str = ZigString.init("");
+ value.jsonStringify(this.globalThis, this.indent, &str);
+ this.addForNewLine(str.len);
+ if (jsType == JSValue.JSType.JSDate) {
+ // in the code for printing dates, it never exceeds this amount
+ var iso_string_buf: [36]u8 = undefined;
+ var out_buf: []const u8 = std.fmt.bufPrint(&iso_string_buf, "{}", .{str}) catch "";
+ if (out_buf.len > 2) {
+ // trim the quotes
+ out_buf = out_buf[1 .. out_buf.len - 1];
+ }
+
+ writer.print(comptime Output.prettyFmt("<r><magenta>{s}<r>", enable_ansi_colors), .{out_buf});
+ return;
+ }
+
+ writer.print("{}", .{str});
+ },
+ .Event => {
+ const event_type = EventType.map.getWithEql(value.get(this.globalThis, "type").?.getZigString(this.globalThis), ZigString.eqlComptime) orelse EventType.unknown;
+ if (event_type != .MessageEvent and event_type != .ErrorEvent) {
+ return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
+ }
+
+ writer.print(
+ comptime Output.prettyFmt("<r><cyan>{s}<r> {{\n", enable_ansi_colors),
+ .{
+ @tagName(event_type),
+ },
+ );
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ const old_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = old_quote_strings;
+ this.writeIndent(Writer, writer_) catch unreachable;
+
+ writer.print(
+ comptime Output.prettyFmt("<r>type: <green>\"{s}\"<r><d>,<r>\n", enable_ansi_colors),
+ .{
+ event_type.label(),
+ },
+ );
+ this.writeIndent(Writer, writer_) catch unreachable;
+
+ switch (event_type) {
+ .MessageEvent => {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>data<d>:<r> ", enable_ansi_colors),
+ .{},
+ );
+ const data = value.get(this.globalThis, "data").?;
+ const tag = Tag.get(data, this.globalThis);
+ if (tag.cell.isStringLike()) {
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ } else {
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ }
+ },
+ .ErrorEvent => {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>error<d>:<r>\n", enable_ansi_colors),
+ .{},
+ );
+
+ const data = value.get(this.globalThis, "error").?;
+ const tag = Tag.get(data, this.globalThis);
+ this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ },
+ else => unreachable,
+ }
+ writer.writeAll("\n");
+ }
+
+ this.writeIndent(Writer, writer_) catch unreachable;
+ writer.writeAll("}");
+ },
+ .JSX => {
+ writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
+
+ writer.writeAll("<");
+
+ var needs_space = false;
+ var tag_name_str = ZigString.init("");
+
+ var tag_name_slice: ZigString.Slice = ZigString.Slice.empty;
+ var is_tag_kind_primitive = false;
+
+ defer if (tag_name_slice.isAllocated()) tag_name_slice.deinit();
+
+ if (value.get(this.globalThis, "type")) |type_value| {
+ const _tag = Tag.get(type_value, this.globalThis);
+
+ if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) {
+ type_value.toZigString(&tag_name_str, this.globalThis);
+ is_tag_kind_primitive = true;
+ } else if (_tag.cell.isObject() or type_value.isCallable(this.globalThis.vm())) {
+ type_value.getNameProperty(this.globalThis, &tag_name_str);
+ if (tag_name_str.len == 0) {
+ tag_name_str = ZigString.init("NoName");
+ }
+ } else {
+ type_value.toZigString(&tag_name_str, this.globalThis);
+ }
+
+ tag_name_slice = tag_name_str.toSlice(default_allocator);
+ needs_space = true;
+ } else {
+ tag_name_slice = ZigString.init("unknown").toSlice(default_allocator);
+
+ needs_space = true;
+ }
+
+ if (!is_tag_kind_primitive)
+ writer.writeAll(comptime Output.prettyFmt("<cyan>", enable_ansi_colors))
+ else
+ writer.writeAll(comptime Output.prettyFmt("<green>", enable_ansi_colors));
+ writer.writeAll(tag_name_slice.slice());
+ if (enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
+
+ if (value.get(this.globalThis, "key")) |key_value| {
+ if (!key_value.isUndefinedOrNull()) {
+ if (needs_space)
+ writer.writeAll(" key=")
+ else
+ writer.writeAll("key=");
+
+ const old_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = old_quote_strings;
+
+ this.format(Tag.get(key_value, this.globalThis), Writer, writer_, key_value, this.globalThis, enable_ansi_colors);
+
+ needs_space = true;
+ }
+ }
+
+ if (value.get(this.globalThis, "props")) |props| {
+ const prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ var props_iter = JSC.JSPropertyIterator(.{
+ .skip_empty_name = true,
+
+ .include_value = true,
+ }).init(this.globalThis, props.asObjectRef());
+ defer props_iter.deinit();
+
+ var children_prop = props.get(this.globalThis, "children");
+ if (props_iter.len > 0) {
+ {
+ this.indent += 1;
+ defer this.indent -|= 1;
+ const count_without_children = props_iter.len - @as(usize, @boolToInt(children_prop != null));
+
+ while (props_iter.next()) |prop| {
+ if (prop.eqlComptime("children"))
+ continue;
+
+ var property_value = props_iter.value;
+ const tag = Tag.get(property_value, this.globalThis);
+
+ if (tag.cell.isHidden()) continue;
+
+ if (needs_space) writer.writeAll(" ");
+ needs_space = false;
+
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>{s}<d>=<r>", enable_ansi_colors),
+ .{prop.trunc(128)},
+ );
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r><green>", true));
+ }
+ }
+
+ this.format(tag, Writer, writer_, property_value, this.globalThis, enable_ansi_colors);
+
+ if (tag.cell.isStringLike()) {
+ if (comptime enable_ansi_colors) {
+ writer.writeAll(comptime Output.prettyFmt("<r>", true));
+ }
+ }
+
+ if (
+ // count_without_children is necessary to prevent printing an extra newline
+ // if there are children and one prop and the child prop is the last prop
+ props_iter.i + 1 < count_without_children and
+ // 3 is arbitrary but basically
+ // <input type="text" value="foo" />
+ // ^ should be one line
+ // <input type="text" value="foo" bar="true" baz={false} />
+ // ^ should be multiple lines
+ props_iter.i > 3)
+ {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ } else if (props_iter.i + 1 < count_without_children) {
+ writer.writeAll(" ");
+ }
+ }
+ }
+
+ if (children_prop) |children| {
+ const tag = Tag.get(children, this.globalThis);
+
+ const print_children = switch (tag.tag) {
+ .String, .JSX, .Array => true,
+ else => false,
+ };
+
+ if (print_children) {
+ print_children: {
+ switch (tag.tag) {
+ .String => {
+ var children_string = children.getZigString(this.globalThis);
+ if (children_string.len == 0) break :print_children;
+ if (comptime enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", true));
+
+ writer.writeAll(">");
+ if (children_string.len < 128) {
+ writer.writeString(children_string);
+ } else {
+ this.indent += 1;
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ this.indent -|= 1;
+ writer.writeString(children_string);
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ }
+ },
+ .JSX => {
+ writer.writeAll(">\n");
+
+ {
+ this.indent += 1;
+ this.writeIndent(Writer, writer_) catch unreachable;
+ defer this.indent -|= 1;
+ this.format(Tag.get(children, this.globalThis), Writer, writer_, children, this.globalThis, enable_ansi_colors);
+ }
+
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ },
+ .Array => {
+ const length = children.getLengthOfArray(this.globalThis);
+ if (length == 0) break :print_children;
+ writer.writeAll(">\n");
+
+ {
+ this.indent += 1;
+ this.writeIndent(Writer, writer_) catch unreachable;
+ const _prev_quote_strings = this.quote_strings;
+ this.quote_strings = false;
+ defer this.quote_strings = _prev_quote_strings;
+
+ defer this.indent -|= 1;
+
+ var j: usize = 0;
+ while (j < length) : (j += 1) {
+ const child = JSC.JSObject.getIndex(children, this.globalThis, @intCast(u32, j));
+ this.format(Tag.get(child, this.globalThis), Writer, writer_, child, this.globalThis, enable_ansi_colors);
+ if (j + 1 < length) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ }
+ }
+ }
+
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch unreachable;
+ },
+ else => unreachable,
+ }
+
+ writer.writeAll("</");
+ if (!is_tag_kind_primitive)
+ writer.writeAll(comptime Output.prettyFmt("<r><cyan>", enable_ansi_colors))
+ else
+ writer.writeAll(comptime Output.prettyFmt("<r><green>", enable_ansi_colors));
+ writer.writeAll(tag_name_slice.slice());
+ if (enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
+ writer.writeAll(">");
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
+ writer.writeAll(" />");
+ },
+ .Object => {
+ const prev_quote_strings = this.quote_strings;
+ this.quote_strings = true;
+ defer this.quote_strings = prev_quote_strings;
+
+ const Iterator = PropertyIterator(Writer, enable_ansi_colors);
+
+ // We want to figure out if we should print this object
+ // on one line or multiple lines
+ //
+ // The 100% correct way would be to print everything to
+ // a temporary buffer and then check how long each line was
+ //
+ // But it's important that console.log() is fast. So we
+ // do a small compromise to avoid multiple passes over input
+ //
+ // We say:
+ //
+ // If the object has at least 2 properties and ANY of the following conditions are met:
+ // - total length of all the property names is more than
+ // 14 characters
+ // - the parent object is printing each property on a new line
+ // - The first property is a DOM object, ESM namespace, Map, Set, or Blob
+ //
+ // Then, we print it each property on a new line, recursively.
+ //
+ const prev_always_newline_scope = this.always_newline_scope;
+ defer this.always_newline_scope = prev_always_newline_scope;
+ var iter = Iterator{
+ .formatter = this,
+ .writer = writer_,
+ .always_newline = this.always_newline_scope or this.goodTimeForANewLine(),
+ .parent = value,
+ };
+
+ value.forEachPropertyOrdered(this.globalThis, &iter, Iterator.forEach);
+
+ if (iter.i == 0) {
+ var object_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &object_name);
+
+ if (!strings.eqlComptime(object_name.slice(), "Object")) {
+ if (value.as(JSC.Jest.ExpectAny)) |_| {
+ var constructor = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse unreachable;
+ var constructor_name = ZigString.Empty;
+ constructor.getNameProperty(this.globalThis, &constructor_name);
+ writer.print("Any<{s}>", .{constructor_name});
+ } else {
+ writer.print("{s} {{}}", .{object_name});
+ }
+ } else {
+ // don't write "Object"
+ writer.writeAll("{}");
+ }
+ } else {
+ this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
+
+ if (iter.always_newline) {
+ this.indent -|= 1;
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+ this.estimated_line_length += 1;
+ } else {
+ this.estimated_line_length += 2;
+ writer.writeAll(" }");
+ }
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ }
+ },
+ .TypedArray => {
+ const arrayBuffer = value.asArrayBuffer(this.globalThis).?;
+ const slice = arrayBuffer.byteSlice();
+
+ if (this.indent == 0 and slice.len > 0) {
+ writer.writeAll("\n");
+ }
+
+ if (jsType == .Uint8Array) {
+ var buffer_name = ZigString.Empty;
+ value.getClassName(this.globalThis, &buffer_name);
+ if (strings.eqlComptime(buffer_name.slice(), "Buffer")) {
+ // special formatting for 'Buffer' snapshots only
+ if (slice.len == 0 and this.indent == 0) writer.writeAll("\n");
+ writer.writeAll("{\n");
+ this.indent += 1;
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("\"data\": [");
+
+ this.indent += 1;
+ for (slice) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ this.indent -|= 1;
+
+ if (slice.len > 0) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("],\n");
+ } else {
+ writer.writeAll("],\n");
+ }
+
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("\"type\": \"Buffer\",\n");
+
+ this.indent -|= 1;
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("}");
+
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+
+ return;
+ }
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ } else {
+ writer.writeAll(bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)));
+ }
+
+ writer.writeAll(" [");
+
+ if (slice.len > 0) {
+ switch (jsType) {
+ .Int8Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i8), std.mem.bytesAsSlice(i8, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Int16Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i16), std.mem.bytesAsSlice(i16, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Uint16Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u16), std.mem.bytesAsSlice(u16, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Int32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i32), std.mem.bytesAsSlice(i32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Uint32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u32), std.mem.bytesAsSlice(u32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Float32Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]f32), std.mem.bytesAsSlice(f32, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .Float64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]f64), std.mem.bytesAsSlice(f64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .BigInt64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]i64), std.mem.bytesAsSlice(i64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ .BigUint64Array => {
+ const slice_with_type = @alignCast(std.meta.alignment([]u64), std.mem.bytesAsSlice(u64, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+
+ // Uint8Array, Uint8ClampedArray, DataView, ArrayBuffer
+ else => {
+ var slice_with_type = @alignCast(std.meta.alignment([]u8), std.mem.bytesAsSlice(u8, slice));
+ this.indent += 1;
+ defer this.indent -|= 1;
+ for (slice_with_type) |el| {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.print("{d},", .{el});
+ }
+ },
+ }
+ }
+
+ if (slice.len > 0) {
+ writer.writeAll("\n");
+ this.writeIndent(Writer, writer_) catch {};
+ writer.writeAll("]");
+ if (this.indent == 0) {
+ writer.writeAll("\n");
+ }
+ } else {
+ writer.writeAll("]");
+ }
+ },
+ else => {},
+ }
+ }
+
+ pub fn format(this: *JestPrettyFormat.Formatter, result: Tag.Result, comptime Writer: type, writer: Writer, value: JSValue, globalThis: *JSGlobalObject, comptime enable_ansi_colors: bool) void {
+ if (comptime is_bindgen) {
+ return;
+ }
+ var prevGlobalThis = this.globalThis;
+ defer this.globalThis = prevGlobalThis;
+ this.globalThis = globalThis;
+
+ // This looks incredibly redundant. We make the JestPrettyFormat.Formatter.Tag a
+ // comptime var so we have to repeat it here. The rationale there is
+ // it _should_ limit the stack usage because each version of the
+ // function will be relatively small
+ return switch (result.tag) {
+ .StringPossiblyFormatted => this.printAs(.StringPossiblyFormatted, Writer, writer, value, result.cell, enable_ansi_colors),
+ .String => this.printAs(.String, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Undefined => this.printAs(.Undefined, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Double => this.printAs(.Double, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Integer => this.printAs(.Integer, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Null => this.printAs(.Null, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Boolean => this.printAs(.Boolean, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Array => this.printAs(.Array, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Object => this.printAs(.Object, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Function => this.printAs(.Function, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Class => this.printAs(.Class, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Error => this.printAs(.Error, Writer, writer, value, result.cell, enable_ansi_colors),
+ .ArrayBuffer, .TypedArray => this.printAs(.TypedArray, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Map => this.printAs(.Map, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Set => this.printAs(.Set, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Symbol => this.printAs(.Symbol, Writer, writer, value, result.cell, enable_ansi_colors),
+ .BigInt => this.printAs(.BigInt, Writer, writer, value, result.cell, enable_ansi_colors),
+ .GlobalObject => this.printAs(.GlobalObject, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Private => this.printAs(.Private, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Promise => this.printAs(.Promise, Writer, writer, value, result.cell, enable_ansi_colors),
+ .JSON => this.printAs(.JSON, Writer, writer, value, result.cell, enable_ansi_colors),
+ .NativeCode => this.printAs(.NativeCode, Writer, writer, value, result.cell, enable_ansi_colors),
+ .JSX => this.printAs(.JSX, Writer, writer, value, result.cell, enable_ansi_colors),
+ .Event => this.printAs(.Event, Writer, writer, value, result.cell, enable_ansi_colors),
+ };
+ }
+ };
+};
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 5a41f5f8c..b670a3aaa 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -307,7 +307,7 @@ pub const Blob = struct {
},
);
}
- pub fn writeFormat(this: *const Blob, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Blob, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
if (this.isDetached()) {
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index d4bd490c1..509d02989 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -75,7 +75,7 @@ pub const Body = struct {
};
}
- pub fn writeFormat(this: *const Body, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Body, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try formatter.writeIndent(Writer, writer);
@@ -98,7 +98,7 @@ pub const Body = struct {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try this.value.Blob.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.value.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else if (this.value == .InternalBlob) {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index e1fa4a9bb..6581649fc 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -116,7 +116,7 @@ pub const Request = struct {
};
}
- pub fn writeFormat(this: *Request, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *Request, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try writer.print("Request ({}) {{\n", .{bun.fmt.size(this.body.slice().len)});
{
@@ -139,12 +139,12 @@ pub const Request = struct {
if (this.body == .Blob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try this.body.Blob.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.body.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else if (this.body == .InternalBlob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
if (this.body.size() == 0) {
- try Blob.initEmpty(undefined).writeFormat(formatter, writer, enable_ansi_colors);
+ try Blob.initEmpty(undefined).writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else {
try Blob.writeFormatForSize(this.body.size(), writer, enable_ansi_colors);
}
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 06323d790..f36af88cf 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -113,7 +113,7 @@ pub const Response = struct {
pub const Props = struct {};
- pub fn writeFormat(this: *const Response, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
+ pub fn writeFormat(this: *const Response, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())});
{
@@ -145,7 +145,7 @@ pub const Response = struct {
formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
try writer.writeAll("\n");
formatter.resetLine();
- try this.body.writeFormat(formatter, writer, enable_ansi_colors);
+ try this.body.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
}
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
diff --git a/src/cli.zig b/src/cli.zig
index e05bd113d..9b04a3588 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -214,8 +214,14 @@ pub const Arguments = struct {
clap.parseParam("--outdir <STR> Default to \"dist\" if multiple files") catch unreachable,
};
+ // TODO: update test completions
+ const test_only_params = [_]ParamType{
+ clap.parseParam("--update-snapshots Update snapshot files") catch unreachable,
+ };
+
const build_params_public = public_params ++ build_only_params;
pub const build_params = build_params_public ++ debug_params;
+ pub const test_params = params ++ test_only_params;
fn printVersionAndExit() noreturn {
@setCold(true);
@@ -368,6 +374,10 @@ pub const Arguments = struct {
cwd = try std.process.getCwdAlloc(allocator);
}
+ if (cmd == .TestCommand) {
+ ctx.test_options.update_snapshots = args.flag("--update-snapshots");
+ }
+
ctx.args.absolute_working_dir = cwd;
ctx.positionals = args.positionals();
@@ -859,6 +869,10 @@ pub const Command = struct {
test_directory: []const u8 = "",
};
+ pub const TestOptions = struct {
+ update_snapshots: bool = false,
+ };
+
pub const Context = struct {
start_time: i128,
args: Api.TransformOptions,
@@ -869,6 +883,7 @@ pub const Command = struct {
install: ?*Api.BunInstall = null,
debug: DebugOptions = DebugOptions{},
+ test_options: TestOptions = TestOptions{},
preloads: []const string = &[_]string{},
has_loaded_global_config: bool = false,
@@ -1418,6 +1433,7 @@ pub const Command = struct {
pub fn params(comptime cmd: Tag) []const Arguments.ParamType {
return &comptime switch (cmd) {
Command.Tag.BuildCommand => Arguments.build_params,
+ Command.Tag.TestCommand => Arguments.test_params,
else => Arguments.params,
};
}
diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig
index 445398fea..e3a7f797e 100644
--- a/src/cli/test_command.zig
+++ b/src/cli/test_command.zig
@@ -41,6 +41,7 @@ const HTTPThread = @import("bun").HTTP.HTTPThread;
const JSC = @import("bun").JSC;
const jest = JSC.Jest;
const TestRunner = JSC.Jest.TestRunner;
+const Snapshots = JSC.Jest.Snapshots;
const Test = TestRunner.Test;
const NetworkThread = @import("bun").HTTP.NetworkThread;
const uws = @import("bun").uws;
@@ -365,12 +366,23 @@ pub const TestCommand = struct {
bun.JSC.initialize();
HTTPThread.init() catch {};
+ var snapshot_file_buf = std.ArrayList(u8).init(ctx.allocator);
+ var snapshot_values = Snapshots.ValuesHashMap.init(ctx.allocator);
+ var snapshot_counts = bun.StringHashMap(usize).init(ctx.allocator);
+
var reporter = try ctx.allocator.create(CommandLineReporter);
reporter.* = CommandLineReporter{
.jest = TestRunner{
.allocator = ctx.allocator,
.log = ctx.log,
.callback = undefined,
+ .snapshots = Snapshots{
+ .allocator = ctx.allocator,
+ .update_snapshots = ctx.test_options.update_snapshots,
+ .file_buf = &snapshot_file_buf,
+ .values = &snapshot_values,
+ .counts = &snapshot_counts,
+ },
},
.callback = undefined,
};
@@ -421,6 +433,8 @@ pub const TestCommand = struct {
runAllTests(reporter, vm, test_files, ctx.allocator);
}
+ try jest.Jest.runner.?.snapshots.writeSnapshotFile();
+
if (reporter.summary.pass > 20) {
if (reporter.summary.skip > 0) {
Output.prettyError("\n<r><d>{d} tests skipped:<r>\n", .{reporter.summary.skip});
@@ -481,6 +495,40 @@ pub const TestCommand = struct {
Output.prettyError(" {d:5>} fail<r>\n", .{reporter.summary.fail});
+ if (reporter.jest.snapshots.total > 0) {
+ const passed = reporter.jest.snapshots.passed;
+ const failed = reporter.jest.snapshots.failed;
+ const added = reporter.jest.snapshots.added;
+
+ var first = true;
+ Output.prettyError(" <d>snapshots:<r> ", .{});
+
+ if (passed > 0) {
+ Output.prettyError("<d>{d} passed<r>", .{passed});
+ first = false;
+ }
+
+ if (added > 0) {
+ if (first) {
+ first = false;
+ Output.prettyError("<d>{d} added<r>", .{added});
+ } else {
+ Output.prettyError("<d>, {d} added<r>", .{added});
+ }
+ }
+
+ if (failed > 0) {
+ if (first) {
+ first = false;
+ Output.prettyError("<red>{d} failed<r>", .{failed});
+ } else {
+ Output.prettyError(", <red>{d} failed<r>", .{failed});
+ }
+ }
+
+ Output.prettyError("\n", .{});
+ }
+
if (reporter.summary.expectations > 0) Output.prettyError(" {d:5>} expect() calls\n", .{reporter.summary.expectations});
Output.prettyError("Ran {d} tests across {d} files ", .{
diff --git a/test/js/bun/test/snapshot-tests/__snapshots__/bun-snapshots.test.ts.snap b/test/js/bun/test/snapshot-tests/__snapshots__/bun-snapshots.test.ts.snap
new file mode 100644
index 000000000..449a99d72
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/__snapshots__/bun-snapshots.test.ts.snap
@@ -0,0 +1,77 @@
+// Bun Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`toMatchSnapshot errors should throw if arguments are in the wrong order: right spot 1`] = `
+{
+ "a": "oops",
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 1`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<Boolean>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 2`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<String>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 3`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<Number>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 4`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<BigInt>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 5`] = `
+{
+ "a": Any<Date>,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 6`] = `
+{
+ "a": "any",
+ "b": Any<String>,
+ "j": Any<Number>,
+}
+`;
+
+exports[`it will create a snapshot file if it doesn't exist 7`] = `
+{
+ "a": "any",
+ "b": Any<String>,
+ "j": Any<RegExp>,
+}
+`;
diff --git a/test/js/bun/test/snapshot-tests/__snapshots__/existing-snapshots.test.ts.snap b/test/js/bun/test/snapshot-tests/__snapshots__/existing-snapshots.test.ts.snap
new file mode 100644
index 000000000..7c0d55c60
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/__snapshots__/existing-snapshots.test.ts.snap
@@ -0,0 +1,71 @@
+// Bun Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it will work with an existing snapshot file made with bun 1`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<Boolean>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 2`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<String>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 3`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<Number>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 4`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<BigInt>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 5`] = `
+{
+ "a": Any<Date>,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 6`] = `
+{
+ "a": "any",
+ "b": Any<String>,
+ "j": Any<Number>,
+}
+`;
+
+exports[`it will work with an existing snapshot file made with bun 7`] = `
+{
+ "a": "any",
+ "b": Any<String>,
+ "j": Any<RegExp>,
+}
+`;
diff --git a/test/js/bun/test/snapshot-tests/bun-snapshots.test.ts b/test/js/bun/test/snapshot-tests/bun-snapshots.test.ts
new file mode 100644
index 000000000..27ebb1b56
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/bun-snapshots.test.ts
@@ -0,0 +1,64 @@
+import fs from "fs";
+
+beforeAll(() => {
+ fs.rmSync(import.meta.dir + "/__snapshots__/bun-snapshots.test.ts.snap", { force: true });
+});
+
+test("it will create a snapshot file if it doesn't exist", () => {
+ expect({ a: { b: { c: false } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(Boolean) } } });
+ expect({ a: { b: { c: "string" } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(String) } } });
+ expect({ a: { b: { c: 4 } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(Number) } } });
+ expect({ a: { b: { c: 2n } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(BigInt) } } });
+ expect({ a: new Date() }).toMatchSnapshot({ a: expect.any(Date) });
+ expect({ j: 2, a: "any", b: "any2" }).toMatchSnapshot({ j: expect.any(Number), a: "any", b: expect.any(String) });
+ expect({ j: /regex/, a: "any", b: "any2" }).toMatchSnapshot({
+ j: expect.any(RegExp),
+ a: "any",
+ b: expect.any(String),
+ });
+});
+
+describe("toMatchSnapshot errors", () => {
+ it("should throw if property matchers exist and received is not an object", () => {
+ expect(() => {
+ expect(1).toMatchSnapshot({ a: 1 });
+ }).toThrow();
+ });
+ it("should throw if property matchers don't match", () => {
+ expect(() => {
+ expect({ a: 3 }).toMatchSnapshot({ a: 1 });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 3 }).toMatchSnapshot({ a: expect.any(Date) });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 3 }).toMatchSnapshot({ a: expect.any(String) });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 4n }).toMatchSnapshot({ a: expect.any(Number) });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 3 }).toMatchSnapshot({ a: expect.any(BigInt) });
+ }).toThrow();
+ });
+ it("should throw if arguments are in the wrong order", () => {
+ expect(() => {
+ expect({ a: "oops" }).toMatchSnapshot("wrong spot", { a: "oops" });
+ }).toThrow();
+ expect(() => {
+ expect({ a: "oops" }).toMatchSnapshot({ a: "oops" }, "right spot");
+ }).not.toThrow();
+ });
+
+ it("should throw if expect.any() doesn't received a constructor", () => {
+ expect(() => {
+ expect({ a: 4 }).toMatchSnapshot({ a: expect.any() });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 5 }).toMatchSnapshot({ a: expect.any(5) });
+ }).toThrow();
+ expect(() => {
+ expect({ a: 4 }).toMatchSnapshot({ a: expect.any("not a constructor") });
+ }).toThrow();
+ });
+});
diff --git a/test/js/bun/test/snapshot-tests/existing-snapshots.test.ts b/test/js/bun/test/snapshot-tests/existing-snapshots.test.ts
new file mode 100644
index 000000000..30040e2f1
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/existing-snapshots.test.ts
@@ -0,0 +1,13 @@
+test("it will work with an existing snapshot file made with bun", () => {
+ expect({ a: { b: { c: false } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(Boolean) } } });
+ expect({ a: { b: { c: "string" } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(String) } } });
+ expect({ a: { b: { c: 4 } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(Number) } } });
+ expect({ a: { b: { c: 2n } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(BigInt) } } });
+ expect({ a: new Date() }).toMatchSnapshot({ a: expect.any(Date) });
+ expect({ j: 2, a: "any", b: "any2" }).toMatchSnapshot({ j: expect.any(Number), a: "any", b: expect.any(String) });
+ expect({ j: /regex/, a: "any", b: "any2" }).toMatchSnapshot({
+ j: expect.any(RegExp),
+ a: "any",
+ b: expect.any(String),
+ });
+});
diff --git a/test/js/bun/test/snapshot-tests/generate-jest-snapshots.test.ts b/test/js/bun/test/snapshot-tests/generate-jest-snapshots.test.ts
new file mode 100644
index 000000000..c70174357
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/generate-jest-snapshots.test.ts
@@ -0,0 +1,45 @@
+import { bunExe } from "harness";
+import { tmpdir } from "os";
+import { mkdirSync, copyFileSync, writeFileSync } from "node:fs";
+
+test("generate jest snapshot output", () => {
+ // generate jest snapshots and let bun test runner test against them
+ const tempDir = tmpdir() + "/generate-jest-snapshots";
+ mkdirSync(tempDir + "/snapshots/more-snapshots", { recursive: true });
+ copyFileSync(import.meta.dir + "/snapshots/snapshot.test.ts", tempDir + "/snapshots/snapshot.test.ts");
+ copyFileSync(import.meta.dir + "/snapshots/more.test.ts", tempDir + "/snapshots/more.test.ts");
+ copyFileSync(import.meta.dir + "/snapshots/moremore.test.ts", tempDir + "/snapshots/moremore.test.ts");
+ copyFileSync(
+ import.meta.dir + "/snapshots/more-snapshots/different-directory.test.ts",
+ tempDir + "/snapshots/more-snapshots/different-directory.test.ts",
+ );
+ writeFileSync(tempDir + "/jest.config.js", "");
+
+ const { exitCode, stderr } = Bun.spawnSync({
+ cmd: [bunExe(), "x", "jest", tempDir + "/snapshots/", "--updateSnapshot"],
+ cwd: tempDir,
+ });
+
+ expect(exitCode).toBe(0);
+
+ // ensure snapshot directories exist
+ mkdirSync(import.meta.dir + "/snapshots/__snapshots__", { recursive: true });
+ mkdirSync(import.meta.dir + "/snapshots/more-snapshots/__snapshots__", { recursive: true });
+
+ copyFileSync(
+ tempDir + "/snapshots/__snapshots__/snapshot.test.ts.snap",
+ import.meta.dir + "/snapshots/__snapshots__/snapshot.test.ts.snap",
+ );
+ copyFileSync(
+ tempDir + "/snapshots/__snapshots__/more.test.ts.snap",
+ import.meta.dir + "/snapshots/__snapshots__/more.test.ts.snap",
+ );
+ copyFileSync(
+ tempDir + "/snapshots/__snapshots__/moremore.test.ts.snap",
+ import.meta.dir + "/snapshots/__snapshots__/moremore.test.ts.snap",
+ );
+ copyFileSync(
+ tempDir + "/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap",
+ import.meta.dir + "/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap",
+ );
+});
diff --git a/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/more.test.ts.snap b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/more.test.ts.snap
new file mode 100644
index 000000000..bee96183b
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/more.test.ts.snap
@@ -0,0 +1,148 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`d0 d1 t1 1`] = `"hello\`snapshot\\"`;
+
+exports[`d0 d1 t2 1`] = `"hey"`;
+
+exports[`d0 snapshot serialize edgecases 1`] = `1`;
+
+exports[`d0 snapshot serialize edgecases 2`] = `
+"12
+3
+4"
+`;
+
+exports[`d0 snapshot serialize edgecases 3`] = `
+"
+"
+`;
+
+exports[`d0 snapshot serialize edgecases 4`] = `
+"12
+3
+
+
+
+
+
+
+
+
+
+
+
+4 5 6 7\\
+
+
+
+r
+r
+"
+`;
+
+exports[`d0 snapshot serialize edgecases 5`] = `
+"12
+3
+4 5 6 7\\"
+`;
+
+exports[`d0 snapshot serialize edgecases 6`] = `
+"
+"
+`;
+
+exports[`d0 snapshot serialize edgecases 7`] = `
+"
+"
+`;
+
+exports[`d0 snapshot serialize edgecases 8`] = `"\\"`;
+
+exports[`d0 snapshot serialize edgecases 9`] = `" "`;
+
+exports[`d0 snapshot serialize edgecases 10`] = `" "`;
+
+exports[`d0 snapshot serialize edgecases 11`] = `" "`;
+
+exports[`d0 snapshot serialize edgecases 12`] = `""`;
+
+exports[`d0 snapshot serialize edgecases 13`] = `" "`;
+
+exports[`d0 snapshot serialize edgecases 14`] = `
+"hello sn
+ apshot"
+`;
+
+exports[`d0 snapshot serialize edgecases 15`] = `String {}`;
+
+exports[`d0 snapshot serialize edgecases 16`] = `String {}`;
+
+exports[`d0 snapshot serialize edgecases 17`] = `
+"\\
+export with test name
+
+"
+`;
+
+exports[`d0 snapshot serialize edgecases 18`] = `1`;
+
+exports[`d0 snapshot serialize edgecases 19`] = `2`;
+
+exports[`d0 snapshot serialize edgecases 20`] = `"\`\`\`\`\`\`\`\`\`\\\`\`\`\`\`\`\\\`\\\`\`\`\`\`\`\\\`\`\`\`\`\\\`\`\\\\\`\\\`\`\`\`\`\`\`\`\`\`\`\`"`;
+
+exports[`d0 snapshot serialize edgecases 21`] = `"\`\`\`\`\`\`\`\`\`\\\`\`\`\`\`\`\\\`\\\`\`\`\`\`\`\\\`\`\`\`\`\\\`\`\\\\\`\\\`\`\`\`\`\`\`\`\`\`\`\`\\"`;
+
+exports[`d0 snapshot serialize edgecases 22`] = `"\\\`\`\`\`\`\`\`\`\`\\\`\`\`\`\`\`\\\`\\\`\`\`\`\`\`\\\`\`\`\`\`\\\`\`\\\\\`\\\`\`\`\`\`\`\`\`\`\`\`\`"`;
+
+exports[`d0 snapshot serialize edgecases 23`] = `"\\\`\`\`\`\`\`\`\`\`\\\`\`\`\`\`\`\\\`\\\`\`\`\`\`\`\\\`\`\`\`\`\\\`\`\\\\\`\\\`\`\`\`\`\`\`\`\`\`\`\`\\"`;
+
+exports[`d0 snapshot serialize edgecases 24`] = `"one t\`wo \`three"`;
+
+exports[`d0 snapshot serialize edgecases 25`] = `"one tw\\\`o three"`;
+
+exports[`d0 snapshot serialize edgecases 26`] = `
+"
+export[\\\`hello snap'shot 2\`] = \`"
+`;
+
+exports[`d0 snapshot serialize edgecases 27`] = `
+"
+export[\`hello snapshot 2\`] = \`"
+`;
+
+exports[`d0 snapshot serialize edgecases 28`] = `"\`hello snapshot3 \\\`\`"`;
+
+exports[`d0 snapshot serialize edgecases 29`] = `"\`hello snapshot4 \\\`\\\`"`;
+
+exports[`d0 snapshot serialize edgecases 30`] = `"\\\`hello snapshot5 \\\`\\\`"`;
+
+exports[`d0 snapshot serialize edgecases: one 1`] = `1`;
+
+exports[`d0 snapshot serialize edgecases: one 2`] = `3`;
+
+exports[`d0 snapshot serialize edgecases: ¾ 1`] = `
+{
+ "a": 1,
+ "b": 2,
+ "c": 3,
+}
+`;
+
+exports[`d0 snapshot serialize edgecases: 🐄 1`] = `
+{
+ "a": 1,
+ "b": 2,
+ "c": 3,
+}
+`;
+
+exports[`d0 snapshot serialize edgecases: 😃 1`] = `
+{
+ "a": "🐄",
+ "b": "🐈",
+}
+`;
+
+exports[`d0 t3 1`] = `"hello snapshot"`;
+
+exports[`d0 t4 1`] = `"hello\`snapshot\\"`;
diff --git a/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/moremore.test.ts.snap b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/moremore.test.ts.snap
new file mode 100644
index 000000000..ab8c168fa
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/moremore.test.ts.snap
@@ -0,0 +1,96 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`test snapshots with Boolean and Number 1`] = `1`;
+
+exports[`test snapshots with Boolean and Number 2`] = `NaN`;
+
+exports[`test snapshots with Boolean and Number 3`] = `Infinity`;
+
+exports[`test snapshots with Boolean and Number 4`] = `-Infinity`;
+
+exports[`test snapshots with Boolean and Number 5`] = `0`;
+
+exports[`test snapshots with Boolean and Number 6`] = `-0`;
+
+exports[`test snapshots with Boolean and Number 7`] = `1.1`;
+
+exports[`test snapshots with Boolean and Number 8`] = `-1.1`;
+
+exports[`test snapshots with Boolean and Number 9`] = `undefined`;
+
+exports[`test snapshots with Boolean and Number 10`] = `null`;
+
+exports[`test snapshots with Boolean and Number 11`] = `"hello"`;
+
+exports[`test snapshots with Boolean and Number 12`] = `""`;
+
+exports[`test snapshots with Boolean and Number 13`] = `Number {}`;
+
+exports[`test snapshots with Boolean and Number 14`] = `Number2 {}`;
+
+exports[`test snapshots with Boolean and Number 15`] = `Number3 {}`;
+
+exports[`test snapshots with Boolean and Number 16`] = `123348923.2341281`;
+
+exports[`test snapshots with Boolean and Number 17`] = `false`;
+
+exports[`test snapshots with Boolean and Number 18`] = `true`;
+
+exports[`test snapshots with Boolean and Number 19`] = `Boolean {}`;
+
+exports[`test snapshots with Boolean and Number 20`] = `Boolean {}`;
+
+exports[`test snapshots with Boolean and Number 21`] = `Boolean2 {}`;
+
+exports[`test snapshots with Boolean and Number 22`] = `Boolean2 {}`;
+
+exports[`test snapshots with Boolean and Number 23`] = `
+Boolean3 {
+ "false": true,
+}
+`;
+
+exports[`test snapshots with Boolean and Number 24`] = `
+Boolean3 {
+ "false": true,
+}
+`;
+
+exports[`test snapshots with Boolean and Number 25`] = `
+{
+ "a": {
+ "b": {
+ "c": {
+ "d": {
+ "e": {
+ "bigint": Any<BigInt>,
+ "f": {
+ "g": {
+ "compare": "compare",
+ "h": {
+ "bool": Any<Boolean>,
+ "i": Any<Number3>,
+ },
+ },
+ },
+ "ignore1": 234,
+ "ignore2": {
+ "ignore3": 23421,
+ "ignore4": {
+ "ignore5": {
+ "ignore6": "hello",
+ "ignore7": "done",
+ },
+ },
+ },
+ },
+ },
+ "num": Any<Number>,
+ "string": Any<String>,
+ },
+ },
+ "j": Any<Date>,
+ },
+ "first": Any<Boolean2>,
+}
+`;
diff --git a/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap
new file mode 100644
index 000000000..51953013d
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/__snapshots__/snapshot.test.ts.snap
@@ -0,0 +1,370 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`most types 1`] = `3`;
+
+exports[`most types 2`] = `1`;
+
+exports[`most types 3`] = `2`;
+
+exports[`most types 4`] = `
+{
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": Any<A>,
+ "e": 5,
+ "f": 6,
+}
+`;
+
+exports[`most types 5`] = `
+{
+ "a": {
+ "b": {
+ "c": {
+ "d": {
+ "e": {
+ "bigint": Any<BigInt>,
+ "f": {
+ "g": {
+ "compare": "compare",
+ "h": {
+ "bool": Any<Boolean>,
+ "i": Any<Date>,
+ },
+ },
+ },
+ "ignore1": 234,
+ "ignore2": {
+ "ignore3": 23421,
+ "ignore4": {
+ "ignore5": {
+ "ignore6": "hello",
+ "ignore7": "done",
+ },
+ },
+ },
+ },
+ },
+ "num": Any<Number>,
+ "string": Any<String>,
+ },
+ },
+ "j": Any<Date>,
+ },
+ "first": Any<Date>,
+}
+`;
+
+exports[`most types: Array 1`] = `[]`;
+
+exports[`most types: Array with empty array 1`] = `
+[
+ [],
+]
+`;
+
+exports[`most types: Array with multiple empty arrays 1`] = `
+[
+ [],
+ [],
+ [],
+ [],
+]
+`;
+
+exports[`most types: Array with nested arrays 1`] = `
+[
+ 1,
+ 2,
+ [
+ 3,
+ 4,
+ ],
+ [
+ 4,
+ [
+ 5,
+ 6,
+ ],
+ ],
+ 8,
+]
+`;
+
+exports[`most types: Array2 1`] = `
+[
+ 1,
+ 2,
+ 3,
+]
+`;
+
+exports[`most types: ArrayBuffer 1`] = `ArrayBuffer []`;
+
+exports[`most types: Boolean 1`] = `Boolean {}`;
+
+exports[`most types: Buffer 1`] = `
+{
+ "data": [],
+ "type": "Buffer",
+}
+`;
+
+exports[`most types: Buffer with property 1`] = `
+{
+ "data": [
+ 104,
+ 101,
+ 108,
+ 108,
+ 111,
+ ],
+ "type": "Buffer",
+}
+`;
+
+exports[`most types: Buffer2 1`] = `
+{
+ "data": [
+ 104,
+ 101,
+ 108,
+ 108,
+ 111,
+ ],
+ "type": "Buffer",
+}
+`;
+
+exports[`most types: Buffer3 1`] = `
+{
+ "data": [
+ 104,
+ 101,
+ 108,
+ 96,
+ 10,
+ 10,
+ 96,
+ ],
+ "type": "Buffer",
+}
+`;
+
+exports[`most types: Class 1`] = `
+A {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+}
+`;
+
+exports[`most types: DataView 1`] = `DataView []`;
+
+exports[`most types: Date 1`] = `1970-01-01T00:00:00.000Z`;
+
+exports[`most types: Empty Error 1`] = `[Error]`;
+
+exports[`most types: Error 1`] = `[Error: hello]`;
+
+exports[`most types: Float32Array 1`] = `Float32Array []`;
+
+exports[`most types: Float64Array 1`] = `Float64Array []`;
+
+exports[`most types: Function 1`] = `[Function]`;
+
+exports[`most types: Int8Array 1`] = `Int8Array []`;
+
+exports[`most types: Int8Array with elements 1`] = `
+Int8Array [
+ 1,
+ 2,
+ 3,
+ 4,
+]
+`;
+
+exports[`most types: Int8Array with one element 1`] = `
+Int8Array [
+ 3,
+]
+`;
+
+exports[`most types: Int16Array 1`] = `Int16Array []`;
+
+exports[`most types: Int32Array 1`] = `Int32Array []`;
+
+exports[`most types: Map 1`] = `
+Map {
+ 1 => "eight",
+ "seven" => "312390840812",
+}
+`;
+
+exports[`most types: Number 1`] = `Number {}`;
+
+exports[`most types: Object 1`] = `{}`;
+
+exports[`most types: Object with Buffer 1`] = `
+{
+ "a": {
+ "data": [
+ 104,
+ 101,
+ 108,
+ 108,
+ 111,
+ ],
+ "type": "Buffer",
+ },
+}
+`;
+
+exports[`most types: Object with Int8Array 1`] = `
+{
+ "a": 1,
+ "b": Int8Array [
+ 123,
+ -89,
+ 4,
+ 34,
+ ],
+}
+`;
+
+exports[`most types: Object with String with property 1`] = `
+{
+ "a": String {},
+}
+`;
+
+exports[`most types: Object with empty Buffer 1`] = `
+{
+ "a": {
+ "data": [],
+ "type": "Buffer",
+ },
+}
+`;
+
+exports[`most types: Object with empty String 1`] = `
+{
+ "a": String {},
+}
+`;
+
+exports[`most types: Object with empty object 1`] = `
+{
+ "a": {},
+}
+`;
+
+exports[`most types: Object2 1`] = `
+{
+ "a": 1,
+ "b": 2,
+}
+`;
+
+exports[`most types: Promise 1`] = `Promise {}`;
+
+exports[`most types: RegExp 1`] = `/hello/`;
+
+exports[`most types: Set 1`] = `Set {}`;
+
+exports[`most types: Set2 1`] = `
+Set {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+}
+`;
+
+exports[`most types: String 1`] = `
+String {
+ "0": "h",
+ "1": "e",
+ "2": "l",
+ "3": "l",
+ "4": "o",
+}
+`;
+
+exports[`most types: String with property 1`] = `String {}`;
+
+exports[`most types: Uint8Array 1`] = `Uint8Array []`;
+
+exports[`most types: Uint8ClampedArray 1`] = `Uint8ClampedArray []`;
+
+exports[`most types: Uint16Array 1`] = `Uint16Array []`;
+
+exports[`most types: Uint32Array 1`] = `Uint32Array []`;
+
+exports[`most types: WeakMap 1`] = `WeakMap {}`;
+
+exports[`most types: WeakSet 1`] = `WeakSet {}`;
+
+exports[`most types: arrow function 1`] = `[Function]`;
+
+exports[`most types: empty map 1`] = `Map {}`;
+
+exports[`most types: nested object with Buffer 1`] = `
+{
+ "a": {
+ "b": {
+ "data": [
+ 104,
+ 101,
+ 108,
+ 108,
+ 111,
+ ],
+ "type": "Buffer",
+ },
+ },
+}
+`;
+
+exports[`most types: nested object with empty Buffer 1`] = `
+{
+ "a": {
+ "b": {
+ "data": [],
+ "type": "Buffer",
+ },
+ },
+}
+`;
+
+exports[`most types: nested object with empty Int8Array 1`] = `
+{
+ "a": {
+ "b": Int8Array [],
+ },
+}
+`;
+
+exports[`most types: null 1`] = `null`;
+
+exports[`most types: string 1`] = `"hello string"`;
+
+exports[`most types: testing 4 1`] = `6`;
+
+exports[`most types: testing 4 2`] = `4`;
+
+exports[`most types: testing 5 1`] = `5`;
+
+exports[`most types: testing 7 1`] = `7`;
+
+exports[`most types: testing 7 2`] = `9`;
+
+exports[`most types: testing 7 3`] = `8`;
+
+exports[`most types: undefined 1`] = `undefined`;
diff --git a/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap b/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap
new file mode 100644
index 000000000..f45127b38
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/__snapshots__/different-directory.test.ts.snap
@@ -0,0 +1,76 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`snapshots in different directory 1`] = `
+"12
+3
+4"
+`;
+
+exports[`snapshots in different directory 2`] = `
+"
+"
+`;
+
+exports[`snapshots in different directory 3`] = `
+"12
+3
+ r
+\\"
+`;
+
+exports[`snapshots in different directory 4`] = `
+"12
+3
+4 5 6 7\\"
+`;
+
+exports[`snapshots in different directory 5`] = `
+"\\
+\\
+\\ \\ \\ \\"
+`;
+
+exports[`snapshots in different directory 6`] = `
+"
+"
+`;
+
+exports[`snapshots in different directory 7`] = `
+"
+"
+`;
+
+exports[`snapshots in different directory 8`] = `"\\"`;
+
+exports[`snapshots in different directory 9`] = `" "`;
+
+exports[`snapshots in different directory 10`] = `" "`;
+
+exports[`snapshots in different directory 11`] = `" "`;
+
+exports[`snapshots in different directory 12`] = `""`;
+
+exports[`snapshots in different directory 13`] = `
+"'
+
+
+
+
+
+
+
+
+"
+`;
+
+exports[`snapshots in different directory 14`] = `
+{
+ "a": {
+ "b": {
+ "c": Any<Date>,
+ },
+ },
+ "c": 2,
+ "jkfje": 99238,
+}
+`;
diff --git a/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/different-directory.test.ts b/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/different-directory.test.ts
new file mode 100644
index 000000000..6d29cf26f
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/more-snapshots/different-directory.test.ts
@@ -0,0 +1,18 @@
+test("snapshots in different directory", () => {
+ expect("1\b2\n3\r4").toMatchSnapshot();
+ expect("\r\n").toMatchSnapshot();
+ expect("1\b2\n3\r r\r\\").toMatchSnapshot();
+ expect("1\b2\n3\r4\v5\f6\t7\\").toMatchSnapshot();
+ expect("\\\r\\\n\\\t\\\v\\\f\\\b").toMatchSnapshot();
+ expect("\r").toMatchSnapshot();
+ expect("\n").toMatchSnapshot();
+ expect("\\").toMatchSnapshot();
+ expect("\v").toMatchSnapshot();
+ expect("\f").toMatchSnapshot();
+ expect("\t").toMatchSnapshot();
+ expect("\b").toMatchSnapshot();
+ expect("\b'\b\r\r\n\r\n\n\r\n\n\r\r\r").toMatchSnapshot();
+ expect("\n\\\n");
+
+ expect({ a: { b: { c: new Date() } }, c: 2, jkfje: 99238 }).toMatchSnapshot({ a: { b: { c: expect.any(Date) } } });
+});
diff --git a/test/js/bun/test/snapshot-tests/snapshots/more.test.ts b/test/js/bun/test/snapshot-tests/snapshots/more.test.ts
new file mode 100644
index 000000000..4cf0c8a1c
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/more.test.ts
@@ -0,0 +1,62 @@
+describe("d0", () => {
+ test("snapshot serialize edgecases", () => {
+ expect(1).toMatchSnapshot();
+ expect("1\b2\n3\r4").toMatchSnapshot();
+ expect("\r\n").toMatchSnapshot();
+ expect("1\b2\n3\r\r\r\r\r\r\r\r\r\r\r\r4\v5\f6\t7\\\n\r\n\n\nr\nr\n").toMatchSnapshot();
+ expect("1\b2\n3\r4\v5\f6\t7\\").toMatchSnapshot();
+ expect("\r").toMatchSnapshot();
+ expect("\n").toMatchSnapshot();
+ expect("\\").toMatchSnapshot();
+ expect("\v").toMatchSnapshot();
+ expect("\f").toMatchSnapshot();
+ expect("\t").toMatchSnapshot();
+ expect("\b").toMatchSnapshot();
+ expect("\b\t").toMatchSnapshot();
+
+ expect(`hello sn
+ apshot`).toMatchSnapshot();
+ expect(new String()).toMatchSnapshot();
+ expect(new String("")).toMatchSnapshot();
+
+ expect({ a: { b: 1 } }).toEqual({ a: { b: 1 } });
+ expect("\\\nexport with test name\n\n").toMatchSnapshot();
+
+ expect(1).toMatchSnapshot();
+ expect(1).toMatchSnapshot("one");
+ expect(2).toMatchSnapshot();
+ expect(3).toMatchSnapshot("one");
+ expect("`````````\\``````\\`\\``````\\`````\\``\\\\`\\````````````").toMatchSnapshot();
+ expect("`````````\\``````\\`\\``````\\`````\\``\\\\`\\````````````\\").toMatchSnapshot();
+ expect("\\`````````\\``````\\`\\``````\\`````\\``\\\\`\\````````````").toMatchSnapshot();
+ expect("\\`````````\\``````\\`\\``````\\`````\\``\\\\`\\````````````\\").toMatchSnapshot();
+ expect("one t`wo `three").toMatchSnapshot();
+ expect("one tw\\`o three").toMatchSnapshot();
+ expect("\nexport[\\`hello snap'shot 2`] = `").toMatchSnapshot();
+ expect("\nexport[`hello snapshot 2`] = `").toMatchSnapshot();
+ expect("`hello snapshot3 \\``").toMatchSnapshot();
+ expect("`hello snapshot4 \\`\\`").toMatchSnapshot();
+ expect("\\`hello snapshot5 \\`\\`").toMatchSnapshot();
+ expect({ a: 1, b: 2, c: 3 }).toMatchSnapshot("¾");
+ expect({ a: 1, b: 2, c: 3 }).toMatchSnapshot("\uD83D\uDC04");
+ expect({ a: "\uD83D\uDC04", b: "🐈" }).toMatchSnapshot("😃");
+ });
+});
+
+describe("d0", () => {
+ describe("d1", () => {
+ test("t1", () => {
+ expect("hello`snapshot\\").toEqual("hello`snapshot\\");
+ expect("hello`snapshot\\").toMatchSnapshot();
+ });
+ test("t2", () => {
+ expect("hey").toMatchSnapshot();
+ });
+ });
+ test("t3", () => {
+ expect("hello snapshot").toMatchSnapshot();
+ });
+ test("t4", () => {
+ expect("hello`snapshot\\").toMatchSnapshot();
+ });
+});
diff --git a/test/js/bun/test/snapshot-tests/snapshots/moremore.test.ts b/test/js/bun/test/snapshot-tests/snapshots/moremore.test.ts
new file mode 100644
index 000000000..d3ed3da42
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/moremore.test.ts
@@ -0,0 +1,118 @@
+class Number2 extends Number {
+ constructor(value) {
+ super(value);
+ }
+}
+class Number3 extends Number2 {
+ constructor(value) {
+ super(value);
+ }
+}
+
+class Boolean2 extends Boolean {
+ constructor(value) {
+ super(value);
+ }
+}
+
+class Boolean3 extends Boolean2 {
+ constructor(value) {
+ super(value);
+ }
+
+ false = true;
+
+ helloBoolean3() {
+ return "true";
+ }
+}
+
+test("test snapshots with Boolean and Number", () => {
+ expect(1).toMatchSnapshot();
+ expect(NaN).toMatchSnapshot();
+ expect(Infinity).toMatchSnapshot();
+ expect(-Infinity).toMatchSnapshot();
+ expect(0).toMatchSnapshot();
+ expect(-0).toMatchSnapshot();
+ expect(1.1).toMatchSnapshot();
+ expect(-1.1).toMatchSnapshot();
+ expect(undefined).toMatchSnapshot();
+ expect(null).toMatchSnapshot();
+ expect("hello").toMatchSnapshot();
+ expect("").toMatchSnapshot();
+
+ expect(new Number(1)).toMatchSnapshot();
+ expect(new Number2(1)).toMatchSnapshot();
+ expect(new Number3(1)).toMatchSnapshot();
+ expect(123348923.2341281).toMatchSnapshot();
+ expect(false).toMatchSnapshot();
+ expect(true).toMatchSnapshot();
+ expect(new Boolean(false)).toMatchSnapshot();
+ expect(new Boolean(true)).toMatchSnapshot();
+ expect(new Boolean2(true)).toMatchSnapshot();
+ expect(new Boolean2(false)).toMatchSnapshot();
+ expect(new Boolean3(true)).toMatchSnapshot();
+ expect(new Boolean3(false)).toMatchSnapshot();
+
+ expect({
+ first: new Boolean2(false),
+ a: {
+ j: new Date(),
+ b: {
+ c: {
+ num: 1,
+ d: {
+ e: {
+ bigint: 123n,
+ f: {
+ g: {
+ h: {
+ i: new Number3(2),
+ bool: true,
+ },
+ compare: "compare",
+ },
+ },
+ ignore1: 234,
+ ignore2: {
+ ignore3: 23421,
+ ignore4: {
+ ignore5: {
+ ignore6: "hello",
+ ignore7: "done",
+ },
+ },
+ },
+ },
+ },
+ string: "hello",
+ },
+ },
+ },
+ }).toMatchSnapshot({
+ first: expect.any(Boolean2),
+ a: {
+ j: expect.any(Date),
+ b: {
+ c: {
+ num: expect.any(Number),
+ string: expect.any(String),
+ d: {
+ e: {
+ bigint: expect.any(BigInt),
+ f: {
+ g: {
+ compare: "compare",
+ h: {
+ i: expect.any(Number3),
+ bool: expect.any(Boolean),
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+});
diff --git a/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts b/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts
new file mode 100644
index 000000000..e5a024379
--- /dev/null
+++ b/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts
@@ -0,0 +1,161 @@
+function test1000000(arg1, arg218718132) {}
+
+test("most types", () => {
+ expect(test1000000).toMatchSnapshot("Function");
+ expect(null).toMatchSnapshot("null");
+ expect(() => {}).toMatchSnapshot("arrow function");
+ expect(7).toMatchSnapshot("testing 7");
+ expect(6).toMatchSnapshot("testing 4");
+ expect(5).toMatchSnapshot("testing 5");
+ expect(4).toMatchSnapshot("testing 4");
+ expect(3).toMatchSnapshot();
+ expect(1).toMatchSnapshot();
+ expect(2).toMatchSnapshot();
+ expect(9).toMatchSnapshot("testing 7");
+ expect(8).toMatchSnapshot("testing 7");
+ expect(undefined).toMatchSnapshot("undefined");
+ expect("hello string").toMatchSnapshot("string");
+ expect([[]]).toMatchSnapshot("Array with empty array");
+ expect([[], [], [], []]).toMatchSnapshot("Array with multiple empty arrays");
+ expect([1, 2, [3, 4], [4, [5, 6]], 8]).toMatchSnapshot("Array with nested arrays");
+ let buf = new Buffer("hello");
+ buf.x = "yyyyyyyyyy";
+ expect(buf).toMatchSnapshot("Buffer with property");
+ expect(new Buffer("hello")).toMatchSnapshot("Buffer2");
+ expect(new Buffer("hel`\n\n`")).toMatchSnapshot("Buffer3");
+ expect({ a: new Buffer("hello") }).toMatchSnapshot("Object with Buffer");
+ expect({ a: { b: new Buffer("hello") } }).toMatchSnapshot("nested object with Buffer");
+ expect({ a: { b: new Buffer("") } }).toMatchSnapshot("nested object with empty Buffer");
+ expect({ a: new Buffer("") }).toMatchSnapshot("Object with empty Buffer");
+ expect(new Buffer("")).toMatchSnapshot("Buffer");
+ expect(new Date(0)).toMatchSnapshot("Date");
+ expect(new Error("hello")).toMatchSnapshot("Error");
+ expect(new Error()).toMatchSnapshot("Empty Error");
+ expect(new Map()).toMatchSnapshot("empty map");
+ expect(
+ new Map([
+ [1, "eight"],
+ ["seven", "312390840812"],
+ ]),
+ ).toMatchSnapshot("Map");
+ expect(new Set()).toMatchSnapshot("Set");
+ expect(new Set([1, 2, 3, 4, 5, 6, 7, 8, 9])).toMatchSnapshot("Set2");
+ expect(new WeakMap()).toMatchSnapshot("WeakMap");
+ expect(new WeakSet()).toMatchSnapshot("WeakSet");
+ expect(new Promise(() => {})).toMatchSnapshot("Promise");
+ expect(new RegExp("hello")).toMatchSnapshot("RegExp");
+
+ let s = new String("");
+
+ expect(s).toMatchSnapshot("String with property");
+ expect({ a: s }).toMatchSnapshot("Object with String with property");
+ expect({ a: new String() }).toMatchSnapshot("Object with empty String");
+ expect(new String("hello")).toMatchSnapshot("String");
+
+ expect(new Number(7)).toMatchSnapshot("Number");
+ expect({ a: {} }).toMatchSnapshot("Object with empty object");
+ expect(new Boolean(true)).toMatchSnapshot("Boolean");
+ expect(new Int8Array([3])).toMatchSnapshot("Int8Array with one element");
+ expect(new Int8Array([1, 2, 3, 4])).toMatchSnapshot("Int8Array with elements");
+ expect(new Int8Array()).toMatchSnapshot("Int8Array");
+ expect({ a: 1, b: new Int8Array([123, 423, 4, 34]) }).toMatchSnapshot("Object with Int8Array");
+ expect({ a: { b: new Int8Array([]) } }).toMatchSnapshot("nested object with empty Int8Array");
+ expect(new Uint8Array()).toMatchSnapshot("Uint8Array");
+ expect(new Uint8ClampedArray()).toMatchSnapshot("Uint8ClampedArray");
+ expect(new Int16Array()).toMatchSnapshot("Int16Array");
+ expect(new Uint16Array()).toMatchSnapshot("Uint16Array");
+ expect(new Int32Array()).toMatchSnapshot("Int32Array");
+ expect(new Uint32Array()).toMatchSnapshot("Uint32Array");
+ expect(new Float32Array()).toMatchSnapshot("Float32Array");
+ expect(new Float64Array()).toMatchSnapshot("Float64Array");
+ expect(new ArrayBuffer(0)).toMatchSnapshot("ArrayBuffer");
+ expect(new DataView(new ArrayBuffer(0))).toMatchSnapshot("DataView");
+ expect({}).toMatchSnapshot("Object");
+ expect({ a: 1, b: 2 }).toMatchSnapshot("Object2");
+ expect([]).toMatchSnapshot("Array");
+ expect([1, 2, 3]).toMatchSnapshot("Array2");
+ class A {
+ a = 1;
+ b = 2;
+ constructor() {
+ this.c = 3;
+ }
+ d() {
+ return 4;
+ }
+ get e() {
+ return 5;
+ }
+ set e(value) {
+ this.f = value;
+ }
+ }
+ expect(new A()).toMatchSnapshot("Class");
+
+ expect({ a: 1, b: 2, c: 3, d: new A(), e: 5, f: 6 }).toMatchSnapshot({ d: expect.any(A) });
+ expect({
+ first: new Date(),
+ a: {
+ j: new Date(),
+ b: {
+ c: {
+ num: 1,
+ d: {
+ e: {
+ bigint: 123n,
+ f: {
+ g: {
+ h: {
+ i: new Date(),
+ bool: true,
+ },
+ compare: "compare",
+ },
+ },
+ ignore1: 234,
+ ignore2: {
+ ignore3: 23421,
+ ignore4: {
+ ignore5: {
+ ignore6: "hello",
+ ignore7: "done",
+ },
+ },
+ },
+ },
+ },
+ string: "hello",
+ },
+ },
+ },
+ }).toMatchSnapshot({
+ first: expect.any(Date),
+ a: {
+ j: expect.any(Date),
+ b: {
+ c: {
+ num: expect.any(Number),
+ string: expect.any(String),
+ d: {
+ e: {
+ bigint: expect.any(BigInt),
+ f: {
+ g: {
+ compare: "compare",
+ h: {
+ i: expect.any(Date),
+ bool: expect.any(Boolean),
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+});
+
+it("should work with expect.anything()", () => {
+ // expect({ a: 0 }).toMatchSnapshot({ a: expect.anything() });
+});
diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js
index ec2fb0b45..fe8f62a1c 100644
--- a/test/js/bun/util/inspect.test.js
+++ b/test/js/bun/util/inspect.test.js
@@ -15,7 +15,7 @@ it("when prototype defines the same property, don't print the same property twic
};
var obj = Object.create(base);
obj.foo = "456";
- expect(Bun.inspect(obj).trim()).toBe('{\n foo: "456"\n}'.trim());
+ expect(Bun.inspect(obj).trim()).toBe('{\n "foo": "456"\n}'.trim());
});
it("Blob inspect", () => {
@@ -194,10 +194,10 @@ it("inspect", () => {
expect(Bun.inspect(1, "hi")).toBe("1 hi");
expect(Bun.inspect([])).toBe("[]");
expect(Bun.inspect({})).toBe("{}");
- expect(Bun.inspect({ hello: 1 })).toBe("{\n hello: 1\n}");
- expect(Bun.inspect({ hello: 1, there: 2 })).toBe("{\n hello: 1,\n there: 2\n}");
- expect(Bun.inspect({ hello: "1", there: 2 })).toBe('{\n hello: "1",\n there: 2\n}');
- expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe('{\n "hello-\\"there": "1",\n there: 2\n}');
+ expect(Bun.inspect({ hello: 1 })).toBe('{\n "hello": 1\n}');
+ expect(Bun.inspect({ hello: 1, there: 2 })).toBe('{\n "hello": 1,\n "there": 2\n}');
+ expect(Bun.inspect({ hello: "1", there: 2 })).toBe('{\n "hello": "1",\n "there": 2\n}');
+ expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe('{\n "hello-\\"there": "1",\n "there": 2\n}');
var str = "123";
while (str.length < 4096) {
str += "123";
diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt
index 97191c8be..938f81e05 100644
--- a/test/js/web/console/console-log.expected.txt
+++ b/test/js/web/console/console-log.expected.txt
@@ -11,21 +11,21 @@ Symbol(Symbol Description)
2000-06-27T02:24:34.304Z
[ 123, 456, 789 ]
{
- name: "foo"
+ "name": "foo"
}
{
- a: 123,
- b: 456,
- c: 789
+ "a": 123,
+ "b": 456,
+ "c": 789
}
{
- a: {
- b: {
- c: 123
+ "a": {
+ "b": {
+ "c": 123
},
- bacon: true
+ "bacon": true
},
- name: "bar"
+ "name": "bar"
}
Promise { <pending> }
[Function]
@@ -36,10 +36,10 @@ Promise { <pending> }
Is it a bug or a feature that formatting numbers like 123 is colored
String 123 should be 2nd word, 456 == 456 and percent s %s == What okay
{
- foo: {
- name: "baz"
+ "foo": {
+ "name": "baz"
},
- bar: [Circular]
+ "bar": [Circular]
} am
[
{}, {}, {}, {}
diff --git a/test/js/web/url/url.test.ts b/test/js/web/url/url.test.ts
index 19e10b262..de24ba64a 100644
--- a/test/js/web/url/url.test.ts
+++ b/test/js/web/url/url.test.ts
@@ -3,34 +3,34 @@ import { describe, it, expect } from "bun:test";
describe("url", () => {
it("prints", () => {
expect(Bun.inspect(new URL("https://example.com"))).toBe(`URL {
- href: "https://example.com/",
- origin: "https://example.com",
- protocol: "https:",
- username: "",
- password: "",
- host: "example.com",
- hostname: "example.com",
- port: "",
- pathname: "/",
- hash: "",
- search: "",
- searchParams: URLSearchParams {
- append: [Function: append],
- delete: [Function: delete],
- get: [Function: get],
- getAll: [Function: getAll],
- has: [Function: has],
- set: [Function: set],
- sort: [Function: sort],
- entries: [Function: entries],
- keys: [Function: keys],
- values: [Function: values],
- forEach: [Function: forEach],
- toString: [Function: toString],
- [Symbol(Symbol.iterator)]: [Function: entries]
+ "href": "https://example.com/",
+ "origin": "https://example.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "example.com",
+ "hostname": "example.com",
+ "port": "",
+ "pathname": "/",
+ "hash": "",
+ "search": "",
+ "searchParams": URLSearchParams {
+ "append": [Function: append],
+ "delete": [Function: delete],
+ "get": [Function: get],
+ "getAll": [Function: getAll],
+ "has": [Function: has],
+ "set": [Function: set],
+ "sort": [Function: sort],
+ "entries": [Function: entries],
+ "keys": [Function: keys],
+ "values": [Function: values],
+ "forEach": [Function: forEach],
+ "toString": [Function: toString],
+ [Symbol(Symbol.iterator)]: [Function: entries],
},
- toJSON: [Function: toJSON],
- toString: [Function: toString]
+ "toJSON": [Function: toJSON],
+ "toString": [Function: toString],
}`);
expect(
@@ -38,34 +38,34 @@ describe("url", () => {
new URL("https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night"),
),
).toBe(`URL {
- href: "https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night",
- origin: "https://github.com",
- protocol: "https:",
- username: "",
- password: "",
- host: "github.com",
- hostname: "github.com",
- port: "",
- pathname: "/oven-sh/bun/issues/135",
- hash: "",
- search: "?hello%20i%20have%20spaces%20thank%20you%20good%20night",
- searchParams: URLSearchParams {
- append: [Function: append],
- delete: [Function: delete],
- get: [Function: get],
- getAll: [Function: getAll],
- has: [Function: has],
- set: [Function: set],
- sort: [Function: sort],
- entries: [Function: entries],
- keys: [Function: keys],
- values: [Function: values],
- forEach: [Function: forEach],
- toString: [Function: toString],
- [Symbol(Symbol.iterator)]: [Function: entries]
+ "href": "https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night",
+ "origin": "https://github.com",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "github.com",
+ "hostname": "github.com",
+ "port": "",
+ "pathname": "/oven-sh/bun/issues/135",
+ "hash": "",
+ "search": "?hello%20i%20have%20spaces%20thank%20you%20good%20night",
+ "searchParams": URLSearchParams {
+ "append": [Function: append],
+ "delete": [Function: delete],
+ "get": [Function: get],
+ "getAll": [Function: getAll],
+ "has": [Function: has],
+ "set": [Function: set],
+ "sort": [Function: sort],
+ "entries": [Function: entries],
+ "keys": [Function: keys],
+ "values": [Function: values],
+ "forEach": [Function: forEach],
+ "toString": [Function: toString],
+ [Symbol(Symbol.iterator)]: [Function: entries],
},
- toJSON: [Function: toJSON],
- toString: [Function: toString]
+ "toJSON": [Function: toJSON],
+ "toString": [Function: toString],
}`);
});
it("works", () => {