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;
              }
            )
          ))

Summary: Editing is allowed for valid numerical string inputs, confirming they pass validity checks.


unknown

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;
            })
          ))

Summary: The input field should be invalid for any value that does not match the regex pattern for digits only (`/^[0-9]*$/`).


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;
        })
      );
    })

Summary: The test ensures that the `nameTextField` validity check returns false for names starting with a decimal character.


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;
            }
          )
        ))

Summary: Setting the input value to a string matching `regExp.lnClass` of length 4 ensures `input!.checkValidity()` returns true after update.


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;
          })
        );
      })

Summary: The property checks that when a description is set in `wizardUI.inputs[1]` using regex-generated strings, the input validity is true after updating the UI.


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;
          })
        );
      })

Summary: The test checks that when a valid input matches a specified regular expression, the input field's validity state is true.


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;
        })
      );
    })

Summary: The property ensures that when a nominal frequency matching a decimal regex is set as an input, the input validity check returns 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;
        })
      );
    })

Summary: Editing operations should only occur for inputs that match the specified regular expression, ensuring validity.


it('TINY-6891: Should be able to convert to and from numbers/list numbering', () => {
    const arbitrary = Arr.map([
      fc.mapToConstant({ num: 26, build: (v) => String.fromCharCode(65 + v) }),
      fc.mapToConstant({ num: 26, build: (v) => String.fromCharCode(97 + v) }),
      fc.mapToConstant({ num: 10, build: (v) => v.toString() })
    ], (c) => fc.stringOf(c, 1, 5));

    fc.assert(fc.property(
      fc.oneof(...arbitrary),
      (start) => {
        assert.equal(
          start,
          parseStartValue(start).map(parseDetail).getOrDie(),
          'Should be initial start value'
        );
      }
    ), { endOnFailure: true });
  })


it('no initial data', () => {
      const dialogChange = DialogChanges.init({ title: '', text: '' } as LinkDialogData, { } as LinkDialogCatalog);

      fc.assert(fc.property(fc.webUrl(), fc.asciiString(), fc.asciiString(), (url, title, text) => {
        const data = Fun.constant({ url: {
          value: url,
          meta: { title, text }
        }} as LinkDialogData);
        const dataNoMeta = Fun.constant({ url: {
          value: url,
          meta: { }
        }} as LinkDialogData);

        Assert.eq('on url change should include url title and text',
          Optional.some<Partial<LinkDialogData>>({ title, text }),
          dialogChange.onChange(data, { name: 'url' }),
          tOptional()
        );

        Assert.eq('on url change should fallback to url for text',
          Optional.some<Partial<LinkDialogData>>({ title: '', text: url }),
          dialogChange.onChange(dataNoMeta, { name: 'url' }),
          tOptional()
        );
      }));
    })


it('with original data', () => {
      const dialogChange = DialogChanges.init({ title: 'orig title', text: 'orig text' } as LinkDialogData, { } as LinkDialogCatalog);
      const dialogChangeNoTitle = DialogChanges.init({ title: '', text: 'orig text' } as LinkDialogData, { } as LinkDialogCatalog);
      const dialogChangeNoText = DialogChanges.init({ title: 'orig title', text: '' } as LinkDialogData, { } as LinkDialogCatalog);

      fc.assert(fc.property(fc.webUrl(), fc.asciiString(), fc.asciiString(), (url, title, text) => {
        const data = Fun.constant({ url: {
          value: url,
          meta: { title, text }
        }} as LinkDialogData);

        Assert.eq('on url change should not try to change title and text',
          Optional.none(),
          dialogChange.onChange(data, { name: 'url' }),
          tOptional()
        );

        Assert.eq('No Title - on url change should only try to change title',
          Optional.some<Partial<LinkDialogData>>({ title }),
          dialogChangeNoTitle.onChange(data, { name: 'url' }),
          tOptional()
        );

        Assert.eq('No Text - on url change should only try to change text',
          Optional.some<Partial<LinkDialogData>>({ text }),
          dialogChangeNoText.onChange(data, { name: 'url' }),
          tOptional()
        );
      }));
    })


it('TINY-4773: multiple @ characters', () => {
    const editor = hook.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}`);
    }));
  })


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


UnitTest.test('KAssert.eqValue: success', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    KAssert.eqValue('eq', i, Result.value(i));
    KAssert.eqValue('eq', i, Result.value(i), tNumber);
    KAssert.eqValue('eq', i, Result.value(i), tNumber, tBoom());
  }));
})

Summary: `KAssert.eqValue` successfully evaluates when comparing an integer with a `Result` containing the same integer, with optional types and a function that's not executed.


UnitTest.test('KAssert.eqValue: failure', () => {
  fc.assert(fc.property(twoDifferentNumbers, ([ a, b ]) => {
    Assert.throws('should throw if numbers differ #1', () => {
      KAssert.eqValue('eq', a, Result.value(b));
    });
    Assert.throws('should throw if numbers differ #2', () => {
      KAssert.eqValue('eq', a, Result.value(b), tNumber);
    });
    Assert.throws('should throw if numbers differ #2', () => {
      KAssert.eqValue('eq', a, Result.value(b), tNumber, tBoom());
    });
  }));

  fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
    Assert.throws('should throw if error #1', () => {
      KAssert.eqValue('eq', i, Result.error(s));
    });
    Assert.throws('should throw if error #2', () => {
      KAssert.eqValue('eq', i, Result.error(s), tBoom());
    });
    Assert.throws('should throw if error #3', () => {
      KAssert.eqValue('eq', i, Result.error(s), tBoom(), tBoom());
    });
  }));
})

Summary: `KAssert.eqValue` should throw an error when comparing two different numbers or when a Result is an error rather than a value.


UnitTest.test('KAssert.eqSome: success (reflexivity)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    KAssert.eqSome('eq', i, Optional.some(i));
    KAssert.eqSome('eq', i, Optional.some(i), tNumber);
  }));
})

Summary: `KAssert.eqSome` verifies the reflexivity of equality by checking if an integer matches an `Optional.some` of itself, with and without a specified type.


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


UnitTest.test('KAssert.eqNone: failure', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    Assert.throws('some(i) != none', () => {
      KAssert.eqNone('eq', Optional.some(i));
    });
  }));
})


UnitTest.test('KAssert.eqOption: success (reflexivity)', () => {
  fc.assert(fc.property(fc.integer(), (i) => {
    KAssert.eqOptional('eq', Optional.some(i), Optional.some(i));
    KAssert.eqOptional('eq', Optional.some(i), Optional.some(i), tNumber);
  }));
  KAssert.eqOptional('eq', Optional.none(), Optional.none());
})


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


it('returns 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.deepEqual(values.length, oKeys.length);

        assert.deepEqual(Arr.forall(oKeys, (oKey) => {
          const index = parseInt(oKey, 10);
          const expected = values[index];
          return output[oKey] === expected;
        }), true);
      }
    ));
  })


it('matches corresponding tuples', () => {
    fc.assert(fc.property(
      fc.array(fc.integer()),
      fc.array(fc.integer()),
      (keys, values) => {
        if (keys.length !== values.length) {
          assert.throws(() => Zip.zipToTuples(keys, values));
        } else {
          const output = Zip.zipToTuples(keys, values);
          assert.equal(output.length, keys.length);
          assert.isTrue(Arr.forall(output, (x, i) => x.k === keys[i] && x.v === values[i]));
        }
      }
    ));
  })


it('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.never);
        assert.deepEqual(output.pass.length, 0);
        assert.deepEqual(output.fail, arr);
      }
    ));
  })

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


it('filter const true is identity', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
      assert.deepEqual(Obj.filter(obj, Fun.always), obj);
    }));
  })

Summary: Filtering an object with a function that always returns true should return the original object unchanged.


it('finds element in middle of array', () => {
    fc.assert(fc.property(fc.array(fc.nat()), arbNegativeInteger(), fc.array(fc.nat()), (prefix, element, suffix) => {
      const arr = prefix.concat([ element ]).concat(suffix);
      assertSome(Arr.indexOf(arr, element), prefix.length);
    }));
  })

Summary: `Arr.indexOf` correctly identifies the position of an element located in the middle of an array.


it('wrap then flatten array is identity', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
      assert.deepEqual(arr, Arr.flatten(Arr.pure(arr)));
    }));
  })

Summary: Flattening an array that has been wrapped using `Arr.pure` should result in the original array.


it('mapping pure then flattening array is identity', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
      assert.deepEqual(arr, Arr.flatten(Arr.map(arr, Arr.pure)));
    }));
  })

Summary: Mapping an array with `Arr.pure` and then flattening it should result in the original array.


it('flattening two lists === concat', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.array(fc.integer()), (xs, ys) => {
      assert.deepEqual(xs.concat(ys), Arr.flatten([ xs, ys ]));
    }));
  })

Summary: Flattening two integer arrays should produce the same result as concatenating them using `concat`.


describe('finds element in middle of array', () => {
    fc.assert(fc.property(fc.array(fc.nat()), arbNegativeInteger(), fc.array(fc.nat()), (prefix, element, suffix) => {
      const arr = [ ...prefix, element, ...suffix ];
      assertSome(
        Arr.findIndex(arr, (x) => x === element),
        prefix.length
      );
    }));
  })

Summary: Verification that `Arr.findIndex` returns the index of an element located in the middle of an array.


it('Element exists in singleton array of itself', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (i) => {
      assert.isTrue(Arr.exists([ i ], eqc(i)));
    }));
  })

Summary: An element should exist in a singleton array that contains only itself.


it('ys-xs contains no elements from 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));
    }));
  })


it('every member of ys-xs is in ys', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.array(fc.integer()), (xs, ys) => {
      const diff = Arr.difference(ys, xs);
      return Arr.forall(diff, (d) => Arr.contains(ys, d));
    }));
  })


it('returns true when element is in array', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), fc.array(fc.integer()), (prefix, element, suffix) => {
      const arr2 = [ ...prefix, element, ...suffix ];
      assert.isTrue(Arr.contains(arr2, element));
    }));
  })


it('creates an array of the appropriate length except for the last one', () => {
    fc.assert(fc.property(
      fc.array(fc.integer()),
      fc.nat(),
      (arr, rawChunkSize) => {
        // ensure chunkSize is at least one
        const chunkSize = rawChunkSize + 1;
        const chunks = Arr.chunk(arr, chunkSize);

        const numChunks = chunks.length;
        const firstParts = chunks.slice(0, numChunks - 1);

        for (const firstPart of firstParts) {
          assert.lengthOf(firstPart, chunkSize);
        }

        if (arr.length === 0) {
          assert.deepEqual(chunks, []);
        } else {
          assert.isAtMost(chunks[chunks.length - 1].length, chunkSize);
        }
      }
    ));
  })


it('is idempotent', () => {
    fc.assert(fc.property(
      fc.array(fc.nat()), (arr) => {
        assert.deepEqual(Arr.sort(Arr.sort(arr)), Arr.sort(arr));
      }
    ));
  })


it('returns last element when non-empty', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), (init, last) => {
      const arr = init.concat([ last ]);
      assertSome(Arr.last(arr), last);
    }));
  })


it('returns first element when nonEmpty', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), (t, h) => {
      const arr = [ h, ...t ];
      assertSome(Arr.head(arr), h);
    }));
  })


it('returns none for element of empty list', () => {
    fc.assert(fc.property(fc.integer(), (n) => {
      assertNone(Arr.get<number>([], n));
    }));
  })


it('returns some for valid index (property test)', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), fc.integer(), (array, h, t) => {
      const arr = [ h ].concat(array);
      const length = arr.push(t);
      const midIndex = Math.round(arr.length / 2);

      assertSome(Arr.get(arr, 0), h);
      assertSome(Arr.get(arr, midIndex), arr[midIndex]);
      assertSome(Arr.get(arr, length - 1), t);
    }));
  })


it('finds a value in the array', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), fc.array(fc.integer()), (prefix, i, suffix) => {
      const arr = prefix.concat([ i ]).concat(suffix);
      const pred = (x: number) => x === i;
      const result = Arr.find(arr, pred);
      assertSome(result, i);
    }));
  })


it('cannot find a nonexistent value', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
      const result = Arr.find(arr, Fun.never);
      assertNone(result);
    }));
  })


it('Arr.findMap of non-empty is first if f is Optional.some', () => {
    fc.assert(fc.property(
      fc.integer(),
      fc.array(fc.integer()),
      (head, tail) => {
        const arr = [ head, ...tail ];
        assertSome(Arr.findMap(arr, Optional.some), head);
      }
    ));
  })


it('Arr.findMap of non-empty is none if f is Optional.none', () => {
    fc.assert(fc.property(
      fc.array(fc.integer()),
      (arr) => {
        assertNone(Arr.findMap(arr, Optional.none));
      }
    ));
  })


it('Arr.findMap finds an element', () => {
    fc.assert(fc.property(
      fc.array(fc.integer()),
      fc.integer(),
      fc.integer(),
      fc.array(fc.integer()),
      (prefix, element, ret, suffix) => {
        const arr = [ ...prefix, element, ...suffix ];
        assertSome(Arr.findMap(arr, (x) => Optionals.someIf(x === element, ret)), ret);
      }
    ));
  })


it('Arr.findMap does not find an element', () => {
    fc.assert(fc.property(
      fc.array(fc.nat()),
      fc.nat(),
      (arr, ret) => {
        assertNone(Arr.findMap(arr, (x) => Optionals.someIf(x === -1, ret)));
      }
    ));
  })


it('binding an array of empty arrays with identity equals an empty array', () => {
    fc.assert(fc.property(fc.array(fc.constant<number[]>([])), (arr) => {
      assert.deepEqual(Arr.bind(arr, Fun.identity), []);
    }));
  })


it('bind (pure .) is map', () => {
    fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), (arr, j) => {
      const f = (x: number) => x + j;
      assert.deepEqual(Arr.bind(arr, Fun.compose(Arr.pure, f)), Arr.map(arr, f));
    }));
  })


it('obeys left identity law', () => {
      fc.assert(fc.property(fc.integer(), fc.integer(), (i, j) => {
        const f = (x: number) => [ x, j, x + j ];
        assert.deepEqual(Arr.bind(Arr.pure(i), f), f(i));
      }));
    })


it('obeys right identity law', () => {
      fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
        assert.deepEqual(Arr.bind(arr, Arr.pure), arr);
      }));
    })


it('is associative', () => {
      fc.assert(fc.property(fc.array(fc.integer()), fc.integer(), (arr, j) => {
        const f = (x: number) => [ x, j, x + j ];
        const g = (x: number) => [ j, x, x + j ];
        assert.deepEqual(Arr.bind(Arr.bind(arr, f), g), Arr.bind(arr, (x) => Arr.bind(f(x), g)));
      }));
    })


it('Reversing twice is identity', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (arr) => {
      assert.deepEqual(Arr.reverse(Arr.reverse(arr)), arr);
    }));
  })


it('reversing a one element array is identity', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (a) => {
      assert.deepEqual(Arr.reverse([ a ]), [ a ]);
    }));
  })


it('reverses 2 elements', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
      assert.deepEqual(Arr.reverse([ a, b ]), [ b, a ]);
    }));
  })


it('reverses 3 elements', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), fc.integer(), (a, b, c) => {
      assert.deepEqual(Arr.reverse([ a, b, c ]), [ c, b, a ]);
    }));
  })


it('every element in the input is in the output, and vice-versa', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (xs) => {
      const rxs = Arr.reverse(xs);
      assert.isTrue(Arr.forall(rxs, (x) => Arr.contains(xs, x)));
      assert.isTrue(Arr.forall(xs, (x) => Arr.contains(rxs, x)));
    }));
  })


it('cell(x).get() === x', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const cell = Cell(i);
      assert.equal(cell.get(), i);
    }));
  })


it('cell.get() === last set call', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), fc.integer(), (a, b, c) => {
      const cell = Cell(a);
      assert.equal(cell.get(), a);
      cell.set(b);
      assert.equal(cell.get(), b);
      cell.set(c);
      assert.equal(cell.get(), c);
    }));
  })


it('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: any) {
        return err.message.indexOf('nothing') > -1;
      }
    }));
  })


it('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.deepEqual(contents, []);
    }));
  })


it('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.deepEqual(folded, matched);
    }));
  })


it('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.deepEqual(contents.length, 1);
    }));
  })


it('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.deepEqual(folded, matched);
    }));
  })


it('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.deepEqual(contents.length, 2);
    }));
  })


it('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.deepEqual(folded, matched);
    }));
  })


it('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: any) {
        return err.message.indexOf('nothing') > -1;
      }
    }));
  })


it('leftTrim(whitespace + s) === leftTrim(s)', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.equal(Strings.lTrim(s), Strings.lTrim(' ' + s));
      }
    ));
  })


it('rightTrim(s + whitespace) === rightTrim(s)', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.equal(Strings.rTrim(s), Strings.rTrim(s + ' '));
      }
    ));
  })


it('trim(whitespace + s) === trim(s)', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.equal(Strings.trim(s), Strings.trim(' ' + s));
      }
    ));
  })


it('trim(s + whitespace) === trim(s)', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.equal(Strings.trim(s), Strings.trim(s + ' '));
      }
    ));
  })


it('trim(whitespace + s + whitespace) === trim(s)', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.equal(Strings.trim(s), Strings.trim(' ' + s + ' '));
      }
    ));
  })


it('StringMatch.matches(StringMatch.starts(s1), s1 + s) === true', () => {
    fc.assert(fc.property(
      fc.string(),
      fc.string(),
      (s, s1) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.starts(s1, StringMatch.caseInsensitive),
          s1 + s
        ));
      }
    ));
  })


it('StringMatch.matches(StringMatch.contains(s1), s1 + s) === true', () => {
    fc.assert(fc.property(
      fc.string(),
      fc.string(),
      (s, s1) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.contains(s1, StringMatch.caseInsensitive),
          s1 + s
        ));
      }
    ));
  })


it('StringMatch.matches(StringMatch.contains(s1), s + s1) === true', () => {
    fc.assert(fc.property(
      fc.string(),
      fc.string(),
      (s, s1) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.contains(s1, StringMatch.caseInsensitive),
          s + s1
        ));
      }
    ));
  })


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


it('StringMatch.matches(StringMatch.contains(s), s) === true', () => {
    fc.assert(fc.property(
      fc.string(),
      (s) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.contains(s, StringMatch.caseInsensitive),
          s
        ));
      }
    ));
  })


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


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


it('StringMatch.matches(StringMatch.exact(s), s) === true', () => {
    fc.assert(fc.property(
      fc.string(1, 40),
      (s) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.exact(s, StringMatch.caseInsensitive),
          s
        ));
      }
    ));
  })


it('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()
      )
    ));
  })


it('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()
      )
    ));
  })


it('StringMatch.matches(StringMatch.all(s1), *) === true', () => {
    fc.assert(fc.property(
      fc.string(),
      fc.string(),
      (s) => {
        assert.isTrue(StringMatch.matches(
          StringMatch.all(),
          s
        ));
      }
    ));
  })


it('positive range', () => {
    fc.assert(fc.property(fc.char(), fc.integer(1, 100), (c, count) => {
      const actual = Strings.repeat(c, count);
      assert.equal(actual.length, count);
      assert.equal(actual.charAt(0), c);
      assert.equal(actual.charAt(actual.length - 1), c);
    }));

    fc.assert(fc.property(fc.asciiString(5), fc.integer(1, 100), (s, count) => {
      const actual = Strings.repeat(s, count);
      assert.equal(actual.length, count * s.length);
      assert.equal(actual.indexOf(s), 0);
      assert.equal(actual.lastIndexOf(s), actual.length - s.length);
    }));
  })


it('negative range', () => {
    fc.assert(fc.property(fc.char(), fc.integer(-100, 0), (c, count) => {
      const actual = Strings.repeat(c, count);
      assert.equal(actual.length, 0);
      assert.equal(actual, '');
    }));
  })


it('removeTrailing property', () => {
    fc.assert(fc.property(
      fc.asciiString(),
      fc.asciiString(),
      (prefix, suffix) => {
        assert.equal(Strings.removeTrailing(prefix + suffix, suffix), prefix);
      }
    ));
  })


it('removeLeading removes prefix', () => {
    fc.assert(fc.property(
      fc.asciiString(),
      fc.asciiString(),
      (prefix, suffix) => {
        assert.equal(Strings.removeLeading(prefix + suffix, prefix), suffix);
      }
    ));
  })


it('ensureTrailing is identity if string already ends with suffix', () => {
    fc.assert(fc.property(
      fc.string(),
      fc.string(),
      (prefix, suffix) => {
        const s = prefix + suffix;
        assert.equal(Strings.ensureTrailing(s, suffix), s);
      }));
  })


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


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


it('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)
    ));
  })


it('A string with length 1 or larger should never be empty', () => {
    fc.assert(fc.property(fc.string(1, 40), (str) => {
      assert.isFalse(Strings.isEmpty(str));
      assert.isTrue(Strings.isNotEmpty(str));
    }));
  })


it('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.isTrue(Strings.contains(r, contents));
      }
    ));
  })


it('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.isTrue(Strings.contains(r, contents));
      }
    ));
  })


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


it('tail of the string is unchanged', () => {
    fc.assert(fc.property(fc.ascii(), fc.asciiString(30), (h, t) => {
      assert.equal(Strings.capitalize(h + t).substring(1), t);
    }));
  })


it('head is uppercase', () => {
    fc.assert(fc.property(fc.ascii(), fc.asciiString(30), (h, t) => {
      const actualH = Strings.capitalize(h + t).charAt(0);
      assert.equal(actualH, h.toUpperCase());
    }));
  })


it('value(x) = value(x)', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assertResult(Result.value<number, string>(i), Result.value<number, string>(i));
    }));
  })


it('error(x) = error(x)', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assertResult(Result.error<string, number>(i), Result.error<string, number>(i));
    }));
  })


it('value(a) != error(e)', () => {
    fc.assert(fc.property(fc.integer(), fc.string(), (a, e) => {
      assert.isFalse(tResult(tNumber, tString).eq(
        Result.value<number, string>(a),
        Result.error<number, string>(e)
      ));

      assert.isFalse(tResult(tNumber, tString).eq(
        Result.error<number, string>(e),
        Result.value<number, string>(a)
      ));
    }));
  })


it('(a = b) = (value(a) = value(b))', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
      assert.equal(tResult(tNumber, tString).eq(
        Result.value<number, string>(a),
        Result.value<number, string>(b)
      ), a === b);
    }));
  })


it('(a = b) = (error(a) = error(b))', () => {
    fc.assert(fc.property(fc.string(), fc.string(), (a, b) => {
      assert.equal(tResult(tNumber, tString).eq(
        Result.error<number, string>(a),
        Result.error<number, string>(b)
      ), a === b);
    }));
  })


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

      assert.equal(Pprint.render(Result.error(i), tResult(tString, tNumber)), `Result.error(
  ${i}
)`);
    }));
  })


it('Optionals.cat of only nones should be an empty array', () => {
    fc.assert(fc.property(
      fc.array(ArbDataTypes.arbOptionalNone()),
      (options) => {
        const output = Optionals.cat(options);
        assert.deepEqual(output, []);
      }
    ));
  })


it('Optionals.cat of only somes should have the same length', () => {
    fc.assert(fc.property(
      fc.array(ArbDataTypes.arbOptionalSome(fc.integer())),
      (options) => {
        const output = Optionals.cat(options);
        assert.lengthOf(output, options.length);
      }
    ));
  })


it('Optionals.cat of Arr.map(xs, Optional.some) should be xs', () => {
    fc.assert(fc.property(
      fc.array(fc.json()),
      (arr) => {
        const options = Arr.map(arr, Optional.some);
        const output = Optionals.cat(options);
        assert.deepEqual(output, arr);
      }
    ));
  })


it('Optionals.cat of somes and nones should have length <= original', () => {
    fc.assert(fc.property(
      fc.array(ArbDataTypes.arbOptional(fc.integer())),
      (arr) => {
        const output = Optionals.cat(arr);
        assert.isAtMost(output.length, arr.length);
      }
    ));
  })


it('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: Optional<string>[] = 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.deepEqual(output, on);
      }
    ));
  })


it('someIf(false) is none', () => {
    fc.assert(fc.property(fc.integer(), (n) => {
      assertNone(Optionals.someIf<number>(false, n));
    }));
  })


it('someIf(true) is some', () => {
    fc.assert(fc.property(fc.integer(), (n) => {
      assertSome(Optionals.someIf<number>(true, n), n);
    }));
  })


it('Single some value', () => {
    fc.assert(fc.property(fc.integer(), (n) => {
      assertOptional(Optionals.sequence([ Optional.some(n) ]), Optional.some([ n ]));
    }));
  })


it('Two some values', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), (n, m) => {
      assertOptional(Optionals.sequence<number>([ Optional.some(n), Optional.some(m) ]), Optional.some([ n, m ]));
    }));
  })


it('Array of numbers', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (n) => {
      const someNumbers = Arr.map(n, (x) => Optional.some(x));
      assertOptional(Optionals.sequence<number>(someNumbers), Optional.some(n));
    }));
  })


it('Some then none', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (n) => {
      const someNumbers = Arr.map(n, (x) => Optional.some(x));
      assertNone(Optionals.sequence<number>([ ...someNumbers, Optional.none<number>() ]));
    }));
  })


it('None then some', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (n) => {
      const someNumbers = Arr.map(n, (x) => Optional.some(x));
      assertNone(Optionals.sequence<number>([ Optional.none<number>(), ...someNumbers ]));
    }));
  })


it('all some', () => {
    fc.assert(fc.property(fc.array(fc.integer()), (n) =>
      assertOptional(Optionals.sequence<number>(Arr.map(n, (x) => Optional.some(x))), Optionals.traverse<number, number>(n, (x) => Optional.some(x)))
    ));
  })


it('flattens some(some(x)) to some(x)', () => {
    fc.assert(fc.property(fc.integer(), (n) => {
      assertOptional(Optionals.flatten(Optional.some(Optional.some<number>(n))), Optional.some(n));
    }));
  })


it('Optional.none() !== Optional.some(x)', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assert.isFalse(Optionals.equals(Optional.none(), Optional.some(i)));
    }));
  })


it('Optional.some(x) !== Optional.none()', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assert.isFalse((Optionals.equals(Optional.some(i), Optional.none())));
    }));
  })


it('Optional.some(x) === Optional.some(x)', () => {
    fc.assert(fc.property(fc.integer(), (i) => assert.isTrue(Optionals.equals(Optional.some(i), Optional.some(i)))));
  })


it('Optional.some(x) === Optional.some(x) (same ref)', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const ob = Optional.some(i);
      assert.isTrue(Optionals.equals(ob, ob));
    }));
  })


it('Optional.some(x) !== Optional.some(x + y) where y is not identity', () => {
    fc.assert(fc.property(fc.string(), fc.string(1, 40), (a, b) => {
      assert.isFalse(Optionals.equals(Optional.some(a), Optional.some(a + b)));
    }));
  })


it('some !== none, for any predicate', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), (opt1) => {
      assert.isFalse(Optionals.equals(opt1, Optional.none(), boom));
    }));
  })


it('none !== some, for any predicate', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), (opt1) => {
      assert.isFalse(Optionals.equals(Optional.none(), opt1, boom));
    }));
  })


it('Checking Optionals.equals(some(x), some(y), _, _ -> false) === false', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), arbOptionSome(fc.integer()), (opt1, opt2) => {
      assert.isFalse(Optionals.equals(opt1, opt2, Fun.never));
    }));
  })


it('Checking Optionals.equals(some(x), 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.isTrue(Optionals.equals(opt1, opt2, Fun.always));
    }));
  })


it('Checking Optionals.equals(some(x), 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) === Optionals.equals(opt1, opt2, f);
    }));
  })


it('Checking some(x).fold(die, id) === x', () => {
    fc.assert(fc.property(fc.integer(), (json) => {
      const opt = Optional.some(json);
      const actual = opt.fold(Fun.die('Should not be none!'), Fun.identity);
      assert.deepEqual(actual, json);
    }));
  })


it('Checking some(x).is(x) === true', () => {
    fc.assert(fc.property(fc.integer(), (json) => {
      const opt = Optional.some(json);
      assert.isTrue(Optionals.is(opt, json));
    }));
  })


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


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


it('Checking some(x).getOr(v) === x', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), arbOptionSome(fc.integer()), (a, b) => {
      assert.equal(Optional.some(a).getOr(b), a);
    }));
  })


it('Checking some(x).getOrThunk(_ -> v) === x', () => {
    fc.assert(fc.property(fc.integer(), fc.func(fc.integer()), (a, thunk) => {
      assert.equal(Optional.some(a).getOrThunk(thunk), a);
    }));
  })


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


it('Checking some(x).or(oSomeValue) === some(x)', () => {
    fc.assert(fc.property(fc.integer(), arbOptionSome(fc.integer()), (json, other) => {
      const output = Optional.some(json).or(other);
      assert.isTrue(Optionals.is(output, json));
    }));
  })


it('Checking some(x).orThunk(_ -> oSomeValue) === some(x)', () => {
    fc.assert(fc.property(fc.integer(), arbOptionSome(fc.integer()), (i, other) => {
      const output = Optional.some(i).orThunk(() => other);
      assert.isTrue(Optionals.is(output, i));
    }));
  })


it('Checking some(x).map(f) === some(f(x))', () => {
    fc.assert(fc.property(fc.integer(), fc.func(fc.integer()), (a, f) => {
      const opt = Optional.some(a);
      const actual = opt.map(f);
      assert.equal(actual.getOrDie(), f(a));
    }));
  })


it('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.isUndefined(actual);
      assert.equal(opt.getOrDie(), hack);
    }));
  })


it('Given f :: s -> some(b), checking some(x).bind(f) === some(b)', () => {
    fc.assert(fc.property(fc.integer(), fc.func(arbOptionSome(fc.integer())), (i, f) => {
      const actual = Optional.some(i).bind(f);
      assert.deepEqual(actual, f(i));
    }));
  })


it('Given f :: s -> none, checking some(x).bind(f) === none', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), fc.func(arbOptionNone()), (opt, f) => {
      const actual = opt.bind(f);
      assertNone(actual);
    }));
  })


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


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


it('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.isTrue(opt.exists(f));
      } else {
        assert.isFalse(opt.exists(f));
      }
    }));
  })


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


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


it('Checking some(x).forall(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.isTrue(opt.forall(f));
      } else {
        assert.isFalse(opt.forall(f));
      }
    }));
  })


it('Checking some(x).toArray equals [ x ]', () => {
    fc.assert(fc.property(fc.integer(), (json) => {
      assert.deepEqual(Optional.some(json).toArray(), [ json ]);
    }));
  })


it('Checking some(x).toString equals "some(x)"', () => {
    fc.assert(fc.property(fc.integer(), (json) => {
      assert.equal(Optional.some(json).toString(), 'some(' + json + ')');
    }));
  })


it('Checking none.fold(_ -> x, die) === x', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const actual = Optional.none<string>().fold(Fun.constant(i), Fun.die('Should not be called'));
      assert.equal(actual, i);
    }));
  })


it('Checking none.is === false', () => {
    fc.assert(fc.property(fc.integer(), (v) => {
      assert.isFalse(Optionals.is(Optional.none(), v));
    }));
  })


it('Checking none.getOr(v) === v', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assert.equal(Optional.none<number>().getOr(i), i);
    }));
  })


it('Checking none.getOrThunk(_ -> v) === v', () => {
    fc.assert(fc.property(fc.func(fc.integer()), (thunk) => {
      assert.equal(Optional.none<number>().getOrThunk(thunk), thunk());
    }));
  })


it('Checking none.getOrDie() always throws', () => {
  // Require non empty string of msg falsiness gets in the way.
    fc.assert(fc.property(fc.string(1, 40), (s) => {
      assert.throws(() => {
        Optional.none().getOrDie(s);
      });
    }));
  })


it('Checking none.or(oSomeValue) === oSomeValue', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const output = Optional.none().or(Optional.some(i));
      assert.isTrue(Optionals.is(output, i));
    }));
  })


it('Checking none.orThunk(_ -> v) === v', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const output = Optional.none().orThunk(() => Optional.some(i));
      assert.isTrue(Optionals.is(output, i));
    }));
  })


it('none is not some', () => {
    assert.isFalse(Optional.none().isSome());
    fc.assert(fc.property(fc.anything(), (x) => {
      assert.isTrue(Optional.some(x).isSome());
    }));
  })


it('Optional.isNone', () => {
    assert.isTrue(Optional.none().isNone());
    fc.assert(fc.property(fc.anything(), (x) => {
      assert.isFalse(Optional.some(x).isNone());
    }));
  })


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


it('Checking some(x).filter(_ -> false) === none', () => {
    fc.assert(fc.property(arbOptionSome(fc.integer()), (opt) => {
      assertNone(opt.filter(Fun.never));
    }));
  })


it('Checking some(x).filter(_ -> true) === some(x)', () => {
    fc.assert(fc.property(fc.integer(), (x) => {
      assertSome(some(x).filter(Fun.always), x);
    }));
  })


it('Checking some(x).filter(f) === some(x) iff. f(x)', () => {
    fc.assert(fc.property(fc.integer(), fc.func(fc.boolean()), (i, f) => {
      if (f(i)) {
        assertSome(some(i).filter(f), i);
      } else {
        assertNone(some(i).filter(f));
      }
    }));
  })


it('fails for none vs some', () => {
    fc.assert(fc.property(fc.nat(), (n) => {
      assert.throw(() => {
        assertOptional(Optional.none(), Optional.some(n));
      });
    }));
  })


it('fails for some vs none', () => {
    fc.assert(fc.property(fc.nat(), (n) => {
      assert.throw(() => {
        assertOptional(Optional.some(n), Optional.none());
      });
    }));
  })


it('fails when some values are different', () => {
    fc.assert(fc.property(fc.nat(), (n) => {
      assert.throw(() => {
        assertOptional(Optional.some(n), Optional.some(n + 1));
      });
    }));
  })


it('passes for identical somes', () => {
    fc.assert(fc.property(fc.nat(), (n) => {
      assertOptional(Optional.some(n), Optional.some(n));
    }));
  })


it('passes for identical some arrays', () => {
    fc.assert(fc.property(fc.array(fc.nat()), (n) => {
      assertOptional(Optional.some(n), Optional.some(n));
    }));
  })


it('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),
      (dict, parts, field, newValue) => {
        const created = Resolve.forge(parts, dict);
        created[field] = newValue;
        const resolved = Resolve.path(parts.concat([ field ]), dict);
        assert.deepEqual(resolved, newValue);
      }
    ), { endOnFailure: true });
  })


it('map id obj = obj', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.integer()), (obj) => {
      const actual = Obj.map(obj, Fun.identity);
      assert.deepEqual(actual, obj);
    }));
  })


it('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);
    }));
  })


it('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.deepEqual(output, obj);
    }));
  })


it('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.deepEqual(Arr.sort(aKeys), Arr.sort(keys));
      assert.deepEqual(Arr.sort(aValues), Arr.sort(values));
    }));
  })


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


it('the value found by find always passes predicate', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(), fc.json()),
      fc.func(fc.boolean()),
      (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, (v) => {
          return pred(v);
        });
        return value.fold(() => {
          const values = Obj.values(obj);
          return !Arr.exists(values, (v) => {
            return pred(v);
          });
        }, (v) => {
          return pred(v);
        });
      }
    ));
  })


it('If predicate is always false, then find is always none', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(), fc.json()),
      (obj) => {
        const value = Obj.find(obj, Fun.never);
        return value.isNone();
      }
    ));
  })


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


it('If predicate is always true, then value is always the some(first), or none if dict is empty', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(), fc.json()),
      (obj) => {
        const value = Obj.find(obj, Fun.always);
        // No order is specified, so we cannot know what "first" is
        return Obj.keys(obj).length === 0 ? value.isNone() : true;
      }
    ));
  })


it('Each + set should equal the same object', () => {
    fc.assert(fc.property(
      fc.dictionary(fc.asciiString(), fc.json()),
      (obj) => {
        const values: Record<string, string> = {};
        const output = Obj.each(obj, (x, i) => {
          values[i] = x;
        });
        assert.deepEqual(values, obj);
        assert.isUndefined(output);
      }
    ));
  })


it('Merged with identity on left', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.merge({}, obj), obj);
    }));
  })


it('Merged with identity on right', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.merge(obj, {}), obj);
    }));
  })


it('Merged with itself is itself', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.merge(obj, obj), obj);
    }));
  })


it('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()), (a, b, c) => {
      const one = Merger.merge(a, Merger.merge(b, c));
      const other = Merger.merge(Merger.merge(a, b), c);
      assert.deepEqual(other, one);
    }));
  })


it('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()), (a, b) => {
      const output = Merger.merge(a, b);
      const keys = Obj.keys(b);
      const oKeys = Obj.keys(output);
      return Arr.forall(keys, (k) => {
        return Arr.contains(oKeys, k);
      });
    }));
  })


it('Deep-merged with identity on left', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.deepMerge({}, obj), obj);
    }));
  })


it('Deep-merged with identity on right', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.deepMerge(obj, {}), obj);
    }));
  })


it('Deep-merged with itself is itself', () => {
    fc.assert(fc.property(fc.dictionary(fc.asciiString(), fc.json()), (obj) => {
      assert.deepEqual(Merger.deepMerge(obj, obj), obj);
    }));
  })


it('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()), (a, b, c) => {
      const one = Merger.merge(a, Merger.merge(b, c));
      const other = Merger.merge(Merger.merge(a, b), c);
      assert.deepEqual(other, one);
    }));
  })


it('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()), (a, b) => {
      const output = Merger.deepMerge(a, b);
      const keys = Obj.keys(b);
      const oKeys = Obj.keys(output);
      return Arr.forall(keys, (k) => {
        return Arr.contains(oKeys, k);
      });
    }));
  })


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


it('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.always);
        assert.lengthOf(Obj.keys(output.f), 0);
        assert.lengthOf(Obj.keys(output.t), Obj.keys(obj).length);
        return true;
      }
    ));
  })


it('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: number) => x % 2 === 0;
        const output = Obj.bifilter(obj, predicate);

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

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

        assert.isFalse(Arr.exists(falseKeys, matches));
        assert.isTrue(Arr.forall(trueKeys, matches));
      }
    ));
  })


it('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;
      }
    ));
  })


it('0 has no effect', () => {
    fc.assert(fc.property(fc.nat(), fc.nat(), (value, delta) => {
      const actual = Num.cycleBy(value, 0, value, value + delta);
      assert.equal(actual, value);
    }));
  })


it('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.equal(actual, max);
    }));
  })


it('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.equal(Num.clamp(med, low, high), med);
        assert.equal(Num.clamp(med, low, high), med);
        assert.equal(Num.clamp(high, low, med), med);
      }));
  })


it('mirrors bind', () => {
      const arbInner = fc.tuple(fc.boolean(), fc.string()).map(([ isJust, value ]) => {
        if (isJust) {
          return Maybes.just(value);
        } else {
          return Maybes.nothing<string>();
        }
      });
      const arbNested = fc.tuple(fc.boolean(), arbInner).map(([ isJust, value ]) => {
        if (isJust) {
          return Maybes.just(value);
        } else {
          return Maybes.nothing<Maybe<string>>();
        }
      });
      fc.assert(fc.property(arbNested, (maybe) => {
        const flattened = Maybes.flatten(maybe);
        const bound = Maybes.bind<Maybe<string>, string>(Fun.identity)(maybe);
        assert.deepEqual(flattened, bound);
      }));
    })


it('allows creating "Just" maybes', () => {
    fc.assert(fc.property(fc.anything(), (thing) => {
      const item = Maybes.just(thing);
      assert.isTrue(Maybes.isJust(item));
      assert.isFalse(Maybes.isNothing(item));
    }));
  })


it('allows folding on "Just" maybes', () => {
    // Simple value, complex assertions
    Fun.pipe(
      Maybes.just('test'),
      Maybes.fold(
        Fun.die('Should call other branch'),
        (test) => {
          assert.equal(test, 'test');
          return 'other-test';
        }
      ),
      (result) => assert.equal(result, 'other-test')
    );

    // Complex values, simple assertions
    fc.assert(fc.property(fc.anything(), (thing) => {
      Fun.pipe(
        Maybes.just(thing),
        Maybes.fold(Fun.die('Should call other branch'), Fun.noop)
      );
    }));
  })


it('gets the value on "Just"', () => {
      fc.assert(fc.property(fc.string(), (s) => {
        const val = Fun.pipe(
          Maybes.just(s),
          Maybes.getOrDie
        );
        assert.equal(val, s);
      }));
    })


it('never turns Just to Nothing (or vice versa)', () => {
    const mapper = Maybes.map(Fun.identity);

    const nothing = mapper(Maybes.nothing());
    assert.isTrue(Maybes.isNothing(nothing));

    fc.assert(fc.property(fc.anything(), (thing) => {
      const just = mapper(Maybes.just(thing));
      assert.isTrue(Maybes.isJust(just));
    }));
  })


it('is always false for "Nothing"', () => {
      fc.assert(fc.property(fc.anything(), (thing) => {
        const matches = Fun.pipe(
          Maybes.nothing(),
          Maybes.is(thing)
        );

        assert.isFalse(matches);
      }));
    })


it('is always false for "Nothing" with explicit comparator', () => {
      fc.assert(fc.property(fc.anything(), (thing) => {
        const matches = Fun.pipe(
          Maybes.nothing(),
          Maybes.is(thing, Fun.die('should not be called'))
        );

        assert.isFalse(matches);
      }));
    })


it('is correct for "Just"', () => {
      fc.assert(fc.property(fc.string(), (thing) => {
        const matches = Fun.pipe(
          Maybes.just(thing),
          Maybes.is(thing)
        );

        assert.isTrue(matches);
      }));

      fc.assert(fc.property(fc.string(), (thing) => {
        const matches = Fun.pipe(
          Maybes.just(thing),
          Maybes.is(thing + 'foo')
        );

        assert.isFalse(matches);
      }));
    })


it('is correct for "Just" with an explicit comparator', () => {

      // Just an example data type that doesn't have a simple equality
      const thunkEq = (a: () => string, b: () => string): boolean => a() === b();

      fc.assert(fc.property(fc.string(), (thing) => {
        const matches = Fun.pipe(
          Maybes.just(Fun.constant(thing)),
          Maybes.is(Fun.constant(thing), thunkEq)
        );

        assert.isTrue(matches);
      }));

      fc.assert(fc.property(fc.string(), (thing) => {
        const matches = Fun.pipe(
          Maybes.just(Fun.constant(thing)),
          Maybes.is(() => thing + 'foo', thunkEq)
        );

        assert.isFalse(matches);
      }));
    })


it('is false for one "Nothing"', () => {
      fc.assert(fc.property(fc.anything(), (thing) => {
        assert.isFalse(Maybes.equals(Maybes.nothing(), Maybes.just(thing)));
        assert.isFalse(Maybes.equals(Maybes.just(thing), Maybes.nothing()));
      }));
    })


it('compares normally for two "Just"s', () => {
      const different = fc.tuple(fc.anything(), fc.anything()).filter(([ lhs, rhs ]) => lhs !== rhs);
      fc.assert(fc.property(different, ([ lhs, rhs ]) => {
        assert.isFalse(Maybes.equals(Maybes.just(lhs), Maybes.just(rhs)));
      }));

      const theSame = fc.string().map((x) => [ x, x ]);
      fc.assert(fc.property(theSame, ([ lhs, rhs ]) => {
        assert.isTrue(Maybes.equals(Maybes.just(lhs), Maybes.just(rhs)));
      }));
    })


it('is false for one "Nothing"', () => {
      fc.assert(fc.property(fc.anything(), (thing) => {
        assert.isFalse(Maybes.equals(Maybes.nothing(), Maybes.just(thing), unusedComparator));
        assert.isFalse(Maybes.equals(Maybes.just(thing), Maybes.nothing(), unusedComparator));
      }));
    })


it('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.deepEqual(other, value);
    }));
  })


it('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.deepEqual(h(x), f(g(x)));
    }));
  })


it('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.deepEqual(h(x), f(g(x)));
    }));
  })


it('Check constant :: constant(a)() === a', () => {
    fc.assert(fc.property(fc.json(), (json) => {
      assert.deepEqual(Fun.constant(json)(), json);
    }));
  })


it('Check identity :: identity(a) === a', () => {
    fc.assert(fc.property(fc.json(), (json) => {
      assert.deepEqual(Fun.identity(json), json);
    }));
  })


it('Check always :: f(x) === true', () => {
    fc.assert(fc.property(fc.json(), (json) => {
      assert.isTrue(Fun.always(json));
    }));
  })


it('Check never :: f(x) === false', () => {
    fc.assert(fc.property(fc.json(), (json) => {
      assert.isFalse(Fun.never(json));
    }));
  })


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

      assert.deepEqual([ a, b, c, d ], Fun.curry(f, a)(b, c, d));
      assert.deepEqual([ a, b, c, d ], Fun.curry(f, a, b)(c, d));
      assert.deepEqual([ a, b, c, d ], Fun.curry(f, a, b, c)(d));
      assert.deepEqual([ a, b, c, d ], Fun.curry(f, a, b, c, d)());
    }));
  })


it('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.deepEqual(!g(x), f(x));
    }));
  })


it('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.deepEqual(g(x), f(x));
    }));
  })


it('Check apply :: apply(constant(a)) === a', () => {
    fc.assert(fc.property(fc.json(), (x) => {
      assert.deepEqual(Fun.apply(Fun.constant(x)), x);
    }));
  })


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

      assert.isUndefined(output);
      assert.deepEqual(hack, x);
    }));
  })


it('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())),
      (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;
      }
    ));
  })


it('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())),
      (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;
      }
    ));
  })


it('Check that the total number of values and errors matches the input size', () => {
    fc.assert(fc.property(
      fc.array(arbResult(fc.integer(), fc.string())),
      (results) => {
        const actual = Results.partition(results);
        assert.equal(actual.errors.length + actual.values.length, results.length);
      }
    ));
  })


it('Check that two errors always equal comparison.bothErrors', () => {
    fc.assert(fc.property(
      arbResultError(fc.integer()),
      arbResultError(fc.integer()),
      (r1, r2) => {
        const comparison = Results.compare(r1, r2);
        return comparison.match({
          bothErrors: Fun.always,
          firstError: Fun.never,
          secondError: Fun.never,
          bothValues: Fun.never
        });
      }
    ));
  })


it('Check that error, value always equal comparison.firstError', () => {
    fc.assert(fc.property(
      arbResultError(fc.integer()),
      arbResultValue(fc.string()),
      (r1, r2) => {
        const comparison = Results.compare(r1, r2);
        return comparison.match({
          bothErrors: Fun.never,
          firstError: Fun.always,
          secondError: Fun.never,
          bothValues: Fun.never
        });
      }
    ));
  })


it('Check that value, error always equal comparison.secondError', () => {
    fc.assert(fc.property(
      arbResultValue(fc.integer()),
      arbResultError(fc.string()),
      (r1, r2) => {
        const comparison = Results.compare(r1, r2);
        return comparison.match({
          bothErrors: Fun.never,
          firstError: Fun.never,
          secondError: Fun.always,
          bothValues: Fun.never
        });
      }
    ));
  })


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


it('Results.unite', () => {
    fc.assert(fc.property(fc.integer(), (a) => {
      assert.equal(Results.unite(Result.error<number, number>(a)), a);
      assert.equal(Results.unite(Result.value<number, number>(a)), a);
    }));
  })


it('Checking value.is(value.getOrDie()) === true', () => {
    fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
      assert.isTrue(Results.is(res, res.getOrDie()));
    }));
  })


it('Checking value.isValue === true', () => {
    fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
      assert.isTrue(res.isValue());
    }));
  })


it('Checking value.isError === false', () => {
    fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
      assert.isFalse(res.isError());
    }));
  })


it('Checking value.getOr(v) === value.value', () => {
    fc.assert(fc.property(fc.integer(), (a) => {
      assert.equal(Result.value(a).getOr(a), a);
    }));
  })


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


it('Checking value.or(oValue) === value', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
      assertResult(Result.value(a).or(Result.value(b)), Result.value(a));
    }));
  })


it('Checking error.or(value) === value', () => {
    fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
      assertResult(Result.error(a).or(Result.value(b)), Result.value(b));
    }));
  })


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


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


it('Checking value.map(f) === f(value.getOrDie())', () => {
    fc.assert(fc.property(arbResultValue(fc.integer()), fc.func(fc.json()), (res, f) => {
      assert.equal(f(res.getOrDie()), res.map(f).getOrDie());
    }));
  })


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


it('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.equal(f(res.getOrDie()).getOrDie(), res.bind(f).getOrDie());
    }));
  })


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


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


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


it('Checking value.toOptional is always Optional.some(value.getOrDie())', () => {
    fc.assert(fc.property(arbResultValue(fc.integer()), (res) => {
      assert.equal(res.toOptional().getOrDie(), res.getOrDie());
    }));
  })


it('error.is === false', () => {
    fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
      assert.isFalse(Results.is(Result.error(s), i));
    }));
  })


it('error.isValue === false', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
      assert.isFalse(res.isValue());
    }));
  })


it('error.isError === true', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
      assert.isTrue(res.isError());
    }));
  })


it('error.getOr(v) === v', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), fc.json(), (res, json) => {
      assert.deepEqual(res.getOr(json), json);
    }));
  })


it('error.getOrDie() always throws', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
      assert.throws(() => {
        res.getOrDie();
      });
    }));
  })


it('error.or(oValue) === oValue', () => {
    fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
      assertResult(Result.error<number, string>(s).or(Result.value(i)), Result.value(i));
    }));
  })


it('error.orThunk(_ -> v) === v', () => {
    fc.assert(fc.property(fc.integer(), fc.string(), (i, s) => {
      assertResult(Result.error<number, string>(s).orThunk(() => Result.value(i)), Result.value(i));
    }));
  })


it('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.deepEqual(actual, json);
    }));
  })


it('error.map returns an error', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assertResult(Result.error(i).map(Fun.die('should not be called')), Result.error(i));
    }));
  })


it('error.mapError(f) === f(error)', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      const f = (x: number) => x % 3;
      assertResult(Result.error(i).mapError(f), Result.error(f(i)));
    }));
  })


it('error.each returns undefined', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
      const actual = res.each(Fun.die('should not be called'));
      assert.isUndefined(actual);
    }));
  })


it('Given f :: s -> RV, 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.deepEqual(getErrorOrDie(actual), getErrorOrDie(res));
    }));
  })


it('Given f :: s -> RE, error.bind(f) === error', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), fc.func(arbResultError(fc.integer())), (res, f) => {
      const actual = res.bind(f);
      assert.deepEqual(getErrorOrDie(actual), getErrorOrDie(res));
    }));
  })


it('error.forall === true', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), fc.func(fc.boolean()), (res, f) => {
      assert.isTrue(res.forall(f));
    }));
  })


it('error.exists === false', () => {
    fc.assert(fc.property(fc.integer(), (i) => {
      assert.isFalse(Result.error<unknown, number>(i).exists(Fun.die('should not be called')));
    }));
  })


it('error.toOptional is always none', () => {
    fc.assert(fc.property(arbResultError(fc.integer()), (res) => {
      assertNone(res.toOptional());
    }));
  })


it('should not generate identical IDs', () => {
    const arbId = fc.string(1, 30).map(Id.generate);
    fc.assert(fc.property(arbId, arbId, (id1, id2) => id1 !== id2));
  })


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


it('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();
      });
    }))))


it('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();
      });
    }))))


it('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();
    });
  }))))


it('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();
    });
  }))))


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


it('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();
    });
  }))))


it('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();
    });
  }))))


it('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();
    });
  }))))


it('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();
    });
  }))))


it('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();
    });
  }))))


it('value mapResult', () => {
    const f = (x: number) => 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();
      });
    })));
  })


it('error mapResult', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    FutureResult.error(i).mapResult(Fun.die('should not be called')).get((ii) => {
      eqAsync('eq', Result.error(i), ii, reject, tResult());
      resolve();
    });
  }))))


it('value mapError', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    FutureResult.value(i).mapError(Fun.die('should not be called')).get((ii) => {
      eqAsync('eq', Result.value(i), ii, reject, tResult());
      resolve();
    });
  }))))


it('err mapError', () => {
    const f = (x: number) => 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();
      });
    })));
  })


it('value bindFuture value', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    const f = (x: number) => 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();
    });
  }))))


it('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();
    });
  }))))


it('error bindFuture', () => fc.assert(fc.asyncProperty(fc.integer(), (i) => new Promise((resolve, reject) => {
    FutureResult.error(i).bindFuture<never>(Fun.die('should not be called')).get((actual) => {
      eqAsync('bind result', Result.error(i), actual, reject, tResult(tNumber));
      resolve();
    });
  }))))


it('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();
    })))))


it('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();
    })))))


it('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();
    })))))


it('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();
    })))))


it('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();
    })))))


it('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();
    }))))
  )


it('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();
      }))))
  )


it('TINY-9226: basic property - no outside constraints', () => {
        fc.assert(
          fc.property(arbBounds, arbBounds, (original, constraint) => {
            const actual = Boxes.constrain(original, constraint);
            assert.isAtLeast(actual.x, constraint.x);
            assert.isAtMost(actual.right, constraint.right);
            assert.isAtLeast(actual.y, constraint.y);
            assert.isAtMost(actual.bottom, constraint.bottom);
          })
        );
      })


it('TINY-9226: no constrants', () => {
        fc.assert(
          fc.property(arbBounds, (original) => {
            const actual = Boxes.constrainByMany(original, [ ]);
            assert.deepEqual(actual, original);
          })
        );
      })


it('TINY-9226: basic property', () => {
        fc.assert(
          fc.property(arbBounds, arbBounds, fc.array(arbBounds), (original, first, rest) => {
            const constraints = [ first ].concat(rest);
            const all = [ original ].concat(constraints);
            const actual = Boxes.constrainByMany(original, constraints);
            const optLargestLeft = Arr.last(sortNumbers(Arr.map(all, (c) => c.x)));
            const optLargestTop = Arr.last(sortNumbers(Arr.map(all, (c) => c.y)));
            const optSmallestRight = Arr.head(sortNumbers(Arr.map(all, (c) => c.right)));
            const optSmallestBottom = Arr.head(sortNumbers(Arr.map(all, (c) => c.bottom)));

            const assertOpt = (label: string, optValue: Optional<number>, actualValue: number): void => {
              optValue.fold(
                () => assert.fail('There were no candidates. Actual value: ' + actualValue),
                (v) => assert.equal(
                  actualValue,
                  v,
                  `Property ${label}. Expected: ${v}, Actual: ${actualValue}, \n
                   All: ${JSON.stringify(all, null, 2)}`
                )
              );
            };

            assertOpt('left', optLargestLeft, actual.x);
            assertOpt('right', optSmallestRight, actual.right);
            assertOpt('top', optLargestTop, actual.y);
            assertOpt('bottom', optSmallestBottom, actual.bottom);
          })
        );
      })


describe('transaction executors', () => {
  it('queries with `splits: inline` returns only non-parents', async () => {
    await fc.assert(
      fc.asyncProperty(
        arbs.makeTransactionArray({
          splitFreq: 2,
          minLength: 2,
          maxLength: 20,
        }),
        async arr => {
          await insertTransactions(arr);

          const { data } = await runQuery(
            q('transactions')
              .filter({ amount: { $lt: 0 } })
              .select('*')
              .options({ splits: 'inline' })
              .serialize(),
          );

          expect(data.filter(t => t.is_parent).length).toBe(0);
          expect(data.filter(t => t.tombstone).length).toBe(0);

          const { data: defaultData } = await runQuery(
            q('transactions')
              .filter({ amount: { $lt: 0 } })
              .select('*')
              .serialize(),
          );

          // inline should be the default
          expect(defaultData).toEqual(data);
        },
      ),
      { numRuns: 50 },
    );
  });

  it('queries with `splits: none` returns only parents', async () => {
    await fc.assert(
      fc.asyncProperty(
        arbs.makeTransactionArray({
          splitFreq: 2,
          minLength: 2,
          maxLength: 8,
        }),
        async arr => {
          await insertTransactions(arr);

          const { data } = await runQuery(
            q('transactions')
              .filter({ amount: { $lt: 0 } })
              .select('*')
              .options({ splits: 'none' })
              .serialize(),
          );

          expect(data.filter(t => t.is_child).length).toBe(0);
        },
      ),
      { numRuns: 50 },
    );
  });

  it('aggregate queries work with `splits: grouped`', async () => {
    const payeeIds = ['payee1', 'payee2', 'payee3', 'payee4', 'payee5'];

    await fc.assert(
      fc
        .asyncProperty(
          arbs.makeTransactionArray({ splitFreq: 2, payeeIds, maxLength: 100 }),
          async arr => {
            await insertTransactions(arr, payeeIds);

            const aggQuery = q('transactions')
              .filter({
                $or: [{ amount: { $lt: -5 } }, { amount: { $gt: -2 } }],
                'payee.name': { $gt: '' },
              })
              .options({ splits: 'grouped' })
              .calculate({ $sum: '$amount' });

            const { data } = await runQuery(aggQuery.serialize());

            const sum = aliveTransactions(arr).reduce((sum, trans) => {
              const amount = trans.amount || 0;
              const matched =
                (amount < -5 || amount > -2) && trans.payee != null;
              if (!trans.tombstone && !trans.is_parent && matched) {
                return sum + amount;
              }
              return sum;
            }, 0);

            expect(data).toBe(sum);
          },
        )
        .beforeEach(() => {
          setClock(null);
          setSyncingMode('import');
          return db.execQuery(`
            DELETE FROM transactions;
            DELETE FROM payees;
            DELETE FROM payee_mapping;
          `);
        }),
    );
  });

  function runTest(makeQuery) {
    const payeeIds = ['payee1', 'payee2', 'payee3', 'payee4', 'payee5'];

    async function check(arr) {
      const orderFields = ['payee.name', 'amount', 'id'];

      // Insert transactions and get a list of all the alive
      // ones to make it easier to check the data later (don't
      // have to always be filtering out dead ones)
      await insertTransactions(arr, payeeIds);
      const allTransactions = aliveTransactions(arr);

      // Query time
      const { query, expectedIds, expectedMatchedIds } = makeQuery(arr);

      // First to a query without order to make sure the default
      // order works
      const { data: defaultOrderData } = await runQuery(query.serialize());
      expectTransactionOrder(defaultOrderData);
      expect(new Set(defaultOrderData.map(t => t.id))).toEqual(expectedIds);

      // Now do the full test, and add a custom order to make
      // sure that doesn't effect anything
      const orderedQuery = query.orderBy(orderFields);
      const { data } = await runQuery(orderedQuery.serialize());
      expect(new Set(data.map(t => t.id))).toEqual(expectedIds);

      // Validate paging and ordering
      await expectPagedData(orderedQuery, arr.length, data);
      expectTransactionOrder(data, orderFields);

      const matchedIds = new Set();

      // Check that all the subtransactions were returned
      for (const trans of data) {
        expect(trans.tombstone).toBe(false);

        if (expectedMatchedIds) {
          if (!trans._unmatched) {
            expect(expectedMatchedIds.has(trans.id)).toBe(true);
            matchedIds.add(trans.id);
          } else {
            expect(expectedMatchedIds.has(trans.id)).not.toBe(true);
          }
        }

        if (trans.is_parent) {
          // Parent transactions should never have a category
          expect(trans.category).toBe(null);

          expect(trans.subtransactions.length).toBe(
            allTransactions.filter(t => t.parent_id === trans.id).length,
          );

          // Subtransactions should be ordered as well
          expectTransactionOrder(trans.subtransactions, orderFields);

          trans.subtransactions.forEach(subtrans => {
            expect(subtrans.tombstone).toBe(false);

            if (expectedMatchedIds) {
              if (!subtrans._unmatched) {
                expect(expectedMatchedIds.has(subtrans.id)).toBe(true);
                matchedIds.add(subtrans.id);
              } else {
                expect(expectedMatchedIds.has(subtrans.id)).not.toBe(true);
              }
            }
          });
        }
      }

      if (expectedMatchedIds) {
        // Check that transactions that should be matched are
        // marked as such
        expect(matchedIds).toEqual(expectedMatchedIds);
      }
    }

    return fc.assert(
      fc
        .asyncProperty(
          arbs.makeTransactionArray({
            splitFreq: 0.1,
            payeeIds,
            maxLength: 100,
          }),
          check,
        )
        .beforeEach(() => {
          setClock(null);
          setSyncingMode('import');
          return db.execQuery(`
            DELETE FROM transactions;
            DELETE FROM payees;
            DELETE FROM payee_mapping;
          `);
        }),
      { numRuns: 300 },
    );
  }

  it('queries the correct transactions without filters', async () => {
    return runTest(arr => {
      const expectedIds = new Set(
        arr.filter(t => !t.tombstone && !t.is_child).map(t => t.id),
      );

      // Even though we're applying some filters, these are always
      // guaranteed to return the full split transaction so they
      // should take the optimized path
      const happyQuery = q('transactions')
        .filter({
          date: { $gt: '2017-01-01' },
        })
        .options({ splits: 'grouped' })
        .select(['*', 'payee.name']);

      // Make sure it's actually taking the happy path
      expect(isHappyPathQuery(happyQuery.serialize())).toBe(true);

      return {
        expectedIds,
        query: happyQuery,
      };
    });
  });

  it(`queries the correct transactions with a filter`, async () => {
    return runTest(arr => {
      const expectedIds = new Set();

      // let parents = toGroup(
      //   arr.filter(t => t.is_parent),
      //   new Map(Object.entries(groupById(arr.filter(t => t.parent_id))))
      // );

      const parents = groupById(arr.filter(t => t.is_parent && !t.tombstone));
      const matched = new Set();

      // Pick out some ids to query
      let ids = arr.reduce((ids, trans, idx) => {
        if (idx % 2 === 0) {
          const amount = trans.amount == null ? 0 : trans.amount;
          const matches = (amount < -2 || amount > -1) && trans.payee > '';

          if (matches && isAlive(trans, parents)) {
            expectedIds.add(trans.parent_id || trans.id);
            matched.add(trans.id);
          }

          ids.push(trans.id);
        }

        return ids;
      }, []);

      // Because why not? It should deduplicate them
      ids = repeat(ids, 100);

      const unhappyQuery = q('transactions')
        .filter({
          id: [{ $oneof: ids }],
          payee: { $gt: '' },
          $or: [{ amount: { $lt: -2 } }, { amount: { $gt: -1 } }],
        })
        .options({ splits: 'grouped' })
        .select(['*', 'payee.name'])
        // Using this because we want `payee` to have ids for the above
        // filter regardless if it points to a dead one or not
        .withoutValidatedRefs();

      expect(isHappyPathQuery(unhappyQuery.serialize())).toBe(false);

      return {
        expectedIds,
        expectedMatchedIds: matched,
        query: unhappyQuery,
      };
    });
  });
})

insertTransactions aliveTransactions expectTransactionOrder expectPagedData isAlive repeat

Summary:
The test suite validates that transaction queries handle different `splits` options correctly by checking:

- Queries with `splits: inline` return only non-parent, non-tombstone transactions.
- Queries with `splits: none` return only parent transactions.
- Aggregate queries with `splits: grouped` calculate the correct sum of amounts for specific conditions.
- A separate `runTest` function confirms transaction queries return the expected results with and without filters, ensuring ordered and paged data retrieval aligns with expectations.


Summary: When the query is executed with `splits: inline`, it returns only non-parent transactions that are not marked as tombstones, ensuring consistency with default query results. Using `splits: none` ensures that only parent transactions are returned. Aggregate queries with `splits: grouped` accurately calculate sums of non-parent, non-tombstone transactions matching specific criteria. Transaction queries without filters ensure all non-tombstone, non-child transactions are fetched correctly. When applying filters, transactions are correctly identified, deduplicated, and returned according to specific conditions.


test('normalizeUndefined', () => {
  fc.assert(
    fc.property(fc.constantFrom(null, undefined), v => {
      expect(normalizeUndefined(v)).toBe(null);
    }),
  );
  fc.assert(
    fc.property(fc.oneof(fc.boolean(), fc.double(), fc.string()), b => {
      expect(normalizeUndefined(b)).toBe(b);
    }),
  );
})


test('compareValues', () => {
  // null and undefined are equal to each other
  fc.assert(
    fc.property(
      fc.constantFrom(null, undefined),
      fc.constantFrom(null, undefined),
      (v1, v2) => {
        expect(compareValues(v1, v2)).toBe(0);
      },
    ),
  );

  // null and undefined are less than any other value
  fc.assert(
    fc.property(
      fc.constantFrom(null, undefined),
      fc.oneof(fc.boolean(), fc.double(), fc.fullUnicodeString()),
      (v1, v2) => {
        expect(compareValues(v1, v2)).lessThan(0);
        expect(compareValues(v2, v1)).greaterThan(0);
      },
    ),
  );

  // boolean
  fc.assert(
    fc.property(fc.boolean(), fc.boolean(), (b1, b2) => {
      expect(compareValues(b1, b2)).toBe(b1 === b2 ? 0 : b1 ? 1 : -1);
    }),
  );
  fc.assert(
    fc.property(
      fc.boolean(),
      fc.oneof(fc.double(), fc.fullUnicodeString()),
      (b, v) => {
        expect(() => compareValues(b, v)).toThrow('expected boolean');
      },
    ),
  );

  // number
  fc.assert(
    fc.property(fc.double(), fc.double(), (n1, n2) => {
      expect(compareValues(n1, n2)).toBe(n1 - n2);
    }),
  );
  fc.assert(
    fc.property(
      fc.double(),
      fc.oneof(fc.boolean(), fc.fullUnicodeString()),
      (n, v) => {
        expect(() => compareValues(n, v)).toThrow('expected number');
      },
    ),
  );

  // string
  fc.assert(
    fc.property(fc.fullUnicodeString(), fc.fullUnicodeString(), (s1, s2) => {
      expect(compareValues(s1, s2)).toBe(compareUTF8(s1, s2));
    }),
  );
  fc.assert(
    fc.property(
      fc.fullUnicodeString(),
      fc.oneof(fc.boolean(), fc.double()),
      (s, v) => {
        expect(() => compareValues(s, v)).toThrow('expected string');
      },
    ),
  );
})