diff --git a/packages/core/src/types/core/useRegle.types.ts b/packages/core/src/types/core/useRegle.types.ts index aa23a8a5..f47777d5 100644 --- a/packages/core/src/types/core/useRegle.types.ts +++ b/packages/core/src/types/core/useRegle.types.ts @@ -3,14 +3,13 @@ import type { MaybeRef } from 'vue'; import type { CustomRulesDeclarationTree, RegleCollectionRuleDecl, - RegleCollectionRuleDefinition, RegleFormPropertyType, ReglePartialRuleTree, RegleRoot, RegleRuleDecl, RegleRuleDefinition, } from '../rules'; -import type { ExtendOnlyRealRecord, ExtractFromGetter, Maybe, Prettify } from '../utils'; +import type { ArrayElement, ExtendOnlyRealRecord, ExtractFromGetter, Maybe, Prettify } from '../utils'; import type { RegleShortcutDefinition, RegleValidationGroupEntry } from './options.types'; export interface Regle< @@ -50,21 +49,24 @@ export type DeepReactiveState> = { export type DeepSafeFormState< TState extends Record, - TRules extends ReglePartialRuleTree, + TRules extends ReglePartialRuleTree | undefined, > = [unknown] extends [TState] ? {} - : Prettify< - { - [K in keyof TState as IsPropertyOutputRequired extends false ? K : never]?: SafeProperty< - TState[K], - TRules[K] - >; - } & { - [K in keyof TState as IsPropertyOutputRequired extends false ? never : K]-?: NonNullable< - SafeProperty - >; - } - >; + : TRules extends undefined + ? TState + : TRules extends ReglePartialRuleTree + ? Prettify< + { + [K in keyof TState as IsPropertyOutputRequired extends false + ? K + : never]?: SafeProperty; + } & { + [K in keyof TState as IsPropertyOutputRequired extends false + ? never + : K]-?: NonNullable>; + } + > + : TState; type FieldHaveRequiredRule = unknown extends TRule['required'] ? false @@ -76,25 +78,33 @@ type FieldHaveRequiredRule = unknown extends TRule[ : false : false; -type ObjectHaveAtLeastOneRequiredField> = +type ObjectHaveAtLeastOneRequiredField< + TState extends Record, + TRule extends ReglePartialRuleTree, +> = TState extends Maybe ? { - [K in keyof TRule]: TRule[K] extends RegleRuleDecl ? FieldHaveRequiredRule : false; - }[keyof TRule] + [K in keyof NonNullable]-?: IsPropertyOutputRequired[K], TRule[K]>; + }[keyof TState] extends false + ? false + : true : true; -type ArrayHaveAtLeastOneRequiredField> = +type ArrayHaveAtLeastOneRequiredField, TRule extends RegleCollectionRuleDecl> = TState extends Maybe - ? { - [K in keyof ExtractFromGetter]: ExtractFromGetter[K] extends RegleRuleDecl - ? FieldHaveRequiredRule[K]> - : false; - }[keyof ExtractFromGetter] + ? + | FieldHaveRequiredRule extends RegleRuleDecl ? Omit : {}> + | ObjectHaveAtLeastOneRequiredField< + ArrayElement>, + ExtractFromGetter extends undefined ? {} : NonNullable> + > extends false + ? false + : true : true; export type SafeProperty | undefined> = [unknown] extends [TState] ? unknown - : TRule extends RegleCollectionRuleDefinition + : TRule extends RegleCollectionRuleDecl ? TState extends Array> ? DeepSafeFormState>[] : TState @@ -112,21 +122,24 @@ export type IsPropertyOutputRequired - ? TState extends Array - ? ArrayHaveAtLeastOneRequiredField extends true - ? true - : false + : NonNullable extends Array + ? TRule extends RegleCollectionRuleDecl + ? ArrayHaveAtLeastOneRequiredField, TRule> extends false + ? false + : true : false : TRule extends ReglePartialRuleTree ? ExtendOnlyRealRecord extends true - ? ObjectHaveAtLeastOneRequiredField extends true - ? true - : false + ? ObjectHaveAtLeastOneRequiredField< + NonNullable extends Record ? NonNullable : {}, + TRule + > extends false + ? false + : true : TRule extends RegleRuleDecl - ? FieldHaveRequiredRule extends true - ? true - : false + ? FieldHaveRequiredRule extends false + ? false + : true : false : false; diff --git a/packages/core/src/types/rules/rule.declaration.types.ts b/packages/core/src/types/rules/rule.declaration.types.ts index 8ea2d15a..d7a01671 100644 --- a/packages/core/src/types/rules/rule.declaration.types.ts +++ b/packages/core/src/types/rules/rule.declaration.types.ts @@ -108,7 +108,7 @@ export type RegleRuleDecl< * @internal * @reference {@link RegleRuleDecl} */ -export type $InternalRegleRuleDecl = Record>; +export type $InternalRegleRuleDecl = FieldRegleBehaviourOptions & Record>; /** * @public diff --git a/packages/nuxt/test/fixtures/pages/index.vue b/packages/nuxt/test/fixtures/pages/index.vue index 3db940fe..d1318169 100644 --- a/packages/nuxt/test/fixtures/pages/index.vue +++ b/packages/nuxt/test/fixtures/pages/index.vue @@ -1,11 +1,41 @@ diff --git a/packages/nuxt/test/nuxt.spec.ts b/packages/nuxt/test/nuxt.spec.ts index a5b775e1..bba0ac76 100644 --- a/packages/nuxt/test/nuxt.spec.ts +++ b/packages/nuxt/test/nuxt.spec.ts @@ -10,6 +10,6 @@ describe('ssr', async () => { it('renders the index page', async () => { const html = await $fetch('/'); - expect(html).toContain('
hello
'); + expect(html).toContain('
Hello
'); }); }); diff --git a/tests/unit/createRule/createRule.spec.ts b/tests/unit/createRule/createRule.spec.ts index e044f8a2..dd69b1c7 100644 --- a/tests/unit/createRule/createRule.spec.ts +++ b/tests/unit/createRule/createRule.spec.ts @@ -1,4 +1,4 @@ -import { createRule, type RegleRuleDefinition } from '@regle/core'; +import { createRule, type RegleRuleDefinition, type RegleRuleWithParamsDefinition } from '@regle/core'; describe('createRule', () => { it('should error when creating a rule without a function', () => { @@ -69,7 +69,7 @@ describe('createRule', () => { message: '', }); - expect(await rule.exec('fooo')).toBe(false); + expect(rule.exec('fooo')).toBe(false); }); it('should recognize mutliple parameters with default', async () => { @@ -83,11 +83,15 @@ describe('createRule', () => { expect(rule().exec('fooo')).toBe(true); expect(rule(true).exec('fooo')).toBe(true); expect(rule(true, true).exec('fooo')).toBe(true); + + expectTypeOf(rule).toEqualTypeOf< + RegleRuleWithParamsDefinition + >(); }); it('should recognize mutliple parameters with spread', async () => { const rule = createRule({ - validator(value, ...params: any[]) { + validator(value, ...params: boolean[]) { return true; }, message: '', @@ -96,5 +100,7 @@ describe('createRule', () => { expect(rule().exec('fooo')).toBe(true); expect(rule(true).exec('fooo')).toBe(true); expect(rule(true, true).exec('fooo')).toBe(true); + + expectTypeOf(rule).toEqualTypeOf>(); }); }); diff --git a/tests/unit/useRegle/validate/$validate.spec.ts b/tests/unit/useRegle/validate/$validate.spec.ts index 5b572c0b..1e15456c 100644 --- a/tests/unit/useRegle/validate/$validate.spec.ts +++ b/tests/unit/useRegle/validate/$validate.spec.ts @@ -11,6 +11,7 @@ function simpleNestedStateWithMixedValidation() { lastName?: string; }; contacts?: [{ name: string }]; + collection?: [{ name: string }]; } return useRegle({} as Form, { @@ -23,6 +24,9 @@ function simpleNestedStateWithMixedValidation() { name: { required }, }, }, + collection: { + required, + }, }); } @@ -44,6 +48,9 @@ describe('$validate', () => { contacts: { name: string; }[]; + collection: { + name: string; + }[]; }>(); } else { expectTypeOf(data).toEqualTypeOf<{ @@ -56,6 +63,9 @@ describe('$validate', () => { contacts?: { name?: Maybe; }[]; + collection?: { + name?: Maybe; + }[]; }>(); } });