Skip to content

Commit a550b96

Browse files
committed
feat: implemented events & fixed tests
1 parent 32857eb commit a550b96

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+954
-107
lines changed

packages/ui/src/components/molecules/inputs/AutocompleteInput/AutocompleteInput.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface AutocompleteInputProps {
2525
textInputClassName?: string;
2626
onChange: (event: AutocompleteChangeEvent) => void;
2727
onBlur?: (event: FocusEvent<any>) => void;
28+
onFocus?: (event: FocusEvent<any>) => void;
2829
}
2930

3031
export const AutocompleteInput = ({
@@ -37,6 +38,7 @@ export const AutocompleteInput = ({
3738
textInputClassName,
3839
onChange,
3940
onBlur,
41+
onFocus,
4042
}: AutocompleteInputProps) => {
4143
const safeValue = useMemo(() => {
4244
if (typeof value !== 'string') {
@@ -81,6 +83,7 @@ export const AutocompleteInput = ({
8183
PaperComponent={Paper as ComponentProps<typeof Autocomplete>['PaperComponent']}
8284
onChange={handleChange}
8385
disabled={disabled}
86+
onFocus={onFocus}
8487
slotProps={{
8588
paper: {
8689
className: 'mt-2 mb-2 w-full',

packages/ui/src/components/molecules/inputs/DatePickerInput/DatePickerInput.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface DatePickerProps {
3636
textInputClassName?: string;
3737
onChange: (event: DatePickerChangeEvent) => void;
3838
onBlur?: (event: FocusEvent<any>) => void;
39+
onFocus?: (event: FocusEvent<any>) => void;
3940
}
4041

4142
export const DatePickerInput = ({
@@ -47,6 +48,7 @@ export const DatePickerInput = ({
4748
textInputClassName,
4849
onChange,
4950
onBlur,
51+
onFocus,
5052
}: DatePickerProps) => {
5153
const {
5254
outputValueFormat = 'iso',
@@ -124,6 +126,7 @@ export const DatePickerInput = ({
124126
onFocus={e => {
125127
setFocused(true);
126128
props.onFocus && props.onFocus(e);
129+
onFocus && onFocus(e);
127130
}}
128131
onBlur={e => {
129132
setFocused(false);

packages/ui/src/components/molecules/inputs/MultiSelect/MultiSelect.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface MultiSelectProps {
3030
renderSelected: MultiSelectSelectedItemRenderer;
3131
onChange: (selected: MultiSelectValue[], inputName: string) => void;
3232
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
33+
onFocus?: (event: FocusEvent<HTMLInputElement>) => void;
3334
}
3435

3536
export const MultiSelect = ({
@@ -43,6 +44,7 @@ export const MultiSelect = ({
4344
renderSelected,
4445
onChange,
4546
onBlur,
47+
onFocus,
4648
}: MultiSelectProps) => {
4749
const inputRef = useRef<HTMLInputElement>(null);
4850
const [open, setOpen] = useState(false);
@@ -174,7 +176,10 @@ export const MultiSelect = ({
174176
placeholder={searchPlaceholder}
175177
style={{ border: 'none' }}
176178
className={ctw('placeholder:text-muted-foreground h-6', textInputClassName)}
177-
onFocus={() => setOpen(true)}
179+
onFocus={event => {
180+
setOpen(true);
181+
onFocus?.(event);
182+
}}
178183
onBlur={onBlur}
179184
data-testid={testId ? `${testId}-search-input` : undefined}
180185
/>

packages/ui/src/components/organisms/Form/DynamicForm/DynamicForm.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const DynamicFormV2: FunctionComponent<IDynamicFormProps> = ({
1818
onChange,
1919
onFieldChange,
2020
onSubmit,
21+
onEvent,
2122
}) => {
2223
const validationSchema = useValidationSchema(elements);
2324
const valuesApi = useValues({
@@ -36,8 +37,11 @@ export const DynamicFormV2: FunctionComponent<IDynamicFormProps> = ({
3637
submit,
3738
fieldHelpers,
3839
elementsMap,
40+
callbacks: {
41+
onEvent,
42+
},
3943
}),
40-
[touchedApi.touched, valuesApi.values, submit, fieldHelpers, elementsMap],
44+
[touchedApi.touched, valuesApi.values, submit, fieldHelpers, elementsMap, onEvent],
4145
);
4246

4347
return (
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { cleanup, render } from '@testing-library/react';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { Renderer } from '../../Renderer';
4+
import { ValidatorProvider } from '../Validator';
5+
import { DynamicFormContext } from './context';
6+
import { DynamicFormV2 } from './DynamicForm';
7+
import { useSubmit } from './hooks/external';
8+
import { useFieldHelpers } from './hooks/internal/useFieldHelpers';
9+
import { useTouched } from './hooks/internal/useTouched';
10+
import { useValidationSchema } from './hooks/internal/useValidationSchema';
11+
import { useValues } from './hooks/internal/useValues';
12+
import { ICommonFieldParams, IDynamicFormProps, IFormElement, TBaseFormElements } from './types';
13+
14+
// Mock dependencies
15+
vi.mock('../../Renderer');
16+
17+
vi.mock('../Validator');
18+
19+
vi.mock('./hooks/external/useSubmit');
20+
21+
vi.mock('./hooks/internal/useFieldHelpers');
22+
23+
vi.mock('./hooks/internal/useTouched');
24+
25+
vi.mock('./hooks/internal/useValidationSchema');
26+
27+
vi.mock('./hooks/internal/useValues');
28+
29+
vi.mock('./context', () => ({
30+
DynamicFormContext: {
31+
Provider: vi.fn(({ children, value }: any) => {
32+
return <div data-testid="context-provider">{children}</div>;
33+
}),
34+
},
35+
}));
36+
37+
describe('DynamicFormV2', () => {
38+
beforeEach(() => {
39+
cleanup();
40+
vi.restoreAllMocks();
41+
42+
vi.mocked(Renderer).mockImplementation(({ children }: any) => {
43+
return <div data-testid="renderer">{children}</div>;
44+
});
45+
vi.mocked(ValidatorProvider).mockImplementation(({ children }: any) => {
46+
return <div data-testid="validator">{children}</div>;
47+
});
48+
49+
vi.mocked(useTouched).mockReturnValue({
50+
touched: {},
51+
setTouched: vi.fn(),
52+
setFieldTouched: vi.fn(),
53+
touchAllFields: vi.fn(),
54+
} as any);
55+
vi.mocked(useFieldHelpers).mockReturnValue({
56+
getTouched: vi.fn(),
57+
getValue: vi.fn(),
58+
setTouched: vi.fn(),
59+
setValue: vi.fn(),
60+
} as any);
61+
vi.mocked(useSubmit).mockReturnValue({ submit: vi.fn() } as any);
62+
vi.mocked(useValidationSchema).mockReturnValue([] as any);
63+
vi.mocked(useValues).mockReturnValue({
64+
values: {},
65+
setValues: vi.fn(),
66+
setFieldValue: vi.fn(),
67+
} as any);
68+
});
69+
70+
afterEach(() => {
71+
vi.resetAllMocks();
72+
});
73+
74+
const mockProps = {
75+
elements: [],
76+
values: {},
77+
validationParams: {},
78+
elementsMap: {},
79+
onChange: vi.fn(),
80+
onFieldChange: vi.fn(),
81+
onSubmit: vi.fn(),
82+
onEvent: vi.fn(),
83+
} as unknown as IDynamicFormProps<object, TBaseFormElements>;
84+
85+
it('should render without crashing', () => {
86+
render(<DynamicFormV2 {...mockProps} />);
87+
});
88+
89+
it('should pass elements to useValidationSchema', () => {
90+
const elements = [{ id: 'test', element: 'text' }] as unknown as Array<
91+
IFormElement<TBaseFormElements, ICommonFieldParams>
92+
>;
93+
render(<DynamicFormV2 {...mockProps} elements={elements} />);
94+
expect(useValidationSchema).toHaveBeenCalledWith(elements);
95+
});
96+
97+
it('should pass correct props to useValues', () => {
98+
render(<DynamicFormV2 {...mockProps} />);
99+
expect(useValues).toHaveBeenCalledWith({
100+
values: mockProps.values,
101+
onChange: mockProps.onChange,
102+
onFieldChange: mockProps.onFieldChange,
103+
});
104+
});
105+
106+
it('should pass correct props to useTouched', () => {
107+
render(<DynamicFormV2 {...mockProps} />);
108+
expect(useTouched).toHaveBeenCalledWith(mockProps.elements, mockProps.values);
109+
});
110+
111+
it('should pass correct props to useFieldHelpers', () => {
112+
render(<DynamicFormV2 {...mockProps} />);
113+
expect(useFieldHelpers).toHaveBeenCalledWith({
114+
valuesApi: useValues({
115+
values: mockProps.values,
116+
onChange: mockProps.onChange,
117+
onFieldChange: mockProps.onFieldChange,
118+
}),
119+
touchedApi: useTouched(mockProps.elements, mockProps.values),
120+
});
121+
});
122+
123+
it('should pass correct props to useSubmit', () => {
124+
render(<DynamicFormV2 {...mockProps} />);
125+
expect(useSubmit).toHaveBeenCalledWith({
126+
values: mockProps.values,
127+
onSubmit: mockProps.onSubmit,
128+
});
129+
});
130+
it('should pass context to DynamicFormContext.Provider', () => {
131+
const touchedMock = {
132+
touched: { field1: true },
133+
setTouched: vi.fn(),
134+
setFieldTouched: vi.fn(),
135+
touchAllFields: vi.fn(),
136+
};
137+
const valuesMock = {
138+
values: { field1: 'value1' },
139+
setValues: vi.fn(),
140+
setFieldValue: vi.fn(),
141+
};
142+
const submitMock = { submit: vi.fn() };
143+
const fieldHelpersMock = {
144+
getTouched: vi.fn(),
145+
getValue: vi.fn(),
146+
setTouched: vi.fn(),
147+
setValue: vi.fn(),
148+
};
149+
150+
vi.mocked(useTouched).mockReturnValue(touchedMock);
151+
vi.mocked(useValues).mockReturnValue(valuesMock);
152+
vi.mocked(useSubmit).mockReturnValue(submitMock);
153+
vi.mocked(useFieldHelpers).mockReturnValue(fieldHelpersMock);
154+
155+
render(<DynamicFormV2 {...mockProps} />);
156+
157+
// Get the actual props passed to DynamicFormContext.Provider
158+
const providerProps = vi.mocked(DynamicFormContext.Provider).mock.calls[0]?.[0];
159+
160+
expect(providerProps?.value).toEqual({
161+
touched: touchedMock.touched,
162+
values: valuesMock.values,
163+
submit: submitMock.submit,
164+
fieldHelpers: fieldHelpersMock,
165+
elementsMap: mockProps.elementsMap,
166+
callbacks: {
167+
onEvent: mockProps.onEvent,
168+
},
169+
});
170+
});
171+
});

packages/ui/src/components/organisms/Form/DynamicForm/context/hooks/useDynamicForm/useDynamicForm.unit.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { renderHook } from '@testing-library/react';
22
import { useContext } from 'react';
3-
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
44
import { DynamicFormContext } from '../../dynamic-form.context';
55
import { useDynamicForm } from './useDynamicForm';
66

@@ -21,6 +21,10 @@ describe('useDynamicForm', () => {
2121
vi.mocked(useContext).mockReturnValue(mockContextValue);
2222
});
2323

24+
afterEach(() => {
25+
vi.restoreAllMocks();
26+
});
27+
2428
it('should call useContext with DynamicFormContext', () => {
2529
renderHook(() => useDynamicForm());
2630

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import { IFormEventElement, TElementEvent } from '../hooks/internal/useEvents/types';
12
import { IFieldHelpers } from '../hooks/internal/useFieldHelpers/types';
23
import { ITouchedState } from '../hooks/internal/useTouched';
34
import { TElementsMap } from '../types';
45

6+
export interface IDynamicFormCallbacks {
7+
onEvent?: (eventName: TElementEvent, element: IFormEventElement<any, any>) => void;
8+
}
9+
510
export interface IDynamicFormContext<TValues extends object> {
611
values: TValues;
712
touched: ITouchedState;
813
elementsMap: TElementsMap;
914
fieldHelpers: IFieldHelpers;
1015
submit: () => void;
16+
callbacks: IDynamicFormCallbacks;
1117
}

packages/ui/src/components/organisms/Form/DynamicForm/controls/SubmitButton/SubmitButton.unit.test.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Button } from '@/components/atoms';
22
import '@testing-library/jest-dom';
33
import { cleanup, render, screen } from '@testing-library/react';
44
import userEvent from '@testing-library/user-event';
5-
import { beforeEach, describe, expect, it, vi } from 'vitest';
5+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
66
import { useValidator } from '../../../Validator';
77
import { useDynamicForm } from '../../context';
8+
import { useElement } from '../../hooks/external';
89
import { useField } from '../../hooks/external/useField';
910
import { IFormElement, TBaseFormElements } from '../../types';
1011
import { ISubmitButtonParams, SubmitButton } from './SubmitButton';
@@ -13,13 +14,7 @@ vi.mock('@/components/atoms', () => ({
1314
Button: vi.fn(),
1415
}));
1516

16-
vi.mock('../../hooks/external/useElement', () => ({
17-
useElement: vi.fn().mockReturnValue({ id: 'test-id' }),
18-
}));
19-
20-
vi.mock('../../hooks/external/useField', () => ({
21-
useField: vi.fn().mockReturnValue({ disabled: false }),
22-
}));
17+
vi.mock('../../hooks/external/useElement');
2318

2419
vi.mock('../../../Validator', () => ({
2520
useValidator: vi.fn(),
@@ -29,9 +24,7 @@ vi.mock('../../context', () => ({
2924
useDynamicForm: vi.fn(),
3025
}));
3126

32-
vi.mock('../../hooks/external', () => ({
33-
useField: vi.fn(),
34-
}));
27+
vi.mock('../../hooks/external/useField');
3528

3629
describe('SubmitButton', () => {
3730
const mockElement = {
@@ -54,6 +47,12 @@ describe('SubmitButton', () => {
5447
vi.mocked(useField).mockReturnValue({ disabled: false } as any);
5548
vi.mocked(useDynamicForm).mockReturnValue({ submit: mockSubmit } as any);
5649
vi.mocked(useValidator).mockReturnValue({ isValid: true } as any);
50+
vi.mocked(useElement).mockReturnValue({ id: 'test-id' } as any);
51+
vi.mocked(useField).mockReturnValue({ disabled: false } as any);
52+
});
53+
54+
afterEach(() => {
55+
vi.restoreAllMocks();
5756
});
5857

5958
it('should render button with default text', () => {

packages/ui/src/components/organisms/Form/DynamicForm/fields/AutocompleteField/AutocompleteField.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AutocompleteInput } from '@/components/molecules';
22
import { createTestId } from '@/components/organisms/Renderer';
3-
import { useField } from '../../hooks/external';
3+
import { useField } from '../../hooks/external/useField';
44
import { FieldLayout } from '../../layouts/FieldLayout';
55
import { TBaseFormElements, TDynamicFormField } from '../../types';
66
import { useStack } from '../FieldList/providers/StackProvider';
@@ -20,7 +20,10 @@ export const AutocompleteField: TDynamicFormField<TBaseFormElements, IAutocomple
2020
}) => {
2121
const { params } = element;
2222
const { stack } = useStack();
23-
const { value, onChange, onBlur, disabled } = useField<string | undefined>(element, stack);
23+
const { value, onChange, onBlur, onFocus, disabled } = useField<string | undefined>(
24+
element,
25+
stack,
26+
);
2427
const { options = [], placeholder = '' } = params || {};
2528

2629
return (
@@ -33,6 +36,7 @@ export const AutocompleteField: TDynamicFormField<TBaseFormElements, IAutocomple
3336
placeholder={placeholder}
3437
onChange={event => onChange(event.target.value || '')}
3538
onBlur={onBlur}
39+
onFocus={onFocus}
3640
/>
3741
</FieldLayout>
3842
);

0 commit comments

Comments
 (0)