diff --git a/assert/equals.ts b/assert/equals.ts index b8370584425b..7e800851d616 100644 --- a/assert/equals.ts +++ b/assert/equals.ts @@ -64,5 +64,20 @@ export function assertEquals( 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") + ) { + 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."; + } throw new AssertionError(message); } diff --git a/assert/equals_test.ts b/assert/equals_test.ts index 8f23661d6b2f..6cfb7de95f5a 100644 --- a/assert/equals_test.ts +++ b/assert/equals_test.ts @@ -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, @@ -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"); + }, +}); + +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", + ); + }, +}); + Deno.test({ name: "assertEquals() matches same Set with object keys", fn() {