Testing and types updates (#39657)
This commit is contained in:
29 files changed
+263
-191
No files matched your search
@@ -2,13 +2,13 @@
|
||||
import { getNestedProperty } from './objects';
|
||||
|
||||
describe('getNestedProperty', () => {
|
||||
const obj = { a: { b: { c: 42 } } } as const;
|
||||
|
||||
test('returns the value of a nested property if it exists', () => {
|
||||
const obj = { a: { b: { c: 42 } } };
|
||||
expect(getNestedProperty(obj, 'a', 'b', 'c')).toBe(42);
|
||||
});
|
||||
|
||||
test('returns undefined if any part of the path does not exist', () => {
|
||||
const obj = { a: { b: { c: 42 } } };
|
||||
expect(getNestedProperty(obj, 'a', 'x', 'c')).toBeUndefined();
|
||||
expect(getNestedProperty(obj, 'a', 'b', 'x')).toBeUndefined();
|
||||
expect(getNestedProperty(obj, 'x', 'b', 'c')).toBeUndefined();
|
||||
@@ -20,8 +20,7 @@ describe('getNestedProperty', () => {
|
||||
expect(getNestedProperty('string', 'a', 'b')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined if no keys are provided', () => {
|
||||
const obj = { a: 1 };
|
||||
expect(getNestedProperty(obj)).toBeUndefined();
|
||||
test('returns the object if no keys are provided', () => {
|
||||
expect(getNestedProperty({ a: 1 })).toStrictEqual({ a: 1 });
|
||||
});
|
||||
});
|
||||
@@ -1,52 +1,37 @@
|
||||
import { isPlainObject } from '@reduxjs/toolkit';
|
||||
|
||||
export type RecordObject = Record<PropertyKey, unknown>;
|
||||
import type { Get, IsUnknown, UnknownRecord } from 'type-fest';
|
||||
|
||||
export function isRecordObject(obj: unknown): obj is RecordObject {
|
||||
export function isRecordObject(obj: unknown): obj is UnknownRecord {
|
||||
return isPlainObject(obj);
|
||||
}
|
||||
|
||||
type NestedProperty<T, K extends readonly PropertyKey[]> = K extends readonly [
|
||||
infer Head,
|
||||
...infer Tail,
|
||||
]
|
||||
? Head extends keyof NonNullable<T>
|
||||
? Tail extends readonly PropertyKey[]
|
||||
? NestedProperty<NonNullable<T>[Head], Tail>
|
||||
: NonNullable<T>[Head]
|
||||
: undefined
|
||||
: T;
|
||||
type NestedProperty<TObject, TKeys extends readonly string[]> =
|
||||
IsUnknown<TObject> extends true
|
||||
? unknown
|
||||
: IsUnknown<Get<TObject, TKeys>> extends true
|
||||
? undefined
|
||||
: Get<TObject, TKeys>;
|
||||
|
||||
export function getNestedProperty<TObject>(object: TObject): TObject;
|
||||
export function getNestedProperty<
|
||||
TObject extends RecordObject,
|
||||
const TKeys extends readonly PropertyKey[],
|
||||
>(object: TObject, ...keys: TKeys): NestedProperty<TObject, TKeys> | undefined;
|
||||
export function getNestedProperty(
|
||||
object: unknown,
|
||||
...keys: PropertyKey[]
|
||||
): unknown;
|
||||
export function getNestedProperty(
|
||||
object: unknown,
|
||||
...keys: PropertyKey[]
|
||||
): unknown {
|
||||
if (!isRecordObject(object) || keys.length === 0) {
|
||||
return undefined;
|
||||
TObject,
|
||||
const TKeys extends readonly string[],
|
||||
>(object: TObject, ...keys: TKeys): NestedProperty<TObject, TKeys>;
|
||||
export function getNestedProperty(object: unknown, ...keys: readonly string[]) {
|
||||
if (keys.length === 0) {
|
||||
return object;
|
||||
}
|
||||
|
||||
const remainingKeys = [...keys];
|
||||
let currentObject: RecordObject = object;
|
||||
while (remainingKeys.length > 0) {
|
||||
const currentKey = remainingKeys.shift();
|
||||
if (currentKey !== undefined && currentKey in currentObject) {
|
||||
const nextObject = currentObject[currentKey];
|
||||
if (isRecordObject(nextObject)) {
|
||||
currentObject = nextObject;
|
||||
continue;
|
||||
} else if (remainingKeys.length === 0) {
|
||||
return nextObject;
|
||||
}
|
||||
let currentValue = object;
|
||||
|
||||
for (const key of keys) {
|
||||
if (!isRecordObject(currentValue) || !(key in currentValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
currentValue = currentValue[key];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return currentValue;
|
||||
}
|
||||
@@ -1,41 +1,5 @@
|
||||
/**
|
||||
* Extend an existing type and make some of its properties required or optional.
|
||||
* @example
|
||||
* interface Person {
|
||||
* name: string;
|
||||
* age?: number;
|
||||
* likesIceCream?: boolean;
|
||||
* }
|
||||
*
|
||||
* type PersonWithSomeRequired = SomeRequired<Person, 'age' | 'likesIceCream' >;
|
||||
* type PersonWithSomeOptional = SomeOptional<Person, 'name' >;
|
||||
*/
|
||||
import type { SetOptional } from 'type-fest';
|
||||
|
||||
export type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]?: DeepPartial<T[K]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
export type SomeRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
||||
export type SomeOptional<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> &
|
||||
Partial<Pick<T, K>>;
|
||||
|
||||
export type RequiredExcept<T, K extends keyof T> = SomeOptional<Required<T>, K>;
|
||||
|
||||
export type OmitValueType<T, V> = {
|
||||
[K in keyof T as T[K] extends V ? never : K]: T[K];
|
||||
};
|
||||
|
||||
export type OmitUnion<TUnion, TBase> = TBase & Omit<TUnion, keyof TBase>;
|
||||
|
||||
export type PickValueType<T, V> = {
|
||||
[K in keyof T as T[K] extends V | undefined ? K : never]: T[K];
|
||||
};
|
||||
export type RequiredExcept<T, K extends keyof T> = SetOptional<Required<T>, K>;
|
||||
|
||||
export type AnyFunction = (...args: never) => unknown;
|
||||
|
||||
export type SnakeToCamelCase<S extends string> =
|
||||
S extends `${infer T}_${infer U}`
|
||||
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
|
||||
: S;
|
||||
Reference in new issue
Block a user