Skip to content

Commit 007896d

Browse files
refactor(assert): improve assertArrayIncludes perf and docs (#6953)
1 parent 548363d commit 007896d

File tree

1 file changed

+38
-19
lines changed

1 file changed

+38
-19
lines changed

assert/array_includes.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,61 @@ import { AssertionError } from "./assertion_error.ts";
77
/** An array-like object (`Array`, `Uint8Array`, `NodeList`, etc.) that is not a string */
88
export type ArrayLikeArg<T> = ArrayLike<T> & object;
99

10+
function isPrimitive(value: unknown): boolean {
11+
return value === null ||
12+
typeof value !== "object" && typeof value !== "function";
13+
}
14+
1015
/**
11-
* Make an assertion that `actual` includes the `expected` values. If not then
12-
* an error will be thrown.
16+
* Asserts that `actual` contains all values in `expected`, using deep equality
17+
* for non-primitive values.
18+
*
19+
* @example Usage with primitives
20+
* ```ts ignore
21+
* import { assertArrayIncludes } from "@std/assert";
1322
*
14-
* Type parameter can be specified to ensure values under comparison have the
15-
* same type.
23+
* assertArrayIncludes([1, 2, 3], [2, 3]); // Passes
24+
* assertArrayIncludes([1, 2, 3], [4]); // Throws
25+
* ```
1626
*
17-
* @example Usage
27+
* @example Usage with objects (deep equality)
1828
* ```ts ignore
1929
* import { assertArrayIncludes } from "@std/assert";
2030
*
21-
* assertArrayIncludes([1, 2], [2]); // Doesn't throw
22-
* assertArrayIncludes([1, 2], [3]); // Throws
31+
* assertArrayIncludes([{ a: 1 }, { b: 2 }], [{ a: 1 }]); // Passes
2332
* ```
2433
*
25-
* @typeParam T The type of the elements in the array to compare.
26-
* @param actual The array-like object to check for.
27-
* @param expected The array-like object to check for.
28-
* @param msg The optional message to display if the assertion fails.
34+
* @typeParam T The element type of the arrays.
35+
* @param actual The array-like object to search within.
36+
* @param expected The values that must be present in `actual`.
37+
* @param msg Optional message to display on failure.
38+
* @throws {AssertionError} If any value in `expected` is not found in `actual`.
2939
*/
3040
export function assertArrayIncludes<T>(
3141
actual: ArrayLikeArg<T>,
3242
expected: ArrayLikeArg<T>,
3343
msg?: string,
34-
) {
44+
): void {
3545
const missing: unknown[] = [];
36-
for (let i = 0; i < expected.length; i++) {
37-
let found = false;
38-
for (let j = 0; j < actual.length; j++) {
39-
if (equal(expected[i], actual[j])) {
40-
found = true;
41-
break;
46+
const expectedLen = expected.length;
47+
const actualLen = actual.length;
48+
for (let i = 0; i < expectedLen; i++) {
49+
const item = expected[i];
50+
let found: boolean;
51+
if (isPrimitive(item)) {
52+
// Fast path
53+
found = Array.prototype.includes.call(actual, item);
54+
} else {
55+
found = false;
56+
for (let j = 0; j < actualLen; j++) {
57+
if (equal(item, actual[j])) {
58+
found = true;
59+
break;
60+
}
4261
}
4362
}
4463
if (!found) {
45-
missing.push(expected[i]);
64+
missing.push(item);
4665
}
4766
}
4867
if (missing.length === 0) {

0 commit comments

Comments
 (0)