Skip to content

Commit

Permalink
feat: added initial demo & bugfixes & updated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Dec 18, 2024
1 parent 7f07d19 commit fbc135a
Show file tree
Hide file tree
Showing 37 changed files with 644 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type AutocompleteChangeEvent = React.ChangeEvent<{
}>;

export interface AutocompleteInputProps {
id?: string;
value?: string;
options: AutocompleteOption[];
placeholder?: string;
Expand All @@ -29,6 +30,7 @@ export interface AutocompleteInputProps {
}

export const AutocompleteInput = ({
id,
options,
value = '',
placeholder,
Expand Down Expand Up @@ -75,6 +77,7 @@ export const AutocompleteInput = ({
return (
<ThemeProvider theme={muiTheme}>
<Autocomplete
id={id}
disablePortal
options={optionLabels}
getOptionLabel={label => label}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DynamicFormV2 } from './DynamicForm';
import { InputsShowcaseComponent } from './_stories/InputsShowcase/InputsShowcase';

export default {
component: DynamicFormV2,
};

export const InputsShowcase = {
render: () => <InputsShowcaseComponent />,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { AnyObject } from '@/common';
import { useState } from 'react';
import { JSONEditorComponent } from '../../../Validator/_stories/components/JsonEditor/JsonEditor';
import { DynamicFormV2 } from '../../DynamicForm';
import { IFormElement } from '../../types';

const schema: Array<IFormElement<any, any>> = [
{
id: 'TextField',
element: 'textfield',
valueDestination: 'textfield',
params: {
label: 'Text Field',
placeholder: 'Enter text',
},
validate: [
{
type: 'required',
value: {},
message: 'Text field value is required',
},
],
},
{
id: 'AutocompleteField',
element: 'autocompletefield',
valueDestination: 'autocomplete',
params: {
label: 'Autocomplete Field',
placeholder: 'Select an option',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
],
},
},
{
id: 'CheckboxListField',
element: 'checkboxlistfield',
valueDestination: 'checkboxlist',
params: {
label: 'Checkbox List Field',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
],
},
},
{
id: 'DateField',
element: 'datefield',
valueDestination: 'date',
params: {
label: 'Date Field',
},
},
{
id: 'MultiselectField',
element: 'multiselectfield',
valueDestination: 'multiselect',
params: {
label: 'Multiselect Field',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
],
},
},
{
id: 'SelectField',
element: 'selectfield',
valueDestination: 'select',
params: {
label: 'Select Field',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' },
],
},
},
{
id: 'CheckboxField',
element: 'checkboxfield',
valueDestination: 'checkbox',
params: {
label: 'Checkbox Field',
},
},
{
id: 'FieldList',
element: 'fieldlist',
valueDestination: 'fieldlist',
params: {
label: 'Field List',
},
children: [
{
id: 'Nested-TextField',
element: 'textfield',
valueDestination: 'textfield[$0]',
params: {
label: 'Text Field',
placeholder: 'Enter text',
},
validate: [],
},
],
},
{
id: 'SubmitButton',
element: 'submitbutton',
valueDestination: 'submitbutton',
params: {
label: 'Submit Button',
},
},
];

export const InputsShowcaseComponent = () => {
const [context, setContext] = useState<AnyObject>({});

return (
<div className="flex h-screen w-full flex-row flex-nowrap gap-4">
<div className="w-1/2">
<DynamicFormV2
elements={schema}
values={context}
onSubmit={() => {
console.log('onSubmit');
}}
onChange={setContext}
onEvent={console.log}
/>
</div>
<div className="w-1/2">
<JSONEditorComponent value={context} readOnly />
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './InputsShowcase';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button } from '@/components/atoms';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { useValidator } from '../../../Validator';
import { useDynamicForm } from '../../context';
import { useElement } from '../../hooks/external/useElement';
Expand All @@ -14,7 +14,10 @@ export interface ISubmitButtonParams {
export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({ element }) => {
const { id } = useElement(element);
const { disabled: _disabled } = useField(element);
const { submit } = useDynamicForm();
const { fieldHelpers, submit } = useDynamicForm();

const { touchAllFields } = fieldHelpers;

const { isValid } = useValidator();

const { disableWhenFormIsInvalid = false, text = 'Submit' } = element.params || {};
Expand All @@ -25,12 +28,20 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({
return _disabled;
}, [disableWhenFormIsInvalid, isValid, _disabled]);

const handleSubmit = useCallback(() => {
touchAllFields();

if (!isValid) return;

submit();
}, [submit, isValid, touchAllFields]);

return (
<Button
data-testid={`${id}-submit-button`}
variant="secondary"
disabled={disabled}
onClick={() => submit()}
onClick={handleSubmit}
>
{text}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useValidator } from '../../../Validator';
import { useDynamicForm } from '../../context';
import { useElement } from '../../hooks/external';
import { useField } from '../../hooks/external/useField';
import { useFieldHelpers } from '../../hooks/internal/useFieldHelpers';
import { IFormElement } from '../../types';
import { ISubmitButtonParams, SubmitButton } from './SubmitButton';

Expand All @@ -20,11 +21,10 @@ vi.mock('../../../Validator', () => ({
useValidator: vi.fn(),
}));

vi.mock('../../context', () => ({
useDynamicForm: vi.fn(),
}));
vi.mock('../../context');

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

describe('SubmitButton', () => {
const mockElement = {
Expand All @@ -38,17 +38,37 @@ describe('SubmitButton', () => {

beforeEach(() => {
cleanup();
// vi.restoreAllMocks();
vi.clearAllMocks();

const mockSubmit = vi.fn();

vi.mocked(Button).mockImplementation(({ children, ...props }) => (
<button {...props}>{children}</button>
<button
{...props}
type="button"
onClick={e => {
console.log('CLICKED');
props.onClick?.(e);
}}
>
{children}
</button>
));

vi.mocked(useFieldHelpers).mockReturnValue({
touchAllFields: vi.fn(),
} as any);
vi.mocked(useDynamicForm).mockReturnValue({
submit: mockSubmit,
fieldHelpers: {
touchAllFields: vi.fn(),
},
} as any);

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(() => {
Expand All @@ -57,8 +77,7 @@ describe('SubmitButton', () => {

it('should render button with default text', () => {
render(<SubmitButton element={mockElement} />);

screen.getByText('Submit');
expect(screen.getByText('Submit')).toBeInTheDocument();
});

it('should render button with custom text', () => {
Expand All @@ -68,16 +87,7 @@ describe('SubmitButton', () => {
};

render(<SubmitButton element={elementWithText} />);

screen.getByText('Custom Submit');
});

it('should call submit when clicked', async () => {
render(<SubmitButton element={mockElement} />);

await userEvent.click(screen.getByRole('button'));

expect(mockSubmit).toHaveBeenCalled();
expect(screen.getByText('Custom Submit')).toBeInTheDocument();
});

describe('disabled state', () => {
Expand Down Expand Up @@ -110,19 +120,29 @@ describe('SubmitButton', () => {

expect(screen.getByRole('button')).not.toBeDisabled();
});

it('should not call submit when form is invalid and disableWhenFormIsInvalid is true', async () => {
vi.mocked(useValidator).mockReturnValue({ isValid: false } as any);

const elementWithDisable = {
...mockElement,
params: { disableWhenFormIsInvalid: true },
};

render(<SubmitButton element={elementWithDisable} />);
await userEvent.click(screen.getByRole('button'));

expect(mockSubmit).not.toHaveBeenCalled();
});
});

it('should have correct test id', () => {
render(<SubmitButton element={mockElement} />);

expect(screen.getByTestId('test-id-submit-button')).toBeInTheDocument();
});

it('shold call submit when clicked', async () => {
it('should render with secondary variant', () => {
render(<SubmitButton element={mockElement} />);

await userEvent.click(screen.getByRole('button'));

expect(mockSubmit).toHaveBeenCalled();
expect(vi.mocked(Button).mock.calls[0]?.[0]?.variant).toBe('secondary');
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AutocompleteInput } from '@/components/molecules';
import { createTestId } from '@/components/organisms/Renderer';
import { useElement } from '../../hooks/external';
import { useField } from '../../hooks/external/useField';
import { FieldLayout } from '../../layouts/FieldLayout';
import { TDynamicFormField } from '../../types';
Expand All @@ -18,6 +19,7 @@ export interface IAutocompleteFieldParams {
export const AutocompleteField: TDynamicFormField<IAutocompleteFieldParams> = ({ element }) => {
const { params } = element;
const { stack } = useStack();
const { id } = useElement(element, stack);
const { value, onChange, onBlur, onFocus, disabled } = useField<string | undefined>(
element,
stack,
Expand All @@ -27,6 +29,7 @@ export const AutocompleteField: TDynamicFormField<IAutocompleteFieldParams> = ({
return (
<FieldLayout element={element}>
<AutocompleteInput
id={id}
disabled={disabled}
value={value}
options={options}
Expand Down
Loading

0 comments on commit fbc135a

Please sign in to comment.