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

Updating Types on Matchers #4

Open
FullPint opened this issue Mar 19, 2024 · 0 comments
Open

Updating Types on Matchers #4

FullPint opened this issue Mar 19, 2024 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@FullPint
Copy link

Matchers

toBe

type toBe = (a: unknown, b: unknown) => boolean

Unless the intended purpose of the above is to test two possibly different types, the function parameters should be unambiguous and of the same type.

type toBe = <Type>(a: Type, b: Type) => boolean

Examples:

declare function f(a: unknown, b: unknown): boolean
declare function g<Type>(a: Type, b: Type): boolean

f(1, '1') // compiler does not complain
g(1, '1') // compiler does complain 

toBeDefined

type toBeDefined = (a:  any) => boolean

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeDefined = <Type>(a: Type) => boolean

toBeFalse

type toBeFalse = (a:  any) => boolean

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeFalse = <Type>(a: Type) => boolean

toBeInstanceOf

type toBeInstanceOf = (a: unknown, b: Function): boolean

This one is a bit difficult due to duck typing. That is that if two class A and B have the same interface, then even with restrictive type checking an instance of A can be passed as argument against the constructor of B and vice versa. The runtime will still catch this due to the use of instaceof. In the proposed new typing the arguments are spread and of the type never. Never is okay here because we do not need to know the arguments and covers different arguments along with no arguments.

// the most restrictive: ensures from a static perspective that instance is of the constructor. 
// This is not perfect due to duck typing,

type toBeInstanceOf = 
   <Type, Constructor extends { new (...args : never[]): Type}>
       (constructor: Constructor, instance: InstanceType<Constructor>): boolean

Implementation

function toBeInstanceOf<Type, Constructor extends { new (...args : never[]): Type}>(constructor: Constructor, instance: InstanceType<Constructor>): boolean {
    return  (instance instanceof constructor) ||  (instance?.constructor === constructor)
}

Examples:

class A {
  size: number
  constructor(initialSize: number) {
    this.size = initialSize;
  }
}

class B {
  size: string
  constructor(initialSize: string) {
    this.size = initialSize;
  }
}

class C {
  size: number
  constructor() {
    this.size = 0
  }
}

class D {
  size: number
  shape: string
  constructor(size: number, shape: string) {
    this.size = size
    this.shape = shape
  }
}


const a = new A(1)
const b = new B('1')
const c = new C()
const d = new D(1, 'l')

console.log(toBeInstanceOf(A, a)) // base case with arguments
console.log(toBeInstanceOf(A, b)) // this is caught as A and B interfaces do not have the same shape
console.log(toBeInstanceOf(A, c)) // duck typing does not catch this, both A and C have the same shape
console.log(toBeInstanceOf(C, c)) // no args works
console.log(toBeInstanceOf(D, d)) // more than one arg works

// duck typing does not catch this as D has { size: number } in its interface 
// and { size: number } is the interface of A
console.log(toBeInstanceOf(A, d)) 

// This does not work as A does not have the same shape as D
// A is missing { shape: string }
console.log(toBeInstanceOf(D, a))

toBeNan

type toBeNaN = (a: string | number) => boolean 

Only a small update needed if there is a desire to keep function signatures uniform.

type toBeNaN = <Type extends string | number>(a: Type) => boolean

toBeNull

type toBeNull = (a: any) => boolean 

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeNull = <Type>(a: Type) => boolean

toBeTrue

type toBeTrue = (a: any) => boolean 

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeTrue = <Type>(a: Type) => boolean

toBeTypeOf

type toBeTypeOf = (a: any, b:  any) => boolean 

This just needs a small update to ensure that both arguments are statically the same type. It may still catch errors in the runtime.

type toBeTypeOf = <Type>(a: Type, b: Type) => boolean

toBeUndefined

type toBeUndefined = (a: any) => boolean 

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeUndefined = <Type>(a: Type) => boolean

toContain

type toContain = (a: any[] | { [key: string]: any }, b: any) => boolean 

This function can be a bit more specific, and with using function overloads you can ensure that it works for both object types with heterogeneous keys/values, along with heterogeneous arrays.

type toContain = (<T, K extends keyof T, V extends T[K]>(target: T, val: V): => boolean) |  
        (<V>(target: V[], val: V) =>  boolean)

Implementation:

function toContain<T, K extends keyof T, V extends T[K]>(target: T, val: V): boolean;
function toContain<V>(target: V[], val: V): boolean 
{
  if (Array.isArray(target)) return target.some((v) => v === val)
  else return Object.values<V>(target).some((v) => v === val)
}

Examples:

interface A { a: string, 'b': number, 1: symbol }
declare const a: A;
declare const b: Array<number>
declare const c: Array<A>

toContain(a, '') // compiles
toContain(b, 1) // compiles
toContain(c, a) // compiles

toEqual

type toEqual = (a: any, b: any) => boolean 

This just needs a small update if an only if getting rid of the 'no implicit any' error is found to be necessary.

type toBeUndefined = <Type>(a: Type, b: Type) => boolean

One thing to take note of is the current implementation does not check for deep equality, and it may be worth while creating a toBeDeepEqual matcher

toHaveLength

type toHaveLength = (a: unknown, b: number) => boolean

This could be a bit more specific. One signature could be anything that has the length property, allowing you to test anything with length, not just arrays. And another signature for any type of object. Does this need to also handle length of numbers?

type toHaveLength = <T extends object>(target: T, length: number) => boolean | 
   <T extends string>(target: T, length: number) => boolean |
   <T extends { length: number }>(target: T, length: number) => boolean

Implementation

function toHaveLength<T extends object>(target: T, length: number): boolean
function toHaveLength<T extends string>(target: T, length: number): boolean
function toHaveLength<T extends { length: number }>(target: T, length: number): boolean
{
  if (target.hasOwnProperty('length') === false && typeof target === 'object') 
    return Object.values(target).length  === length
  else return target.length === length;
}

Examples

declare const arr: Array<number | string>
declare const obj: {}
declare const str: string
declare const num: number

class A {
  length: number = 2
  
  increaseLength(): void {
    length++
  }

  decreaseLength(): void {
    length--
  }
}

declare const a: A

toHaveLength(arr, 1) // compiles
toHaveLength(obj, 0) // compiles
toHaveLength(str, 3) // compiles
toHaveLength(num, 0) // does not compile
toHaveLength(a, 10) // compiles

toStrictlyEqual

type toStrictlyEqual = (a: any, b:  any) => boolean 

This just needs a small update to ensure that both arguments are statically the same type. It may still catch errors in the runtime.

type toStrictlyEqual = <Type>(a: Type, b: Type) => boolean

toThrow

This one is a little difficult to figure out and determining the spec would make it much easier to refactor. Let's circle back to it.

@ElijahKotyluk ElijahKotyluk added the enhancement New feature or request label Mar 20, 2024
@ElijahKotyluk ElijahKotyluk self-assigned this Mar 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants