Skip to content

Commit

Permalink
[patch] handle circular references in checkCustomDeepQuality
Browse files Browse the repository at this point in the history
  • Loading branch information
electrovir committed Oct 10, 2024
1 parent 4356f08 commit 37da0e3
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 59 deletions.
46 changes: 23 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/mono-repo-root",
"version": "30.2.0",
"version": "30.3.0",
"private": true,
"homepage": "https://github.com/electrovir/augment-vir",
"bugs": {
Expand Down
4 changes: 2 additions & 2 deletions packages/assert/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/assert",
"version": "30.2.0",
"version": "30.3.0",
"description": "A collection of assertions for test and production code alike.",
"keywords": [
"augment",
Expand Down Expand Up @@ -41,7 +41,7 @@
"test:update": "npm test"
},
"dependencies": {
"@augment-vir/core": "^30.2.0",
"@augment-vir/core": "^30.3.0",
"@date-vir/duration": "^6.0.1",
"deep-eql": "^5.0.2",
"expect-type": "^0.20.0",
Expand Down
24 changes: 24 additions & 0 deletions packages/assert/src/augments/custom-equality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,30 @@ describe(checkCustomDeepQuality.name, () => {
},
]);

it('handles equal circular references', () => {
const circular1 = {hi: 'bye', nested: {}};
circular1.nested = circular1;
const circular2 = {hi: 'bye', nested: {}};
circular2.nested = circular2;

assert.isTrue(checkCustomDeepQuality(circular1, circular2, check.strictEquals));
});
it('handles circular arrays', () => {
const circular1: any[] = [];
circular1.push(circular1);

assert.isTrue(checkCustomDeepQuality(circular1, circular1, check.strictEquals));
});

it('handles unequal circular references', () => {
const circular1 = {hi: 'bye', nested: {}};
circular1.nested = circular1;
const circular2 = {hi: 'different', nested: {}};
circular2.nested = circular2;

assert.isFalse(checkCustomDeepQuality(circular1, circular2, check.strictEquals));
});

it('handles return types', async () => {
const asyncResult = checkCustomDeepQuality('a', 'a', (a, b) =>
Promise.resolve(check.strictEquals(a, b)),
Expand Down
52 changes: 41 additions & 11 deletions packages/assert/src/augments/custom-equality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
import {check} from './guards/check.js';

/**
* Runs a custom provided checker that is applied deeply to any given variables. Automatically
* handles async custom checkers.
* Deeply checks inputs for equality with a custom checker callback. All objects are recursed into
* and the custom checker is only run on primitives. This function automatically handles async
* custom checkers and circular references.
*
* @category Assert
*/
Expand All @@ -18,8 +19,9 @@ export function checkCustomDeepQuality(
customChecker: (a: unknown, b: unknown) => boolean,
): boolean;
/**
* Runs a custom provided checker that is applied deeply to any given variables. Automatically
* handles async custom checkers.
* Deeply checks inputs for equality with a custom checker callback. All objects are recursed into
* and the custom checker is only run on primitives. This function automatically handles async
* custom checkers and circular references.
*
* @category Assert
*/
Expand All @@ -29,8 +31,9 @@ export function checkCustomDeepQuality(
customChecker: (a: unknown, b: unknown) => Promise<boolean>,
): Promise<boolean>;
/**
* Runs a custom provided checker that is applied deeply to any given variables. Automatically
* handles async custom checkers.
* Deeply checks inputs for equality with a custom checker callback. All objects are recursed into
* and the custom checker is only run on primitives. This function automatically handles async
* custom checkers and circular references.
*
* @category Assert
*/
Expand All @@ -40,25 +43,41 @@ export function checkCustomDeepQuality(
customChecker: (a: unknown, b: unknown) => MaybePromise<boolean>,
): MaybePromise<boolean>;
/**
* Runs a custom provided checker that is applied deeply to any given variables. Automatically
* handles async custom checkers.
* Deeply checks inputs for equality with a custom checker callback. All objects are recursed into
* and the custom checker is only run on primitives. This function automatically handles async
* custom checkers and circular references.
*
* @category Assert
*/
export function checkCustomDeepQuality(
a: unknown,
b: unknown,
customChecker: (a: unknown, b: unknown) => MaybePromise<boolean>,
): MaybePromise<boolean> {
return recursiveCheckCustomDeepQuality(a, b, customChecker, new Set());
}

function recursiveCheckCustomDeepQuality(
a: unknown,
b: unknown,
customChecker: (a: unknown, b: unknown) => MaybePromise<boolean>,
checkedObjects: Set<AnyObject>,
): MaybePromise<boolean> {
a = flattenComplexObject(a);
b = flattenComplexObject(b);

if (check.isObject(a) && check.isObject(b)) {
if (checkedObjects.has(a) || checkedObjects.has(b)) {
return true;
}
checkedObjects.add(a);
checkedObjects.add(b);
if (
!checkCustomDeepQuality(
!recursiveCheckCustomDeepQuality(
getObjectTypedKeys(a).sort(),
getObjectTypedKeys(b).sort(),
customChecker,
checkedObjects,
)
) {
return false;
Expand All @@ -67,10 +86,11 @@ export function checkCustomDeepQuality(
let receivedPromise = false as boolean;

const results = getObjectTypedKeys(a).map((key) => {
const result = checkCustomDeepQuality(
const result = recursiveCheckCustomDeepQuality(
(a as AnyObject)[key],
(b as AnyObject)[key],
customChecker,
checkedObjects,
);
if (check.isPromise(result)) {
receivedPromise = true;
Expand All @@ -80,13 +100,23 @@ export function checkCustomDeepQuality(

return handleMaybePromise(receivedPromise, results);
} else if (check.isArray(a) && check.isArray(b)) {
if (checkedObjects.has(a) || checkedObjects.has(b)) {
return true;
}
checkedObjects.add(a);
checkedObjects.add(b);
if (a.length !== b.length) {
return false;
}

let receivedPromise = false as boolean;
const results = a.map((entry, index) => {
const result = checkCustomDeepQuality(entry, b[index], customChecker);
const result = recursiveCheckCustomDeepQuality(
entry,
b[index],
customChecker,
checkedObjects,
);
if (check.isPromise(result)) {
receivedPromise = true;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/common",
"version": "30.2.0",
"version": "30.3.0",
"description": "A collection of augments, helpers types, functions, and classes for any JavaScript environment.",
"keywords": [
"augment",
Expand Down Expand Up @@ -39,8 +39,8 @@
"test:web": "virmator --no-deps test web"
},
"dependencies": {
"@augment-vir/assert": "^30.2.0",
"@augment-vir/core": "^30.2.0",
"@augment-vir/assert": "^30.3.0",
"@augment-vir/core": "^30.3.0",
"@date-vir/duration": "^6.0.1",
"ansi-styles": "^6.2.1",
"json5": "^2.2.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/core",
"version": "30.2.0",
"version": "30.3.0",
"description": "Core augment-vir augments. Use @augment-vir/common instead.",
"homepage": "https://github.com/electrovir/augment-vir",
"bugs": {
Expand Down
8 changes: 4 additions & 4 deletions packages/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/node",
"version": "30.2.0",
"version": "30.3.0",
"description": "A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.",
"keywords": [
"augment",
Expand Down Expand Up @@ -37,16 +37,16 @@
"test:update": "npm test"
},
"dependencies": {
"@augment-vir/assert": "^30.2.0",
"@augment-vir/common": "^30.2.0",
"@augment-vir/assert": "^30.3.0",
"@augment-vir/common": "^30.3.0",
"@date-vir/duration": "^6.0.1",
"ansi-styles": "^6.2.1",
"terminate": "^2.8.0",
"type-fest": "^4.26.1",
"typed-event-target": "^3.4.0"
},
"devDependencies": {
"@augment-vir/test": "^30.2.0",
"@augment-vir/test": "^30.3.0",
"@prisma/client": "^5.20.0",
"@types/node": "^22.7.4",
"@web/dev-server-esbuild": "^1.0.2",
Expand Down
8 changes: 4 additions & 4 deletions packages/scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/scripts",
"version": "30.2.0",
"version": "30.3.0",
"private": true,
"homepage": "https://github.com/electrovir/augment-vir",
"bugs": {
Expand All @@ -24,14 +24,14 @@
"test:update": "npm test"
},
"dependencies": {
"@augment-vir/assert": "^30.2.0",
"@augment-vir/core": "^30.2.0",
"@augment-vir/assert": "^30.3.0",
"@augment-vir/core": "^30.3.0",
"@virmator/docs": "^13.4.1",
"jsdom": "^25.0.1",
"typedoc": "^0.26.7"
},
"devDependencies": {
"@augment-vir/test": "^30.2.0",
"@augment-vir/test": "^30.3.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.7.4"
},
Expand Down
Loading

0 comments on commit 37da0e3

Please sign in to comment.