From 0292deee1d3939f6116fa478493b0306d3556edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Garc=C3=ADa=20Seco?= Date: Wed, 7 May 2025 15:02:27 +0200 Subject: [PATCH] Revert "Revert "Add MoneyField component and corresponding tests (#123)" (#126)" This reverts commit 5021703e8e6ba95d2efbbfa53d2af36c33dd027a. --- src/components/form/fields/MoneyField.tsx | 41 ++++++ src/components/form/fields/NumberField.tsx | 2 +- src/components/form/fields/fieldsMapping.tsx | 5 +- .../form/fields/tests/MoneyField.test.tsx | 127 ++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/components/form/fields/MoneyField.tsx create mode 100644 src/components/form/fields/tests/MoneyField.test.tsx diff --git a/src/components/form/fields/MoneyField.tsx b/src/components/form/fields/MoneyField.tsx new file mode 100644 index 00000000..0ca7512b --- /dev/null +++ b/src/components/form/fields/MoneyField.tsx @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { FormField } from '@/src/components/ui/form'; +import { useFormFields } from '@/src/context'; +import React from 'react'; +import { useFormContext } from 'react-hook-form'; +import { TextField, TextFieldProps } from './TextField'; + +export function MoneyField(props: TextFieldProps) { + const { components } = useFormFields(); + const { control } = useFormContext(); + console.log('MoneyField', props); + if (components?.money) { + return ( + { + const CustomNumberField = + components.money as React.ComponentType; + return ( + { + field.onChange(value); + props.onChange?.(value); + }, + }} + fieldState={fieldState} + fieldData={props} + /> + ); + }} + /> + ); + } + + return ( + + ); +} diff --git a/src/components/form/fields/NumberField.tsx b/src/components/form/fields/NumberField.tsx index 0e913ca1..8e40f60a 100644 --- a/src/components/form/fields/NumberField.tsx +++ b/src/components/form/fields/NumberField.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { FormField } from '@/src/components/ui/form'; import { useFormFields } from '@/src/context'; import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { FormField } from '../../ui/form'; import { TextField, TextFieldProps } from './TextField'; export function NumberField(props: TextFieldProps) { diff --git a/src/components/form/fields/fieldsMapping.tsx b/src/components/form/fields/fieldsMapping.tsx index 8a7c6570..d1e55d75 100644 --- a/src/components/form/fields/fieldsMapping.tsx +++ b/src/components/form/fields/fieldsMapping.tsx @@ -1,13 +1,14 @@ import { CheckBoxField } from '@/src/components/form/fields/CheckBoxField'; import { DatePickerField } from '@/src/components/form/fields/DatePickerField'; +import { EmailField } from '@/src/components/form/fields/EmailField'; import { FieldSetField } from '@/src/components/form/fields/FieldSetField'; import { FileUploadField } from '@/src/components/form/fields/FileUploadField'; +import { MoneyField } from '@/src/components/form/fields/MoneyField'; import { NumberField } from '@/src/components/form/fields/NumberField'; import { RadioGroupField } from '@/src/components/form/fields/RadioGroupField'; import { SelectField } from '@/src/components/form/fields/SelectField'; import { TextAreaField } from '@/src/components/form/fields/TextAreaField'; import { TextField } from '@/src/components/form/fields/TextField'; -import { EmailField } from '@/src/components/form/fields/EmailField'; import { SupportedTypes } from '@/src/components/form/fields/types'; import React from 'react'; @@ -16,7 +17,7 @@ export const fieldsMap: Record> = { checkbox: CheckBoxField, text: TextField, email: EmailField, - money: NumberField, + money: MoneyField, select: SelectField, radio: RadioGroupField, number: NumberField, diff --git a/src/components/form/fields/tests/MoneyField.test.tsx b/src/components/form/fields/tests/MoneyField.test.tsx new file mode 100644 index 00000000..b5cce696 --- /dev/null +++ b/src/components/form/fields/tests/MoneyField.test.tsx @@ -0,0 +1,127 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useFormFields } from '@/src/context'; +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { number } from 'yup'; +import { MoneyField } from '../MoneyField'; +import { TextFieldProps } from '../TextField'; + +// Mock dependencies +vi.mock('@/src/context', () => ({ + useFormFields: vi.fn(), +})); + +describe('MoneyField Component', () => { + const mockOnChange = vi.fn(); + const defaultProps: TextFieldProps = { + name: 'testField', + label: 'Test Field', + description: 'This is a test field', + type: 'money', + computedAttributes: {}, + errorMessage: { + required: 'This field is required', + }, + inputType: 'number' as const, + isVisible: true, + jsonType: 'number', + required: true, + schema: number(), + scopedJsonSchema: {}, + }; + + // Helper function to render the component with a form context + const renderWithFormContext = (props: TextFieldProps) => { + const TestComponent = () => { + const methods = useForm(); + return ( + + + + ); + }; + + return render(); + }; + + beforeEach(() => { + vi.clearAllMocks(); + (useFormFields as any).mockReturnValue({ components: {} }); + }); + + it('renders the default implementation correctly', () => { + renderWithFormContext(defaultProps); + + expect(screen.getByText('Test Field')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Test Field')).toBeInTheDocument(); + expect(screen.getByText('This is a test field')).toBeInTheDocument(); + }); + + it('handles input change correctly', () => { + renderWithFormContext({ ...defaultProps, onChange: mockOnChange }); + + const input = screen.getByPlaceholderText('Test Field'); + fireEvent.change(input, { target: { value: '123.45' } }); + + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); + + it('renders custom money component when provided', () => { + const CustomMoneyField = vi + .fn() + .mockImplementation(() => ( +
Custom Money Field
+ )); + + (useFormFields as any).mockReturnValue({ + components: { money: CustomMoneyField }, + }); + + renderWithFormContext({ ...defaultProps, onChange: mockOnChange }); + + expect(CustomMoneyField).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('custom-money-field')).toBeInTheDocument(); + }); + + it('passes field props to custom component correctly', () => { + const CustomMoneyField = vi + .fn() + .mockImplementation(() => ( +
Custom Money Field
+ )); + + (useFormFields as any).mockReturnValue({ + components: { money: CustomMoneyField }, + }); + + renderWithFormContext({ ...defaultProps, onChange: mockOnChange }); + + const call = CustomMoneyField.mock.calls[0][0]; + expect(call.fieldData).toMatchObject(defaultProps); + expect(call.field).toBeDefined(); + expect(call.fieldState).toBeDefined(); + }); + + it('handles onChange in custom money component', () => { + const CustomMoneyField = vi.fn().mockImplementation(({ field }) => { + const handleChange = (e: React.ChangeEvent) => { + field.onChange(e); + }; + + return ; + }); + + (useFormFields as any).mockReturnValue({ + components: { money: CustomMoneyField }, + }); + + renderWithFormContext({ ...defaultProps, onChange: mockOnChange }); + + const customInput = screen.getByTestId('custom-input'); + fireEvent.change(customInput, { target: { value: '456.78' } }); + + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); +});