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
2 changes: 2 additions & 0 deletions assert/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"./almost-equals": "./almost_equals.ts",
"./array-includes": "./array_includes.ts",
"./equals": "./equals.ts",
"./unstable-equals": "./unstable_equals.ts",
"./exists": "./exists.ts",
"./false": "./false.ts",
"./greater": "./greater.ts",
Expand All @@ -24,6 +25,7 @@
"./object-match": "./object_match.ts",
"./rejects": "./rejects.ts",
"./strict-equals": "./strict_equals.ts",
"./unstable-strict-equals": "./unstable_strict_equals.ts",
"./string-includes": "./string_includes.ts",
"./throws": "./throws.ts",
"./assertion-error": "./assertion_error.ts",
Expand Down
3 changes: 2 additions & 1 deletion assert/equals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function assertEquals<T>(
const diffResult = stringDiff
? diffStr(actual as string, expected as string)
: diff(actualString.split("\n"), expectedString.split("\n"));
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
const diffMsg = buildMessage(diffResult, { stringDiff }, arguments[3])
.join("\n");
message = `${message}\n${diffMsg}`;
throw new AssertionError(message);
}
3 changes: 2 additions & 1 deletion assert/strict_equals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function assertStrictEquals<T>(
const diffResult = stringDiff
? diffStr(actual as string, expected as string)
: diff(actualString.split("\n"), expectedString.split("\n"));
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
const diffMsg = buildMessage(diffResult, { stringDiff }, arguments[3])
.join("\n");
message = `Values are not strictly equal${msgSuffix}\n${diffMsg}`;
}

Expand Down
56 changes: 56 additions & 0 deletions assert/unstable_equals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import { assertEquals as _assertEquals } from "./equals.ts";
import { truncateDiff } from "@std/internal/truncate-build-message";
// deno-lint-ignore no-unused-vars
import type { DIFF_CONTEXT_LENGTH } from "@std/internal/truncate-build-message";

/**
* Make an assertion that `actual` and `expected` are equal, deeply. If not
* deeply equal, then throw.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* Type parameter can be specified to ensure values under comparison have the
* same type.
*
* Note: When comparing `Blob` objects, you should first convert them to
* `Uint8Array` using the `Blob.bytes()` method and then compare their
* contents.
*
* The {@linkcode DIFF_CONTEXT_LENGTH} environment variable can be set to
* enable truncation of long diffs, in which case its value should be a
* positive integer representing the number of unchanged context lines to show
* around each changed part of the diff. By default, diffs are not truncated.
*
* @example Usage
* ```ts ignore
* import { assertEquals } from "@std/assert";
*
* assertEquals("world", "world"); // Doesn't throw
* assertEquals("hello", "world"); // Throws
* ```
* @example Compare `Blob` objects
* ```ts ignore
* import { assertEquals } from "@std/assert";
*
* const bytes1 = await new Blob(["foo"]).bytes();
* const bytes2 = await new Blob(["foo"]).bytes();
*
* assertEquals(bytes1, bytes2);
* ```
*
* @typeParam T The type of the values to compare. This is usually inferred.
* @param actual The actual value to compare.
* @param expected The expected value to compare.
* @param msg The optional message to display if the assertion fails.
*/
export function assertEquals<T>(
actual: T,
expected: T,
msg?: string,
) {
const args: Parameters<typeof _assertEquals> = [actual, expected, msg];
// @ts-expect-error extra arg
_assertEquals(...args, truncateDiff);
}
257 changes: 257 additions & 0 deletions assert/unstable_equals_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import { AssertionError, assertThrows } from "./mod.ts";
import { assertEquals } from "./unstable_equals.ts";
import { DIFF_CONTEXT_LENGTH } from "@std/internal/truncate-build-message";
import { stripAnsiCode } from "@std/internal/styles";
import { dedent } from "@std/text/unstable-dedent";
import { stub } from "@std/testing/mock";
import { disposableStack } from "../internal/_testing.ts";

function assertDiffMessage(a: unknown, b: unknown, expected: string) {
const err = assertThrows(() => assertEquals(a, b), AssertionError);
// TODO(lionel-rowe): re-spell `fullExpectedMessage` indentation once https://github.com/denoland/std/issues/6830
// is fixed
const fullExpectedMessage = dedent`
Values are not equal.


[Diff] Actual / Expected


${expected}
`;
assertEquals(
// TODO(lionel-rowe): compare full messages without trimming once https://github.com/denoland/std/issues/6830 and
// https://github.com/denoland/std/issues/6831 are fixed
stripAnsiCode(err.message).trimEnd(),
fullExpectedMessage,
);
}

Deno.test(`assertEquals() truncates unchanged lines of large diffs when "${DIFF_CONTEXT_LENGTH}" is set`, async (t) => {
using stack = disposableStack();
stack.use(stub(Deno.permissions, "querySync", (x) => {
if (x.name === "env") return { state: "granted" } as Deno.PermissionStatus;
throw new Error(`Unexpected permission descriptor: ${x.name}`);
}));
stack.use(stub(Deno.env, "get", (key) => {
if (key === DIFF_CONTEXT_LENGTH) return (10).toString();
throw new Error(`Unexpected env var key: ${key}`);
}));

const a = Array.from({ length: 1000 }, (_, i) => i);
const b = [...a];
b[500] = -1;

await t.step("array", () => {
assertDiffMessage(
a,
b,
dedent`
[
... 490 unchanged lines ...
490,
491,
492,
493,
494,
495,
496,
497,
498,
499,
- 500,
+ -1,
501,
502,
503,
504,
505,
506,
507,
508,
509,
510,
... 489 unchanged lines ...
]
`,
);
});

await t.step("object", () => {
assertDiffMessage(
Object.fromEntries(a.entries()),
Object.fromEntries(b.entries()),
dedent`
{
... 437 unchanged lines ...
"492": 492,
"493": 493,
"494": 494,
"495": 495,
"496": 496,
"497": 497,
"498": 498,
"499": 499,
"5": 5,
"50": 50,
- "500": 500,
+ "500": -1,
"501": 501,
"502": 502,
"503": 503,
"504": 504,
"505": 505,
"506": 506,
"507": 507,
"508": 508,
"509": 509,
"51": 51,
... 542 unchanged lines ...
}
`,
);
});

await t.step("string", () => {
assertDiffMessage(
a.join("\n"),
b.join("\n"),
dedent`
0\\n
... 489 unchanged lines ...
490\\n
491\\n
492\\n
493\\n
494\\n
495\\n
496\\n
497\\n
498\\n
499\\n
- 500\\n
+ -1\\n
501\\n
502\\n
503\\n
504\\n
505\\n
506\\n
507\\n
508\\n
509\\n
510\\n
... 488 unchanged lines ...
999
`,
);
});

await t.step("Set", () => {
assertDiffMessage(
new Set(a),
new Set(b),
dedent`
Set(1000) {
+ -1,
0,
1,
10,
100,
101,
102,
103,
104,
105,
106,
... 427 unchanged lines ...
492,
493,
494,
495,
496,
497,
498,
499,
5,
50,
- 500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
51,
... 542 unchanged lines ...
}
`,
);
});

await t.step("diff near start", () => {
const a = Array.from({ length: 1000 }, (_, i) => i);
const b = [...a];
b[3] = -1;

assertDiffMessage(
a,
b,
dedent`
[
0,
1,
2,
- 3,
+ -1,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
... 986 unchanged lines ...
]
`,
);
});

await t.step("diff near end", () => {
const a = Array.from({ length: 1000 }, (_, i) => i);
const b = [...a];
b[996] = -1;

assertDiffMessage(
a,
b,
dedent`
[
... 986 unchanged lines ...
986,
987,
988,
989,
990,
991,
992,
993,
994,
995,
- 996,
+ -1,
997,
998,
999,
]
`,
);
});
});
45 changes: 45 additions & 0 deletions assert/unstable_strict_equals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import { assertStrictEquals as _assertStrictEquals } from "./strict_equals.ts";
import { truncateDiff } from "@std/internal/truncate-build-message";
// deno-lint-ignore no-unused-vars
import type { DIFF_CONTEXT_LENGTH } from "@std/internal/truncate-build-message";

/**
* Make an assertion that `actual` and `expected` are strictly equal, using
* {@linkcode Object.is} for equality comparison. If not, then throw.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* The {@linkcode DIFF_CONTEXT_LENGTH} environment variable can be set to
* enable truncation of long diffs, in which case its value should be a
* positive integer representing the number of unchanged context lines to show
* around each changed part of the diff. By default, diffs are not truncated.
*
* @example Usage
* ```ts ignore
* import { assertStrictEquals } from "@std/assert";
*
* const a = {};
* const b = a;
* assertStrictEquals(a, b); // Doesn't throw
*
* const c = {};
* const d = {};
* assertStrictEquals(c, d); // Throws
* ```
*
* @typeParam T The type of the expected value.
* @param actual The actual value to compare.
* @param expected The expected value to compare.
* @param msg The optional message to display if the assertion fails.
*/
export function assertStrictEquals<T>(
actual: unknown,
expected: T,
msg?: string,
): asserts actual is T {
const args: Parameters<typeof _assertStrictEquals> = [actual, expected, msg];
// @ts-expect-error extra arg
_assertStrictEquals(...args, truncateDiff);
}
Loading
Loading