Skip to content

Commit

Permalink
Implement expect().toMatchInlineSnapshot() (#15570)
Browse files Browse the repository at this point in the history
  • Loading branch information
pfgithub authored Dec 5, 2024
1 parent b7b1ca8 commit bcf023c
Show file tree
Hide file tree
Showing 16 changed files with 1,093 additions and 126 deletions.
1 change: 0 additions & 1 deletion docs/guides/test/migrate-from-jest.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Bun implements the vast majority of Jest's matchers, but compatibility isn't 100

Some notable missing features:

- `expect().toMatchInlineSnapshot()`
- `expect().toHaveReturned()`

---
Expand Down
6 changes: 1 addition & 5 deletions docs/guides/test/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ name: Use snapshot testing in `bun test`

Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.

{% callout %}
The `.toMatchInlineSnapshot()` method is not yet supported.
{% /callout %}

```ts#snap.test.ts
import { test, expect } from "bun:test";

Expand Down Expand Up @@ -96,4 +92,4 @@ Ran 1 tests across 1 files. [102.00ms]

---

See [Docs > Test Runner > Snapshots](https://bun.sh/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
See [Docs > Test Runner > Snapshots](https://bun.sh/docs/test/snapshots) for complete documentation on snapshots with the Bun test runner.
6 changes: 1 addition & 5 deletions docs/guides/test/update-snapshots.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ name: Update snapshots in `bun test`

Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.

{% callout %}
The `.toMatchInlineSnapshot()` method is not yet supported.
{% /callout %}

```ts#snap.test.ts
import { test, expect } from "bun:test";

Expand Down Expand Up @@ -47,4 +43,4 @@ Ran 1 tests across 1 files. [102.00ms]

---

See [Docs > Test Runner > Snapshots](https://bun.sh/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
See [Docs > Test Runner > Snapshots](https://bun.sh/docs/test/snapshots) for complete documentation on snapshots with the Bun test runner.
2 changes: 1 addition & 1 deletion docs/test/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap

---

-
-
- [`.toMatchInlineSnapshot()`](https://jestjs.io/docs/expect#tomatchinlinesnapshotpropertymatchers-inlinesnapshot)

---
Expand Down
23 changes: 23 additions & 0 deletions packages/bun-types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,29 @@ declare module "bun:test" {
* @param hint Hint used to identify the snapshot in the snapshot file.
*/
toMatchSnapshot(propertyMatchers?: object, hint?: string): void;
/**
* Asserts that a value matches the most recent inline snapshot.
*
* @example
* expect("Hello").toMatchInlineSnapshot(`"Hello"`);
* @param value The latest snapshot value.
*/
toMatchInlineSnapshot(value?: string): void;
/**
* Asserts that a value matches the most recent inline snapshot.
*
* @example
* expect("Hello").toMatchInlineSnapshot(`"Hello"`);
* expect({ c: new Date() }).toMatchInlineSnapshot({ c: expect.any(Date) }, `
* {
* "v": Any<Date>,
* }
* `);
*
* @param propertyMatchers Object containing properties to match against the value.
* @param hint Hint used to identify the snapshot in the snapshot file.
*/
toMatchInlineSnapshot(propertyMatchers?: object, value?: string): void;
/**
* Asserts that an object matches a subset of properties.
*
Expand Down
73 changes: 73 additions & 0 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
#include "JavaScriptCore/CustomGetterSetter.h"

#include "ErrorStackFrame.h"
#include "ErrorStackTrace.h"
#include "ObjectBindings.h"

#if OS(DARWIN)
Expand Down Expand Up @@ -6030,3 +6031,75 @@ CPP_DECL bool Bun__CallFrame__isFromBunMain(JSC::CallFrame* callFrame, JSC::VM*
return false;
return source.string() == "builtin://bun/main"_s;
}

CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, unsigned int* outSourceID, unsigned int* outLine, unsigned int* outColumn)
{
JSC::VM& vm = globalObject->vm();
JSC::LineColumn lineColumn;
JSC::SourceID sourceID = 0;
String sourceURL;

ZigStackFrame remappedFrame = {};

JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
if (Zig::isImplementationVisibilityPrivate(visitor))
return WTF::IterationStatus::Continue;

if (visitor->hasLineAndColumnInfo()) {
lineColumn = visitor->computeLineAndColumn();

String sourceURLForFrame = visitor->sourceURL();

// Sometimes, the sourceURL is empty.
// For example, pages in Next.js.
if (sourceURLForFrame.isEmpty()) {

// hasLineAndColumnInfo() checks codeBlock(), so this is safe to access here.
const auto& source = visitor->codeBlock()->source();

// source.isNull() is true when the SourceProvider is a null pointer.
if (!source.isNull()) {
auto* provider = source.provider();
// I'm not 100% sure we should show sourceURLDirective here.
if (!provider->sourceURLDirective().isEmpty()) {
sourceURLForFrame = provider->sourceURLDirective();
} else if (!provider->sourceURL().isEmpty()) {
sourceURLForFrame = provider->sourceURL();
} else {
const auto& origin = provider->sourceOrigin();
if (!origin.isNull()) {
sourceURLForFrame = origin.string();
}
}

sourceID = provider->asID();
}
}

sourceURL = sourceURLForFrame;

return WTF::IterationStatus::Done;
}

return WTF::IterationStatus::Continue;
});

if (!sourceURL.isEmpty() and lineColumn.line > 0) {
OrdinalNumber originalLine = OrdinalNumber::fromOneBasedInt(lineColumn.line);
OrdinalNumber originalColumn = OrdinalNumber::fromOneBasedInt(lineColumn.column);

remappedFrame.position.line_zero_based = originalLine.zeroBasedInt();
remappedFrame.position.column_zero_based = originalColumn.zeroBasedInt();
remappedFrame.source_url = Bun::toStringRef(sourceURL);

Bun__remapStackFramePositions(globalObject, &remappedFrame, 1);

sourceURL = remappedFrame.source_url.toWTFString();
lineColumn.line = OrdinalNumber::fromZeroBasedInt(remappedFrame.position.line_zero_based).oneBasedInt();
lineColumn.column = OrdinalNumber::fromZeroBasedInt(remappedFrame.position.column_zero_based).oneBasedInt();
}

*outSourceID = sourceID;
*outLine = lineColumn.line;
*outColumn = lineColumn.column;
}
22 changes: 22 additions & 0 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6618,6 +6618,10 @@ pub const CallFrame = opaque {
};
}

pub fn arguments(self: *const CallFrame) []const JSValue {
// this presumably isn't allowed given that it doesn't exist
return self.argumentsPtr()[0..self.argumentsCount()];
}
pub fn arguments_old(self: *const CallFrame, comptime max: usize) Arguments(max) {
const len = self.argumentsCount();
const ptr = self.argumentsPtr();
Expand Down Expand Up @@ -6661,6 +6665,24 @@ pub const CallFrame = opaque {
}
return value;
}

extern fn Bun__CallFrame__getCallerSrcLoc(*const CallFrame, *JSGlobalObject, *c_uint, *c_uint, *c_uint) void;
pub const CallerSrcLoc = struct {
source_file_id: c_uint,
line: c_uint,
column: c_uint,
};
pub fn getCallerSrcLoc(call_frame: *const CallFrame, globalThis: *JSGlobalObject) CallerSrcLoc {
var source_id: c_uint = undefined;
var line: c_uint = undefined;
var column: c_uint = undefined;
Bun__CallFrame__getCallerSrcLoc(call_frame, globalThis, &source_id, &line, &column);
return .{
.source_file_id = source_id,
.line = line,
.column = column,
};
}
};

pub const EncodedJSValue = extern union {
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/event_loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ pub const EventLoop = struct {
}
}

this.processGCTimer();
this.processGCTimer();
loop.tick();

Expand Down
Loading

0 comments on commit bcf023c

Please sign in to comment.