From b1814d6abf5be3e0d7cab7b1cc189cb749bdd0d5 Mon Sep 17 00:00:00 2001 From: Bob Bergman Date: Mon, 13 Jan 2020 15:54:05 -0700 Subject: [PATCH] feat: add conveniences to ts-types update typescript to 3.7.4 for additional language features --- packages/dev-config/package.json | 2 +- packages/dev-scripts/package.json | 4 +- packages/ts-types/src/errors.ts | 9 + packages/ts-types/src/narrowing/as.ts | 45 ++-- packages/ts-types/src/narrowing/assert.ts | 196 ++++++++++++++++++ packages/ts-types/src/narrowing/ensure.ts | 56 +++-- packages/ts-types/src/narrowing/is.ts | 73 +++++-- .../ts-types/test/narrowing/assert.test.ts | 188 +++++++++++++++++ .../ts-types/test/narrowing/ensure.test.ts | 14 +- packages/ts-types/test/narrowing/is.test.ts | 46 ++++ yarn.lock | 40 +++- 11 files changed, 619 insertions(+), 54 deletions(-) create mode 100644 packages/ts-types/src/narrowing/assert.ts create mode 100644 packages/ts-types/test/narrowing/assert.test.ts diff --git a/packages/dev-config/package.json b/packages/dev-config/package.json index 1214fe4d..6a592c41 100644 --- a/packages/dev-config/package.json +++ b/packages/dev-config/package.json @@ -25,7 +25,7 @@ "dependencies": { "tslint": "^5.18.0", "tslint-microsoft-contrib": "^5.2.1", - "typescript": "~3.1.6" + "typescript": "~3.7.4" }, "scripts": { "clean-all": "shx rm -rf node_modules" diff --git a/packages/dev-scripts/package.json b/packages/dev-scripts/package.json index 64d605d3..569c475d 100644 --- a/packages/dev-scripts/package.json +++ b/packages/dev-scripts/package.json @@ -44,13 +44,13 @@ "chalk": "2.4.2", "mocha": "^5.2.0", "nyc": "^13.3.0", - "prettier": "^1.18.2", + "prettier": "^1.19.1", "pretty-quick": "^1.11.1", "shelljs": "~0.8.3", "sinon": "^5.1.1", "source-map-support": "~0.5.12", "ts-node": "^7.0.1", - "tslint": "^5.18.0", + "tslint": "^5.20.1", "typedoc": "~0.15.0", "typedoc-plugin-external-module-name": "~1.1.3", "xunit-file": "^1.0.0" diff --git a/packages/ts-types/src/errors.ts b/packages/ts-types/src/errors.ts index 7ce25053..d50002ff 100644 --- a/packages/ts-types/src/errors.ts +++ b/packages/ts-types/src/errors.ts @@ -18,6 +18,15 @@ export class NamedError extends Error { } } +/** + * Indicates an unexpected type was encountered during a type-narrowing operation. + */ +export class AssertionFailedError extends NamedError { + constructor(message: string) { + super('AssertionFailedError', message); + } +} + /** * Indicates an unexpected type was encountered during a type-narrowing operation. */ diff --git a/packages/ts-types/src/narrowing/as.ts b/packages/ts-types/src/narrowing/as.ts index 58f50425..20656ae3 100644 --- a/packages/ts-types/src/narrowing/as.ts +++ b/packages/ts-types/src/narrowing/as.ts @@ -5,10 +5,11 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { AnyArray, AnyConstructor, AnyFunction, AnyJson, JsonArray, JsonMap, Optional } from '../types'; +import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, Optional } from '../types'; import { isArray, isBoolean, + isDictionary, isFunction, isInstance, isJsonArray, @@ -78,17 +79,17 @@ export function asBoolean(value: unknown, defaultValue?: boolean): Optional; +export function asObject(value: unknown): Optional; /** * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. * * @param value The value to test. * @param defaultValue The default to return if `value` was undefined or of the incorrect type. */ -export function asObject(value: unknown, defaultValue: object): object; +export function asObject(value: unknown, defaultValue: T): T; // underlying function -export function asObject(value: unknown, defaultValue?: object): Optional { - return isObject(value) ? value : defaultValue; +export function asObject(value: unknown, defaultValue?: T): Optional { + return isObject(value) ? value : defaultValue; } /** @@ -96,17 +97,35 @@ export function asObject(value: unknown, defaultValue?: object): Optional; +export function asPlainObject(value: unknown): Optional; /** * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. * * @param value The value to test. * @param defaultValue The default to return if `value` was undefined or of the incorrect type. */ -export function asPlainObject(value: unknown, defaultValue: object): object; +export function asPlainObject(value: unknown, defaultValue: T): T; // underlying function -export function asPlainObject(value: unknown, defaultValue?: object): Optional { - return isPlainObject(value) ? value : defaultValue; +export function asPlainObject(value: unknown, defaultValue?: T): Optional { + return isPlainObject(value) ? value : defaultValue; +} + +/** + * Narrows an `unknown` value to a `Dictionary` if it is type-compatible, or returns `undefined` otherwise. + * + * @param value The value to test. + */ +export function asDictionary(value: unknown): Optional>; +/** + * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. + * + * @param value The value to test. + * @param defaultValue The default to return if `value` was undefined or of the incorrect type. + */ +export function asDictionary(value: unknown, defaultValue: Dictionary): Dictionary; +// underlying function +export function asDictionary(value: unknown, defaultValue?: Dictionary): Optional> { + return isDictionary(value) ? value : defaultValue; } /** @@ -141,17 +160,17 @@ export function asInstance( * * @param value The value to test. */ -export function asArray(value: unknown): Optional; +export function asArray(value: unknown): Optional; /** * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. * * @param value The value to test. * @param defaultValue The default to return if `value` was undefined or of the incorrect type. */ -export function asArray(value: unknown, defaultValue: AnyArray): AnyArray; +export function asArray(value: unknown, defaultValue: T[]): T[]; // underlying function -export function asArray(value: unknown, defaultValue?: AnyArray): Optional { - return isArray(value) ? value : defaultValue; +export function asArray(value: unknown, defaultValue?: T[]): Optional { + return isArray(value) ? value : defaultValue; } /** diff --git a/packages/ts-types/src/narrowing/assert.ts b/packages/ts-types/src/narrowing/assert.ts new file mode 100644 index 00000000..1557f56c --- /dev/null +++ b/packages/ts-types/src/narrowing/assert.ts @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { AssertionFailedError } from '../errors'; +import { AnyArray, AnyConstructor, AnyFunction, AnyJson, JsonArray, JsonMap, Nullable, Optional } from '../types'; +import { + asArray, + asBoolean, + asDictionary, + asFunction, + asInstance, + asJsonArray, + asJsonMap, + asNumber, + asObject, + asPlainObject, + asString +} from './as'; +import { toAnyJson } from './to'; + +/** + * Asserts that a given `condition` is true, or raises an error otherwise. + * + * @param condition The condition to test. + * @param message The error message to use if the condition is false. + * @throws {@link AssertionFailedError} If the assertion failed. + */ +export function assert(condition: boolean, message?: string): asserts condition { + if (!condition) { + throw new AssertionFailedError(message || 'Assertion condition was false'); + } +} + +/** + * Narrows a type `Nullable` to a `T` or raises an error. + * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * + * @param value The value to test. + * @param message The error message to use if `value` is `undefined` or `null`. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertNonNull(value: Nullable, message?: string): asserts value is T { + assert(value != null, message || 'Value is not defined'); +} + +/** + * Narrows an `unknown` value to a `string` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertString(value: unknown, message?: string): asserts value is string { + assertNonNull(asString(value), message || 'Value is not a string'); +} + +/** + * Narrows an `unknown` value to a `number` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertNumber(value: unknown, message?: string): asserts value is number { + assertNonNull(asNumber(value), message || 'Value is not a number'); +} + +/** + * Narrows an `unknown` value to a `boolean` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertBoolean(value: unknown, message?: string): asserts value is boolean { + assertNonNull(asBoolean(value), message || 'Value is not a boolean'); +} + +/** + * Narrows an `unknown` value to an `object` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertObject(value: unknown, message?: string): asserts value is object { + assertNonNull(asObject(value), message || 'Value is not an object'); +} + +/** + * Narrows an `unknown` value to an `object` if it is type-compatible and tests positively with {@link isPlainObject}, + * or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertPlainObject(value: unknown, message?: string): asserts value is object { + assertNonNull(asPlainObject(value), message || 'Value is not an object'); +} + +/** + * Narrows an `unknown` value to a `Dictionary` if it is type-compatible and tests positively with {@link isDictionary}, + * or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertDictionary(value: unknown, message?: string): asserts value is object { + assertNonNull(asDictionary(value), message || 'Value is not an object'); +} + +/** + * Narrows an `unknown` value to instance of constructor type `T` if it is type-compatible, or raises an error + * otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertInstance( + value: unknown, + ctor: C, + message?: string +): asserts value is InstanceType { + assertNonNull(asInstance(value, ctor), message || `Value is not an instance of ${ctor.name}`); +} + +/** + * Narrows an `unknown` value to an `Array` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertArray(value: unknown, message?: string): asserts value is AnyArray { + assertNonNull(asArray(value), message || 'Value is not an array'); +} + +/** + * Narrows an `unknown` value to an `AnyFunction` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertFunction(value: unknown, message?: string): asserts value is AnyFunction { + assertNonNull(asFunction(value), message || 'Value is not a function'); +} + +/** + * Narrows an `unknown` value to an `AnyJson` if it is type-compatible, or returns `undefined` otherwise. + * + * See also caveats noted in {@link isAnyJson}. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was not a JSON value type. + */ +export function assertAnyJson(value: unknown, message?: string): asserts value is AnyJson { + assertNonNull(toAnyJson(value), message || 'Value is not a JSON-compatible value type'); +} + +/** + * Narrows an `AnyJson` value to a `JsonMap` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertJsonMap(value: Optional, message?: string): asserts value is JsonMap { + assertNonNull(asJsonMap(value), message || 'Value is not a JsonMap'); +} + +/** + * Narrows an `AnyJson` value to a `JsonArray` if it is type-compatible, or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link AssertionFailedError} If the value was undefined. + */ +export function assertJsonArray(value: Optional, message?: string): asserts value is JsonArray { + assertNonNull(asJsonArray(value), message || 'Value is not a JsonArray'); +} diff --git a/packages/ts-types/src/narrowing/ensure.ts b/packages/ts-types/src/narrowing/ensure.ts index 8e227471..9b6deea7 100644 --- a/packages/ts-types/src/narrowing/ensure.ts +++ b/packages/ts-types/src/narrowing/ensure.ts @@ -6,20 +6,40 @@ */ import { UnexpectedValueTypeError } from '../errors'; -import { AnyArray, AnyConstructor, AnyFunction, AnyJson, JsonArray, JsonMap, Nullable, Optional } from '../types'; -import { asArray, asBoolean, asFunction, asInstance, asJsonArray, asJsonMap, asNumber, asObject, asString } from './as'; +import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, Nullable, Optional } from '../types'; +import { + asArray, + asBoolean, + asDictionary, + asFunction, + asInstance, + asJsonArray, + asJsonMap, + asNumber, + asObject, + asPlainObject, + asString +} from './as'; import { toAnyJson } from './to'; /** * Narrows a type `Nullable` to a `T` or raises an error. * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * * @param value The value to test. * @param message The error message to use if `value` is `undefined` or `null`. * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ -export function ensure(value: Nullable, message?: string): T { +export function ensure(value: Nullable, message?: string): T { if (value == null) { - throw new UnexpectedValueTypeError(message || 'Value is undefined'); + throw new UnexpectedValueTypeError(message || 'Value is not defined'); } return value; } @@ -32,7 +52,7 @@ export function ensure(value: Nullable, message?: string): T { * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ export function ensureString(value: unknown, message?: string): string { - return ensure(asString(value), message || 'Value is not an string'); + return ensure(asString(value), message || 'Value is not a string'); } /** @@ -43,7 +63,7 @@ export function ensureString(value: unknown, message?: string): string { * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ export function ensureNumber(value: unknown, message?: string): number { - return ensure(asNumber(value), message || 'Value is not an number'); + return ensure(asNumber(value), message || 'Value is not a number'); } /** @@ -54,7 +74,7 @@ export function ensureNumber(value: unknown, message?: string): number { * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ export function ensureBoolean(value: unknown, message?: string): boolean { - return ensure(asBoolean(value), message || 'Value is not an boolean'); + return ensure(asBoolean(value), message || 'Value is not a boolean'); } /** @@ -64,7 +84,7 @@ export function ensureBoolean(value: unknown, message?: string): boolean { * @param message The error message to use if `value` is not type-compatible. * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ -export function ensureObject(value: unknown, message?: string): object { +export function ensureObject(value: unknown, message?: string): T { return ensure(asObject(value), message || 'Value is not an object'); } @@ -76,8 +96,20 @@ export function ensureObject(value: unknown, message?: string): object { * @param message The error message to use if `value` is not type-compatible. * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ -export function ensurePlainObject(value: unknown, message?: string): object { - return ensure(asObject(value), message || 'Value is not an object'); +export function ensurePlainObject(value: unknown, message?: string): T { + return ensure(asPlainObject(value), message || 'Value is not a plain object'); +} + +/** + * Narrows an `unknown` value to a `Dictionary` if it is type-compatible and tests positively with {@link isDictionary}, + * or raises an error otherwise. + * + * @param value The value to test. + * @param message The error message to use if `value` is not type-compatible. + * @throws {@link UnexpectedValueTypeError} If the value was undefined. + */ +export function ensureDictionary(value: unknown, message?: string): Dictionary { + return ensure(asDictionary(value), message || 'Value is not a dictionary object'); } /** @@ -99,7 +131,7 @@ export function ensureInstance(value: unknown, ctor: C * @param message The error message to use if `value` is not type-compatible. * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ -export function ensureArray(value: unknown, message?: string): AnyArray { +export function ensureArray(value: unknown, message?: string): T[] { return ensure(asArray(value), message || 'Value is not an array'); } @@ -146,5 +178,5 @@ export function ensureJsonMap(value: Optional, message?: string): JsonM * @throws {@link UnexpectedValueTypeError} If the value was undefined. */ export function ensureJsonArray(value: Optional, message?: string): JsonArray { - return ensure(asJsonArray(value), message || 'Value is not JsonArray'); + return ensure(asJsonArray(value), message || 'Value is not a JsonArray'); } diff --git a/packages/ts-types/src/narrowing/is.ts b/packages/ts-types/src/narrowing/is.ts index 4ac01392..77dc4a20 100644 --- a/packages/ts-types/src/narrowing/is.ts +++ b/packages/ts-types/src/narrowing/is.ts @@ -5,19 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { - AnyArray, - AnyArrayLike, - AnyConstructor, - AnyFunction, - AnyJson, - Dictionary, - JsonArray, - JsonMap, - KeyOf, - Optional, - View -} from '../types'; +import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, KeyOf, Optional, View } from '../types'; /** * Tests whether an `unknown` value is a `string`. @@ -52,9 +40,17 @@ export function isBoolean(value: unknown): value is boolean { * were created from literals or that otherwise were not created via a non-`Object` constructor and do * not have a prototype chain should instead use {@link isPlainObject}. * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * * @param value The value to test. */ -export function isObject(value: unknown): value is object { +export function isObject(value: unknown): value is T { return value != null && (typeof value === 'object' || typeof value === 'function'); } @@ -62,9 +58,17 @@ export function isObject(value: unknown): value is object { * Tests whether or not an `unknown` value is a plain JavaScript object. That is, if it is an object created * by the Object constructor or one with a null `prototype`. * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * * @param value The value to test. */ -export function isPlainObject(value: unknown): value is object { +export function isPlainObject(value: unknown): value is T { const isObjectObject = (o: unknown): o is Dictionary => { return isObject(o) && Object.prototype.toString.call(o) === '[object Object]'; }; @@ -76,6 +80,25 @@ export function isPlainObject(value: unknown): value is object { return true; } +/** + * A shortcut for testing the suitability of a value to be used as a `Dictionary` type. Shorthand for + * writing `isPlainObject>(value)`. While some non-plain-object types are compatible with + * index signatures, they were less typically used as such, so this function focuses on the 80% case. + * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * + * @param value The value to test. + */ +export function isDictionary(value: unknown): value is Dictionary { + return isPlainObject(value); +} + /** * Tests whether an `unknown` value is a `function`. * @@ -101,18 +124,34 @@ export function isClassAssignableTo(value: unknown, cl /** * Tests whether an `unknown` value is an `Array`. * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * * @param value The value to test. */ -export function isArray(value: unknown): value is AnyArray { +export function isArray(value: unknown): value is T[] { return Array.isArray(value); } /** * Tests whether an `unknown` value conforms to {@link AnyArrayLike}. * + * Use of the type parameter `T` to further narrow the type signature of the value being tested is + * strongly discouraged unless you are completely confident that the value is of the necessary shape to + * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of + * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a + * bad practice unless you have performed some other due diligence in proving that the value must be of + * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial + * proofs. + * * @param value The value to test. */ -export function isArrayLike(value: unknown): value is AnyArrayLike { +export function isArrayLike(value: unknown): value is ArrayLike { // avoid circular dependency with has.ts const hasLength = (v: unknown): v is View<'length', number> => isObject(v) && 'length' in v; return !isFunction(value) && (isString(value) || hasLength(value)); diff --git a/packages/ts-types/test/narrowing/assert.test.ts b/packages/ts-types/test/narrowing/assert.test.ts new file mode 100644 index 00000000..e4330416 --- /dev/null +++ b/packages/ts-types/test/narrowing/assert.test.ts @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { expect } from 'chai'; +import { AssertionFailedError } from '../../src/errors'; +import { + assert, + assertAnyJson, + assertArray, + assertBoolean, + assertDictionary, + assertFunction, + assertInstance, + assertJsonArray, + assertJsonMap, + assertNonNull, + assertNumber, + assertObject, + assertPlainObject, + assertString +} from '../../src/narrowing/assert'; + +class TestClass { + constructor(public name = 'test') {} +} + +describe('assert type', () => { + describe('assert', () => { + it('should do nothing when passed true', () => { + assert(true); + }); + + it('should raise an error when passed false', () => { + expect(() => assert(false)).to.throw(AssertionFailedError); + }); + }); + + describe('assertNonNull', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertNonNull(undefined)).to.throw(AssertionFailedError); + }); + + it('should raise an error when passed null', () => { + expect(() => assertNonNull(null)).to.throw(AssertionFailedError); + }); + + it('should do nothing given a non-nullish value', () => { + const value = 'string'; + assertNonNull(value); + }); + }); + + describe('assertString', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertString(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a string', () => { + const value = 'string'; + assertString(value); + }); + }); + + describe('assertNumber', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertNumber(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a number', () => { + const value = 0; + assertNumber(value); + }); + }); + + describe('assertBoolean', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertBoolean(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a boolean', () => { + const value = true; + assertBoolean(value); + }); + }); + + describe('assertObject', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertObject(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a object', () => { + const value = { a: 'b' }; + assertObject(value); + }); + }); + + describe('assertPlainObject', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertPlainObject(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a plain object', () => { + const value = { a: 'b' }; + assertPlainObject(value); + }); + }); + + describe('assertDictionary', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertDictionary(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a dictionary object', () => { + const value = { a: 'b' }; + assertDictionary(value); + }); + }); + + describe('assertInstance', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertInstance(undefined, TestClass)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a class instance', () => { + const value = new TestClass('foo'); + assertInstance(value, TestClass); + }); + }); + + describe('assertArray', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertArray(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed an array', () => { + const value = ['a', 'b']; + assertArray(value); + }); + }); + + describe('assertFunction', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertFunction(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a function', () => { + const value = () => {}; + assertFunction(value); + }); + }); + + describe('assertAnyJson', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertAnyJson(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a string', () => { + const value = 'string'; + assertAnyJson(value); + }); + }); + + describe('assertJsonMap', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertJsonMap(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a JsonMap', () => { + const value = { a: 'b', c: 'd' }; + assertJsonMap(value); + }); + }); + + describe('assertJsonArray', () => { + it('should raise an error when passed undefined', () => { + expect(() => assertJsonArray(undefined)).to.throw(AssertionFailedError); + }); + + it('should do nothing when passed a JsonArray', () => { + const value = ['a', 'b']; + assertJsonArray(value); + }); + }); +}); diff --git a/packages/ts-types/test/narrowing/ensure.test.ts b/packages/ts-types/test/narrowing/ensure.test.ts index 5f05d427..073f7030 100644 --- a/packages/ts-types/test/narrowing/ensure.test.ts +++ b/packages/ts-types/test/narrowing/ensure.test.ts @@ -12,6 +12,7 @@ import { ensureAnyJson, ensureArray, ensureBoolean, + ensureDictionary, ensureFunction, ensureInstance, ensureJsonArray, @@ -91,12 +92,23 @@ describe('ensure type', () => { expect(() => ensurePlainObject(undefined)).to.throw(UnexpectedValueTypeError); }); - it('should return a object when passed a object', () => { + it('should return a plain object when passed a plain object', () => { const value = { a: 'b' }; expect(ensurePlainObject(value)).to.equal(value); }); }); + describe('ensureDictionary', () => { + it('should raise an error when passed undefined', () => { + expect(() => ensureDictionary(undefined)).to.throw(UnexpectedValueTypeError); + }); + + it('should return a dictionary object when passed a dictionary object', () => { + const value = { a: 'b' }; + expect(ensureDictionary(value)).to.equal(value); + }); + }); + describe('ensureInstance', () => { it('should raise an error when passed undefined', () => { expect(() => ensureInstance(undefined, TestClass)).to.throw(UnexpectedValueTypeError); diff --git a/packages/ts-types/test/narrowing/is.test.ts b/packages/ts-types/test/narrowing/is.test.ts index 900a8fa0..a64a24ec 100644 --- a/packages/ts-types/test/narrowing/is.test.ts +++ b/packages/ts-types/test/narrowing/is.test.ts @@ -12,6 +12,7 @@ import { isArrayLike, isBoolean, isClassAssignableTo, + isDictionary, isFunction, isInstance, isJsonArray, @@ -170,6 +171,51 @@ describe('is type', () => { }); }); + describe('isDictionary', () => { + it('should reject undefined', () => { + expect(isDictionary(undefined)).to.be.false; + }); + + it('should reject null', () => { + expect(isDictionary(null)).to.be.false; + }); + + it('should reject string', () => { + expect(isDictionary('string')).to.be.false; + }); + + it('should reject number', () => { + expect(isDictionary(1)).to.be.false; + }); + + it('should reject boolean', () => { + expect(isDictionary(true)).to.be.false; + }); + + it('should reject array', () => { + expect(isDictionary([])).to.be.false; + }); + + it('should accept object', () => { + expect(isDictionary({})).to.be.true; + }); + + it('should reject instance of TestClass', () => { + expect(isDictionary(new TestClass())).to.be.false; + }); + + it('should reject mock class', () => { + expect(isDictionary({ constructor: true })).to.be.false; + const wtf = () => {}; + wtf.prototype = TestClass; + expect(isDictionary({ constructor: wtf })).to.be.false; + }); + + it('should reject function', () => { + expect(isDictionary(() => {})).to.be.false; + }); + }); + describe('isInstance', () => { it('should reject undefined', () => { expect(isInstance(undefined, TestClass)).to.be.false; diff --git a/yarn.lock b/yarn.lock index 1f0d9b4a..784faa98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2241,6 +2241,11 @@ diff@3.5.0, diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -4830,10 +4835,10 @@ posix-character-classes@^0.1.0: resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prettier@^1.18.2: - version "1.18.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" - integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== +prettier@^1.19.1: + version "1.19.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== pretty-quick@^1.11.1: version "1.11.1" @@ -5945,6 +5950,25 @@ tslint@^5.18.0: tslib "^1.8.0" tsutils "^2.29.0" +tslint@^5.20.1: + version "5.20.1" + resolved "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" + integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + "tsutils@^2.27.2 <2.29.0": version "2.28.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz#6bd71e160828f9d019b6f4e844742228f85169a1" @@ -6023,10 +6047,10 @@ typescript@3.5.x: resolved "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@~3.1.6: - version "3.1.6" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" - integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== +typescript@~3.7.4: + version "3.7.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" + integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== uglify-js@2.7.5: version "2.7.5"