Skip to content

Commit

Permalink
fix(core): remove unnecessary conditional type in `RequiredCompareKey…
Browse files Browse the repository at this point in the history
…s` (#56)

* fix(core): remove unnecessary conditional type in `RequiredCompareKeys`, fix base using type in `OptionalCompareKeys`

* fix type using object mapping to union extends

Co-authored-by: L2HYUNN <[email protected]>
  • Loading branch information
minuukang and L2HYUNN authored Sep 17, 2024
1 parent 3369bc9 commit 0af3a1a
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 14 deletions.
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"package.json"
],
"scripts": {
"test": "vitest run",
"test:unit": "vitest --root test/",
"test": "vitest run --typecheck",
"test:unit": "vitest --typecheck --root test/",
"build": "rimraf dist && concurrently \"pnpm:build:*\"",
"build:dist": "tsup",
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
Expand Down
22 changes: 10 additions & 12 deletions packages/core/src/typeUtil.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
export type Prettify<T> = { [K in keyof T]: T[K] } & {};
export type Prettify<T> = Omit<T, never>;
/**
* Extracts the keys that are required when converting from TBase to TResult.
* @example RequiredCompareKeys<{a: string, b?: string}, {a: string, b: string}> // 'b'
*/
export type RequiredCompareKeys<TBase, TResult> = {
[K in keyof TBase | keyof TResult]: K extends keyof TResult
export type RequiredCompareKeys<TBase, TResult> = keyof TResult | keyof TBase extends infer K
? K extends keyof TResult
? K extends keyof TBase
? TBase[K] extends TResult[K]
? never
: K
: undefined extends TResult[K]
? never
: K
: K extends keyof TBase
? never
: never;
}[keyof TBase | keyof TResult];
: never
: never;

/**
* Extracts the keys that are optional when converting from TBase to TResult.
* @example OptionalCompareKeys<{a: string, b?: string}, {a: string, b: string}> // 'a'
*/
type OptionalCompareKeys<TBase, TResult> = {
[K in keyof TBase | keyof TResult]: K extends keyof TResult
export type OptionalCompareKeys<TBase, TResult> = keyof TBase | keyof TResult extends infer K
? K extends keyof TResult
? K extends keyof TBase
? TBase[K] extends TResult[K]
? K
Expand All @@ -31,9 +29,9 @@ type OptionalCompareKeys<TBase, TResult> = {
? K
: never
: K extends keyof TBase
? never
: never;
}[keyof TBase | keyof TResult];
? K
: never
: never;

/**
* Compares TBase with TResult to create a new object that uses TResult's values for required keys,
Expand Down
129 changes: 129 additions & 0 deletions packages/core/test/typeUtil.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { describe, expectTypeOf, test } from 'vitest';
import { CompareMergeContext, OptionalCompareKeys, RequiredCompareKeys } from '../src/typeUtil';

describe('typeUtil test', () => {
describe('RequiredCompareKeys', () => {
describe('When TBase has optional keys and TResult has compare required key', () => {
test('Should return the keys that are required when converting from TBase to TResult', () => {
type Result = RequiredCompareKeys<{ a: string; b?: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'b'>();
});
});

describe('When TBase has no key and TResult has compare required key', () => {
test('Should return the keys that are required when converting from TBase to TResult', () => {
type Result = RequiredCompareKeys<{ a: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'b'>();
});
});

describe('When TBase has no keys', () => {
test('Should return the keys that are required when converting from TBase to TResult', () => {
type Result = RequiredCompareKeys<{}, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'a' | 'b'>();
});
});

describe('When TBase and TResult have no keys', () => {
test('Should return the never', () => {
type Result = RequiredCompareKeys<{}, {}>;
expectTypeOf<Result>().toEqualTypeOf<never>();
});
});

describe('When TBase and TResult have the same type', () => {
test('Should return the never', () => {
type Result = RequiredCompareKeys<{ a: string; b: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<never>();
});
});
});

describe('OptionalCompareKeys', () => {
describe('When TBase has optional keys and TResult has compare required key', () => {
test('Should return the keys that are optional when converting from TBase to TResult', () => {
type Result = OptionalCompareKeys<{ a: string; b?: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'a'>();
});
});

describe('When TBase has no key and TResult has compare required key', () => {
test('Should return the keys that are optional when converting from TBase to TResult', () => {
type Result = OptionalCompareKeys<{ a: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'a'>();
});
});

describe('When TBase has no keys', () => {
test('Should return the never', () => {
type Result = OptionalCompareKeys<{}, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<never>();
});
});

describe('When TBase and TResult have no keys', () => {
test('Should return the never', () => {
type Result = OptionalCompareKeys<{}, {}>;
expectTypeOf<Result>().toEqualTypeOf<never>();
});
});

describe('When TBase and TResult have the same type', () => {
test('Should return all key', () => {
type Result = OptionalCompareKeys<{ a: string; b: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<'a' | 'b'>();
});
});

describe('When TBase has required keys and TResult compare nothing key', () => {
test('Should return the TBase keys', () => {
type Result = OptionalCompareKeys<{ a: string; b: string }, { c: number }>;
expectTypeOf<Result>().toEqualTypeOf<'a' | 'b'>();
});
});
});

describe('CompareMergeContext', () => {
describe('When TBase has optional keys and TResult has compare required key', () => {
test('Should return the new object that uses TResult values for required keys, and TBase values for optional keys', () => {
type Result = CompareMergeContext<{ a: string; b?: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<{ a?: string; b: string }>();
});
});

describe('When TBase has no key and TResult has compare required key', () => {
test('Should return the new object that uses TResult values for required keys, and TBase values for optional keys', () => {
type Result = CompareMergeContext<{ a: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<{ a?: string; b: string }>();
});
});

describe('When TBase has no keys', () => {
test('Should return the TResult type', () => {
type Result = CompareMergeContext<{}, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<{ a: string; b: string }>();
});
});

describe('When TBase and TResult have no keys', () => {
test('Should return the empty object', () => {
type Result = CompareMergeContext<{}, {}>;
expectTypeOf<Result>().toEqualTypeOf<{}>();
});
});

describe('When TBase and TResult have the same type', () => {
test('Should return the new object patial object for that types', () => {
type Result = CompareMergeContext<{ a: string; b: string }, { a: string; b: string }>;
expectTypeOf<Result>().toEqualTypeOf<{ a?: string; b?: string }>();
});
});

describe('When TBase has required keys and TResult compare nothing key', () => {
test('Should return the new object that uses TResult values for required keys, and TBase values for optional keys', () => {
type Result = CompareMergeContext<{ a: string; b: string }, { c: number }>;
expectTypeOf<Result>().toEqualTypeOf<{ a?: string; b?: string; c: number }>();
});
});
});
});

0 comments on commit 0af3a1a

Please sign in to comment.