From eb7120e96d96818b9aaa3ca08b4f89855f6fb4fb Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Tue, 10 Dec 2024 18:26:08 +0100 Subject: [PATCH] fix: fixed `withTooltip` behaviour --- .../core/src/core/createRule/createRule.ts | 3 +- .../core/createRule/defineRuleProcessors.ts | 3 +- .../createReactiveRuleStatus.ts | 14 +- .../src/types/rules/rule.declaration.types.ts | 5 + .../src/types/rules/rule.internal.types.ts | 3 +- packages/rules/src/helpers/withMessage.ts | 10 +- packages/rules/src/helpers/withTooltip.ts | 2 + tests/unit/useRegle/errors/metadata.spec.ts | 147 ++++++++++++++---- tests/unit/useRegle/errors/tooltips.spec.ts | 112 ++++++++++++- 9 files changed, 252 insertions(+), 47 deletions(-) diff --git a/packages/core/src/core/createRule/createRule.ts b/packages/core/src/core/createRule/createRule.ts index e0c226d..d27c107 100644 --- a/packages/core/src/core/createRule/createRule.ts +++ b/packages/core/src/core/createRule/createRule.ts @@ -67,7 +67,8 @@ export function createRule< ruleFactory._active = staticProcessors.active; ruleFactory._tooltip = staticProcessors.tooltip; ruleFactory._type = definition.type; - ruleFactory._patched = false; + ruleFactory._message_pacthed = false; + ruleFactory._tooltip_pacthed = false; ruleFactory._async = isAsync as TAsync; return ruleFactory as any; } else { diff --git a/packages/core/src/core/createRule/defineRuleProcessors.ts b/packages/core/src/core/createRule/defineRuleProcessors.ts index aadac28..0b1e94b 100644 --- a/packages/core/src/core/createRule/defineRuleProcessors.ts +++ b/packages/core/src/core/createRule/defineRuleProcessors.ts @@ -82,7 +82,8 @@ export function defineRuleProcessors( _active: definition.active, _tooltip: definition.tooltip, _type: definition.type, - _patched: false, + _message_patched: false, + _tooltip_patched: false, _async: isAsync, _params: createReactiveParams(params), }; diff --git a/packages/core/src/core/useRegle/useStateProperties/createReactiveRuleStatus.ts b/packages/core/src/core/useRegle/useStateProperties/createReactiveRuleStatus.ts index 3c53fc1..b8ee560 100644 --- a/packages/core/src/core/useRegle/useStateProperties/createReactiveRuleStatus.ts +++ b/packages/core/src/core/useRegle/useStateProperties/createReactiveRuleStatus.ts @@ -1,11 +1,13 @@ import type { ComputedRef, Ref, WatchStopHandle } from 'vue'; import { computed, effectScope, reactive, ref, watch } from 'vue'; +import { isEmpty } from '../../../../../shared'; import type { + $InternalInlineRuleDeclaration, + $InternalRegleRuleDefinition, $InternalRegleRuleMetadataConsumer, $InternalRegleRuleStatus, CustomRulesDeclarationTree, InlineRuleDeclaration, - RegleRuleDefinition, RegleRuleDefinitionProcessor, RegleRuleMetadataDefinition, } from '../../../types'; @@ -13,12 +15,11 @@ import { debounce } from '../../../utils'; import { unwrapRuleParameters } from '../../createRule/unwrapRuleParameters'; import type { RegleStorage } from '../../useStorage'; import { isFormRuleDefinition, isRuleDef } from '../guards'; -import { isEmpty } from '../../../../../shared'; interface CreateReactiveRuleStatusOptions { state: Ref; ruleKey: string; - rule: Ref | RegleRuleDefinition>; + rule: Ref<$InternalInlineRuleDeclaration | $InternalRegleRuleDefinition>; $dirty: Ref; customMessages: CustomRulesDeclarationTree | undefined; path: string; @@ -88,7 +89,8 @@ export function createReactiveRuleStatus({ } } if (isFormRuleDefinition(rule)) { - if (!(customMessageRule && !rule.value._patched)) { + const patchedKey = `_${key}_patched` as const; + if (!(customMessageRule && !rule.value[patchedKey])) { if (typeof rule.value[key] === 'function') { result = rule.value[key](state.value, $defaultMetadata.value); } else { @@ -104,7 +106,9 @@ export function createReactiveRuleStatus({ if (isEmpty(message)) { message = 'Error'; - console.warn(`No error message defined for ${path}.${ruleKey}`); + if (typeof window !== 'undefined') { + console.warn(`No error message defined for ${path}.${ruleKey}`); + } } return message; diff --git a/packages/core/src/types/rules/rule.declaration.types.ts b/packages/core/src/types/rules/rule.declaration.types.ts index e15fd40..8ea2d15 100644 --- a/packages/core/src/types/rules/rule.declaration.types.ts +++ b/packages/core/src/types/rules/rule.declaration.types.ts @@ -158,6 +158,11 @@ export type InlineRuleDeclaration< | Promise, > = (value: Maybe, ...args: UnwrapRegleUniversalParams) => TReturn; +/** + * @internal + */ +export type $InternalInlineRuleDeclaration = (value: Maybe, ...args: any[]) => any; + /** * @public * Regroup inline and registered rules diff --git a/packages/core/src/types/rules/rule.internal.types.ts b/packages/core/src/types/rules/rule.internal.types.ts index 8837d9b..556160b 100644 --- a/packages/core/src/types/rules/rule.internal.types.ts +++ b/packages/core/src/types/rules/rule.internal.types.ts @@ -22,7 +22,8 @@ export interface RegleInternalRuleDefs< | string[] | ((value: Maybe, metadata: PossibleRegleRuleMetadataConsumer) => string | string[]); _type?: string; - _patched: boolean; + _message_patched: boolean; + _tooltip_patched: boolean; _params?: RegleUniversalParams; _async: TAsync; } diff --git a/packages/rules/src/helpers/withMessage.ts b/packages/rules/src/helpers/withMessage.ts index 1458b5c..b1d3c46 100644 --- a/packages/rules/src/helpers/withMessage.ts +++ b/packages/rules/src/helpers/withMessage.ts @@ -54,11 +54,7 @@ export function withMessage< ): RegleRuleDefinition; export function withMessage( rule: RegleRuleRaw | InlineRuleDeclaration, - newMessage: RegleRuleDefinitionWithMetadataProcessor< - any, - RegleRuleMetadataConsumer, - string | string[] - > + newMessage: RegleRuleDefinitionWithMetadataProcessor, string | string[]> ): RegleRuleWithParamsDefinition | RegleRuleDefinition { let _type: string | undefined; let validator: RegleRuleDefinitionProcessor>; @@ -85,11 +81,11 @@ export function withMessage( const newParams = [...(_params ?? [])]; newRule._params = newParams as any; - newRule._patched = true; + newRule._message_patched = true; if (typeof newRule === 'function') { const executedRule = newRule(...newParams); - executedRule._patched = true; + executedRule._message_patched = true; return executedRule; } else { return newRule; diff --git a/packages/rules/src/helpers/withTooltip.ts b/packages/rules/src/helpers/withTooltip.ts index e1a4030..546622f 100644 --- a/packages/rules/src/helpers/withTooltip.ts +++ b/packages/rules/src/helpers/withTooltip.ts @@ -83,9 +83,11 @@ export function withTooltip( const newParams = [...(_params ?? [])]; newRule._params = newParams as any; + newRule._tooltip_patched = true; if (typeof newRule === 'function') { const executedRule = newRule(...newParams); + newRule._tooltip_patched = true; return executedRule; } else { return newRule; diff --git a/tests/unit/useRegle/errors/metadata.spec.ts b/tests/unit/useRegle/errors/metadata.spec.ts index 1ea97ff..a4e09ca 100644 --- a/tests/unit/useRegle/errors/metadata.spec.ts +++ b/tests/unit/useRegle/errors/metadata.spec.ts @@ -1,56 +1,143 @@ -import { createRule, useRegle } from '@regle/core'; -import { withMessage, withParams } from '@regle/rules'; +import { createRule, useRegle, type Maybe } from '@regle/core'; +import { withMessage, withParams, withTooltip } from '@regle/rules'; import { ref } from 'vue'; +import { createRegleComponent } from '../../../utils/test.utils'; function metadateRules() { const externalDep = ref('external'); - const inlineMetadataRule = withMessage( - withParams((value, external) => ({ $valid: false, external, value }), [externalDep]), - (_, { external, value }) => `${external}-${value}` + const inlineMetadataRule = withTooltip( + withMessage( + withParams( + (value: Maybe, external) => ({ $valid: false, metaExternal: external, value }), + [externalDep] + ), + (_, { metaExternal, value, $params: [external] }) => `${metaExternal}-${external}-${value?.length}` + ), + (_, { metaExternal, value, $params: [external] }) => `${metaExternal}-${external}-${value?.length}` ); const createRuleMetadataRule = createRule({ - validator: (value, external) => ({ $valid: false, external, value }), - message: (_, { external, value }) => `${external}-${value}`, + validator: (value: Maybe, external) => ({ $valid: false, metaExternal: external, value }), + message: (_, { metaExternal, value, $params: [external] }) => `${metaExternal}-${external}-${value?.length}`, + tooltip: (_, { metaExternal, value, $params: [external] }) => `${metaExternal}-${external}-${value?.length}`, + active: (_, { $params: [external] }) => external === 'external', }); const form = ref({ - email: '', + email: 'a', user: { - firstName: '', + firstName: 'a', nested: { - child: '', - collection: [{ name: '' }], + child: 'a', + collection: [{ name: 'a' }], }, }, - contacts: [{ name: '' }], + contacts: [{ name: 'a' }], }); const allRules = { inlineMetadataRule, createRuleMetadataRule: createRuleMetadataRule(externalDep) }; - return useRegle(form, { - email: allRules, - user: { - firstName: allRules, - nested: { - child: allRules, - collection: { - ...allRules, - $each: { - name: allRules, + return { + externalDep, + ...useRegle(form, { + email: allRules, + user: { + firstName: allRules, + nested: { + child: allRules, + collection: { + ...allRules, + $each: { + name: allRules, + }, }, }, }, - }, - contacts: { - $each: { - name: allRules, + contacts: { + $each: { + name: allRules, + }, }, - }, - }); + }), + }; } describe('metadata', () => { - it('correctly return metadata for validator and active handlers', () => { - expect(true).toBe(true); + it('correctly return metadata for validator and active handlers', async () => { + const { vm } = createRegleComponent(metadateRules); + + vm.r$.$touch(); + await vm.$nextTick(); + + const expectedMessage = ['external-external-1', 'external-external-1']; + + expect(vm.r$.$fields.email.$errors).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.firstName.$errors).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$errors).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$errors).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$errors).toStrictEqual( + expectedMessage + ); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$errors).toStrictEqual(expectedMessage); + + // Tooltips + expect(vm.r$.$fields.email.$tooltips).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.firstName.$tooltips).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$tooltips).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$tooltips).toStrictEqual(expectedMessage); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$tooltips).toStrictEqual( + expectedMessage + ); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$tooltips).toStrictEqual(expectedMessage); + + // Active + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$rules.createRuleMetadataRule.$active).toBe(true); + expect(vm.r$.$fields.email.$rules.createRuleMetadataRule.$active).toBe(true); + expect(vm.r$.$fields.user.$fields.firstName.$rules.createRuleMetadataRule.$active).toBe(true); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$rules.createRuleMetadataRule.$active).toBe(true); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$rules.createRuleMetadataRule.$active).toBe( + true + ); + expect( + vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$rules.createRuleMetadataRule.$active + ).toBe(true); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$rules.createRuleMetadataRule.$active).toBe(true); + + // Change value + vm.externalDep = 'new'; + await vm.$nextTick(); + + const expectedMessage2 = ['new-new-1', 'new-new-1']; + + expect(vm.r$.$fields.email.$errors).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.firstName.$errors).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$errors).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$errors).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$errors).toStrictEqual( + expectedMessage2 + ); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$errors).toStrictEqual(expectedMessage2); + + // Tooltips + expect(vm.r$.$fields.email.$tooltips).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.firstName.$tooltips).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$tooltips).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$tooltips).toStrictEqual(expectedMessage2); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$tooltips).toStrictEqual( + expectedMessage2 + ); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$tooltips).toStrictEqual(expectedMessage2); + + // Active + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$rules.createRuleMetadataRule.$active).toBe(false); + expect(vm.r$.$fields.email.$rules.createRuleMetadataRule.$active).toBe(false); + expect(vm.r$.$fields.user.$fields.firstName.$rules.createRuleMetadataRule.$active).toBe(false); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$rules.createRuleMetadataRule.$active).toBe(false); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$rules.createRuleMetadataRule.$active).toBe( + false + ); + expect( + vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$rules.createRuleMetadataRule.$active + ).toBe(false); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$rules.createRuleMetadataRule.$active).toBe(false); }); }); diff --git a/tests/unit/useRegle/errors/tooltips.spec.ts b/tests/unit/useRegle/errors/tooltips.spec.ts index fb974a9..0c7d1aa 100644 --- a/tests/unit/useRegle/errors/tooltips.spec.ts +++ b/tests/unit/useRegle/errors/tooltips.spec.ts @@ -1,5 +1,113 @@ +import { createRule, defineRegleConfig, useRegle } from '@regle/core'; +import { withTooltip } from '@regle/rules'; +import { ref } from 'vue'; +import { createRegleComponent } from '../../../utils/test.utils'; + +function tooltipsRules() { + const ruleWithOneTooltip = withTooltip(() => false, 'Tooltip'); + const ruleWithMultipleTooltips = withTooltip(() => false, ['Tooltip 1.1', 'Tooltip 1.2']); + const ruleFunctionWithOneTooltip = withTooltip( + () => false, + () => 'Tooltip 2' + ); + const ruleFunctionWithMultipleTooltip = withTooltip( + () => false, + () => ['Tooltip 2.1', 'Tooltip 2.2'] + ); + + const createRuleOneTooltip = createRule({ validator: () => false, message: '', tooltip: 'Tooltip 3' }); + const createRuleMultipleTooltip = createRule({ + validator: () => false, + message: '', + tooltip: ['Tooltip 3.1', 'Tooltip 3.2', 'Tooltip 3.3'], + }); + const createRuleFunctionOneTooltip = createRule({ validator: () => false, message: '', tooltip: () => 'Tooltip 4' }); + const createRuleFunctionMultipleTooltip = createRule({ + validator: () => false, + message: '', + tooltip: () => ['Tooltip 4.1'], + }); + + const form = ref({ + email: '', + user: { + firstName: '', + nested: { + child: '', + collection: [{ name: '' }], + }, + }, + contacts: [{ name: '' }], + }); + + const allRules = { + ruleWithOneTooltip, + ruleWithMultipleTooltips, + ruleFunctionWithOneTooltip, + ruleFunctionWithMultipleTooltip, + createRuleOneTooltip, + createRuleMultipleTooltip, + createRuleFunctionOneTooltip, + createRuleFunctionMultipleTooltip, + }; + return useRegle(form, { + email: allRules, + user: { + firstName: allRules, + nested: { + child: allRules, + collection: { + ...allRules, + $each: { + name: allRules, + }, + }, + }, + }, + contacts: { + $each: { + name: allRules, + }, + }, + }); +} + describe('tooltips', () => { - it('should work', () => { - expect(true).toBe(true); + it('should report any tooltip format for rules', async () => { + const { vm } = createRegleComponent(tooltipsRules); + + await vm.$nextTick(); + + const expectedTooltips = [ + 'Tooltip', + 'Tooltip 1.1', + 'Tooltip 1.2', + 'Tooltip 2', + 'Tooltip 2.1', + 'Tooltip 2.2', + 'Tooltip 3', + 'Tooltip 3.1', + 'Tooltip 3.2', + 'Tooltip 3.3', + 'Tooltip 4', + 'Tooltip 4.1', + ]; + + expect(vm.r$.$fields.email.$tooltips).toStrictEqual(expectedTooltips); + expect(vm.r$.$fields.user.$fields.firstName.$tooltips).toStrictEqual(expectedTooltips); + expect(vm.r$.$fields.user.$fields.nested.$fields.child.$tooltips).toStrictEqual(expectedTooltips); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$tooltips).toStrictEqual(expectedTooltips); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$field.$tooltips).toStrictEqual(expectedTooltips); + expect(vm.r$.$fields.user.$fields.nested.$fields.collection.$each[0].$fields.name.$tooltips).toStrictEqual( + expectedTooltips + ); + expect(vm.r$.$fields.contacts.$each[0].$fields.name.$tooltips).toStrictEqual(expectedTooltips); + + vm.r$.$value.contacts.push({ name: '' }); + await vm.$nextTick(); + vm.r$.$touch(); + await vm.$nextTick(); + + expect(vm.r$.$fields.contacts.$each[1].$fields.name.$tooltips).toStrictEqual(expectedTooltips); }); });