Skip to content

Commit

Permalink
Revert "Update: propEq to allow wider-typing for value in comparison" (
Browse files Browse the repository at this point in the history
  • Loading branch information
Harris-Miller authored Mar 2, 2024
1 parent f6b89e2 commit 1595fed
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 318 deletions.
18 changes: 0 additions & 18 deletions test/allPass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,3 @@ expectError(
nickname: 'Blade'
})
);

const isQueen = propEq('Q', 'rank');
const isSpade = propEq('♠︎', 'suit');
const isQueenOfSpades = allPass([isQueen, isSpade]);

isQueenOfSpades({
rank: '2',
suit: '♠︎'
});

const isQueen2 = (x: Record<'rank', string>) => x.rank === 'Q';
const isSpade2 = (x: Record<'suit', string>) => x.suit === '♠︎';
const isQueenOfSpades2 = allPass([isQueen2, isSpade2]);

isQueenOfSpades2({
rank: '2',
suit: '♠︎'
});
29 changes: 3 additions & 26 deletions test/anyPass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ expectType<boolean>(
})
);

expectType<boolean>(
expectError(
isVampire({
age: 21,
garlic_allergy: true,
sun_allergy: true,
fast: null,
fear: undefined
fast: false,
fear: true
})
);

Expand All @@ -48,26 +48,3 @@ expectError(
nickname: 'Blade'
})
);

const isQueen = propEq('Q', 'rank');
const isSpade = propEq('♠︎', 'suit');
const isQueenOfSpades = anyPass([isQueen, isSpade]);

expectType<boolean>(isQueenOfSpades({
rank: '2',
suit: '♠︎'
}));

expectError(isQueenOfSpades({
rank: 2,
suit: '♠︎'
}));

const isQueen2 = (x: Record<'rank', string>) => x.rank === 'Q';
const isSpade2 = (x: Record<'suit', string>) => x.suit === '♠︎';
const isQueenOfSpades2 = anyPass([isQueen2, isSpade2]);

isQueenOfSpades2({
rank: '2',
suit: '♠︎'
});
180 changes: 32 additions & 148 deletions test/propEq.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,153 +3,37 @@ import { expectError, expectType } from 'tsd';
import { propEq } from '../es';

type Obj = {
literals: 'A' | 'B';
unions: number | string;
nullable: number | null | undefined;
optional?: number;
union: 'foo' | 'bar';
str: string;
num: number;
u: undefined;
n: null;
};

const obj = {} as Obj;

//
// literals
//

// happy path works as expected
expectType<boolean>(propEq('A')('literals')(obj));
expectType<boolean>(propEq('A', 'literals')(obj));
expectType<boolean>(propEq('A', 'literals', obj));

// accepts any type that obj[key] can be widened too
expectType<boolean>(propEq('C')('literals')(obj));
expectType<boolean>(propEq('C', 'literals')(obj));
// only propEq(val, key, obj) requests non-widened types
expectError(propEq('C', 'literals', obj));

// rejects if type cannot be widened too
expectError(propEq(2)('literals')(obj));
expectError(propEq(2, 'literals')(obj));
expectError(propEq(2, 'literals', obj));

// manually widened also works
expectType<boolean>(propEq('A' as string)('literals')(obj));
expectType<boolean>(propEq('A' as string, 'literals')(obj));
// only rejects for propEq(val, key, obj), `string` is too wide for 'A' | 'B'
expectError(propEq('A' as string, 'literals', obj));

// rejects if key is not on obj
expectError(propEq('A')('literals')({} as Omit<Obj, 'literals'>));
expectError(propEq('A', 'literals')({} as Omit<Obj, 'literals'>));
expectError(propEq('A', 'literals', {} as Omit<Obj, 'literals'>));

// rejects empty object literal
expectError(propEq('A')('literals')({}));
expectError(propEq('A', 'literals')({}));
expectError(propEq('A', 'literals', {}));

//
// unions
//

// happy path works as expected
expectType<boolean>(propEq('1')('unions')(obj));
expectType<boolean>(propEq('1', 'unions')(obj));
expectType<boolean>(propEq('1', 'unions', obj));

expectType<boolean>(propEq(1)('unions')(obj));
expectType<boolean>(propEq(1, 'unions')(obj));
expectType<boolean>(propEq(1, 'unions', obj));

// rejects if typeof val not part of union type
expectError(propEq(true)('unions')(obj));
expectError(propEq(true, 'unions')(obj));
expectError(propEq(true, 'unions', obj));

// rejects if key is not on obj
expectError(propEq('1')('unions')({} as Omit<Obj, 'unions'>));
expectError(propEq('1', 'unions')({} as Omit<Obj, 'unions'>));
expectError(propEq('1', 'unions', {} as Omit<Obj, 'unions'>));

// rejects empty object literal
expectError(propEq('1')('unions')({}));
expectError(propEq('1', 'unions')({}));
expectError(propEq('1', 'unions', {}));

//
// nullable
//

// happy path works as expected
expectType<boolean>(propEq(1)('nullable')(obj));
expectType<boolean>(propEq(1, 'nullable')(obj));
expectType<boolean>(propEq(1, 'nullable', obj));

expectType<boolean>(propEq(null)('nullable')(obj));
expectType<boolean>(propEq(null, 'nullable')(obj));
expectType<boolean>(propEq(null, 'nullable', obj));

expectType<boolean>(propEq(undefined)('nullable')(obj));
expectType<boolean>(propEq(undefined, 'nullable')(obj));
expectType<boolean>(propEq(undefined, 'nullable', obj));

// rejects if typeof val not part of union type
expectError(propEq(true)('nullable')(obj));
expectError(propEq(true, 'nullable')(obj));
expectError(propEq(true, 'nullable', obj));

// rejects if key is not on obj
expectError(propEq(1)('nullable')({} as Omit<Obj, 'nullable'>));
expectError(propEq(1, 'nullable')({} as Omit<Obj, 'nullable'>));
expectError(propEq(1, 'nullable', {} as Omit<Obj, 'nullable'>));

// rejects empty object literal
expectError(propEq(1)('nullable')({}));
expectError(propEq(1, 'nullable')({}));
expectError(propEq(1, 'nullable', {}));

//
// optional
//

// happy path works as expected
expectType<boolean>(propEq(1)('optional')(obj));
expectType<boolean>(propEq(1, 'optional')(obj));
expectType<boolean>(propEq(1, 'optional', obj));

expectType<boolean>(propEq(undefined)('optional')(obj));
expectType<boolean>(propEq(undefined, 'optional')(obj));
expectType<boolean>(propEq(undefined, 'optional', obj));

// `null` produces error for `optional`. this is expected because typescript strictNullCheck `null !== undefined`
expectError(propEq(null)('optional')(obj));
expectError(propEq(null, 'optional')(obj));
expectError(propEq(null, 'optional', obj));

// rejects if typeof val not part of union type
expectError(propEq(true)('optional')(obj));
expectError(propEq(true, 'optional')(obj));
expectError(propEq(true, 'optional', obj));

// rejects if key is not on obj
expectError(propEq(1)('optional')({} as Omit<Obj, 'optional'>));
expectError(propEq(1, 'optional')({} as Omit<Obj, 'optional'>));
expectError(propEq(1, 'optional', {} as Omit<Obj, 'optional'>));

// rejects empty object literal literal
expectError(propEq(1)('optional')({}));
expectError(propEq(1, 'optional')({}));
expectError(propEq(1, 'optional', {}));

//
// other non-happy paths
//

// rejects unknown key
expectError(propEq(1)('whatever')(obj));
expectError(propEq(1, 'whatever')(obj));
expectError(propEq(1, 'whatever', obj));

// rejects unknown key on emptyu object literal
expectError(propEq(1)('whatever')({}));
expectError(propEq(1, 'whatever')({}));
expectError(propEq(1, 'whatever', {}));
// propEq(val, name, obj)
expectType<boolean>(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));

// propEq(val)(name)(obj)
expectType<boolean>(propEq('foo')('union')({} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope')('union')({} as Obj));
// completely different type fails
expectError(propEq(2)('union')({} as Obj));

// propEq(val)(name), obj)
expectType<boolean>(propEq('foo')('union', {} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope')('union', {} as Obj));
// completely different type fails
expectError(propEq(2)('union', {} as Obj));

// propEq(val, name)(obj)
expectType<boolean>(propEq('foo', 'union')({} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope', 'union')({} as Obj));
// completely different type fails
expectError(propEq(2, 'union')({} as Obj));
61 changes: 3 additions & 58 deletions types/allPass.d.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
// narrowing
export function allPass<T, TF1 extends T, TF2 extends T>(
predicates: [
(a: T) => a is TF1,
(a: T) => a is TF2
]
predicates: [(a: T) => a is TF1, (a: T) => a is TF2]
): (a: T) => a is TF1 & TF2;
export function allPass<T, TF1 extends T, TF2 extends T, TF3 extends T>(
predicates: [
(a: T) => a is TF1,
(a: T) => a is TF2,
(a: T) => a is TF3
],
predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3],
): (a: T) => a is TF1 & TF2 & TF3;
export function allPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T>(
predicates: [
(a: T) => a is TF1,
(a: T) => a is TF2,
(a: T) => a is TF3,
(a: T) => a is TF4
],
predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3, (a: T) => a is TF4],
): (a: T) => a is TF1 & TF2 & TF3 & TF4;
export function allPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T, TF5 extends T>(
predicates: [
Expand All @@ -39,46 +26,4 @@ export function allPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 exte
(a: T) => a is TF6
],
): (a: T) => a is TF1 & TF2 & TF3 & TF4 & TF5 & TF6;
// regular
export function allPass<T1, T2>(
predicates: [
(a: T1) => boolean,
(a: T2) => boolean
],
): (a: T1 & T2) => boolean;
export function allPass<T1, T2, T3>(
predicates: [
(a: T1) => boolean,
(a: T2) => boolean,
(a: T3) => boolean
],
): (a: T1 & T2 & T3) => boolean;
export function allPass<T1, T2, T3, T4>(
predicates: [
(a: T1) => boolean,
(a: T2) => boolean,
(a: T3) => boolean,
(a: T4) => boolean
],
): (a: T1 & T2 & T3 & T4) => boolean;
export function allPass<T1, T2, T3, T4, T5>(
predicates: [
(a: T1) => boolean,
(a: T2) => boolean,
(a: T3) => boolean,
(a: T4) => boolean,
(a: T5) => boolean
],
): (a: T1 & T2 & T3 & T4 & T5) => boolean;
export function allPass<T1, T2, T3, T4, T5, T6>(
predicates: [
(a: T1) => boolean,
(a: T2) => boolean,
(a: T3) => boolean,
(a: T4) => boolean,
(a: T5) => boolean,
(a: T6) => boolean
],
): (a: T1 & T2 & T3 & T4 & T5 & T6) => boolean;
// catch-all
export function allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
Loading

0 comments on commit 1595fed

Please sign in to comment.