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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions assert/equals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,20 @@ export function assertEquals<T>(
const diffMsg = buildMessage(diffResult, { stringDiff }, arguments[3])
.join("\n");
message = `${message}\n${diffMsg}`;
// #6878: if the diff shows no removed/added lines, the values stringify
// identically but were still deemed unequal — typically because at least
// one nested property is a function (or Promise / Request / Blob / etc.)
// that gets compared by reference. The empty diff is confusing, so append
// a hint pointing at the likely cause.
if (
!stringDiff &&
diffResult.every((r) => r.type !== "added" && r.type !== "removed")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DiffResult.type is "added" | "removed" | "common", so this reads more directly as diffResult.every((r) => r.type === "common").

) {
message = `${message}\n` +
" Note: values stringify identically but are not structurally equal. " +
"Functions, Promises, Requests, Blobs, and other built-ins are compared by " +
"reference, so two distinct instances are never equal even when their " +
"representations match.";
Comment on lines +76 to +80

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the Note: text hard-codes 4 leading spaces to align with buildMessage's [Diff] block — an implicit coupling to that formatter's indentation. Fine to leave, but a one-line comment noting why the 4 spaces are there would help the next person who touches it.

}
throw new AssertionError(message);
}
49 changes: 48 additions & 1 deletion assert/equals_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Copyright 2018-2026 the Deno authors. MIT license.
import { assertEquals, AssertionError, assertThrows } from "./mod.ts";
import {
assertEquals,
AssertionError,
assertStringIncludes,
assertThrows,
} from "./mod.ts";
import {
bold,
gray,
Expand Down Expand Up @@ -207,6 +212,48 @@ Deno.test({
},
});

Deno.test({
name:
"assertEquals() hints at reference-equality when objects with function props stringify identically (#6878)",
fn() {
let caught: AssertionError | undefined;
try {
assertEquals(
{ x: 1, y: () => 2 },
{ x: 1, y: () => 2 },
);
} catch (e) {
caught = e as AssertionError;
}
if (!caught) throw new Error("Expected assertEquals to throw");
assertStringIncludes(caught.message, "Values are not equal");
assertStringIncludes(
caught.message,
"stringify identically but are not structurally equal",
);
assertStringIncludes(caught.message, "compared by reference");
},
Comment on lines +219 to +235

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: this file already imports assertThrows, which returns the thrown error — that's cleaner than the hand-rolled try/catch + if (!caught) throw:

const error = assertThrows(
  () => assertEquals({ x: 1, y: () => 2 }, { x: 1, y: () => 2 }),
  AssertionError,
);
assertStringIncludes(error.message, "Values are not equal");
assertStringIncludes(error.message, "stringify identically but are not structurally equal");
assertStringIncludes(error.message, "compared by reference");

});

Deno.test({
name:
"assertEquals() does not append reference-equality hint when there is a real textual diff",
fn() {
let caught: AssertionError | undefined;
try {
assertEquals({ x: 1 }, { x: 2 });
} catch (e) {
caught = e as AssertionError;
}
if (!caught) throw new Error("Expected assertEquals to throw");
assertEquals(
caught.message.includes("stringify identically"),
false,
"Hint should only fire when the diff is empty",
);
Comment on lines +242 to +253

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same assertThrows-returns-the-error simplification applies here. For the negative check, assert(!error.message.includes("stringify identically")) (or assertNotMatch) is clearer than assertEquals(..., false, msg).

},
});

Deno.test({
name: "assertEquals() matches same Set with object keys",
fn() {
Expand Down
Loading