Skip to content
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
4 changes: 2 additions & 2 deletions src/types/PropsWithType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-type-alias
export type PropsWithType<O, T> = keyof Pick<O, {
export type PropsWithType<O, T> = Exclude<keyof Pick<O, {
[K in keyof O]: O[K] extends T ? K : never;
}[keyof O]>;
}[keyof O]>, undefined>;
44 changes: 43 additions & 1 deletion tests/types/PropsWithType.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { expect } from 'chai';
import { PropsWithType } from '../../src/index';
import { isUndefined } from 'util';

describe('PropsWithType', () => {

// NOTE This test does not really check much at runtime. The real value is in the type
// NOTE These tests do not really check much at runtime. The real value is in the type
// checking that happens by *using* the `PropsWithType` type in this test. If we break
// the definition of `PropsWithType` so that it no longer selects the correct
// properties, the TypeScript compiler will complain about this test.

it('allows property keys that have a given type', () => {
interface TestType {
readonly prop: string;
Expand All @@ -25,4 +27,44 @@ describe('PropsWithType', () => {
expect(getNumber(obj, 'additionalProp')).to.strictlyEqual(2);
});

it('correctly defines types for types that have optional params', () => {
interface TestType {
readonly prop: string;
readonly anotherProp: number;
readonly additionalProp?: number;
}

function getNumber(from: TestType, field: PropsWithType<TestType, number | undefined>): number {
// `field` will only be allowed to be `anotherProp` or `additionalProp`. Should
// note that `from[field]` is `number | undefined`.
const val = from[field];

return isUndefined(val) ? 0 : val;
}

let obj: TestType = { prop: 'test', anotherProp: 1, additionalProp: 2 };

expect(getNumber(obj, 'anotherProp')).to.strictlyEqual(1);
expect(getNumber(obj, 'additionalProp')).to.strictlyEqual(2);

// NOTE: leaving the following cases commented out as we don't have an automated way
// to test types. This package cannot ship with these uncommented as they report the
// error "X is declared but never used.ts(6196)".

// Should be: "prop"
// type onlyStrings = PropsWithType<TestType, string>;

// Should be: "anotherProp"
// type onlyNumbers = PropsWithType<TestType, number>;

// Should be: "anotherProp" | "additionalProp"
// type numbersOrUndefineds = PropsWithType<TestType, number | undefined>;

// Should be: never
// type undefineds = PropsWithType<TestType, undefined>;

// Should be: never
// type booleans = PropsWithType<TestType, boolean>;
});

});