Compare commits

...

6 Commits

Author SHA1 Message Date
Claude Bot
5088b0e6f4 Fix Bun.deepMatch to properly compare Map and Set instances
Previously, Bun.deepMatch would always return true when comparing
Map and Set instances regardless of their contents or sizes, because
it treated them as regular objects and used getPropertyNames() which
doesn't enumerate Map/Set entries.

This fix adds special handling for JSMapType and JSSetType in the
Bun__deepMatch function to:
- Check that both objects are the same type (Map vs Map, Set vs Set)
- Compare their sizes first - if different, return false
- If sizes match, compare their actual contents using proper iterators

The implementation follows the same pattern used in Bun__deepEquals
but enforces strict equality rather than subset matching for Maps/Sets,
which matches the expected behavior described in the issue.

Fixes #15673

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-06 23:36:22 +00:00
pfg
3652008b0d Update bun:test diff (#21158)
Fixes #6229 (Fixes BAPI-655): 

|before|<img width="806" height="84" alt="image"
src="https://github.com/user-attachments/assets/6d6c8628-40a8-4950-a7a4-8a85ee07a302"
/>|
|-|-|
|after|<img width="802" height="87" alt="image"
src="https://github.com/user-attachments/assets/c336a626-2b08-469e-aa73-676f43a0f176"
/>|

Fixes #21498 (Fixes BAPI-2240), Fixes #10852 (Fixes BAPI-743):

|before|after|
|-|-|
|<img width="474" height="147" alt="image"
src="https://github.com/user-attachments/assets/bf2225de-a573-4672-a095-f9ff359ec86c"
/>|<img width="283" height="226" alt="image"
src="https://github.com/user-attachments/assets/89cb0e45-b1b7-4dbb-9ddb-b9835baa4b74"
/>|
|<img width="279" height="176" alt="image"
src="https://github.com/user-attachments/assets/e9be7308-dc38-43d2-901c-c77ce4757a51"
/>|<img width="278" height="212" alt="image"
src="https://github.com/user-attachments/assets/8c29b385-a053-4606-9474-3e5c0e60278c"
/>|

Improves multiline string and long output

|before|after|
|-|-|
|<img width="537" height="897" alt="image"
src="https://github.com/user-attachments/assets/034800c5-ab22-4915-90d9-19831906bb2e"
/>|<img width="345" height="1016" alt="image"
src="https://github.com/user-attachments/assets/fa95339e-c136-4c7c-af94-5f11400836dd"
/>|

Improves long single line string output

|before|<img width="1903" height="191" alt="image"
src="https://github.com/user-attachments/assets/bae35c81-0566-4291-810e-e65dc0381aef"
/>|
|-|-|
|after|<img width="1905" height="123" alt="image"
src="https://github.com/user-attachments/assets/bf9f492a-1d52-4cfc-9b1b-c6544a072814"
/>|

Puts 'expected' before 'received' on object diffs. The new version
matches Jest and Vitest, and I find it more intuitive:

|before|after|
|-|-|
|<img width="344" height="221" alt="image"
src="https://github.com/user-attachments/assets/44d42655-c441-411e-9b67-c0db7a5dce08"
/>|<img width="342" height="293" alt="image"
src="https://github.com/user-attachments/assets/565e3934-a2a2-4f99-9d6f-b7df1905f933"
/>|

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-06 06:44:46 -07:00
pfg
7c65c35f8f Fix expect(() => { throw undefined; }).toThrow(TypeError) (#21637)
Fixes #19107
2025-08-06 06:39:25 -07:00
Jarred Sumner
455f3a65b9 enable mimalloc simd (#21644)
### What does this PR do?

### How did you verify your code works?
2025-08-06 06:38:34 -07:00
Meghan Denny
4d301cc3c4 deps: bump WebKit (#21647)
642e2252f6...75f6499360
2025-08-06 06:35:55 -07:00
Meghan Denny
e9dc25200a ci: increase mac test parallelism to 7 (#21530) 2025-08-05 23:17:18 -07:00
22 changed files with 5061 additions and 2547 deletions

View File

@@ -568,7 +568,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
agents: getTestAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10,
parallelism: unifiedTests ? undefined : os === "darwin" ? 7 : 10,
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
command:
os === "windows"

View File

@@ -274,6 +274,8 @@ src/bun.js/RuntimeTranspilerCache.zig
src/bun.js/SavedSourceMap.zig
src/bun.js/Strong.zig
src/bun.js/test/diff_format.zig
src/bun.js/test/diff/diff_match_patch.zig
src/bun.js/test/diff/printDiff.zig
src/bun.js/test/expect.zig
src/bun.js/test/jest.zig
src/bun.js/test/pretty_format.zig
@@ -511,7 +513,6 @@ src/defines.zig
src/deps/boringssl.translated.zig
src/deps/brotli_c.zig
src/deps/c_ares.zig
src/deps/diffz/DiffMatchPatch.zig
src/deps/libdeflate.zig
src/deps/libuv.zig
src/deps/lol-html.zig

View File

@@ -64,6 +64,12 @@ if(ENABLE_VALGRIND)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_VALGRIND=ON)
endif()
# Enable SIMD optimizations when not building for baseline (older CPUs)
if(NOT ENABLE_BASELINE)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_ARCH=ON)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_SIMD=ON)
endif()
if(DEBUG)
if (ENABLE_ASAN)
set(MIMALLOC_LIBRARY mimalloc-asan-debug)

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 642e2252f6298387edb6d2f991a0408fd0320466)
set(WEBKIT_VERSION 75f6499360f42d580c406f4969689a1e14b46447)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)

View File

@@ -82,6 +82,10 @@ function getNodeParallelTestTimeout(testPath) {
return 10_000;
}
process.on("SIGTRAP", () => {
console.warn("Test runner received SIGTRAP. Doing nothing.");
});
const { values: options, positionals: filters } = parseArgs({
allowPositionals: true,
options: {

View File

@@ -1024,6 +1024,10 @@ pub const JSValue = enum(i64) {
extern fn JSC__JSValue__getClassName(this: JSValue, global: *JSGlobalObject, ret: *ZigString) void;
// TODO: absorb this into className()
pub fn getClassName(this: JSValue, global: *JSGlobalObject, ret: *ZigString) bun.JSError!void {
if (!this.isCell()) {
ret.* = ZigString.static("[not a class]").*;
return;
}
return bun.jsc.fromJSHostCallGeneric(global, @src(), JSC__JSValue__getClassName, .{ this, global, ret });
}

View File

@@ -1599,6 +1599,104 @@ bool Bun__deepMatch(
JSObject* obj = objValue.getObject();
JSObject* subsetObj = subsetValue.getObject();
// Special handling for Maps and Sets
JSCell* objCell = objValue.asCell();
JSCell* subsetCell = subsetValue.asCell();
uint8_t objType = objCell->type();
uint8_t subsetType = subsetCell->type();
if (subsetType == JSSetType) {
if (objType != JSSetType) {
return false;
}
JSSet* objSet = jsCast<JSSet*>(objCell);
JSSet* subsetSet = jsCast<JSSet*>(subsetCell);
// For Maps and Sets, require exact equality (same size and contents)
// This matches the behavior expected in the issue
if (objSet->size() != subsetSet->size()) {
return false;
}
// All elements in subset should exist in obj
auto iter = JSSetIterator::create(globalObject, globalObject->setIteratorStructure(), subsetSet, IterationKind::Keys);
RETURN_IF_EXCEPTION(throwScope, false);
JSValue key;
while (iter->next(globalObject, key)) {
bool has = objSet->has(globalObject, key);
RETURN_IF_EXCEPTION(throwScope, false);
if (has) {
continue;
}
// We couldn't find the key in the target set. This may be a false positive due to how
// JSValues are represented in JSC, so we need to fall back to a linear search to be sure.
auto objIter = JSSetIterator::create(globalObject, globalObject->setIteratorStructure(), objSet, IterationKind::Keys);
RETURN_IF_EXCEPTION(throwScope, false);
JSValue objKey;
bool foundMatchingKey = false;
while (objIter->next(globalObject, objKey)) {
bool equal = JSC::sameValue(globalObject, key, objKey);
RETURN_IF_EXCEPTION(throwScope, false);
if (equal) {
foundMatchingKey = true;
break;
}
}
if (!foundMatchingKey) {
return false;
}
}
return true;
} else if (subsetType == JSMapType) {
if (objType != JSMapType) {
return false;
}
JSMap* objMap = jsCast<JSMap*>(objCell);
JSMap* subsetMap = jsCast<JSMap*>(subsetCell);
// For Maps and Sets, require exact equality (same size and contents)
// This matches the behavior expected in the issue
if (objMap->size() != subsetMap->size()) {
return false;
}
// All key-value pairs in subset should exist in obj
auto iter = JSMapIterator::create(globalObject, globalObject->mapIteratorStructure(), subsetMap, IterationKind::Entries);
RETURN_IF_EXCEPTION(throwScope, false);
JSValue subsetKey, subsetValue;
while (iter->nextKeyValue(globalObject, subsetKey, subsetValue)) {
JSValue objValue = objMap->get(globalObject, subsetKey);
RETURN_IF_EXCEPTION(throwScope, false);
if (objValue.isUndefined()) {
// We couldn't find the key in the target map. This may be a false positive due to
// how JSValues are represented in JSC, so we need to fall back to a linear search
// to be sure.
auto objIter = JSMapIterator::create(globalObject, globalObject->mapIteratorStructure(), objMap, IterationKind::Entries);
RETURN_IF_EXCEPTION(throwScope, false);
JSValue objKey;
bool foundMatchingKey = false;
while (objIter->nextKeyValue(globalObject, objKey, objValue)) {
bool keysEqual = JSC::sameValue(globalObject, subsetKey, objKey);
RETURN_IF_EXCEPTION(throwScope, false);
if (keysEqual) {
foundMatchingKey = true;
break;
}
}
if (!foundMatchingKey) {
return false;
}
// Compare both values below.
}
bool valuesEqual = JSC::sameValue(globalObject, subsetValue, objValue);
RETURN_IF_EXCEPTION(throwScope, false);
if (!valuesEqual) {
return false;
}
}
return true;
}
PropertyNameArray subsetProps(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
subsetObj->getPropertyNames(globalObject, subsetProps, DontEnumPropertiesMode::Exclude);
RETURN_IF_EXCEPTION(throwScope, false);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,541 @@
//! Tested in test/js/bun/test/printing/diffexample.test.ts. If modified, the snapshots will need to be updated.
const DMP = diff_match_patch.DMP(u8);
const DMPUsize = diff_match_patch.DMP(usize);
const Mode = enum {
bg_always,
bg_diff_only,
fg,
fg_diff,
};
const mode: Mode = .bg_diff_only;
pub const DiffConfig = struct {
min_bytes_before_chunking: usize,
chunk_context_lines: usize,
enable_ansi_colors: bool,
truncate_threshold: usize,
truncate_context: usize,
pub fn default(is_agent: bool, enable_ansi_colors: bool) DiffConfig {
return .{
.min_bytes_before_chunking = if (is_agent) 0 else 2 * 1024, // 2kb
.chunk_context_lines = if (is_agent) 1 else 5,
.enable_ansi_colors = enable_ansi_colors,
.truncate_threshold = if (is_agent) 1 * 1024 else 2 * 1024, // 2kb
.truncate_context = if (is_agent) 50 else 100,
};
}
};
fn removeTrailingNewline(text: []const u8) []const u8 {
if (!std.mem.endsWith(u8, text, "\n")) return text;
return text[0 .. text.len - 1];
}
pub fn printDiffMain(arena: std.mem.Allocator, not: bool, received_slice: []const u8, expected_slice: []const u8, writer: anytype, config: DiffConfig) !void {
if (not) {
switch (config.enable_ansi_colors) {
true => try writer.print("Expected: not " ++ colors.red ++ "{s}" ++ colors.reset, .{expected_slice}),
false => try writer.print("Expected: not {s}", .{expected_slice}),
}
return;
}
// check if the diffs are single-line
if (std.mem.indexOfScalar(u8, received_slice, '\n') == null and std.mem.indexOfScalar(u8, expected_slice, '\n') == null) {
try printModifiedSegment(.{
.removed = expected_slice,
.inserted = received_slice,
.mode = .modified,
}, arena, writer, config, .{ .single_line = true });
return;
}
var dmp = DMPUsize.default;
dmp.config.diff_timeout = 200;
const linesToChars = try DMP.diffLinesToChars(arena, expected_slice, received_slice);
const charDiffs = try dmp.diff(arena, linesToChars.chars_1, linesToChars.chars_2, false);
const diffs = try DMP.diffCharsToLines(arena, &charDiffs, linesToChars.line_array.items);
var diff_segments = std.ArrayList(DiffSegment).init(arena);
for (diffs.items) |diff| {
if (diff.operation == .delete) {
try diff_segments.append(DiffSegment{
.removed = diff.text,
.inserted = "",
.mode = .removed,
});
} else if (diff.operation == .insert) {
if (diff_segments.items.len > 0 and diff_segments.items[diff_segments.items.len - 1].mode == .removed) {
diff_segments.items[diff_segments.items.len - 1].inserted = diff.text;
diff_segments.items[diff_segments.items.len - 1].mode = .modified;
} else {
try diff_segments.append(DiffSegment{
.removed = "",
.inserted = diff.text,
.mode = .inserted,
});
}
} else if (diff.operation == .equal) {
try diff_segments.append(DiffSegment{
.removed = diff.text,
.inserted = diff.text,
.mode = .equal,
});
}
}
// trim all segments except the last one
if (diff_segments.items.len > 0) for (diff_segments.items[0 .. diff_segments.items.len - 1]) |*diff_segment| {
diff_segment.removed = removeTrailingNewline(diff_segment.removed);
diff_segment.inserted = removeTrailingNewline(diff_segment.inserted);
};
// Determine if the diff needs to be chunked
if (expected_slice.len > config.min_bytes_before_chunking or received_slice.len > config.min_bytes_before_chunking) {
// Split 'equal' segments into lines
var new_diff_segments = std.ArrayList(DiffSegment).init(arena);
for (diff_segments.items) |diff_segment| {
if (diff_segment.mode == .equal) {
var split = std.mem.splitScalar(u8, diff_segment.removed, '\n');
while (split.next()) |line| {
try new_diff_segments.append(DiffSegment{
.removed = line,
.inserted = line,
.mode = .equal,
.skip = true,
});
}
} else {
try new_diff_segments.append(diff_segment);
}
}
diff_segments = new_diff_segments;
// Forward pass: unskip segments after non-equal segments
for (diff_segments.items, 0..) |segment, i| {
if (segment.mode != .equal) {
const end = @min(i +| config.chunk_context_lines +| 1, diff_segments.items.len);
for (diff_segments.items[i..end]) |*seg| {
seg.skip = false;
}
}
}
{
// Reverse pass: unskip segments before non-equal segments
var i = diff_segments.items.len;
while (i > 0) {
i -= 1;
const segment = diff_segments.items[i];
if (segment.mode != .equal) {
const start = i -| config.chunk_context_lines;
for (diff_segments.items[start .. i + 1]) |*seg| {
seg.skip = false;
}
}
}
}
}
// fill removed_line_count and inserted_line_count
for (diff_segments.items) |*segment| {
for (segment.removed) |char| if (char == '\n') {
segment.removed_line_count += 1;
};
segment.removed_line_count += 1;
for (segment.inserted) |char| if (char == '\n') {
segment.inserted_line_count += 1;
};
segment.inserted_line_count += 1;
}
try printDiff(arena, writer, diff_segments.items, config);
}
pub const Diff = struct {
pub const Operation = enum {
insert,
delete,
equal,
};
operation: Operation,
text: []const u8,
};
const colors = struct {
const red = "\x1b[31m";
const green = "\x1b[32m";
const yellow = "\x1b[33m";
const invert = "\x1b[7m";
const underline = "\x1b[4m";
const dim = "\x1b[2m";
const white = "\x1b[97m";
const reset = "\x1b[0m";
};
const prefix_styles = struct {
const inserted = PrefixStyle{
.msg = "+ ",
.color = colors.red,
};
const removed = PrefixStyle{
.msg = "- ",
.color = colors.green,
};
const equal = PrefixStyle{
.msg = " ",
.color = "",
};
const single_line_inserted = PrefixStyle{
.msg = "Received: ",
.color = "",
};
const single_line_removed = PrefixStyle{
.msg = "Expected: ",
.color = "",
};
};
const base_styles = struct {
const red_bg_inserted = Style{
.prefix = prefix_styles.inserted,
.text_color = colors.red ++ colors.invert,
};
const green_bg_removed = Style{
.prefix = prefix_styles.removed,
.text_color = colors.green ++ colors.invert,
};
const dim_equal = Style{
.prefix = prefix_styles.equal,
.text_color = colors.dim,
};
const red_fg_inserted = Style{
.prefix = prefix_styles.inserted,
.text_color = colors.red,
};
const green_fg_removed = Style{
.prefix = prefix_styles.removed,
.text_color = colors.green,
};
const dim_inserted = Style{
.prefix = prefix_styles.inserted,
.text_color = colors.dim,
};
const dim_removed = Style{
.prefix = prefix_styles.removed,
.text_color = colors.dim,
};
};
const styles = switch (mode) {
.bg_always => struct {
const inserted_line = base_styles.red_bg_inserted;
const removed_line = base_styles.green_bg_removed;
const inserted_diff = base_styles.red_bg_inserted;
const removed_diff = base_styles.green_bg_removed;
const equal = base_styles.dim_equal;
const inserted_equal = base_styles.red_fg_inserted;
const removed_equal = base_styles.green_fg_removed;
},
.bg_diff_only => struct {
const inserted_line = base_styles.red_fg_inserted;
const removed_line = base_styles.green_fg_removed;
const inserted_diff = base_styles.red_bg_inserted;
const removed_diff = base_styles.green_bg_removed;
const equal = base_styles.dim_equal;
const inserted_equal = base_styles.red_fg_inserted;
const removed_equal = base_styles.green_fg_removed;
},
.fg => struct {
const inserted_line = base_styles.red_fg_inserted;
const removed_line = base_styles.green_fg_removed;
const equal = base_styles.dim_equal;
const inserted_equal = base_styles.red_fg_inserted;
const removed_equal = base_styles.green_fg_removed;
},
.fg_diff => struct {
const inserted_line = base_styles.red_fg_inserted;
const removed_line = base_styles.green_fg_removed;
const inserted_diff = base_styles.red_fg_inserted;
const removed_diff = base_styles.green_fg_removed;
const equal = base_styles.dim_equal;
const inserted_equal = base_styles.dim_inserted;
const removed_equal = base_styles.dim_removed;
},
};
pub const DiffSegment = struct {
removed: []const u8,
inserted: []const u8,
mode: enum {
equal,
removed,
inserted,
modified,
},
removed_line_count: usize = 0,
inserted_line_count: usize = 0,
skip: bool = false,
};
fn printDiffFooter(writer: anytype, config: DiffConfig, removed_diff_lines: usize, inserted_diff_lines: usize) !void {
if (config.enable_ansi_colors) try writer.writeAll(styles.removed_line.prefix.color);
try writer.writeAll(styles.removed_line.prefix.msg);
try writer.writeAll("Expected");
try writer.print(" {s}{d}", .{ styles.removed_line.prefix.msg, removed_diff_lines });
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
try writer.writeAll("\n");
if (config.enable_ansi_colors) try writer.writeAll(styles.inserted_line.prefix.color);
try writer.writeAll(styles.inserted_line.prefix.msg);
try writer.writeAll("Received");
try writer.print(" {s}{d}", .{ styles.inserted_line.prefix.msg, inserted_diff_lines });
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
}
const PrefixStyle = struct {
msg: []const u8,
color: []const u8,
};
const Style = struct {
prefix: PrefixStyle,
text_color: []const u8,
};
fn printLinePrefix(
writer: anytype,
config: DiffConfig,
prefix: PrefixStyle,
) !void {
if (config.enable_ansi_colors) try writer.writeAll(prefix.color);
try writer.writeAll(prefix.msg);
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
}
fn printTruncatedLine(
line: []const u8,
writer: anytype,
config: DiffConfig,
style: Style,
) !void {
if (line.len <= config.truncate_threshold or line.len <= config.truncate_context * 2) {
if (config.enable_ansi_colors) try writer.writeAll(style.text_color);
try writer.writeAll(line);
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
return;
}
// Line is too long, truncate it.
if (config.enable_ansi_colors) try writer.writeAll(style.text_color);
try writer.writeAll(line[0..config.truncate_context]);
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
if (config.enable_ansi_colors) try writer.writeAll(colors.white);
// The context is shown on both sides, so we truncate line.len - 2 * context
try writer.print("... ({} bytes truncated) ...", .{line.len - 2 * config.truncate_context});
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
if (config.enable_ansi_colors) try writer.writeAll(style.text_color);
try writer.writeAll(line[line.len - config.truncate_context ..]);
if (config.enable_ansi_colors) try writer.writeAll(colors.reset);
}
fn printSegment(
text: []const u8,
writer: anytype,
config: DiffConfig,
style: Style,
) !void {
var lines = std.mem.splitScalar(u8, text, '\n');
try printTruncatedLine(lines.next().?, writer, config, style);
while (lines.next()) |line| {
try writer.writeAll("\n");
try printLinePrefix(writer, config, style.prefix);
try printTruncatedLine(line, writer, config, style);
}
}
fn printModifiedSegmentWithoutDiffdiff(
writer: anytype,
config: DiffConfig,
segment: DiffSegment,
modified_style: ModifiedStyle,
) !void {
const removed_prefix = switch (modified_style.single_line) {
true => prefix_styles.single_line_removed,
false => prefix_styles.removed,
};
const inserted_prefix = switch (modified_style.single_line) {
true => prefix_styles.single_line_inserted,
false => prefix_styles.inserted,
};
try printLinePrefix(writer, config, removed_prefix);
try printSegment(segment.removed, writer, config, styles.removed_line);
try writer.writeAll("\n");
try printLinePrefix(writer, config, inserted_prefix);
try printSegment(segment.inserted, writer, config, styles.inserted_line);
if (!modified_style.single_line) try writer.writeAll("\n");
}
const ModifiedStyle = struct {
single_line: bool,
};
fn printModifiedSegment(
segment: DiffSegment,
arena: std.mem.Allocator,
writer: anytype,
config: DiffConfig,
modified_style: ModifiedStyle,
) !void {
const removed_prefix = switch (modified_style.single_line) {
true => prefix_styles.single_line_removed,
false => prefix_styles.removed,
};
const inserted_prefix = switch (modified_style.single_line) {
true => prefix_styles.single_line_inserted,
false => prefix_styles.inserted,
};
if (mode == .fg) {
return printModifiedSegmentWithoutDiffdiff(writer, config, segment, modified_style);
}
var char_diff = try DMP.default.diff(arena, segment.removed, segment.inserted, true);
try DMP.diffCleanupSemantic(arena, &char_diff);
var deleted_highlighted_length: usize = 0;
var inserted_highlighted_length: usize = 0;
var unhighlighted_length: usize = 0;
for (char_diff.items) |item| {
switch (item.operation) {
.delete => deleted_highlighted_length += item.text.len,
.insert => inserted_highlighted_length += item.text.len,
.equal => unhighlighted_length += item.text.len,
}
}
if ((deleted_highlighted_length > 10 and deleted_highlighted_length > segment.removed.len / 3 * 2) or (inserted_highlighted_length > 10 and inserted_highlighted_length > segment.inserted.len / 3 * 2)) {
// the diff is too significant (more than 2/3 of the original text on one side is modified), so skip printing the second layer of diffs.
return printModifiedSegmentWithoutDiffdiff(writer, config, segment, modified_style);
}
const is_valid_utf_8 = for (char_diff.items) |item| {
if (!bun.strings.isValidUTF8(item.text)) {
break false;
}
} else true;
if (!is_valid_utf_8) {
// utf-8 was cut up, so skip printing the second layer of diffs. ideally we would update the diff cleanup to handle this case instead.
return printModifiedSegmentWithoutDiffdiff(writer, config, segment, modified_style);
}
try printLinePrefix(writer, config, removed_prefix);
for (char_diff.items) |item| {
switch (item.operation) {
.delete => try printSegment(item.text, writer, config, styles.removed_diff),
.insert => {},
.equal => try printSegment(item.text, writer, config, styles.removed_equal),
}
}
try writer.writeAll("\n");
try printLinePrefix(writer, config, inserted_prefix);
for (char_diff.items) |item| {
switch (item.operation) {
.delete => {},
.insert => try printSegment(item.text, writer, config, styles.inserted_diff),
.equal => try printSegment(item.text, writer, config, styles.inserted_equal),
}
}
if (!modified_style.single_line) try writer.writeAll("\n");
}
pub fn printHunkHeader(writer: anytype, config: DiffConfig, original_line_number: usize, original_line_count: usize, changed_line_number: usize, changed_line_count: usize) !void {
if (config.enable_ansi_colors) {
try writer.print("{s}@@ -{},{} +{},{} @@{s}\n", .{ colors.yellow, original_line_number, original_line_count, changed_line_number, changed_line_count, colors.reset });
} else {
try writer.print("@@ -{},{} +{},{} @@\n", .{ original_line_number, original_line_count, changed_line_number, changed_line_count });
}
}
pub fn printDiff(
arena: std.mem.Allocator,
writer: anytype,
diff_segments: []const DiffSegment,
config: DiffConfig,
) !void {
var removed_line_number: usize = 1;
var inserted_line_number: usize = 1;
var removed_diff_lines: usize = 0;
var inserted_diff_lines: usize = 0;
const has_skipped_segments = for (diff_segments) |seg| {
if (seg.skip) break true;
} else false;
var was_skipped = false;
for (diff_segments, 0..) |segment, i| {
defer {
removed_line_number += segment.removed_line_count;
inserted_line_number += segment.inserted_line_count;
}
if ((was_skipped and !segment.skip) or (has_skipped_segments and i == 0 and !segment.skip)) {
// have to calculate the length of the non-skipped segment
var original_line_count: usize = 0;
var changed_line_count: usize = 0;
for (diff_segments[i..]) |seg| {
if (seg.skip) break;
original_line_count += seg.removed_line_count;
changed_line_count += seg.inserted_line_count;
}
try printHunkHeader(writer, config, removed_line_number, original_line_count, inserted_line_number, changed_line_count);
was_skipped = false;
}
switch (segment.mode) {
.equal => {
if (segment.skip) {
was_skipped = true;
continue;
}
try printLinePrefix(writer, config, prefix_styles.equal);
try printSegment(segment.removed, writer, config, styles.equal);
try writer.writeAll("\n");
},
.removed => {
try printLinePrefix(writer, config, prefix_styles.removed);
try printSegment(segment.removed, writer, config, styles.removed_line);
try writer.writeAll("\n");
removed_diff_lines += segment.removed_line_count;
},
.inserted => {
try printLinePrefix(writer, config, prefix_styles.inserted);
try printSegment(segment.inserted, writer, config, styles.inserted_line);
try writer.writeAll("\n");
inserted_diff_lines += segment.inserted_line_count;
},
.modified => {
try printModifiedSegment(segment, arena, writer, config, .{ .single_line = false });
removed_diff_lines += segment.removed_line_count;
inserted_diff_lines += segment.inserted_line_count;
},
}
}
try writer.writeAll("\n");
try printDiffFooter(writer, config, removed_diff_lines, inserted_diff_lines);
}
const bun = @import("bun");
const diff_match_patch = @import("./diff_match_patch.zig");
const std = @import("std");

View File

@@ -7,69 +7,25 @@ pub const DiffFormatter = struct {
not: bool = false,
pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
var scope = bun.AllocationScope.init(default_allocator);
// defer scope.deinit(); // TODO: fix leaks
const allocator = scope.allocator();
const diff_config: DiffConfig = .default(Output.isAIAgent(), Output.enable_ansi_colors);
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});
}
},
}
}
try printDiffMain(allocator, this.not, received, expected, writer, diff_config);
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;
var received_buf = MutableString.init(allocator, 0) catch unreachable;
var expected_buf = MutableString.init(allocator, 0) catch unreachable;
defer {
received_buf.deinit();
expected_buf.deinit();
@@ -82,15 +38,13 @@ pub const DiffFormatter = struct {
const buf_writer = buffered_writer.writer();
const Writer = @TypeOf(buf_writer);
const fmt_options = ConsoleObject.FormatOptions{
const fmt_options = JestPrettyFormat.FormatOptions{
.enable_colors = false,
.add_newline = false,
.flush = false,
.ordered_properties = true,
.quote_strings = true,
.max_depth = 100,
};
ConsoleObject.format2(
JestPrettyFormat.format(
.Debug,
this.globalThis,
@as([*]const JSValue, @ptrCast(&received)),
@@ -104,7 +58,7 @@ pub const DiffFormatter = struct {
buffered_writer_.context = &expected_buf;
ConsoleObject.format2(
JestPrettyFormat.format(
.Debug,
this.globalThis,
@as([*]const JSValue, @ptrCast(&this.expected)),
@@ -117,175 +71,25 @@ pub const DiffFormatter = struct {
buffered_writer.flush() catch unreachable;
}
const received_slice = received_buf.slice();
const expected_slice = expected_buf.slice();
var received_slice = received_buf.slice();
var expected_slice = expected_buf.slice();
if (std.mem.startsWith(u8, received_slice, "\n")) received_slice = received_slice[1..];
if (std.mem.startsWith(u8, expected_slice, "\n")) expected_slice = expected_slice[1..];
if (std.mem.endsWith(u8, received_slice, "\n")) received_slice = received_slice[0 .. received_slice.len - 1];
if (std.mem.endsWith(u8, expected_slice, "\n")) expected_slice = expected_slice[0 .. expected_slice.len - 1];
if (this.not) {
const not_fmt = "Expected: not <green>{s}<r>";
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(not_fmt, true), .{expected_slice});
} else {
try writer.print(Output.prettyFmt(not_fmt, false), .{expected_slice});
}
return;
}
switch (received.determineDiffMethod(expected, this.globalThis)) {
.none => {
const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>";
var formatter = ConsoleObject.Formatter{ .globalThis = this.globalThis, .quote_strings = true };
defer formatter.deinit();
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(fmt, true), .{
expected.toFmt(&formatter),
received.toFmt(&formatter),
});
return;
}
try writer.print(Output.prettyFmt(fmt, true), .{
expected.toFmt(&formatter),
received.toFmt(&formatter),
});
return;
},
.character => {
var dmp = DiffMatchPatch.default;
dmp.diff_timeout = 200;
var diffs = try dmp.diff(default_allocator, received_slice, expected_slice, 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;
},
.line => {
var dmp = DiffMatchPatch.default;
dmp.diff_timeout = 200;
var diffs = try dmp.diffLines(default_allocator, received_slice, expected_slice);
defer diffs.deinit(default_allocator);
const equal_fmt = "<d> {s}<r>";
const delete_fmt = "<red>+ {s}<r>";
const insert_fmt = "<green>- {s}<r>";
var insert_count: usize = 0;
var delete_count: usize = 0;
for (diffs.items) |df| {
var prev: usize = 0;
var curr: usize = 0;
switch (df.operation) {
.equal => {
while (curr < df.text.len) {
if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) {
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text[prev .. curr + 1]});
} else {
try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text[prev .. curr + 1]});
}
prev = curr + 1;
}
curr += 1;
}
},
.insert => {
while (curr < df.text.len) {
if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) {
insert_count += 1;
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text[prev .. curr + 1]});
} else {
try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text[prev .. curr + 1]});
}
prev = curr + 1;
}
curr += 1;
}
},
.delete => {
while (curr < df.text.len) {
if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) {
delete_count += 1;
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text[prev .. curr + 1]});
} else {
try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text[prev .. curr + 1]});
}
prev = curr + 1;
}
curr += 1;
}
},
}
if (df.text[df.text.len - 1] != '\n') try writer.writeAll("\n");
}
if (Output.enable_ansi_colors) {
try writer.print(Output.prettyFmt("\n<green>- Expected - {d}<r>\n", true), .{insert_count});
try writer.print(Output.prettyFmt("<red>+ Received + {d}<r>", true), .{delete_count});
return;
}
try writer.print("\n- Expected - {d}\n", .{insert_count});
try writer.print("+ Received + {d}", .{delete_count});
return;
},
.word => {
// not implemented
// https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs#word-mode
},
}
return;
try printDiffMain(allocator, this.not, received_slice, expected_slice, writer, diff_config);
}
};
const string = []const u8;
const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig");
const std = @import("std");
const JestPrettyFormat = @import("./pretty_format.zig").JestPrettyFormat;
const printDiffFile = @import("./diff/printDiff.zig");
const DiffConfig = printDiffFile.DiffConfig;
const printDiffMain = printDiffFile.printDiffMain;
const bun = @import("bun");
const MutableString = bun.MutableString;
@@ -293,6 +97,5 @@ const Output = bun.Output;
const default_allocator = bun.default_allocator;
const jsc = bun.jsc;
const ConsoleObject = jsc.ConsoleObject;
const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue;

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,6 @@ describe("Bun.deepMatch", () => {
new Map([ ["foo", 1] ]),
new Map([ ["foo", 1] ]),
],
// Sets
[new Set(), new Set()],
[
@@ -100,39 +99,51 @@ describe("Bun.deepMatch", () => {
[[], [undefined]],
[["a", "b", "c"], ["a", "b", "d"]],
// Maps
// FIXME: I assume this is incorrect but I need confirmation on expected behavior.
// [
// new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
// new Map<number, number>([ [1, 2], [2, 3] ]),
// ],
// [
// new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
// new Map<number, number>([ [1, 2], [2, 3], [3, 4], [4, 5] ]),
// ],
// [
// new Map<number, number>([ [1, 2], [2, 3], [3, 4], [4, 5] ]),
// new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
// ],
// Maps - subset should not be contained in object
[
new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
new Map<number, number>([ [1, 2], [2, 3] ]),
],
[
new Map<number, number>([ [1, 2], [2, 3], [3, 4], [4, 5] ]),
new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
],
[
new Map([ ["foo", 1] ]),
new Map([ ["bar", 1] ]),
],
[
new Map([ ["foo", 1] ]),
new Map([ ["foo", 2] ]),
],
// Sets
// FIXME: I assume this is incorrect but I need confirmation on expected behavior.
// [
// new Set([1, 2, 3]),
// new Set([4, 5, 6]),
// ],
// [
// new Set([1, 2, 3]),
// new Set([1, 2]),
// ],
// [
// new Set([1, 2]),
// new Set([1, 2, 3]),
// ],
// [
// new Set(["a", "b", "c"]),
// new Set(["a", "b", "d"]),
// ],
// Maps with different sizes (even if one is subset of another)
[
new Map([ ["foo", 1] ]),
new Map([ ["foo", 1], ["bar", 2] ]),
],
[
new Map<number, number>([ [1, 2], [2, 3] ]),
new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
],
// Sets with different contents or sizes
[
new Set([1, 2, 3]),
new Set([4, 5, 6]),
],
[
new Set([1, 2, 3]),
new Set([1, 2]),
],
[
new Set([1, 2]),
new Set([1, 2, 3]),
],
[
new Set(["a", "b", "c"]),
new Set(["a", "b", "d"]),
],
])("Bun.deepMatch(%p, %p) === false", (a, b) => {
expect(Bun.deepMatch(a, b)).toBe(false);
});

View File

@@ -124,22 +124,6 @@ exports[`expect().toEqual() on objects with property indices doesn't print undef
"expect(received).toEqual(expected)
{
+ "0": 0,
+ "1": 1,
+ "10": 10,
+ "11": 11,
+ "12": 12,
+ "13": 13,
+ "14": 14,
+ "15": 15,
+ "2": 2,
+ "3": 3,
+ "4": 4,
+ "5": 5,
+ "6": 6,
+ "7": 7,
+ "8": 8,
+ "9": 9,
- "0": 123,
- "1": 123,
- "10": 123,
@@ -156,6 +140,22 @@ exports[`expect().toEqual() on objects with property indices doesn't print undef
- "7": 123,
- "8": 123,
- "9": 123,
+ "0": 0,
+ "1": 1,
+ "10": 10,
+ "11": 11,
+ "12": 12,
+ "13": 13,
+ "14": 14,
+ "15": 15,
+ "2": 2,
+ "3": 3,
+ "4": 4,
+ "5": 5,
+ "6": 6,
+ "7": 7,
+ "8": 8,
+ "9": 9,
}
- Expected - 16

View File

@@ -0,0 +1,109 @@
import { expect } from "bun:test";
try {
expect("a\nb\nc\n d\ne").toEqual("a\nd\nc\nd\ne");
} catch (e) {
console.log(e.message);
}
const a = {
age: 25,
name: "Alice",
logs: [
"Entered the building",
"Checked in at reception",
"Took elevator to floor 3",
"Attended morning meeting",
"Started working on project",
],
};
const b = {
age: 30,
name: "Bob",
logs: [
"Logged into system",
"Accessed dashboard",
"Reviewed daily reports",
"Updated project status",
"Sent status email to team",
"Scheduled follow-up meeting",
],
};
try {
expect(a).toEqual(b);
} catch (e) {
console.log(e.message);
}
const longInt32ArrayExpected = new Int32Array(100000);
const longInt32ArrayReceived = new Int32Array(100000);
for (let i = 0; i < 100000; i++) {
longInt32ArrayExpected[i] = i;
longInt32ArrayReceived[i] = i + 1;
}
try {
expect(longInt32ArrayReceived).toEqual(longInt32ArrayExpected);
} catch (e) {
console.log(e.message);
}
try {
expect("Hello 👋 世界 🌍").toEqual("Hello 👋 世界 🌎");
} catch (e) {
console.log(e.message);
}
try {
expect("Line 1: 你好\nLine 2: مرحبا\nLine 3: Здравствуйте").toEqual("Line 1: 你好\nLine 2: مرحبا\nLine 3: Привет");
} catch (e) {
console.log(e.message);
}
try {
expect({
emoji: "🔥💧🌊",
chinese: "测试字符串",
arabic: "اختبار",
mixed: "Hello 世界 🌍",
}).toEqual({
emoji: "🔥💧🌊",
chinese: "测试文本",
arabic: "اختبار",
mixed: "Hello 世界 🌎",
});
} catch (e) {
console.log(e.message);
}
try {
expect("café résumé naïve").toEqual("café resumé naive");
} catch (e) {
console.log(e.message);
}
try {
expect("© ® ™ £ € ¥ § ¶").toEqual("© ® ™ £ € ¥ § ¶");
} catch (e) {
console.log(e.message);
}
try {
expect("Línea 1: ñoño\nLínea 2: àèìòù\nLínea 3: äëïöü").toEqual("Línea 1: ñoño\nLínea 2: àèìòù\nLínea 3: aeiou");
} catch (e) {
console.log(e.message);
}
try {
expect({
french: "crème brûlée",
spanish: "niño español",
special: "½ ¼ ¾ ± × ÷",
}).toEqual({
french: "crème brulée",
spanish: "niño español",
special: "½ ¼ ¾ ± × ÷",
});
} catch (e) {
console.log(e.message);
}

View File

@@ -0,0 +1,298 @@
import { test, expect } from "bun:test";
test("example 1", () => {
expect("a\nb\nc\n d\ne").toEqual("a\nd\nc\nd\ne");
});
test("example 2", () => {
expect({
object1: "a",
object2: "b",
object3: "c\nd\ne",
}).toEqual({
object1: "a",
object2: " b",
object3: "c\nd",
});
});
test("example 3 - very long string with few changes", () => {
// Create a 1000 line string with only a few differences
const lines = Array.from({ length: 1000 }, (_, i) => `line ${i + 1}`);
const originalString = lines.join("\n");
// Create expected string with only a few changes
const expectedLines = [...lines];
expectedLines[499] = "line 500 - CHANGED"; // Change line 500
expectedLines[750] = "line 751 - MODIFIED"; // Change line 751
expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901
expectedLines.splice(100, 0, "line 101 - INSERTED");
const expectedString = expectedLines.join("\n");
expect(originalString).toEqual(expectedString);
});
test.todo("example 4 - ansi colors don't get printed to console", () => {
expect("\x1b[31mhello\x1b[0m").toEqual("\x1b[32mhello\x1b[0m");
});
test("example 12 - large multiline diff", () => {
const received = `line one
line two
line three!
line four
line five
!-!six
line seven
line eight
line ten
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
line 28!
line 29
line 30
line 31
line 32
line 33
line 34
line 35
line 36
line 37
line 38
line 39`;
const expected = `line one
line two
line three
line four
line five
line six
line seven
line eight
line nine (inserted only)
line ten
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
line 28
line 29
line 30
line 31
line 32
line 33
line 34
line 35
line 36
line 37
line 38
line 39`;
expect(received).toEqual(expected);
});
test("example 13 - simple multiline diff with sections", () => {
const received = `=== diffdiff ===
line one
line two!
line six
line seven
=== each line changed ===
line one?
line two
line three?
line four?
=== deleted ===
line one
line two
line three
line four
line five
line six
line seven
=== inserted ===
line one
line two
line six
line seven
=== inserted newline ===
line one
line two
line three
line four
line five
line six
line seven
=== has newline at end vs doesn't ===`;
const expected = `=== diffdiff ===
line one
line two
line three
line four
line five
line six
line seven
=== each line changed ===
line one
line two!
line three
line four!
=== deleted ===
line one
line two
line six
line seven
=== inserted ===
line one
line two
line three
line four
line five
line six
line seven
=== inserted newline ===
line one
line two
line three
line four
line five
line six
line seven
=== has newline at end vs doesn't ===
`;
expect(received).toEqual(expected);
});
test("example 14 - single line diff", () => {
const received = `"¡hello, world"`;
const expected = `"hello, world!"`;
expect(received).toEqual(expected);
});
test("example 15 - unicode char diff", () => {
const received = `Hello 👋 世界 🌎!`;
const expected = `Hello 👋 世界 🌍!`;
expect(received).toEqual(expected);
});
test("example 16 - indentation change diff", () => {
const received = `function main() {
if (true) {
print("Hello, world!");
print("Goodbye, world!");
}
}`;
const expected = `function main() {
print("Hello, world!");
print("Goodbye, world!");
}`;
expect(received).toEqual(expected);
});
test("example 17 - very long string", () => {
const receivedLines: string[] = [];
const expectedLines: string[] = [];
for (let i = 0; i < 1000; i++) {
if (i === 100) {
receivedLines.push(`line ${i} - inserted`);
expectedLines.push(`line ${i}`);
continue;
}
if (i === 200) {
receivedLines.push(`line ${i}`);
expectedLines.push(`line ${i} - deleted`);
continue;
}
if (i === 300) {
receivedLines.push(`line ${i} - modified`);
expectedLines.push(`modified - line ${i}`);
continue;
}
if (i === 400) {
receivedLines.push(`line ${i}`);
receivedLines.push(`extra line!`);
expectedLines.push(`line ${i}`);
continue;
}
receivedLines.push(`line ${i}`);
expectedLines.push(`line ${i}`);
}
// The Zig code adds a trailing newline to each string.
const receivedString = receivedLines.join("\n") + "\n";
const expectedString = expectedLines.join("\n") + "\n";
expect(receivedString).toEqual(expectedString);
});
test("example 18 - very long single line string", () => {
const expected = "a".repeat(1000000);
const received = "a".repeat(1000001);
expect(received).toEqual(expected);
});
test("not", () => {
expect("Hello, World!").not.toEqual("Hello, World!");
});
test("has end newline vs doesn't", () => {
expect("Hello, World!\n").toEqual("Hello, World!");
});
test("extremely float64array", () => {
const length = 10000;
const expected = new Float64Array(length);
const received = new Float64Array(length);
for (let i = 0; i < length; i++) {
expected[i] = i;
received[i] = i + 1;
}
expect(received).toEqual(expected);
});
test("completely different long value does not truncate", () => {
const length = 100;
const expected = new Int32Array(length);
const received = new Int32Array(length);
for (let i = 0; i < length; i++) {
expected[i] = i;
received[i] = length - i - 1;
}
expect(received).toEqual(expected);
});

View File

@@ -0,0 +1,848 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
function cleanOutput(output: string) {
return output
.replaceAll(/ \[[0-9\.]+ms\]/g, "")
.replaceAll(/at <anonymous> \(.*\)/g, "at <anonymous> (FILE:LINE)")
.replaceAll(
"test\\js\\bun\\test\\printing\\diffexample.fixture.ts:",
"test/js/bun/test/printing/diffexample.fixture.ts:",
);
}
function cleanAnsiEscapes(output: string) {
return output.replaceAll(/\x1B\[[0-9;]*m/g, "");
}
test("no color", async () => {
const noColorSpawn = Bun.spawn({
cmd: [bunExe(), "test", import.meta.dir + "/diffexample.fixture.ts"],
stdio: ["inherit", "pipe", "pipe"],
env: {
...bunEnv,
FORCE_COLOR: "0",
},
});
await noColorSpawn.exited;
const noColorStderr = cleanOutput(await noColorSpawn.stderr.text());
const noColorStdout = await noColorSpawn.stdout.text();
expect(noColorStderr).toMatchInlineSnapshot(`
"
test/js/bun/test/printing/diffexample.fixture.ts:
1 | import { test, expect } from "bun:test";
2 |
3 | test("example 1", () => {
4 | expect("a\\nb\\nc\\n d\\ne").toEqual("a\\nd\\nc\\nd\\ne");
^
error: expect(received).toEqual(expected)
"a
- d
+ b
c
- d
+ d
e"
- Expected - 2
+ Received + 2
at <anonymous> (FILE:LINE)
(fail) example 1
6 | test("example 2", () => {
7 | expect({
8 | object1: "a",
9 | object2: "b",
10 | object3: "c\\nd\\ne",
11 | }).toEqual({
^
error: expect(received).toEqual(expected)
{
"object1": "a",
- "object2": " b",
+ "object2": "b",
"object3":
"c
- d"
+ d
+ e"
,
}
- Expected - 2
+ Received + 3
at <anonymous> (FILE:LINE)
(fail) example 2
26 | expectedLines[750] = "line 751 - MODIFIED"; // Change line 751
27 | expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901
28 | expectedLines.splice(100, 0, "line 101 - INSERTED");
29 | const expectedString = expectedLines.join("\\n");
30 |
31 | expect(originalString).toEqual(expectedString);
^
error: expect(received).toEqual(expected)
@@ -96,11 +96,11 @@
line 96
line 97
line 98
line 99
line 100
- line 101 - INSERTED
line 101
line 102
line 103
line 104
line 105
@@ -496,11 +496,11 @@
line 495
line 496
line 497
line 498
line 499
- line 500 - CHANGED
+ line 500
line 501
line 502
line 503
line 504
line 505
@@ -747,11 +747,11 @@
line 746
line 747
line 748
line 749
line 750
- line 751 - MODIFIED
+ line 751
line 752
line 753
line 754
line 755
line 756
@@ -897,11 +897,11 @@
line 896
line 897
line 898
line 899
line 900
- line 901 - DIFFERENT
+ line 901
line 902
line 903
line 904
line 905
line 906
- Expected - 4
+ Received + 3
at <anonymous> (FILE:LINE)
(fail) example 3 - very long string with few changes
(todo) example 4 - ansi colors don't get printed to console
111 | line 35
112 | line 36
113 | line 37
114 | line 38
115 | line 39\`;
116 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
"line one
line two
- line three
+ line three!
line four
line five
- line six
+ !-!six
line seven
line eight
- line nine (inserted only)
line ten
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
- line 28
+ line 28!
line 29
line 30
line 31
line 32
line 33
line 34
line 35
line 36
line 37
line 38
line 39"
- Expected - 4
+ Received + 3
at <anonymous> (FILE:LINE)
(fail) example 12 - large multiline diff
194 | line six
195 | line seven
196 |
197 | === has newline at end vs doesn't ===
198 | \`;
199 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
"=== diffdiff ===
line one
- line two
- line three
- line four
- line five
+ line two!
line six
line seven
=== each line changed ===
- line one
- line two!
- line three
- line four!
+ line one?
+ line two
+ line three?
+ line four?
=== deleted ===
line one
line two
+ line three
+ line four
+ line five
line six
line seven
=== inserted ===
line one
line two
- line three
- line four
- line five
line six
line seven
=== inserted newline ===
line one
line two
-
line three
line four
line five
line six
line seven
- === has newline at end vs doesn't ===
- "
+ === has newline at end vs doesn't ==="
- Expected - 14
+ Received + 9
at <anonymous> (FILE:LINE)
(fail) example 13 - simple multiline diff with sections
200 | });
201 |
202 | test("example 14 - single line diff", () => {
203 | const received = \`"¡hello, world"\`;
204 | const expected = \`"hello, world!"\`;
205 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
Expected: ""hello, world!""
Received: ""¡hello, world""
at <anonymous> (FILE:LINE)
(fail) example 14 - single line diff
206 | });
207 |
208 | test("example 15 - unicode char diff", () => {
209 | const received = \`Hello 👋 世界 🌎!\`;
210 | const expected = \`Hello 👋 世界 🌍!\`;
211 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
Expected: "Hello 👋 世界 🌍!"
Received: "Hello 👋 世界 🌎!"
at <anonymous> (FILE:LINE)
(fail) example 15 - unicode char diff
220 | }\`;
221 | const expected = \`function main() {
222 | print("Hello, world!");
223 | print("Goodbye, world!");
224 | }\`;
225 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
"function main() {
- print("Hello, world!");
- print("Goodbye, world!");
+ if (true) {
+ print("Hello, world!");
+ print("Goodbye, world!");
+ }
}"
- Expected - 2
+ Received + 4
at <anonymous> (FILE:LINE)
(fail) example 16 - indentation change diff
256 | }
257 |
258 | // The Zig code adds a trailing newline to each string.
259 | const receivedString = receivedLines.join("\\n") + "\\n";
260 | const expectedString = expectedLines.join("\\n") + "\\n";
261 | expect(receivedString).toEqual(expectedString);
^
error: expect(received).toEqual(expected)
@@ -96,11 +96,11 @@
line 95
line 96
line 97
line 98
line 99
- line 100
+ line 100 - inserted
line 101
line 102
line 103
line 104
line 105
@@ -196,11 +196,11 @@
line 195
line 196
line 197
line 198
line 199
- line 200 - deleted
+ line 200
line 201
line 202
line 203
line 204
line 205
@@ -296,11 +296,11 @@
line 295
line 296
line 297
line 298
line 299
- modified - line 300
+ line 300 - modified
line 301
line 302
line 303
line 304
line 305
@@ -397,11 +397,11 @@
line 396
line 397
line 398
line 399
line 400
+ extra line!
line 401
line 402
line 403
line 404
line 405
- Expected - 3
+ Received + 4
at <anonymous> (FILE:LINE)
(fail) example 17 - very long string
262 | });
263 |
264 | test("example 18 - very long single line string", () => {
265 | const expected = "a".repeat(1000000);
266 | const received = "a".repeat(1000001);
267 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
Expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... (999801 bytes truncated) ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Received: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... (999801 bytes truncated) ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
at <anonymous> (FILE:LINE)
(fail) example 18 - very long single line string
266 | const received = "a".repeat(1000001);
267 | expect(received).toEqual(expected);
268 | });
269 |
270 | test("not", () => {
271 | expect("Hello, World!").not.toEqual("Hello, World!");
^
error: expect(received).not.toEqual(expected)
Expected: not "Hello, World!"
at <anonymous> (FILE:LINE)
(fail) not
270 | test("not", () => {
271 | expect("Hello, World!").not.toEqual("Hello, World!");
272 | });
273 |
274 | test("has end newline vs doesn't", () => {
275 | expect("Hello, World!\\n").toEqual("Hello, World!");
^
error: expect(received).toEqual(expected)
- "Hello, World!"
+ "Hello, World!
+ "
- Expected - 1
+ Received + 2
at <anonymous> (FILE:LINE)
(fail) has end newline vs doesn't
281 | const received = new Float64Array(length);
282 | for (let i = 0; i < length; i++) {
283 | expected[i] = i;
284 | received[i] = i + 1;
285 | }
286 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
@@ -1,7 +1,7 @@
Float64Array [
- 0,
1,
2,
3,
4,
5,
@@ -9997,7 +9997,7 @@
9995,
9996,
9997,
9998,
9999,
+ 10000,
]
- Expected - 1
+ Received + 1
at <anonymous> (FILE:LINE)
(fail) extremely float64array
292 | const received = new Int32Array(length);
293 | for (let i = 0; i < length; i++) {
294 | expected[i] = i;
295 | received[i] = length - i - 1;
296 | }
297 | expect(received).toEqual(expected);
^
error: expect(received).toEqual(expected)
Int32Array [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- 17,
- 18,
- 19,
- 20,
- 21,
- 22,
- 23,
- 24,
- 25,
- 26,
- 27,
- 28,
- 29,
- 30,
- 31,
- 32,
- 33,
- 34,
- 35,
- 36,
- 37,
- 38,
- 39,
- 40,
- 41,
- 42,
- 43,
- 44,
- 45,
- 46,
- 47,
- 48,
- 49,
- 50,
- 51,
- 52,
- 53,
- 54,
- 55,
- 56,
- 57,
- 58,
- 59,
- 60,
- 61,
- 62,
- 63,
- 64,
- 65,
- 66,
- 67,
- 68,
- 69,
- 70,
- 71,
- 72,
- 73,
- 74,
- 75,
- 76,
- 77,
- 78,
- 79,
- 80,
- 81,
- 82,
- 83,
- 84,
- 85,
- 86,
- 87,
- 88,
- 89,
- 90,
- 91,
- 92,
- 93,
- 94,
- 95,
- 96,
- 97,
- 98,
99,
+ 98,
+ 97,
+ 96,
+ 95,
+ 94,
+ 93,
+ 92,
+ 91,
+ 90,
+ 89,
+ 88,
+ 87,
+ 86,
+ 85,
+ 84,
+ 83,
+ 82,
+ 81,
+ 80,
+ 79,
+ 78,
+ 77,
+ 76,
+ 75,
+ 74,
+ 73,
+ 72,
+ 71,
+ 70,
+ 69,
+ 68,
+ 67,
+ 66,
+ 65,
+ 64,
+ 63,
+ 62,
+ 61,
+ 60,
+ 59,
+ 58,
+ 57,
+ 56,
+ 55,
+ 54,
+ 53,
+ 52,
+ 51,
+ 50,
+ 49,
+ 48,
+ 47,
+ 46,
+ 45,
+ 44,
+ 43,
+ 42,
+ 41,
+ 40,
+ 39,
+ 38,
+ 37,
+ 36,
+ 35,
+ 34,
+ 33,
+ 32,
+ 31,
+ 30,
+ 29,
+ 28,
+ 27,
+ 26,
+ 25,
+ 24,
+ 23,
+ 22,
+ 21,
+ 20,
+ 19,
+ 18,
+ 17,
+ 16,
+ 15,
+ 14,
+ 13,
+ 12,
+ 11,
+ 10,
+ 9,
+ 8,
+ 7,
+ 6,
+ 5,
+ 4,
+ 3,
+ 2,
+ 1,
+ 0,
]
- Expected - 99
+ Received + 99
at <anonymous> (FILE:LINE)
(fail) completely different long value does not truncate
0 pass
1 todo
14 fail
14 expect() calls
Ran 15 tests across 1 file.
"
`);
expect(noColorSpawn.exitCode).toBe(1);
const colorSpawn = Bun.spawn({
cmd: [bunExe(), "test", import.meta.dir + "/diffexample.fixture.ts"],
stdio: ["inherit", "pipe", "pipe"],
env: {
...bunEnv,
FORCE_COLOR: "0",
},
});
await colorSpawn.exited;
const colorStderr = cleanOutput(cleanAnsiEscapes(await colorSpawn.stderr.text()));
const colorStdout = cleanAnsiEscapes(await colorSpawn.stdout.text());
expect(colorStderr).toEqual(noColorStderr);
expect(colorStdout).toEqual(noColorStdout);
});
function getDiffPart(stderr: string): string {
stderr = stderr.split("a\\nd\\nc\\nd\\ne")[1];
const split = stderr.split("\n\n");
split.pop();
stderr = split.join("\n\n");
return stderr;
}
test("color", async () => {
const spawn = Bun.spawn({
cmd: [bunExe(), import.meta.dir + "/diffexample-color.fixture.ts"],
stdio: ["inherit", "pipe", "pipe"],
env: {
...bunEnv,
FORCE_COLOR: "1",
},
});
await spawn.exited;
const stderr = await spawn.stderr.text();
expect(stderr).toMatchInlineSnapshot(`""`);
expect(await spawn.stdout.text()).toMatchInlineSnapshot(`
"\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m"a\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m\x1B[7md\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m\x1B[7mb\x1B[0m
\x1B[0m\x1B[2mc\x1B[0m
\x1B[32m- \x1B[0m\x1B[32md\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m\x1B[7m \x1B[0m\x1B[31md\x1B[0m
\x1B[0m\x1B[2me"\x1B[0m
\x1B[32m- Expected - 2\x1B[0m
\x1B[31m+ Received + 2\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m{\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "age": \x1B[0m\x1B[32m\x1B[7m30\x1B[0m\x1B[32m,\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "age": \x1B[0m\x1B[31m\x1B[7m25\x1B[0m\x1B[31m,\x1B[0m
\x1B[0m\x1B[2m "logs": [\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Logged into system",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Accessed dashboard",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Reviewed daily reports",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Updated project status",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Sent status email to team",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "Scheduled follow-up meeting",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "Entered the building",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "Checked in at reception",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "Took elevator to floor 3",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "Attended morning meeting",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "Started working on project",\x1B[0m
\x1B[0m\x1B[2m ],\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "name": "\x1B[0m\x1B[32m\x1B[7mBob\x1B[0m\x1B[32m",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "name": "\x1B[0m\x1B[31m\x1B[7mAlice\x1B[0m\x1B[31m",\x1B[0m
\x1B[0m\x1B[2m}\x1B[0m
\x1B[32m- Expected - 8\x1B[0m
\x1B[31m+ Received + 7\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[33m@@ -1,7 +1,7 @@\x1B[0m
\x1B[0m\x1B[2mInt32Array [\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m 0,\x1B[0m
\x1B[0m\x1B[2m 1,\x1B[0m
\x1B[0m\x1B[2m 2,\x1B[0m
\x1B[0m\x1B[2m 3,\x1B[0m
\x1B[0m\x1B[2m 4,\x1B[0m
\x1B[0m\x1B[2m 5,\x1B[0m
\x1B[33m@@ -99997,7 +99997,7 @@\x1B[0m
\x1B[0m\x1B[2m 99995,\x1B[0m
\x1B[0m\x1B[2m 99996,\x1B[0m
\x1B[0m\x1B[2m 99997,\x1B[0m
\x1B[0m\x1B[2m 99998,\x1B[0m
\x1B[0m\x1B[2m 99999,\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m 100000,\x1B[0m
\x1B[0m\x1B[2m]\x1B[0m
\x1B[32m- Expected - 1\x1B[0m
\x1B[31m+ Received + 1\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
Expected: \x1B[0m\x1B[32m"Hello 👋 世界 🌎"\x1B[0m
Received: \x1B[0m\x1B[31m"Hello 👋 世界 🌍"\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m"Line 1: 你好\x1B[0m
\x1B[0m\x1B[2mLine 2: مرحبا\x1B[0m
\x1B[32m- \x1B[0m\x1B[32mLine 3: Привет"\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31mLine 3: Здравствуйте"\x1B[0m
\x1B[32m- Expected - 1\x1B[0m
\x1B[31m+ Received + 1\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m{\x1B[0m
\x1B[0m\x1B[2m "arabic": "اختبار",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "chinese": "测试\x1B[0m\x1B[32m\x1B[7m文本\x1B[0m\x1B[32m",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "chinese": "测试\x1B[0m\x1B[31m\x1B[7m字符串\x1B[0m\x1B[31m",\x1B[0m
\x1B[0m\x1B[2m "emoji": "🔥💧🌊",\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "mixed": "Hello 世界 🌎",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "mixed": "Hello 世界 🌍",\x1B[0m
\x1B[0m\x1B[2m}\x1B[0m
\x1B[32m- Expected - 2\x1B[0m
\x1B[31m+ Received + 2\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
Expected: \x1B[0m\x1B[32m"café r\x1B[0m\x1B[32m\x1B[7me\x1B[0m\x1B[32msumé na\x1B[0m\x1B[32m\x1B[7mi\x1B[0m\x1B[32mve"\x1B[0m
Received: \x1B[0m\x1B[31m"café r\x1B[0m\x1B[31m\x1B[7mé\x1B[0m\x1B[31msumé na\x1B[0m\x1B[31m\x1B[7mï\x1B[0m\x1B[31mve"\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m"Línea 1: ñoño\x1B[0m
\x1B[0m\x1B[2mLínea 2: àèìòù\x1B[0m
\x1B[32m- \x1B[0m\x1B[32mLínea 3: \x1B[0m\x1B[32m\x1B[7maeiou\x1B[0m\x1B[32m"\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31mLínea 3: \x1B[0m\x1B[31m\x1B[7mäëïöü\x1B[0m\x1B[31m"\x1B[0m
\x1B[32m- Expected - 1\x1B[0m
\x1B[31m+ Received + 1\x1B[0m
\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoEqual\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
\x1B[0m\x1B[2m{\x1B[0m
\x1B[32m- \x1B[0m\x1B[32m "french": "crème br\x1B[0m\x1B[32m\x1B[7mu\x1B[0m\x1B[32mlée",\x1B[0m
\x1B[31m+ \x1B[0m\x1B[31m "french": "crème br\x1B[0m\x1B[31m\x1B[7mû\x1B[0m\x1B[31mlée",\x1B[0m
\x1B[0m\x1B[2m "spanish": "niño español",\x1B[0m
\x1B[0m\x1B[2m "special": "½ ¼ ¾ ± × ÷",\x1B[0m
\x1B[0m\x1B[2m}\x1B[0m
\x1B[32m- Expected - 1\x1B[0m
\x1B[31m+ Received + 1\x1B[0m
"
`);
expect(spawn.exitCode).toBe(0);
});
/*
issue:
in inline snapshot diffing, it is printing the color codes
*/

View File

@@ -607,19 +607,3 @@ exports[`snapshot numbering 4`] = `"snap"`;
exports[`snapshot numbering 6`] = `"hello"`;
exports[`snapshot numbering: hinted 1`] = `"hello"`;
exports[`indented inline snapshots 4`] = `
"\x1B[2mexpect(\x1B[0m\x1B[31mreceived\x1B[0m\x1B[2m).\x1B[0mtoMatchInlineSnapshot\x1B[2m(\x1B[0m\x1B[32mexpected\x1B[0m\x1B[2m)\x1B[0m
Expected: \x1B[2m
\x1B[0m\x1B[32m \x1B[0m\x1B[2m{
\x1B[0m\x1B[32m \x1B[0m\x1B[2m"a": 2,
\x1B[0m\x1B[32m \x1B[0m\x1B[2m}
\x1B[0m
Received: \x1B[2m
\x1B[0m\x1B[2m{
\x1B[0m\x1B[2m"a": 2,
\x1B[0m\x1B[2m}
\x1B[0m
"
`;

View File

@@ -879,7 +879,7 @@ test("indented inline snapshots", () => {
"a": 2,
}
`);
}).toThrowErrorMatchingSnapshot();
}).toThrow();
});
test("error snapshots", () => {

View File

@@ -270,22 +270,38 @@ test/napi/node-napi-tests/test/js-native-api/test_conversions/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_dataview/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_date/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_error/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_exception/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_finalizer/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_function/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_general/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_handle_scope/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_instance_data/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_new_target/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_number/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_object/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_promise/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_properties/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_reference/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_reference_double_free/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_string/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_symbol/do.test.ts
test/napi/node-napi-tests/test/js-native-api/test_typedarray/do.test.ts
test/napi/node-napi-tests/test/node-api/1_hello_world/do.test.ts
test/napi/node-napi-tests/test/node-api/test_async/do.test.ts
test/napi/node-napi-tests/test/node-api/test_buffer/do.test.ts
test/napi/node-napi-tests/test/node-api/test_callback_scope/do.test.ts
test/napi/node-napi-tests/test/node-api/test_cleanup_hook/do.test.ts
test/napi/node-napi-tests/test/node-api/test_exception/do.test.ts
test/napi/node-napi-tests/test/node-api/test_fatal/do.test.ts
test/napi/node-napi-tests/test/node-api/test_fatal_exception/do.test.ts
test/napi/node-napi-tests/test/node-api/test_general/do.test.ts
test/napi/node-napi-tests/test/node-api/test_init_order/do.test.ts
test/napi/node-napi-tests/test/node-api/test_instance_data/do.test.ts
test/napi/node-napi-tests/test/node-api/test_make_callback/do.test.ts
test/napi/node-napi-tests/test/node-api/test_make_callback_recurse/do.test.ts
test/napi/node-napi-tests/test/node-api/test_threadsafe_function/do.test.ts
test/napi/node-napi-tests/test/node-api/test_worker_terminate_finalization/do.test.ts
test/napi/node-napi-tests/test/node-api/test_reference_by_node_api_version/do.test.ts
# normalizeCryptoAlgorithmParameters
test/js/node/test/parallel/test-webcrypto-derivekey.js

View File

@@ -1,5 +1,7 @@
import { expect, mock, test } from "bun:test";
const stripAnsi = (str: string) => str.replaceAll(/\x1b\[[0-9;]*m/g, "");
test("toHaveBeenCalledWith should show diff when assertion fails", () => {
const mockedFn = mock(args => args);
@@ -18,8 +20,8 @@ test("toHaveBeenCalledWith should show diff when assertion fails", () => {
expect(error).toBeDefined();
expect(error!.message).toContain("- Expected");
expect(error!.message).toContain("+ Received");
expect(error!.message).toContain("d: 1");
expect(error!.message).toContain("d: 2");
expect(stripAnsi(error!.message)).toContain('"d": 1');
expect(stripAnsi(error!.message)).toContain('"d": 2');
});
test("toHaveBeenNthCalledWith should show diff when assertion fails", () => {
@@ -80,8 +82,8 @@ test("toHaveBeenCalledWith should show diff for multiple arguments", () => {
expect(error).toBeDefined();
expect(error!.message).toContain("- Expected");
expect(error!.message).toContain("+ Received");
expect(error!.message).toContain("bar");
expect(error!.message).toContain("baz");
expect(stripAnsi(error!.message)).toContain("bar");
expect(stripAnsi(error!.message)).toContain("baz");
});
test("toHaveBeenCalledWith should show diff for complex nested structures", () => {

View File

@@ -0,0 +1,43 @@
// Test for https://github.com/oven-sh/bun/issues/15673
// `Bun.deepMatch` always returns `true` when comparing `Set` and `Map` instances with different number of entries
import { test, expect } from "bun:test";
test.each([
// Maps with different number of entries should return false
[
new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
new Map<number, number>([ [1, 2], [2, 3] ]),
],
[
new Map<number, number>([ [1, 2], [2, 3], [3, 4] ]),
new Map<number, number>([ [1, 2], [2, 3], [3, 4], [4, 5] ]),
],
// Sets with different number of entries should return false
[
new Set([1, 2, 3]),
new Set([1, 2]),
],
[
new Set([1, 2, 3]),
new Set([4, 5, 6]),
],
])("Bun.deepMatch should return false for Maps/Sets with different contents (%p, %p)", (a, b) => {
expect(Bun.deepMatch(a, b)).toBe(false);
});
// Additional cases that were not working in the original issue
test.each([
[new Map([ ["foo", 1] ]), new Map([ [ "bar", 1 ] ])],
[new Map([ ["foo", 1] ]), new Map([ [ "foo", 2 ] ])],
])("Bun.deepMatch should return false for Maps with different keys/values (%p, %p)", (a, b) => {
expect(Bun.deepMatch(a, b)).toBe(false);
});
// These cases should also return false (different sizes, even if subset)
test.each([
[new Map([ ["foo", 1] ]), new Map([ [ "foo", 1 ], ["bar", 2] ])],
[new Set([1, 2]), new Set([1, 2, 3])],
])("Bun.deepMatch should return false for Maps/Sets with different sizes (%p, %p)", (a, b) => {
expect(Bun.deepMatch(a, b)).toBe(false);
});

View File

@@ -0,0 +1,5 @@
test.failing("no crash", () => {
expect(() => {
throw undefined;
}).toThrow(TypeError);
});