Repository: dubzzz/fast-check
Pull request #1388 (2021-01-22 08:31:29)

✅ Add snapshot tests for new core shrinkers: integer, bigint, arrayInt64
<!-- Why is this PR for? -->
<!-- Describe the reason why you opened this PR or give a link towards the associated issue. -->

## In a nutshell

❌ New feature
❌ Fix an issue
❌ Documentation improvement
✔️ Other: *test*

(✔️: yes, ❌: no)

## Potential impacts

None


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/1388/builds/94379) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 7e6cf4b635740aea5e95fab3cf7bc1ce81708512:

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

codecov[bot]: # [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=h1) Report
> Merging [#1388](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=desc) (7e6cf4b) into [master](https://codecov.io/gh/dubzzz/fast-check/commit/a159489b0894937559c1398a140ccdd5888e8f2a?el=desc) (a159489) will **decrease** coverage by `9.13%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/1388/graphs/tree.svg?width=650&height=150&src=pr&token=fRi2fJN730)](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1388      +/-   ##
==========================================
- Coverage   98.40%   89.26%   -9.14%     
==========================================
  Files          91       91              
  Lines        3075     3075              
  Branches      594      594              
==========================================
- Hits         3026     2745     -281     
- Misses         46      322     +276     
- Partials        3        8       +5     
```

| Flag | Coverage Δ | |
|---|---|---|
| e2e-tests | `89.26% <100.00%> (ø)` | |
| e2e-tests-10.x-Linux | `89.26% <100.00%> (+0.03%)` | :arrow_up: |
| e2e-tests-10.x-Windows | `?` | |
| e2e-tests-10.x-macOS | `?` | |
| e2e-tests-12.x-Linux | `89.26% <100.00%> (+0.03%)` | :arrow_up: |
| e2e-tests-12.x-Windows | `?` | |
| e2e-tests-12.x-macOS | `?` | |
| e2e-tests-14.x-Linux | `89.23% <100.00%> (ø)` | |
| e2e-tests-14.x-Windows | `?` | |
| e2e-tests-14.x-macOS | `?` | |
| unit-tests | `?` | |
| unit-tests-10.x-Linux | `?` | |
| unit-tests-12.x-Linux | `?` | |
| unit-tests-14.x-Linux | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9oZWxwZXJzL0FycmF5SW50NjRBcmJpdHJhcnkudHM=) | `88.46% <100.00%> (-5.13%)` | :arrow_down: |
| [src/check/property/TimeoutProperty.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL3Byb3BlcnR5L1RpbWVvdXRQcm9wZXJ0eS50cw==) | `11.76% <0.00%> (-88.24%)` | :arrow_down: |
| [src/check/model/commands/ScheduledCommand.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL21vZGVsL2NvbW1hbmRzL1NjaGVkdWxlZENvbW1hbmQudHM=) | `7.14% <0.00%> (-82.15%)` | :arrow_down: |
| [src/utils/hash.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2hhc2gudHM=) | `29.62% <0.00%> (-70.38%)` | :arrow_down: |
| [...itrary/definition/ArbitraryWithContextualShrink.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9kZWZpbml0aW9uL0FyYml0cmFyeVdpdGhDb250ZXh0dWFsU2hyaW5rLnRz) | `46.66% <0.00%> (-53.34%)` | :arrow_down: |
| [.../check/arbitrary/definition/ArbitraryWithShrink.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9kZWZpbml0aW9uL0FyYml0cmFyeVdpdGhTaHJpbmsudHM=) | `60.00% <0.00%> (-40.00%)` | :arrow_down: |
| [src/stream/LazyIterableIterator.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL3N0cmVhbS9MYXp5SXRlcmFibGVJdGVyYXRvci50cw==) | `63.63% <0.00%> (-36.37%)` | :arrow_down: |
| [src/check/property/AsyncProperty.generic.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL3Byb3BlcnR5L0FzeW5jUHJvcGVydHkuZ2VuZXJpYy50cw==) | `66.66% <0.00%> (-33.34%)` | :arrow_down: |
| [src/check/model/ModelRunner.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL21vZGVsL01vZGVsUnVubmVyLnRz) | `58.33% <0.00%> (-31.26%)` | :arrow_down: |
| [src/check/property/Property.generic.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree#diff-c3JjL2NoZWNrL3Byb3BlcnR5L1Byb3BlcnR5LmdlbmVyaWMudHM=) | `70.58% <0.00%> (-29.42%)` | :arrow_down: |
| ... and [31 more](https://codecov.io/gh/dubzzz/fast-check/pull/1388/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=footer). Last update [a159489...7e6cf4b](https://codecov.io/gh/dubzzz/fast-check/pull/1388?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

github-actions[bot]: Give a try to https://github.com/dubzzz/fast-check/pull/1388/commits/7e6cf4b635740aea5e95fab3cf7bc1ce81708512 with:

```bash
yarn add https://600a8df8f2ab3dcd306c09be--dazzling-goodall-4f0e38.netlify.app/fast-check.tgz
npm i https://600a8df8f2ab3dcd306c09be--dazzling-goodall-4f0e38.netlify.app/fast-check.tgz
```

Or have a look to the [generated documentation](https://600a8df8f2ab3dcd306c09be--dazzling-goodall-4f0e38.netlify.app).

Patch:
diff --git a/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts b/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
index e9d9ffe3..1c94e540 100644
--- a/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
+++ b/src/check/arbitrary/helpers/ArrayInt64Arbitrary.ts
@@ -161,6 +161,6 @@ class ArrayInt64Arbitrary extends ArbitraryWithContextualShrink<ArrayInt64> {
 }
 
 /** @internal */
-export function arrayInt64(min: ArrayInt64, max: ArrayInt64): Arbitrary<ArrayInt64> {
+export function arrayInt64(min: ArrayInt64, max: ArrayInt64): ArbitraryWithContextualShrink<ArrayInt64> {
   return new ArrayInt64Arbitrary(min, max, min, max);
 }


Test patch:
diff --git a/test/unit/check/arbitrary/BigIntArbitrary.spec.ts b/test/unit/check/arbitrary/BigIntArbitrary.spec.ts
index 70ee09df..7f652aa5 100644
--- a/test/unit/check/arbitrary/BigIntArbitrary.spec.ts
+++ b/test/unit/check/arbitrary/BigIntArbitrary.spec.ts
@@ -4,6 +4,7 @@ import { bigIntN, bigUintN, bigInt, bigUint } from '../../../../src/check/arbitr
 import { generateOneValue } from './generic/GenerateOneValue';
 
 import * as genericHelper from './generic/GenericArbitraryHelper';
+import { buildShrinkTree, renderTree } from './generic/ShrinkTree';
 
 declare function BigInt(n: number | bigint | string): bigint;
 
@@ -80,6 +81,165 @@ describe('BigIntArbitrary', () => {
         );
       });
     });
+
+    describe('contextualShrinkableFor', () => {
+      it('Should shrink strictly positive value for positive range including zero', () => {
+        // Arrange
+        const arb = bigInt({ min: BigInt(0), max: BigInt(10) });
+
+        // Act
+        const tree = buildShrinkTree(arb.contextualShrinkableFor(BigInt(8)));
+        const renderedTree = renderTree(tree).join('\n');
+
+        // Assert
+        //   When there is no more option, the shrinker retry one time with the value
+        //   current-1 to check if something that changed outside (another value not itself)
+        //   may have changed the situation
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "8n
+          ├> 0n
+          ├> 4n
+          |  ├> 2n
+          |  |  └> 1n
+          |  |     └> 0n
+          |  └> 3n
+          |     └> 2n
+          |        ├> 0n
+          |        └> 1n
+          |           └> 0n
+          ├> 6n
+          |  └> 5n
+          |     └> 4n
+          |        ├> 0n
+          |        ├> 2n
+          |        |  └> 1n
+          |        |     └> 0n
+          |        └> 3n
+          |           └> 2n
+          |              ├> 0n
+          |              └> 1n
+          |                 └> 0n
+          └> 7n
+             └> 6n
+                ├> 0n
+                ├> 3n
+                |  └> 2n
+                |     └> 1n
+                |        └> 0n
+                └> 5n
+                   └> 4n
+                      └> 3n
+                         ├> 0n
+                         └> 2n
+                            └> 1n
+                               └> 0n"
+        `);
+      });
+      it('Should shrink strictly positive value for range not including zero', () => {
+        // Arrange
+        const arb = bigInt({ min: BigInt(10), max: BigInt(20) });
+
+        // Act
+        const tree = buildShrinkTree(arb.contextualShrinkableFor(BigInt(18)));
+        const renderedTree = renderTree(tree).join('\n');
+
+        // Assert
+        //   As the range [10, 20] and the value 18
+        //   are just offset by +10 compared to the first case,
+        //   the rendered tree will be offset by 10 too
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "18n
+          ├> 10n
+          ├> 14n
+          |  ├> 12n
+          |  |  └> 11n
+          |  |     └> 10n
+          |  └> 13n
+          |     └> 12n
+          |        ├> 10n
+          |        └> 11n
+          |           └> 10n
+          ├> 16n
+          |  └> 15n
+          |     └> 14n
+          |        ├> 10n
+          |        ├> 12n
+          |        |  └> 11n
+          |        |     └> 10n
+          |        └> 13n
+          |           └> 12n
+          |              ├> 10n
+          |              └> 11n
+          |                 └> 10n
+          └> 17n
+             └> 16n
+                ├> 10n
+                ├> 13n
+                |  └> 12n
+                |     └> 11n
+                |        └> 10n
+                └> 15n
+                   └> 14n
+                      └> 13n
+                         ├> 10n
+                         └> 12n
+                            └> 11n
+                               └> 10n"
+        `);
+      });
+      it('Should shrink strictly negative value for negative range including zero', () => {
+        // Arrange
+        const arb = bigInt({ min: BigInt(-10), max: BigInt(0) });
+
+        // Act
+        const tree = buildShrinkTree(arb.contextualShrinkableFor(BigInt(-8)));
+        const renderedTree = renderTree(tree).join('\n');
+
+        // Assert
+        //   As the range [-10, 0] and the value -8
+        //   are the opposite of first case, the rendered tree will be the same except
+        //   it contains opposite values
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "-8n
+          ├> 0n
+          ├> -4n
+          |  ├> -2n
+          |  |  └> -1n
+          |  |     └> 0n
+          |  └> -3n
+          |     └> -2n
+          |        ├> 0n
+          |        └> -1n
+          |           └> 0n
+          ├> -6n
+          |  └> -5n
+          |     └> -4n
+          |        ├> 0n
+          |        ├> -2n
+          |        |  └> -1n
+          |        |     └> 0n
+          |        └> -3n
+          |           └> -2n
+          |              ├> 0n
+          |              └> -1n
+          |                 └> 0n
+          └> -7n
+             └> -6n
+                ├> 0n
+                ├> -3n
+                |  └> -2n
+                |     └> -1n
+                |        └> 0n
+                └> -5n
+                   └> -4n
+                      └> -3n
+                         ├> 0n
+                         └> -2n
+                            └> -1n
+                               └> 0n"
+        `);
+      });
+    });
   });
   describe('bigUint', () => {
     describe('Given no constraints [positive values]', () => {
diff --git a/test/unit/check/arbitrary/IntegerArbitrary.spec.ts b/test/unit/check/arbitrary/IntegerArbitrary.spec.ts
index 968c0972..1176f66d 100644
--- a/test/unit/check/arbitrary/IntegerArbitrary.spec.ts
+++ b/test/unit/check/arbitrary/IntegerArbitrary.spec.ts
@@ -1,39 +1,15 @@
 import * as fc from '../../../../lib/fast-check';
 
-import { Shrinkable } from '../../../../src/check/arbitrary/definition/Shrinkable';
 import { integer, nat, maxSafeNat, maxSafeInteger } from '../../../../src/check/arbitrary/IntegerArbitrary';
 
 import * as genericHelper from './generic/GenericArbitraryHelper';
 
 import * as stubRng from '../../stubs/generators';
 import { generateOneValue } from './generic/GenerateOneValue';
+import { buildShrinkTree, renderTree } from './generic/ShrinkTree';
 
 const isStrictlySmallerInteger = (v1: number, v2: number) => Math.abs(v1) < Math.abs(v2);
 
-type ShrinkTree<T> = [T, ShrinkTree<T>[]];
-function buildShrinkTree(s: Shrinkable<number, number>): ShrinkTree<number> {
-  return [s.value_, [...s.shrink().map((ss) => buildShrinkTree(ss))]];
-}
-function renderTree(tree: ShrinkTree<number>): string[] {
-  const [current, subTrees] = tree;
-  const lines = [fc.stringify(current)];
-  for (let index = 0; index !== subTrees.length; ++index) {
-    const subTree = subTrees[index];
-    const isLastSubTree = index === subTrees.length - 1;
-    const firstPrefix = isLastSubTree ? '└> ' : '├> ';
-    const otherPrefix = isLastSubTree ? '   ' : '|  ';
-    const subRender = renderTree(subTree);
-    for (let renderedIndex = 0; renderedIndex !== subRender.length; ++renderedIndex) {
-      if (renderedIndex === 0) {
-        lines.push(`${firstPrefix}${subRender[renderedIndex]}`);
-      } else {
-        lines.push(`${otherPrefix}${subRender[renderedIndex]}`);
-      }
-    }
-  }
-  return lines;
-}
-
 describe('IntegerArbitrary', () => {
   describe('integer', () => {
     it('Should not fail on single value range', () =>
@@ -214,51 +190,56 @@ describe('IntegerArbitrary', () => {
 
     describe('contextualShrinkableFor', () => {
       it('Should shrink strictly positive value for positive range including zero', () => {
+        // Arrange
         const arb = integer({ min: 0, max: 10 });
 
+        // Act
         const tree = buildShrinkTree(arb.contextualShrinkableFor(8));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        expect(renderTree(tree).join('\n')).toBe(
-          '8\n' + 
-          '├> 0\n' + 
-          '├> 4\n' + 
-          '|  ├> 2\n' + 
-          '|  |  └> 1\n' + 
-          '|  |     └> 0\n' + // Last try...
-          '|  └> 3\n' + 
-          '|     └> 2\n' + // Last try...
-          '|        ├> 0\n' + 
-          '|        └> 1\n' + 
-          '|           └> 0\n' + // Last try...
-          '├> 6\n' + 
-          '|  └> 5\n' + 
-          '|     └> 4\n' + // Last try...
-          '|        ├> 0\n' + 
-          '|        ├> 2\n' + 
-          '|        |  └> 1\n' + 
-          '|        |     └> 0\n' + // Last try...
-          '|        └> 3\n' + 
-          '|           └> 2\n' + // Last try...
-          '|              ├> 0\n' + 
-          '|              └> 1\n' + 
-          '|                 └> 0\n' + // Last try...
-          '└> 7\n' + 
-          '   └> 6\n' + // Last try...
-          '      ├> 0\n' + 
-          '      ├> 3\n' + 
-          '      |  └> 2\n' + 
-          '      |     └> 1\n' + 
-          '      |        └> 0\n' + // Last try...
-          '      └> 5\n' + 
-          '         └> 4\n' + 
-          '            └> 3\n' + // Last try...
-          '               ├> 0\n' + 
-          '               └> 2\n' + 
-          '                  └> 1\n' + 
-          '                     └> 0' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        //   When there is no more option, the shrinker retry one time with the value
+        //   current-1 to check if something that changed outside (another value not itself)
+        //   may have changed the situation.
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "8
+          ├> 0
+          ├> 4
+          |  ├> 2
+          |  |  └> 1
+          |  |     └> 0
+          |  └> 3
+          |     └> 2
+          |        ├> 0
+          |        └> 1
+          |           └> 0
+          ├> 6
+          |  └> 5
+          |     └> 4
+          |        ├> 0
+          |        ├> 2
+          |        |  └> 1
+          |        |     └> 0
+          |        └> 3
+          |           └> 2
+          |              ├> 0
+          |              └> 1
+          |                 └> 0
+          └> 7
+             └> 6
+                ├> 0
+                ├> 3
+                |  └> 2
+                |     └> 1
+                |        └> 0
+                └> 5
+                   └> 4
+                      └> 3
+                         ├> 0
+                         └> 2
+                            └> 1
+                               └> 0"
+        `);
 
         // Remarks:
         // * When we shrink 5 in path 8 > 6 > 5
@@ -268,105 +249,110 @@ describe('IntegerArbitrary', () => {
         // * When we shrink 7 in path 8 > 7
         //   we already now that 6 passed so we now that the smallest failing case
         //   to look for is >= 7
-        // * "Last try" are used to retry a small move right after all other arbitraries ended.
-        //   It tries to confirm that what we thought to be the minimal failing case is still
-        //   the minimal one given the change that occurred around it
       });
-      it('Should shrink strictly positive value for range not included zero', () => {
+      it('Should shrink strictly positive value for range not including zero', () => {
+        // Arrange
         const arb = integer({ min: 10, max: 20 });
 
+        // Act
         const tree = buildShrinkTree(arb.contextualShrinkableFor(18));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        // Same as first case but offset by +10 (as other values)
-        expect(renderTree(tree).join('\n')).toBe(
-          '18\n' + 
-          '├> 10\n' + 
-          '├> 14\n' + 
-          '|  ├> 12\n' + 
-          '|  |  └> 11\n' + 
-          '|  |     └> 10\n' + // Last try...
-          '|  └> 13\n' + 
-          '|     └> 12\n' + // Last try...
-          '|        ├> 10\n' + 
-          '|        └> 11\n' + 
-          '|           └> 10\n' + // Last try...
-          '├> 16\n' + 
-          '|  └> 15\n' + 
-          '|     └> 14\n' + // Last try...
-          '|        ├> 10\n' + 
-          '|        ├> 12\n' + 
-          '|        |  └> 11\n' + 
-          '|        |     └> 10\n' + // Last try...
-          '|        └> 13\n' + 
-          '|           └> 12\n' + // Last try...
-          '|              ├> 10\n' + 
-          '|              └> 11\n' + 
-          '|                 └> 10\n' + // Last try...
-          '└> 17\n' + 
-          '   └> 16\n' + // Last try...
-          '      ├> 10\n' + 
-          '      ├> 13\n' + 
-          '      |  └> 12\n' + 
-          '      |     └> 11\n' + 
-          '      |        └> 10\n' + // Last try...
-          '      └> 15\n' + 
-          '         └> 14\n' + 
-          '            └> 13\n' + // Last try...
-          '               ├> 10\n' + 
-          '               └> 12\n' + 
-          '                  └> 11\n' + 
-          '                     └> 10' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        //   As the range [10, 20] and the value 18
+        //   are just offset by +10 compared to the first case,
+        //   the rendered tree will be offset by 10 too
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "18
+          ├> 10
+          ├> 14
+          |  ├> 12
+          |  |  └> 11
+          |  |     └> 10
+          |  └> 13
+          |     └> 12
+          |        ├> 10
+          |        └> 11
+          |           └> 10
+          ├> 16
+          |  └> 15
+          |     └> 14
+          |        ├> 10
+          |        ├> 12
+          |        |  └> 11
+          |        |     └> 10
+          |        └> 13
+          |           └> 12
+          |              ├> 10
+          |              └> 11
+          |                 └> 10
+          └> 17
+             └> 16
+                ├> 10
+                ├> 13
+                |  └> 12
+                |     └> 11
+                |        └> 10
+                └> 15
+                   └> 14
+                      └> 13
+                         ├> 10
+                         └> 12
+                            └> 11
+                               └> 10"
+        `);
       });
       it('Should shrink strictly negative value for negative range including zero', () => {
+        // Arrange
         const arb = integer({ min: -10, max: 0 });
 
+        // Act
         const tree = buildShrinkTree(arb.contextualShrinkableFor(-8));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        // Same as first case but multiplied by -1
-        expect(renderTree(tree).join('\n')).toBe(
-          '-8\n' + 
-          '├> 0\n' + 
-          '├> -4\n' + 
-          '|  ├> -2\n' + 
-          '|  |  └> -1\n' + 
-          '|  |     └> 0\n' + // Last try...
-          '|  └> -3\n' + 
-          '|     └> -2\n' + // Last try...
-          '|        ├> 0\n' + 
-          '|        └> -1\n' + 
-          '|           └> 0\n' + // Last try...
-          '├> -6\n' + 
-          '|  └> -5\n' + 
-          '|     └> -4\n' + // Last try...
-          '|        ├> 0\n' + 
-          '|        ├> -2\n' + 
-          '|        |  └> -1\n' + 
-          '|        |     └> 0\n' + // Last try...
-          '|        └> -3\n' + 
-          '|           └> -2\n' + // Last try...
-          '|              ├> 0\n' + 
-          '|              └> -1\n' + 
-          '|                 └> 0\n' + // Last try...
-          '└> -7\n' + 
-          '   └> -6\n' + // Last try...
-          '      ├> 0\n' + 
-          '      ├> -3\n' + 
-          '      |  └> -2\n' + 
-          '      |     └> -1\n' + 
-          '      |        └> 0\n' + // Last try...
-          '      └> -5\n' + 
-          '         └> -4\n' + 
-          '            └> -3\n' + // Last try...
-          '               ├> 0\n' + 
-          '               └> -2\n' + 
-          '                  └> -1\n' + 
-          '                     └> 0' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        //   As the range [-10, 0] and the value -8
+        //   are the opposite of first case, the rendered tree will be the same except
+        //   it contains opposite values
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "-8
+          ├> 0
+          ├> -4
+          |  ├> -2
+          |  |  └> -1
+          |  |     └> 0
+          |  └> -3
+          |     └> -2
+          |        ├> 0
+          |        └> -1
+          |           └> 0
+          ├> -6
+          |  └> -5
+          |     └> -4
+          |        ├> 0
+          |        ├> -2
+          |        |  └> -1
+          |        |     └> 0
+          |        └> -3
+          |           └> -2
+          |              ├> 0
+          |              └> -1
+          |                 └> 0
+          └> -7
+             └> -6
+                ├> 0
+                ├> -3
+                |  └> -2
+                |     └> -1
+                |        └> 0
+                └> -5
+                   └> -4
+                      └> -3
+                         ├> 0
+                         └> -2
+                            └> -1
+                               └> 0"
+        `);
       });
     });
 
@@ -377,91 +363,97 @@ describe('IntegerArbitrary', () => {
       // took its place for faster shrinker.
 
       it('Should shrink strictly positive value for positive range including zero', () => {
+        // Arrange
         const arb = integer({ min: 0, max: 7 });
 
+        // Act
         const tree = buildShrinkTree(arb.shrinkableFor(6));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        expect(renderTree(tree).join('\n')).toBe(
-          '6\n' + 
-          '├> 0\n' + 
-          '├> 3\n' + 
-          '|  └> 2\n' + 
-          '|     └> 1\n' + 
-          '|        └> 0\n' + // Last try...
-          '└> 5\n' + 
-          '   ├> 3\n' + 
-          '   |  └> 2\n' + 
-          '   |     └> 1\n' + 
-          '   |        └> 0\n' + // Last try...
-          '   └> 4\n' + 
-          '      ├> 2\n' + 
-          '      |  └> 1\n' + 
-          '      |     └> 0\n' + // Last try...
-          '      └> 3\n' + 
-          '         └> 2\n' + 
-          '            └> 1\n' +
-          '               └> 0' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "6
+          ├> 0
+          ├> 3
+          |  └> 2
+          |     └> 1
+          |        └> 0
+          └> 5
+             ├> 3
+             |  └> 2
+             |     └> 1
+             |        └> 0
+             └> 4
+                ├> 2
+                |  └> 1
+                |     └> 0
+                └> 3
+                   └> 2
+                      └> 1
+                         └> 0"
+        `);
       });
-      it('Should shrink strictly positive value for range not included zero', () => {
+      it('Should shrink strictly positive value for range not including zero', () => {
+        // Arrange
         const arb = integer({ min: 2, max: 9 });
 
+        // Act
         const tree = buildShrinkTree(arb.shrinkableFor(8));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        expect(renderTree(tree).join('\n')).toBe(
-          '8\n' + 
-          '├> 2\n' + 
-          '├> 5\n' + 
-          '|  └> 4\n' + 
-          '|     └> 3\n' + 
-          '|        └> 2\n' + // Last try...
-          '└> 7\n' + 
-          '   ├> 5\n' + 
-          '   |  └> 4\n' + 
-          '   |     └> 3\n' + 
-          '   |        └> 2\n' + // Last try...
-          '   └> 6\n' + 
-          '      ├> 4\n' + 
-          '      |  └> 3\n' + 
-          '      |     └> 2\n' + // Last try...
-          '      └> 5\n' + 
-          '         └> 4\n' + 
-          '            └> 3\n' + 
-          '               └> 2' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "8
+          ├> 2
+          ├> 5
+          |  └> 4
+          |     └> 3
+          |        └> 2
+          └> 7
+             ├> 5
+             |  └> 4
+             |     └> 3
+             |        └> 2
+             └> 6
+                ├> 4
+                |  └> 3
+                |     └> 2
+                └> 5
+                   └> 4
+                      └> 3
+                         └> 2"
+        `);
       });
       it('Should shrink strictly negative value for negative range including zero', () => {
+        // Arrange
         const arb = integer({ min: -7, max: 0 });
 
+        // Act
         const tree = buildShrinkTree(arb.shrinkableFor(-6));
+        const renderedTree = renderTree(tree).join('\n');
 
-        // prettier-ignore
-        expect(renderTree(tree).join('\n')).toBe(
-          '-6\n' + 
-          '├> 0\n' + 
-          '├> -3\n' + 
-          '|  └> -2\n' + 
-          '|     └> -1\n' +
-          '|        └> 0\n' + // Last try...
-          '└> -5\n' + 
-          '   ├> -3\n' + 
-          '   |  └> -2\n' + 
-          '   |     └> -1\n' + 
-          '   |        └> 0\n' + // Last try...
-          '   └> -4\n' + 
-          '      ├> -2\n' + 
-          '      |  └> -1\n' + 
-          '      |     └> 0\n' + // Last try...
-          '      └> -3\n' + 
-          '         └> -2\n' + 
-          '            └> -1\n' + 
-          '               └> 0' // Last try...
-        );
-        // prettier-ignore-end
+        // Assert
+        expect(renderedTree).toMatchInlineSnapshot(`
+          "-6
+          ├> 0
+          ├> -3
+          |  └> -2
+          |     └> -1
+          |        └> 0
+          └> -5
+             ├> -3
+             |  └> -2
+             |     └> -1
+             |        └> 0
+             └> -4
+                ├> -2
+                |  └> -1
+                |     └> 0
+                └> -3
+                   └> -2
+                      └> -1
+                         └> 0"
+        `);
       });
     });
   });
diff --git a/test/unit/check/arbitrary/generic/ShrinkTree.ts b/test/unit/check/arbitrary/generic/ShrinkTree.ts
new file mode 100644
index 00000000..e11cdf48
--- /dev/null
+++ b/test/unit/check/arbitrary/generic/ShrinkTree.ts
@@ -0,0 +1,28 @@
+import * as fc from '../../../../../lib/fast-check';
+import { Shrinkable } from '../../../../../src/check/arbitrary/definition/Shrinkable';
+
+export type ShrinkTree<T> = [T, ShrinkTree<T>[]];
+
+export function buildShrinkTree<T>(s: Shrinkable<T>): ShrinkTree<T> {
+  return [s.value_, [...s.shrink().map((ss) => buildShrinkTree(ss))]];
+}
+
+export function renderTree<T>(tree: ShrinkTree<T>): string[] {
+  const [current, subTrees] = tree;
+  const lines = [fc.stringify(current)];
+  for (let index = 0; index !== subTrees.length; ++index) {
+    const subTree = subTrees[index];
+    const isLastSubTree = index === subTrees.length - 1;
+    const firstPrefix = isLastSubTree ? '└> ' : '├> ';
+    const otherPrefix = isLastSubTree ? '   ' : '|  ';
+    const subRender = renderTree(subTree);
+    for (let renderedIndex = 0; renderedIndex !== subRender.length; ++renderedIndex) {
+      if (renderedIndex === 0) {
+        lines.push(`${firstPrefix}${subRender[renderedIndex]}`);
+      } else {
+        lines.push(`${otherPrefix}${subRender[renderedIndex]}`);
+      }
+    }
+  }
+  return lines;
+}
diff --git a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
index 81ab0145..1c08e48d 100644
--- a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
+++ b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
@@ -5,6 +5,7 @@ import { arrayInt64 } from '../../../../../src/check/arbitrary/helpers/ArrayInt6
 
 import { mocked } from 'ts-jest/utils';
 import { Random } from '../../../../../src/random/generator/Random';
+import { buildShrinkTree, renderTree } from '../generic/ShrinkTree';
 
 import * as BiasedArbitraryWrapperMock from '../../../../../src/check/arbitrary/definition/BiasedArbitraryWrapper';
 import * as BiasNumericMock from '../../../../../src/check/arbitrary/helpers/BiasNumeric';
@@ -138,4 +139,163 @@ describe('ArrayInt64', () => {
         ));
     });
   });
+
+  describe('contextualShrinkableFor', () => {
+    it('Should shrink strictly positive value for positive range including zero', () => {
+      // Arrange
+      const arb = arrayInt64({ sign: 1, data: [0, 0] }, { sign: 1, data: [0, 10] });
+
+      // Act
+      const tree = buildShrinkTree(arb.contextualShrinkableFor({ sign: 1, data: [0, 8] }));
+      const renderedTree = renderTree(tree).join('\n');
+
+      // Assert
+      //   When there is no more option, the shrinker retry one time with the value
+      //   current-1 to check if something that changed outside (another value not itself)
+      //   may have changed the situation
+      expect(renderedTree).toMatchInlineSnapshot(`
+        "{\\"sign\\":1,\\"data\\":[0,8]}
+        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        ├> {\\"sign\\":1,\\"data\\":[0,4]}
+        |  ├> {\\"sign\\":1,\\"data\\":[0,2]}
+        |  |  └> {\\"sign\\":1,\\"data\\":[0,1]}
+        |  |     └> {\\"sign\\":1,\\"data\\":[0,0]}
+        |  └> {\\"sign\\":1,\\"data\\":[0,3]}
+        |     └> {\\"sign\\":1,\\"data\\":[0,2]}
+        |        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        └> {\\"sign\\":1,\\"data\\":[0,1]}
+        |           └> {\\"sign\\":1,\\"data\\":[0,0]}
+        ├> {\\"sign\\":1,\\"data\\":[0,6]}
+        |  └> {\\"sign\\":1,\\"data\\":[0,5]}
+        |     └> {\\"sign\\":1,\\"data\\":[0,4]}
+        |        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        ├> {\\"sign\\":1,\\"data\\":[0,2]}
+        |        |  └> {\\"sign\\":1,\\"data\\":[0,1]}
+        |        |     └> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        └> {\\"sign\\":1,\\"data\\":[0,3]}
+        |           └> {\\"sign\\":1,\\"data\\":[0,2]}
+        |              ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |              └> {\\"sign\\":1,\\"data\\":[0,1]}
+        |                 └> {\\"sign\\":1,\\"data\\":[0,0]}
+        └> {\\"sign\\":1,\\"data\\":[0,7]}
+           └> {\\"sign\\":1,\\"data\\":[0,6]}
+              ├> {\\"sign\\":1,\\"data\\":[0,0]}
+              ├> {\\"sign\\":1,\\"data\\":[0,3]}
+              |  └> {\\"sign\\":1,\\"data\\":[0,2]}
+              |     └> {\\"sign\\":1,\\"data\\":[0,1]}
+              |        └> {\\"sign\\":1,\\"data\\":[0,0]}
+              └> {\\"sign\\":1,\\"data\\":[0,5]}
+                 └> {\\"sign\\":1,\\"data\\":[0,4]}
+                    └> {\\"sign\\":1,\\"data\\":[0,3]}
+                       ├> {\\"sign\\":1,\\"data\\":[0,0]}
+                       └> {\\"sign\\":1,\\"data\\":[0,2]}
+                          └> {\\"sign\\":1,\\"data\\":[0,1]}
+                             └> {\\"sign\\":1,\\"data\\":[0,0]}"
+      `);
+    });
+    it('Should shrink strictly positive value for range not including zero', () => {
+      // Arrange
+      const arb = arrayInt64({ sign: 1, data: [1, 10] }, { sign: 1, data: [1, 20] });
+
+      // Act
+      const tree = buildShrinkTree(arb.contextualShrinkableFor({ sign: 1, data: [1, 18] }));
+      const renderedTree = renderTree(tree).join('\n');
+
+      // Assert
+      //   As the range [[1,10], [1,20]] and the value [1,18]
+      //   are just offset by +[1,10] compared to the first case,
+      //   the rendered tree will be offset by [1,10] too
+      expect(renderedTree).toMatchInlineSnapshot(`
+        "{\\"sign\\":1,\\"data\\":[1,18]}
+        ├> {\\"sign\\":1,\\"data\\":[1,10]}
+        ├> {\\"sign\\":1,\\"data\\":[1,14]}
+        |  ├> {\\"sign\\":1,\\"data\\":[1,12]}
+        |  |  └> {\\"sign\\":1,\\"data\\":[1,11]}
+        |  |     └> {\\"sign\\":1,\\"data\\":[1,10]}
+        |  └> {\\"sign\\":1,\\"data\\":[1,13]}
+        |     └> {\\"sign\\":1,\\"data\\":[1,12]}
+        |        ├> {\\"sign\\":1,\\"data\\":[1,10]}
+        |        └> {\\"sign\\":1,\\"data\\":[1,11]}
+        |           └> {\\"sign\\":1,\\"data\\":[1,10]}
+        ├> {\\"sign\\":1,\\"data\\":[1,16]}
+        |  └> {\\"sign\\":1,\\"data\\":[1,15]}
+        |     └> {\\"sign\\":1,\\"data\\":[1,14]}
+        |        ├> {\\"sign\\":1,\\"data\\":[1,10]}
+        |        ├> {\\"sign\\":1,\\"data\\":[1,12]}
+        |        |  └> {\\"sign\\":1,\\"data\\":[1,11]}
+        |        |     └> {\\"sign\\":1,\\"data\\":[1,10]}
+        |        └> {\\"sign\\":1,\\"data\\":[1,13]}
+        |           └> {\\"sign\\":1,\\"data\\":[1,12]}
+        |              ├> {\\"sign\\":1,\\"data\\":[1,10]}
+        |              └> {\\"sign\\":1,\\"data\\":[1,11]}
+        |                 └> {\\"sign\\":1,\\"data\\":[1,10]}
+        └> {\\"sign\\":1,\\"data\\":[1,17]}
+           └> {\\"sign\\":1,\\"data\\":[1,16]}
+              ├> {\\"sign\\":1,\\"data\\":[1,10]}
+              ├> {\\"sign\\":1,\\"data\\":[1,13]}
+              |  └> {\\"sign\\":1,\\"data\\":[1,12]}
+              |     └> {\\"sign\\":1,\\"data\\":[1,11]}
+              |        └> {\\"sign\\":1,\\"data\\":[1,10]}
+              └> {\\"sign\\":1,\\"data\\":[1,15]}
+                 └> {\\"sign\\":1,\\"data\\":[1,14]}
+                    └> {\\"sign\\":1,\\"data\\":[1,13]}
+                       ├> {\\"sign\\":1,\\"data\\":[1,10]}
+                       └> {\\"sign\\":1,\\"data\\":[1,12]}
+                          └> {\\"sign\\":1,\\"data\\":[1,11]}
+                             └> {\\"sign\\":1,\\"data\\":[1,10]}"
+      `);
+    });
+    it('Should shrink strictly negative value for negative range including zero', () => {
+      // Arrange
+      const arb = arrayInt64({ sign: -1, data: [0, 10] }, { sign: 1, data: [0, 0] });
+
+      // Act
+      const tree = buildShrinkTree(arb.contextualShrinkableFor({ sign: -1, data: [0, 8] }));
+      const renderedTree = renderTree(tree).join('\n');
+
+      // Assert
+      //   As the range [-10, 0] and the value -8
+      //   are the opposite of first case, the rendered tree will be the same except
+      //   it contains opposite values
+      expect(renderedTree).toMatchInlineSnapshot(`
+        "{\\"sign\\":-1,\\"data\\":[0,8]}
+        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        ├> {\\"sign\\":-1,\\"data\\":[0,4]}
+        |  ├> {\\"sign\\":-1,\\"data\\":[0,2]}
+        |  |  └> {\\"sign\\":-1,\\"data\\":[0,1]}
+        |  |     └> {\\"sign\\":1,\\"data\\":[0,0]}
+        |  └> {\\"sign\\":-1,\\"data\\":[0,3]}
+        |     └> {\\"sign\\":-1,\\"data\\":[0,2]}
+        |        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        └> {\\"sign\\":-1,\\"data\\":[0,1]}
+        |           └> {\\"sign\\":1,\\"data\\":[0,0]}
+        ├> {\\"sign\\":-1,\\"data\\":[0,6]}
+        |  └> {\\"sign\\":-1,\\"data\\":[0,5]}
+        |     └> {\\"sign\\":-1,\\"data\\":[0,4]}
+        |        ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        ├> {\\"sign\\":-1,\\"data\\":[0,2]}
+        |        |  └> {\\"sign\\":-1,\\"data\\":[0,1]}
+        |        |     └> {\\"sign\\":1,\\"data\\":[0,0]}
+        |        └> {\\"sign\\":-1,\\"data\\":[0,3]}
+        |           └> {\\"sign\\":-1,\\"data\\":[0,2]}
+        |              ├> {\\"sign\\":1,\\"data\\":[0,0]}
+        |              └> {\\"sign\\":-1,\\"data\\":[0,1]}
+        |                 └> {\\"sign\\":1,\\"data\\":[0,0]}
+        └> {\\"sign\\":-1,\\"data\\":[0,7]}
+           └> {\\"sign\\":-1,\\"data\\":[0,6]}
+              ├> {\\"sign\\":1,\\"data\\":[0,0]}
+              ├> {\\"sign\\":-1,\\"data\\":[0,3]}
+              |  └> {\\"sign\\":-1,\\"data\\":[0,2]}
+              |     └> {\\"sign\\":-1,\\"data\\":[0,1]}
+              |        └> {\\"sign\\":1,\\"data\\":[0,0]}
+              └> {\\"sign\\":-1,\\"data\\":[0,5]}
+                 └> {\\"sign\\":-1,\\"data\\":[0,4]}
+                    └> {\\"sign\\":-1,\\"data\\":[0,3]}
+                       ├> {\\"sign\\":1,\\"data\\":[0,0]}
+                       └> {\\"sign\\":-1,\\"data\\":[0,2]}
+                          └> {\\"sign\\":-1,\\"data\\":[0,1]}
+                             └> {\\"sign\\":1,\\"data\\":[0,0]}"
+      `);
+    });
+  });
 });