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

Revert "Update: propEq to allow wider-typing for value in comparison" #99

Merged
merged 1 commit into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading