Repository: dubzzz/fast-check
Pull request #2587 (2021-12-28 22:03:44)

⬆️ Bump actions/setup-node from 2.5.0 to 2.5.1
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.0 to 2.5.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-node/releases">actions/setup-node's releases</a>.</em></p>
<blockquote>
<h2>Fix logic of error handling for npm warning and uncaught exception</h2>
<p>In scope of this release we fix logic of error handling related to caching (<a href="https://github-redirect.dependabot.com/actions/setup-node/pull/358">actions/setup-node#358</a>) and (<a href="https://github-redirect.dependabot.com/actions/setup-node/pull/359">actions/setup-node#359</a>).</p>
<p>In the previous behaviour we relied on <code>stderr</code> output to throw error. The warning messages from package managers can be written to the stderr's output. For now the action will throw an error only if exit code differs from zero. Besides, we add logic to сatch and log unhandled exceptions.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-node/commit/1f8c6b94b26d0feae1e387ca63ccbdc44d27b561"><code>1f8c6b9</code></a> Pass to warning uncaught exceptions (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/359">#359</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/9a74eb4e6473f91fbde564f97c2662fd1dc4875c"><code>9a74eb4</code></a> Throw error only if exit code is note zero.  (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/358">#358</a>)</li>
<li>See full diff in <a href="https://github.com/actions/setup-node/compare/v2.5.0...v2.5.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=2.5.0&new-version=2.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/dubzzz/fast-check/pr/2587/builds/203734) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 07c3a472926832698d264246213b12662d70b28c:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-u9v40)| Configuration |
|[fast-check-examples](https://codesandbox.io/s/fast-check-examples-iqzmv)| Configuration |

codecov[bot]: # [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) Report
> Merging [#2587](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (07c3a47) into [main](https://codecov.io/gh/dubzzz/fast-check/commit/27bed70047a4fc6bd908409035818651aec42683?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (27bed70) will **decrease** coverage by `0.02%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/2587/graphs/tree.svg?width=650&height=150&src=pr&token=fRi2fJN730&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)

```diff
@@            Coverage Diff             @@
##             main    #2587      +/-   ##
==========================================
- Coverage   95.00%   94.98%   -0.03%     
==========================================
  Files         206      206              
  Lines        4747     4747              
  Branches      932      932              
==========================================
- Hits         4510     4509       -1     
- Misses        234      235       +1     
  Partials        3        3              
```

| Flag | Coverage Δ | |
|---|---|---|
| unit-tests | `94.98% <ø> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) | Coverage Δ | |
|---|---|---|
| [...bitrary/\_internals/builders/AnyArbitraryBuilder.ts](https://codecov.io/gh/dubzzz/fast-check/pull/2587/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL2J1aWxkZXJzL0FueUFyYml0cmFyeUJ1aWxkZXIudHM=) | `98.27% <0.00%> (-1.73%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Last update [27bed70...07c3a47](https://codecov.io/gh/dubzzz/fast-check/pull/2587?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN).


Patch:
diff --git a/.github/actions/deploy-netlify/index.cjs b/.github/actions/deploy-netlify/index.cjs
index 712bdc16..ac5c8e86 100644
--- a/.github/actions/deploy-netlify/index.cjs
+++ b/.github/actions/deploy-netlify/index.cjs
@@ -42,7 +42,11 @@ async function run() {
     return;
   }
   // Website Draft URL: https://xxxxxxx.netlify.app
-  const netlifyUrlLine = netlifyLog.split('\n').find((line) => line.includes('Website Draft URL: '));
+  const ansiModifiersRegex = /\u001b\[\d\dm/gu;
+  const netlifyUrlLine = netlifyLog
+    .split('\n')
+    .map((line) => line.replace(ansiModifiersRegex, ''))
+    .find((line) => line.includes('Website Draft URL: '));
   if (!netlifyUrlLine) {
     core.setFailed(`deploy-netlify failed to find the deployment line in:\n\n${netlifyLog}`);
     return;
diff --git a/.github/workflows/build-status.yml b/.github/workflows/build-status.yml
index 12067e3a..708bd057 100644
--- a/.github/workflows/build-status.yml
+++ b/.github/workflows/build-status.yml
@@ -25,7 +25,7 @@ jobs:
           path: '**/node_modules'
           key: ${{runner.os}}-modules-${{hashFiles('**/yarn.lock')}}
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Install dependencies
@@ -47,7 +47,7 @@ jobs:
           path: '**/node_modules'
           key: ${{runner.os}}-modules-${{hashFiles('**/yarn.lock')}}-${{matrix.node-version}}
       - name: Using Node v${{matrix.node-version}}
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: ${{matrix.node-version}}
       - name: Install dependencies
@@ -84,7 +84,7 @@ jobs:
           path: '**/node_modules'
           key: ${{runner.os}}-modules-${{hashFiles('**/yarn.lock')}}-${{matrix.node-version}}
       - name: Using Node v${{matrix.node-version}}
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: ${{matrix.node-version}}
       - name: Install dependencies
@@ -106,7 +106,7 @@ jobs:
     steps:
       - uses: actions/checkout@v2.4.0
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Check package score using skypack
@@ -121,7 +121,7 @@ jobs:
           path: '**/node_modules'
           key: ${{runner.os}}-modules-${{hashFiles('**/yarn.lock')}}-v16.x
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Install dependencies
@@ -149,7 +149,7 @@ jobs:
           path: '**/node_modules'
           key: ${{runner.os}}-modules-${{hashFiles('**/yarn.lock')}}-v16.x
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Install dependencies
@@ -174,7 +174,7 @@ jobs:
     steps:
       - uses: actions/checkout@v2.4.0
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Download production package
@@ -209,7 +209,7 @@ jobs:
     steps:
       - uses: actions/checkout@v2.4.0
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Download production package
@@ -251,7 +251,7 @@ jobs:
     steps:
       - uses: actions/checkout@v2.4.0
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Download production package
@@ -263,7 +263,7 @@ jobs:
         with:
           path: 'test/legacy/node-8'
       - name: Using Node v8.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '8.x'
       - name: Test test/legacy/node-8/
@@ -385,7 +385,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
           registry-url: 'https://registry.npmjs.org'
diff --git a/.github/workflows/please-actions.yml b/.github/workflows/please-actions.yml
index d9b3a770..fa9935d9 100644
--- a/.github/workflows/please-actions.yml
+++ b/.github/workflows/please-actions.yml
@@ -28,7 +28,7 @@ jobs:
         env:
           GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
       - name: Using Node v16.x
-        uses: actions/setup-node@v2.5.0
+        uses: actions/setup-node@v2.5.1
         with:
           node-version: '16.x'
       - name: Install fast-check dependencies
@@ -79,10 +79,20 @@ jobs:
           git config --local user.email "github-actions[bot]@users.noreply.github.com"
           git config --local user.name "github-actions[bot]"
       - name: Merge BASE_REF into current PR
-        run: |
-          export BASE_REF=$(hub api repos/gopigment/monorepo/pulls/${{github.event.issue.number}} | jq ".base.ref" --raw-output)
-          echo "BASE_REF is: ${BASE_REF}"
-          git pull --no-rebase origin "${BASE_REF}"
+        uses: actions/github-script@v5
+        with:
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          script: |
+            const util = require('util');
+            const execFile = util.promisify(require('child_process').execFile);
+            const owner = context.payload.repository.owner.login;
+            const repo = context.payload.repository.name;
+            const pull_number = context.payload.issue.number;
+            const prDetails = await github.rest.pulls.get({ owner, repo, pull_number });
+            const baseRef = prDetails.data.base.ref;
+            const { stdout } = await execFile("git", ["pull", "--no-rebase", "origin", baseRef]);
+            const body = "Merge of `" + baseRef + "` resulted into:\n\n```txt\n" + stdout.trim() + "\n```\n\nWill try to push!";
+            await github.rest.issues.createComment({ issue_number: pull_number, owner, repo, body });
         env:
           GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
       - name: Push
@@ -104,3 +114,27 @@ jobs:
         uses: actions/github-script@v5
         with:
           script: console.log(JSON.stringify(context, undefined, 2));
+      - name: Dump GitHub context
+        env:
+          GITHUB_CONTEXT: ${{toJSON(github)}}
+        run: echo "$GITHUB_CONTEXT"
+      - name: Dump job context
+        env:
+          JOB_CONTEXT: ${{toJSON(job)}}
+        run: echo "$JOB_CONTEXT"
+      - name: Dump steps context
+        env:
+          STEPS_CONTEXT: ${{toJSON(steps)}}
+        run: echo "$STEPS_CONTEXT"
+      - name: Dump runner context
+        env:
+          RUNNER_CONTEXT: ${{toJSON(runner)}}
+        run: echo "$RUNNER_CONTEXT"
+      - name: Dump strategy context
+        env:
+          STRATEGY_CONTEXT: ${ toJSON(strategy)}}
+        run: echo "$STRATEGY_CONTEXT"
+      - name: Dump matrix context
+        env:
+          MATRIX_CONTEXT: ${{toJSON(matrix)}}
+        run: echo "$MATRIX_CONTEXT"
diff --git a/documentation/Arbitraries.md b/documentation/Arbitraries.md
index e88cfa89..663aeb12 100644
--- a/documentation/Arbitraries.md
+++ b/documentation/Arbitraries.md
@@ -2447,7 +2447,12 @@ fc.array(fc.nat(), {minLength: 5, maxLength: 7})
 - `arb` — _arbitrary instance responsible to generate values_
 - `minLength?` — default: `0` — _minimal length (included)_
 - `maxLength?` — default: `2 * minLength + 10` — _maximal length (included)_
-- `compare?` — default: `(a, b) => a === b` — _custom compare function used to distinguish duplicates in order to remove them from the resulting array_
+- `compare?` — default: `{type: 'IsStrictlyEqual', selector: v => v}` equivalent to `(a, b) => a === b` — _custom compare operator used to distinguish duplicates in order to remove them from the resulting array. it can either be an object `{type,selector}` or a function returning `true` whenever items are equivalent (function-based is less optimized for large arrays)_
+  - `type` can be one of:
+    - `IsStrictlyEqual` to rely on `===` to compare items ([more details](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal))
+    - `SameValue` to rely on `Object.is` to compare items ([more details](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevalue))
+    - `SameValueZero` to rely on the same logic as the one of `Set` or `Map` to compare items ([more details](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero))
+  - `selector` to define how to project values before comparing them together
 
 *&#8195;Usages*
 
@@ -2461,18 +2466,29 @@ fc.set(fc.nat(99), {maxLength: 3})
 fc.set(fc.nat(99), {minLength: 5, maxLength: 7})
 // Examples of generated values: [4,3,29,1,48,25], [53,44,67,56,49,42], [69,6,47,13,20,3,58], [5,35,70,0,6,27], [3,5,0,70,6,20]…
 
-fc.set(fc.hexaString(), {compare: (s1, s2) => s1.length === s2.length})
+fc.set(fc.hexaString(), {compare: {selector: s => s.length}})
 // Note: Resulting arrays will never contain two strings with the same number of characters
+// Examples of generated values:
+// • []
+// • ["9c06b50b60","030fcd41f","1f89","84d8c","7cb","3","e9b9af","c5ee4a9","","eb301204"]
+// • ["f296eb0"]
+// • ["","a76be85","8"]
+// • ["d3","912966f8","82b07f","822","0d714","","2ff2623190","137c99f","eee7"]
+// • …
+
+fc.set(fc.hexaString(), {compare: (s1, s2) => s1.length === s2.length})
+// Note: Same behaviour as the one above BUT less optimized in terms of runtime (function-based compare
+// has poor performances for large arrays)
 // Examples of generated values: ["20",""], [], ["447","","893c89edb1","b31a5"], ["79429d9",""], ["0","c20ea408b9","1f1574","117d"]…
 
-fc.set(fc.hexaString(), {minLength: 5, maxLength: 10, compare: (s1, s2) => s1.length === s2.length})
+fc.set(fc.hexaString(), {minLength: 5, maxLength: 10, compare: {selector: s => s.length}})
 // Note: Resulting arrays will never contain two strings with the same number of characters and it will contain between 5 and 10 strings
 // Examples of generated values:
-// • ["","18028609c9","8b9e4d","bd945ddc","7262","63636"]
-// • ["7e","59ae73fd9","d200d","d504","","4d18e69a","fb2"]
-// • ["65655ac0b","c20","02f6","42ff080184","80","f04e066",""]
-// • ["320","","1e","1","2ce0","5fb80","41265c649c"]
-// • ["","b","cb03601","e3","052844fe0a","c410","82cfcb523"]
+// • ["0d4","1cef","20d51","74","bb8afd3e0b","916c7c4"]
+// • ["fd0d3c99f9","95b0","f42","f","0cc997"]
+// • ["835f9","692caf3d1","46f08be","","8","ab46","22"]
+// • ["","a31e3f74c","1ae0b","3c17","782","a","eb","cb724e","68c79c08db"]
+// • ["c2f3c9621f","","32352ef","6e","91b0ef","11c","5f6c34d88","8509","8","21131"]
 // • …
 ```
 </details>
diff --git a/example/package.json b/example/package.json
index 027b785d..2aa002b3 100644
--- a/example/package.json
+++ b/example/package.json
@@ -7,7 +7,7 @@
   },
   "dependencies": {
     "@testing-library/dom": "^8.11.1",
-    "@testing-library/jest-dom": "^5.15.1",
+    "@testing-library/jest-dom": "^5.16.1",
     "@testing-library/react": "^12.1.2",
     "@testing-library/user-event": "^13.5.0",
     "fast-check": "*",
@@ -18,11 +18,11 @@
   },
   "devDependencies": {
     "@types/jest": "^27.0.3",
-    "@types/lodash": "^4.14.177",
+    "@types/lodash": "^4.14.178",
     "@types/react": "^17.0.37",
     "jest": "^26.6.3",
     "ts-jest": "^26.5.6",
-    "typescript": "^4.5.2"
+    "typescript": "^4.5.4"
   },
   "author": "Nicolas DUBIEN <github@dubien.org>",
   "license": "MIT",
diff --git a/example/yarn.lock b/example/yarn.lock
index 5168885b..7a1db6ba 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -232,15 +232,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/runtime-corejs3@^7.10.2":
-  version "7.12.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4"
-  integrity sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==
-  dependencies:
-    core-js-pure "^3.0.0"
-    regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2":
   version "7.12.5"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
   integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@@ -519,14 +511,14 @@
     lz-string "^1.4.4"
     pretty-format "^27.0.2"
 
-"@testing-library/jest-dom@^5.15.1":
-  version "5.15.1"
-  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.15.1.tgz#4c49ba4d244f235aec53f0a83498daeb4ee06c33"
-  integrity sha512-kmj8opVDRE1E4GXyLlESsQthCXK7An28dFWxhiMwD7ZUI7ZxA6sjdJRxLerD9Jd8cHX4BDc1jzXaaZKqzlUkvg==
+"@testing-library/jest-dom@^5.16.1":
+  version "5.16.1"
+  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz#3db7df5ae97596264a7da9696fe14695ba02e51f"
+  integrity sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==
   dependencies:
     "@babel/runtime" "^7.9.2"
     "@types/testing-library__jest-dom" "^5.9.1"
-    aria-query "^4.2.2"
+    aria-query "^5.0.0"
     chalk "^3.0.0"
     css "^3.0.0"
     css.escape "^1.5.1"
@@ -621,10 +613,10 @@
     jest-diff "^27.0.0"
     pretty-format "^27.0.0"
 
-"@types/lodash@^4.14.177":
-  version "4.14.177"
-  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578"
-  integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==
+"@types/lodash@^4.14.178":
+  version "4.14.178"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
+  integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
 
 "@types/node@*":
   version "14.14.20"
@@ -778,14 +770,6 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
-aria-query@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
-  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
-  dependencies:
-    "@babel/runtime" "^7.10.2"
-    "@babel/runtime-corejs3" "^7.10.2"
-
 aria-query@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
@@ -1161,11 +1145,6 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
-core-js-pure@^3.0.0:
-  version "3.8.2"
-  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.8.2.tgz#286f885c0dac1cdcd6d78397392abc25ddeca225"
-  integrity sha512-v6zfIQqL/pzTVAbZvYUozsxNfxcFb6Ks3ZfEbuneJl3FW9Jb8F6vLWB6f+qTmAu72msUdyb84V8d/yBFf7FNnw==
-
 core-util-is@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3694,10 +3673,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@^4.5.2:
-  version "4.5.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
-  integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
+typescript@^4.5.4:
+  version "4.5.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
+  integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
 
 union-value@^1.0.0:
   version "1.0.1"
diff --git a/src/arbitrary/_internals/builders/AnyArbitraryBuilder.ts b/src/arbitrary/_internals/builders/AnyArbitraryBuilder.ts
index a17e83c0..ba3657c7 100644
--- a/src/arbitrary/_internals/builders/AnyArbitraryBuilder.ts
+++ b/src/arbitrary/_internals/builders/AnyArbitraryBuilder.ts
@@ -30,7 +30,7 @@ import { letrec } from '../../letrec';
 function entriesOf<T, U>(keyArb: Arbitrary<T>, valueArb: Arbitrary<U>, maxKeys: number) {
   // TODO - Depending on the situation, the selected compare function might not be appropriate
   // eg.: in the case of Map, NaN is NaN but NaN !== NaN
-  return convertToNext(set(tuple(keyArb, valueArb), { maxLength: maxKeys, compare: (t1, t2) => t1[0] === t2[0] }));
+  return convertToNext(set(tuple(keyArb, valueArb), { maxLength: maxKeys, compare: { selector: (t) => t[0] } }));
 }
 
 /** @internal */
diff --git a/src/arbitrary/_internals/helpers/SameValueSet.ts b/src/arbitrary/_internals/helpers/SameValueSet.ts
new file mode 100644
index 00000000..f4c80444
--- /dev/null
+++ b/src/arbitrary/_internals/helpers/SameValueSet.ts
@@ -0,0 +1,49 @@
+import { CustomSet } from '../interfaces/CustomSet';
+
+/**
+ * CustomSet based on "SameValue" as defined by:
+ * https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevalue
+ *
+ * And coming with the ability to select a sub-value from the received one.
+ * @internal
+ */
+export class SameValueSet<T, U> implements CustomSet<T> {
+  // In JavaScript, Set uses "SameValueZero" to compare two items
+  // thus a specific treatment would have to be done to properly distinguish -0 from +0
+  private readonly selectedItemsExceptMinusZero: Set<U>;
+  private readonly data: T[];
+  private hasMinusZero: boolean;
+
+  constructor(private readonly selector: (value: T) => U) {
+    this.selectedItemsExceptMinusZero = new Set();
+    this.data = [];
+    this.hasMinusZero = false;
+  }
+
+  tryAdd(value: T): boolean {
+    const selected = this.selector(value);
+    if (Object.is(selected, -0)) {
+      if (this.hasMinusZero) {
+        return false;
+      }
+      this.data.push(value);
+      this.hasMinusZero = true;
+      return true;
+    }
+    const sizeBefore = this.selectedItemsExceptMinusZero.size;
+    this.selectedItemsExceptMinusZero.add(selected);
+    if (sizeBefore !== this.selectedItemsExceptMinusZero.size) {
+      this.data.push(value);
+      return true;
+    }
+    return false;
+  }
+
+  size(): number {
+    return this.data.length;
+  }
+
+  getData(): T[] {
+    return this.data;
+  }
+}
diff --git a/src/arbitrary/_internals/helpers/SameValueZeroSet.ts b/src/arbitrary/_internals/helpers/SameValueZeroSet.ts
new file mode 100644
index 00000000..0dcfb534
--- /dev/null
+++ b/src/arbitrary/_internals/helpers/SameValueZeroSet.ts
@@ -0,0 +1,38 @@
+import { CustomSet } from '../interfaces/CustomSet';
+
+/**
+ * CustomSet based on "SameValueZero" as defined by:
+ * https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero
+ *
+ * And coming with the ability to select a sub-value from the received one.
+ * @internal
+ */
+export class SameValueZeroSet<T, U> implements CustomSet<T> {
+  // In JavaScript, Set uses "SameValueZero" to compare two items
+  private readonly selectedItems: Set<U>;
+  private readonly data: T[];
+
+  constructor(private readonly selector: (value: T) => U) {
+    this.selectedItems = new Set();
+    this.data = [];
+  }
+
+  tryAdd(value: T): boolean {
+    const selected = this.selector(value);
+    const sizeBefore = this.selectedItems.size;
+    this.selectedItems.add(selected);
+    if (sizeBefore !== this.selectedItems.size) {
+      this.data.push(value);
+      return true;
+    }
+    return false;
+  }
+
+  size(): number {
+    return this.data.length;
+  }
+
+  getData(): T[] {
+    return this.data;
+  }
+}
diff --git a/src/arbitrary/_internals/helpers/StrictlyEqualSet.ts b/src/arbitrary/_internals/helpers/StrictlyEqualSet.ts
new file mode 100644
index 00000000..4f7816cc
--- /dev/null
+++ b/src/arbitrary/_internals/helpers/StrictlyEqualSet.ts
@@ -0,0 +1,45 @@
+import { CustomSet } from '../interfaces/CustomSet';
+
+/**
+ * CustomSet based on "strict equality" as defined by:
+ * https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal
+ *
+ * And coming with the ability to select a sub-value from the received one.
+ * @internal
+ */
+export class StrictlyEqualSet<T, U> implements CustomSet<T> {
+  // Warning: Set uses SameValueZero equality
+  // SameValueZero means:
+  // - NaN equals NaN   => unexpected for ===
+  // - 0   equals -0    => OK
+  private readonly selectedItemsExceptNaN: Set<U>;
+  private readonly data: T[];
+
+  constructor(private readonly selector: (value: T) => U) {
+    this.selectedItemsExceptNaN = new Set();
+    this.data = [];
+  }
+
+  tryAdd(value: T): boolean {
+    const selected = this.selector(value);
+    if (Number.isNaN(selected)) {
+      this.data.push(value);
+      return true;
+    }
+    const sizeBefore = this.selectedItemsExceptNaN.size;
+    this.selectedItemsExceptNaN.add(selected);
+    if (sizeBefore !== this.selectedItemsExceptNaN.size) {
+      this.data.push(value);
+      return true;
+    }
+    return false;
+  }
+
+  size(): number {
+    return this.data.length;
+  }
+
+  getData(): T[] {
+    return this.data;
+  }
+}
diff --git a/src/arbitrary/dictionary.ts b/src/arbitrary/dictionary.ts
index e31c08f7..0101e22e 100644
--- a/src/arbitrary/dictionary.ts
+++ b/src/arbitrary/dictionary.ts
@@ -15,7 +15,7 @@ import { keyValuePairsToObjectMapper, keyValuePairsToObjectUnmapper } from './_i
  */
 export function dictionary<T>(keyArb: Arbitrary<string>, valueArb: Arbitrary<T>): Arbitrary<Record<string, T>> {
   return convertFromNext(
-    convertToNext(set(tuple(keyArb, valueArb), { compare: (t1, t2) => t1[0] === t2[0] })).map(
+    convertToNext(set(tuple(keyArb, valueArb), { compare: { selector: (t) => t[0] } })).map(
       keyValuePairsToObjectMapper,
       keyValuePairsToObjectUnmapper
     )
diff --git a/src/arbitrary/set.ts b/src/arbitrary/set.ts
index b6eabc1d..3afad43f 100644
--- a/src/arbitrary/set.ts
+++ b/src/arbitrary/set.ts
@@ -5,6 +5,29 @@ import { maxLengthFromMinLength } from './_internals/helpers/MaxLengthFromMinLen
 import { CustomSetBuilder } from './_internals/interfaces/CustomSet';
 import { CustomEqualSet } from './_internals/helpers/CustomEqualSet';
 import { NextValue } from '../check/arbitrary/definition/NextValue';
+import { StrictlyEqualSet } from './_internals/helpers/StrictlyEqualSet';
+import { SameValueSet } from './_internals/helpers/SameValueSet';
+import { SameValueZeroSet } from './_internals/helpers/SameValueZeroSet';
+
+/** @internal */
+function buildSetBuilder<T>(constraints: SetConstraints<T>): CustomSetBuilder<NextValue<T>> {
+  const compare: NonNullable<typeof constraints.compare> = constraints.compare || {};
+  if (typeof compare === 'function') {
+    const isEqualForBuilder = (nextA: NextValue<T>, nextB: NextValue<T>) => compare(nextA.value_, nextB.value_);
+    return () => new CustomEqualSet(isEqualForBuilder);
+  }
+  const selector = compare.selector || ((v) => v);
+  const refinedSelector = (next: NextValue<T>) => selector(next.value_);
+  switch (compare.type) {
+    case 'SameValue':
+      return () => new SameValueSet(refinedSelector);
+    case 'SameValueZero':
+      return () => new SameValueZeroSet(refinedSelector);
+    case 'IsStrictlyEqual':
+    case undefined:
+      return () => new StrictlyEqualSet(refinedSelector);
+  }
+}
 
 /**
  * Build fully set SetConstraints from a partial data
@@ -15,9 +38,7 @@ function buildCompleteSetConstraints<T>(
 ): Required<Omit<SetConstraints<T>, 'compare'>> & { setBuilder: CustomSetBuilder<NextValue<T>> } {
   const minLength = constraints.minLength !== undefined ? constraints.minLength : 0;
   const maxLength = constraints.maxLength !== undefined ? constraints.maxLength : maxLengthFromMinLength(minLength);
-  const compare = constraints.compare !== undefined ? constraints.compare : (a: T, b: T) => a === b;
-  const isEqualForBuilder = (nextA: NextValue<T>, nextB: NextValue<T>) => compare(nextA.value_, nextB.value_);
-  const setBuilder = () => new CustomEqualSet(isEqualForBuilder);
+  const setBuilder = buildSetBuilder(constraints);
   return { minLength, maxLength, setBuilder };
 }
 
@@ -57,6 +78,27 @@ function extractSetConstraints<T>(
   return { minLength: sargs[0], maxLength: sargs[1], compare: sargs[2] }; // set(arb, minLength, maxLength, compare)
 }
 
+/**
+ * Type used to define a constraints to compare values together based on a selector approach
+ * @remarks Since 2.21.0
+ * @public
+ */
+export type SetConstraintsSelector<T> = {
+  /**
+   * The operator to be used to compare the values:
+   * - IsStrictlyEqual behaves like `===` — {@link https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal}
+   * - SameValue behaves like `Object.is` — {@link https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevalue}
+   * - SameValueZero behaves like `Set` or `Map` — {@link https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero}
+   * @remarks Since 2.21.0
+   */
+  type?: 'IsStrictlyEqual' | 'SameValue' | 'SameValueZero';
+  /**
+   * How we should project the values before comparing them together
+   * @remarks Since 2.21.0
+   */
+  selector?: (v: T) => unknown;
+};
+
 /**
  * Constraints to be applied on {@link set}
  * @remarks Since 2.4.0
@@ -74,10 +116,21 @@ export interface SetConstraints<T> {
    */
   maxLength?: number;
   /**
-   * Compare function - Return true when the two values are equals
+   * Compare operator - It can either be:
+   * - a selector based definition consisting into projecting the value (if needed) onto a relevant
+   *   value (see selector) and comparing all those values together based on a selected comparison
+   *   operator (see type)
+   * - a compare function returning true whenever the two values are equal (not recommended for
+   *   large arrays as this approach is more costly in terms of computation and does not scale)
+   *
+   * It defaults to:
+   * - type = "IsStrictlyEqual"
+   * - selector = v =&gt; v
+   * Which is equivalent (except performances) to: compare = (a, b) =&gt; a === b
+   *
    * @remarks Since 2.4.0
    */
-  compare?: (a: T, b: T) => boolean;
+  compare?: ((a: T, b: T) => boolean) | SetConstraintsSelector<T>;
 }
 
 /**
diff --git a/src/arbitrary/sparseArray.ts b/src/arbitrary/sparseArray.ts
index 9a6e4f69..cdd3ccf8 100644
--- a/src/arbitrary/sparseArray.ts
+++ b/src/arbitrary/sparseArray.ts
@@ -83,7 +83,7 @@ export function sparseArray<T>(arb: Arbitrary<T>, constraints: SparseArrayConstr
       set(tuple(nat(maxIndexAuthorized), arb), {
         minLength: minNumElements,
         maxLength: resultedMaxNumElements,
-        compare: (itemA, itemB) => itemA[0] === itemB[0],
+        compare: { selector: (item) => item[0] },
       })
     ).map(
       (items) => {
diff --git a/src/fast-check-default.ts b/src/fast-check-default.ts
index e46d4340..9525c051 100644
--- a/src/fast-check-default.ts
+++ b/src/fast-check-default.ts
@@ -74,7 +74,7 @@ import { unicodeJson } from './arbitrary/unicodeJson';
 import { oneof, OneOfValue, OneOfConstraints } from './arbitrary/oneof';
 import { option, OptionConstraints } from './arbitrary/option';
 import { record, RecordConstraints, RecordValue } from './arbitrary/record';
-import { set, SetConstraints } from './arbitrary/set';
+import { set, SetConstraints, SetConstraintsSelector } from './arbitrary/set';
 import { infiniteStream } from './arbitrary/infiniteStream';
 import { asciiString } from './arbitrary/asciiString';
 import { base64String } from './arbitrary/base64String';
@@ -383,6 +383,7 @@ export {
   RecordConstraints,
   SchedulerConstraints,
   SetConstraints,
+  SetConstraintsSelector,
   SparseArrayConstraints,
   StringSharedConstraints,
   SubarrayConstraints,


Test patch:
diff --git a/test/unit/arbitrary/_internals/helpers/SameValueSet.spec.ts b/test/unit/arbitrary/_internals/helpers/SameValueSet.spec.ts
new file mode 100644
index 00000000..15be0cd9
--- /dev/null
+++ b/test/unit/arbitrary/_internals/helpers/SameValueSet.spec.ts
@@ -0,0 +1,125 @@
+import * as fc from '../../../../../lib/fast-check';
+import { SameValueSet } from '../../../../../src/arbitrary/_internals/helpers/SameValueSet';
+
+describe('SameValueSet', () => {
+  it('should discard strictly equal items', () => {
+    // Arrange
+    const s = new SameValueSet((item) => item);
+
+    // Arrange / Act
+    expect(s.tryAdd(1)).toBe(true);
+    expect(s.tryAdd(5)).toBe(true);
+    expect(s.tryAdd(-0)).toBe(true);
+    expect(s.tryAdd(0)).toBe(true);
+    expect(s.tryAdd(Number.NaN)).toBe(true);
+    expect(s.tryAdd(Number.NaN)).toBe(false);
+    expect(s.tryAdd(1)).toBe(false);
+    expect(s.tryAdd(5)).toBe(false);
+    expect(s.tryAdd(6)).toBe(true);
+    expect(s.tryAdd('6')).toBe(true);
+    expect(s.tryAdd(-0)).toBe(false);
+    expect(s.tryAdd(0)).toBe(false);
+    expect(s.getData()).toEqual([1, 5, -0, 0, Number.NaN, 6, '6']);
+  });
+
+  it('should discard strictly equal items after selector', () => {
+    // Arrange
+    const s = new SameValueSet((item: { value?: unknown }) => item.value);
+
+    // Arrange / Act
+    expect(s.tryAdd({ value: 1 })).toBe(true);
+    expect(s.tryAdd({ value: 5 })).toBe(true);
+    expect(s.tryAdd({ value: -0 })).toBe(true);
+    expect(s.tryAdd({ value: 0 })).toBe(true);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(true);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(false);
+    expect(s.tryAdd({ value: 1 })).toBe(false);
+    expect(s.tryAdd({ value: 5 })).toBe(false);
+    expect(s.tryAdd({ value: 6 })).toBe(true);
+    expect(s.tryAdd({ value: '6' })).toBe(true);
+    expect(s.tryAdd({})).toBe(true);
+    expect(s.tryAdd({ value: undefined })).toBe(false);
+    expect(s.tryAdd({ value: -0 })).toBe(false);
+    expect(s.tryAdd({ value: 0 })).toBe(false);
+    expect(s.getData()).toEqual([
+      { value: 1 },
+      { value: 5 },
+      { value: -0 },
+      { value: 0 },
+      { value: Number.NaN },
+      { value: 6 },
+      { value: '6' },
+      {},
+    ]);
+  });
+
+  it('should increase the size whenever tryAdd returns true', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 1 }), (rawItems) => {
+        // Arrange
+        let expectedSize = 0;
+        const s = new SameValueSet((item) => item);
+
+        // Act / Assert
+        for (const item of rawItems) {
+          if (s.tryAdd(item)) {
+            expectedSize += 1;
+          }
+          expect(s.size()).toBe(expectedSize);
+        }
+      })
+    );
+  });
+
+  it('should never have two equivalent items in the Set', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new SameValueSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 0; i !== data.length; ++i) {
+          for (let j = i + 1; j !== data.length; ++j) {
+            expect(isSameValue(data[i], data[j])).toBe(false);
+          }
+        }
+      })
+    );
+  });
+
+  it('should preserve add order', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new SameValueSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 1; i < data.length; ++i) {
+          const indexPrevious = rawItems.findIndex((item) => isSameValue(item, data[i - 1]));
+          const indexCurrent = rawItems.findIndex((item) => isSameValue(item, data[i]));
+          expect(indexPrevious).not.toBe(-1);
+          expect(indexCurrent).not.toBe(-1);
+          expect(indexPrevious).toBeLessThan(indexCurrent);
+        }
+      })
+    );
+  });
+});
+
+// Helpers
+
+function isSameValue(a: unknown, b: unknown): boolean {
+  return Object.is(a, b);
+}
diff --git a/test/unit/arbitrary/_internals/helpers/SameValueZeroSet.spec.ts b/test/unit/arbitrary/_internals/helpers/SameValueZeroSet.spec.ts
new file mode 100644
index 00000000..dcfecfbe
--- /dev/null
+++ b/test/unit/arbitrary/_internals/helpers/SameValueZeroSet.spec.ts
@@ -0,0 +1,124 @@
+import * as fc from '../../../../../lib/fast-check';
+import { SameValueZeroSet } from '../../../../../src/arbitrary/_internals/helpers/SameValueZeroSet';
+
+describe('SameValueZeroSet', () => {
+  it('should discard strictly equal items', () => {
+    // Arrange
+    const s = new SameValueZeroSet((item) => item);
+
+    // Arrange / Act
+    expect(s.tryAdd(1)).toBe(true);
+    expect(s.tryAdd(5)).toBe(true);
+    expect(s.tryAdd(-0)).toBe(true);
+    expect(s.tryAdd(0)).toBe(false);
+    expect(s.tryAdd(Number.NaN)).toBe(true);
+    expect(s.tryAdd(Number.NaN)).toBe(false);
+    expect(s.tryAdd(1)).toBe(false);
+    expect(s.tryAdd(5)).toBe(false);
+    expect(s.tryAdd(6)).toBe(true);
+    expect(s.tryAdd('6')).toBe(true);
+    expect(s.tryAdd(-0)).toBe(false);
+    expect(s.tryAdd(0)).toBe(false);
+    expect(s.getData()).toEqual([1, 5, -0, Number.NaN, 6, '6']);
+  });
+
+  it('should discard strictly equal items after selector', () => {
+    // Arrange
+    const s = new SameValueZeroSet((item: { value?: unknown }) => item.value);
+
+    // Arrange / Act
+    expect(s.tryAdd({ value: 1 })).toBe(true);
+    expect(s.tryAdd({ value: 5 })).toBe(true);
+    expect(s.tryAdd({ value: -0 })).toBe(true);
+    expect(s.tryAdd({ value: 0 })).toBe(false);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(true);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(false);
+    expect(s.tryAdd({ value: 1 })).toBe(false);
+    expect(s.tryAdd({ value: 5 })).toBe(false);
+    expect(s.tryAdd({ value: 6 })).toBe(true);
+    expect(s.tryAdd({ value: '6' })).toBe(true);
+    expect(s.tryAdd({})).toBe(true);
+    expect(s.tryAdd({ value: undefined })).toBe(false);
+    expect(s.tryAdd({ value: -0 })).toBe(false);
+    expect(s.tryAdd({ value: 0 })).toBe(false);
+    expect(s.getData()).toEqual([
+      { value: 1 },
+      { value: 5 },
+      { value: -0 },
+      { value: Number.NaN },
+      { value: 6 },
+      { value: '6' },
+      {},
+    ]);
+  });
+
+  it('should increase the size whenever tryAdd returns true', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 1 }), (rawItems) => {
+        // Arrange
+        let expectedSize = 0;
+        const s = new SameValueZeroSet((item) => item);
+
+        // Act / Assert
+        for (const item of rawItems) {
+          if (s.tryAdd(item)) {
+            expectedSize += 1;
+          }
+          expect(s.size()).toBe(expectedSize);
+        }
+      })
+    );
+  });
+
+  it('should never have two equivalent items in the Set', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new SameValueZeroSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 0; i !== data.length; ++i) {
+          for (let j = i + 1; j !== data.length; ++j) {
+            expect(isSameValueZero(data[i], data[j])).toBe(false);
+          }
+        }
+      })
+    );
+  });
+
+  it('should preserve add order', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new SameValueZeroSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 1; i < data.length; ++i) {
+          const indexPrevious = rawItems.findIndex((item) => isSameValueZero(item, data[i - 1]));
+          const indexCurrent = rawItems.findIndex((item) => isSameValueZero(item, data[i]));
+          expect(indexPrevious).not.toBe(-1);
+          expect(indexCurrent).not.toBe(-1);
+          expect(indexPrevious).toBeLessThan(indexCurrent);
+        }
+      })
+    );
+  });
+});
+
+// Helpers
+
+function isSameValueZero(a: unknown, b: unknown): boolean {
+  return Object.is(a, b) || (a === 0 && b === 0);
+}
diff --git a/test/unit/arbitrary/_internals/helpers/StrictlyEqualSet.spec.ts b/test/unit/arbitrary/_internals/helpers/StrictlyEqualSet.spec.ts
new file mode 100644
index 00000000..08614e23
--- /dev/null
+++ b/test/unit/arbitrary/_internals/helpers/StrictlyEqualSet.spec.ts
@@ -0,0 +1,125 @@
+import * as fc from '../../../../../lib/fast-check';
+import { StrictlyEqualSet } from '../../../../../src/arbitrary/_internals/helpers/StrictlyEqualSet';
+
+describe('StrictyEqualSet', () => {
+  it('should discard strictly equal items', () => {
+    // Arrange
+    const s = new StrictlyEqualSet((item) => item);
+
+    // Arrange / Act
+    expect(s.tryAdd(1)).toBe(true);
+    expect(s.tryAdd(5)).toBe(true);
+    expect(s.tryAdd(-0)).toBe(true);
+    expect(s.tryAdd(0)).toBe(false);
+    expect(s.tryAdd(Number.NaN)).toBe(true);
+    expect(s.tryAdd(Number.NaN)).toBe(true);
+    expect(s.tryAdd(1)).toBe(false);
+    expect(s.tryAdd(5)).toBe(false);
+    expect(s.tryAdd(6)).toBe(true);
+    expect(s.tryAdd('6')).toBe(true);
+    expect(s.tryAdd(-0)).toBe(false);
+    expect(s.tryAdd(0)).toBe(false);
+    expect(s.getData()).toEqual([1, 5, -0, Number.NaN, Number.NaN, 6, '6']);
+  });
+
+  it('should discard strictly equal items after selector', () => {
+    // Arrange
+    const s = new StrictlyEqualSet((item: { value?: unknown }) => item.value);
+
+    // Arrange / Act
+    expect(s.tryAdd({ value: 1 })).toBe(true);
+    expect(s.tryAdd({ value: 5 })).toBe(true);
+    expect(s.tryAdd({ value: -0 })).toBe(true);
+    expect(s.tryAdd({ value: 0 })).toBe(false);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(true);
+    expect(s.tryAdd({ value: Number.NaN })).toBe(true);
+    expect(s.tryAdd({ value: 1 })).toBe(false);
+    expect(s.tryAdd({ value: 5 })).toBe(false);
+    expect(s.tryAdd({ value: 6 })).toBe(true);
+    expect(s.tryAdd({ value: '6' })).toBe(true);
+    expect(s.tryAdd({})).toBe(true);
+    expect(s.tryAdd({ value: undefined })).toBe(false);
+    expect(s.tryAdd({ value: -0 })).toBe(false);
+    expect(s.tryAdd({ value: 0 })).toBe(false);
+    expect(s.getData()).toEqual([
+      { value: 1 },
+      { value: 5 },
+      { value: -0 },
+      { value: Number.NaN },
+      { value: Number.NaN },
+      { value: 6 },
+      { value: '6' },
+      {},
+    ]);
+  });
+
+  it('should increase the size whenever tryAdd returns true', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 1 }), (rawItems) => {
+        // Arrange
+        let expectedSize = 0;
+        const s = new StrictlyEqualSet((item) => item);
+
+        // Act / Assert
+        for (const item of rawItems) {
+          if (s.tryAdd(item)) {
+            expectedSize += 1;
+          }
+          expect(s.size()).toBe(expectedSize);
+        }
+      })
+    );
+  });
+
+  it('should never have two equivalent items in the Set', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new StrictlyEqualSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 0; i !== data.length; ++i) {
+          for (let j = i + 1; j !== data.length; ++j) {
+            expect(isStrictlyEqual(data[i], data[j])).toBe(false);
+          }
+        }
+      })
+    );
+  });
+
+  it('should preserve add order', () => {
+    fc.assert(
+      fc.property(fc.array(fc.anything(), { minLength: 2 }), (rawItems) => {
+        // Arrange
+        const s = new StrictlyEqualSet((item) => item);
+
+        // Act
+        for (const item of rawItems) {
+          s.tryAdd(item);
+        }
+        const data = s.getData();
+
+        // Assert
+        for (let i = 1; i < data.length; ++i) {
+          const indexPrevious = rawItems.findIndex((item) => isStrictlyEqual(item, data[i - 1]));
+          const indexCurrent = rawItems.findIndex((item) => isStrictlyEqual(item, data[i]));
+          expect(indexPrevious).not.toBe(-1);
+          expect(indexCurrent).not.toBe(-1);
+          expect(indexPrevious).toBeLessThan(indexCurrent);
+        }
+      })
+    );
+  });
+});
+
+// Helpers
+
+function isStrictlyEqual(a: unknown, b: unknown): boolean {
+  return a === b;
+}