Repository: dubzzz/fast-check
Pull request #1888 (2021-05-20 19:21:58)

✨ Add built-in unmapper support onto ip
<!-- Context of the PR: short description and potentially linked issues -->

<!-- ...a few words to describe the content of this PR...               -->
<!-- ... -->

<!-- Type of PR: [ ] unchecked / [ ] checked -->
**_Category:_**

- [x] ⚡️ Introduce new features
- [ ] 📝 Add or update documentation
- [ ] ✅ Add or update tests
- [ ] 🐛 Fix a bug
- [ ] 🏷️ Add or update types
- [ ] _Other(s):_ ...
<!-- Don't forget to add the gitmoji icon in the name of the PR -->
<!-- See: https://gitmoji.dev/                                  -->

<!-- Fixing bugs, adding feastures... may impact existing ones           -->
<!-- in order to track potential issues that could be related to your PR -->
<!-- please check the impacts and describe more precisely what to expect -->

**_Potential impacts:_**

<!-- Generated values: Can your change impact any of the existing generators in terms of generated values, if so which ones? when? -->
<!-- Shrink values:    Can your change impact any of the existing generators in terms of shrink values, if so which ones? when? -->
<!-- Performance:      Can it require some typings changes on user side? Please give more details -->
<!-- Typings:          Is there a potential performance impact? In which cases? -->
- [ ] Generated values
- [ ] Shrink values
- [ ] Performance
- [ ] Typings
- [ ] _Other(s):_ ...


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

Latest deployment of this branch, based on commit ad4a1616ac0d1af1e41dbfc0a2a8a9a108a38768:

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

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

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/1888/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/1888?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    #1888      +/-   ##
==========================================
- Coverage   97.49%   97.46%   -0.03%     
==========================================
  Files         148      151       +3     
  Lines        3948     4020      +72     
  Branches      721      781      +60     
==========================================
+ Hits         3849     3918      +69     
- Misses         95       98       +3     
  Partials        4        4              
```

| Flag | Coverage Δ | |
|---|---|---|
| unit-tests | `97.46% <96.93%> (-0.03%)` | :arrow_down: |
| unit-tests-12.x-Linux | `97.46% <96.93%> (-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/1888?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) | Coverage Δ | |
|---|---|---|
| [src/arbitrary/ipV4Extended.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9pcFY0RXh0ZW5kZWQudHM=) | `91.66% <87.50%> (-8.34%)` | :arrow_down: |
| [src/arbitrary/ipV4.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9pcFY0LnRz) | `91.66% <88.88%> (-8.34%)` | :arrow_down: |
| [...rbitrary/\_internals/mappers/NatToStringifiedNat.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL21hcHBlcnMvTmF0VG9TdHJpbmdpZmllZE5hdC50cw==) | `95.65% <95.65%> (ø)` | |
| [...ternals/builders/StringifiedNatArbitraryBuilder.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL2J1aWxkZXJzL1N0cmluZ2lmaWVkTmF0QXJiaXRyYXJ5QnVpbGRlci50cw==) | `100.00% <100.00%> (ø)` | |
| [src/arbitrary/\_internals/mappers/EntitiesToIPv6.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL21hcHBlcnMvRW50aXRpZXNUb0lQdjYudHM=) | `100.00% <100.00%> (ø)` | |
| [src/arbitrary/ipV6.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1888/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9pcFY2LnRz) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1888?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/1888?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Last update [b51dead...ad4a161](https://codecov.io/gh/dubzzz/fast-check/pull/1888?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/src/arbitrary/_internals/builders/StringifiedNatArbitraryBuilder.ts b/src/arbitrary/_internals/builders/StringifiedNatArbitraryBuilder.ts
new file mode 100644
index 00000000..7e829bc3
--- /dev/null
+++ b/src/arbitrary/_internals/builders/StringifiedNatArbitraryBuilder.ts
@@ -0,0 +1,16 @@
+import { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../../../check/arbitrary/definition/Converters';
+import { constantFrom } from '../../constantFrom';
+import { nat } from '../../nat';
+import { tuple } from '../../tuple';
+import { natToStringifiedNatMapper, natToStringifiedNatUnmapper } from '../mappers/NatToStringifiedNat';
+
+/** @internal */
+export function buildStringifiedNatArbitrary(maxValue: number): Arbitrary<string> {
+  return convertFromNext(
+    convertToNext(tuple(constantFrom<('dec' | 'oct' | 'hex')[]>('dec', 'oct', 'hex'), nat(maxValue))).map(
+      natToStringifiedNatMapper,
+      natToStringifiedNatUnmapper
+    )
+  );
+}
diff --git a/src/arbitrary/_internals/mappers/EntitiesToIPv6.ts b/src/arbitrary/_internals/mappers/EntitiesToIPv6.ts
new file mode 100644
index 00000000..72db2285
--- /dev/null
+++ b/src/arbitrary/_internals/mappers/EntitiesToIPv6.ts
@@ -0,0 +1,100 @@
+/** @internal */
+function readBh(value: string): string[] {
+  if (value.length === 0) return [];
+  else return value.split(':');
+}
+
+/** @internal */
+function extractEhAndL(value: string): [string[], string] {
+  const valueSplits = value.split(':');
+  if (valueSplits.length >= 2 && valueSplits[valueSplits.length - 1].length <= 4) {
+    // valueSplits[valueSplits.length - 1] is a h16
+    // so we need to take the two last entries for l
+    return [
+      valueSplits.slice(0, valueSplits.length - 2),
+      `${valueSplits[valueSplits.length - 2]}:${valueSplits[valueSplits.length - 1]}`,
+    ];
+  }
+  return [valueSplits.slice(0, valueSplits.length - 1), valueSplits[valueSplits.length - 1]];
+}
+
+/** @internal */
+export function fullySpecifiedMapper(data: [/*eh*/ string[], /*l*/ string]): string {
+  return `${data[0].join(':')}:${data[1]}`;
+}
+/** @internal */
+export function fullySpecifiedUnmapper(value: unknown): [string[], string] {
+  // Shape:
+  // >  6( h16 ":" ) ls32
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  return extractEhAndL(value);
+}
+
+/** @internal */
+export function onlyTrailingMapper(data: [/*eh*/ string[], /*l*/ string]): string {
+  return `::${data[0].join(':')}:${data[1]}`;
+}
+/** @internal */
+export function onlyTrailingUnmapper(value: unknown): [string[], string] {
+  // Shape:
+  // >  "::" 5( h16 ":" ) ls32
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  if (!value.startsWith('::')) throw new Error('Invalid value');
+  return extractEhAndL(value.substring(2));
+}
+
+/** @internal */
+export function multiTrailingMapper(data: [/*bh*/ string[], /*eh*/ string[], /*l*/ string]): string {
+  return `${data[0].join(':')}::${data[1].join(':')}:${data[2]}`;
+}
+/** @internal */
+export function multiTrailingUnmapper(value: unknown): [string[], string[], string] {
+  // Shape:
+  // >  [               h16 ] "::" 4( h16 ":" ) ls32
+  // >  [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+  // >  [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+  // >  [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  const [bhString, trailingString] = value.split('::', 2);
+  const [eh, l] = extractEhAndL(trailingString);
+  return [readBh(bhString), eh, l];
+}
+
+/** @internal */
+export function multiTrailingMapperOne(data: [/*bh*/ string[], /*eh*/ string, /*l*/ string]): string {
+  return multiTrailingMapper([data[0], [data[1]], data[2]]);
+}
+/** @internal */
+export function multiTrailingUnmapperOne(value: unknown): [string[], string, string] {
+  // Shape:
+  // >  [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
+  const out = multiTrailingUnmapper(value);
+  return [out[0], out[1].join(':') /* nothing to join in theory */, out[2]];
+}
+
+/** @internal */
+export function singleTrailingMapper(data: [/*bh*/ string[], /*l / eh*/ string]): string {
+  return `${data[0].join(':')}::${data[1]}`;
+}
+/** @internal */
+export function singleTrailingUnmapper(value: unknown): [string[], string] {
+  // Shape:
+  // >  [ *4( h16 ":" ) h16 ] "::" ls32
+  // >  [ *5( h16 ":" ) h16 ] "::" h16
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  const [bhString, trailing] = value.split('::', 2);
+  return [readBh(bhString), trailing];
+}
+
+/** @internal */
+export function noTrailingMapper(data: [/*bh*/ string[]]): string {
+  return `${data[0].join(':')}::`;
+}
+/** @internal */
+export function noTrailingUnmapper(value: unknown): [string[]] {
+  // Shape:
+  // >  [ *6( h16 ":" ) h16 ] "::"
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  if (!value.endsWith('::')) throw new Error('Invalid value');
+  return [readBh(value.substring(0, value.length - 2))];
+}
diff --git a/src/arbitrary/_internals/mappers/NatToStringifiedNat.ts b/src/arbitrary/_internals/mappers/NatToStringifiedNat.ts
new file mode 100644
index 00000000..61dbc231
--- /dev/null
+++ b/src/arbitrary/_internals/mappers/NatToStringifiedNat.ts
@@ -0,0 +1,36 @@
+/** @internal */
+export function natToStringifiedNatMapper(options: ['dec' | 'oct' | 'hex', number]): string {
+  const [style, v] = options;
+  switch (style) {
+    case 'oct':
+      return `0${Number(v).toString(8)}`;
+    case 'hex':
+      return `0x${Number(v).toString(16)}`;
+    case 'dec':
+    default:
+      return `${v}`;
+  }
+}
+
+/** @internal */
+export function tryParseStringifiedNat(stringValue: string, radix: number): number {
+  const parsedNat = Number.parseInt(stringValue, radix);
+  if (parsedNat.toString(radix) !== stringValue) {
+    throw new Error('Invalid value');
+  }
+  return parsedNat;
+}
+
+/** @internal */
+export function natToStringifiedNatUnmapper(value: unknown): ['dec' | 'oct' | 'hex', number] {
+  if (typeof value !== 'string') {
+    throw new Error('Invalid type');
+  }
+  if (value.length >= 2 && value[0] === '0') {
+    if (value[1] === 'x') {
+      return ['hex', tryParseStringifiedNat(value.substr(2), 16)];
+    }
+    return ['oct', tryParseStringifiedNat(value.substr(1), 8)];
+  }
+  return ['dec', tryParseStringifiedNat(value, 10)];
+}
diff --git a/src/arbitrary/ipV4.ts b/src/arbitrary/ipV4.ts
index abb66ec3..cc6466e8 100644
--- a/src/arbitrary/ipV4.ts
+++ b/src/arbitrary/ipV4.ts
@@ -1,6 +1,21 @@
 import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../check/arbitrary/definition/Converters';
 import { nat } from './nat';
 import { tuple } from './tuple';
+import { tryParseStringifiedNat } from './_internals/mappers/NatToStringifiedNat';
+
+/** @internal */
+function dotJoinerMapper(data: number[]): string {
+  return data.join('.');
+}
+
+/** @internal */
+function dotJoinerUnmapper(value: unknown): number[] {
+  if (typeof value !== 'string') {
+    throw new Error('Invalid type');
+  }
+  return value.split('.').map((v) => tryParseStringifiedNat(v, 10));
+}
 
 /**
  * For valid IP v4
@@ -12,5 +27,7 @@ import { tuple } from './tuple';
  */
 export function ipV4(): Arbitrary<string> {
   // IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
-  return tuple(nat(255), nat(255), nat(255), nat(255)).map(([a, b, c, d]) => `${a}.${b}.${c}.${d}`);
+  return convertFromNext(
+    convertToNext(tuple<number[]>(nat(255), nat(255), nat(255), nat(255))).map(dotJoinerMapper, dotJoinerUnmapper)
+  );
 }
diff --git a/src/arbitrary/ipV4Extended.ts b/src/arbitrary/ipV4Extended.ts
index 451d918e..45973a5c 100644
--- a/src/arbitrary/ipV4Extended.ts
+++ b/src/arbitrary/ipV4Extended.ts
@@ -1,8 +1,21 @@
-import { constantFrom } from './constantFrom';
 import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
-import { nat } from './nat';
 import { oneof } from './oneof';
 import { tuple } from './tuple';
+import { buildStringifiedNatArbitrary } from './_internals/builders/StringifiedNatArbitraryBuilder';
+import { convertFromNext, convertToNext } from '../check/arbitrary/definition/Converters';
+
+/** @internal */
+function dotJoinerMapper(data: string[]): string {
+  return data.join('.');
+}
+
+/** @internal */
+function dotJoinerUnmapper(value: unknown): string[] {
+  if (typeof value !== 'string') {
+    throw new Error('Invalid type');
+  }
+  return value.split('.');
+}
 
 /**
  * For valid IP v4 according to WhatWG
@@ -15,22 +28,32 @@ import { tuple } from './tuple';
  * @public
  */
 export function ipV4Extended(): Arbitrary<string> {
-  const natRepr = (maxValue: number) =>
-    tuple(constantFrom('dec', 'oct', 'hex'), nat(maxValue)).map(([style, v]) => {
-      switch (style) {
-        case 'oct':
-          return `0${Number(v).toString(8)}`;
-        case 'hex':
-          return `0x${Number(v).toString(16)}`;
-        case 'dec':
-        default:
-          return `${v}`;
-      }
-    });
   return oneof(
-    tuple(natRepr(255), natRepr(255), natRepr(255), natRepr(255)).map(([a, b, c, d]) => `${a}.${b}.${c}.${d}`),
-    tuple(natRepr(255), natRepr(255), natRepr(65535)).map(([a, b, c]) => `${a}.${b}.${c}`),
-    tuple(natRepr(255), natRepr(16777215)).map(([a, b]) => `${a}.${b}`),
-    natRepr(4294967295)
+    convertFromNext(
+      convertToNext(
+        tuple<string[]>(
+          buildStringifiedNatArbitrary(255),
+          buildStringifiedNatArbitrary(255),
+          buildStringifiedNatArbitrary(255),
+          buildStringifiedNatArbitrary(255)
+        )
+      ).map(dotJoinerMapper, dotJoinerUnmapper)
+    ),
+    convertFromNext(
+      convertToNext(
+        tuple<string[]>(
+          buildStringifiedNatArbitrary(255),
+          buildStringifiedNatArbitrary(255),
+          buildStringifiedNatArbitrary(65535)
+        )
+      ).map(dotJoinerMapper, dotJoinerUnmapper)
+    ),
+    convertFromNext(
+      convertToNext(tuple<string[]>(buildStringifiedNatArbitrary(255), buildStringifiedNatArbitrary(16777215))).map(
+        dotJoinerMapper,
+        dotJoinerUnmapper
+      )
+    ),
+    buildStringifiedNatArbitrary(4294967295)
   );
 }
diff --git a/src/arbitrary/ipV6.ts b/src/arbitrary/ipV6.ts
index ec873520..35c620b4 100644
--- a/src/arbitrary/ipV6.ts
+++ b/src/arbitrary/ipV6.ts
@@ -1,9 +1,36 @@
 import { array } from './array';
 import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
+import { convertFromNext, convertToNext } from '../check/arbitrary/definition/Converters';
 import { oneof } from './oneof';
 import { hexaString } from './hexaString';
 import { tuple } from './tuple';
 import { ipV4 } from './ipV4';
+import {
+  fullySpecifiedMapper,
+  fullySpecifiedUnmapper,
+  onlyTrailingMapper,
+  onlyTrailingUnmapper,
+  multiTrailingMapper,
+  multiTrailingUnmapper,
+  multiTrailingMapperOne,
+  multiTrailingUnmapperOne,
+  singleTrailingMapper,
+  singleTrailingUnmapper,
+  noTrailingMapper,
+  noTrailingUnmapper,
+} from './_internals/mappers/EntitiesToIPv6';
+
+/** @internal */
+function h16sTol32Mapper([a, b]: [string, string]): string {
+  return `${a}:${b}`;
+}
+
+/** @internal */
+function h16sTol32Unmapper(value: unknown): [string, string] {
+  if (typeof value !== 'string') throw new Error('Invalid type');
+  if (!value.includes(':')) throw new Error('Invalid value');
+  return value.split(':', 2) as [string, string];
+}
 
 /**
  * For valid IP v6
@@ -27,26 +54,57 @@ export function ipV6(): Arbitrary<string> {
   //               / [ *6( h16 ":" ) h16 ] "::"
   const h16Arb = hexaString({ minLength: 1, maxLength: 4 });
   const ls32Arb = oneof(
-    tuple(h16Arb, h16Arb).map(([a, b]) => `${a}:${b}`),
+    convertFromNext(convertToNext(tuple(h16Arb, h16Arb)).map(h16sTol32Mapper, h16sTol32Unmapper)),
     ipV4()
   );
   return oneof(
-    tuple(array(h16Arb, { minLength: 6, maxLength: 6 }), ls32Arb).map(([eh, l]) => `${eh.join(':')}:${l}`),
-    tuple(array(h16Arb, { minLength: 5, maxLength: 5 }), ls32Arb).map(([eh, l]) => `::${eh.join(':')}:${l}`),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 1 }), array(h16Arb, { minLength: 4, maxLength: 4 }), ls32Arb).map(
-      ([bh, eh, l]) => `${bh.join(':')}::${eh.join(':')}:${l}`
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 6, maxLength: 6 }), ls32Arb)).map(
+        fullySpecifiedMapper,
+        fullySpecifiedUnmapper
+      )
+    ),
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 5, maxLength: 5 }), ls32Arb)).map(
+        onlyTrailingMapper,
+        onlyTrailingUnmapper
+      )
+    ),
+    convertFromNext(
+      convertToNext(
+        tuple(array(h16Arb, { minLength: 0, maxLength: 1 }), array(h16Arb, { minLength: 4, maxLength: 4 }), ls32Arb)
+      ).map(multiTrailingMapper, multiTrailingUnmapper)
+    ),
+    convertFromNext(
+      convertToNext(
+        tuple(array(h16Arb, { minLength: 0, maxLength: 2 }), array(h16Arb, { minLength: 3, maxLength: 3 }), ls32Arb)
+      ).map(multiTrailingMapper, multiTrailingUnmapper)
+    ),
+    convertFromNext(
+      convertToNext(
+        tuple(array(h16Arb, { minLength: 0, maxLength: 3 }), array(h16Arb, { minLength: 2, maxLength: 2 }), ls32Arb)
+      ).map(multiTrailingMapper, multiTrailingUnmapper)
     ),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 2 }), array(h16Arb, { minLength: 3, maxLength: 3 }), ls32Arb).map(
-      ([bh, eh, l]) => `${bh.join(':')}::${eh.join(':')}:${l}`
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 0, maxLength: 4 }), h16Arb, ls32Arb)).map(
+        multiTrailingMapperOne,
+        multiTrailingUnmapperOne
+      )
     ),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 3 }), array(h16Arb, { minLength: 2, maxLength: 2 }), ls32Arb).map(
-      ([bh, eh, l]) => `${bh.join(':')}::${eh.join(':')}:${l}`
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 0, maxLength: 5 }), ls32Arb)).map(
+        singleTrailingMapper,
+        singleTrailingUnmapper
+      )
     ),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 4 }), h16Arb, ls32Arb).map(
-      ([bh, eh, l]) => `${bh.join(':')}::${eh}:${l}`
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 0, maxLength: 6 }), h16Arb)).map(
+        singleTrailingMapper,
+        singleTrailingUnmapper
+      )
     ),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 5 }), ls32Arb).map(([bh, l]) => `${bh.join(':')}::${l}`),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 6 }), h16Arb).map(([bh, eh]) => `${bh.join(':')}::${eh}`),
-    tuple(array(h16Arb, { minLength: 0, maxLength: 7 })).map(([bh]) => `${bh.join(':')}::`)
+    convertFromNext(
+      convertToNext(tuple(array(h16Arb, { minLength: 0, maxLength: 7 }))).map(noTrailingMapper, noTrailingUnmapper)
+    )
   );
 }


Test patch:
diff --git a/test/unit/arbitrary/IpArbitrary.spec.ts b/test/unit/arbitrary/IpArbitrary.spec.ts
deleted file mode 100644
index af93a7ad..00000000
--- a/test/unit/arbitrary/IpArbitrary.spec.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { ipV4 } from '../../../src/arbitrary/ipV4';
-import { ipV4Extended } from '../../../src/arbitrary/ipV4Extended';
-import { ipV6 } from '../../../src/arbitrary/ipV6';
-
-import * as genericHelper from '../check/arbitrary/generic/GenericArbitraryHelper';
-
-const isValidIpV4 = (i: string) => {
-  if (typeof i !== 'string') return false;
-  const chunks = i.split('.').map((v) => {
-    if (v[0] === '0') {
-      if (v[1] === 'x' || v[1] === 'X') return parseInt(v, 16);
-      return parseInt(v, 8);
-    }
-    return parseInt(v, 10);
-  });
-
-  // one invalid chunk
-  if (chunks.find((v) => Number.isNaN(v)) !== undefined) return false;
-
-  // maximal amount of 4 chunks
-  if (chunks.length > 4) return false;
-
-  // all chunks, except the last one are inferior or equal to 255
-  if (chunks.slice(0, -1).find((v) => v < 0 && v > 255) !== undefined) return false;
-
-  // last chunk must be below 256^(5 − number of chunks)
-  return chunks[chunks.length - 1] < 256 ** (5 - chunks.length);
-};
-const isValidIpV6 = (i: string) => {
-  if (typeof i !== 'string') return false;
-  const firstElision = i.indexOf('::');
-  if (firstElision !== -1) {
-    // At most one '::'
-    if (i.substr(firstElision + 1).includes('::')) return false;
-  }
-  const chunks = i.split(':');
-  const last = chunks[chunks.length - 1];
-  // The ipv4 can only be composed of 4 decimal chunks separated by dots
-  // 1.1000 is not a valid IP v4 in the context of IP v6
-  const endByIpV4 = last.includes('.') && isValidIpV4(last);
-
-  const nonEmptyChunks = chunks.filter((c) => c !== '');
-  const hexaChunks = endByIpV4 ? nonEmptyChunks.slice(0, nonEmptyChunks.length - 1) : nonEmptyChunks;
-  if (!hexaChunks.every((s) => /^[0-9a-f]{1,4}$/.test(s))) return false;
-
-  const equivalentChunkLength = endByIpV4 ? hexaChunks.length + 2 : hexaChunks.length;
-  return firstElision !== -1 ? equivalentChunkLength < 8 : equivalentChunkLength === 8;
-};
-
-describe('IpArbitrary', () => {
-  describe('ipV4', () => {
-    genericHelper.isValidArbitrary(() => ipV4(), {
-      isValidValue: (g: string) => isValidIpV4(g),
-    });
-  });
-  describe('ipV4Extended', () => {
-    genericHelper.isValidArbitrary(() => ipV4Extended(), {
-      isValidValue: (g: string) => isValidIpV4(g),
-    });
-  });
-  describe('ipV6', () => {
-    genericHelper.isValidArbitrary(() => ipV6(), {
-      isValidValue: (g: string) => isValidIpV6(g),
-    });
-  });
-});
diff --git a/test/unit/arbitrary/__snapshots__/ipV4.spec.ts.snap b/test/unit/arbitrary/__snapshots__/ipV4.spec.ts.snap
new file mode 100644
index 00000000..46150e67
--- /dev/null
+++ b/test/unit/arbitrary/__snapshots__/ipV4.spec.ts.snap
@@ -0,0 +1,219 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ipV4 (integration) should be able to shrink 8.8.4.4 1`] = `
+"\\"8.8.4.4\\"
+├> \\"0.8.4.4\\"
+|  ├> \\"0.0.4.4\\"
+|  |  ├> \\"0.0.0.4\\"
+|  |  |  ├> \\"0.0.0.0\\"
+|  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  └> \\"0.0.0.3\\"
+|  |  |     └> \\"0.0.0.2\\"
+|  |  |        ├> \\"0.0.0.0\\"
+|  |  |        └> \\"0.0.0.1\\"
+|  |  |           └> \\"0.0.0.0\\"
+|  |  ├> \\"0.0.2.4\\"
+|  |  |  ├> \\"0.0.1.4\\"
+|  |  |  |  ├> \\"0.0.0.4\\"
+|  |  |  |  |  ├> \\"0.0.0.0\\"
+|  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"0.0.0.3\\"
+|  |  |  |  |     └> \\"0.0.0.2\\"
+|  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |        └> \\"0.0.0.1\\"
+|  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  ├> \\"0.0.1.0\\"
+|  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  ├> \\"0.0.1.2\\"
+|  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"0.0.1.1\\"
+|  |  |  |  |     ├> \\"0.0.0.1\\"
+|  |  |  |  |     |  └> \\"0.0.0.0\\"
+|  |  |  |  |     └> \\"0.0.1.0\\"
+|  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  └> \\"0.0.1.3\\"
+|  |  |  |     ├> \\"0.0.0.3\\"
+|  |  |  |     |  └> \\"0.0.0.2\\"
+|  |  |  |     |     ├> \\"0.0.0.0\\"
+|  |  |  |     |     └> \\"0.0.0.1\\"
+|  |  |  |     |        └> \\"0.0.0.0\\"
+|  |  |  |     └> \\"0.0.1.2\\"
+|  |  |  |        ├> \\"0.0.0.2\\"
+|  |  |  |        |  ├> \\"0.0.0.0\\"
+|  |  |  |        |  └> \\"0.0.0.1\\"
+|  |  |  |        |     └> \\"0.0.0.0\\"
+|  |  |  |        ├> \\"0.0.1.0\\"
+|  |  |  |        |  └> \\"0.0.0.0\\"
+|  |  |  |        └> \\"0.0.1.1\\"
+|  |  |  |           ├> \\"0.0.0.1\\"
+|  |  |  |           |  └> \\"0.0.0.0\\"
+|  |  |  |           └> \\"0.0.1.0\\"
+|  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  ├> \\"0.0.2.0\\"
+|  |  |  |  └> \\"0.0.1.0\\"
+|  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  ├> \\"0.0.2.2\\"
+|  |  |  |  ├> \\"0.0.1.2\\"
+|  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"0.0.1.1\\"
+|  |  |  |  |     ├> \\"0.0.0.1\\"
+|  |  |  |  |     |  └> \\"0.0.0.0\\"
+|  |  |  |  |     └> \\"0.0.1.0\\"
+|  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  └> \\"0.0.2.1\\"
+|  |  |  |     ├> \\"0.0.1.1\\"
+|  |  |  |     |  ├> \\"0.0.0.1\\"
+|  |  |  |     |  |  └> \\"0.0.0.0\\"
+|  |  |  |     |  └> \\"0.0.1.0\\"
+|  |  |  |     |     └> \\"0.0.0.0\\"
+|  |  |  |     └> \\"0.0.2.0\\"
+|  |  |  |        └> \\"0.0.1.0\\"
+|  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  └> \\"0.0.2.3\\"
+|  |  |     ├> \\"0.0.1.3\\"
+|  |  |     |  ├> \\"0.0.0.3\\"
+|  |  |     |  |  └> \\"0.0.0.2\\"
+|  |  |     |  |     ├> \\"0.0.0.0\\"
+|  |  |     |  |     └> \\"0.0.0.1\\"
+|  |  |     |  |        └> \\"0.0.0.0\\"
+|  |  |     |  └> \\"0.0.1.2\\"
+|  |  |     |     ├> \\"0.0.0.2\\"
+|  |  |     |     |  ├> \\"0.0.0.0\\"
+|  |  |     |     |  └> \\"0.0.0.1\\"
+|  |  |     |     |     └> \\"0.0.0.0\\"
+|  |  |     |     ├> \\"0.0.1.0\\"
+|  |  |     |     |  └> \\"0.0.0.0\\"
+|  |  |     |     └> \\"0.0.1.1\\"
+|  |  |     |        ├> \\"0.0.0.1\\"
+|  |  |     |        |  └> \\"0.0.0.0\\"
+|  |  |     |        └> \\"0.0.1.0\\"
+|  |  |     |           └> \\"0.0.0.0\\"
+|  |  |     └> \\"0.0.2.2\\"
+|  |  |        ├> \\"0.0.1.2\\"
+|  |  |        |  ├> \\"0.0.0.2\\"
+|  |  |        |  |  ├> \\"0.0.0.0\\"
+|  |  |        |  |  └> \\"0.0.0.1\\"
+|  |  |        |  |     └> …
+|  |  |        |  └> …
+|  |  |        └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
+
+exports[`ipV4 (integration) should be able to shrink 128.0.0.1 1`] = `
+"\\"128.0.0.1\\"
+├> \\"0.0.0.1\\"
+|  └> \\"0.0.0.0\\"
+├> \\"64.0.0.1\\"
+|  ├> \\"32.0.0.1\\"
+|  |  ├> \\"16.0.0.1\\"
+|  |  |  ├> \\"8.0.0.1\\"
+|  |  |  |  ├> \\"4.0.0.1\\"
+|  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  ├> \\"3.0.0.1\\"
+|  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"3.0.0.0\\"
+|  |  |  |  |  |     └> \\"2.0.0.0\\"
+|  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |        └> \\"1.0.0.0\\"
+|  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"4.0.0.0\\"
+|  |  |  |  |     ├> \\"2.0.0.0\\"
+|  |  |  |  |     |  └> \\"1.0.0.0\\"
+|  |  |  |  |     |     └> \\"0.0.0.0\\"
+|  |  |  |  |     └> \\"3.0.0.0\\"
+|  |  |  |  |        └> \\"2.0.0.0\\"
+|  |  |  |  |           ├> \\"0.0.0.0\\"
+|  |  |  |  |           └> \\"1.0.0.0\\"
+|  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  ├> \\"6.0.0.1\\"
+|  |  |  |  |  ├> \\"5.0.0.1\\"
+|  |  |  |  |  |  ├> \\"4.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"3.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"3.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"4.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"2.0.0.0\\"
+|  |  |  |  |  |  |     |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |     |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"3.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |           ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |           └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"5.0.0.0\\"
+|  |  |  |  |  |     └> \\"4.0.0.0\\"
+|  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |        ├> \\"2.0.0.0\\"
+|  |  |  |  |  |        |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |        |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |        └> \\"3.0.0.0\\"
+|  |  |  |  |  |           └> \\"2.0.0.0\\"
+|  |  |  |  |  |              ├> \\"0.0.0.0\\"
+|  |  |  |  |  |              └> \\"1.0.0.0\\"
+|  |  |  |  |  |                 └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"6.0.0.0\\"
+|  |  |  |  |     └> \\"5.0.0.0\\"
+|  |  |  |  |        └> \\"4.0.0.0\\"
+|  |  |  |  |           └> …
+|  |  |  |  └> …
+|  |  |  └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
diff --git a/test/unit/arbitrary/__snapshots__/ipV4Extended.spec.ts.snap b/test/unit/arbitrary/__snapshots__/ipV4Extended.spec.ts.snap
new file mode 100644
index 00000000..de60b396
--- /dev/null
+++ b/test/unit/arbitrary/__snapshots__/ipV4Extended.spec.ts.snap
@@ -0,0 +1,221 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ipV4Extended (integration) should be able to shrink 010.0x8.4.04 1`] = `
+"\\"010.0x8.4.04\\"
+├> \\"8.0x8.4.04\\"
+|  ├> \\"0.0x8.4.04\\"
+|  |  ├> \\"0.8.4.04\\"
+|  |  |  ├> \\"0.0.4.04\\"
+|  |  |  |  ├> \\"0.0.0.04\\"
+|  |  |  |  |  ├> \\"0.0.0.4\\"
+|  |  |  |  |  |  ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"0.0.0.3\\"
+|  |  |  |  |  |     └> \\"0.0.0.2\\"
+|  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |        └> \\"0.0.0.1\\"
+|  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  ├> \\"0.0.0.00\\"
+|  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  ├> \\"0.0.0.02\\"
+|  |  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"0.0.0.01\\"
+|  |  |  |  |  |     ├> \\"0.0.0.1\\"
+|  |  |  |  |  |     |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |     └> \\"0.0.0.00\\"
+|  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"0.0.0.03\\"
+|  |  |  |  |     ├> \\"0.0.0.3\\"
+|  |  |  |  |     |  └> \\"0.0.0.2\\"
+|  |  |  |  |     |     ├> \\"0.0.0.0\\"
+|  |  |  |  |     |     └> \\"0.0.0.1\\"
+|  |  |  |  |     |        └> \\"0.0.0.0\\"
+|  |  |  |  |     └> \\"0.0.0.02\\"
+|  |  |  |  |        ├> \\"0.0.0.2\\"
+|  |  |  |  |        |  ├> \\"0.0.0.0\\"
+|  |  |  |  |        |  └> \\"0.0.0.1\\"
+|  |  |  |  |        |     └> \\"0.0.0.0\\"
+|  |  |  |  |        ├> \\"0.0.0.00\\"
+|  |  |  |  |        |  └> \\"0.0.0.0\\"
+|  |  |  |  |        └> \\"0.0.0.01\\"
+|  |  |  |  |           ├> \\"0.0.0.1\\"
+|  |  |  |  |           |  └> \\"0.0.0.0\\"
+|  |  |  |  |           └> \\"0.0.0.00\\"
+|  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  ├> \\"0.0.2.04\\"
+|  |  |  |  |  ├> \\"0.0.1.04\\"
+|  |  |  |  |  |  ├> \\"0.0.0.04\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.4\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.3\\"
+|  |  |  |  |  |  |  |     └> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.00\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.02\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.01\\"
+|  |  |  |  |  |  |  |     ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |     |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0.0.0.00\\"
+|  |  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"0.0.0.03\\"
+|  |  |  |  |  |  |     ├> \\"0.0.0.3\\"
+|  |  |  |  |  |  |     |  └> \\"0.0.0.2\\"
+|  |  |  |  |  |  |     |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     |     └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |     |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"0.0.0.02\\"
+|  |  |  |  |  |  |        ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |        |  ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |        |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |        |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |        ├> \\"0.0.0.00\\"
+|  |  |  |  |  |  |        |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"0.0.0.01\\"
+|  |  |  |  |  |  |           ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |           |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |           └> \\"0.0.0.00\\"
+|  |  |  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  |  |  ├> \\"0.0.1.4\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.4\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  |  |  └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.3\\"
+|  |  |  |  |  |  |  |     └> \\"0.0.0.2\\"
+|  |  |  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"0.0.1.0\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> …
+|  |  |  |  |  |  └> …
+|  |  |  |  |  └> …
+|  |  |  |  └> …
+|  |  |  └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
+
+exports[`ipV4Extended (integration) should be able to shrink 128.0.0.1 1`] = `
+"\\"128.0.0.1\\"
+├> \\"0.0.0.1\\"
+|  └> \\"0.0.0.0\\"
+├> \\"64.0.0.1\\"
+|  ├> \\"32.0.0.1\\"
+|  |  ├> \\"16.0.0.1\\"
+|  |  |  ├> \\"8.0.0.1\\"
+|  |  |  |  ├> \\"4.0.0.1\\"
+|  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  ├> \\"3.0.0.1\\"
+|  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"3.0.0.0\\"
+|  |  |  |  |  |     └> \\"2.0.0.0\\"
+|  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |        └> \\"1.0.0.0\\"
+|  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"4.0.0.0\\"
+|  |  |  |  |     ├> \\"2.0.0.0\\"
+|  |  |  |  |     |  └> \\"1.0.0.0\\"
+|  |  |  |  |     |     └> \\"0.0.0.0\\"
+|  |  |  |  |     └> \\"3.0.0.0\\"
+|  |  |  |  |        └> \\"2.0.0.0\\"
+|  |  |  |  |           ├> \\"0.0.0.0\\"
+|  |  |  |  |           └> \\"1.0.0.0\\"
+|  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  ├> \\"6.0.0.1\\"
+|  |  |  |  |  ├> \\"5.0.0.1\\"
+|  |  |  |  |  |  ├> \\"4.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"3.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"2.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  ├> \\"1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  ├> \\"0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |  |        └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"3.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |  |           └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"4.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"2.0.0.0\\"
+|  |  |  |  |  |  |     |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |     |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"3.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"2.0.0.0\\"
+|  |  |  |  |  |  |           ├> \\"0.0.0.0\\"
+|  |  |  |  |  |  |           └> \\"1.0.0.0\\"
+|  |  |  |  |  |  |              └> \\"0.0.0.0\\"
+|  |  |  |  |  |  └> \\"5.0.0.0\\"
+|  |  |  |  |  |     └> \\"4.0.0.0\\"
+|  |  |  |  |  |        ├> \\"0.0.0.0\\"
+|  |  |  |  |  |        ├> \\"2.0.0.0\\"
+|  |  |  |  |  |        |  └> \\"1.0.0.0\\"
+|  |  |  |  |  |        |     └> \\"0.0.0.0\\"
+|  |  |  |  |  |        └> \\"3.0.0.0\\"
+|  |  |  |  |  |           └> \\"2.0.0.0\\"
+|  |  |  |  |  |              ├> \\"0.0.0.0\\"
+|  |  |  |  |  |              └> \\"1.0.0.0\\"
+|  |  |  |  |  |                 └> \\"0.0.0.0\\"
+|  |  |  |  |  └> \\"6.0.0.0\\"
+|  |  |  |  |     └> \\"5.0.0.0\\"
+|  |  |  |  |        └> \\"4.0.0.0\\"
+|  |  |  |  |           └> …
+|  |  |  |  └> …
+|  |  |  └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
diff --git a/test/unit/arbitrary/__snapshots__/ipV6.spec.ts.snap b/test/unit/arbitrary/__snapshots__/ipV6.spec.ts.snap
new file mode 100644
index 00000000..66495175
--- /dev/null
+++ b/test/unit/arbitrary/__snapshots__/ipV6.spec.ts.snap
@@ -0,0 +1,117 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ipV6 (integration) should be able to shrink ::1 1`] = `
+"\\"::1\\"
+└> \\"::0\\""
+`;
+
+exports[`ipV6 (integration) should be able to shrink 0123:5678:9abc:ef01:2345:6789:128.0.0.1 1`] = `
+"\\"0123:5678:9abc:ef01:2345:6789:128.0.0.1\\"
+├> \\"3:5678:9abc:ef01:2345:6789:128.0.0.1\\"
+|  ├> \\"0:5678:9abc:ef01:2345:6789:128.0.0.1\\"
+|  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  ├> \\"0:5678:9abc:ef01:2345:6789:64.0.0.1\\"
+|  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:32.0.0.1\\"
+|  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:16.0.0.1\\"
+|  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:8.0.0.1\\"
+|  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:4.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:3.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |     ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:3.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |        ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |           └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:4.0.0.0\\"
+|  |  |  |  |  |  |     ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |     |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |     |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:3.0.0.0\\"
+|  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |           ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |           └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |              └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:6.0.0.1\\"
+|  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:5.0.0.1\\"
+|  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:4.0.0.1\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:3.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:1.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  |  ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.1\\"
+|  |  |  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |     ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:3.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |        ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |  |           └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:4.0.0.0\\"
+|  |  |  |  |  |  |  |  |     ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |     ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |     |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |     |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:3.0.0.0\\"
+|  |  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |  |           ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  |           └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |  |              └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:5.0.0.0\\"
+|  |  |  |  |  |  |  |     └> \\"0:5678:9abc:ef01:2345:6789:4.0.0.0\\"
+|  |  |  |  |  |  |  |        ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |        ├> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |        |  └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |        |     └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |        └> \\"0:5678:9abc:ef01:2345:6789:3.0.0.0\\"
+|  |  |  |  |  |  |  |           └> \\"0:5678:9abc:ef01:2345:6789:2.0.0.0\\"
+|  |  |  |  |  |  |  |              ├> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  |              └> \\"0:5678:9abc:ef01:2345:6789:1.0.0.0\\"
+|  |  |  |  |  |  |  |                 └> \\"0:5678:9abc:ef01:2345:6789:0.0.0.0\\"
+|  |  |  |  |  |  |  └> \\"0:5678:9abc:ef01:2345:6789:6.0.0.0\\"
+|  |  |  |  |  |  |     └> …
+|  |  |  |  |  |  └> …
+|  |  |  |  |  └> …
+|  |  |  |  └> …
+|  |  |  └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
diff --git a/test/unit/arbitrary/_internals/mappers/EntitiesToIPv6.spec.ts b/test/unit/arbitrary/_internals/mappers/EntitiesToIPv6.spec.ts
new file mode 100644
index 00000000..caa2421f
--- /dev/null
+++ b/test/unit/arbitrary/_internals/mappers/EntitiesToIPv6.spec.ts
@@ -0,0 +1,141 @@
+import {
+  fullySpecifiedUnmapper,
+  multiTrailingUnmapper,
+  multiTrailingUnmapperOne,
+  noTrailingUnmapper,
+  onlyTrailingUnmapper,
+  singleTrailingUnmapper,
+} from '../../../../../src/arbitrary/_internals/mappers/EntitiesToIPv6';
+
+describe('fullySpecifiedUnmapper', () => {
+  it.each`
+    ip                                           | expected
+    ${'0123:5678:9abc:ef01:2345:6789:128.0.0.1'} | ${[['0123', '5678', '9abc', 'ef01', '2345', '6789'], '128.0.0.1']}
+    ${'0123:5678:9abc:ef01:2345:6789:0000:ffff'} | ${[['0123', '5678', '9abc', 'ef01', '2345', '6789'], '0000:ffff']}
+    ${'0:56:9abc:ef01:234:67:8.8.8.8'}           | ${[['0', '56', '9abc', 'ef01', '234', '67'], '8.8.8.8']}
+    ${'0:56:9abc:ef01:234:67:0:f'}               | ${[['0', '56', '9abc', 'ef01', '234', '67'], '0:f']}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = fullySpecifiedUnmapper(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
+
+describe('onlyTrailingUnmapper', () => {
+  it.each`
+    ip                                        | expected
+    ${'::5678:9abc:ef01:2345:6789:128.0.0.1'} | ${[['5678', '9abc', 'ef01', '2345', '6789'], '128.0.0.1']}
+    ${'::5678:9abc:ef01:2345:6789:0000:ffff'} | ${[['5678', '9abc', 'ef01', '2345', '6789'], '0000:ffff']}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = onlyTrailingUnmapper(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
+
+describe('multiTrailingUnmapper', () => {
+  it.each`
+    ip                                       | expected
+    ${'::9abc:ef01:2345:6789:128.0.0.1'}     | ${[[], ['9abc', 'ef01', '2345', '6789'], '128.0.0.1']}
+    ${'::9abc:ef01:2345:6789:0000:ffff'}     | ${[[], ['9abc', 'ef01', '2345', '6789'], '0000:ffff']}
+    ${'5678::9abc:ef01:2345:6789:128.0.0.1'} | ${[['5678'], ['9abc', 'ef01', '2345', '6789'], '128.0.0.1']}
+    ${'5678::9abc:ef01:2345:6789:0000:ffff'} | ${[['5678'], ['9abc', 'ef01', '2345', '6789'], '0000:ffff']}
+    ${'::ef01:2345:6789:128.0.0.1'}          | ${[[], ['ef01', '2345', '6789'], '128.0.0.1']}
+    ${'::ef01:2345:6789:0000:ffff'}          | ${[[], ['ef01', '2345', '6789'], '0000:ffff']}
+    ${'9abc::ef01:2345:6789:128.0.0.1'}      | ${[['9abc'], ['ef01', '2345', '6789'], '128.0.0.1']}
+    ${'9abc::ef01:2345:6789:0000:ffff'}      | ${[['9abc'], ['ef01', '2345', '6789'], '0000:ffff']}
+    ${'5678:9abc::ef01:2345:6789:128.0.0.1'} | ${[['5678', '9abc'], ['ef01', '2345', '6789'], '128.0.0.1']}
+    ${'5678:9abc::ef01:2345:6789:0000:ffff'} | ${[['5678', '9abc'], ['ef01', '2345', '6789'], '0000:ffff']}
+    ${'::2345:6789:128.0.0.1'}               | ${[[], ['2345', '6789'], '128.0.0.1']}
+    ${'::2345:6789:0000:ffff'}               | ${[[], ['2345', '6789'], '0000:ffff']}
+    ${'ef01::2345:6789:128.0.0.1'}           | ${[['ef01'], ['2345', '6789'], '128.0.0.1']}
+    ${'ef01::2345:6789:0000:ffff'}           | ${[['ef01'], ['2345', '6789'], '0000:ffff']}
+    ${'9abc:ef01::2345:6789:128.0.0.1'}      | ${[['9abc', 'ef01'], ['2345', '6789'], '128.0.0.1']}
+    ${'9abc:ef01::2345:6789:0000:ffff'}      | ${[['9abc', 'ef01'], ['2345', '6789'], '0000:ffff']}
+    ${'5678:9abc:ef01::2345:6789:128.0.0.1'} | ${[['5678', '9abc', 'ef01'], ['2345', '6789'], '128.0.0.1']}
+    ${'5678:9abc:ef01::2345:6789:0000:ffff'} | ${[['5678', '9abc', 'ef01'], ['2345', '6789'], '0000:ffff']}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = multiTrailingUnmapper(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
+
+describe('multiTrailingUnmapperOne', () => {
+  it.each`
+    ip                                       | expected
+    ${'::6789:128.0.0.1'}                    | ${[[], '6789', '128.0.0.1']}
+    ${'::6789:0000:ffff'}                    | ${[[], '6789', '0000:ffff']}
+    ${'2345::6789:128.0.0.1'}                | ${[['2345'], '6789', '128.0.0.1']}
+    ${'2345::6789:0000:ffff'}                | ${[['2345'], '6789', '0000:ffff']}
+    ${'ef01:2345::6789:128.0.0.1'}           | ${[['ef01', '2345'], '6789', '128.0.0.1']}
+    ${'ef01:2345::6789:0000:ffff'}           | ${[['ef01', '2345'], '6789', '0000:ffff']}
+    ${'9abc:ef01:2345::6789:128.0.0.1'}      | ${[['9abc', 'ef01', '2345'], '6789', '128.0.0.1']}
+    ${'9abc:ef01:2345::6789:0000:ffff'}      | ${[['9abc', 'ef01', '2345'], '6789', '0000:ffff']}
+    ${'5678:9abc:ef01:2345::6789:128.0.0.1'} | ${[['5678', '9abc', 'ef01', '2345'], '6789', '128.0.0.1']}
+    ${'5678:9abc:ef01:2345::6789:0000:ffff'} | ${[['5678', '9abc', 'ef01', '2345'], '6789', '0000:ffff']}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = multiTrailingUnmapperOne(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
+
+describe('singleTrailingUnmapper', () => {
+  it.each`
+    ip                                       | expected
+    ${'::128.0.0.1'}                         | ${[[], '128.0.0.1']}
+    ${'::0000:ffff'}                         | ${[[], '0000:ffff']}
+    ${'2345::128.0.0.1'}                     | ${[['2345'], '128.0.0.1']}
+    ${'2345::0000:ffff'}                     | ${[['2345'], '0000:ffff']}
+    ${'ef01:2345::128.0.0.1'}                | ${[['ef01', '2345'], '128.0.0.1']}
+    ${'ef01:2345::0000:ffff'}                | ${[['ef01', '2345'], '0000:ffff']}
+    ${'9abc:ef01:2345::128.0.0.1'}           | ${[['9abc', 'ef01', '2345'], '128.0.0.1']}
+    ${'9abc:ef01:2345::0000:ffff'}           | ${[['9abc', 'ef01', '2345'], '0000:ffff']}
+    ${'5678:9abc:ef01:2345::128.0.0.1'}      | ${[['5678', '9abc', 'ef01', '2345'], '128.0.0.1']}
+    ${'5678:9abc:ef01:2345::0000:ffff'}      | ${[['5678', '9abc', 'ef01', '2345'], '0000:ffff']}
+    ${'0123:5678:9abc:ef01:2345::128.0.0.1'} | ${[['0123', '5678', '9abc', 'ef01', '2345'], '128.0.0.1']}
+    ${'0123:5678:9abc:ef01:2345::0000:ffff'} | ${[['0123', '5678', '9abc', 'ef01', '2345'], '0000:ffff']}
+    ${'::0123'}                              | ${[[], '0123']}
+    ${'6789::0123'}                          | ${[['6789'], '0123']}
+    ${'2345:6789::0123'}                     | ${[['2345', '6789'], '0123']}
+    ${'ef01:2345:6789::0123'}                | ${[['ef01', '2345', '6789'], '0123']}
+    ${'9abc:ef01:2345:6789::0123'}           | ${[['9abc', 'ef01', '2345', '6789'], '0123']}
+    ${'5678:9abc:ef01:2345:6789::0123'}      | ${[['5678', '9abc', 'ef01', '2345', '6789'], '0123']}
+    ${'0123:5678:9abc:ef01:2345:6789::0123'} | ${[['0123', '5678', '9abc', 'ef01', '2345', '6789'], '0123']}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = singleTrailingUnmapper(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
+
+describe('noTrailingUnmapper', () => {
+  it.each`
+    ip                                        | expected
+    ${'::'}                                   | ${[[]]}
+    ${'0123::'}                               | ${[['0123']]}
+    ${'6789:0123::'}                          | ${[['6789', '0123']]}
+    ${'2345:6789:0123::'}                     | ${[['2345', '6789', '0123']]}
+    ${'ef01:2345:6789:0123::'}                | ${[['ef01', '2345', '6789', '0123']]}
+    ${'9abc:ef01:2345:6789:0123::'}           | ${[['9abc', 'ef01', '2345', '6789', '0123']]}
+    ${'5678:9abc:ef01:2345:6789:0123::'}      | ${[['5678', '9abc', 'ef01', '2345', '6789', '0123']]}
+    ${'0123:5678:9abc:ef01:2345:6789:0123::'} | ${[['0123', '5678', '9abc', 'ef01', '2345', '6789', '0123']]}
+  `('should properly unmap IPv6 $ip', ({ ip, expected }) => {
+    // Arrange / Act
+    const out = noTrailingUnmapper(ip);
+
+    // Assert
+    expect(out).toEqual(expected);
+  });
+});
diff --git a/test/unit/arbitrary/_internals/mappers/NatToStringifiedNat.spec.ts b/test/unit/arbitrary/_internals/mappers/NatToStringifiedNat.spec.ts
new file mode 100644
index 00000000..c46c521e
--- /dev/null
+++ b/test/unit/arbitrary/_internals/mappers/NatToStringifiedNat.spec.ts
@@ -0,0 +1,53 @@
+import fc from '../../../../../lib/fast-check';
+import {
+  natToStringifiedNatUnmapper,
+  natToStringifiedNatMapper,
+  tryParseStringifiedNat,
+} from '../../../../../src/arbitrary/_internals/mappers/NatToStringifiedNat';
+
+describe('tryParseStringifiedNat', () => {
+  it('should be able to parse any nat serialized with toString(radix)', () =>
+    fc.assert(
+      fc.property(fc.maxSafeNat(), fc.integer({ min: 2, max: 36 }), (n, radix) => {
+        // Arrange
+        const stringifiedNat = n.toString(radix);
+
+        // Act
+        const out = tryParseStringifiedNat(stringifiedNat, radix);
+
+        // Assert
+        expect(out).toBe(n);
+      })
+    ));
+});
+
+describe('natToStringifiedNatUnmapper', () => {
+  it.each`
+    value     | reason
+    ${''}     | ${'empty string'}
+    ${'1e0'}  | ${'no scientific notation'}
+    ${'x00'}  | ${'incomplete hex with missing start'}
+    ${'0x'}   | ${'incomplete hex with missing end'}
+    ${'0x00'} | ${'too many figures hex'}
+    ${'0xF'}  | ${'wrong case for hex'}
+    ${'000'}  | ${'too many figures oct'}
+  `('should reject "$value" ($reason)', ({ value }) => {
+    // Arrange / Act / Assert
+    expect(() => natToStringifiedNatUnmapper(value)).toThrowError();
+  });
+
+  it('should be able to unmap any mapped value', () =>
+    fc.assert(
+      fc.property(fc.maxSafeNat(), fc.constantFrom(...(['dec', 'oct', 'hex'] as const)), (n, base) => {
+        // Arrange
+        const stringifiedNat = natToStringifiedNatMapper([base, n]);
+
+        // Act
+        const out = natToStringifiedNatUnmapper(stringifiedNat);
+
+        // Assert
+        expect(out[0]).toBe(base);
+        expect(out[1]).toBe(n);
+      })
+    ));
+});
diff --git a/test/unit/arbitrary/dictionary.spec.ts b/test/unit/arbitrary/dictionary.spec.ts
index 3131d71e..b03b20bb 100644
--- a/test/unit/arbitrary/dictionary.spec.ts
+++ b/test/unit/arbitrary/dictionary.spec.ts
@@ -29,7 +29,8 @@ describe('dictionary (integration)', () => {
       expect(extra.keys).toContain(k);
     }
     for (const v of Object.values(value)) {
-      expect(extra.values).toContain(v);
+      if (Number.isNaN(v)) expect(extra.values.includes(v)).toBe(true);
+      else expect(extra.values).toContain(v); // exact same value (not a copy)
     }
   };
 
diff --git a/test/unit/arbitrary/ipV4.spec.ts b/test/unit/arbitrary/ipV4.spec.ts
new file mode 100644
index 00000000..5532a2b1
--- /dev/null
+++ b/test/unit/arbitrary/ipV4.spec.ts
@@ -0,0 +1,98 @@
+import { ipV4 } from '../../../src/arbitrary/ipV4';
+
+import { convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { NextValue } from '../../../src/check/arbitrary/definition/NextValue';
+import {
+  assertGenerateProducesSameValueGivenSameSeed,
+  assertGenerateProducesCorrectValues,
+  assertGenerateProducesValuesFlaggedAsCanGenerate,
+  assertShrinkProducesSameValueWithoutInitialContext,
+  assertShrinkProducesCorrectValues,
+  assertShrinkProducesValuesFlaggedAsCanGenerate,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+import { buildNextShrinkTree, renderTree } from '../check/arbitrary/generic/ShrinkTree';
+
+describe('ipV4 (integration)', () => {
+  const isCorrect = (value: string) => {
+    expect(value).toMatch(/^\d+\.\d+\.\d+\.\d+$/);
+    for (const item of value.split('.')) {
+      expect(Number(item)).not.toBeNaN();
+      expect(Number(item)).toBeGreaterThanOrEqual(0);
+      expect(Number(item)).toBeLessThanOrEqual(255);
+    }
+  };
+
+  const ipV4Builder = () => convertToNext(ipV4());
+
+  it('should generate the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV4Builder);
+  });
+
+  it('should only generate correct values', () => {
+    assertGenerateProducesCorrectValues(ipV4Builder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during generate', () => {
+    assertGenerateProducesValuesFlaggedAsCanGenerate(ipV4Builder);
+  });
+
+  it('should shrink towards the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV4Builder);
+  });
+
+  it('should be able to shrink without any context', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(ipV4Builder);
+  });
+
+  it('should only shrink towards correct values', () => {
+    assertShrinkProducesCorrectValues(ipV4Builder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during shrink', () => {
+    assertShrinkProducesValuesFlaggedAsCanGenerate(ipV4Builder);
+  });
+
+  it.each`
+    source
+    ${'0.0.0.0'}
+    ${'255.255.255.255'}
+  `('should be able to generate $source with fc.ipV4()', ({ source }) => {
+    // Arrange / Act
+    const arb = convertToNext(ipV4());
+    const out = arb.canGenerate(source);
+
+    // Assert
+    expect(out).toBe(true);
+  });
+
+  it.each`
+    source
+    ${'256.0.0.0'}
+    ${'00.0.0.0'}
+    ${'0xff.0.0.0'}
+    ${'-1.0.0.0'}
+  `('should not be able to generate $source with fc.ipV4()', ({ source }) => {
+    // Arrange / Act
+    const arb = convertToNext(ipV4());
+    const out = arb.canGenerate(source);
+
+    // Assert
+    expect(out).toBe(false);
+  });
+
+  it.each`
+    rawValue
+    ${'128.0.0.1'}
+    ${'8.8.4.4'}
+  `('should be able to shrink $rawValue', ({ rawValue }) => {
+    // Arrange
+    const arb = convertToNext(ipV4());
+    const value = new NextValue(rawValue);
+
+    // Act
+    const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
+
+    // Assert
+    expect(renderedTree).toMatchSnapshot();
+  });
+});
diff --git a/test/unit/arbitrary/ipV4Extended.spec.ts b/test/unit/arbitrary/ipV4Extended.spec.ts
new file mode 100644
index 00000000..a938f406
--- /dev/null
+++ b/test/unit/arbitrary/ipV4Extended.spec.ts
@@ -0,0 +1,117 @@
+import { ipV4Extended } from '../../../src/arbitrary/ipV4Extended';
+
+import { convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { NextValue } from '../../../src/check/arbitrary/definition/NextValue';
+import {
+  assertGenerateProducesSameValueGivenSameSeed,
+  assertGenerateProducesCorrectValues,
+  assertGenerateProducesValuesFlaggedAsCanGenerate,
+  assertShrinkProducesSameValueWithoutInitialContext,
+  assertShrinkProducesCorrectValues,
+  assertShrinkProducesValuesFlaggedAsCanGenerate,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+import { buildNextShrinkTree, renderTree } from '../check/arbitrary/generic/ShrinkTree';
+
+describe('ipV4Extended (integration)', () => {
+  const isCorrect = (value: string) => {
+    const chunks = value.split('.').map((v) => {
+      if (v[0] === '0') {
+        if (v[1] === 'x' || v[1] === 'X') return parseInt(v, 16);
+        return parseInt(v, 8);
+      }
+      return parseInt(v, 10);
+    });
+
+    // one invalid chunk
+    if (chunks.find((v) => Number.isNaN(v)) !== undefined) return false;
+
+    // maximal amount of 4 chunks
+    if (chunks.length > 4) return false;
+
+    // all chunks, except the last one are inferior or equal to 255
+    if (chunks.slice(0, -1).find((v) => v < 0 && v > 255) !== undefined) return false;
+
+    // last chunk must be below 256^(5 − number of chunks)
+    return chunks[chunks.length - 1] < 256 ** (5 - chunks.length);
+  };
+
+  const ipV4ExtendedBuilder = () => convertToNext(ipV4Extended());
+
+  it('should generate the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV4ExtendedBuilder);
+  });
+
+  it('should only generate correct values', () => {
+    assertGenerateProducesCorrectValues(ipV4ExtendedBuilder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during generate', () => {
+    assertGenerateProducesValuesFlaggedAsCanGenerate(ipV4ExtendedBuilder);
+  });
+
+  it('should shrink towards the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV4ExtendedBuilder);
+  });
+
+  it('should be able to shrink without any context', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(ipV4ExtendedBuilder);
+  });
+
+  it('should only shrink towards correct values', () => {
+    assertShrinkProducesCorrectValues(ipV4ExtendedBuilder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during shrink', () => {
+    assertShrinkProducesValuesFlaggedAsCanGenerate(ipV4ExtendedBuilder);
+  });
+
+  it.each`
+    source
+    ${'0.0.0.0'}
+    ${'255.255.255.255'}
+    ${'00.0.0.0'}
+    ${'0377.0.0.0'}
+    ${'0xff.0.0.0'}
+  `('should be able to generate $source with fc.ipV4Extended()', ({ source }) => {
+    // Arrange / Act
+    const arb = convertToNext(ipV4Extended());
+    const out = arb.canGenerate(source);
+
+    // Assert
+    expect(out).toBe(true);
+  });
+
+  it.each`
+    source
+    ${'256.0.0.0'}
+    ${'0378.0.0.0'}
+    ${'0400.0.0.0'}
+    ${'0x100.0.0.0'}
+    ${'000.0.0.0'}
+    ${'0x00.0.0.0'}
+    ${'-1.0.0.0'}
+  `('should not be able to generate $source with fc.ipV4Extended()', ({ source }) => {
+    // Arrange / Act
+    const arb = convertToNext(ipV4Extended());
+    const out = arb.canGenerate(source);
+
+    // Assert
+    expect(out).toBe(false);
+  });
+
+  it.each`
+    rawValue
+    ${'128.0.0.1'}
+    ${'010.0x8.4.04'}
+  `('should be able to shrink $rawValue', ({ rawValue }) => {
+    // Arrange
+    const arb = convertToNext(ipV4Extended());
+    const value = new NextValue(rawValue);
+
+    // Act
+    const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
+
+    // Assert
+    expect(renderedTree).toMatchSnapshot();
+  });
+});
diff --git a/test/unit/arbitrary/ipV6.spec.ts b/test/unit/arbitrary/ipV6.spec.ts
new file mode 100644
index 00000000..5515175f
--- /dev/null
+++ b/test/unit/arbitrary/ipV6.spec.ts
@@ -0,0 +1,175 @@
+import { ipV6 } from '../../../src/arbitrary/ipV6';
+
+import { convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { NextValue } from '../../../src/check/arbitrary/definition/NextValue';
+import {
+  assertGenerateProducesSameValueGivenSameSeed,
+  assertGenerateProducesCorrectValues,
+  assertGenerateProducesValuesFlaggedAsCanGenerate,
+  assertShrinkProducesSameValueWithoutInitialContext,
+  assertShrinkProducesCorrectValues,
+  assertShrinkProducesValuesFlaggedAsCanGenerate,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+import { buildNextShrinkTree, renderTree } from '../check/arbitrary/generic/ShrinkTree';
+
+describe('ipV6 (integration)', () => {
+  const isValidIpV4 = (value: string) => {
+    const chunks = value.split('.').map((v) => {
+      if (v[0] === '0') {
+        if (v[1] === 'x' || v[1] === 'X') return parseInt(v, 16);
+        return parseInt(v, 8);
+      }
+      return parseInt(v, 10);
+    });
+
+    // one invalid chunk
+    if (chunks.find((v) => Number.isNaN(v)) !== undefined) return false;
+
+    // maximal amount of 4 chunks
+    if (chunks.length > 4) return false;
+
+    // all chunks, except the last one are inferior or equal to 255
+    if (chunks.slice(0, -1).find((v) => v < 0 && v > 255) !== undefined) return false;
+
+    // last chunk must be below 256^(5 − number of chunks)
+    return chunks[chunks.length - 1] < 256 ** (5 - chunks.length);
+  };
+  const isCorrect = (value: string) => {
+    const firstElision = value.indexOf('::');
+    if (firstElision !== -1) {
+      // At most one '::'
+      if (value.substr(firstElision + 1).includes('::')) return false;
+    }
+    const chunks = value.split(':');
+    const last = chunks[chunks.length - 1];
+    // The ipv4 can only be composed of 4 decimal chunks separated by dots
+    // 1.1000 is not a valid IP v4 in the context of IP v6
+    const endByIpV4 = last.includes('.') && isValidIpV4(last);
+
+    const nonEmptyChunks = chunks.filter((c) => c !== '');
+    const hexaChunks = endByIpV4 ? nonEmptyChunks.slice(0, nonEmptyChunks.length - 1) : nonEmptyChunks;
+    if (!hexaChunks.every((s) => /^[0-9a-f]{1,4}$/.test(s))) return false;
+
+    const equivalentChunkLength = endByIpV4 ? hexaChunks.length + 2 : hexaChunks.length;
+    return firstElision !== -1 ? equivalentChunkLength < 8 : equivalentChunkLength === 8;
+  };
+
+  const ipV6Builder = () => convertToNext(ipV6());
+
+  it('should generate the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV6Builder);
+  });
+
+  it('should only generate correct values', () => {
+    assertGenerateProducesCorrectValues(ipV6Builder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during generate', () => {
+    assertGenerateProducesValuesFlaggedAsCanGenerate(ipV6Builder);
+  });
+
+  it('should shrink towards the same values given the same seed', () => {
+    assertGenerateProducesSameValueGivenSameSeed(ipV6Builder);
+  });
+
+  it('should be able to shrink without any context', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(ipV6Builder);
+  });
+
+  it('should only shrink towards correct values', () => {
+    assertShrinkProducesCorrectValues(ipV6Builder, isCorrect);
+  });
+
+  it('should recognize values that would have been generated using it during shrink', () => {
+    assertShrinkProducesValuesFlaggedAsCanGenerate(ipV6Builder);
+  });
+
+  it.each`
+    source
+    ${'::1'}
+    ${'0123:5678:9abc:ef01:2345:6789:128.0.0.1'}
+    ${'0123:5678:9abc:ef01:2345:6789:0000:ffff'}
+    ${'0:56:9abc:ef01:234:67:8.8.8.8'}
+    ${'0:56:9abc:ef01:234:67:0:f'}
+    ${'::5678:9abc:ef01:2345:6789:128.0.0.1'}
+    ${'::5678:9abc:ef01:2345:6789:0000:ffff'}
+    ${'::9abc:ef01:2345:6789:128.0.0.1'}
+    ${'::9abc:ef01:2345:6789:0000:ffff'}
+    ${'5678::9abc:ef01:2345:6789:128.0.0.1'}
+    ${'5678::9abc:ef01:2345:6789:0000:ffff'}
+    ${'::ef01:2345:6789:128.0.0.1'}
+    ${'::ef01:2345:6789:0000:ffff'}
+    ${'9abc::ef01:2345:6789:128.0.0.1'}
+    ${'9abc::ef01:2345:6789:0000:ffff'}
+    ${'5678:9abc::ef01:2345:6789:128.0.0.1'}
+    ${'5678:9abc::ef01:2345:6789:0000:ffff'}
+    ${'::2345:6789:128.0.0.1'}
+    ${'::2345:6789:0000:ffff'}
+    ${'ef01::2345:6789:128.0.0.1'}
+    ${'ef01::2345:6789:0000:ffff'}
+    ${'9abc:ef01::2345:6789:128.0.0.1'}
+    ${'9abc:ef01::2345:6789:0000:ffff'}
+    ${'5678:9abc:ef01::2345:6789:128.0.0.1'}
+    ${'5678:9abc:ef01::2345:6789:0000:ffff'}
+    ${'::6789:128.0.0.1'}
+    ${'::6789:0000:ffff'}
+    ${'2345::6789:128.0.0.1'}
+    ${'2345::6789:0000:ffff'}
+    ${'ef01:2345::6789:128.0.0.1'}
+    ${'ef01:2345::6789:0000:ffff'}
+    ${'9abc:ef01:2345::6789:128.0.0.1'}
+    ${'9abc:ef01:2345::6789:0000:ffff'}
+    ${'5678:9abc:ef01:2345::6789:128.0.0.1'}
+    ${'5678:9abc:ef01:2345::6789:0000:ffff'}
+    ${'::128.0.0.1'}
+    ${'::0000:ffff'}
+    ${'2345::128.0.0.1'}
+    ${'2345::0000:ffff'}
+    ${'ef01:2345::128.0.0.1'}
+    ${'ef01:2345::0000:ffff'}
+    ${'9abc:ef01:2345::128.0.0.1'}
+    ${'9abc:ef01:2345::0000:ffff'}
+    ${'5678:9abc:ef01:2345::128.0.0.1'}
+    ${'5678:9abc:ef01:2345::0000:ffff'}
+    ${'0123:5678:9abc:ef01:2345::128.0.0.1'}
+    ${'0123:5678:9abc:ef01:2345::0000:ffff'}
+    ${'::0123'}
+    ${'6789::0123'}
+    ${'2345:6789::0123'}
+    ${'ef01:2345:6789::0123'}
+    ${'9abc:ef01:2345:6789::0123'}
+    ${'5678:9abc:ef01:2345:6789::0123'}
+    ${'0123:5678:9abc:ef01:2345:6789::0123'}
+    ${'::'}
+    ${'0123::'}
+    ${'6789:0123::'}
+    ${'2345:6789:0123::'}
+    ${'ef01:2345:6789:0123::'}
+    ${'9abc:ef01:2345:6789:0123::'}
+    ${'5678:9abc:ef01:2345:6789:0123::'}
+    ${'0123:5678:9abc:ef01:2345:6789:0123::'}
+  `('should be able to generate $source with fc.ipV6()', ({ source }) => {
+    // Arrange / Act
+    const arb = convertToNext(ipV6());
+    const out = arb.canGenerate(source);
+
+    // Assert
+    expect(out).toBe(true);
+  });
+
+  it.each`
+    rawValue
+    ${'::1'}
+    ${'0123:5678:9abc:ef01:2345:6789:128.0.0.1'}
+  `('should be able to shrink $rawValue', ({ rawValue }) => {
+    // Arrange
+    const arb = convertToNext(ipV6());
+    const value = new NextValue(rawValue);
+
+    // Act
+    const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
+
+    // Assert
+    expect(renderedTree).toMatchSnapshot();
+  });
+});