diff --git a/.changeset/neat-keys-invent.md b/.changeset/neat-keys-invent.md new file mode 100644 index 000000000..9404da9d5 --- /dev/null +++ b/.changeset/neat-keys-invent.md @@ -0,0 +1,6 @@ +--- +'@tanstack/form-core': patch +'@tanstack/react-form': patch +--- + +fix(form-core): fix DeepValue from record values being wrong diff --git a/packages/form-core/src/FieldGroupApi.ts b/packages/form-core/src/FieldGroupApi.ts index 0bc575d67..58639c690 100644 --- a/packages/form-core/src/FieldGroupApi.ts +++ b/packages/form-core/src/FieldGroupApi.ts @@ -353,10 +353,7 @@ export class FieldGroupApi< getFieldValue = >( field: TField, ): DeepValue => { - return this.form.getFieldValue(this.getFormFieldName(field)) as DeepValue< - TFieldGroupData, - TField - > + return this.form.getFieldValue(this.getFormFieldName(field)) as never } /** diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 836b7b49b..5bb957cf2 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -150,10 +150,6 @@ export type DeepKeysAndValuesImpl< ? DeepKeyAndValueObject : TAcc -export type DeepRecord = { - [TRecord in DeepKeysAndValues as TRecord['key']]: TRecord['value'] -} - /** * The keys of an object or array, deeply nested. */ @@ -161,13 +157,45 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] +type ValueMatchingAccessor< + TValue extends AnyDeepKeyAndValue, + TAccessor extends string, +> = TValue extends TValue + ? TAccessor extends TValue['key'] + ? TValue + : never + : never + +type MostSpecificKey = MostSpecificKeyImpl< + TValue, + TValue +> + +type LongerPrefix = `${K}.${string}` | `${K}[${string}` + +type HasLonger = + Extract }> extends never ? false : true + +type MostSpecificKeyImpl< + TValue extends AnyDeepKeyAndValue, + TAll extends AnyDeepKeyAndValue, +> = TValue extends TValue + ? HasLonger extends true + ? never + : TValue['value'] + : never + +type DeepValueImpl = MostSpecificKey< + ValueMatchingAccessor, TAccessor> +> + /** * Infer the type of a deeply nested property within an object or an array. */ -export type DeepValue = unknown extends TValue +export type DeepValue = unknown extends TValue ? TValue : TAccessor extends DeepKeys - ? DeepRecord[TAccessor] + ? DeepValueImpl : never /** diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 2df6827e1..d1ec7d676 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -1,4 +1,4 @@ -import { describe, expectTypeOf, it } from 'vitest' +import { describe, expect, expectTypeOf, it } from 'vitest' import type { DeepKeys, DeepKeysOfType, @@ -6,451 +6,738 @@ import type { FieldsMap, } from '../src/index' -/** - * Properly recognizes that `0` is not an object and should not have subkeys - */ -type TupleSupport = { topUsers: [User, 0, User] } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'topUsers' - | 'topUsers[0]' - | 'topUsers[0].name' - | 'topUsers[0].id' - | 'topUsers[0].age' - | 'topUsers[1]' - | 'topUsers[2]' - | 'topUsers[2].name' - | 'topUsers[2].id' - | 'topUsers[2].age' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0].age' | 'topUsers[1]' | 'topUsers[2].age' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0].name' | 'topUsers[0].id' | 'topUsers[2].name' | 'topUsers[2].id' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0]' | 'topUsers[2]' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly recognizes that a normal number index won't cut it and should be `[number]` prefixed instead - */ -type ArraySupport = { users: User[] } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'users' - | `users[${number}]` - | `users[${number}].name` - | `users[${number}].id` - | `users[${number}].age` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`users[${number}].age`>() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - `users[${number}].name` | `users[${number}].id` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`users[${number}]`>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly handles deep object nesting like so: - */ -type NestedSupport = { meta: { mainUser: User } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'meta' - | 'meta.mainUser' - | 'meta.mainUser.name' - | 'meta.mainUser.id' - | 'meta.mainUser.age' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`meta.mainUser.age`>() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - `meta.mainUser.name` | `meta.mainUser.id` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`meta.mainUser`>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly handles deep partial object nesting like so: - */ -type NestedPartialSupport = { meta?: { mainUser?: User } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'meta' - | 'meta.mainUser' - | 'meta.mainUser.name' - | 'meta.mainUser.id' - | 'meta.mainUser.age' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta.mainUser.age'>() - -/** - * Properly handles `object` edgecase nesting like so: - */ -type ObjectNestedEdgecase = { meta: { mainUser: object } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf( - 0 as never as 'meta' | 'meta.mainUser' | `meta.mainUser.${string}`, -) -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta' | 'meta.mainUser'>() - -/** - * Properly handles `object` edgecase like so: - */ -type ObjectEdgecase = DeepKeys -expectTypeOf(0 as never as ObjectEdgecase).toEqualTypeOf() - -/** - * Properly handles `object` edgecase nesting like so: - */ -type UnknownNestedEdgecase = { meta: { mainUser: unknown } } -expectTypeOf( - 0 as never as 'meta' | 'meta.mainUser' | `meta.mainUser.${string}`, -).toEqualTypeOf(0 as never as DeepKeys) -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta'>() - -/** - * Properly handles discriminated unions like so: - */ -type DiscriminatedUnion = { name: string } & ( - | { variant: 'foo' } - | { variant: 'bar'; baz: boolean } -) -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - 'name' | 'variant' | 'baz' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'name' | 'variant'>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'baz'>() - -type DiscriminatedUnionValueShared = DeepValue -expectTypeOf(0 as never as DiscriminatedUnionValueShared).toEqualTypeOf< - 'foo' | 'bar' ->() -type DiscriminatedUnionValueFixed = DeepValue -expectTypeOf( - 0 as never as DiscriminatedUnionValueFixed, -).toEqualTypeOf() - -/** - * Properly handles `unknown` edgecase like so: - */ -type UnknownEdgecase = DeepKeys -expectTypeOf(0 as never as UnknownEdgecase).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -type NestedKeysExample = DeepValue< - { meta: { mainUser: User } }, - 'meta.mainUser.age' -> -expectTypeOf(0 as never as NestedKeysExample).toEqualTypeOf() - -type NestedNullableObjectCase = { - null: { mainUser: 'name' } | null - undefined: { mainUser: 'name' } | undefined - optional?: { mainUser: 'name' } - mixed: { mainUser: 'name' } | null | undefined -} - -type NestedNullableObjectCaseNull = DeepValue< - NestedNullableObjectCase, - 'null.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseNull).toEqualTypeOf< - 'name' | null ->() -type NestedNullableObjectCaseUndefined = DeepValue< - NestedNullableObjectCase, - 'undefined.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseUndefined).toEqualTypeOf< - 'name' | undefined ->() -type NestedNullableObjectCaseOptional = DeepValue< - NestedNullableObjectCase, - 'undefined.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseOptional).toEqualTypeOf< - 'name' | undefined ->() -type NestedNullableObjectCaseMixed = DeepValue< - NestedNullableObjectCase, - 'mixed.mainUser' -> -expectTypeOf(0 as never as 'name' | null | undefined).toEqualTypeOf( - 0 as never as NestedNullableObjectCaseMixed, -) - -type DoubleNestedNullableObjectCase = { - mixed?: { mainUser: { name: 'name' } } | null | undefined -} -type DoubleNestedNullableObjectA = DeepValue< - DoubleNestedNullableObjectCase, - 'mixed.mainUser' -> -expectTypeOf(0 as never as { name: 'name' } | null | undefined).toEqualTypeOf( - 0 as never as DoubleNestedNullableObjectA, -) -type DoubleNestedNullableObjectB = DeepValue< - DoubleNestedNullableObjectCase, - 'mixed.mainUser.name' -> -expectTypeOf(0 as never as DoubleNestedNullableObjectB).toEqualTypeOf< - 'name' | null | undefined ->() - -type NestedObjectUnionCase = { - normal: - | { a: User } - | { a: string } - | { b: string } - | { c: { user: User } | { user: number } } -} -type NestedObjectUnionA = DeepValue -expectTypeOf(0 as never as NestedObjectUnionA).toEqualTypeOf() -type NestedObjectUnionB = DeepValue -expectTypeOf(0 as never as NestedObjectUnionB).toEqualTypeOf() -type NestedObjectUnionC = DeepValue -expectTypeOf(0 as never as NestedObjectUnionC).toEqualTypeOf() - -type NestedNullableObjectUnionCase = { - nullable: - | { a?: number; b?: { c: boolean } | null } - | { b?: { c: string; e: number } } -} -type NestedNullableObjectUnionA = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.a' -> -expectTypeOf(0 as never as NestedNullableObjectUnionA).toEqualTypeOf< - number | undefined ->() -type NestedNullableObjectUnionB = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.b.c' -> -expectTypeOf(0 as never as string | boolean | null | undefined).toEqualTypeOf( - 0 as never as NestedNullableObjectUnionB, -) -type NestedNullableObjectUnionC = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.b.e' -> -expectTypeOf(0 as never as NestedNullableObjectUnionC).toEqualTypeOf< - number | undefined ->() - -type NestedArrayExample = DeepValue<{ users: User[] }, 'users[0].age'> -expectTypeOf(0 as never as NestedArrayExample).toEqualTypeOf() - -type NestedLooseArrayExample = DeepValue< - { users: User[] }, - `users[${number}].age` -> -expectTypeOf(0 as never as NestedLooseArrayExample).toEqualTypeOf() - -type NestedArrayUnionExample = DeepValue< - { users: string | User[] }, - 'users[0].age' -> -expectTypeOf(0 as never as NestedArrayUnionExample).toEqualTypeOf() - -type NestedTupleExample = DeepValue< - { topUsers: [User, 0, User] }, - 'topUsers[0].age' -> -expectTypeOf(0 as never as NestedTupleExample).toEqualTypeOf() - -type NestedTupleBroadExample = DeepValue< - { topUsers: User[] }, - `topUsers[${number}].age` -> -expectTypeOf(0 as never as NestedTupleBroadExample).toEqualTypeOf() - -type DeeplyNestedTupleBroadExample = DeepValue< - { nested: { topUsers: User[] } }, - `nested.topUsers[${number}].age` -> -expectTypeOf( - 0 as never as DeeplyNestedTupleBroadExample, -).toEqualTypeOf() - -type SimpleArrayExample = DeepValue -expectTypeOf(0 as never as SimpleArrayExample).toEqualTypeOf() - -type SimpleNestedArrayExample = DeepValue -expectTypeOf(0 as never as SimpleNestedArrayExample).toEqualTypeOf() - -type NestedTupleItemExample = DeepValue< - { topUsers: [User, 0, User] }, - 'topUsers[1]' -> -expectTypeOf(0 as never as NestedTupleItemExample).toEqualTypeOf<0>() - -type ArrayExample = DeepValue<[1, 2, 3], '[1]'> -expectTypeOf(0 as never as ArrayExample).toEqualTypeOf<2>() - -type NonNestedObjExample = DeepValue<{ a: 1 }, 'a'> -expectTypeOf(0 as never as NonNestedObjExample).toEqualTypeOf<1>() - -interface User { - name: string - id: string - age: number -} - -type FormDefinition = { - nested: { - people: User[] - } -} - -type FormDefinitionValue = DeepValue< - FormDefinition, - `nested.people[${number}].name` -> - -expectTypeOf(0 as never as FormDefinitionValue).toEqualTypeOf() - -type DoubleDeepArray = DeepValue< - { - people: { - parents: { - name: string - age: number +describe('DeepKeys, DeepKeysOfType', () => { + it('should support tuples', () => { + type User = { + name: string + id: string + age: number + } + /** + * It should recognize that '0' does not have subkeys + */ + type Tuple = { topUsers: [User, 0, User] } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithDate = DeepKeysOfType + type WithUser = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'topUsers' + | 'topUsers[0]' + | 'topUsers[0].name' + | 'topUsers[0].id' + | 'topUsers[0].age' + | 'topUsers[1]' + | 'topUsers[2]' + | 'topUsers[2].name' + | 'topUsers[2].id' + | 'topUsers[2].age' + >() + + expectTypeOf().toEqualTypeOf< + 'topUsers[0].age' | 'topUsers[1]' | 'topUsers[2].age' + >() + + expectTypeOf().toEqualTypeOf< + | 'topUsers[0].name' + | 'topUsers[0].id' + | 'topUsers[2].name' + | 'topUsers[2].id' + >() + + expectTypeOf().toEqualTypeOf<'topUsers[0]' | 'topUsers[2]'>() + + expectTypeOf().toBeNever() + }) + + it('should support arrays', () => { + type User = { + name: string + id: string + age: number + } + /** + * Properly recognizes that a normal number index won't cut it and should be `[number]` prefixed instead + */ + type Array = { users: User[] } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithUser = DeepKeysOfType + type WithDate = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'users' + | `users[${number}]` + | `users[${number}].name` + | `users[${number}].id` + | `users[${number}].age` + >() + + expectTypeOf().toEqualTypeOf<`users[${number}].age`>() + + expectTypeOf().toEqualTypeOf< + `users[${number}].name` | `users[${number}].id` + >() + + expectTypeOf().toEqualTypeOf<`users[${number}]`>() + + expectTypeOf().toBeNever() + }) + + it('should support nested objects', () => { + type User = { + name: string + id: string + age: number + } + type Nested = { meta: { mainUser: User } } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithUser = DeepKeysOfType + type WithDate = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'meta' + | 'meta.mainUser' + | 'meta.mainUser.name' + | 'meta.mainUser.id' + | 'meta.mainUser.age' + >() + expectTypeOf().toEqualTypeOf<`meta.mainUser.age`>() + + expectTypeOf().toEqualTypeOf< + `meta.mainUser.name` | `meta.mainUser.id` + >() + + expectTypeOf().toEqualTypeOf<`meta.mainUser`>() + + expectTypeOf().toBeNever() + }) + + it('should support nested partial objects', () => { + type User = { + name: string + id: string + age: number + } + type NestedPartial = { meta?: { mainUser?: User } } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithMaybeNumber = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'meta' + | 'meta.mainUser' + | 'meta.mainUser.name' + | 'meta.mainUser.id' + | 'meta.mainUser.age' + >() + + expectTypeOf().toBeNever() + + expectTypeOf().toEqualTypeOf<'meta.mainUser.age'>() + }) + + it('should handle a `object`', () => { + type NestedObject = { meta: { mainUser: object } } + + type Keys = DeepKeys + type WithObject = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'meta' | 'meta.mainUser' | `meta.mainUser.${string}` + >() + + expectTypeOf().toEqualTypeOf<'meta' | 'meta.mainUser'>() + + type ObjectKeys = DeepKeys + expectTypeOf().toBeString() + }) + + it('should handle `unknown`', () => { + type NestedUnknown = { meta: { mainUser: unknown } } + + type Keys = DeepKeys + type WithObject = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'meta' | 'meta.mainUser' | `meta.mainUser.${string}` + >() + + expectTypeOf().toEqualTypeOf<'meta'>() + + type UnknownKeys = DeepKeys + type UnknownWithUnknown = DeepKeysOfType + type UnknownWithObject = DeepKeysOfType + + expectTypeOf().toBeString() + + expectTypeOf().toBeString() + + expectTypeOf().toBeNever() + }) + + it('should handle discriminated unions', () => { + type DiscriminatedUnion = { name: string } & ( + | { variant: 'foo' } + | { variant: 'bar'; baz: boolean } + ) + + type Keys = DeepKeys + type WithString = DeepKeysOfType + type WithBoolean = DeepKeysOfType + + expectTypeOf().toEqualTypeOf<'name' | 'variant' | 'baz'>() + + expectTypeOf().toEqualTypeOf<'name' | 'variant'>() + + expectTypeOf().toEqualTypeOf<'baz'>() + }) + + it('should handle records', () => { + type Value = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + + expectTypeOf>().toEqualTypeOf< + | 'records' + | `records.${string}` + | `records.${string}.a` + | `records.${string}.b` + | `records.${string}.c` + | `records.${string}.c.d` + >() + + expectTypeOf().toEqualTypeOf<`records.${string}.b`>() + + expectTypeOf().toEqualTypeOf< + `records.${string}.a` | `records.${string}.c.d` + >() + }) + + it('should handle objects containing any', () => { + type ObjectWithAny = { + a: any + b: number + obj: { + c: any + d: number + } + } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'a' | 'b' | 'obj' | `a.${string}` | 'obj.c' | `obj.c.${string}` | 'obj.d' + >() + // since any can also be number, It's okay to be included + expectTypeOf().toEqualTypeOf<'a' | 'b' | 'obj.c' | 'obj.d'>() + expectTypeOf().toEqualTypeOf<'a' | 'obj.c'>() + }) +}) + +describe('DeepValue', () => { + it('should handle discriminated unions', () => { + type DiscriminatedUnion = { name: string } & ( + | { variant: 'foo' } + | { variant: 'bar'; baz: boolean } + ) + + type SharedValue = DeepValue + type FixedValue = DeepValue + type FixedValue2 = DeepValue + + expectTypeOf().toEqualTypeOf<'foo' | 'bar'>() + // TODO this might have implications for high-level nullable / undefinable not cascading. + expectTypeOf().toBeBoolean() + expectTypeOf().toBeString() + }) + + it('should handle nested objects', () => { + type User = { + name: string + id: string + age: number + } + type Nested = { meta: { mainUser: User } } + + type ExpectNumber = DeepValue + + expectTypeOf().toBeNumber() + }) + + it('should handle undefined / nullable in objects', () => { + type NestedNullableObjectCase = { + null: { mainUser: 'name' } | null + undefined: { mainUser: 'name' } | undefined + optional?: { mainUser: 'name' } + mixed: { mainUser: 'name' } | null | undefined + } + + type NameOrNullValue = DeepValue + type NameOrUndefinedValue = DeepValue< + NestedNullableObjectCase, + 'undefined.mainUser' + > + type NameOrUndefinedValue2 = DeepValue< + NestedNullableObjectCase, + 'optional.mainUser' + > + type NameOrNil = DeepValue + + expectTypeOf().toEqualTypeOf<'name' | null>() + + expectTypeOf().toEqualTypeOf<'name' | undefined>() + + expectTypeOf().toEqualTypeOf<'name' | undefined>() + + expectTypeOf().toEqualTypeOf<'name' | null | undefined>() + + type DoubleNestedNullableObjectCase = { + mixed?: { mainUser: { name: 'name' } } | null | undefined + } + type ObjectOrNil = DeepValue< + DoubleNestedNullableObjectCase, + 'mixed.mainUser' + > + type NameOrNil2 = DeepValue< + DoubleNestedNullableObjectCase, + 'mixed.mainUser.name' + > + + expectTypeOf().toEqualTypeOf< + { name: 'name' } | null | undefined + >() + + expectTypeOf().toEqualTypeOf<'name' | null | undefined>() + }) + + it('should handle unions in objects', () => { + type User = { + name: string + id: string + age: number + } + type NestedUnion = { + normal: + | { a: User } + | { a: string } + | { b: string } + | { c: { user: User } | { user: number } } + } + type NumberValue = DeepValue + type StringValue = DeepValue + type StringValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeString() + + expectTypeOf().toBeString() + + type NestedNullableUnion = { + mixed: + | { a?: number; b?: { c: boolean } | null } + | { b?: { c: string; e: number } } + } + type NumberOptional = DeepValue + type MixedValue = DeepValue + type NumberOptional2 = DeepValue + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf< + string | boolean | null | undefined + >() + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle nested arrays', () => { + type User = { + name: string + id: string + age: number + } + type NestedArray = { users: User[] } + + type NumberValue = DeepValue + type NumberValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNumber() + }) + + it('should handle nested arrays with unions', () => { + type User = { + name: string + id: string + age: number + } + type NestedArrayUnion1 = { users: string | User[] } + type NestedArrayUnion2 = { users: (string | User)[] } + + type NumberValue = DeepValue + type NumberValue2 = DeepValue + type UserValue = DeepValue + type UserOrString = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle nested tuples', () => { + type User = { + name: string + id: string + age: number + } + + type NestedTuple = { topUsers: [User, 0, User] } + + type NumberValue = DeepValue + type NumberValue2 = DeepValue + type UserValue = DeepValue + type ZeroValue = DeepValue + type UserValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf(0 as const) + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle top-level arrays', () => { + type User = { + name: string + id: string + age: number + } + + type UserValue = DeepValue + // ^? actual: never + type NumberValue = DeepValue + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeNumber() + }) + + it('should allow string and number literals', () => { + type Tuple = ['a', 'b', 'c'] + + type OneValue = DeepValue<{ a: 1 }, 'a'> + + type AValue = DeepValue + type BValue = DeepValue + type CValue = DeepValue + + expectTypeOf().toEqualTypeOf(1 as const) + expectTypeOf().toEqualTypeOf('a' as const) + expectTypeOf().toEqualTypeOf('b' as const) + expectTypeOf().toEqualTypeOf('c' as const) + }) + + it('should handle 2-dimensional arrays', () => { + type User = { + name: string + id: string + age: number + } + + type DoubleArray = { + people: { + parents: User[] }[] - }[] - }, - `people[${0}].parents[${0}].name` -> - -expectTypeOf(0 as never as DoubleDeepArray).toEqualTypeOf() - -// Deepness is infinite error check -type Cart = { - id: number - product: { - id: string - description?: string - price_internet?: number - price_dealer_region?: number - price_dealer?: number - stock: - | { - id: string - quantity: number - isChecked: boolean - }[] - | null - } - quantity: number - isChecked: boolean -}[] - -type Payment_types = { - id: string - title: string - name: string -}[] - -type Shipping_methods = { - id: string - title: string - name: string -}[] - -type Userr = { - id: string - first_name: string | null - email: string | null - avatar: - | string - | ({ - url?: string - } & { + } + + type StringValue = DeepValue< + DoubleArray, + `people[${number}].parents[${number}].name` + > + + expectTypeOf().toBeString() + }) + + it('should handle records', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + + type RecordValue = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type StringValue2 = DeepValue + + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + + it('should handle records with arrays as values', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + + type RecordValue = DeepValue + type FooArrayValue = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue< + RecordExample, + `records.${string}[${number}].a` + > + type NumberValue = DeepValue< + RecordExample, + `records.${string}[${number}].b` + > + type ObjectValue = DeepValue< + RecordExample, + `records.${string}[${number}].c` + > + type StringValue2 = DeepValue< + RecordExample, + `records.${string}[${number}].c.d` + > + + type FooArrayValue2 = DeepValue + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + + it('should handle nested records', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type NestedRecord = Record> + type RecordExample = { + records: NestedRecord + } + + type RecordValue = DeepValue + type RecordValue2 = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type StringValue2 = DeepValue< + RecordExample, + `records.${string}.${string}.c.d` + > + + type RecordValue3 = DeepValue + type RecordValue4 = DeepValue + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf>() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + + it('should not error for large objects', () => { + type Cart = { + id: number + product: { id: string - storage: string - filename_disk: string | null - filename_original: string | null - filename_download: string | null - filename_preview: string | null - filename_thumbnail: string | null - filename_medium: string | null - filename_large: string | null - filename_huge: string | null - filename_icon: string | null - filename_icon_large: string | null - focal_point_y: number | null - }) - | null - // Reference Cart, Payment_types, Shipping_methods - cart: Cart | null - payment_types: Payment_types | null - shipping_methods: Shipping_methods | null -} - -type UserKeys = DeepValue> - -type ObjectWithAny = { - a: any - b: number - obj: { - c: any - d: number - } -} - -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - 'a' | 'b' | 'obj' | `a.${string}` | 'obj.c' | `obj.c.${string}` | 'obj.d' ->() -// since any can also be number, It's okay to be included -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'a' | 'b' | 'obj.c' | 'obj.d' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'a' | 'obj.c' ->() -type AnyObjectExample = DeepValue -expectTypeOf(0 as never as AnyObjectExample).toEqualTypeOf() -type AnyObjectExample2 = DeepValue -expectTypeOf(0 as never as AnyObjectExample2).toEqualTypeOf() -type AnyObjectExample3 = DeepValue -expectTypeOf(0 as never as AnyObjectExample3).toEqualTypeOf<{ - c: any - d: number -}> -type AnyObjectExample4 = DeepValue -expectTypeOf(0 as never as AnyObjectExample4).toEqualTypeOf() -type AnyObjectExample5 = DeepValue -expectTypeOf(0 as never as AnyObjectExample5).toEqualTypeOf() + description?: string + price_internet?: number + price_dealer_region?: number + price_dealer?: number + stock: + | { + id: string + quantity: number + isChecked: boolean + }[] + | null + } + quantity: number + isChecked: boolean + }[] + + type Payment_types = { + id: string + title: string + name: string + }[] + + type Shipping_methods = { + id: string + title: string + name: string + }[] + + type Userr = { + id: string + first_name: string | null + email: string | null + avatar: + | string + | ({ + url?: string + } & { + id: string + storage: string + filename_disk: string | null + filename_original: string | null + filename_download: string | null + filename_preview: string | null + filename_thumbnail: string | null + filename_medium: string | null + filename_large: string | null + filename_huge: string | null + filename_icon: string | null + filename_icon_large: string | null + focal_point_y: number | null + }) + | null + // Reference Cart, Payment_types, Shipping_methods + cart: Cart | null + payment_types: Payment_types | null + shipping_methods: Shipping_methods | null + } + + type UserKeys = DeepValue> + }) + + it('should handle objects containing any', () => { + type ObjectWithAny = { + a: any + b: number + obj: { + c: any + d: number + } + } + + type AnyValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type AnyValue2 = DeepValue + type NumberValue2 = DeepValue + + expectTypeOf().toBeAny() + expectTypeOf().toBeNumber() + expectTypeOf().toEqualTypeOf<{ + c: any + d: number + }> + expectTypeOf().toBeAny() + expectTypeOf().toBeNumber() + }) +}) describe('FieldsMap', () => { it('should map to all available types', () => { diff --git a/packages/react-form/tests/createFormHook.test-d.tsx b/packages/react-form/tests/createFormHook.test-d.tsx index b8a9b3971..db8245d52 100644 --- a/packages/react-form/tests/createFormHook.test-d.tsx +++ b/packages/react-form/tests/createFormHook.test-d.tsx @@ -87,9 +87,9 @@ describe('createFormHook', () => { render: ({ form, initialValues }) => { return (
- + {(field) => { - expectTypeOf(field.state.value).toExtend() + expectTypeOf(field.state.value).toEqualTypeOf() return null }}