UnitTest.test('KAssert.eqOption: failure', () => {
  fc.assert(fc.property(twoDifferentNumbers, ([ a, b ]) => {
    Assert.throws('some(i) != some(!i) #1', () => {
      KAssert.eqOption('eq', Option.some(a), Option.some(b));
    });
  }));
  fc.assert(fc.property(twoDifferentNumbers, ([ a, b ]) => {
    Assert.throws('some(i) != some(!i) #2', () => {
      KAssert.eqOption('eq', Option.some(a), Option.some(b), tNumber);
    });
  }));
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.throws('none != some(i) #1', () => {
      KAssert.eqOption('eq', Option.none(), Option.some(i));
    });
    Assert.throws('none != some(i) #2', () => {
      KAssert.eqOption('eq', Option.none(), Option.some(i), tBoom());
    });
  }));
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.throws('some(i) != none #1', () => {
      KAssert.eqOption('eq', Option.some(i), Option.none());
    });
    Assert.throws('some(i) != none #2', () => {
      KAssert.eqOption('eq', Option.some(i), Option.none(), tBoom());
    });
  }));
})


unknown

UnitTest.test('leftTrim(whitespace + s) === leftTrim(s)', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('leftTrim', Strings.lTrim(' ' + s), Strings.lTrim(s), tString);
    }
  ));
})


UnitTest.test('rightTrim(s + whitespace) === rightTrim(s)', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('rightTrim', Strings.rTrim(s + ' '), Strings.rTrim(s), tString);
    }
  ));
})


UnitTest.test('trim(whitespace + s) === trim(s)', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('trim', Strings.trim(' ' + s), Strings.trim(s), tString);
    }
  ));
})


UnitTest.test('trim(s + whitespace) === trim(s)', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('trim', Strings.trim(s + ' '), Strings.trim(s), tString);
    }
  ));
})


UnitTest.test('trim(whitespace + s + whitespace) === trim(s)', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('trim', Strings.trim(' ' + s + ' '), Strings.trim(s), tString);
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.starts(s1), s1 + s) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, s1) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.starts(s1, StringMatch.caseInsensitive),
        s1 + s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s1 + s) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, s1) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s1 + s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s + s1) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, s1) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s + s1
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s) === s.indexOf(s1)', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(1, 40),
    (s, s1) => {
      Assert.eq('eq', s.toLowerCase().indexOf(s1.toLowerCase()) > -1, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.contains(s), s) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s, StringMatch.caseInsensitive),
        s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.exact(s), s + s1) === false', () => {
  fc.assert(fc.property(
    fc.string(1, 40),
    fc.string(1, 40),
    (s, s1) => {
      Assert.eq('eq', false, StringMatch.matches(
        StringMatch.exact(s, StringMatch.caseInsensitive),
        s + s1
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.exact(s), s1 + s) === false', () => {
  fc.assert(fc.property(
    fc.string(1, 40),
    fc.string(1, 40),
    (s, s1) => {
      Assert.eq('eq', false, StringMatch.matches(
        StringMatch.exact(s, StringMatch.caseInsensitive),
        s1 + s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.exact(s), s) === true', () => {
  fc.assert(fc.property(
    fc.string(1, 40),
    (s) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.exact(s, StringMatch.caseInsensitive),
        s
      ));
    }
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.exact(s), s) === false when different case and case-insensitive', () => {
  fc.assert(fc.property(
    fc.asciiString(1, 40),
    (s) => s.toUpperCase() === s.toLowerCase() || StringMatch.matches(
      StringMatch.exact(s.toLowerCase(), StringMatch.caseInsensitive),
      s.toUpperCase()
    )
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.exact(s), s) === false when different case and case-sensitive', () => {
  fc.assert(fc.property(
    fc.asciiString(1, 40),
    (s) => s.toUpperCase() === s.toLowerCase() || !StringMatch.matches(
      StringMatch.exact(s.toLowerCase(), StringMatch.caseSensitive),
      s.toUpperCase()
    )
  ));
})


UnitTest.test('StringMatch.matches(StringMatch.all(s1), *) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.all(),
        s
      ));
    }
  ));
})


UnitTest.test('Strings.repeat: positive range', () => {
  fc.assert(fc.property(fc.char(), fc.integer(1, 100), (c, count) => {
    const actual = Strings.repeat(c, count);
    Assert.eq('length should be the same as count', count, actual.length);
    Assert.eq('first char should be the same', c, actual.charAt(0));
    Assert.eq('last char should be the same', c, actual.charAt(actual.length - 1));
  }));

  fc.assert(fc.property(fc.asciiString(5), fc.integer(1, 100), (s, count) => {
    const actual = Strings.repeat(s, count);
    Assert.eq('length should be count * original length', count * s.length, actual.length);
    Assert.eq('should start with string', 0, actual.indexOf(s));
    Assert.eq('should end with string', actual.length - s.length, actual.lastIndexOf(s));
  }));
})


UnitTest.test('Strings.repeat: negative range', () => {
  fc.assert(fc.property(fc.char(), fc.integer(-100, 0), (c, count) => {
    const actual = Strings.repeat(c, count);
    Assert.eq('length should be 0', 0, actual.length);
    Assert.eq('should be an empty string', '', actual);
  }));
})


UnitTest.test('removeTrailing property', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(),
    (prefix, suffix) => {
      Assert.eq('removeTrailing', prefix, Strings.removeTrailing(prefix + suffix, suffix));
    }
  ));
})


UnitTest.test('removeLeading removes prefix', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(),
    (prefix, suffix) => {
      Assert.eq('removeLeading', suffix, Strings.removeLeading(prefix + suffix, prefix));
    }
  ));
})


UnitTest.test('ensureTrailing is identity if string already ends with suffix', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    function (prefix, suffix) {
      const s = prefix + suffix;
      Assert.eq('id', s, Strings.ensureTrailing(s, suffix));
    }));
})


UnitTest.test('ensureTrailing endsWith', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, suffix) => Strings.endsWith(Strings.ensureTrailing(s, suffix), suffix)
  ));
})


UnitTest.test('startsWith a prefix', () => {
  fc.assert(fc.property(fc.string(), fc.string(),
    (prefix, suffix) => Strings.startsWith(prefix + suffix, prefix)
  ));
})


UnitTest.test('endsWith: A string added to a string (at the end) must have endsWith as true', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(),
    (str, contents) => Strings.endsWith(str + contents, contents)
  ));
})


UnitTest.test('A string with length 1 or larger should never be empty', () => {
  fc.assert(fc.property(fc.string(1, 40), (str) => {
    Assert.eq('isEmpty', false, Strings.isEmpty(str));
    Assert.eq('isNotEmpty', true, Strings.isNotEmpty(str));
  }));
})


UnitTest.test('A string added to a string (at the end) must be contained within it', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(1, 40),
    (str, contents) => {
      const r = str + contents;
      Assert.eq('eq', true, Strings.contains(r, contents));
    }
  ));
})


UnitTest.test('A string added to a string (at the start) must be contained within it', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(1, 40),
    (str, contents) => {
      const r = contents = str;
      Assert.eq('eq', true, Strings.contains(r, contents));
    }
  ));
})


UnitTest.test('An empty string should contain nothing', () => {
  fc.assert(fc.property(
    fc.string(1, 40),
    (value) => !Strings.contains('', value)
  ));
})


UnitTest.test('capitalize: tail of the string is unchanged', () => {
  fc.assert(fc.property(fc.ascii(), fc.asciiString(30), (h, t) => {
    Assert.eq('tail', t, Strings.capitalize(h + t).substring(1), tString);
  }));
})


UnitTest.test('ResultInstances.pprint', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('pprint value',
      `Result.value(
  ${i}
)`,
      Pprint.render(Result.value(i), tResult(tNumber, tString))
    );

    Assert.eq('pprint error',
      `Result.error(
  ${i}
)`,
      Pprint.render(Result.error(i), tResult(tString, tNumber))
    );
  }));
})

Summary: Verifies that `Pprint.render` correctly formats both `Result.value` and `Result.error` instances with integers, producing an expected multi-line string with the integer inserted.


UnitTest.test('Options.cat of only somes should have the same length', () => {
  fc.assert(fc.property(
    fc.array(ArbDataTypes.arbOptionSome(fc.integer())),
    (options) => {
      const output = Options.cat(options);
      Assert.eq('eq', options.length, output.length);
    }
  ));
})

Summary: `Options.cat` retains the same length as the input array of `Some` options.


UnitTest.test('Options.sequence: Some then none', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (n) => {
    const someNumbers = Arr.map(n, (x) => Option.some(x));
    Assert.eq('eq', Option.none(), Options.sequence<number>([ ...someNumbers, Option.none<number>() ]), tOption());
  }));
})

Summary: The test ensures that when an array of `Option.some` values is combined with an `Option.none`, the result of `Options.sequence` is `Option.none`.


UnitTest.test('Options.flatten: some(some(x))', () => {
  fc.assert(fc.property(fc.integer(), function (n) {
    Assert.eq('eq', Option.some(n), Options.flatten(Option.some(Option.some<number>(n))), tOption());
  }));
})

Summary: `Options.flatten` should return a single `Option.some` with the value `n` when applied to `Option.some(Option.some(n))`.


UnitTest.test('Option.none() !== Option.some(x)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', false, Option.none().equals(Option.some(i)));
  }));
})

Summary: `Option.none()` should not be equal to `Option.some(x)` for any integer `x`.


UnitTest.test('Option.some(x) !== Option.none()', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', false, (Option.some(i).equals(Option.none())));
  }));
})

Summary: `Option.some(x)` should not be equal to `Option.none()`.


UnitTest.test('Option.some(x) === Option.some(x)', () => {
  fc.assert(fc.property(fc.integer(), (i) => Assert.eq('eq', true, Option.some(i).equals(Option.some(i)))));
})

Summary: `Option.some(x)` should be equal to `Option.some(x)` for any integer `x`.


UnitTest.test('Option.some(x) === Option.some(x) (same ref)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const ob = Option.some(i);
    Assert.eq('eq', true, ob.equals(ob));
  }));
})

Summary: `Option.some(x)` should be equal to itself when using the `equals` method.


UnitTest.test('Option.some(x) !== Option.some(x + y) where y is not identity', () => {
  fc.assert(fc.property(fc.string(), fc.string(1, 40), (a, b) => {
    Assert.eq('eq', false, Option.some(a).equals(Option.some(a + b)));
  }));
})

Summary: `Option.some(x)` should not be equal to `Option.some(x + y)` when `y` is not an identity value.


UnitTest.test('Checking some(x).getOr(v) === x', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), arbOptionSome(fc.integer()), (a, b) => {
    Assert.eq('eq', a, Option.some(a).getOr(b));
  }));
})

Summary: `Option.some(x).getOr(v)` should always return `x`.


UnitTest.test('Checking some(x).getOrThunk(_ -> v) === x', () => {
  fc.assert(fc.property(fc.integer(), fc.func(fc.integer()), (a, thunk) => {
    Assert.eq('eq', a, Option.some(a).getOrThunk(thunk));
  }));
})

Summary: `Option.some(x).getOrThunk(thunk)` should return `x`, regardless of the thunk provided.


UnitTest.test('Checking some.getOrDie() never throws', () => {
  fc.assert(fc.property(fc.integer(), fc.string(1, 40), (i, s) => {
    const opt = Option.some(i);
    opt.getOrDie(s);
  }));
})

Summary: `Option.some(i).getOrDie(s)` should never throw an error for any integer `i` and string `s` within the specified length.


UnitTest.test('Checking some(x).or(oSomeValue) === some(x)', () => {
  fc.assert(fc.property(fc.integer(), arbOptionSome(fc.integer()), (json, other) => {
    const output = Option.some(json).or(other);
    Assert.eq('eq', true, output.is(json));
  }));
})

Summary: `Option.some(x).or(oSomeValue)` should produce `some(x)`, verifying that `or` returns the original `some(x)` instead of an alternative value.


UnitTest.test('Checking some(x).orThunk(_ -> oSomeValue) === some(x)', () => {
  fc.assert(fc.property(fc.integer(), arbOptionSome(fc.integer()), (i, other) => {
    const output = Option.some(i).orThunk(() => other);
    Assert.eq('eq', true, output.is(i));
  }));
})

Summary: `Option.some(x).orThunk` called with any integer and another optional integer value should return `Option.some(x)`.


UnitTest.test('Checking some(x).forall(_ -> true) === true', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => opt.forall(Fun.constant(true))));
})

Summary: The result of `forall` on `some(x)` with a function always returning `true` should be `true`.


UnitTest.test('Checking some(x).forall(f) iff. f(x)', () => {
  fc.assert(fc.property(fc.integer(), fc.func(fc.boolean()), (i, f) => {
    const opt = Option.some(i);
    if (f(i)) {
      Assert.eq('eq', true, opt.forall(f));
    } else {
      Assert.eq('eq', false, opt.forall(f));
    }
  }));
})

Summary: `Option.some(x).forall(f)` should return true if and only if the function `f` applied to `x` returns true.


UnitTest.test('Checking some(x).toArray equals [ x ]', () => {
  fc.assert(fc.property(fc.integer(), (json) => {
    Assert.eq('eq', [ json ], Option.some(json).toArray());
  }));
})

Summary: `Option.some(x).toArray()` results in an array containing just the element `x`.


UnitTest.test('Checking some(x).toString equals "some(x)"', () => {
  fc.assert(fc.property(fc.integer(), (json) => {
    Assert.eq('toString', 'some(' + json + ')', Option.some(json).toString());
  }));
})

Summary: `Option.some(x).toString` produces the string "some(x)" for any integer x.


UnitTest.test('Checking none.getOr(v) === v', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', i, Option.none<any>().getOr(i));
  }));
})

Summary: `Option.none().getOr(i)` should return the value `i`.


UnitTest.test('Checking none.getOrThunk(_ -> v) === v', () => {
  fc.assert(fc.property(fc.func(fc.integer()), (thunk) => {
    Assert.eq('eq', thunk(), Option.none<number>().getOrThunk(thunk));
  }));
})

Summary: `Option.none<number>().getOrThunk(thunk)` returns the result of the `thunk` function.


UnitTest.test('Checking some(x).filter(_ -> true) === some(x)', () => {
  fc.assert(fc.property(fc.integer(), (x) => {
    Assert.eq('eq', true, tOption().eq(
      some(x),
      some(x).filter(Fun.constant(true))
    ));
  }));
})

Summary: Filtering a `some(x)` with a condition that always returns true should result in `some(x)` unchanged.


UnitTest.test('Checking that creating a namespace (forge) from an obj will enable that value to be retrieved by resolving (path)', () => {
  fc.assert(fc.property(
    // NOTE: This value is being modified, so it cannot be shrunk.
    fc.dictionary(fc.asciiString(),
      // We want to make sure every path in the object is an object
      // also, because that is a limitation of forge.
      fc.dictionary(fc.asciiString(),
        fc.dictionary(fc.asciiString(), fc.constant({}))
      )
    ),
    fc.array(fc.asciiString(1, 30), 1, 40),
    fc.asciiString(1, 30),
    fc.asciiString(1, 30),
    function (dict, parts, field, newValue) {
      const created = Resolve.forge(parts, dict);
      created[field] = newValue;
      const resolved = Resolve.path(parts.concat([ field ]), dict);
      Assert.eq(
        'Checking that namespace works with resolve',
        newValue,
        resolved
      );
    }
  ), { endOnFailure: true });
})

Summary: Creating a namespace using `forge` allows a value to be set and then accurately retrieved using `path`.


UnitTest.test('tupleMap obj (x, i) -> { k: i, v: x }', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const output = Obj.tupleMap(obj, (x, i) => ({ k: i, v: x }));
    Assert.eq('tupleMap', obj, output);
  }));
})

Summary: `Obj.tupleMap` should transform a dictionary into an array of objects with keys `k` and `v`, using entries where `k` is the key and `v` is the value, resulting in the same structure as the input.


UnitTest.test('Merged with identity on right', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.merge(obj, {}));
  }));
})

Summary: Merging an object with an empty object on the right should result in the original object.


UnitTest.test('Merged with itself is itself', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.merge(obj, obj));
  }));
})

Summary: Merging an object with itself should result in the original object being unchanged.


UnitTest.test('Merge(a, Merge(b, c)) === Merge(Merge(a, b), c)', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), function (a, b, c) {
    const one = Merger.merge(a, Merger.merge(b, c));
    const other = Merger.merge(Merger.merge(a, b), c);
    Assert.eq('eq', one, other);
  }));
})

Summary: The merge operation is associative, meaning `Merge(a, Merge(b, c))` should equal `Merge(Merge(a, b), c)`.


UnitTest.test('Deep-merged with identity on left', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.deepMerge({}, obj));
  }));
})

Summary: Verifies that deeply merging an empty object with any given dictionary results in the original dictionary remaining unchanged.


UnitTest.test('Check that if the filter always returns true, then everything is in "t"', () => {
  fc.assert(fc.property(
    fc.dictionary(fc.asciiString(), fc.string(1, 40)),
    (obj) => {
      const output = Obj.bifilter(obj, Fun.constant(true));
      Assert.eq('eq', 0, Obj.keys(output.f).length);
      Assert.eq('eq', Obj.keys(obj).length, Obj.keys(output.t).length);
      return true;
    }
  ));
})

Summary: If the filter function always returns true, all elements from the input object should appear in the output object's "t" section, while the "f" section remains empty.


UnitTest.test('Checking struct with one fewer argument', () => {
  fc.assert(fc.property(
    fc.array(fc.string(), 1, 40),
    (rawValues: string[]) => {
      // Remove duplications.
      const values = toUnique(rawValues);

      const struct = Struct.immutable.apply(undefined, values);
      try {
        struct.apply(undefined, values.slice(1));
        return false;
      } catch (err) {
        return err.message.indexOf('Wrong number') > -1;
      }
    }
  ));
})

Summary: `Struct.immutable` should throw an error with the message containing 'Wrong number' if it is applied with one fewer argument than the number of unique strings used to create the struct.


UnitTest.test('adt.unknown.match should pass 1 parameter: [ guesses ]', () => {
  fc.assert(fc.property(arbUnknown, (subject) => {
    const contents = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: record,
      exact: Fun.die('should not be exact')
    });
    Assert.eq('eq', 1, contents.length);
  }));
})

Summary: The function `adt.unknown.match` should return an array with exactly one element when the `unknown` case is matched.


UnitTest.test('adt.exact.match should be same as fold', () => {
  fc.assert(fc.property(arbExact, (subject) => {
    const matched = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: Fun.die('should not be unknown'),
      exact: record
    });

    const folded = subject.fold(Fun.die('should not be nothing'), Fun.die('should not be unknown'), record);
    Assert.eq('eq', matched, folded);
  }));
})

Summary: The property asserts that using `match` on a subject with the `exact` case produces the same result as using `fold` with the same parameters.


UnitTest.test('adt.match must have the right arguments, not just the right number', () => {
  fc.assert(fc.property(arbAdt, (subject) => {
    try {
      subject.match({
        not: Fun.identity,
        the: Fun.identity,
        right: Fun.identity
      });
      return false;
    } catch (err) {
      return err.message.indexOf('nothing') > -1;
    }
  }));
})

Summary: `adt.match` should throw an error if the argument keys do not match the expected pattern, not just if the number of keys is correct.


UnitTest.test('Thunk.cached counter', () => {
  fc.assert(fc.property(fc.json(), fc.func(fc.json()), fc.json(), (a, f, b) => {
    let counter = 0;
    const thunk = Thunk.cached((x) => {
      counter++;
      return {
        counter,
        output: f(x)
      };
    });
    const value = thunk(a);
    const other = thunk(b);
    Assert.eq('eq', value, other);
  }));
})


UnitTest.test('Check compose :: compose(f, g)(x) = f(g(x))', () => {
  fc.assert(fc.property(fc.string(), fc.func(fc.string()), fc.func(fc.string()), (x, f, g) => {
    const h = Fun.compose(f, g);
    Assert.eq('eq', f(g(x)), h(x));
  }));
})


UnitTest.test('Check compose1 :: compose1(f, g)(x) = f(g(x))', () => {
  fc.assert(fc.property(fc.string(), fc.func(fc.string()), fc.func(fc.string()), (x, f, g) => {
    const h = Fun.compose1(f, g);
    Assert.eq('eq', f(g(x)), h(x));
  }));
})


UnitTest.test('Check constant :: constant(a)() === a', () => {
  fc.assert(fc.property(fc.json(), (json) => {
    Assert.eq('eq', json, Fun.constant(json)());
  }));
})


UnitTest.test('Check identity :: identity(a) === a', () => {
  fc.assert(fc.property(fc.json(), (json) => {
    Assert.eq('eq', json, Fun.identity(json));
  }));
})


UnitTest.test('Check always :: f(x) === true', () => {
  fc.assert(fc.property(fc.json(), (json) => {
    Assert.eq('eq', true, Fun.always(json));
  }));
})


UnitTest.test('Check never :: f(x) === false', () => {
  fc.assert(fc.property(fc.json(), (json) => {
    Assert.eq('eq', false, Fun.never(json));
  }));
})


UnitTest.test('Check curry', () => {
  fc.assert(fc.property(fc.json(), fc.json(), fc.json(), fc.json(), (a, b, c, d) => {
    const f = (a, b, c, d) => [ a, b, c, d ];

    Assert.eq('curry 1', Fun.curry(f, a)(b, c, d), [ a, b, c, d ]);
    Assert.eq('curry 2', Fun.curry(f, a, b)(c, d), [ a, b, c, d ]);
    Assert.eq('curry 3', Fun.curry(f, a, b, c)(d), [ a, b, c, d ]);
    Assert.eq('curry 4', Fun.curry(f, a, b, c, d)(), [ a, b, c, d ]);
  }));
})


UnitTest.test('Check not :: not(f(x)) === !f(x)', () => {
  fc.assert(fc.property(fc.json(), fc.func(fc.boolean()), (x, f) => {
    const g = Fun.not(f);
    Assert.eq('eq', f(x), !g(x));
  }));
})


UnitTest.test('Check not :: not(not(f(x))) === f(x)', () => {
  fc.assert(fc.property(fc.json(), fc.func(fc.boolean()), (x, f) => {
    const g = Fun.not(Fun.not(f));
    Assert.eq('eq', f(x), g(x));
  }));
})


UnitTest.test('Check apply :: apply(constant(a)) === a', () => {
  fc.assert(fc.property(fc.json(), (x) => {
    Assert.eq('eq', x, Fun.apply(Fun.constant(x)));
  }));
})


UnitTest.test('Check call :: apply(constant(a)) === undefined', () => {
  fc.assert(fc.property(fc.json(), (x) => {
    let hack: any = null;
    const output = Fun.call(() => {
      hack = x;
    });

    Assert.eq('eq', undefined, output);
    Assert.eq('eq', x, hack);
  }));
})


UnitTest.test('Check that value, value always equal comparison.bothValues', () => {
  fc.assert(fc.property(
    arbResultValue(fc.integer()),
    arbResultValue(fc.integer()),
    function (r1, r2) {
      const comparison = Results.compare(r1, r2);
      return comparison.match({
        bothErrors: Fun.constant(false),
        firstError: Fun.constant(false),
        secondError: Fun.constant(false),
        bothValues: Fun.constant(true)
      });
    }
  ));
})

Summary: `Results.compare` should return `bothValues` if both inputs are result values.


UnitTest.test('Results.unite', () => {
  fc.assert(fc.property(fc.integer(), (a) => {
    Assert.eq('should be error', a, Results.unite(Result.error<number, number>(a)));
    Assert.eq('should be value', a, Results.unite(Result.value<number, number>(a)));
  }));
})


UnitTest.test('Checking value.is(value.getOrDie()) === true', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    Assert.eq('eq', true, res.is(res.getOrDie()));
  }));
})


UnitTest.test('Checking value.isValue === true', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    Assert.eq('eq', true, res.isValue());
  }));
})


UnitTest.test('Checking value.isError === false', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    Assert.eq('eq', false, res.isError());
  }));
})


UnitTest.test('Checking value.getOr(v) === value.value', () => {
  fc.assert(fc.property(fc.integer(), (a) => {
    Assert.eq('eq', a, Result.value(a).getOr(a));
  }));
})


UnitTest.test('Checking value.getOrDie() does not throw', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    res.getOrDie();
  }));
})


UnitTest.test('Checking value.or(oValue) === value', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    Assert.eq('eq value', Result.value(a), Result.value(a).or(Result.value(b)), tResult());
  }));
})


UnitTest.test('Checking error.or(value) === value', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    Assert.eq('eq or', Result.value(b), Result.error(a).or(Result.value(b)), tResult());
  }));
})


UnitTest.test('Checking value.orThunk(die) does not throw', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    res.orThunk(Fun.die('dies'));
  }));
})


UnitTest.test('Checking value.fold(die, id) === value.getOrDie()', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    const actual = res.getOrDie();
    Assert.eq('eq', actual, res.fold(Fun.die('should not get here'), Fun.identity));
  }));
})


UnitTest.test('Checking value.map(f) === f(value.getOrDie())', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.json()), (res, f) => {
    Assert.eq('eq', res.map(f).getOrDie(), f(res.getOrDie()));
  }));
})


UnitTest.test('Result.error: error.or(oValue) === oValue', () => {
  fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
    Assert.eq('eq', Result.value(i), Result.error<number, string>(s).or(Result.value(i)), tResult());
  }));
})

Summary: `Result.error` returns the alternative value when using `or` with `Result.value`, ensuring `Result.error<number, string>(s).or(Result.value(i))` equals `Result.value(i)`.


UnitTest.test('Result.error: error.map(⊥) === error', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', Result.error(i), Result.error(i).map(Fun.die('⊥')), tResult());
  }));
})

Summary: Calling `map` on a `Result.error` with a function that should never be invoked results in the original `Result.error`.


UnitTest.test('Result.error: error.mapError(f) === f(error)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const f = (x) => x % 3;
    Assert.eq('eq', Result.error(f(i)), Result.error(i).mapError(f), tResult());
  }));
})

Summary: Mapping a function over an error should produce the same result as directly applying the function to the error.


UnitTest.test('Result.error: error.each(⊥) === undefined', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
    const actual = res.each(Fun.die('⊥'));
    Assert.eq('eq', undefined, actual);
  }));
})

Summary: The property verifies that invoking `each` on a `Result.error` returns `undefined`.


UnitTest.test('Given f :: s -> RE, Result.error: error.bind(f) === error', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.func(arbResultError(fc.integer())), (res, f) => {
    const actual = res.bind(f);
    Assert.eq('eq', true, getErrorOrDie(res) === getErrorOrDie(actual));
  }));
})

Summary: The property should hold that binding a function to a `Result.error` does not change the error value.


UnitTest.test('Result.error: error.forall === true', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.func(fc.boolean()), (res, f) => {
    Assert.eq('eq', true, res.forall(f));
  }));
})

Summary: `Result.error` instance method `forall` should always return true for any function applied to it.


promiseTest('LazyValue: pure', () =>
  fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    LazyValue.pure(i).get((v) => {
      eqAsync('LazyValue.pure', i, v, reject);
      resolve();
    });
  }))))

Summary: `LazyValue.pure` correctly returns the initial integer passed to it.


promiseTest('LazyValue: pure, map', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.func(fc.string()), (i, f) => new Promise((resolve, reject) => {
    LazyValue.pure(i).map(f).get((v) => {
      eqAsync('LazyValue.map', f(i), v, reject);
      resolve();
    });
  }))))

Summary: `LazyValue.pure(i).map(f).get` should result in the function `f` applied to `i`, matching the expected output through asynchronous equality.


promiseTest('Future: future soon get', () =>
  fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    Future.nu((cb) => {
      setTimeout(() => {
        cb(i);
      }, 3);
    }).get((ii) => {
      eqAsync('get', i, ii, reject, tNumber);
      resolve();
    });
  }))))

Summary: `Future.nu` invokes its callback with a delayed integer, and `get` retrieves it to ensure equality with the original integer asynchronously.


promiseTest('FutureResult: pure get', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.pure<number, unknown>(i).get((ii) => {
    eqAsync('eq', Result.value(i), ii, reject, tResult());
    resolve();
  });
}))))

Summary: `FutureResult.pure` correctly retrieves the encapsulated integer value using `get`, validating it against an expected `Result.value`.


promiseTest('FutureResult: error get', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.error<unknown, number>(i).get((ii) => {
    eqAsync('eq', Result.error(i), ii, reject, tResult());
    resolve();
  });
}))))

Summary: `FutureResult.error<unknown, number>(i).get` returns a result matching `Result.error(i)`.


UnitTest.test('Arr.reverse: 1 element', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (a) => {
    Assert.eq('reverse 1 element', [ a ], Arr.reverse([ a ]));
  }));
})

Summary: `Arr.reverse` should return the same single-element array when reversing an array containing only one element.


UnitTest.test('Arr.reverse: 2 elements', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    Assert.eq('reverse 2 elements', [ b, a ], Arr.reverse([ a, b ]));
  }));
})

Summary: Reversing a two-element array should result in an array where the order of elements is swapped.


UnitTest.test('Arr.reverse: 3 elements', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), fc.integer(), (a, b, c) => {
    Assert.eq('reverse 3 elements', [ c, b, a ], Arr.reverse([ a, b, c ]));
  }));
})

Summary: Reversing an array of three integers results in the expected order.


UnitTest.test('Check that if the filter always returns false, then everything is in "fail"', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr) => {
      const output = Arr.partition(arr, Fun.constant(false));
      Assert.eq('eq', 0, output.pass.length);
      Assert.eq('eq', arr, output.fail);
    }
  ));
})

Summary: If the filter always returns false, all elements should be in the "fail" partition, and "pass" should be empty.


UnitTest.test('zipToObject has matching keys and values', () => {
  fc.assert(fc.property(
    fc.array(fc.asciiString(1, 30)),
    (rawValues: string[]) => {
      const values = Unique.stringArray(rawValues);

      const keys = Arr.map(values, (v, i) => i);

      const output = Zip.zipToObject(keys, values);

      const oKeys = Obj.keys(output);
      Assert.eq('Output keys did not match', oKeys.length, values.length);

      Assert.eq('Output keys', true, Arr.forall(oKeys, (oKey) => {
        const index = parseInt(oKey, 10);
        const expected = values[index];
        return output[oKey] === expected;
      }));
    }
  ));
})


UnitTest.test('zipToTuples matches corresponding tuples', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.array(fc.integer()),
    (keys, values) => {
      if (keys.length !== values.length) {
        Assert.throws('Should throw with different lengths', () => Zip.zipToTuples(keys, values));
      } else {
        const output = Zip.zipToTuples(keys, values);
        Assert.eq('Output keys did not match', keys.length, output.length);
        Assert.eq('', true, Arr.forall(output, (x, i) => x.k === keys[i] && x.v === values[i]));
      }
    }
  ));
})


UnitTest.test('Check that if the filter always returns true, then everything is in "pass"', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr) => {
      const output = Arr.partition(arr, Fun.constant(true));
      Assert.eq('eq', 0, output.fail.length);
      Assert.eq('eq', arr, output.pass);
    }
  ));
})

Summary: If the filter function always returns true, all elements of the array should be in the "pass" partition, and the "fail" partition should be empty.


UnitTest.test('Check that everything in fail fails predicate and everything in pass passes predicate', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr) => {
      const predicate = (x) => x % 3 === 0;
      const output = Arr.partition(arr, predicate);
      return Arr.forall(output.fail, (x) => !predicate(x)) && Arr.forall(output.pass, predicate);
    }
  ));
})

Summary: Elements in the `fail` array should not satisfy the predicate, and elements in the `pass` array should satisfy the predicate.


UnitTest.test('Obj.size: inductive case', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(1, 30), fc.integer()),
      fc.asciiString(1, 30),
      fc.integer(),
      (obj, k, v) => {
        const objWithoutK = Obj.filter(obj, (x, i) => i !== k);
        Assert.eq('Adding key adds 1 to size',
          Obj.size(objWithoutK) + 1,
          Obj.size({ k: v, ...objWithoutK })
        );
      }));
  })

Summary: Verifies that adding a key-value pair to an object increases its size by one.


UnitTest.test('Obj.keys are all in input', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const keys = Obj.keys(obj);
    return Arr.forall(keys, (k) => obj.hasOwnProperty(k));
  }));
})

Summary: `Obj.keys` returns keys that are all present in the given object.


UnitTest.test('Flattening two lists === concat', () => {
  fc.assert(fc.property(fc.array(fc.integer()), fc.array(fc.integer()), (xs, ys) => {
    Assert.eq('flatten/concat', Arr.flatten([ xs, ys ]), xs.concat(ys));
  }));
})

Summary: Flattening two lists with `Arr.flatten` should produce the same result as using `concat`.


UnitTest.test('Element not found when predicate always returns false', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (arr) => !Arr.exists(arr, never)));
})

Summary: `Arr.exists` returns false when a predicate that always returns false is applied to an array of integers.


UnitTest.test('difference: ∀ xs ys. x ∈ xs -> x ∉ (ys - xs)', () => {
  fc.assert(fc.property(fc.array(fc.integer()), fc.array(fc.integer()), (xs, ys) => {
    const diff = Arr.difference(ys, xs);
    return Arr.forall(xs, (x) => !Arr.contains(diff, x));
  }));
})

Summary: The difference between two arrays should not contain any elements from the first array if they are subtracted from the second array.


UnitTest.test('Arr.contains: array contains element', () => {
  fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), fc.array(fc.integer()), (prefix, element, suffix) => {
    const arr2 = Arr.flatten([ prefix, [ element ], suffix ]);
    Assert.eq('in array', true, Arr.contains(arr2, element));
  }));
})

Summary: `Arr.contains` should return true when an array, formed by flattening a prefix, an element, and a suffix, includes the specified element.


UnitTest.test('Arr.bind: binding an array of empty arrays with identity equals an empty array', () => {
  fc.assert(fc.property(fc.array(fc.constant<number[]>([])), (arr) => {
    Assert.eq('bind empty arrays', [], Arr.bind(arr, Fun.identity), tArray(tNumber));
  }));
})

Summary: `Arr.bind` applied to an array of empty arrays with the identity function results in an empty array.


UnitTest.test('Arr.bind: bind (pure .) is map', () => {
  fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), (arr, j) => {
    const f = (x: number) => x + j;
    Assert.eq('bind pure', Arr.map(arr, f), Arr.bind(arr, Fun.compose(Arr.pure, f)), tArray(tNumber));
  }));
})

Summary: `Arr.bind` applied with a function composed with `Arr.pure` behaves like `Arr.map`.


UnitTest.test('Arr.bind: Monad Law: left identity', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (i, j) => {
    const f = (x: number) => [ x, j, x + j ];
    Assert.eq('left id', f(i), Arr.bind(Arr.pure(i), f), tArray(tNumber));
  }));
})

Summary: `Arr.bind` satisfies the monad law of left identity, ensuring that binding a function `f` to a wrapped integer `i` yields the same result as directly applying `f` to `i`.


it('allow to edit for valid input', async () =>
        await fc.assert(
          fc.asyncProperty(
            hexaString({ minLength: 3, maxLength: 3 }),
            async testValue => {
              input!.value = testValue.toUpperCase();
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            }
          )
        ))

Summary: Editing is enabled for input values meeting validity requirements when uppercase hexadecimal strings of length 3 are used.


it('allow to edit for valid input', async () =>
        await fc.assert(
          fc.asyncProperty(
            integer({ min: 0, max: 7 }).map(num => `${num}`),
            async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            }
          )
        ))

Summary: Valid input values between 0 and 7 allow editing, as they ensure `checkValidity` returns true.


it('edits bufTime attribute only for valid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(
            integer({ min: 0 }).map(num => `${num}`),
            async bufTime => {
              inputs[6].value = bufTime;
              await (<WizardTextField>inputs[6]).requestUpdate();
              expect(inputs[6].checkValidity()).to.be.true;
            }
          )
        );
      })

Summary: The `bufTime` attribute is edited only if the input is a valid non-negative integer string.


it('rejects name attribute starting with decimals', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.decimal, 1, 1), async name => {
          nameTextField.value = name;
          await nameTextField.requestUpdate();
          expect(nameTextField.checkValidity()).to.be.false;
        })
      );
    })


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(ipV4(), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            })
          ))


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(invertedRegex(regExp.IPv4), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.false;
            })
          ))


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(invertedRegex(regExp.OSI), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.false;
            })
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(
              nat({ max: 65535 }).map(num => `${num}`),
              async testValue => {
                input!.value = testValue;
                await element.requestUpdate();
                expect(input!.checkValidity()).to.be.true;
              }
            )
          ))

Summary: The test confirms that the input field remains valid after setting it to any natural number up to 65535 and updating the element.


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(invertedRegex(/^[0-9]*$/), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.false;
            })
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(
              integer({ min: 1025, max: 65535 }).map(num => `${num}`),
              async testValue => {
                input!.value = testValue;
                await element.requestUpdate();
                expect(input!.checkValidity()).to.be.true;
              }
            )
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(ipV6(), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            })
          ))


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(invertedRegex(regExp.IPv6), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.false;
            })
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(ipV6SubNet(), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            })
          ))


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(invertedRegex(/^[0-9/]*$/), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.false;
            })
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(
              fc.hexaString({ minLength: 1, maxLength: 5 }),
              async testValue => {
                input!.value = testValue;
                await element.requestUpdate();
                expect(input!.checkValidity()).to.be.true;
              }
            )
          ))


it('does not allow to edit for invalid input', async () =>
          await fc.assert(
            fc.asyncProperty(
              invertedRegex(/^[0-9a-fA-F]{1,5}$/),
              async testValue => {
                input!.value = testValue;
                await element.requestUpdate();
                expect(input!.checkValidity()).to.be.false;
              }
            )
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(
              nat({ max: 255 }).map(num => `${num}`),
              async testValue => {
                input!.value = testValue;
                await element.requestUpdate();
                expect(input!.checkValidity()).to.be.true;
              }
            )
          ))


it('allow to edit for valid input', async () =>
          await fc.assert(
            fc.asyncProperty(regexString(regExp.OSI, 1, 2), async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            })
          ))


it('edits name attribute only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(
          regexString(regExp.abstractDataAttributeName, 1, 32),
          async name => {
            nameTextField.value = name;
            await nameTextField.requestUpdate();
            expect(nameTextField.checkValidity()).to.be.true;
          }
        )
      );
    })


it('for valid input', async () =>
        await fc.assert(
          fc.asyncProperty(
            regexString(regExp.lnClass, 4, 4),
            async testValue => {
              input!.value = testValue;
              await element.requestUpdate();
              expect(input!.checkValidity()).to.be.true;
            }
          )
        ))


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.tName, 1), async name => {
          parent.wizardUI.inputs[0].value = name;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[0].checkValidity()).to.be.true;
        })
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.desc), async desc => {
          parent.wizardUI.inputs[1].value = desc;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[1].checkValidity()).to.be.true;
        })
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.unsigned, 1), async nomFreq => {
          parent.wizardUI.inputs[2].value = nomFreq;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[2].checkValidity()).to.be.true;
        })
      );
    })


it('rejects action for invalid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(
          regexString(inverseRegExp.unsigned, 1),
          async nomFreq => {
            parent.wizardUI.inputs[2].value = nomFreq;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[2].checkValidity()).to.be.false;
          }
        )
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(fc.integer(1, 255), async numPhases => {
          parent.wizardUI.inputs[3].value = String(numPhases);
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[3].checkValidity()).to.be.true;
        })
      );
    })


it('rejects edition for invalid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(
          regexString(inverseRegExp.integer, 1),
          async nomFreq => {
            parent.wizardUI.inputs[3].value = nomFreq;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[3].checkValidity()).to.be.false;
          }
        )
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.decimal), async nomFreq => {
          parent.wizardUI.inputs[4].value = nomFreq;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[4].checkValidity()).to.be.true;
        })
      );
    })


it('rejects edition for invalid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(
          regexString(inverseRegExp.decimal, 1),
          async voltage => {
            parent.wizardUI.inputs[4].value = voltage;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[4].checkValidity()).to.be.false;
          }
        )
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.tName, 1), async name => {
          parent.wizardUI.inputs[1].value = name;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[0].checkValidity()).to.be.true;
        })
      );
    })


it('edits only for valid inputs', async () => {
      await fc.assert(
        fc.asyncProperty(regexString(regExp.desc), async desc => {
          parent.wizardUI.inputs[2].value = desc;
          await parent.updateComplete;
          expect(parent.wizardUI.inputs[1].checkValidity()).to.be.true;
        })
      );
    })


it('edits only for valid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(regexString(regExp.tName, 1), async name => {
            parent.wizardUI.inputs[0].value = name;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[0].checkValidity()).to.be.true;
          })
        );
      })


it('edits only for valid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(regexString(regExp.desc), async desc => {
            parent.wizardUI.inputs[1].value = desc;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[1].checkValidity()).to.be.true;
          })
        );
      })


it('edits only for valid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(regexString(regExp.desc, 1), async type => {
            parent.wizardUI.inputs[2].value = type;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[2].checkValidity()).to.be.true;
          })
        );
      })


it('edits only for valid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(regexString(regExp.decimal), async BitRate => {
            parent.wizardUI.inputs[3].value = BitRate;
            await parent.updateComplete;
            expect(parent.wizardUI.inputs[3].checkValidity()).to.be.true;
          })
        );
      })


it('rejects edition for invalid inputs', async () => {
        await fc.assert(
          fc.asyncProperty(
            regexString(inverseRegExp.decimal, 1),
            async BitRate => {
              parent.wizardUI.inputs[3].value = BitRate;
              await parent.updateComplete;
              expect(parent.wizardUI.inputs[3].checkValidity()).to.be.false;
            }
          )
        );
      })


UnitTest.test('ShadowDom - SelectorFind.descendant', () => {
  if (SugarShadowDom.isSupported()) {
    fc.assert(fc.property(htmlBlockTagName(), htmlInlineTagName(), fc.hexaString(), (block, inline, text) => {
      withShadowElement((ss) => {
        const id = 'theid';
        const inner = SugarElement.fromHtml(`<${block}><p>hello<${inline} id="${id}">${text}</${inline}></p></${block}>`);
        Insert.append(ss, inner);

        const frog: SugarElement<Element> = SelectorFind.descendant(ss, `#${id}`).getOrDie('Element not found');
        Assert.eq('textcontent', text, frog.dom.textContent);
      });
    }));
  }
})


UnitTest.test('All valid floats are valid', () => {
  fc.assert(fc.property(fc.oneof(
    // small to medium floats
    fc.float(),
    // big floats
    fc.tuple(fc.float(), fc.integer(-20, 20)).map(([ mantissa, exponent ]) => mantissa * Math.pow(10, exponent))
  ), (num) => {
    const parsed = Dimension.parse(num.toString(), [ 'empty' ]).getOrDie();
    Assert.eq('Number is unchanged by stringifying and parsing', num, parsed.value);
    Assert.eq('Unit is empty', '', parsed.unit);
    return true;
  }));
})


UnitTest.test('All valid integers are valid', () => {
  fc.assert(fc.property(fc.integer(), (num) => {
    const parsed = Dimension.parse(num.toString(), [ 'empty' ]).getOrDie();
    Assert.eq('Number is unchanged by stringifying and parsing', num, parsed.value);
    Assert.eq('Unit is empty', '', parsed.unit);
    return true;
  }));
})


test('TINY-4773: AutoLink: multiple @ characters', (editor) => {
    fc.assert(fc.property(fc.hexaString(0, 30), fc.hexaString(0, 30), fc.hexaString(0, 30), (s1, s2, s3) => {
      assertNoLink(editor, `${s1}@@${s2}@.@${s3}`, `${s1}@@${s2}@.@${s3}`);
    }));
  })


test('TINY-4773: AutoLink: ending in @ character', (editor) => {
    fc.assert(fc.property(fc.hexaString(0, 100), (s1) => {
      assertNoLink(editor, `${s1}@`, `${s1}@`);
    }));
  })


promiseTest('LazyValue: pure', () =>
  fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    LazyValue.pure(i).get((v) => {
      eqAsync('LazyValue.pure', i, v, reject);
      resolve();
    });
  }))))


promiseTest('LazyValue: pure, map', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.func(fc.string()), (i, f) => new Promise((resolve, reject) => {
    LazyValue.pure(i).map(f).get((v) => {
      eqAsync('LazyValue.map', f(i), v, reject);
      resolve();
    });
  }))))


promiseTest('LazyValue: delayed, map', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.func(fc.string()), (i, f) => new Promise((resolve, reject) => {
    LazyValue.nu((c) => {
      setTimeout(() => {
        c(i);
      }, 2);
    }).map(f).get((v) => {
      eqAsync('LazyValue.map', f(i), v, reject);
      resolve();
    });
  }))))


promiseTest('LazyValue: parallel spec', () => fc.assert(fc.asyncProperty(fc.array(fc.integer(), 0, 20), (vals) => new Promise((resolve, reject) => {
  const lazyVals = Arr.map(vals, LazyValue.pure);
  LazyValues.par(lazyVals).get((actual) => {
    eqAsync('pars', vals, actual, reject);
    resolve();
  });
}))))


promiseTest('Future: pure get', () =>
  fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    Future.pure(i).get((ii) => {
      eqAsync('pure get', i, ii, reject, tNumber);
      resolve();
    });
  }))))


promiseTest('Future: future soon get', () =>
  fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    Future.nu((cb) => {
      setTimeout(() => {
        cb(i);
      }, 3);
    }).get((ii) => {
      eqAsync('get', i, ii, reject, tNumber);
      resolve();
    });
  }))))


promiseTest('Future: map', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.func(fc.string()), (i, f) => new Promise((resolve, reject) => {
    Future.pure(i).map(f).get((ii) => {
      eqAsync('get', f(i), ii, reject, tString);
      resolve();
    });
  }))))


promiseTest('Future: bind', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.func(fc.string()), (i, f) => new Promise((resolve, reject) => {
    Future.pure(i).bind(Fun.compose(Future.pure, f)).get((ii) => {
      eqAsync('get', f(i), ii, reject, tString);
      resolve();
    });
  }))))


promiseTest('Future: anonBind', () =>
  fc.assert(fc.asyncProperty(fc.integer(), fc.string(), (i, s) => new Promise((resolve, reject) => {
    Future.pure(i).anonBind(Future.pure(s)).get((ii) => {
      eqAsync('get', s, ii, reject, tString);
      resolve();
    });
  }))))


promiseTest('Future: parallel spec', () =>
  fc.assert(fc.asyncProperty(fc.array(fc.tuple(fc.integer(1, 10), fc.integer())), (tuples) => new Promise((resolve, reject) => {
    Futures.par(Arr.map(tuples, ([ timeout, value ]) => Future.nu((cb) => {
      setTimeout(() => {
        cb(value);
      }, timeout);
    }))).get((ii) => {
      eqAsync('pars', tuples.map(([ _, i ]) => i), ii, reject, tArray(tNumber));
      resolve();
    });
  })))
)


promiseTest('Future: mapM spec', () =>
  fc.assert(fc.asyncProperty(fc.array(fc.tuple(fc.integer(1, 10), fc.integer())), (tuples) => new Promise((resolve, reject) => {
    Futures.mapM(tuples, ([ timeout, value ]) => Future.nu((cb) => {
      setTimeout(() => {
        cb(value);
      }, timeout);
    })).get((ii) => {
      eqAsync('pars', tuples.map(([ _, i ]) => i), ii, reject, tArray(tNumber));
      resolve();
    });
  })))
)


promiseTest('FutureResult: nu', () => fc.assert(fc.asyncProperty(arbResult(fc.integer(), fc.integer()), (r) => new Promise((resolve, reject) => {
  FutureResult.nu((completer) => {
    completer(r);
  }).get((ii) => {
    eqAsync('eq', r, ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: fromFuture', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.fromFuture<number, unknown>(Future.pure(i)).get((ii) => {
    eqAsync('eq', Result.value(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: wrap get', () => fc.assert(fc.asyncProperty(arbResult(fc.integer(), fc.integer()), (r) => new Promise((resolve, reject) => {
  FutureResult.wrap(Future.pure(r)).get((ii) => {
    eqAsync('eq', r, ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: fromResult get', () => fc.assert(fc.asyncProperty(arbResult(fc.integer(), fc.integer()), (r) => new Promise((resolve, reject) => {
  FutureResult.fromResult(r).get((ii) => {
    eqAsync('eq', r, ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: pure get', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.pure<number, unknown>(i).get((ii) => {
    eqAsync('eq', Result.value(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: value get', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.value<number, unknown>(i).get((ii) => {
    eqAsync('eq', Result.value(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: error get', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.error<unknown, number>(i).get((ii) => {
    eqAsync('eq', Result.error(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: value mapResult', () => {
  const f = (x) => x + 3;
  return fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    FutureResult.value(i).mapResult(f).get((ii) => {
      eqAsync('eq', Result.value(f(i)), ii, reject, tResult());
      resolve();
    });
  })));
})


promiseTest('FutureResult: error mapResult', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.error(i).mapResult(Fun.die('⊥')).get((ii) => {
    eqAsync('eq', Result.error(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: value mapError', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.value(i).mapError(Fun.die('⊥')).get((ii) => {
    eqAsync('eq', Result.value(i), ii, reject, tResult());
    resolve();
  });
}))))


promiseTest('FutureResult: err mapError', () => {
  const f = (x) => x + 3;
  return fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    FutureResult.error(i).mapError(f).get((ii) => {
      eqAsync('eq', Result.error(f(i)), ii, reject, tResult());
      resolve();
    });
  })));
})


promiseTest('FutureResult: value bindFuture value', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  const f = (x) => x % 4;
  FutureResult.value(i).bindFuture((x) => FutureResult.value(f(x))).get((actual) => {
    eqAsync('bind result', Result.value(f(i)), actual, reject, tResult(tNumber));
    resolve();
  });
}))))


promiseTest('FutureResult: bindFuture: value bindFuture error', () => fc.assert(fc.asyncProperty(fc.integer(), fc.string(), (i, s) => new Promise((resolve, reject) => {
  FutureResult.value(i).bindFuture(() => FutureResult.error(s)).get((actual) => {
    eqAsync('bind result', Result.error(s), actual, reject, tResult(tNumber));
    resolve();
  });
}))))


promiseTest('FutureResult: error bindFuture', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
  FutureResult.error(i).bindFuture(Fun.die('⊥')).get((actual) => {
    eqAsync('bind result', Result.error(i), actual, reject, tResult(tNumber));
    resolve();
  });
}))))


UnitTest.test('Error is thrown if not all arguments are supplied', () => {
  fc.assert(fc.property(arbAdt, fc.array(arbKeys, 1, 40), (subject, exclusions) => {
    const original = Arr.filter(allKeys, (k) => !Arr.contains(exclusions, k));

    try {
      const branches = Arr.mapToObject(original, () => Fun.identity);
      subject.match(branches);
      return false;
    } catch (err) {
      return err.message.indexOf('nothing') > -1;
    }
  }));
})


UnitTest.test('adt.nothing.match should pass [ ]', () => {
  fc.assert(fc.property(arbNothing, (subject) => {
    const contents = subject.match({
      nothing: record,
      unknown: Fun.die('should not be unknown'),
      exact: Fun.die('should not be exact')
    });
    Assert.eq('eq', [], contents);
  }));
})


UnitTest.test('adt.nothing.match should be same as fold', () => {
  fc.assert(fc.property(arbNothing, (subject) => {
    const matched = subject.match({
      nothing: record,
      unknown: Fun.die('should not be unknown'),
      exact: Fun.die('should not be exact')
    });

    const folded = subject.fold(record, Fun.die('should not be unknown'), Fun.die('should not be exact'));
    Assert.eq('eq', matched, folded);
  }));
})


UnitTest.test('adt.unknown.match should pass 1 parameter: [ guesses ]', () => {
  fc.assert(fc.property(arbUnknown, (subject) => {
    const contents = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: record,
      exact: Fun.die('should not be exact')
    });
    Assert.eq('eq', 1, contents.length);
  }));
})


UnitTest.test('adt.unknown.match should be same as fold', () => {
  fc.assert(fc.property(arbUnknown, (subject) => {
    const matched = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: record,
      exact: Fun.die('should not be exact')
    });

    const folded = subject.fold(Fun.die('should not be nothing'), record, Fun.die('should not be exact'));
    Assert.eq('eq', matched, folded);
  }));
})


UnitTest.test('adt.exact.match should pass 2 parameters [ value, precision ]', () => {
  fc.assert(fc.property(arbExact, (subject) => {
    const contents = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: Fun.die('should not be unknown'),
      exact: record
    });
    Assert.eq('eq', 2, contents.length);
  }));
})


UnitTest.test('adt.exact.match should be same as fold', () => {
  fc.assert(fc.property(arbExact, (subject) => {
    const matched = subject.match({
      nothing: Fun.die('should not be nothing'),
      unknown: Fun.die('should not be unknown'),
      exact: record
    });

    const folded = subject.fold(Fun.die('should not be nothing'), Fun.die('should not be unknown'), record);
    Assert.eq('eq', matched, folded);
  }));
})


UnitTest.test('adt.match must have the right arguments, not just the right number', () => {
  fc.assert(fc.property(arbAdt, (subject) => {
    try {
      subject.match({
        not: Fun.identity,
        the: Fun.identity,
        right: Fun.identity
      });
      return false;
    } catch (err) {
      return err.message.indexOf('nothing') > -1;
    }
  }));
})


UnitTest.test('Cell: cell(x).get() === x', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const cell = Cell(i);
    Assert.eq('eq', i, cell.get());
  }));
})


UnitTest.test('Cell: cell.get() === last set call', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), fc.integer(), (a, b, c) => {
    const cell = Cell(a);
    Assert.eq('a', a, cell.get());
    cell.set(b);
    Assert.eq('b', b, cell.get());
    cell.set(c);
    Assert.eq('c', c, cell.get());
  }));
})


UnitTest.test('removeLeading removes prefix', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(),
    (prefix, suffix) => {
      Assert.eq('removeLeading', suffix, Strings.removeLeading(prefix + suffix, prefix));
    }
  ));
})

Summary: The function `removeLeading` should remove the specified prefix from the beginning of a string, leaving the suffix unchanged.


UnitTest.test('ensureTrailing is identity if string already ends with suffix', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    function (prefix, suffix) {
      const s = prefix + suffix;
      Assert.eq('id', s, Strings.ensureTrailing(s, suffix));
    }));
})

Summary: `Strings.ensureTrailing` should return the original string if it already ends with the specified suffix.


UnitTest.test('startsWith a prefix', () => {
  fc.assert(fc.property(fc.string(), fc.string(),
    (prefix, suffix) => Strings.startsWith(prefix + suffix, prefix)
  ));
})

Summary: A string concatenated with a prefix should be verified to start with that prefix.


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s1 + s) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, s1) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s1 + s
      ));
    }
  ));
})

Summary: `StringMatch.matches` should return true when using `StringMatch.contains` with a case-insensitive match of `s1` and the string `s1 + s`.


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s + s1) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.string(),
    (s, s1) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s + s1
      ));
    }
  ));
})

Summary: `StringMatch.matches` returns true when using `StringMatch.contains` with a case-insensitive match for `s1` appended to any string `s`.


UnitTest.test('StringMatch.matches(StringMatch.contains(s1), s) === s.indexOf(s1)', () => {
  fc.assert(fc.property(
    fc.asciiString(),
    fc.asciiString(1, 40),
    (s, s1) => {
      Assert.eq('eq', s.toLowerCase().indexOf(s1.toLowerCase()) > -1, StringMatch.matches(
        StringMatch.contains(s1, StringMatch.caseInsensitive),
        s
      ));
    }
  ));
})

Summary: `StringMatch.matches(StringMatch.contains(s1))` should return true if and only if `s.indexOf(s1)` is greater than -1, indicating that `s1` is a substring of `s`, ignoring case.


UnitTest.test('StringMatch.matches(StringMatch.contains(s), s) === true', () => {
  fc.assert(fc.property(
    fc.string(),
    (s) => {
      Assert.eq('eq', true, StringMatch.matches(
        StringMatch.contains(s, StringMatch.caseInsensitive),
        s
      ));
    }
  ));
})

Summary: `StringMatch.matches` should return true when using `StringMatch.contains` with a string in a case-insensitive manner.


UnitTest.test('capitalize: head is uppercase', () => {
  fc.assert(fc.property(fc.ascii(), fc.asciiString(30), (h, t) => {
    const actualH = Strings.capitalize(h + t).charAt(0);
    Assert.eq('head is uppercase', h.toUpperCase(), actualH, tString);
  }));
})


UnitTest.test('Optionals.cat of nones.concat(somes).concat(nones) should be somes', () => {
  fc.assert(fc.property(
    fc.array(fc.json()),
    fc.array(fc.json()),
    fc.array(fc.json()),
    (before, on, after) => {
      const beforeNones = Arr.map(before, Optional.none);
      const afterNones = Arr.map(after, Optional.none);
      const onSomes = Arr.map(on, Optional.some);
      const output = Optionals.cat(beforeNones.concat(onSomes).concat(afterNones));
      Assert.eq('eq', on, output);
    }
  ));
})

Summary: `Optionals.cat` should return only the elements wrapped in `Optional.some`, ignoring those wrapped in `Optional.none`.


UnitTest.test('Optionals.someIf: true -> some', () => {
  fc.assert(fc.property(fc.integer(), (n) => {
    Assert.eq('eq', Optional.some(n), Optionals.someIf<number>(true, n), tOptional());
  }));
})

Summary: `Optionals.someIf` should return `Optional.some(n)` when the condition is true.


UnitTest.test('Optionals.sequence: Single some value', () => {
  fc.assert(fc.property(fc.integer(), (n) => {
    Assert.eq('eq', Optional.some([ n ]), Optionals.sequence([ Optional.some(n) ]), tOptional());
  }));
})

Summary: `Optionals.sequence` converts an array containing a single `Optional.some` value into an `Optional.some` of an array with that value.


UnitTest.test('Optionals.sequence: Two some values', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (n, m) => {
    Assert.eq('eq', Optional.some([ n, m ]), Optionals.sequence<number>([ Optional.some(n), Optional.some(m) ]), tOptional());
  }));
})

Summary: `Optionals.sequence` constructs a single `Optional` containing an array of values when given a sequence of `Optional` instances, all containing values.


UnitTest.test('Optionals.sequence: Array of numbers', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (n) => {
    const someNumbers = Arr.map(n, (x) => Optional.some(x));
    Assert.eq('eq', Optional.some(n), Optionals.sequence<number>(someNumbers), tOptional());
  }));
})

Summary: `Optionals.sequence` converts an array of `Optional` numbers back into an `Optional` array of numbers.


UnitTest.test('Optional.some(x) === Optional.some(x)', () => {
  fc.assert(fc.property(fc.integer(), (i) => Assert.eq('eq', true, Optional.some(i).equals(Optional.some(i)))));
})

Summary: `Optional.some(x).equals(Optional.some(x))` should return true for any integer `x`.


UnitTest.test('Optional.some(x) === Optional.some(x) (same ref)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const ob = Optional.some(i);
    Assert.eq('eq', true, ob.equals(ob));
  }));
})

Summary: `Optional.some(x)` is equal to itself when the reference is the same.


UnitTest.test('Checking some(x).equals_(some(y), _, _ -> true) === true', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    const opt1 = Optional.some(a);
    const opt2 = Optional.some(b);
    Assert.eq('eq', true, opt1.equals_(opt2, Fun.always));
  }));
})

Summary: `Optional.some(x).equals_(Optional.some(y), _, _ -> true)` should return true.


UnitTest.test('Checking some(x).equals_(some(y), f) iff. f(x, y)', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), arbOptionSome(fc.integer()), fc.func(fc.boolean()), (a, b, f) => {
    const opt1 = Optional.some(a);
    const opt2 = Optional.some(b);
    return f(a, b) === opt1.equals_(opt2, f);
  }));
})

Summary: Two `Optional.some` objects are equal using a comparator function `f` if and only if `f` applied to their values returns true.


UnitTest.test('Checking some(x).exists(_ -> false) === false', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => !opt.exists(Fun.never)));
})

Summary: `some(x).exists(_ -> false)` should always return false for any integer wrapped in an `opt`.


UnitTest.test('Checking some(x).exists(_ -> true) === true', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => opt.exists(Fun.always)));
})

Summary: `opt.exists(Fun.always)` should always return true for `some(x)`.


UnitTest.test('Checking some(x).isSome === true', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => opt.isSome()));
})


UnitTest.test('Checking some(x).isNone === false', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => !opt.isNone()));
})


UnitTest.test('Checking some(x).exists(f) iff. f(x)', () => {
  fc.assert(fc.property(fc.integer(), fc.func(fc.boolean()), (i, f) => {
    const opt = Optional.some(i);
    if (f(i)) {
      Assert.eq('eq', true, opt.exists(f));
    } else {
      Assert.eq('eq', false, opt.exists(f));
    }
  }));
})

Summary: `Optional.some(x).exists(f)` should return true if and only if the function `f(x)` returns true.


UnitTest.test('Checking some(x).forall(_ -> false) === false', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => !opt.forall(Fun.never)));
})

Summary: `some(x).forall(_ -> false)` should always evaluate to false when using `OptionalSome`.


UnitTest.test('Checking some(x).each(f) === undefined and f gets x', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => {
    let hack: number | null = null;
    const actual = opt.each((x) => {
      hack = x;
    });
    Assert.eq('eq', undefined, actual);
    Assert.eq('hack', hack, opt.getOrDie());
  }));
})


UnitTest.test('Checking some(x).forall(_ -> true) === true', () => {
  fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => opt.forall(Fun.always)));
})

Summary: `opt.forall(Fun.always)` should return true for `some(x)` with any integer value `x`.


UnitTest.test('Checking none.or(oSomeValue) === oSomeValue', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const output = Optional.none().or(Optional.some(i));
    Assert.eq('eq', true, output.is(i));
  }));
})

Summary: `Optional.none().or(Optional.some(i))` should produce an `Optional` containing the value `i`.


UnitTest.test('Optional.getOr', () => {
  fc.assert(fc.property(fc.integer(), (x) => {
    Assert.eq('none', x, Optional.none().getOr(x));
    Assert.eq('none', x, Optional.none().getOrThunk(() => x));
  }));
  fc.assert(fc.property(fc.integer(), fc.integer(), (x, y) => {
    Assert.eq('some', x, Optional.some(x).getOr(y));
    Assert.eq('some', x, Optional.some(x).getOrThunk(Fun.die('boom')));
  }));
})

Summary: `Optional.getOr` should return the provided value for `Optional.none`, and the wrapped value for `Optional.some`; `Optional.getOrThunk` should behave similarly but take a function returning the alternative value.


UnitTest.test('ResultInstances.eq: value(x) = value(x)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq(
      'results should be equal',
      Result.value<number, string>(i),
      Result.value<number, string>(i),
      tResult(tNumber, tString)
    );
  }));
})


UnitTest.test('ResultInstances.eq: error(x) = error(x)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq(
      'results should be equal',
      Result.error<string, number>(i),
      Result.error<string, number>(i),
      tResult(tString, tNumber)
    );
  }));
})


UnitTest.test('ResultInstances.eq: value(a) != error(e)', () => {
  fc.assert(fc.property(fc.integer(), fc.string(), (a, e) => {
    Assert.eq(
      'results should not be equal #1',
      false,
      tResult(tNumber, tString).eq(
        Result.value<number, string>(a),
        Result.error<number, string>(e)
      ));

    Assert.eq(
      'results should not be equal #2',
      false,
      tResult(tNumber, tString).eq(
        Result.error<number, string>(e),
        Result.value<number, string>(a)
      ));
  }));
})


UnitTest.test('ResultInstances.eq: (a = b) = (value(a) = value(b))', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    Assert.eq(
      'eq',
      a === b,
      tResult(tNumber, tString).eq(
        Result.value<number, string>(a),
        Result.value<number, string>(b)
      ));
  }));
})


UnitTest.test('ResultInstances.eq: (a = b) = (error(a) = error(b))', () => {
  fc.assert(fc.property(fc.string(), fc.string(), (a, b) => {
    Assert.eq(
      'eq',
      a === b,
      tResult(tNumber, tString).eq(
        Result.error<number, string>(a),
        Result.error<number, string>(b)
      ));
  }));
})


UnitTest.test('ResultInstances.pprint', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('pprint value',
      `Result.value(
  ${i}
)`,
      Pprint.render(Result.value(i), tResult(tNumber, tString))
    );

    Assert.eq('pprint error',
      `Result.error(
  ${i}
)`,
      Pprint.render(Result.error(i), tResult(tString, tNumber))
    );
  }));
})


UnitTest.test('Obj.map: map id obj = obj', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const actual = Obj.map(obj, Fun.identity);
    Assert.eq('map id', obj, actual);
  }));
})


UnitTest.test('map constant obj means that values(obj) are all the constant', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), fc.integer(), (obj, x) => {
    const output = Obj.map(obj, Fun.constant(x));
    const values = Obj.values(output);
    return Arr.forall(values, (v) => v === x);
  }));
})


UnitTest.test('tupleMap obj (x, i) -> { k: i, v: x }', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const output = Obj.tupleMap(obj, (x, i) => ({ k: i, v: x }));
    Assert.eq('tupleMap', obj, output);
  }));
})


UnitTest.test('mapToArray is symmetric with tupleMap', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const array = Obj.mapToArray(obj, (x, i) => ({ k: i, v: x }));

    const aKeys = Arr.map(array, (x) => x.k);
    const aValues = Arr.map(array, (x) => x.v);

    const keys = Obj.keys(obj);
    const values = Obj.values(obj);

    Assert.eq('same keys', Arr.sort(keys), Arr.sort(aKeys));
    Assert.eq('same values', Arr.sort(values), Arr.sort(aValues));
  }));
})


UnitTest.test('Obj.isEmpty: single key/value', () => {
  fc.assert(fc.property(
    fc.string(),
    fc.json(),
    (k: string, v: any) => {
      const o = { [k]: v };
      Assert.eq('eq', false, Obj.isEmpty(o));
    }
  ));
})


UnitTest.test('the value found by find always passes predicate', () => {
  fc.assert(fc.property(
    fc.dictionary(fc.asciiString(), fc.json()),
    fc.func(fc.boolean()),
    function (obj, pred) {
      // It looks like the way that fc.fun works is it cares about all of its arguments, so therefore
      // we have to only pass in one if we want it to be deterministic. Just an assumption
      const value = Obj.find(obj, function (v) {
        return pred(v);
      });
      return value.fold(function () {
        const values = Obj.values(obj);
        return !Arr.exists(values, function (v) {
          return pred(v);
        });
      }, function (v) {
        return pred(v);
      });
    }
  ));
})


UnitTest.test('If object is empty, find is always none', () => {
  fc.assert(fc.property(
    fc.func(fc.boolean()),
    function (pred) {
      const value = Obj.find({ }, pred);
      return value.isNone();
    }
  ));
})


UnitTest.test('Each + set should equal the same object', () => {
  fc.assert(fc.property(
    fc.dictionary(fc.asciiString(), fc.json()),
    function (obj: Record<string, any>) {
      const values = { };
      const output = Obj.each(obj, function (x, i) {
        values[i] = x;
      });
      Assert.eq('eq', obj, values);
      Assert.eq('output', undefined, output);
    }
  ));
})


UnitTest.test('Merged with identity on left', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.merge({}, obj));
  }));
})


UnitTest.test('Merged with identity on right', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.merge(obj, {}));
  }));
})


UnitTest.test('Merged with itself is itself', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.merge(obj, obj));
  }));
})


UnitTest.test('Merge(a, Merge(b, c)) === Merge(Merge(a, b), c)', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), function (a, b, c) {
    const one = Merger.merge(a, Merger.merge(b, c));
    const other = Merger.merge(Merger.merge(a, b), c);
    Assert.eq('eq', one, other);
  }));
})


UnitTest.test('Merge(a, b) contains all the keys of b', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), function (a, b) {
    const output = Merger.merge(a, b);
    const keys = Obj.keys(b);
    const oKeys = Obj.keys(output);
    return Arr.forall(keys, function (k) {
      return Arr.contains(oKeys, k);
    });
  }));
})


UnitTest.test('Deep-merged with identity on left', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.deepMerge({}, obj));
  }));
})


UnitTest.test('Deep-merged with identity on right', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.deepMerge(obj, {}));
  }));
})


UnitTest.test('Deep-merged with itself is itself', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), function (obj) {
    Assert.eq('eq', obj, Merger.deepMerge(obj, obj));
  }));
})


UnitTest.test('Deep-merge(a, Deep-merge(b, c)) === Deep-merge(Deep-merge(a, b), c)', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), function (a, b, c) {
    const one = Merger.merge(a, Merger.merge(b, c));
    const other = Merger.merge(Merger.merge(a, b), c);
    Assert.eq('eq', one, other);
  }));
})


UnitTest.test('Deep-merge(a, b) contains all the keys of b', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), fc.dictionary(fc.asciiString(), fc.json()), function (a, b) {
    const output = Merger.deepMerge(a, b);
    const keys = Obj.keys(b);
    const oKeys = Obj.keys(output);
    return Arr.forall(keys, function (k) {
      return Arr.contains(oKeys, k);
    });
  }));
})


UnitTest.test('Check that everything in f fails predicate and everything in t passes predicate', () => {
  fc.assert(fc.property(
    fc.dictionary(fc.asciiString(1, 30), fc.integer()),
    (obj) => {
      const predicate = (x) => x % 2 === 0;
      const output = Obj.bifilter(obj, predicate);

      const matches = (k) => predicate(obj[k]);

      const falseKeys = Obj.keys(output.f);
      const trueKeys = Obj.keys(output.t);

      Assert.eq('Something in "f" passed predicate', false, Arr.exists(falseKeys, matches));
      Assert.eq('Something in "t" failed predicate', true, Arr.forall(trueKeys, matches));
    }
  ));
})


UnitTest.test('Checking that creating a namespace (forge) from an obj will enable that value to be retrieved by resolving (path)', () => {
  fc.assert(fc.property(
    // NOTE: This value is being modified, so it cannot be shrunk.
    fc.dictionary(fc.asciiString(),
      // We want to make sure every path in the object is an object
      // also, because that is a limitation of forge.
      fc.dictionary(fc.asciiString(),
        fc.dictionary(fc.asciiString(), fc.constant({}))
      )
    ),
    fc.array(fc.asciiString(1, 30), 1, 40),
    fc.asciiString(1, 30),
    fc.asciiString(1, 30),
    function (dict, parts, field, newValue) {
      const created = Resolve.forge(parts, dict);
      created[field] = newValue;
      const resolved = Resolve.path(parts.concat([ field ]), dict);
      Assert.eq(
        'Checking that namespace works with resolve',
        newValue,
        resolved
      );
    }
  ), { endOnFailure: true });
})


UnitTest.test('Num.clamp', () => {
  fc.assert(fc.property(
    fc.nat(1000),
    fc.nat(1000),
    fc.nat(1000),
    (a, b, c) => {
      const low = a;
      const med = low + b;
      const high = med + c;
      // low <= med <= high
      Assert.eq('Number should be unchanged when item is within bounds', med, Num.clamp(med, low, high));
      Assert.eq('Number should snap to min', med, Num.clamp(med, low, high));
      Assert.eq('Number should snap to max', med, Num.clamp(high, low, med));
    }));
})


UnitTest.test('CycleBy should have an adjustment of delta, or be the min or max', () => {
  fc.assert(fc.property(
    fc.nat(),
    fc.integer(),
    fc.nat(),
    fc.nat(),
    (value, delta, min, range) => {
      const max = min + range;
      const actual = Num.cycleBy(value, delta, min, max);
      return (actual - value) === delta || actual === min || actual === max;
    }
  ));
})


UnitTest.test('CycleBy 0 has no effect', () => {
  fc.assert(fc.property(fc.nat(), fc.nat(), (value, delta) => {
    const actual = Num.cycleBy(value, 0, value, value + delta);
    Assert.eq('eq', value, actual);
  }));
})


UnitTest.test('CycleBy delta is max', () => {
  fc.assert(fc.property(fc.nat(), fc.nat(), (value, delta) => {
    const max = value + delta;
    const actual = Num.cycleBy(value, delta, value, max);
    Assert.eq('eq', max, actual);
  }));
})


UnitTest.test('Check that values should be empty and  errors should be all if we only generate errors', () => {
  fc.assert(fc.property(
    fc.array(arbResultError(fc.integer())),
    function (resErrors) {
      const actual = Results.partition(resErrors);
      if (actual.values.length !== 0) {
        Assert.fail('Values length should be 0');
      } else if (resErrors.length !== actual.errors.length) {
        Assert.fail('Errors length should be ' + resErrors.length);
      }
      return true;
    }
  ));
})


UnitTest.test('Check that errors should be empty and values should be all if we only generate values', () => {
  fc.assert(fc.property(
    fc.array(arbResultValue(fc.integer())),
    function (resValues) {
      const actual = Results.partition(resValues);
      if (actual.errors.length !== 0) {
        Assert.fail('Errors length should be 0');
      } else if (resValues.length !== actual.values.length) {
        Assert.fail('Values length should be ' + resValues.length);
      }
      return true;
    }
  ));
})


UnitTest.test('Check that the total number of values and errors matches the input size', () => {
  fc.assert(fc.property(
    fc.array(arbResult(fc.integer(), fc.string())),
    function (results) {
      const actual = Results.partition(results);
      Assert.eq('Total number should match size of input', results.length, actual.errors.length + actual.values.length);
    }
  ));
})


UnitTest.test('Results.unite', () => {
  fc.assert(fc.property(fc.integer(), (a) => {
    Assert.eq('should be error', a, Results.unite(Result.error<number, number>(a)));
    Assert.eq('should be value', a, Results.unite(Result.value<number, number>(a)));
  }));
})

Summary: The method `Results.unite` should return the input number regardless of whether it is wrapped as an error or a value.


UnitTest.test('Checking value.getOr(v) === value.value', () => {
  fc.assert(fc.property(fc.integer(), (a) => {
    Assert.eq('eq', a, Result.value(a).getOr(a));
  }));
})

Summary: The property ensures that `Result.value(a).getOr(a)` returns the value `a`.


UnitTest.test('Checking value.orThunk(die) does not throw', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
    res.orThunk(Fun.die('dies'));
  }));
})

Summary: `orThunk(die)` function executes without throwing an error when applied to `arbResultValue` containing integers.


UnitTest.test('Checking value.map(f) === f(value.getOrDie())', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.json()), (res, f) => {
    Assert.eq('eq', res.map(f).getOrDie(), f(res.getOrDie()));
  }));
})

Summary: `value.map(f)` should equal `f(value.getOrDie())` for a given result value and function `f`.


UnitTest.test('Checking value.each(f) === undefined', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.json()), (res, f) => {
    const actual = res.each(f);
    Assert.eq('eq', undefined, actual);
  }));
})


UnitTest.test('Given f :: s -> RV, checking value.bind(f).getOrDie() === f(value.getOrDie()).getOrDie()', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(arbResultValue(fc.integer())), (res, f) => {
    Assert.eq('eq', res.bind(f).getOrDie(), f(res.getOrDie()).getOrDie());
  }));
})


UnitTest.test('Given f :: s -> RE, checking value.bind(f).fold(id, die) === f(value.getOrDie()).fold(id, die)', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(arbResultError(fc.integer())), (res, f) => {
    const toErrString = (r) => r.fold(Fun.identity, Fun.die('Not a Result.error'));
    Assert.eq('eq', toErrString(res.bind(f)), toErrString(f(res.getOrDie())));
  }));
})


UnitTest.test('Checking value.forall is true iff. f(value.getOrDie() === true)', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.boolean()), (res, f) => {
    Assert.eq('eq', f(res.getOrDie()), res.forall(f));
  }));
})


UnitTest.test('Checking value.exists is true iff. f(value.getOrDie() === true)', () => {
  fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.boolean()), (res, f) => {
    Assert.eq('eq', f(res.getOrDie()), res.exists(f));
  }));
})


UnitTest.test('Result.error: error.is === false', () => {
  fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
    Assert.eq('eq', false, Result.error<number, string>(s).is(i));
  }));
})


UnitTest.test('Result.error: error.isValue === false', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
    Assert.eq('eq', false, res.isValue());
  }));
})


UnitTest.test('Result.error: error.isError === true', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
    Assert.eq('eq', true, res.isError());
  }));
})


UnitTest.test('Result.error: error.getOr(v) === v', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.json(), (res, json) => {
    Assert.eq('eq', json, res.getOr(json));
  }));
})


UnitTest.test('Result.error: error.getOrDie() always throws', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
    Assert.throws('should throw', () => {
      res.getOrDie();
    });
  }));
})


UnitTest.test('Result.error: error.or(oValue) === oValue', () => {
  fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
    Assert.eq('eq', Result.value(i), Result.error<number, string>(s).or(Result.value(i)), tResult());
  }));
})


UnitTest.test('Result.error: error.orThunk(_ -> v) === v', () => {
  fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
    Assert.eq('eq', Result.value(i), Result.error<number, string>(s).orThunk(() => Result.value(i)), tResult());
  }));
})


UnitTest.test('Result.error: error.fold(_ -> x, die) === x', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.json(), (res, json) => {
    const actual = res.fold(Fun.constant(json), Fun.die('Should not die'));
    Assert.eq('eq', json, actual);
  }));
})


UnitTest.test('Result.error: error.map(⊥) === error', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', Result.error(i), Result.error(i).map(Fun.die('⊥')), tResult());
  }));
})


UnitTest.test('Result.error: error.mapError(f) === f(error)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    const f = (x) => x % 3;
    Assert.eq('eq', Result.error(f(i)), Result.error(i).mapError(f), tResult());
  }));
})


UnitTest.test('Result.error: error.each(⊥) === undefined', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
    const actual = res.each(Fun.die('⊥'));
    Assert.eq('eq', undefined, actual);
  }));
})


UnitTest.test('Given f :: s -> RV, Result.error: error.bind(f) === error', () => {
  fc.assert(fc.property(arbResultError<number, number>(fc.integer()), fc.func(arbResultValue<number, number>(fc.integer())), (res, f) => {
    const actual = res.bind(f);
    Assert.eq('eq', true, getErrorOrDie(res) === getErrorOrDie(actual));
  }));
})


UnitTest.test('Given f :: s -> RE, Result.error: error.bind(f) === error', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.func(arbResultError(fc.integer())), (res, f) => {
    const actual = res.bind(f);
    Assert.eq('eq', true, getErrorOrDie(res) === getErrorOrDie(actual));
  }));
})


UnitTest.test('Result.error: error.forall === true', () => {
  fc.assert(fc.property(arbResultError(fc.integer()), fc.func(fc.boolean()), (res, f) => {
    Assert.eq('eq', true, res.forall(f));
  }));
})


UnitTest.test('Result.error: error.exists === false', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.eq('eq', false, Result.error<unknown, number>(i).exists(Fun.die('⊥')));
  }));
})


UnitTest.test('Id: Two ids should not be the same', () => {
  const arbId = fc.string(1, 30).map(Id.generate);
  fc.assert(fc.property(arbId, arbId, (id1, id2) => id1 !== id2));
})


UnitTest.test('Arr.groupBy: Adjacent groups have different hashes, and everything in a group has the same hash', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.func(fc.asciiString()),
    (xs, f) => {
      const groups = Arr.groupBy(xs, (x) => f(x));

      /* Properties about groups
       * 1. No two adjacent groups can have the same g(..) value
       * 2. Each group must have the same g(..) value
       */

      const hasEmptyGroups = Arr.exists(groups, (g) => g.length === 0);

      if (hasEmptyGroups) {
        Assert.fail('Should not have empty groups');
      }
      // No consecutive groups should have the same result of g.
      const values = Arr.map(groups, (group) => {
        const first = f(group[0]);
        const mapped = Arr.map(group, (g) => f(g));

        const isSame = Arr.forall(mapped, (m) => m === first);
        if (!isSame) {
          Assert.fail('Not everything in a group has the same g(..) value');
        }
        return first;
      });

      const hasSameGroup = Arr.exists(values, (v, i) => i > 0 ? values[i - 1] === values[i] : false);

      if (hasSameGroup) {
        Assert.fail('A group is next to another group with the same g(..) value');
      }
      return true;
    }
  ));
})


UnitTest.test('Flattening groups equals the original array', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.func(fc.string()),
    (xs, f) => {
      const groups = Arr.groupBy(xs, (x) => f(x));

      const output = Arr.flatten(groups);
      Assert.eq('eq', xs, output);
    }
  ));
})


UnitTest.test('Arr.reverse: Reversing twice is identity', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
    Assert.eq('reverse twice', arr, Arr.reverse(Arr.reverse(arr)));
  }));
})


UnitTest.test('Arr.reverse: 1 element', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (a) => {
    Assert.eq('reverse 1 element', [ a ], Arr.reverse([ a ]));
  }));
})


UnitTest.test('Arr.reverse: 2 elements', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    Assert.eq('reverse 2 elements', [ b, a ], Arr.reverse([ a, b ]));
  }));
})


UnitTest.test('Arr.reverse: 3 elements', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), fc.integer(), (a, b, c) => {
    Assert.eq('reverse 3 elements', [ c, b, a ], Arr.reverse([ a, b, c ]));
  }));
})


UnitTest.test('Arr.reverse: ∀ xs. x ∈ (reverse xs) <-> x ∈ xs', () => {
  fc.assert(fc.property(fc.array(fc.integer()), (xs) => {
    const rxs = Arr.reverse(xs);
    Assert.eq('∀ xs. x ∈ (reverse xs) -> x ∈ xs', true, Arr.forall(rxs, (x) => Arr.contains(xs, x)));
    Assert.eq('∀ xs. x ∈ xs -> x ∈ (reverse xs)', true, Arr.forall(xs, (x) => Arr.contains(rxs, x)));
  }));
})


UnitTest.test('Check that everything in fail fails predicate and everything in pass passes predicate', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr) => {
      const predicate = (x) => x % 3 === 0;
      const output = Arr.partition(arr, predicate);
      return Arr.forall(output.fail, (x) => !predicate(x)) && Arr.forall(output.pass, predicate);
    }
  ));
})


UnitTest.test('Obj.size: inductive case', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(1, 30), fc.integer()),
      fc.asciiString(1, 30),
      fc.integer(),
      (obj, k, v) => {
        const objWithoutK = Obj.filter(obj, (x, i) => i !== k);
        Assert.eq('Adding key adds 1 to size',
          Obj.size(objWithoutK) + 1,
          Obj.size({ k: v, ...objWithoutK })
        );
      }));
  })


UnitTest.test('Obj.keys are all in input', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    const keys = Obj.keys(obj);
    return Arr.forall(keys, (k) => obj.hasOwnProperty(k));
  }));
})


UnitTest.test('Obj.filter: filter const true is identity', () => {
  fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
    Assert.eq('id', obj, Obj.filter(obj, () => true));
  }));
})


UnitTest.test('Length of interspersed = len(arr) + len(arr)-1', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.integer(),
    (arr, delimiter) => {
      const actual = Jam.intersperse(arr, delimiter);
      const expected = arr.length === 0 ? 0 : arr.length * 2 - 1;
      Assert.eq('eq', expected, actual.length);
    }
  ));
})


UnitTest.test('Every odd element matches delimiter', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.integer(),
    (arr, delimiter) => {
      const actual = Jam.intersperse(arr, delimiter);
      return Arr.forall(actual, (x, i) => i % 2 === 1 ? x === delimiter : true);
    }
  ));
})


UnitTest.test('Filtering out delimiters (assuming different type to array to avoid removing original array) should equal original', () => {
  fc.assert(fc.property(
    fc.array(fc.nat()),
    arbNegativeInteger(),
    (arr, delimiter) => {
      const actual = Jam.intersperse(arr, delimiter);
      const filtered = Arr.filter(actual, (a) => a !== delimiter);
      Assert.eq('eq', arr, filtered);
    }
  ));
})


UnitTest.test('foldl concat [ ] xs === reverse(xs)', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr: number[]) => {
      const output: number[] = Arr.foldl(arr, (b: number[], a: number) => [ a ].concat(b), [ ]);
      Assert.eq('eq', Arr.reverse(arr), output);
    }
  ));
})


UnitTest.test('foldr concat [ ] xs === xs', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    (arr: number[]) => {
      const output = Arr.foldr(arr, (b: number[], a: number) => [ a ].concat(b), [ ]);
      Assert.eq('eq', arr, output);
    }
  ));
})


UnitTest.test('foldr concat ys xs === xs ++ ys', () => {
  fc.assert(fc.property(
    fc.array(fc.integer()),
    fc.array(fc.integer()),
    (xs, ys) => {
      const output = Arr.foldr(xs, (b, a) => [ a ].concat(b), ys);
      Assert.eq('eq', xs.concat(ys), output);
    }
  ));
})