Skip to content

Commit

Permalink
feat: strong assertions of properties
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j committed Jan 1, 2024
1 parent 3bde71d commit 661ecfd
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 50 deletions.
26 changes: 23 additions & 3 deletions src/chai/core/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as _ from '../utils/index.js';
import {
OnlyIf,
Constructor,
KeyedObject,
LengthLike,
CollectionLike
} from '../utils/types.js';
Expand Down Expand Up @@ -191,8 +190,29 @@ declare module '../assertion.js' {
instanceOf: (ctor: Constructor<unknown>, msg?: string) => Assertion<T, TFlags>;
instanceof: (ctor: Constructor<unknown>, msg?: string) => Assertion<T, TFlags>;

key: OnlyIf<T, KeyedObject, (keys: Array<PropertyKey> | Record<PropertyKey, unknown>) => Assertion<T, TFlags>>;
keys: OnlyIf<T, KeyedObject, (keys: Array<PropertyKey> | Record<PropertyKey, unknown>) => Assertion<T, TFlags>>;
keys: T extends (Map<unknown, unknown> | Set<unknown>) ?
{
(keys: unknown[]): Assertion<T, TFlags>;
(...keys: unknown[]): Assertion<T, TFlags>;
} :
(
T extends object ? {
(keys: Record<PropertyKey, unknown>): Assertion<T, TFlags>;
(keys: PropertyKey[]): Assertion<T, TFlags>;
(...keys: PropertyKey[]): Assertion<T, TFlags>;
} : never
);
key: T extends (Map<unknown, unknown> | Set<unknown>) ?
{
(keys: unknown[]): Assertion<T, TFlags>;
(...keys: unknown[]): Assertion<T, TFlags>;
} :
(
T extends object ? {
(keys: PropertyKey[]): Assertion<T, TFlags>;
(...keys: PropertyKey[]): Assertion<T, TFlags>;
} : never
);

length: OnlyIf<
T,
Expand Down
222 changes: 175 additions & 47 deletions src/chai/interface/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* MIT Licensed
*/

import * as chai from '../../chai.js';
import {Assertion} from '../assertion.js';
import {flag, inspect} from '../utils/index.js';
import {AssertionError} from 'assertion-error';
Expand All @@ -16,48 +15,64 @@ import {
} from '../utils/types.js';

export interface AssertInterface {
(expr: unknown, msg?: string): void;
fail(msg?: string): void;
fail<T>(actual: T, expected: T, message: string, operator: string): void;
(expr: unknown, msg?: string): asserts expr;
fail(msg?: string): never;
fail<T>(actual: T, expected: T, message: string, operator: string): never;
isOk(val: unknown, msg?: string): void;
ok(val: unknown, msg?: string): void;
isNotOk(val: unknown, msg?: string): void;
notOk(val: unknown, msg?: string): void;
equal<T>(actual: T, expected: T, msg?: string): void;
equal<TActual, TExpected extends TActual>(
actual: TActual,
expected: TExpected,
msg?: string
): asserts actual is TExpected;
notEqual<T>(actual: T, expected: T, msg?: string): void;
strictEqual<T>(actual: T, expected: T, msg?: string): void;
strictEqual<TActual, TExpected extends TActual>(
actual: TActual,
expected: TExpected,
msg?: string
): asserts actual is TExpected;
notStrictEqual<T>(actual: T, expected: T, msg?: string): void;
deepEqual<T>(actual: T, expected: T, msg?: string): void;
deepStrictEqual<T>(actual: T, expected: T, msg?: string): void;
deepEqual<TActual, TExpected extends TActual>(
actual: TActual,
expected: TExpected,
msg?: string
): asserts actual is TExpected;
deepStrictEqual<TActual, TExpected extends TActual>(
actual: TActual,
expected: TExpected,
msg?: string
): asserts actual is TExpected;
notDeepEqual<T>(actual: T, expected: T, msg?: string): void;
isAbove<T extends Date | number>(val: T, abv: T, msg?: string): void;
isAtLeast<T extends Date | number>(val: T, atlst: T, msg?: string): void;
isBelow<T extends Date | number>(val: T, blw: T, msg?: string): void;
isAtMost<T extends Date | number>(val: T, atmst: T, msg?: string): void;
isTrue(val: unknown, msg?: string): void;
isTrue(val: unknown, msg?: string): asserts val is true;
isNotTrue(val: unknown, msg?: string): void;
isFalse(val: unknown, msg?: string): void;
isFalse(val: unknown, msg?: string): asserts val is false;
isNotFalse(val: unknown, msg?: string): void;
isNull(val: unknown, msg?: string): void;
isNull(val: unknown, msg?: string): asserts val is null;
isNotNull(val: unknown, msg?: string): void;
isNaN(val: unknown, msg?: string): void;
isNaN(val: unknown, msg?: string): asserts val is number;
isNotNaN(val: unknown, msg?: string): void;
exists(val: unknown, msg?: string): void;
exists<T>(val: T, msg?: string): asserts val is NonNullable<T>;
notExists(val: unknown, msg?: string): void;
isUndefined(val: unknown, msg?: string): void;
isUndefined(val: unknown, msg?: string): asserts val is undefined;
isDefined(val: unknown, msg?: string): void;
isFunction(val: unknown, msg?: string): void;
isFunction(val: unknown, msg?: string): asserts val is Function;
isNotFunction(val: unknown, msg?: string): void;
isObject(val: unknown, msg?: string): void;
isObject(val: unknown, msg?: string): asserts val is object;
isNotObject(val: unknown, msg?: string): void;
isArray(val: unknown, msg?: string): void;
isArray(val: unknown, msg?: string): asserts val is Array<unknown>;
isNotArray(val: unknown, msg?: string): void;
isString(val: unknown, msg?: string): void;
isString(val: unknown, msg?: string): asserts val is string;
isNotString(val: unknown, msg?: string): void;
isNumber(val: unknown, msg?: string): void;
isNumber(val: unknown, msg?: string): asserts val is number;
isNotNumber(val: unknown, msg?: string): void;
isFinite(val: number, msg?: string): void;
isBoolean(val: unknown, msg?: string): void;
isBoolean(val: unknown, msg?: string): asserts val is boolean;
isNotBoolean(val: unknown, msg?: string): void;
typeOf(val: unknown, type: string, msg?: string): void;
notTypeOf(val: unknown, type: string, msg?: string): void;
Expand All @@ -77,13 +92,26 @@ export interface AssertInterface {
notDeepOwnInclude(expr: object, inc: unknown, msg?: string): void;
match(expr: string, re: RegExp, msg?: string): void;
notMatch(expr: string, re: RegExp, msg?: string): void;
property(obj: object, prop: PropertyKey, msg?: string): void;
property<T extends object, TKey extends PropertyKey>(
obj: T,
prop: TKey,
msg?: string
): asserts obj is (TKey extends keyof T ? T : (T & {[k in TKey]: unknown}));
notProperty(obj: object, prop: PropertyKey, msg?: string): void;
propertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
propertyVal<T extends object, TKey extends keyof T>(
obj: T,
prop: TKey,
val: T[TKey],
msg?: string
): void;
notPropertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
deepPropertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
notDeepPropertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
ownProperty(obj: object, prop: PropertyKey, msg?: string): void;
ownProperty<T extends object, TKey extends PropertyKey>(
obj: T,
prop: TKey,
msg?: string
): asserts obj is (TKey extends keyof T ? T : (T & {[k in TKey]: unknown}));
notOwnProperty(obj: unknown, prop: PropertyKey, msg?: string): void;
ownPropertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
notOwnPropertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string): void;
Expand All @@ -96,16 +124,116 @@ export interface AssertInterface {
deepNestedPropertyVal(obj: unknown, prop: string, val: unknown, msg?: string): void;
notDeepNestedPropertyVal(obj: unknown, prop: string, val: unknown, msg?: string): void;
lengthOf(expr: LengthLike, len: number, msg?: string): void;
hasAnyKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
hasAllKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
containsAllKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
doesNotHaveAnyKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
doesNotHaveAllKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
hasAnyDeepKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
hasAllDeepKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
containsAllDeepKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
doesNotHaveAnyDeepKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;
doesNotHaveAllDeepKeys(obj: KeyedObject, keys: Array<PropertyKey> | Record<PropertyKey, unknown>, msg?: string): void;

hasAnyKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
hasAnyKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

hasAllKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
hasAllKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

containsAllKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
containsAllKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

doesNotHaveAnyKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
doesNotHaveAnyKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

doesNotHaveAllKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
doesNotHaveAllKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

hasAnyDeepKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
hasAnyDeepKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

hasAllDeepKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
hasAllDeepKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

containsAllDeepKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
containsAllDeepKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

doesNotHaveAnyDeepKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
doesNotHaveAnyDeepKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

doesNotHaveAllDeepKeys(
obj: Set<unknown> | Map<unknown, unknown>,
keys: unknown[],
msg?: string
): void;
doesNotHaveAllDeepKeys(
obj: object,
keys: PropertyKey[] | Record<PropertyKey, unknown>,
msg?: string
): void;

Throw(
fn: Function,
Expand Down Expand Up @@ -375,7 +503,7 @@ export interface AssertInterface {
*/

const assert: AssertInterface = function assert(express: unknown, errmsg?: string) {
var test = Assertion.create(null, null, chai.assert, true);
var test = Assertion.create(null, null, assert, true);
test.assert(
express
, errmsg
Expand Down Expand Up @@ -1776,7 +1904,7 @@ assert.notDeepPropertyVal = function notDeepPropertyVal<T, TKey extends keyof T>
* @api public
*/

assert.ownProperty = function ownProperty<T>(obj: T, prop: keyof T, msg?: string) {
assert.ownProperty = function ownProperty<T extends object, TKey extends PropertyKey>(obj: T, prop: TKey, msg?: string) {
Assertion.create(obj, msg, assert.ownProperty, true)
.to.have.own.property(prop);
};
Expand Down Expand Up @@ -2095,8 +2223,8 @@ assert.lengthOf = function (exp: LengthLike, len: number, msg?: string) {
* @api public
*/

assert.hasAnyKeys = function (obj: KeyedObject, keys: Array<string>|Record<string, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(keys);
assert.hasAnyKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(keys as PropertyKey[]);
}

/**
Expand Down Expand Up @@ -2226,9 +2354,9 @@ assert.doesNotHaveAllKeys = function (obj: KeyedObject, keys: string[], msg?: st
* @api public
*/

assert.hasAnyDeepKeys = function (obj: KeyedObject, keys: Array<string>|Record<string, unknown>, msg?: string) {
assert.hasAnyDeepKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.hasAnyDeepKeys, true)
.to.have.any.deep.keys(keys);
.to.have.any.deep.keys(keys as PropertyKey[]);
}

/**
Expand All @@ -2253,9 +2381,9 @@ assert.hasAnyDeepKeys = function (obj: KeyedObject, keys: Array<string>|Record<s
* @api public
*/

assert.hasAllDeepKeys = function (obj: KeyedObject, keys: Array<string>|Record<string, unknown>, msg?: string) {
assert.hasAllDeepKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.hasAllDeepKeys, true)
.to.have.all.deep.keys(keys);
.to.have.all.deep.keys(keys as PropertyKey[]);
}

/**
Expand All @@ -2280,9 +2408,9 @@ assert.hasAllDeepKeys = function (obj: KeyedObject, keys: Array<string>|Record<s
* @api public
*/

assert.containsAllDeepKeys = function (obj: KeyedObject, keys: Array<string>|Record<string, unknown>, msg?: string) {
assert.containsAllDeepKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.containsAllDeepKeys, true)
.to.contain.all.deep.keys(keys);
.to.contain.all.deep.keys(keys as PropertyKey[]);
}

/**
Expand All @@ -2307,9 +2435,9 @@ assert.containsAllDeepKeys = function (obj: KeyedObject, keys: Array<string>|Rec
* @api public
*/

assert.doesNotHaveAnyDeepKeys = function (obj: CollectionLike<never> | object, keys: Array<string>|Record<string, unknown>, msg?: string) {
assert.doesNotHaveAnyDeepKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.doesNotHaveAnyDeepKeys, true)
.to.not.have.any.deep.keys(keys);
.to.not.have.any.deep.keys(keys as PropertyKey[]);
}

/**
Expand All @@ -2334,9 +2462,9 @@ assert.doesNotHaveAnyDeepKeys = function (obj: CollectionLike<never> | object, k
* @api public
*/

assert.doesNotHaveAllDeepKeys = function (obj: CollectionLike<never> | object, keys: Array<string>|Record<string, unknown>, msg?: string) {
assert.doesNotHaveAllDeepKeys = function (obj: KeyedObject, keys: Array<unknown>|Record<PropertyKey, unknown>, msg?: string) {
Assertion.create(obj, msg, assert.doesNotHaveAllDeepKeys, true)
.to.not.have.all.deep.keys(keys);
.to.not.have.all.deep.keys(keys as PropertyKey[]);
}

/**
Expand Down

0 comments on commit 661ecfd

Please sign in to comment.