Skip to content

Commit

Permalink
feat: strongly typed property assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j committed Jan 1, 2024
1 parent a0f959f commit 015c19a
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 37 deletions.
15 changes: 10 additions & 5 deletions src/chai/assertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ export class Assertion<T, TFlags extends AssertionFlags<T> = AssertionFlags<T>>
util.flag(this, 'eql', config.deepEqual ?? util.eql);
}

public static create<T>(
obj?: T,
public static create<TObj>(
obj?: TObj,
msg?: string | null,
ssfi?: Function,
lockSsfi?: boolean
): Assertion<T> {
return util.proxify(new Assertion<T>(
): Assertion<TObj> {
return util.proxify(new Assertion<TObj>(
obj,
msg,
ssfi,
Expand Down Expand Up @@ -131,7 +131,12 @@ export class Assertion<T, TFlags extends AssertionFlags<T> = AssertionFlags<T>>
>(
name: TKey,
fn: TKey extends MethodNames<Assertion<unknown>> ?
(this: Assertion<unknown>, ...args: Parameters<Assertion<unknown>[TKey]>) => (ReturnType<Assertion<unknown>[TKey]> extends Assertion<unknown> ? (Assertion<unknown> | void) : ReturnType<Assertion<unknown>[TKey]>) :
(
this: Assertion<unknown>,
...args: Parameters<Assertion<unknown>[TKey]>
) => (
ReturnType<Assertion<unknown>[TKey]> | void
) :
((this: Assertion<unknown>, ...args: never) => unknown)
): void {
util.addMethod(this.prototype, name, fn, getDefaultValue);
Expand Down
75 changes: 44 additions & 31 deletions src/chai/core/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,21 @@ declare module '../assertion.js' {
include: OnlyIf<T, CollectionLike<never> | string | object, ChainedMethod<T, TFlags, [val: unknown, msg?: string]>>;
includes: OnlyIf<T, CollectionLike<never> | string | object, ChainedMethod<T, TFlags, [val: unknown, msg?: string]>>;

decrease: ((subject: Function) => Assertion<T, TFlags & {deltaBehavior: string}>) &
((subject: object, prop: PropertyKey, msg?: string) => Assertion<T, TFlags & {deltaBehavior: string}>);
decreases: ((subject: Function) => Assertion<T, TFlags & {deltaBehavior: string}>) &
((subject: object, prop: PropertyKey, msg?: string) => Assertion<T, TFlags & {deltaBehavior: string}>);

eq: (val: unknown, msg?: string) => Assertion<T, TFlags>;
equal: (val: unknown, msg?: string) => Assertion<T, TFlags>;
equals: (val: unknown, msg?: string) => Assertion<T, TFlags>;

eql: (val: unknown, msg?: string) => Assertion<T, TFlags>;
eqls: (val: unknown, msg?: string) => Assertion<T, TFlags>;
decrease: OnlyIf<T, Function, {
(subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
(subject: object, prop: PropertyKey, msg?: string): Assertion<T, TFlags & {deltaBehavior: string}>;
}>;
decreases: OnlyIf<T, Function, {
(subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
(subject: object, prop: PropertyKey, msg?: string): Assertion<T, TFlags & {deltaBehavior: string}>;
}>;

eq: (val: T, msg?: string) => Assertion<T, TFlags>;
equal: (val: T, msg?: string) => Assertion<T, TFlags>;
equals: (val: T, msg?: string) => Assertion<T, TFlags>;

eql: (val: T, msg?: string) => Assertion<T, TFlags>;
eqls: (val: T, msg?: string) => Assertion<T, TFlags>;

greaterThanOrEqual: OnlyIf<
T,
Expand All @@ -157,16 +161,28 @@ declare module '../assertion.js' {
OnlyIf<TFlags, {doLength: true}, (val: number, msg?: string) => Assertion<T, TFlags>>
>;

haveOwnProperty: (name: PropertyKey, val?: unknown, msg?: string) => Assertion<T, TFlags>;
ownProperty: (name: PropertyKey, val?: unknown, msg?: string) => Assertion<T, TFlags>;

haveOwnPropertyDescriptor: (name: PropertyKey, descriptor: PropertyDescriptor, msg?: string) => Assertion<T, TFlags>;
ownPropertyDescriptor: (name: PropertyKey, descriptor: PropertyDescriptor, msg?: string) => Assertion<T, TFlags>;

increase: ((subject: Function) => Assertion<T, TFlags & {deltaBehavior: string}>) &
((subject: object, prop: PropertyKey, msg?: string) => Assertion<T, TFlags & {deltaBehavior: string}>);
increases: ((subject: Function) => Assertion<T, TFlags & {deltaBehavior: string}>) &
((subject: object, prop: PropertyKey, msg?: string) => Assertion<T, TFlags & {deltaBehavior: string}>);
haveOwnProperty: OnlyIf<T, object, {
<TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
(name: PropertyKey): Assertion<unknown>;
<TKey extends keyof T>(name: TKey, val: T[TKey], msg?: string): Assertion<T[TKey]>;
}>;
ownProperty: OnlyIf<T, object, {
<TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
(name: PropertyKey): Assertion<unknown>;
<TKey extends keyof T>(name: TKey, val: T[TKey], msg?: string): Assertion<T[TKey]>;
}>;

haveOwnPropertyDescriptor: (name: PropertyKey, descriptor: PropertyDescriptor, msg?: string) => Assertion<PropertyDescriptor>;
ownPropertyDescriptor: (name: PropertyKey, descriptor: PropertyDescriptor, msg?: string) => Assertion<PropertyDescriptor>

increase: OnlyIf<T, Function, {
(subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
(subject: object, prop: PropertyKey, msg?: string): Assertion<T, TFlags & {deltaBehavior: string}>;
}>;
increases: OnlyIf<T, Function, {
(subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
(subject: object, prop: PropertyKey, msg?: string): Assertion<T, TFlags & {deltaBehavior: string}>;
}>;

instanceOf: (ctor: Constructor<unknown>, msg?: string) => Assertion<T, TFlags>;
instanceof: (ctor: Constructor<unknown>, msg?: string) => Assertion<T, TFlags>;
Expand Down Expand Up @@ -233,15 +249,12 @@ declare module '../assertion.js' {
property: OnlyIf<T, object, OnlyIf<
TFlags,
{nested: true},
(name: string, val?: unknown, msg?: string) => Assertion<T, TFlags>,
OnlyIf<
[keyof T],
[never],
((name: PropertyKey) => Assertion<T, TFlags>) &
((name: PropertyKey, val: unknown, msg?: string) => Assertion<T, TFlags>),
((name: PropertyKey) => Assertion<T, TFlags>) &
(<TKey extends keyof T>(name: TKey, val: T[TKey], msg?: string) => Assertion<T, TFlags>)
>
(name: string, val?: unknown, msg?: string) => Assertion<unknown, TFlags>,
{
<TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
(name: PropertyKey): Assertion<unknown>;
<TKey extends keyof T>(name: TKey, val: T[TKey], msg?: string): Assertion<T[TKey]>;
}
>>;

respondTo: (method: string, msg?: string) => Assertion<T, TFlags>;
Expand Down Expand Up @@ -809,7 +822,7 @@ function include<T>(this: Assertion<unknown>, val: T, msg?: string) {
, numErrs = 0;

props.forEach(function (this: Assertion<unknown>, prop) {
var propAssertion = Assertion.create(obj as object);
var propAssertion = Assertion.create(obj as Record<PropertyKey, unknown>);
_.transferFlags(this, propAssertion, true);
flag(propAssertion, 'lockSsfi', true);

Expand Down
2 changes: 1 addition & 1 deletion src/chai/interface/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1671,7 +1671,7 @@ assert.notProperty = function (obj: object, prop: string, msg?: string) {
* @api public
*/

assert.propertyVal = function propertyVal<T, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string) {
assert.propertyVal = function propertyVal<T extends object, TKey extends keyof T>(obj: T, prop: TKey, val: T[TKey], msg?: string) {
Assertion.create(obj, msg, assert.propertyVal, true)
.to.have.property(prop, val);
};
Expand Down

0 comments on commit 015c19a

Please sign in to comment.