diff --git a/docs/config.md b/docs/config.md index d450690..e2a5a7a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,12 +1,11 @@ - ## Config #### `.include: ChangeType | ChangeType[]` -||| -|-|-| +| | | +| --------------- | ------------------------------------------------------------------------------------- | | **Description** | Include only these change types from the diff result. Can be combined with `exclude`. | -| **Default** | `[ChangeType.NOOP, ChangeType.ADD, ChangeType.UPDATE, ChangeType.REMOVE]` | +| **Default** | `[ChangeType.NOOP, ChangeType.ADD, ChangeType.UPDATE, ChangeType.REMOVE]` | ```javascript diff(a, b, { include: [ChangeType.ADD] }); // only additions @@ -19,10 +18,11 @@ diff(a, b, { --- #### `.exclude: ChangeType | ChangeType[]` -||| -|-|-| + +| | | +| --------------- | ------------------------------------------------------------------------------- | | **Description** | Excludes the change types from the diff result. Can be combined with `include`. | -| **Default** | `[]` | +| **Default** | `[]` | ```javascript diff(a, b, { exclude: ChangeType.NOOP }); @@ -34,10 +34,10 @@ diff(a, b, { exclude: [ChangeType.ADD, ChangeType.NOOP] }); #### `.strict: boolean` -||| -|-|-| +| | | +| --------------- | -------------------------------------- | | **Description** | Performs loose type check if disabled. | -| **Default** | `true` | +| **Default** | `true` | ```javascript const a = { foo: 1 }; @@ -52,10 +52,10 @@ console.log(diff(a, b, { strict: false }).equal); // true #### `.showUpdatedOnly: boolean` -||| -|-|-| +| | | +| --------------- | ----------------------------------------------------------------------------------------------------------------------- | | **Description** | `@sandboxed/diff` creates a `ChangeType.REMOVE` entry for every `ChangeType.UPDATE`. This flags prevents this behavior. | -| **Default** | `false` | +| **Default** | `false` | ```javascript const a = { foo: 'baz' }; @@ -65,6 +65,7 @@ console.log(diff(a, b, { showUpdatedOnly: true })); ``` **Output**: + ```javascript [ { type: 'noop', str: '{', depth: 0, path: [] }, @@ -72,20 +73,20 @@ console.log(diff(a, b, { showUpdatedOnly: true })); type: 'update', str: '"foo": "bar",', depth: 1, - path: [ 'foo', { deleted: false, value: 'bar' } ] + path: ['foo', { deleted: false, value: 'bar' }], }, - { type: 'noop', str: '}', depth: 0, path: [] } -] + { type: 'noop', str: '}', depth: 0, path: [] }, +]; ``` --- #### `.pathHints: PatHints` -||| -|-|-| +| | | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Description** | Hashmap of `map` and `set` path hints. These strings will be used in the `path` array to provide a hit about the object's type. | -| **Default** | `{ map: '__MAP__', set: '__SET__' }` | +| **Default** | `{ map: '__MAP__', set: '__SET__' }` | ⚠️ Warning: **Complex keys are not recursively diffed**, they are treated as references only. **Assume that any string entry in the path array comes from plain objects, and numeric entries come from arrays**. Without these hints, tracking back to the origin can be difficult, though can be disabled if not needed. @@ -104,10 +105,10 @@ console.log(result[1].path); // ['__MAP__', 'foo', { deleted: false, value: 'bar #### `.redactKeys: Array` -||| -|-|-| -| **Description** | List of keys that should be redacted from the output. Works with `string` based keys and serialized `Symbol`.| -|**Default** | `[ 'password', 'secret', 'token', 'Symbol(password)', 'Symbol (secret)', 'Symbol(token)' ]` | +| | | +| --------------- | ------------------------------------------------------------------------------------------------------------- | +| **Description** | List of keys that should be redacted from the output. Works with `string` based keys and serialized `Symbol`. | +| **Default** | `[ 'password', 'secret', 'token', 'Symbol(password)', 'Symbol (secret)', 'Symbol(token)' ]` | ⚠️ Warning: Only the result `str` is redacted, the `path` array still contains the reference to the actual values. Be careful when using this for logging. @@ -119,6 +120,7 @@ console.log(diff(a, b, { showUpdatedOnly: true })); ``` **Output**: + ```javascript [ { type: 'noop', str: '{', depth: 0, path: [] }, @@ -126,40 +128,40 @@ console.log(diff(a, b, { showUpdatedOnly: true })); type: 'update', str: '"password": "*****",', depth: 1, - path: [ 'password', { deleted: false, value: 'secret' } ] + path: ['password', { deleted: false, value: 'secret' }], }, - { type: 'noop', str: '}', depth: 0, path: [] } -] + { type: 'noop', str: '}', depth: 0, path: [] }, +]; ``` --- #### `.maxDepth: number` -||| -|-|-| +| | | +| --------------- | ------------------------------------------------------------------------------- | | **Description** | Max depth that the diffing function can traverse. Throws when reaching the max. | -| **Default** | `50` | -| **Throws** | `Max depth exceeded!` | +| **Default** | `50` | +| **Throws** | `Max depth exceeded!` | --- #### `.maxKeys: number` -||| -|-|-| +| | | +| --------------- | ------------------------------------------------------------------------- | | **Description** | Max keys the diffing function can traverse. Throws when reaching the max. | -|**Default** | `50` | -|**Throws** | `Object is too big to continue! Aborting.` | +| **Default** | `50` | +| **Throws** | `Object is too big to continue! Aborting.` | --- #### `.timeout: number` -||| -|-|-| +| | | +| --------------- | --------------------------------------------- | | **Description** | Milliseconds before throwing a timeout error. | -|**Default** | `1000` | -|**Throws** | `Diff took too much time! Aborting.` | +| **Default** | `1000` | +| **Throws** | `Diff took too much time! Aborting.` | ⚠️ Warning: The diffing function does not check for object size in memory. The process can still hang if the system is unable to handle the object in memory. diff --git a/docs/utils.md b/docs/utils.md index 804332b..2f27dd4 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -9,13 +9,14 @@ Highly configurable util that generates the diff string representation of the di ```javascript import diff from '@sandboxed/diff'; -const a = { name: "Alice", age: 25 }; -const b = { name: "Alice", age: 26, city: "New York" }; +const a = { name: 'Alice', age: 25 }; +const b = { name: 'Alice', age: 26, city: 'New York' }; console.log(diff(a, b).toDiffString()); ``` **Output**: + ``` { "name": "Alice", @@ -27,14 +28,13 @@ console.log(diff(a, b).toDiffString()); #### Config options -| config | default | Description | -|------------|----------|-------------| -| withColors | `true` | Formats the string using AnsiColors. | +| config | default | Description | +| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------- | +| withColors | `false` | Formats the string using AnsiColors. | | colors | `object` | Hashmap for coloring each line based on type: `[ChangeType]: (string) => string`. Should be compatible with `chalk`. | -| symbols | `object` | Hashmap for prefixing each line based on type: `[ChangeType]: string`. | -| wrapper | `[]` | Array with `string` entries. Wraps the result between the first two strings. | -| indentSize | `2` | Whitespace after the `config.symbols`. Indentation is done using `space`. | - +| symbols | `object` | Hashmap for prefixing each line based on type: `[ChangeType]: string`. | +| wrapper | `[]` | Array with `string` entries. Wraps the result between the first two strings. | +| indentSize | `2` | Whitespace after the `config.symbols`. Indentation is done using `space`. | ### Equality detection @@ -45,8 +45,8 @@ Determines whether the inputs are structurally equal based on the diff result. I ```javascript import diff from '@sandboxed/diff'; -const a = { name: "Alice", age: 25 }; -const b = { name: "Alice", age: 26, city: "New York" }; +const a = { name: 'Alice', age: 25 }; +const b = { name: 'Alice', age: 26, city: 'New York' }; console.log(diff(a, b).equal); // Output: false @@ -68,9 +68,7 @@ import diff, { ChangeType } from '@sandboxed/diff'; const a = { name: 'Alice', foo: new Set([1, 2, 'test']) }; const b = { name: 'Alice', bar: new Set(['test', 2, 1]) }; -console.log( - diff(a, b, { exclude: [ChangeType.ADD, ChangeType.REMOVE] }).equal -); // Output: true +console.log(diff(a, b, { exclude: [ChangeType.ADD, ChangeType.REMOVE] }).equal); // Output: true ``` Given that the diff result will not detect the changes in **`foo`**(`ChangeType.REMOVE`) or **`bar`** (`ChangeType.ADD`), the diff result will contain only `ChangeType.NOOP`, causing `.equal` to be `true`. diff --git a/package-lock.json b/package-lock.json index 72781c0..2632477 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@babel/preset-typescript": "^7.26.0", "@eslint/js": "^9.20.0", "@stylistic/eslint-plugin": "^4.0.1", + "@types/jest": "^30.0.0", "@types/node": "^22.13.4", "babel-jest": "^29.7.0", "eslint": "^9.20.1", @@ -46,15 +47,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -335,9 +336,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -2258,9 +2259,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2300,13 +2301,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -2315,9 +2316,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2339,19 +2340,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2393,9 +2397,9 @@ "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2443,19 +2447,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2463,13 +2470,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -2920,6 +2927,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "30.0.0-alpha.7", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.0-alpha.7.tgz", @@ -3077,6 +3094,16 @@ "node": "^16.10.0 || ^18.12.0 || >=20.0.0" } }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "30.0.0-alpha.7", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.0-alpha.7.tgz", @@ -4202,6 +4229,225 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@sinclair/typebox": { + "version": "0.34.47", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", + "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4444,9 +4690,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -4718,9 +4964,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5258,33 +5504,32 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5389,9 +5634,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5406,9 +5651,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5419,9 +5664,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5492,15 +5737,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9817,9 +10062,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4124e7c..4e90a26 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@babel/preset-typescript": "^7.26.0", "@eslint/js": "^9.20.0", "@stylistic/eslint-plugin": "^4.0.1", + "@types/jest": "^30.0.0", "@types/node": "^22.13.4", "babel-jest": "^29.7.0", "eslint": "^9.20.1", diff --git a/src/index.ts b/src/index.ts index be4fefb..e44ab9a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import toDiffString from './utils/toDiffString'; * @param {DiffConfig['timeout']} [config.timeout=1000] - Throws when the diffing timeout is met * @param {DiffConfig['redactKeys']} [config.redactKeys=[]] - Replaces the values of these keys with ***** */ -function diff(lhs: any, rhs: any, config: DiffConfig): Diff { +function diff(lhs: any, rhs: any, config?: Partial): Diff { const defaultConfig: DiffConfig = { include: [ ChangeType.ADD, diff --git a/src/test/__snapshots__/toDiffString.spec.js.snap b/src/test/__snapshots__/toDiffString.spec.js.snap deleted file mode 100644 index 4deca36..0000000 --- a/src/test/__snapshots__/toDiffString.spec.js.snap +++ /dev/null @@ -1,699 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`toDiffString outputs custom colors and symbols 1`] = ` -"%{ -% "id": 123, -# "name": "Alice", -$ "name": "Alice Johnson", -% "email": "alice@example.com", -# "password": "*****", -$ "password": "*****", -% "preferences": { -# "theme": "dark", -$ "theme": "light", -# "notifications": true, -$ "notifications": false, -% "layout": [ -# 0: "grid", -$ 0: "list", -# 1: "compact", -% ], -@ "newFeature": true, -% }, -% "metadata": Map (4) { -# {"complex":"key"}: "value", -$ {"complex":"key"}: "newValue", -# "role": "admin", -$ "role": "user", -% "permissions": Set [ -% "read", -# "write", -% "execute", -% ], -@ "createdAt": 1709294400000, -% }, -% "tags": Set [ -% "user", -# "active", -@ "inactive", -@ "premium", -% ], -# "lastLogin": 1709294400000, -$ "lastLogin": 1711096200000, -% "custom": { -# "key": "value", -$ "key": "modified", -% }, -% "emptyArray": [ -@ 0: "unexpected", -% ], -% "emptyObject": { -@ "newKey": "new", -% }, -% "nestedEmpty": { -% "a": { -@ "b": "value", -% }, -% }, -% "self": { -% "id": 123, -# "name": "Alice", -$ "name": "Alice Johnson", -% "email": "alice@example.com", -# "password": "*****", -$ "password": "*****", -% "preferences": { -# "theme": "dark", -$ "theme": "light", -# "notifications": true, -$ "notifications": false, -% "layout": [ -# 0: "grid", -$ 0: "list", -# 1: "compact", -% ], -@ "newFeature": true, -% }, -% "metadata": Map (4) { -# {"complex":"key"}: "value", -$ {"complex":"key"}: "newValue", -# "role": "admin", -$ "role": "user", -% "permissions": Set [ -% "read", -# "write", -% "execute", -% ], -@ "createdAt": 1709294400000, -% }, -% "tags": Set [ -% "user", -# "active", -@ "inactive", -@ "premium", -% ], -# "lastLogin": 1709294400000, -$ "lastLogin": 1711096200000, -% "custom": { -# "key": "value", -$ "key": "modified", -% }, -% "emptyArray": [ -@ 0: "unexpected", -% ], -% "emptyObject": { -@ "newKey": "new", -% }, -% "nestedEmpty": { -% "a": { -@ "b": "value", -% }, -% }, -% "self": [Circular], -@ "newProp": "New Value", -% }, -@ "newProp": "New Value", -%}" -`; - -exports[`toDiffString outputs empty wrapper strings if missing 1`] = ` -"{ - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": { - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": [Circular], -+ "newProp": "New Value", - }, -+ "newProp": "New Value", -}" -`; - -exports[`toDiffString outputs empty wrapper strings if missing 2`] = ` -"{ - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": { - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": [Circular], -+ "newProp": "New Value", - }, -+ "newProp": "New Value", -}" -`; - -exports[`toDiffString outputs with custom wrapper and indentSize 1`] = ` -"\`\`\`diff -{ - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": { - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": [Circular], -+ "newProp": "New Value", - }, -+ "newProp": "New Value", -} -\`\`\`" -`; - -exports[`toDiffString outputs with defaults 1`] = ` -"{ - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": { - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": [Circular], -+ "newProp": "New Value", - }, -+ "newProp": "New Value", -}" -`; - -exports[`toDiffString outputs without color 1`] = ` -"{ - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": { - "id": 123, -- "name": "Alice", -! "name": "Alice Johnson", - "email": "alice@example.com", -- "password": "*****", -! "password": "*****", - "preferences": { -- "theme": "dark", -! "theme": "light", -- "notifications": true, -! "notifications": false, - "layout": [ -- 0: "grid", -! 0: "list", -- 1: "compact", - ], -+ "newFeature": true, - }, - "metadata": Map (4) { -- {"complex":"key"}: "value", -! {"complex":"key"}: "newValue", -- "role": "admin", -! "role": "user", - "permissions": Set [ - "read", -- "write", - "execute", - ], -+ "createdAt": 1709294400000, - }, - "tags": Set [ - "user", -- "active", -+ "inactive", -+ "premium", - ], -- "lastLogin": 1709294400000, -! "lastLogin": 1711096200000, - "custom": { -- "key": "value", -! "key": "modified", - }, - "emptyArray": [ -+ 0: "unexpected", - ], - "emptyObject": { -+ "newKey": "new", - }, - "nestedEmpty": { - "a": { -+ "b": "value", - }, - }, - "self": [Circular], -+ "newProp": "New Value", - }, -+ "newProp": "New Value", -}" -`; diff --git a/src/test/__snapshots__/toDiffString.spec.ts.snap b/src/test/__snapshots__/toDiffString.spec.ts.snap new file mode 100644 index 0000000..c60ab3d --- /dev/null +++ b/src/test/__snapshots__/toDiffString.spec.ts.snap @@ -0,0 +1,699 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`toDiffString outputs custom colors and symbols 1`] = ` +"%{ +% "id": 123, +# "name": "Alice", +$ "name": "Alice Johnson", +% "email": "alice@example.com", +# "password": "*****", +$ "password": "*****", +% "preferences": { +# "theme": "dark", +$ "theme": "light", +# "notifications": true, +$ "notifications": false, +% "layout": [ +# 0: "grid", +$ 0: "list", +# 1: "compact", +% ], +@ "newFeature": true, +% }, +% "metadata": Map (4) { +# {"complex":"key"}: "value", +$ {"complex":"key"}: "newValue", +# "role": "admin", +$ "role": "user", +% "permissions": Set [ +% "read", +# "write", +% "execute", +% ], +@ "createdAt": 1709294400000, +% }, +% "tags": Set [ +% "user", +# "active", +@ "inactive", +@ "premium", +% ], +# "lastLogin": 1709294400000, +$ "lastLogin": 1711096200000, +% "custom": { +# "key": "value", +$ "key": "modified", +% }, +% "emptyArray": [ +@ 0: "unexpected", +% ], +% "emptyObject": { +@ "newKey": "new", +% }, +% "nestedEmpty": { +% "a": { +@ "b": "value", +% }, +% }, +% "self": { +% "id": 123, +# "name": "Alice", +$ "name": "Alice Johnson", +% "email": "alice@example.com", +# "password": "*****", +$ "password": "*****", +% "preferences": { +# "theme": "dark", +$ "theme": "light", +# "notifications": true, +$ "notifications": false, +% "layout": [ +# 0: "grid", +$ 0: "list", +# 1: "compact", +% ], +@ "newFeature": true, +% }, +% "metadata": Map (4) { +# {"complex":"key"}: "value", +$ {"complex":"key"}: "newValue", +# "role": "admin", +$ "role": "user", +% "permissions": Set [ +% "read", +# "write", +% "execute", +% ], +@ "createdAt": 1709294400000, +% }, +% "tags": Set [ +% "user", +# "active", +@ "inactive", +@ "premium", +% ], +# "lastLogin": 1709294400000, +$ "lastLogin": 1711096200000, +% "custom": { +# "key": "value", +$ "key": "modified", +% }, +% "emptyArray": [ +@ 0: "unexpected", +% ], +% "emptyObject": { +@ "newKey": "new", +% }, +% "nestedEmpty": { +% "a": { +@ "b": "value", +% }, +% }, +% "self": [Circular], +@ "newProp": "New Value", +% }, +@ "newProp": "New Value", +%}" +`; + +exports[`toDiffString outputs empty wrapper strings if missing 1`] = ` +"{ + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": { + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": [Circular], ++ "newProp": "New Value", + }, ++ "newProp": "New Value", +}" +`; + +exports[`toDiffString outputs empty wrapper strings if missing 2`] = ` +"{ + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": { + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": [Circular], ++ "newProp": "New Value", + }, ++ "newProp": "New Value", +}" +`; + +exports[`toDiffString outputs with color 1`] = ` +"{ + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": { + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": [Circular], ++ "newProp": "New Value", + }, ++ "newProp": "New Value", +}" +`; + +exports[`toDiffString outputs with custom wrapper and indentSize 1`] = ` +"\`\`\`diff +{ + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": { + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": [Circular], ++ "newProp": "New Value", + }, ++ "newProp": "New Value", +} +\`\`\`" +`; + +exports[`toDiffString outputs with defaults 1`] = ` +"{ + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": { + "id": 123, +- "name": "Alice", +! "name": "Alice Johnson", + "email": "alice@example.com", +- "password": "*****", +! "password": "*****", + "preferences": { +- "theme": "dark", +! "theme": "light", +- "notifications": true, +! "notifications": false, + "layout": [ +- 0: "grid", +! 0: "list", +- 1: "compact", + ], ++ "newFeature": true, + }, + "metadata": Map (4) { +- {"complex":"key"}: "value", +! {"complex":"key"}: "newValue", +- "role": "admin", +! "role": "user", + "permissions": Set [ + "read", +- "write", + "execute", + ], ++ "createdAt": 1709294400000, + }, + "tags": Set [ + "user", +- "active", ++ "inactive", ++ "premium", + ], +- "lastLogin": 1709294400000, +! "lastLogin": 1711096200000, + "custom": { +- "key": "value", +! "key": "modified", + }, + "emptyArray": [ ++ 0: "unexpected", + ], + "emptyObject": { ++ "newKey": "new", + }, + "nestedEmpty": { + "a": { ++ "b": "value", + }, + }, + "self": [Circular], ++ "newProp": "New Value", + }, ++ "newProp": "New Value", +}" +`; diff --git a/src/test/arrays.spec.js b/src/test/arrays.spec.ts similarity index 100% rename from src/test/arrays.spec.js rename to src/test/arrays.spec.ts diff --git a/src/test/config.spec.js b/src/test/config.spec.ts similarity index 97% rename from src/test/config.spec.js rename to src/test/config.spec.ts index 880a050..e3dd228 100644 --- a/src/test/config.spec.js +++ b/src/test/config.spec.ts @@ -136,7 +136,7 @@ describe('With Config', () => { const a = { foo: 'bar', - map: new Map([ + map: new Map([ [complex, true], ['test', 1], ['foo', 'bar'], @@ -147,11 +147,12 @@ describe('With Config', () => { big: 1n, }; + // @ts-expect-error mimics circular a.circular = a; const b = { foo: 'bar', - map: new Map([ + map: new Map([ ['foo', 'bar'], ['test', 1], [complex, true], @@ -162,6 +163,7 @@ describe('With Config', () => { big: BigInt(1), }; + // @ts-expect-error mimics circular b.circular = b; expect(diff(a, b).equal).toEqual(true); diff --git a/src/test/constructors.spec.js b/src/test/constructors.spec.ts similarity index 98% rename from src/test/constructors.spec.js rename to src/test/constructors.spec.ts index 44c9c7c..970fcc3 100644 --- a/src/test/constructors.spec.js +++ b/src/test/constructors.spec.ts @@ -66,7 +66,7 @@ describe('Diff Constructors', () => { }); it('detects constructor changes between Set and []', () => { - const a = []; + const a: Array = []; const b = new Set(['foo', 'bar']); expect(diff(a, b)).toEqual([ @@ -159,7 +159,7 @@ describe('Diff Constructors', () => { }); it('detects sets constructor changes on empty structures', () => { - const a = []; + const a: Array = []; const b = new Set(); expect(diff(a, b)).toEqual([ @@ -179,7 +179,7 @@ describe('Diff Constructors', () => { }); it('detects objects constructor changes on empty structures', () => { - const a = []; + const a: Array = []; const b = {}; expect(diff(a, b)).toEqual([ diff --git a/src/test/maps.spec.js b/src/test/maps.spec.ts similarity index 97% rename from src/test/maps.spec.js rename to src/test/maps.spec.ts index f6ef7ec..657ad02 100644 --- a/src/test/maps.spec.js +++ b/src/test/maps.spec.ts @@ -2,12 +2,12 @@ import diff, { ChangeType } from '..'; describe('Diff Maps', () => { it('supports Maps', () => { - const a = new Map([ + const a = new Map([ ['foo', 'baz'], ['1', 1], ]); - const b = new Map([ + const b = new Map([ ['foo', 'bar'], ['test', 1], ]); @@ -65,7 +65,7 @@ describe('Diff Maps', () => { const mapKey = new Map([['foo', 1]]); const a = new Map([[objKey, 'baz']]); - const b = new Map([ + const b = new Map([ ['foo', 'bar'], [objKey, 'bar'], [setKey, 'bar'], @@ -116,19 +116,19 @@ describe('Diff Maps', () => { }); it('handles nested Maps', () => { - const a = new Map([ + const a = new Map([ ['foo', 'baz'], ['nested', new Map([['nested', 'mep']])], [ 'deeply-nested', - new Map([ + new Map([ ['inside-deeply-nested', 'map'], ['last', new Map([['nested', 'mep']])], ]), ], ]); - const b = new Map([ + const b = new Map([ ['foo', 'bar'], ['nested', new Map([['nested', 'map']])], [ @@ -328,6 +328,8 @@ describe('Diff Maps', () => { it('handles circular references in keys', () => { const objKey = {}; + + // @ts-expect-error mimics circular objKey.self = objKey; const setKey = new Set(); diff --git a/src/test/objects.spec.js b/src/test/objects.spec.ts similarity index 98% rename from src/test/objects.spec.js rename to src/test/objects.spec.ts index e5d06de..b9b5433 100644 --- a/src/test/objects.spec.js +++ b/src/test/objects.spec.ts @@ -236,12 +236,16 @@ describe('Diff Objects', () => { it('handles custom classes as objects', () => { class MyClassA { + foo: string; + constructor() { this.foo = 'bar'; } } class MyClassB { + bar: string; + constructor() { this.bar = 'foo'; } @@ -278,6 +282,8 @@ describe('Diff Objects', () => { it('handles circular references', () => { const a = {}; const b = {}; + + // @ts-expect-error mimics circular b.foo = b; expect(diff(a, b)).toEqual([ @@ -301,6 +307,7 @@ describe('Diff Objects', () => { }, }; + // @ts-expect-error mimics circular a.self = a; expect(diff(undefined, a)).toEqual([ diff --git a/src/test/security.spec.js b/src/test/security.spec.ts similarity index 94% rename from src/test/security.spec.js rename to src/test/security.spec.ts index 5d6eae5..4153464 100644 --- a/src/test/security.spec.js +++ b/src/test/security.spec.ts @@ -8,7 +8,10 @@ describe('Security checks', () => { // warmup for (let i = 0; i < 1e5; i++) { + // @ts-expect-error mimics deep nesting temp = temp.nested = {}; + + // @ts-expect-error mimics deep nesting bigObj.inner.nested[`key${i}`] = i; } @@ -17,6 +20,7 @@ describe('Security checks', () => { const b = {}; diff(a, b); + // @ts-expect-error expects object pollution expect({}.pollution).not.toEqual(true); }); @@ -34,7 +38,9 @@ describe('Security checks', () => { throw new Error('Timeout was not triggered!'); } catch (e) { expect(performance.now() - start).toBeLessThan(150); - expect(e.message).toEqual('Diff took too much time! Aborting.'); + expect((e as Error).message).toEqual( + 'Diff took too much time! Aborting.' + ); } }); diff --git a/src/test/sets.spec.js b/src/test/sets.spec.ts similarity index 100% rename from src/test/sets.spec.js rename to src/test/sets.spec.ts diff --git a/src/test/toDiffString.spec.js b/src/test/toDiffString.spec.ts similarity index 76% rename from src/test/toDiffString.spec.js rename to src/test/toDiffString.spec.ts index e345e30..3d90994 100644 --- a/src/test/toDiffString.spec.js +++ b/src/test/toDiffString.spec.ts @@ -12,7 +12,7 @@ const a = { notifications: true, layout: ['grid', 'compact'], }, - metadata: new Map([ + metadata: new Map([ [complexKey, 'value'], // Complex key (shared reference) ['role', 'admin'], ['permissions', new Set(['read', 'write', 'execute'])], @@ -27,7 +27,7 @@ const a = { nestedEmpty: { a: {} }, }; -// Circular reference +// @ts-expect-error mimics circular a.self = a; const b = { @@ -41,7 +41,7 @@ const b = { layout: ['list'], // Changed newFeature: true, // **Added property** }, - metadata: new Map([ + metadata: new Map([ [complexKey, 'newValue'], // **Value changed, key remains the same** ['role', 'user'], // Changed ['permissions', new Set(['read', 'execute'])], // Removed "write" @@ -58,7 +58,7 @@ const b = { newProp: 'New Value', // **New property at root level** }; -// Circular reference +// @ts-expect-error mimics circular b.self = b; describe('toDiffString', () => { @@ -68,11 +68,11 @@ describe('toDiffString', () => { expect(result.toDiffString()).toMatchSnapshot(); }); - it('outputs without color', () => { + it('outputs with color', () => { const result = diff(a, b); const config = { - withColors: false, + withColors: true, }; expect(result.toDiffString(config)).toMatchSnapshot(); @@ -82,8 +82,9 @@ describe('toDiffString', () => { const result = diff(a, b); const config = { + withColors: true, colors: { - [ChangeType.NOOP]: (str) => `\x1b[36m${str}\x1b[0m`, + [ChangeType.NOOP]: (str: string) => `\x1b[36m${str}\x1b[0m`, }, symbols: { [ChangeType.ADD]: '@', @@ -96,6 +97,31 @@ describe('toDiffString', () => { expect(result.toDiffString(config)).toMatchSnapshot(); }); + it('throws if colors or symbols are not defined', () => { + const result = diff(a, b); + + const configNoSymbol = { + symbols: { + [ChangeType.ADD]: undefined, + }, + }; + + expect(() => result.toDiffString(configNoSymbol)).toThrow( + ' symbol missing in config' + ); + + const configNoColor = { + withColors: true, + colors: { + [ChangeType.NOOP]: undefined, + }, + }; + + expect(() => result.toDiffString(configNoColor)).toThrow( + ' color function missing in config' + ); + }); + it('outputs with custom wrapper and indentSize', () => { const result = diff(a, b); @@ -111,6 +137,6 @@ describe('toDiffString', () => { const result = diff(a, b); expect(result.toDiffString({ wrapper: [] })).toMatchSnapshot(); - expect(result.toDiffString({ wrapper: null })).toMatchSnapshot(); + expect(result.toDiffString({ wrapper: undefined })).toMatchSnapshot(); }); }); diff --git a/src/types.d.ts b/src/types.d.ts index d79c58b..b1e6f50 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,8 +15,8 @@ type DiffSymbols = Record; type DiffColors = Record string>; export type PathHints = { - map: string; - set: string; + map?: string | boolean; + set?: string | boolean; }; export type DiffConfig = { @@ -97,17 +97,25 @@ export interface DiffConstructorArgs extends DiffMethodArgs { rhs: any; } -export type DiffStringConfig = { +type BaseDiffStringConfig = { withColors: boolean; indentSize: number; wrapper: Array; +}; + +export type DiffStringConfig = BaseDiffStringConfig & { colors: DiffColors; symbols: DiffSymbols; }; +export type PartialDiffStringConfig = BaseDiffStringConfig & { + colors: Partial; + symbols: Partial; +}; + export interface Diff extends Array { /** String formatted with the Github's `diff` md format */ - toDiffString: (config?: DiffStringConfig) => string; + toDiffString: (config?: Partial) => string; /** lsh and rhs are structurally equal */ equal: boolean; diff --git a/src/utils/toDiffString.ts b/src/utils/toDiffString.ts index 69514a2..0819346 100644 --- a/src/utils/toDiffString.ts +++ b/src/utils/toDiffString.ts @@ -1,5 +1,6 @@ import type { DiffResult, DiffStringConfig } from '../types'; import { ChangeType } from './constants'; +import { isNullOrUndefined } from './fns'; const ANSI_RESET = '\x1b[0m'; @@ -18,7 +19,7 @@ const ansiColors = { * Takes the diff result and returns a diff string in md. + Added, - Removed, ! Updated * @param {DiffResult[]} diff - The result from the diff * @param {DiffStringConfig} [config] - * @param {DiffStringConfig['withColors']} [config.withColors=true] - If true, outputs the string formatted with AnsiColors + * @param {DiffStringConfig['withColors']} [config.withColors=false] - If true, outputs the string formatted with AnsiColors * @param {DiffStringConfig['colors']} [config.colors] - Each function takes a string and returns it formatted with ansi colors. Maps to each ChangeType. * @param {DiffStringConfig['symbols']} [config.symbols] - String map for each ChangeType * @param {DiffStringConfig['wrapper']} [config.wrapper=['```diff', '```']] - Strings that wrap the diff string output @@ -29,7 +30,7 @@ export default function toDiffString( config?: DiffStringConfig ) { const defaultConfig: DiffStringConfig = { - withColors: true, + withColors: false, colors: ansiColors, wrapper: [], indentSize: 2, @@ -56,6 +57,20 @@ export default function toDiffString( const diffString = diff .map(({ type, str, depth }, index) => { + if ( + !Object.hasOwn(mergedConfig.symbols, type) || + isNullOrUndefined(mergedConfig.symbols[type]) + ) { + throw new Error(`<${type}> symbol missing in config`); + } + + if ( + !Object.hasOwn(mergedConfig.colors, type) || + isNullOrUndefined(mergedConfig.colors[type]) + ) { + throw new Error(`<${type}> color function missing in config`); + } + let symbolString = mergedConfig.symbols[type]; if (index > 0 && index < diff.length - 1 && !symbolString.length) {