/** *******************************************************************************************************************
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. *
******************************************************************************************************************** */
import React from 'react';
import { render, fireEvent, cleanup, act } from '@testing-library/react';
import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types';
import FormRenderer, { componentTypes } from '.';
describe('FormRenderer', () => {
afterEach(() => {
jest.resetAllMocks();
cleanup();
});
const handleCancel = jest.fn();
const handleSubmit = jest.fn();
const baseSchema = {
header: 'header',
description: 'description',
};
describe('Form basic ', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'name',
label: 'Name',
isRequired: true,
},
],
};
it('should render header and description', () => {
const { getByText } = render(
);
expect(getByText('header')).toBeVisible();
expect(getByText('description')).toBeVisible();
});
it('should trigger cancel event when user clicks the Cancel button', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Cancel'));
expect(handleCancel).toHaveBeenCalled();
});
it('should trigger submit with the data', () => {
const { getByLabelText, getByText } = render(
);
act(() => {
fireEvent.change(getByLabelText('Name'), { target: { value: 'my name' } });
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith({ name: 'my name' }, expect.any(Object), expect.any(Function));
});
it('should render custom label for cancel and submit button', () => {
const customLabelsSchema = {
...schema,
submitLabel: 'Custom Submit Label',
cancelLabel: 'Custom Cancel Label',
};
const { getByText } = render(
);
expect(getByText('Custom Submit Label')).toBeVisible();
expect(getByText('Custom Cancel Label')).toBeVisible();
});
it('should show a loading spinner while submitting', () => {
const { queryByRole } = render(
);
expect(queryByRole('progressbar')).toBeInTheDocument();
});
it('should show a loading spinner while submitting a wizard', () => {
const { queryByRole } = render(
);
expect(queryByRole('progressbar')).toBeInTheDocument();
});
it('should not show a loading spinner by default', () => {
const { queryByRole } = render(
);
expect(queryByRole('progressbar')).not.toBeInTheDocument();
});
it('should trigger reset to reset form to initialValues', () => {
const { getByLabelText, getByText, getByDisplayValue } = render(
);
act(() => {
fireEvent.change(getByLabelText('Name'), { target: { value: 'My new name' } });
});
expect(getByDisplayValue('My new name')).toBeVisible();
act(() => {
fireEvent.click(getByText('Reset'));
});
expect(getByDisplayValue('My name')).toBeVisible();
});
it('can be accessed by custom test-id', () => {
const schemaWithTestId = {
...schema,
'data-testid': 'form-renderer-1',
};
const { getByTestId } = render(
);
expect(getByTestId('form-renderer-1')).toBeInTheDocument();
});
});
describe('Checkbox', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.CHECKBOX,
name: 'checkbox',
label: 'Checkbox',
options: [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
{
label: 'Option 3',
value: '3',
},
],
isRequired: true,
'data-testid': 'checkbox-1',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
it('should render default checkbox', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.CHECKBOX,
name: 'checkbox',
label: 'Checkbox',
isRequired: true,
'data-testid': 'checkbox-1',
},
],
};
const { getByLabelText, getByText, getByTestId } = render(
);
expect(getByTestId('checkbox-1')).toBeInTheDocument();
fireEvent.click(getByLabelText('Checkbox'));
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith({ checkbox: true }, expect.any(Object), expect.any(Function));
});
it('should render checkboxes', () => {
const { getByLabelText, getByText, getByTestId } = render(
);
expect(getByTestId('checkbox-1')).toBeInTheDocument();
expect(getByTestId('checkbox-1-1')).toBeInTheDocument();
expect(getByTestId('checkbox-1-2')).toBeInTheDocument();
expect(getByTestId('checkbox-1-3')).toBeInTheDocument();
fireEvent.click(getByLabelText('Option 1'));
fireEvent.click(getByLabelText('Option 3'));
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith(
{ checkbox: ['1', '3'] },
expect.any(Object),
expect.any(Function)
);
});
it('should trigger validation', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('Treeview', () => {
const treeItems = {
id: '1',
label: 'Tree Root',
children: [
{
id: '2',
label: 'Node 1.0',
children: [
{ id: '3', label: 'Node 1.1' },
{ id: '4', label: 'Node 1.3' },
],
},
{
id: '5',
label: 'Node 2.0',
children: [
{
id: '6',
label: 'Node 2.1.0',
children: [{ id: '7', label: 'Node 2.1.2' }],
},
{ id: '8', label: 'Node 2.2' },
],
},
],
};
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.TREE_VIEW,
label: 'this is a tree',
helperText: 'this is a hint',
description: 'this is a description',
name: 'tree',
treeItems: treeItems,
multiSelect: true,
defaultExpanded: ['1', '2', '5'],
'data-testid': 'tree-view-1',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
it('should render Treeview', () => {
const { getByText, getByTestId } = render(
);
expect(getByTestId('tree-view-1')).toBeInTheDocument();
act(() => {
fireEvent.click(getByText('Node 1.1'));
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith({ tree: ['3'] }, expect.any(Object), expect.any(Function));
});
it('should trigger validation', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('Wizard', () => {
const ReviewTemplate = ({ data }: any) => {
return (
<>
{data.name}-{data.textarea}
>
);
};
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.WIZARD,
name: 'wizard',
submitButtonText: 'Custom Submit Label',
fields: [
{
name: 'step-1',
title: 'Step 1',
description: 'Descrirption for Step 1',
fields: [
{
component: componentTypes.TEXTAREA,
name: 'textarea',
label: 'Textarea',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
},
{
name: 'step-2',
title: 'Step 2',
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'name',
label: 'Name',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
},
{
name: 'step-3',
title: 'Review',
fields: [
{
component: componentTypes.REVIEW,
name: 'review',
Template: ReviewTemplate,
},
],
},
],
'data-testid': 'wizard-1',
},
],
};
it('should render Wizard', () => {
const { getByText, getAllByText, getByLabelText, getByTestId } = render(
);
expect(getAllByText('Step 1')).toHaveLength(3);
expect(getByTestId('wizard-1')).toBeInTheDocument();
expect(getByText('Descrirption for Step 1')).toBeVisible();
act(() => {
fireEvent.change(getByLabelText('textarea'), { target: { value: 'my content' } });
fireEvent.click(getByText('Next'));
});
expect(getAllByText('Step 2')).toHaveLength(3);
act(() => {
fireEvent.change(getByLabelText('Name'), { target: { value: 'my name' } });
fireEvent.click(getByText('Next'));
});
expect(getAllByText('Review')).toHaveLength(2);
expect(getByText('my name-my content')).toBeVisible();
fireEvent.click(getByText('Custom Submit Label'));
expect(handleSubmit).toHaveBeenCalledWith(
{ name: 'my name', textarea: 'my content' },
expect.any(Object),
expect.any(Function)
);
});
it('should handle navigation', () => {
const { getByText, getAllByText, getByLabelText } = render(
);
expect(getAllByText('Step 1')).toHaveLength(3);
act(() => {
fireEvent.change(getByLabelText('textarea'), { target: { value: 'my content' } });
fireEvent.click(getByText('Next'));
});
expect(getAllByText('Step 2')).toHaveLength(3);
act(() => {
fireEvent.click(getByText('Previous'));
});
expect(getAllByText('Step 1')).toHaveLength(3);
});
it('should trigger validation', () => {
const { getByText, getAllByText, getByLabelText } = render(
);
expect(getAllByText('Step 1')).toHaveLength(3);
expect(getByText('Descrirption for Step 1')).toBeVisible();
act(() => {
fireEvent.click(getByText('Next'));
});
expect(getByText('Required')).toBeVisible();
act(() => {
fireEvent.change(getByLabelText('textarea'), { target: { value: 'my content' } });
fireEvent.click(getByText('Next'));
});
expect(getAllByText('Step 2')).toHaveLength(3);
});
});
describe('Subform', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.SUB_FORM,
title: 'Subform 1',
description: 'This is a subform',
name: 'subform1',
'data-testid': 'subform-1',
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'name',
label: 'Name',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
},
],
};
it('should render Subform', () => {
const { getByText, getByLabelText, getByTestId } = render(
);
expect(getByTestId('subform-1')).toBeInTheDocument();
expect(getByText('Subform 1')).toBeVisible();
expect(getByText('This is a subform')).toBeVisible();
act(() => {
fireEvent.change(getByLabelText('Name'), { target: { value: 'my name' } });
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith({ name: 'my name' }, expect.any(Object), expect.any(Function));
});
it('should trigger validate', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
const CustomComponentSimple = ({ label }: any) => <>{label}>;
const CustomComponentComplex = ({ input }: any) => (
);
describe('Custom', () => {
it('renders custom component', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.CUSTOM,
name: 'text',
label: 'This is the content of custom component',
CustomComponent: CustomComponentSimple,
},
],
};
const { getByText } = render();
expect(getByText('This is the content of custom component')).toBeVisible();
});
it('supports interaction', () => {
const handleSubmit = jest.fn();
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.CUSTOM,
name: 'input',
CustomComponent: CustomComponentComplex,
},
],
};
const { getByTestId, getByText } = render(
);
// expect(getByTestId('input')).toHaveValue('initial input');
act(() => {
fireEvent.change(getByTestId('input'), { target: { value: 'updated input' } });
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith(
{
input: 'updated input',
},
expect.any(Object),
expect.any(Function)
);
});
});
describe('ExpandableSection', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.EXPANDABLE_SECTION,
title: 'Additional information',
description: 'This is for additional information',
name: 'additionalInfo',
'data-testid': 'expandable-section-1',
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'name',
label: 'Name',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
},
],
};
it('should render ExpandableSection', () => {
const { getByText, getByLabelText, getByTestId } = render(
);
expect(getByText('Additional information')).toBeVisible();
expect(getByText('This is for additional information')).toBeVisible();
expect(getByLabelText('Name')).not.toBeVisible();
expect(getByTestId('expandable-section-1')).toBeInTheDocument();
act(() => {
fireEvent.click(getByText('Additional information'));
fireEvent.change(getByLabelText('Name'), { target: { value: 'my name' } });
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith(
{
additionalInfo: {
name: 'my name',
},
},
expect.any(Object),
expect.any(Function)
);
});
it('should trigger valiation', () => {
const { getByText } = render(
);
expect(getByText('Additional information')).toBeVisible();
expect(getByText('This is for additional information')).toBeVisible();
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('Switch', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.SWITCH,
name: 'switch',
label: 'Switch',
initialValue: false,
'data-testid': 'switch-1',
},
],
};
it('should render Switch', async () => {
const { getByLabelText, getByText, getByTestId } = render(
);
expect(getByLabelText('Switch')).toBeInTheDocument();
expect(getByTestId('switch-1')).toBeInTheDocument();
act(() => {
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith({ switch: false }, expect.any(Object), expect.any(Function));
});
});
describe('Datepicker', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.DATE_PICKER,
name: 'datePicker',
label: 'Date picker',
'data-testid': 'date-picker-1',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
it('should render Datepicker', () => {
const { getByLabelText, getByText, getByTestId } = render(
);
expect(getByText('Date picker')).toBeVisible();
expect(getByTestId('date-picker-1')).toBeInTheDocument();
act(() => {
fireEvent.change(getByLabelText('Date picker'), { target: { value: '20200101' } });
});
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith(
{ datePicker: expect.any(Date) },
expect.any(Object),
expect.any(Function)
);
});
it('should trigger validation', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
it('should handle initial value', () => {
const date = new Date(2020, 0, 1, 1, 1, 1, 1);
const { getByText, getByLabelText } = render(
);
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith({ datePicker: date }, expect.any(Object), expect.any(Function));
act(() => {
fireEvent.change(getByLabelText('Date picker'), { target: { value: '2021/02/02' } });
});
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenLastCalledWith(
{ datePicker: new Date(2021, 1, 2) },
expect.any(Object),
expect.any(Function)
);
});
});
describe('TimePicker', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.TIME_PICKER,
name: 'timePicker',
label: 'Time picker',
'data-testid': 'time-picker-1',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
it('should render a TimePicker', () => {
const { getByLabelText, getByText, getByTestId } = render(
);
expect(getByText('Time picker')).toBeVisible();
expect(getByTestId('time-picker-1')).toBeInTheDocument();
act(() => {
fireEvent.change(getByLabelText('Time picker'), { target: { value: '10:35 AM' } });
});
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith(
{ timePicker: expect.stringMatching(/.*T10:35:.*Z$/) },
expect.any(Object),
expect.any(Function)
);
});
it('should trigger validation', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('Radio', () => {
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.RADIO,
name: 'radio',
label: 'Radio',
options: [
{
label: 'Option 1',
description: 'Description 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
{
label: 'Option 3',
value: '3',
},
],
'data-testid': 'radio-1',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
it('should render radio buttons', () => {
const { getByLabelText, getByText, getAllByRole, getByTestId } = render(
);
expect(getByText('Radio')).toBeVisible();
expect(getAllByRole('radio')).toHaveLength(3);
expect(getByText('Description 1')).toBeVisible();
expect(getByTestId('radio-1')).toBeInTheDocument();
expect(getByTestId('radio-1-1')).toBeInTheDocument();
expect(getByTestId('radio-1-2')).toBeInTheDocument();
expect(getByTestId('radio-1-3')).toBeInTheDocument();
fireEvent.click(getByLabelText('Option 3'));
fireEvent.click(getByText('Submit'));
expect(handleSubmit).toHaveBeenCalledWith({ radio: '3' }, expect.any(Object), expect.any(Function));
});
it('should trigger validation', () => {
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('Select', () => {
const field = {
component: componentTypes.SELECT,
name: 'select',
label: 'Select',
placeholder: 'Choose an option',
options: [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
{
label: 'Option 3',
value: '3',
},
],
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
};
it('should render Select', () => {
const schema = {
...baseSchema,
'data-testid': 'select-1',
fields: [field],
};
const { getByText, getByTestId } = render(
);
expect(getByText('Select')).toBeVisible();
expect(getByTestId('select-1')).toBeInTheDocument();
});
it('should render Autosuggest when isSearchable is true', () => {
const schema = {
...baseSchema,
fields: [{ ...field, isSearchable: true }],
};
const { getByTestId } = render(
);
expect(getByTestId('autosuggest')).toBeInTheDocument();
});
it('should render Multiselect when multiSelect is true', () => {
const schema = {
...baseSchema,
fields: [{ ...field, multiSelect: true }],
};
const { getByTestId } = render(
);
expect(getByTestId('multiselect')).toBeInTheDocument();
});
it('should trigger validation', () => {
const schema = {
...baseSchema,
fields: [field],
};
const { getByText } = render(
);
fireEvent.click(getByText('Submit'));
expect(getByText('Required')).toBeVisible();
});
});
describe('FieldArray', () => {
const fieldArrayName = 'fieldArray';
const fieldArrayTestId = 'field-array-1';
const schema = {
...baseSchema,
fields: [
{
component: componentTypes.FIELD_ARRAY,
label: 'Attribute Editor',
description: 'This is a form array',
name: fieldArrayName,
helperText: 'You can add up to 6 more items.',
minItems: 1,
maxItems: 3,
noItemsMessage: 'Please add new item',
'data-testid': fieldArrayTestId,
defaultItem: {
key: 'key',
value: 'value',
},
validate: [
{
type: validatorTypes.MIN_ITEMS,
threshold: 1,
},
{
type: validatorTypes.REQUIRED,
},
],
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'key',
label: 'Key',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: componentTypes.TEXT_FIELD,
name: 'value',
label: 'Value',
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
},
],
};
const fieldArrayTest = (schema: any) => {
const { getByText, getAllByRole, getByLabelText, getByTestId } = render(
);
expect(getByText('Attribute Editor')).toBeVisible();
expect(getByText('This is a form array')).toBeVisible();
expect(getByText('You can add up to 6 more items.')).toBeVisible();
expect(getByText('Please add new item'));
expect(getByTestId(fieldArrayTestId)).toBeInTheDocument();
expect(getByTestId(`${fieldArrayTestId}-add-button`)).toBeInTheDocument();
act(() => {
fireEvent.click(getAllByRole('button')[0]);
});
expect(getByText('Key')).toBeVisible();
expect(getByText('Value')).toBeVisible();
expect(getByTestId(`${fieldArrayTestId}-${fieldArrayName}[0]`)).toBeInTheDocument();
expect(
getByTestId(`${fieldArrayTestId}-${fieldArrayName}[0]-${fieldArrayName}[0].key`)
).toBeInTheDocument();
expect(
getByTestId(`${fieldArrayTestId}-${fieldArrayName}[0]-${fieldArrayName}[0].value`)
).toBeInTheDocument();
expect(getByTestId(`${fieldArrayTestId}-${fieldArrayName}[0]-remove-button`)).toBeInTheDocument();
act(() => {
fireEvent.change(getByLabelText('Key'), { target: { value: 'key' } });
fireEvent.change(getByLabelText('Value'), { target: { value: 'value' } });
fireEvent.click(getByText('Submit'));
});
expect(handleSubmit).toHaveBeenCalledWith(
{
fieldArray: [
{
key: 'key',
value: 'value',
},
],
},
expect.any(Object),
expect.any(Function)
);
};
it('should render FieldArray', () => {
fieldArrayTest(schema);
});
it('should render FieldArray with grid', () => {
fieldArrayTest({ ...schema, layout: 'grid' });
});
it('should render FieldArray with stack', () => {
fieldArrayTest({ ...schema, layout: 'stack' });
});
});
});