Repository: dubzzz/fast-check
Pull request #2590 (2022-01-01 22:01:34)

⬆️ Bump @testing-library/jest-dom from 5.15.1 to 5.16.1 in /example
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.15.1 to 5.16.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/testing-library/jest-dom/releases"><code>@​testing-library/jest-dom</code>'s releases</a>.</em></p>
<blockquote>
<h2>v5.16.1</h2>
<h2><a href="https://github.com/testing-library/jest-dom/compare/v5.16.0...v5.16.1">5.16.1</a> (2021-12-06)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Improve <code>toHaveClass</code> error message format (<a href="https://github-redirect.dependabot.com/testing-library/jest-dom/issues/405">#405</a>) (<a href="https://github.com/testing-library/jest-dom/commit/a9beb47455dae0f455ddacc473d0ddabe09f0b43">a9beb47</a>)</li>
</ul>
<h2>v5.16.0</h2>
<h1><a href="https://github.com/testing-library/jest-dom/compare/v5.15.1...v5.16.0">5.16.0</a> (2021-12-03)</h1>
<h3>Features</h3>
<ul>
<li>Update aria-query to 5.0.0 (<a href="https://github-redirect.dependabot.com/testing-library/jest-dom/issues/414">#414</a>) (<a href="https://github.com/testing-library/jest-dom/commit/de26c7ae2f502f284d5d4320634571877dbcf7fc">de26c7a</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/testing-library/jest-dom/commit/a9beb47455dae0f455ddacc473d0ddabe09f0b43"><code>a9beb47</code></a> fix: Improve <code>toHaveClass</code> error message format (<a href="https://github-redirect.dependabot.com/testing-library/jest-dom/issues/405">#405</a>)</li>
<li><a href="https://github.com/testing-library/jest-dom/commit/6f69437085fe59610ff37b99408177ec8a684075"><code>6f69437</code></a> docs: add IanVS as a contributor for code (<a href="https://github-redirect.dependabot.com/testing-library/jest-dom/issues/423">#423</a>)</li>
<li><a href="https://github.com/testing-library/jest-dom/commit/de26c7ae2f502f284d5d4320634571877dbcf7fc"><code>de26c7a</code></a> feat: Update aria-query to 5.0.0 (<a href="https://github-redirect.dependabot.com/testing-library/jest-dom/issues/414">#414</a>)</li>
<li>See full diff in <a href="https://github.com/testing-library/jest-dom/compare/v5.15.1...v5.16.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@testing-library/jest-dom&package-manager=npm_and_yarn&previous-version=5.15.1&new-version=5.16.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/2590/builds/204275) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit c910171b0d275391f800a9135af1a0ccdab76c0b:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-mz103)| Configuration |
|[dubzzz/fast-check](https://codesandbox.io/s/dubzzz-fast-check-o5ttl)| Configuration |

codecov[bot]: # [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2590?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) Report
> Merging [#2590](https://codecov.io/gh/dubzzz/fast-check/pull/2590?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (c910171) 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 **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/2590/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/2590?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    #2590   +/-   ##
=======================================
  Coverage   95.00%   95.00%           
=======================================
  Files         206      206           
  Lines        4747     4747           
  Branches      932      932           
=======================================
  Hits         4510     4510           
  Misses        234      234           
  Partials        3        3           
```

| Flag | Coverage Δ | |
|---|---|---|
| unit-tests | `95.00% <ø> (ø)` | |

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.


------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2590?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/2590?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Last update [27bed70...c910171](https://codecov.io/gh/dubzzz/fast-check/pull/2590?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/please-actions.yml b/.github/workflows/please-actions.yml
index d9b3a770..21b0977f 100644
--- a/.github/workflows/please-actions.yml
+++ b/.github/workflows/please-actions.yml
@@ -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..b494bb3d 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,7 +18,7 @@
   },
   "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",
diff --git a/example/yarn.lock b/example/yarn.lock
index 5168885b..6fb89ce4 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"
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;
+}