diff --git a/test/allPass.test.ts b/test/allPass.test.ts index 37040dd..e5d1c62 100644 --- a/test/allPass.test.ts +++ b/test/allPass.test.ts @@ -35,7 +35,7 @@ expectType( }) ); -expectError( +expectType( isVampire({ age: 40, garlic_allergy: true, diff --git a/test/anyPass.test.ts b/test/anyPass.test.ts index ff46aa5..e063c54 100644 --- a/test/anyPass.test.ts +++ b/test/anyPass.test.ts @@ -25,7 +25,7 @@ expectType( }) ); -expectError( +expectType( isVampire({ age: 21, garlic_allergy: true, @@ -35,7 +35,7 @@ expectError( }) ); -expectError( +expectType( isVampire({ age: 40, garlic_allergy: true, diff --git a/test/propEq.test.ts b/test/propEq.test.ts index dc5b909..4f0f5fa 100644 --- a/test/propEq.test.ts +++ b/test/propEq.test.ts @@ -1,39 +1,129 @@ import { expectError, expectType } from 'tsd'; -import { propEq } from '../es'; +import {__, propEq} from '../es'; type Obj = { union: 'foo' | 'bar'; str: string; - num: number; + int: number; + numLike: number | `${number}`; + optional?: string; + nullable: string | null; u: undefined; n: null; }; +type NumArr = number[]; +// ###################### // propEq(val, name, obj) expectType(propEq('foo', 'union', {} as Obj)); -// non-union string fails -expectError(propEq('nope', 'union', {} as Obj)); -// completely different type fails -expectError(propEq(2, 'union', {} as Obj)); +expectType(propEq('1' as string, 'union', {} as Obj)); +// union of number with literal types should work fine +expectType(propEq(1, 'numLike', {} as Obj)); +expectType(propEq('1', 'numLike', {} as Obj)); +// optional types doesn't fire an error, if passed correct types +expectType(propEq('str', 'optional', {} as Obj)); +expectType(propEq(undefined, 'optional', {} as Obj)); +// fires an error only on wrong type +expectError(propEq(1, 'optional', {} as Obj)); +expectError(propEq(null, 'optional', {} as Obj)); +// nullable types doesn't fire an error, if passed correct types +expectType(propEq('str', 'nullable', {} as Obj)); +expectType(propEq(null, 'nullable', {} as Obj)); +// fires an error only on wrong type +expectError(propEq(1, 'nullable', {} as Obj)); +expectError(propEq(undefined, 'nullable', {} as Obj)); +// unknown field names fails +expectError(propEq('foo', 'unknown', {} as Obj)); +// should work with arrays as well +expectType(propEq(1, 0, [] as NumArr)); +// numeric array should expect only numbers +expectError(propEq('foo', 0, [] as NumArr)); +// array can't accept string as prop name +expectError(propEq(1, 'foo', [] as NumArr)); +// ###################### // propEq(val)(name)(obj) expectType(propEq('foo')('union')({} as Obj)); -// 'nope' is inferred as 'string' here. -expectType(propEq('nope')('union')({} as Obj)); -// completely different type fails -expectError(propEq(2)('union')({} as Obj)); +expectType(propEq('nope' as string)('union')({} as Obj)); +// since we use an exact literal type, it should fire an error +expectError(propEq('nope')('union')({} as Obj)); +// union of number with literal types should work fine +expectType(propEq(1)('numLike')({} as Obj)); +expectType(propEq('1')('numLike')({} as Obj)); +// optional types doesn't fire an error, if passed correct types +expectType(propEq('str')('optional')({} as Obj)); +expectType(propEq(undefined)('optional')({} as Obj)); +// fires an error only on wrong type +expectError(propEq(1)('optional')({} as Obj)); +expectError(propEq(null)('optional')({} as Obj)); +// nullable types doesn't fire an error, if passed correct types +expectType(propEq('str')('nullable')({} as Obj)); +expectType(propEq(null)('nullable')({} as Obj)); +// fires an error only on wrong type +expectError(propEq(1)('nullable')({} as Obj)); +expectError(propEq(undefined)('nullable')({} as Obj)); +// unknown field names fails +expectError(propEq('foo')('unknown')({} as Obj)); -// propEq(val)(name), obj) +// ###################### +// propEq(val)(name, obj) expectType(propEq('foo')('union', {} as Obj)); -// 'nope' is inferred as 'string' here. -expectType(propEq('nope')('union', {} as Obj)); -// completely different type fails -expectError(propEq(2)('union', {} as Obj)); +expectType(propEq('nope' as string)('union', {} as Obj)); +// since we use an exact literal type, it should fire an error +expectError(propEq('nope')('union', {} as Obj)); +// union of number with literal types should work fine +expectType(propEq(1)('numLike', {} as Obj)); +expectType(propEq('1')('numLike', {} as Obj)); +// optional types doesn't fire an error, if passed correct types +expectType(propEq('str')('optional', {} as Obj)); +expectType(propEq(undefined)('optional', {} as Obj)); +// fires an error only on wrong type +expectError(propEq(1)('optional', {} as Obj)); +expectError(propEq(null)('optional', {} as Obj)); +// nullable types doesn't fire an error, if passed correct types +expectType(propEq('str')('nullable', {} as Obj)); +expectType(propEq(null)('nullable', {} as Obj)); +// fires an error only on wrong type +expectError(propEq(1)('nullable', {} as Obj)); +expectError(propEq(undefined)('nullable', {} as Obj)); +// unknown field names fails +expectError(propEq('foo')('unknown', {} as Obj)); +// ###################### // propEq(val, name)(obj) expectType(propEq('foo', 'union')({} as Obj)); -// 'nope' is inferred as 'string' here. -expectType(propEq('nope', 'union')({} as Obj)); -// completely different type fails -expectError(propEq(2, 'union')({} as Obj)); +expectType(propEq('nope' as string, 'union')({} as Obj)); +// since we use an exact literal type, it should fire an error +expectType(propEq('nope', 'union')({} as Obj)); +// union of number with literal types should work fine +expectType(propEq(1, 'numLike')({} as Obj)); +expectType(propEq('1', 'numLike')({} as Obj)); +// optional types doesn't fire an error, if passed correct types +expectType(propEq('str', 'optional')({} as Obj)); +expectType(propEq(undefined, 'optional')({} as Obj)); +// fires an error only on wrong type +expectType(propEq(1, 'optional')({} as Obj)); +expectType(propEq(null, 'optional')({} as Obj)); +// nullable types doesn't fire an error, if passed correct types +expectType(propEq('str', 'nullable')({} as Obj)); +expectType(propEq(null, 'nullable')({} as Obj)); +// fires an error only on wrong type +expectType(propEq(1, 'nullable')({} as Obj)); +expectType(propEq(undefined, 'nullable')({} as Obj)); +// unknown field names fails +expectError(propEq('foo', 'unknown')({} as Obj)); + +// ########################## +// propEq(__, name, obj)(val) +expectType(propEq(__, 'union', {} as Obj)('foo')); +// propEq(val, __, obj)(val) +expectType(propEq('foo', __, {} as Obj)('union')); +// propEq(__, __, obj)(val, name) +expectType(propEq(__, __, {} as Obj)('foo', 'union')); +// propEq(__, __, obj)(val)(name) +expectType(propEq(__, __, {} as Obj)('foo')('union')); + +expectError(propEq('foo', __, {} as Obj)('unknown')); +expectError(propEq(__, __, {} as Obj)('foo', 'unknown')); +expectError(propEq(__, __, {} as Obj)('foo')('unknown')); diff --git a/types/propEq.d.ts b/types/propEq.d.ts index e667fb2..ba1bd06 100644 --- a/types/propEq.d.ts +++ b/types/propEq.d.ts @@ -1,6 +1,31 @@ -export function propEq(val: T): { - (name: K): (obj: Record) => boolean; - (name: K, obj: Record): boolean; +import { Placeholder } from 'ramda'; +import { WidenLiterals } from '../util/tools'; + +export function propEq(__: Placeholder): never; +export function propEq(val: V): { + (name: K): (array: V extends U[K] ? V[] : never) => boolean; + >(name: K): >>(obj: string extends V ? U : V extends U[K] ? U : never) => boolean; + (__: Placeholder, obj: U): (name: string extends V ? U : V extends U[K] ? U : never) => boolean; + (name: K, obj: string extends V ? U : V extends U[K] ? U : never): boolean; }; -export function propEq(val: T, name: K): (obj: Record) => boolean; -export function propEq(val: U[K], name: K, obj: U): boolean; +export function propEq(__: Placeholder, name: K): { + (__: Placeholder, array: U): (val: U[K]) => boolean; + (val: U[K], array: U): boolean + (val: V): (array: V[]) => boolean +}; +export function propEq>(__: Placeholder, name: K): { + >(__: Placeholder, obj: U): (val: U[K]) => boolean; + >(val: U[K], obj: U): boolean; + (val: V): (obj: Partial>) => boolean; +}; +export function propEq(val: V, name: K): (array: V[]) => boolean; +export function propEq(val: V, name: K): >>(obj: U) => V extends U[K] ? boolean : never; + +export function propEq(__: Placeholder, ___: Placeholder, obj: U): { + (__: Placeholder, name: K): (val: U[K]) => boolean; + (val: U[K], name: K): boolean; + (val: U[K]): (name: K) => boolean; +}; +export function propEq(__: Placeholder, name: K, obj: U): (val: U[K]) => boolean; +export function propEq(val: U[K], __: Placeholder, obj: U): (name: K) => boolean; +export function propEq(val: WidenLiterals, name: K, obj: U): boolean;