Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add each-assertions. #408

Merged
merged 17 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2011-2021 the native web. All rights reserved.
Copyright (c) 2011-2022 the native web. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,20 @@ assert.that(actual).is.containingAnyOf([ 'native', 'web' ]);
assert.that(actual).is.containingAllOf([ 'native', 'web' ]);
```

## Asserting on the contents of arrays, sets, and maps

It is possible to run the same assertion on all values in an array, set, or map:

```javascript
assert.that(arrayOfStrings).each.is.startingWith('foo');
yeldiRium marked this conversation as resolved.
Show resolved Hide resolved
yeldiRium marked this conversation as resolved.
Show resolved Hide resolved

assert.that(setOfObjects).each.is.atLeast({ foo: 'bar' });

assert.that(mapOfThings).each.is.not.null();
```

While the `.each.is...` assertions run the assertion on each item of arrays and sets, it runs the assertions only on the values of maps.
yeldiRium marked this conversation as resolved.
Show resolved Hide resolved

## Caveats

Most assertions build upon an internal comparison using a diff-algorithm. To avoid infinite recursion, all asserted values are first dispelled (i.e. recursions in them are detected and removed). These recursions can in principle be compared by value across arrays and objects. However, this does not work with `Set`s and `Map`s, since a `Map` can have reference types as keys and the element in `Set` can not be uniquely identified in a reproducible way. So comparisons of `Set`s and `Map`s that contain recursions might not work as expected.
Expand Down
27 changes: 27 additions & 0 deletions lib/assertions/combined/CombinedAssertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ArrayAssertions } from '../forArrays/ArrayAssertions';
import { CommonAssertions } from '../forAny/CommonAssertions';
import { FunctionAssertions } from '../forFunctions/FunctionAssertions';
import { MapAssertions } from '../forMaps/MapAssertions';
import { NumberAssertions } from '../forNumbers/NumberAssertions';
import { ObjectAssertions } from '../forObjects/ObjectAssertions';
import { Result } from 'defekt';
import { ResultAssertions } from '../forResults/ResultAssertions';
import { SetAssertions } from '../forSets/SetAssertions';
import { StringAssertions } from '../forStrings/StringAssertions';

type CombinedAssertions<TValue> =
CommonAssertions<TValue> &
(TValue extends Set<infer TContent> ? SetAssertions<TContent> :
TValue extends Map<infer TKey, infer TContent> ? MapAssertions<TKey, TContent> :
TValue extends (infer TContent)[] ? ArrayAssertions<TContent> :
TValue extends Result<any, Error> ? ResultAssertions :
TValue extends number ? NumberAssertions :
TValue extends string ? StringAssertions :
// eslint-disable-next-line @typescript-eslint/ban-types
strangedev marked this conversation as resolved.
Show resolved Hide resolved
TValue extends Function ? FunctionAssertions :
TValue extends object ? ObjectAssertions :
CommonAssertions<TValue>);

export type {
CombinedAssertions
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsAValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssertionFailed } from '../../errors';
import { assertResultIsAValue } from '../forResults/assertResultIsAValue';
import { Result } from 'defekt';

// eslint-disable-next-line @typescript-eslint/naming-convention
const assertActualIsAValue = function <TValue, TError extends Error>(
actual: Result<TValue, TError>
): Result<undefined, AssertionFailed> {
return assertResultIsAValue(actual);
};

export {
assertActualIsAValue
};
13 changes: 13 additions & 0 deletions lib/assertions/combined/assertActualIsAnError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AssertionFailed } from '../../errors';
import { assertResultIsAnError } from '../forResults/assertResultIsAnError';
import { Result } from 'defekt';

const assertActualIsAnError = function <TValue, TError extends Error>(
actual: Result<TValue, TError>
): Result<undefined, AssertionFailed> {
return assertResultIsAnError(actual);
};

export {
assertActualIsAnError
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsAnErrorWithMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssertionFailed } from '../../errors';
import { assertResultIsAnErrorWithMessage } from '../forResults/assertResultIsAnErrorWithMessage';
import { Result } from 'defekt';

const assertActualIsAnErrorWithMessage = function <TValue, TError extends Error>(
actual: Result<TValue, TError>,
expected: string
): Result<undefined, AssertionFailed> {
return assertResultIsAnErrorWithMessage(actual, expected);
};

export {
assertActualIsAnErrorWithMessage
};
32 changes: 32 additions & 0 deletions lib/assertions/combined/assertActualIsAtLeast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { assertMapIsAtLeastMap } from '../forMaps/assertMapIsAtLeastMap';
import { assertNumberIsAtLeastNumber } from '../forNumbers/assertNumberIsAtLeastNumber';
import { assertObjectIsAtLeastObject } from '../forObjects/assertObjectIsAtLeastObject';
import { assertSetIsAtLeastSet } from '../forSets/assertSetIsAtLeastSet';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isMap, isNumber, isObject, isSet } from 'typedescriptor';

const assertActualIsAtLeast = function <TKey, TContent>(
actual: Map<TKey, TContent> | number | Set<TContent> | object,
expected: Map<TKey, TContent> | number | Set<TContent> | object
): Result<undefined, AssertionFailed> {
if (isMap(actual) && isMap(expected)) {
return assertMapIsAtLeastMap(actual, expected);
}
if (isNumber(actual) && isNumber(expected)) {
return assertNumberIsAtLeastNumber(actual, expected);
}
if (isSet(actual) && isSet(expected)) {
return assertSetIsAtLeastSet(actual, expected);
}
if (isObject(actual) && isObject(expected)) {
return assertObjectIsAtLeastObject(actual, expected);
}

throw new InvalidOperation();
};

export {
assertActualIsAtLeast
};

31 changes: 31 additions & 0 deletions lib/assertions/combined/assertActualIsAtMost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { assertMapIsAtMostMap } from '../forMaps/assertMapIsAtMostMap';
import { assertNumberIsAtMostNumber } from '../forNumbers/assertNumberIsAtMostNumber';
import { assertObjectIsAtMostObject } from '../forObjects/assertObjectIsAtMostObject';
import { assertSetIsAtMostSet } from '../forSets/assertSetIsAtMostSet';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isMap, isNumber, isObject, isSet } from 'typedescriptor';

const assertActualIsAtMost = function <TKey, TContent>(
actual: Map<TKey, TContent> | number | Set<TContent> | object,
expected: Map<TKey, TContent> | number | Set<TContent> | object
): Result<undefined, AssertionFailed> {
if (isMap(actual) && isMap(expected)) {
return assertMapIsAtMostMap(actual, expected);
}
if (isNumber(actual) && isNumber(expected)) {
return assertNumberIsAtMostNumber(actual, expected);
}
if (isSet(actual) && isSet(expected)) {
return assertSetIsAtMostSet(actual, expected);
}
if (isObject(actual) && isObject(expected)) {
return assertObjectIsAtMostObject(actual, expected);
}

throw new InvalidOperation();
};

export {
assertActualIsAtMost
};
26 changes: 26 additions & 0 deletions lib/assertions/combined/assertActualIsContaining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assertArrayIsContainingItem } from '../forArrays/assertArrayIsContainingItem';
import { assertSetIsContainingItem } from '../forSets/assertSetIsContainingItem';
import { assertStringIsContainingString } from '../forStrings/assertStringIsContainingString';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isArray, isSet, isString } from 'typedescriptor';

const assertActualIsContaining = function <TContent>(
actual: string | TContent[] | Set<TContent>,
expected: string | TContent
): Result<undefined, AssertionFailed> {
if (isString(actual)) {
return assertStringIsContainingString(actual, expected as string);
}
if (isArray(actual)) {
return assertArrayIsContainingItem(actual, expected);
}
if (isSet(actual)) {
return assertSetIsContainingItem(actual, expected);
}
throw new InvalidOperation();
};

export {
assertActualIsContaining
};
26 changes: 26 additions & 0 deletions lib/assertions/combined/assertActualIsContainingAllOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assertArrayIsContainingAllOfIterable } from '../forArrays/assertArrayIsContainingAllOfIterable';
import { assertSetIsContainingAllOfIterable } from '../forSets/assertSetIsContainingAllOfIterable';
import { assertStringIsContainingAllOfIterable } from '../forStrings/assertStringIsContainingAllOfIterable';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isArray, isSet, isString } from 'typedescriptor';

const assertActualIsContainingAllOf = function <TContent>(
actual: string | TContent[] | Set<TContent>,
expected: (string | TContent)[]
): Result<undefined, AssertionFailed> {
if (isString(actual)) {
return assertStringIsContainingAllOfIterable(actual, expected as string[]);
}
if (isArray(actual)) {
return assertArrayIsContainingAllOfIterable(actual, expected);
}
if (isSet(actual)) {
return assertSetIsContainingAllOfIterable(actual, expected);
}
throw new InvalidOperation();
};

export {
assertActualIsContainingAllOf
};
26 changes: 26 additions & 0 deletions lib/assertions/combined/assertActualIsContainingAnyOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assertArrayIsContainingAnyOfIterable } from '../forArrays/assertArrayIsContainingAnyOfIterable';
import { assertSetIsContainingAnyOfIterable } from '../forSets/assertSetIsContainingAnyOfIterable';
import { assertStringIsContainingAnyOfIterable } from '../forStrings/assertStringIsContainingAnyOfIterable';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isArray, isSet, isString } from 'typedescriptor';

const assertActualIsContainingAnyOf = function <TContent>(
actual: string | TContent[] | Set<TContent>,
expected: (string | TContent)[]
): Result<undefined, AssertionFailed> {
if (isString(actual)) {
return assertStringIsContainingAnyOfIterable(actual, expected as string[]);
}
if (isArray(actual)) {
return assertArrayIsContainingAnyOfIterable(actual, expected);
}
if (isSet(actual)) {
return assertSetIsContainingAnyOfIterable(actual, expected);
}
throw new InvalidOperation();
};

export {
assertActualIsContainingAnyOf
};
34 changes: 34 additions & 0 deletions lib/assertions/combined/assertActualIsEmpty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { assertArrayIsEmpty } from '../forArrays/assertArrayIsEmpty';
import { assertMapIsEmpty } from '../forMaps/assertMapIsEmpty';
import { assertObjectIsEmpty } from '../forObjects/assertObjectIsEmpty';
import { assertSetIsEmpty } from '../forSets/assertSetIsEmpty';
import { assertStringIsEmpty } from '../forStrings/assertStringIsEmpty';
import { Result } from 'defekt';
import { AssertionFailed, InvalidOperation } from '../../errors';
import { isArray, isMap, isObject, isSet, isString } from 'typedescriptor';

const assertActualIsEmpty = function <TContent>(
actual: TContent[] | Map<any, TContent> | Set<TContent> | string | object
): Result<undefined, AssertionFailed> {
if (isArray(actual)) {
return assertArrayIsEmpty(actual);
}
if (isMap(actual)) {
return assertMapIsEmpty(actual);
}
if (isSet(actual)) {
return assertSetIsEmpty(actual);
}
if (isString(actual)) {
return assertStringIsEmpty(actual);
}
if (isObject(actual)) {
return assertObjectIsEmpty(actual);
}

throw new InvalidOperation();
};

export {
assertActualIsEmpty
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsEndingWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssertionFailed } from '../../errors';
import { assertStringIsEndingWithString } from '../forStrings/assertStringIsEndingWithString';
import { Result } from 'defekt';

const assertActualIsEndingWithActual = function (
actual: string,
expected: string
): Result<undefined, AssertionFailed> {
return assertStringIsEndingWithString(actual, expected);
};

export {
assertActualIsEndingWithActual
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsEqualTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { assertAnyIsEqualToExpected } from '../forAny/assertAnyIsEqualToExpected';
import { AssertionFailed } from '../../errors';
import { Result } from 'defekt';

const assertActualIsEqualTo = function (
actual: any,
expected: any
): Result<undefined, AssertionFailed> {
return assertAnyIsEqualToExpected(actual, expected);
};

export {
assertActualIsEqualTo
};
13 changes: 13 additions & 0 deletions lib/assertions/combined/assertActualIsFalse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { assertAnyIsFalse } from '../forAny/assertAnyIsFalse';
import { AssertionFailed } from '../../errors';
import { Result } from 'defekt';

const assertActualIsFalse = function (
actual: any
): Result<undefined, AssertionFailed> {
return assertAnyIsFalse(actual);
};

export {
assertActualIsFalse
};
13 changes: 13 additions & 0 deletions lib/assertions/combined/assertActualIsFalsy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { assertAnyIsFalsy } from '../forAny/assertAnyIsFalsy';
import { AssertionFailed } from '../../errors';
import { Result } from 'defekt';

const assertActualIsFalsy = function (
actual: any
): Result<undefined, AssertionFailed> {
return assertAnyIsFalsy(actual);
};

export {
assertActualIsFalsy
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsGreaterThan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssertionFailed } from '../../errors';
import { assertNumberIsGreaterThanNumber } from '../forNumbers/assertNumberIsGreaterThanNumber';
import { Result } from 'defekt';

const assertActualIsGreaterThan = function (
actual: number,
expected: number
): Result<undefined, AssertionFailed> {
return assertNumberIsGreaterThanNumber(actual, expected);
};

export {
assertActualIsGreaterThan
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsIdenticalTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { assertAnyIsIdenticalToExpected } from '../forAny/assertAnyIsIdenticalToExpected';
import { AssertionFailed } from '../../errors';
import { Result } from 'defekt';

const assertActualIsIdenticalTo = function (
actual: any,
expected: any
): Result<undefined, AssertionFailed> {
return assertAnyIsIdenticalToExpected(actual, expected);
};

export {
assertActualIsIdenticalTo
};
15 changes: 15 additions & 0 deletions lib/assertions/combined/assertActualIsInstanceOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AssertionFailed } from '../../errors';
import { assertObjectIsInstanceOfClass } from '../forObjects/assertObjectIsInstanceOfClass';
import { Result } from 'defekt';

const assertActualIsInstanceOf = function (
actual: object,
// eslint-disable-next-line @typescript-eslint/ban-types
yeldiRium marked this conversation as resolved.
Show resolved Hide resolved
expected: Function
): Result<undefined, AssertionFailed> {
return assertObjectIsInstanceOfClass(actual, expected);
};

export {
assertActualIsInstanceOf
};
14 changes: 14 additions & 0 deletions lib/assertions/combined/assertActualIsLessThan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssertionFailed } from '../../errors';
import { assertNumberIsLessThanNumber } from '../forNumbers/assertNumberIsLessThanNumber';
import { Result } from 'defekt';

const assertActualIsLessThan = function (
actual: number,
expected: number
): Result<undefined, AssertionFailed> {
return assertNumberIsLessThanNumber(actual, expected);
};

export {
assertActualIsLessThan
};
Loading