Skip to content

Commit

Permalink
Feat(@inquirer/type): Add new utility types to @inquirer/type + vites…
Browse files Browse the repository at this point in the history
…t type tests
  • Loading branch information
SBoudrias committed Jul 14, 2024
1 parent b43cd2d commit a48d2bd
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 70 deletions.
21 changes: 7 additions & 14 deletions packages/prompts/prompts.test.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, it, expect } from 'vitest';
import { Expect, Equal } from '@inquirer/type';
import { describe, it, expect, expectTypeOf } from 'vitest';
import {
checkbox,
confirm,
Expand All @@ -24,16 +23,10 @@ describe('@inquirer/prompts', () => {
expect(select).toBeTypeOf('function');
expect(Separator).toBeTypeOf('function');
});
});

/**
* Type assertions to validate the interfaces.
*/
export interface Test {
1: Expect<
Equal<
NonNullable<Parameters<typeof checkbox>[0]['theme']>['helpMode'],
NonNullable<Parameters<typeof select>[0]['theme']>['helpMode']
>
>;
}
it('checkbox and select have matching helpMode', () => {
expectTypeOf<
NonNullable<Parameters<typeof checkbox>[0]['theme']>['helpMode']
>().toEqualTypeOf<NonNullable<Parameters<typeof select>[0]['theme']>['helpMode']>();
});
});
58 changes: 2 additions & 56 deletions packages/type/src/index.mts
Original file line number Diff line number Diff line change
@@ -1,56 +1,2 @@
import * as readline from 'node:readline';
import MuteStream from 'mute-stream';

export type InquirerReadline = readline.ReadLine & {
output: MuteStream;
input: NodeJS.ReadableStream;
clearLine: (dir: 0 | 1 | -1) => void; // https://nodejs.org/api/readline.html#rlclearlinedir
};

export class CancelablePromise<T> extends Promise<T> {
public cancel: () => void = () => {};
}

export type Prettify<T> = {
[K in keyof T]: T[K];
// eslint-disable-next-line @typescript-eslint/ban-types
} & {};

export type PartialDeep<T> = T extends object
? {
[P in keyof T]?: PartialDeep<T[P]>;
}
: T;

export type Context = {
input?: NodeJS.ReadableStream;
output?: NodeJS.WritableStream;
clearPromptOnDone?: boolean;
};

export type Prompt<Value, Config> = (
config: Config,
context?: Context,
) => CancelablePromise<Value>;

/**
* Utility types used for writing tests
*
* Equal<A, B> checks that A and B are the same type, and returns
* either `true` or `false`.
*
* You can use it in combination with `Expect` to write type
* inference unit tests:
*
* ```ts
* type t = Expect<
* Equal<Partial<{ a: string }>, { a?: string }>
* >
* ```
*/
export type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

export type Expect<T extends true> = T;

export type Not<T extends boolean> = T extends true ? false : true;
export * from './inquirer.mjs';
export * from './utils.mjs';
23 changes: 23 additions & 0 deletions packages/type/src/inquirer.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as readline from 'node:readline';
import MuteStream from 'mute-stream';

export class CancelablePromise<T> extends Promise<T> {
public cancel: () => void = () => {};
}

export type InquirerReadline = readline.ReadLine & {
output: MuteStream;
input: NodeJS.ReadableStream;
clearLine: (dir: 0 | 1 | -1) => void; // https://nodejs.org/api/readline.html#rlclearlinedir
};

export type Context = {
input?: NodeJS.ReadableStream;
output?: NodeJS.WritableStream;
clearPromptOnDone?: boolean;
};

export type Prompt<Value, Config> = (
config: Config,
context?: Context,
) => CancelablePromise<Value>;
25 changes: 25 additions & 0 deletions packages/type/src/utils.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */

export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};

export type PartialDeep<T> = T extends object
? {
[P in keyof T]?: PartialDeep<T[P]>;
}
: T;

export type LiteralUnion<T extends F, F = string> = T | (F & {});
export type KeyUnion<T> = LiteralUnion<Extract<keyof T, string>>;

export type DistributiveMerge<A, B> = A extends any
? Prettify<Omit<A, keyof B> & B>
: never;

export type UnionToIntersection<T> = (
T extends any ? (input: T) => void : never
) extends (input: infer Intersection) => void
? Intersection
: never;
28 changes: 28 additions & 0 deletions packages/type/type.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expectTypeOf } from 'vitest';
import type {
DistributiveMerge,
PartialDeep,
UnionToIntersection,
} from './src/index.mjs';

test('DistributiveMerge', () => {
expectTypeOf<
DistributiveMerge<{ a: string; b: string }, { b: number }>
>().toEqualTypeOf<{
a: string;
b: number;
}>();
});

test('PartialDeep', () => {
expectTypeOf<PartialDeep<{ a: string }>>().toEqualTypeOf<{ a?: string }>();
expectTypeOf<PartialDeep<{ a: { b: string } }>>().toEqualTypeOf<{
a?: { b?: string };
}>();
});

test('UnionToIntersection', () => {
expectTypeOf<UnionToIntersection<{ x: string } | { y: number }>>().toEqualTypeOf<
{ x: string } & { y: number }
>();
});

0 comments on commit a48d2bd

Please sign in to comment.