Skip to content

Commit

Permalink
feat: implemented events & fixed tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Dec 17, 2024
1 parent 32857eb commit a550b96
Show file tree
Hide file tree
Showing 43 changed files with 954 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface AutocompleteInputProps {
textInputClassName?: string;
onChange: (event: AutocompleteChangeEvent) => void;
onBlur?: (event: FocusEvent<any>) => void;
onFocus?: (event: FocusEvent<any>) => void;
}

export const AutocompleteInput = ({
Expand All @@ -37,6 +38,7 @@ export const AutocompleteInput = ({
textInputClassName,
onChange,
onBlur,
onFocus,
}: AutocompleteInputProps) => {
const safeValue = useMemo(() => {
if (typeof value !== 'string') {
Expand Down Expand Up @@ -81,6 +83,7 @@ export const AutocompleteInput = ({
PaperComponent={Paper as ComponentProps<typeof Autocomplete>['PaperComponent']}
onChange={handleChange}
disabled={disabled}
onFocus={onFocus}
slotProps={{
paper: {
className: 'mt-2 mb-2 w-full',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface DatePickerProps {
textInputClassName?: string;
onChange: (event: DatePickerChangeEvent) => void;
onBlur?: (event: FocusEvent<any>) => void;
onFocus?: (event: FocusEvent<any>) => void;
}

export const DatePickerInput = ({
Expand All @@ -47,6 +48,7 @@ export const DatePickerInput = ({
textInputClassName,
onChange,
onBlur,
onFocus,
}: DatePickerProps) => {
const {
outputValueFormat = 'iso',
Expand Down Expand Up @@ -124,6 +126,7 @@ export const DatePickerInput = ({
onFocus={e => {
setFocused(true);
props.onFocus && props.onFocus(e);
onFocus && onFocus(e);
}}
onBlur={e => {
setFocused(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface MultiSelectProps {
renderSelected: MultiSelectSelectedItemRenderer;
onChange: (selected: MultiSelectValue[], inputName: string) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
onFocus?: (event: FocusEvent<HTMLInputElement>) => void;
}

export const MultiSelect = ({
Expand All @@ -43,6 +44,7 @@ export const MultiSelect = ({
renderSelected,
onChange,
onBlur,
onFocus,
}: MultiSelectProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -174,7 +176,10 @@ export const MultiSelect = ({
placeholder={searchPlaceholder}
style={{ border: 'none' }}
className={ctw('placeholder:text-muted-foreground h-6', textInputClassName)}
onFocus={() => setOpen(true)}
onFocus={event => {
setOpen(true);
onFocus?.(event);
}}
onBlur={onBlur}
data-testid={testId ? `${testId}-search-input` : undefined}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const DynamicFormV2: FunctionComponent<IDynamicFormProps> = ({
onChange,
onFieldChange,
onSubmit,
onEvent,
}) => {
const validationSchema = useValidationSchema(elements);
const valuesApi = useValues({
Expand All @@ -36,8 +37,11 @@ export const DynamicFormV2: FunctionComponent<IDynamicFormProps> = ({
submit,
fieldHelpers,
elementsMap,
callbacks: {
onEvent,
},
}),
[touchedApi.touched, valuesApi.values, submit, fieldHelpers, elementsMap],
[touchedApi.touched, valuesApi.values, submit, fieldHelpers, elementsMap, onEvent],
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { cleanup, render } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Renderer } from '../../Renderer';
import { ValidatorProvider } from '../Validator';
import { DynamicFormContext } from './context';
import { DynamicFormV2 } from './DynamicForm';
import { useSubmit } from './hooks/external';
import { useFieldHelpers } from './hooks/internal/useFieldHelpers';
import { useTouched } from './hooks/internal/useTouched';
import { useValidationSchema } from './hooks/internal/useValidationSchema';
import { useValues } from './hooks/internal/useValues';
import { ICommonFieldParams, IDynamicFormProps, IFormElement, TBaseFormElements } from './types';

// Mock dependencies
vi.mock('../../Renderer');

vi.mock('../Validator');

vi.mock('./hooks/external/useSubmit');

vi.mock('./hooks/internal/useFieldHelpers');

vi.mock('./hooks/internal/useTouched');

vi.mock('./hooks/internal/useValidationSchema');

vi.mock('./hooks/internal/useValues');

vi.mock('./context', () => ({
DynamicFormContext: {
Provider: vi.fn(({ children, value }: any) => {
return <div data-testid="context-provider">{children}</div>;
}),
},
}));

describe('DynamicFormV2', () => {
beforeEach(() => {
cleanup();
vi.restoreAllMocks();

vi.mocked(Renderer).mockImplementation(({ children }: any) => {
return <div data-testid="renderer">{children}</div>;
});
vi.mocked(ValidatorProvider).mockImplementation(({ children }: any) => {
return <div data-testid="validator">{children}</div>;
});

vi.mocked(useTouched).mockReturnValue({
touched: {},
setTouched: vi.fn(),
setFieldTouched: vi.fn(),
touchAllFields: vi.fn(),
} as any);
vi.mocked(useFieldHelpers).mockReturnValue({
getTouched: vi.fn(),
getValue: vi.fn(),
setTouched: vi.fn(),
setValue: vi.fn(),
} as any);
vi.mocked(useSubmit).mockReturnValue({ submit: vi.fn() } as any);
vi.mocked(useValidationSchema).mockReturnValue([] as any);
vi.mocked(useValues).mockReturnValue({
values: {},
setValues: vi.fn(),
setFieldValue: vi.fn(),
} as any);
});

afterEach(() => {
vi.resetAllMocks();
});

const mockProps = {
elements: [],
values: {},
validationParams: {},
elementsMap: {},
onChange: vi.fn(),
onFieldChange: vi.fn(),
onSubmit: vi.fn(),
onEvent: vi.fn(),
} as unknown as IDynamicFormProps<object, TBaseFormElements>;

it('should render without crashing', () => {
render(<DynamicFormV2 {...mockProps} />);
});

it('should pass elements to useValidationSchema', () => {
const elements = [{ id: 'test', element: 'text' }] as unknown as Array<
IFormElement<TBaseFormElements, ICommonFieldParams>
>;
render(<DynamicFormV2 {...mockProps} elements={elements} />);
expect(useValidationSchema).toHaveBeenCalledWith(elements);
});

it('should pass correct props to useValues', () => {
render(<DynamicFormV2 {...mockProps} />);
expect(useValues).toHaveBeenCalledWith({
values: mockProps.values,
onChange: mockProps.onChange,
onFieldChange: mockProps.onFieldChange,
});
});

it('should pass correct props to useTouched', () => {
render(<DynamicFormV2 {...mockProps} />);
expect(useTouched).toHaveBeenCalledWith(mockProps.elements, mockProps.values);
});

it('should pass correct props to useFieldHelpers', () => {
render(<DynamicFormV2 {...mockProps} />);
expect(useFieldHelpers).toHaveBeenCalledWith({
valuesApi: useValues({
values: mockProps.values,
onChange: mockProps.onChange,
onFieldChange: mockProps.onFieldChange,
}),
touchedApi: useTouched(mockProps.elements, mockProps.values),
});
});

it('should pass correct props to useSubmit', () => {
render(<DynamicFormV2 {...mockProps} />);
expect(useSubmit).toHaveBeenCalledWith({
values: mockProps.values,
onSubmit: mockProps.onSubmit,
});
});
it('should pass context to DynamicFormContext.Provider', () => {
const touchedMock = {
touched: { field1: true },
setTouched: vi.fn(),
setFieldTouched: vi.fn(),
touchAllFields: vi.fn(),
};
const valuesMock = {
values: { field1: 'value1' },
setValues: vi.fn(),
setFieldValue: vi.fn(),
};
const submitMock = { submit: vi.fn() };
const fieldHelpersMock = {
getTouched: vi.fn(),
getValue: vi.fn(),
setTouched: vi.fn(),
setValue: vi.fn(),
};

vi.mocked(useTouched).mockReturnValue(touchedMock);
vi.mocked(useValues).mockReturnValue(valuesMock);
vi.mocked(useSubmit).mockReturnValue(submitMock);
vi.mocked(useFieldHelpers).mockReturnValue(fieldHelpersMock);

render(<DynamicFormV2 {...mockProps} />);

// Get the actual props passed to DynamicFormContext.Provider
const providerProps = vi.mocked(DynamicFormContext.Provider).mock.calls[0]?.[0];

expect(providerProps?.value).toEqual({
touched: touchedMock.touched,
values: valuesMock.values,
submit: submitMock.submit,
fieldHelpers: fieldHelpersMock,
elementsMap: mockProps.elementsMap,
callbacks: {
onEvent: mockProps.onEvent,
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react';
import { useContext } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DynamicFormContext } from '../../dynamic-form.context';
import { useDynamicForm } from './useDynamicForm';

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

afterEach(() => {
vi.restoreAllMocks();
});

it('should call useContext with DynamicFormContext', () => {
renderHook(() => useDynamicForm());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { IFormEventElement, TElementEvent } from '../hooks/internal/useEvents/types';
import { IFieldHelpers } from '../hooks/internal/useFieldHelpers/types';
import { ITouchedState } from '../hooks/internal/useTouched';
import { TElementsMap } from '../types';

export interface IDynamicFormCallbacks {
onEvent?: (eventName: TElementEvent, element: IFormEventElement<any, any>) => void;
}

export interface IDynamicFormContext<TValues extends object> {
values: TValues;
touched: ITouchedState;
elementsMap: TElementsMap;
fieldHelpers: IFieldHelpers;
submit: () => void;
callbacks: IDynamicFormCallbacks;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Button } from '@/components/atoms';
import '@testing-library/jest-dom';
import { cleanup, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useValidator } from '../../../Validator';
import { useDynamicForm } from '../../context';
import { useElement } from '../../hooks/external';
import { useField } from '../../hooks/external/useField';
import { IFormElement, TBaseFormElements } from '../../types';
import { ISubmitButtonParams, SubmitButton } from './SubmitButton';
Expand All @@ -13,13 +14,7 @@ vi.mock('@/components/atoms', () => ({
Button: vi.fn(),
}));

vi.mock('../../hooks/external/useElement', () => ({
useElement: vi.fn().mockReturnValue({ id: 'test-id' }),
}));

vi.mock('../../hooks/external/useField', () => ({
useField: vi.fn().mockReturnValue({ disabled: false }),
}));
vi.mock('../../hooks/external/useElement');

vi.mock('../../../Validator', () => ({
useValidator: vi.fn(),
Expand All @@ -29,9 +24,7 @@ vi.mock('../../context', () => ({
useDynamicForm: vi.fn(),
}));

vi.mock('../../hooks/external', () => ({
useField: vi.fn(),
}));
vi.mock('../../hooks/external/useField');

describe('SubmitButton', () => {
const mockElement = {
Expand All @@ -54,6 +47,12 @@ describe('SubmitButton', () => {
vi.mocked(useField).mockReturnValue({ disabled: false } as any);
vi.mocked(useDynamicForm).mockReturnValue({ submit: mockSubmit } as any);
vi.mocked(useValidator).mockReturnValue({ isValid: true } as any);
vi.mocked(useElement).mockReturnValue({ id: 'test-id' } as any);
vi.mocked(useField).mockReturnValue({ disabled: false } as any);
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should render button with default text', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AutocompleteInput } from '@/components/molecules';
import { createTestId } from '@/components/organisms/Renderer';
import { useField } from '../../hooks/external';
import { useField } from '../../hooks/external/useField';
import { FieldLayout } from '../../layouts/FieldLayout';
import { TBaseFormElements, TDynamicFormField } from '../../types';
import { useStack } from '../FieldList/providers/StackProvider';
Expand All @@ -20,7 +20,10 @@ export const AutocompleteField: TDynamicFormField<TBaseFormElements, IAutocomple
}) => {
const { params } = element;
const { stack } = useStack();
const { value, onChange, onBlur, disabled } = useField<string | undefined>(element, stack);
const { value, onChange, onBlur, onFocus, disabled } = useField<string | undefined>(
element,
stack,
);
const { options = [], placeholder = '' } = params || {};

return (
Expand All @@ -33,6 +36,7 @@ export const AutocompleteField: TDynamicFormField<TBaseFormElements, IAutocomple
placeholder={placeholder}
onChange={event => onChange(event.target.value || '')}
onBlur={onBlur}
onFocus={onFocus}
/>
</FieldLayout>
);
Expand Down
Loading

0 comments on commit a550b96

Please sign in to comment.