Repository: dubzzz/fast-check
Pull request #2035 (2021-06-24 22:29:51)

✨ Add built-in unmapper support onto `domain`
<!-- 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
- [ ] ⚡️ Improve performance
- [ ] _Other(s):_ ...
<!-- Don't forget to add the gitmoji icon in the name of the PR -->
<!-- See: https://gitmoji.dev/                                  -->

<!-- Fixing bugs, adding features... 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/2035/builds/143424) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 3e87b04ed8a91f14a284bb90f379f49c71567e26:

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

codecov[bot]: # [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2035?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) Report
> Merging [#2035](https://codecov.io/gh/dubzzz/fast-check/pull/2035?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (3e87b04) into [main](https://codecov.io/gh/dubzzz/fast-check/commit/47dbc614ab4c8dc9fd09758853c4f4aceb4af800?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (47dbc61) will **decrease** coverage by `0.03%`.
> The diff coverage is `88.88%`.

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/2035/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/2035?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    #2035      +/-   ##
==========================================
- Coverage   96.58%   96.54%   -0.04%     
==========================================
  Files         176      176              
  Lines        4332     4346      +14     
  Branches      806      810       +4     
==========================================
+ Hits         4184     4196      +12     
- Misses        144      146       +2     
  Partials        4        4              
```

| Flag | Coverage Δ | |
|---|---|---|
| unit-tests | `96.54% <88.88%> (-0.04%)` | :arrow_down: |
| unit-tests-12.x-Linux | `?` | |
| unit-tests-14.x-Linux | `96.54% <88.88%> (-0.04%)` | :arrow_down: |
| unit-tests-16.x-Linux | `?` | |

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/2035?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) | Coverage Δ | |
|---|---|---|
| [src/arbitrary/domain.ts](https://codecov.io/gh/dubzzz/fast-check/pull/2035/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9kb21haW4udHM=) | `93.54% <88.88%> (-6.46%)` | :arrow_down: |
| [src/check/arbitrary/SparseArrayArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/2035/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9TcGFyc2VBcnJheUFyYml0cmFyeS50cw==) | | |
| [src/arbitrary/sparseArray.ts](https://codecov.io/gh/dubzzz/fast-check/pull/2035/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9zcGFyc2VBcnJheS50cw==) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/2035?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/2035?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Last update [47dbc61...3e87b04](https://codecov.io/gh/dubzzz/fast-check/pull/2035?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/domain.ts b/src/arbitrary/domain.ts
index 0311a18c..4e8bf603 100644
--- a/src/arbitrary/domain.ts
+++ b/src/arbitrary/domain.ts
@@ -8,6 +8,22 @@ import { stringOf } from './stringOf';
 import { tuple } from './tuple';
 import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
 import { filterInvalidSubdomainLabel } from './_internals/helpers/InvalidSubdomainLabelFiIter';
+import { convertFromNext, convertToNext } from '../check/arbitrary/definition/Converters';
+
+/** @internal */
+function toSubdomainLabelMapper([f, d]: [string, [string, string] | null]): string {
+  return d === null ? f : `${f}${d[0]}${d[1]}`;
+}
+/** @internal */
+function toSubdomainLabelUnmapper(value: unknown): [string, [string, string] | null] {
+  if (typeof value !== 'string' || value.length === 0) {
+    throw new Error('Unsupported');
+  }
+  if (value.length === 1) {
+    return [value[0], null];
+  }
+  return [value[0], [value.substring(1, value.length - 1), value[value.length - 1]]];
+}
 
 /** @internal */
 function subdomainLabel() {
@@ -15,9 +31,26 @@ function subdomainLabel() {
   const alphaNumericHyphenArb = buildLowerAlphaNumericArbitrary(['-']);
   // Rq: maxLength = 61 because max length of a label is 63 according to RFC 1034
   //     and we add 2 characters to this generated value
-  return tuple(alphaNumericArb, option(tuple(stringOf(alphaNumericHyphenArb, { maxLength: 61 }), alphaNumericArb)))
-    .map(([f, d]) => (d === null ? f : `${f}${d[0]}${d[1]}`))
-    .filter(filterInvalidSubdomainLabel);
+  return convertFromNext(
+    convertToNext(
+      tuple(alphaNumericArb, option(tuple(stringOf(alphaNumericHyphenArb, { maxLength: 61 }), alphaNumericArb)))
+    )
+      .map(toSubdomainLabelMapper, toSubdomainLabelUnmapper)
+      .filter(filterInvalidSubdomainLabel)
+  );
+}
+
+/** @internal */
+function labelsMapper(elements: [string[], string]): string {
+  return `${elements[0].join('.')}.${elements[1]}`;
+}
+/** @internal */
+function labelsUnmapper(value: unknown): [string[], string] {
+  if (typeof value !== 'string') {
+    throw new Error('Unsupported type');
+  }
+  const lastDotIndex = value.lastIndexOf('.');
+  return [value.substring(0, lastDotIndex).split('.'), value.substring(lastDotIndex + 1)];
 }
 
 /**
@@ -35,12 +68,12 @@ function subdomainLabel() {
 export function domain(): Arbitrary<string> {
   const alphaNumericArb = buildLowerAlphaArbitrary([]);
   // A list of public suffixes can be found here: https://publicsuffix.org/list/public_suffix_list.dat
-  // our current implementation does not follwo this list and generate a fully randomized suffix
+  // our current implementation does not follow this list and generate a fully randomized suffix
   // which is probably not in this list (probability would be low)
   const publicSuffixArb = stringOf(alphaNumericArb, { minLength: 2, maxLength: 10 });
-  return (
-    tuple(array(subdomainLabel(), { minLength: 1, maxLength: 5 }), publicSuffixArb)
-      .map(([mid, ext]) => `${mid.join('.')}.${ext}`)
+  return convertFromNext(
+    convertToNext(tuple(array(subdomainLabel(), { minLength: 1, maxLength: 5 }), publicSuffixArb))
+      .map(labelsMapper, labelsUnmapper)
       // Required by RFC 1034:
       //    To simplify implementations, the total number of octets that represent
       //    a domain name (i.e., the sum of all label octets and label lengths) is limited to 255.


Test patch:
diff --git a/test/unit/arbitrary/HostArbitrary.spec.ts b/test/unit/arbitrary/HostArbitrary.spec.ts
deleted file mode 100644
index e63ab330..00000000
--- a/test/unit/arbitrary/HostArbitrary.spec.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { domain } from '../../../src/arbitrary/domain';
-import { filterInvalidSubdomainLabel } from '../../../src/arbitrary/_internals/helpers/InvalidSubdomainLabelFiIter';
-
-import * as genericHelper from '../check/arbitrary/generic/GenericArbitraryHelper';
-import fc from '../../../lib/fast-check';
-
-const isValidDomain = (t: string) => {
-  // According to https://www.ietf.org/rfc/rfc1034.txt
-  // <domain> ::= <subdomain> | " "
-  // <subdomain> ::= <label> | <subdomain> "." <label>
-  // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
-  // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
-  // <let-dig-hyp> ::= <let-dig> | "-"
-  // <let-dig> ::= <letter> | <digit>
-  // Relaxed by https://www.ietf.org/rfc/rfc1123.txt
-  // allowing first character of subdomain to be a digit
-  const rfc1123SubDomain = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
-  return t.split('.').every((sd) => rfc1123SubDomain.test(sd) && sd.length <= 63) && t.length <= 255;
-};
-
-const isValidDomainWithExtension = (t: string) => {
-  const subdomains = t.split('.');
-  return isValidDomain(t) && subdomains.length >= 2 && /^[a-z]{2,}$/.test(subdomains[subdomains.length - 1]);
-};
-
-describe('DomainArbitrary', () => {
-  describe('domain', () => {
-    genericHelper.isValidArbitrary(() => domain(), {
-      isValidValue: (g: string) => isValidDomainWithExtension(g),
-    });
-  });
-
-  describe('filterInvalidSubdomainLabel', () => {
-    // Internal function:
-    // We are not checking all the requirements of subdomains but just the ones we need to ensure
-    // post construction as they cannot be easily enforced except by a filtering logic
-    const alphaChar = () => fc.mapToConstant({ num: 26, build: (v) => String.fromCharCode(v + 0x61) });
-    it('Should accept any subdomain composed of only alphabet characters and with at most 63 characters', () =>
-      fc.assert(
-        fc.property(fc.stringOf(alphaChar(), { minLength: 1, maxLength: 63 }), (subdomainLabel) => {
-          expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(true);
-        })
-      ));
-    it('Should reject any subdomain with strictly more than 63 characters', () =>
-      fc.assert(
-        fc.property(fc.stringOf(alphaChar(), { minLength: 64 }), (subdomainLabel) => {
-          expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(false);
-        })
-      ));
-    it('Should reject any subdomain starting by "xn--"', () =>
-      fc.assert(
-        fc.property(fc.stringOf(alphaChar(), { maxLength: 63 - 'xn--'.length }), (subdomainLabelEnd) => {
-          const subdomainLabel = `xn--${subdomainLabelEnd}`;
-          expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(false);
-        })
-      ));
-    it('Should not reject subdomains if they start by a substring of "xn--"', () =>
-      fc.assert(
-        fc.property(
-          fc.stringOf(alphaChar(), { maxLength: 63 - 'xn--'.length }),
-          fc.nat('xn--'.length - 1),
-          (subdomainLabelEnd, keep) => {
-            const subdomainLabel = `${'xn--'.substring(0, keep)}${subdomainLabelEnd}`;
-            expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(true);
-          }
-        )
-      ));
-  });
-});
diff --git a/test/unit/arbitrary/__snapshots__/domain.spec.ts.snap b/test/unit/arbitrary/__snapshots__/domain.spec.ts.snap
new file mode 100644
index 00000000..5bdc1243
--- /dev/null
+++ b/test/unit/arbitrary/__snapshots__/domain.spec.ts.snap
@@ -0,0 +1,109 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`domain (integration) should be able to shrink domain.com 1`] = `
+"\\"domain.com\\"
+├> \\"aomain.com\\"
+|  ├> \\"a.com\\"
+|  |  ├> \\"a.om\\"
+|  |  |  ├> \\"a.am\\"
+|  |  |  ├> \\"a.hm\\"
+|  |  |  |  ├> \\"a.em\\"
+|  |  |  |  |  ├> \\"a.cm\\"
+|  |  |  |  |  |  └> \\"a.bm\\"
+|  |  |  |  |  |     └> \\"a.am\\"
+|  |  |  |  |  └> \\"a.dm\\"
+|  |  |  |  |     └> \\"a.cm\\"
+|  |  |  |  |        ├> \\"a.am\\"
+|  |  |  |  |        └> \\"a.bm\\"
+|  |  |  |  |           └> \\"a.am\\"
+|  |  |  |  └> \\"a.gm\\"
+|  |  |  |     └> \\"a.fm\\"
+|  |  |  |        └> \\"a.em\\"
+|  |  |  |           ├> \\"a.am\\"
+|  |  |  |           ├> \\"a.cm\\"
+|  |  |  |           |  └> \\"a.bm\\"
+|  |  |  |           |     └> \\"a.am\\"
+|  |  |  |           └> \\"a.dm\\"
+|  |  |  |              └> \\"a.cm\\"
+|  |  |  |                 ├> \\"a.am\\"
+|  |  |  |                 └> \\"a.bm\\"
+|  |  |  |                    └> \\"a.am\\"
+|  |  |  ├> \\"a.lm\\"
+|  |  |  |  ├> \\"a.jm\\"
+|  |  |  |  |  └> \\"a.im\\"
+|  |  |  |  |     └> \\"a.hm\\"
+|  |  |  |  |        ├> \\"a.am\\"
+|  |  |  |  |        ├> \\"a.em\\"
+|  |  |  |  |        |  ├> \\"a.cm\\"
+|  |  |  |  |        |  |  └> \\"a.bm\\"
+|  |  |  |  |        |  |     └> \\"a.am\\"
+|  |  |  |  |        |  └> \\"a.dm\\"
+|  |  |  |  |        |     └> \\"a.cm\\"
+|  |  |  |  |        |        ├> \\"a.am\\"
+|  |  |  |  |        |        └> \\"a.bm\\"
+|  |  |  |  |        |           └> \\"a.am\\"
+|  |  |  |  |        └> \\"a.gm\\"
+|  |  |  |  |           └> \\"a.fm\\"
+|  |  |  |  |              └> \\"a.em\\"
+|  |  |  |  |                 ├> \\"a.am\\"
+|  |  |  |  |                 ├> \\"a.cm\\"
+|  |  |  |  |                 |  └> \\"a.bm\\"
+|  |  |  |  |                 |     └> \\"a.am\\"
+|  |  |  |  |                 └> \\"a.dm\\"
+|  |  |  |  |                    └> \\"a.cm\\"
+|  |  |  |  |                       ├> \\"a.am\\"
+|  |  |  |  |                       └> \\"a.bm\\"
+|  |  |  |  |                          └> \\"a.am\\"
+|  |  |  |  └> \\"a.km\\"
+|  |  |  |     └> \\"a.jm\\"
+|  |  |  |        ├> \\"a.am\\"
+|  |  |  |        ├> \\"a.fm\\"
+|  |  |  |        |  ├> \\"a.dm\\"
+|  |  |  |        |  |  └> \\"a.cm\\"
+|  |  |  |        |  |     └> \\"a.bm\\"
+|  |  |  |        |  |        └> \\"a.am\\"
+|  |  |  |        |  └> \\"a.em\\"
+|  |  |  |        |     └> \\"a.dm\\"
+|  |  |  |        |        ├> \\"a.am\\"
+|  |  |  |        |        └> \\"a.cm\\"
+|  |  |  |        |           └> \\"a.bm\\"
+|  |  |  |        |              └> \\"a.am\\"
+|  |  |  |        ├> \\"a.hm\\"
+|  |  |  |        |  └> \\"a.gm\\"
+|  |  |  |        |     └> \\"a.fm\\"
+|  |  |  |        |        ├> \\"a.am\\"
+|  |  |  |        |        ├> \\"a.dm\\"
+|  |  |  |        |        |  └> \\"a.cm\\"
+|  |  |  |        |        |     └> \\"a.bm\\"
+|  |  |  |        |        |        └> \\"a.am\\"
+|  |  |  |        |        └> \\"a.em\\"
+|  |  |  |        |           └> \\"a.dm\\"
+|  |  |  |        |              ├> \\"a.am\\"
+|  |  |  |        |              └> \\"a.cm\\"
+|  |  |  |        |                 └> \\"a.bm\\"
+|  |  |  |        |                    └> \\"a.am\\"
+|  |  |  |        └> \\"a.im\\"
+|  |  |  |           └> \\"a.hm\\"
+|  |  |  |              ├> \\"a.am\\"
+|  |  |  |              ├> \\"a.em\\"
+|  |  |  |              |  ├> \\"a.cm\\"
+|  |  |  |              |  |  └> \\"a.bm\\"
+|  |  |  |              |  |     └> \\"a.am\\"
+|  |  |  |              |  └> \\"a.dm\\"
+|  |  |  |              |     └> \\"a.cm\\"
+|  |  |  |              |        ├> \\"a.am\\"
+|  |  |  |              |        └> \\"a.bm\\"
+|  |  |  |              |           └> \\"a.am\\"
+|  |  |  |              └> \\"a.gm\\"
+|  |  |  |                 └> \\"a.fm\\"
+|  |  |  |                    └> \\"a.em\\"
+|  |  |  |                       ├> \\"a.am\\"
+|  |  |  |                       ├> \\"a.cm\\"
+|  |  |  |                       |  └> \\"a.bm\\"
+|  |  |  |                       |     └> \\"a.am\\"
+|  |  |  |                       └> …
+|  |  |  └> …
+|  |  └> …
+|  └> …
+└> …"
+`;
diff --git a/test/unit/arbitrary/_internals/helpers/InvalidSubdomainLabelFiIter.spec.ts b/test/unit/arbitrary/_internals/helpers/InvalidSubdomainLabelFiIter.spec.ts
new file mode 100644
index 00000000..e4f5f61f
--- /dev/null
+++ b/test/unit/arbitrary/_internals/helpers/InvalidSubdomainLabelFiIter.spec.ts
@@ -0,0 +1,43 @@
+import fc from '../../../../../lib/fast-check';
+import { filterInvalidSubdomainLabel } from '../../../../../src/arbitrary/_internals/helpers/InvalidSubdomainLabelFiIter';
+
+describe('filterInvalidSubdomainLabel', () => {
+  // Internal function:
+  // We are not checking all the requirements of subdomains but just the ones we need to ensure
+  // post construction as they cannot be easily enforced except by a filtering logic
+  const alphaChar = () => fc.mapToConstant({ num: 26, build: (v) => String.fromCharCode(v + 0x61) });
+
+  it('should accept any subdomain composed of only alphabet characters and with at most 63 characters', () =>
+    fc.assert(
+      fc.property(fc.stringOf(alphaChar(), { minLength: 1, maxLength: 63 }), (subdomainLabel) => {
+        expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(true);
+      })
+    ));
+
+  it('should reject any subdomain with strictly more than 63 characters', () =>
+    fc.assert(
+      fc.property(fc.stringOf(alphaChar(), { minLength: 64 }), (subdomainLabel) => {
+        expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(false);
+      })
+    ));
+
+  it('should reject any subdomain starting by "xn--"', () =>
+    fc.assert(
+      fc.property(fc.stringOf(alphaChar(), { maxLength: 63 - 'xn--'.length }), (subdomainLabelEnd) => {
+        const subdomainLabel = `xn--${subdomainLabelEnd}`;
+        expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(false);
+      })
+    ));
+
+  it('should not reject subdomains if they start by a substring of "xn--"', () =>
+    fc.assert(
+      fc.property(
+        fc.stringOf(alphaChar(), { maxLength: 63 - 'xn--'.length }),
+        fc.nat('xn--'.length - 1),
+        (subdomainLabelEnd, keep) => {
+          const subdomainLabel = `${'xn--'.substring(0, keep)}${subdomainLabelEnd}`;
+          expect(filterInvalidSubdomainLabel(subdomainLabel)).toBe(true);
+        }
+      )
+    ));
+});
diff --git a/test/unit/arbitrary/domain.spec.ts b/test/unit/arbitrary/domain.spec.ts
new file mode 100644
index 00000000..405e42c7
--- /dev/null
+++ b/test/unit/arbitrary/domain.spec.ts
@@ -0,0 +1,76 @@
+import fc from '../../../lib/fast-check';
+import { domain } from '../../../src/arbitrary/domain';
+import { convertToNext } from '../../../src/check/arbitrary/definition/Converters';
+import { NextValue } from '../../../src/check/arbitrary/definition/NextValue';
+
+import {
+  assertProduceSameValueGivenSameSeed,
+  assertProduceCorrectValues,
+  assertProduceValuesShrinkableWithoutContext,
+  assertShrinkProducesSameValueWithoutInitialContext,
+} from '../check/arbitrary/generic/NextArbitraryAssertions';
+import { buildNextShrinkTree, renderTree } from '../check/arbitrary/generic/ShrinkTree';
+
+function beforeEachHook() {
+  jest.resetModules();
+  jest.restoreAllMocks();
+  fc.configureGlobal({ beforeEach: beforeEachHook });
+}
+beforeEach(beforeEachHook);
+
+describe('domain (integration)', () => {
+  const isValidDomain = (t: string) => {
+    // According to https://www.ietf.org/rfc/rfc1034.txt
+    // <domain> ::= <subdomain> | " "
+    // <subdomain> ::= <label> | <subdomain> "." <label>
+    // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+    // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+    // <let-dig-hyp> ::= <let-dig> | "-"
+    // <let-dig> ::= <letter> | <digit>
+    // Relaxed by https://www.ietf.org/rfc/rfc1123.txt
+    // allowing first character of subdomain to be a digit
+    const rfc1123SubDomain = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
+    return t.split('.').every((sd) => rfc1123SubDomain.test(sd) && sd.length <= 63) && t.length <= 255;
+  };
+
+  const isValidDomainWithExtension = (t: string) => {
+    const subdomains = t.split('.');
+    return isValidDomain(t) && subdomains.length >= 2 && /^[a-z]{2,}$/.test(subdomains[subdomains.length - 1]);
+  };
+
+  const isCorrect = isValidDomainWithExtension;
+
+  const domainBuilder = () => convertToNext(domain());
+
+  it('should produce the same values given the same seed', () => {
+    assertProduceSameValueGivenSameSeed(domainBuilder);
+  });
+
+  it('should only produce correct values', () => {
+    assertProduceCorrectValues(domainBuilder, isCorrect);
+  });
+
+  it('should produce values seen as shrinkable without any context', () => {
+    assertProduceValuesShrinkableWithoutContext(domainBuilder);
+  });
+
+  it('should be able to shrink to the same values without initial context', () => {
+    assertShrinkProducesSameValueWithoutInitialContext(domainBuilder);
+  });
+
+  it.each`
+    rawValue
+    ${'domain.com'}
+  `('should be able to shrink $rawValue', ({ rawValue }) => {
+    // Arrange
+    const arb = convertToNext(domain());
+    const value = new NextValue(rawValue, undefined);
+
+    // Act
+    const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
+
+    // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
+    expect(renderedTree).toMatchSnapshot();
+  });
+});