diff --git a/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.test.ts b/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.test.ts new file mode 100644 index 0000000000..1739ea9a2a --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.test.ts @@ -0,0 +1,84 @@ +import userEvent from '@testing-library/user-event'; + +import { createComponentRenderer } from '@n8n/design-system/__tests__/render'; + +import N8nFormInput from '.'; + +const renderComponent = createComponentRenderer(N8nFormInput); + +describe('N8nFormInput', () => { + it('should render correctly with label and placeholder', () => { + const { getByRole, container } = renderComponent({ + props: { + modelValue: 'test', + label: 'Test Label', + placeholder: 'Enter text', + }, + }); + expect(getByRole('textbox')).toHaveValue('test'); + expect(getByRole('textbox')).toHaveAttribute('placeholder', 'Enter text'); + expect(container.querySelector('label')).toHaveTextContent('Test Label'); + }); + + it('should validate required', async () => { + const { getByRole, emitted, rerender } = renderComponent({ + props: { + modelValue: '', + label: 'Test Label', + required: true, + }, + }); + + expect(emitted('validate')).toEqual([[false]]); + + const input = getByRole('textbox'); + expect(input).toHaveValue(''); + + await userEvent.type(input, 'test'); + await userEvent.tab(); + // Re-render to comply with controlled component behavior + await rerender({ + modelValue: 'test', + label: 'Test Label', + required: true, + }); + + expect(input).toHaveValue('test'); + expect(emitted('validate')).toEqual([[false], [true]]); + + await userEvent.clear(input); + await userEvent.tab(); + // Re-render to comply with controlled component behavior + await rerender({ + modelValue: '', + label: 'Test Label', + required: true, + }); + + expect(emitted('validate')).toEqual([[false], [true], [false]]); + }); + + it('should emit error even with missing or empty error message', async () => { + const { emitted } = renderComponent({ + props: { + modelValue: 'test', + label: 'Test Label', + required: true, + showValidationWarnings: true, + validators: { + MY_VALIDATION: { + validate: (value: string) => { + if (value === 'test') { + return { messageKey: 'key.noTestAllowed' }; + } + return false; + }, + }, + }, + validationRules: [{ name: 'MY_VALIDATION' }], + }, + }); + + expect(emitted('validate')).toEqual([[false]]); + }); +}); diff --git a/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.vue b/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.vue index 2d2061997f..4932b274bf 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nFormInput/FormInput.vue @@ -142,14 +142,18 @@ function onEnter(event: Event) { emit('enter'); } -const validationError = computed(() => { +const validationError = computed<{ message: string } | null>(() => { const error = getInputValidationError(); if (error) { if ('messageKey' in error) { - return t(error.messageKey, error.options); - } else if ('message' in error) { - return error.message; + return { + message: t(error.messageKey, error.options), + }; + } else { + return { + message: error.message, + }; } } @@ -170,10 +174,7 @@ onMounted(() => { if (props.focusInitially && inputRef.value) inputRef.value.focus(); }); -watch( - () => validationError.value, - (error) => emit('validate', !error), -); +watch(validationError, (error) => emit('validate', !error)); defineExpose({ inputRef }); @@ -259,7 +260,7 @@ defineExpose({ inputRef }); />
- +