Testing and types updates (#39657)

This commit is contained in:
Echo authored and GitHub committed 2026-06-29 14:26:22 +00:00
1 parent b2ebfa13a9
commit 00a8fbc43e
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 });
});
});
+23 -38
View File
@@ -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;
}
+2 -38
View File
@@ -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;