remove(field.name)}
52 | />
53 | ) : null}
54 | >
55 | ),
56 | };
57 | }),
58 | }}
59 | />
60 |
63 | >
64 | );
65 | },
66 | },
67 | ],
68 | };
69 |
70 | const handleFinish = useCallback((values: any) => {
71 | console.log('Submit: ', values);
72 | }, []);
73 |
74 | return (
75 |
78 |
81 |
82 |
83 | );
84 | };
85 |
86 | describe('antd/FormListManual', () => {
87 | config.addAdapter(antdAdapter);
88 |
89 | it('renders FormListManual Nice Form using Antd', () => {
90 | render(
);
91 | const inputPwd = screen.getByLabelText('Password');
92 | expect(inputPwd).toBeInTheDocument();
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/Mixed.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { Form, Input, Button } from 'antd';
3 | import NiceForm from '../../src/NiceForm';
4 | import config from '../../src/config';
5 | import { render, screen } from '@testing-library/react';
6 | import antdAdapter from '../../src/adapters/antdAdapter';
7 | import '@testing-library/jest-dom';
8 |
9 | const Mixed = () => {
10 | const [form] = Form.useForm();
11 | const handleFinish = useCallback((values: any) => console.log('Submit: ', values), []);
12 | const meta1 = {
13 | fields: [
14 | { key: 'name.first', label: 'First Name', required: true },
15 | { key: 'name.last', label: 'Last Name', required: true },
16 | { key: 'dob', label: 'Date of Birth', widget: 'date-picker' },
17 | ],
18 | };
19 | const meta2 = {
20 | fields: [
21 | {
22 | key: 'email',
23 | label: 'Email',
24 | rules: [{ type: 'email', message: 'Invalid email' }],
25 | },
26 | ],
27 | };
28 |
29 | const prefixMeta = {
30 | fields: [
31 | {
32 | key: 'prefix',
33 | options: ['+86', '+87'],
34 | widget: 'select',
35 | noStyle: true,
36 | widgetProps: {
37 | style: { width: 70 },
38 | noStyle: true,
39 | },
40 | },
41 | ],
42 | };
43 | const prefixSelector =
;
44 |
45 | return (
46 |
55 |
56 |
57 |
58 |
59 |
62 |
63 |
64 | );
65 | };
66 |
67 | describe('antd/Mixed', () => {
68 | config.addAdapter(antdAdapter);
69 |
70 | it('renders Mixed Nice Form using Antd', () => {
71 | render(
);
72 | const inputFirstName = screen.getByLabelText('First Name');
73 | expect(inputFirstName).toBeInTheDocument();
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/MultipleColumns.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import { Form, Button } from 'antd';
3 | import type { RadioChangeEvent } from 'antd';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import { render, screen } from '@testing-library/react';
7 | import antdAdapter, { AntdNiceFormMeta } from '../../src/adapters/antdAdapter';
8 | import '@testing-library/jest-dom';
9 |
10 | const MultipleColumns = () => {
11 | const [form] = Form.useForm();
12 | const [columns, setColumns] = useState(2);
13 | const handleFinish = useCallback((values: any) => {
14 | console.log('Submit: ', values);
15 | }, []);
16 | const meta: AntdNiceFormMeta = {
17 | columns,
18 | fields: [
19 | {
20 | key: 'columns',
21 | label: 'Columns',
22 | widget: 'radio-group',
23 | widgetProps: {
24 | optionType: 'button',
25 | buttonStyle: 'solid',
26 | onChange: (evt: RadioChangeEvent) => setColumns(evt.target.value),
27 | },
28 | options: [1, 2, 3, 4],
29 | initialValue: 2,
30 | help: 'Change columns to show layout change',
31 | },
32 | { key: 'input', label: 'Input', required: true, tooltip: 'This is the name.' },
33 | {
34 | key: 'checkbox',
35 | label: 'Checkbox',
36 | widget: 'checkbox',
37 | initialValue: true,
38 | },
39 | { key: 'select', label: 'Select', widget: 'select', options: ['Apple', 'Orange', 'Banana'] },
40 | { key: 'password', label: 'Password', widget: 'password' },
41 | { key: 'textarea', label: 'Textarea', widget: 'textarea' },
42 | { key: 'number', label: 'Number', widget: 'number' },
43 | { key: 'date-picker', label: 'Date Picker', widget: 'date-picker' },
44 | ],
45 | };
46 | return (
47 |
50 |
53 |
54 |
55 | );
56 | };
57 |
58 | describe('antd/MultipleColumns', () => {
59 | config.addAdapter(antdAdapter);
60 |
61 | it('renders MultipleColumns Form using Antd', () => {
62 | render(
);
63 | const inputField = screen.getByLabelText('Input');
64 | expect(inputField).toBeInTheDocument();
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/Simple.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { Form, Button } from 'antd';
3 | import NiceForm from '../../src/NiceForm';
4 | import config from '../../src/config';
5 | import { render, screen } from '@testing-library/react';
6 | import antdAdapter from '../../src/adapters/antdAdapter';
7 | import '@testing-library/jest-dom';
8 |
9 | const Simple = () => {
10 | const meta = {
11 | layout: 'horizontal',
12 | columns: 1,
13 |
14 | fields: [
15 | { key: 'username', label: 'User Name' },
16 | { key: 'password', label: 'Password', widget: 'password' },
17 | ],
18 | };
19 |
20 | const handleFinish = useCallback((values: any) => {
21 | console.log('Submit: ', values);
22 | }, []);
23 |
24 | return (
25 |
28 |
31 |
32 |
33 | );
34 | };
35 |
36 | describe('antd/Simple', () => {
37 | config.addAdapter(antdAdapter);
38 |
39 | it('renders Simple Nice Form using Antd', () => {
40 | render(
);
41 | const inputPasswd = screen.getByLabelText('Password');
42 | expect(inputPasswd).toBeInTheDocument();
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/SingleField.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { Form, Button } from 'antd';
3 | import NiceForm from '../../src/NiceForm';
4 | import config from '../../src/config';
5 | import { render, screen } from '@testing-library/react';
6 | import antdAdapter from '../../src/adapters/antdAdapter';
7 | import '@testing-library/jest-dom';
8 |
9 | const SingleField = () => {
10 | const handleFinish = useCallback((values: any) => {
11 | console.log('Submit: ', values);
12 | }, []);
13 |
14 | return (
15 |
27 |
30 |
31 |
32 | );
33 | };
34 |
35 | describe('antd/SingleField', () => {
36 | config.addAdapter(antdAdapter);
37 |
38 | it('renders SingleField Nice Form using Antd', () => {
39 | render(
);
40 | const inputUsername = screen.getByPlaceholderText('Username');
41 | expect(inputUsername).toBeInTheDocument();
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/Validation.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { Form, Button } from 'antd';
3 | import NiceForm from '../../src/NiceForm';
4 | import config from '../../src/config';
5 | import { render, screen } from '@testing-library/react';
6 | import antdAdapter, { AntdNiceFormMeta } from '../../src/adapters/antdAdapter';
7 | import '@testing-library/jest-dom';
8 |
9 | const MOCK_USERNAMES: {
10 | [key: string]: boolean;
11 | } = {
12 | nate: true,
13 | bood: true,
14 | kevin: true,
15 | };
16 |
17 | const Validation = () => {
18 | const [form] = Form.useForm();
19 | const handleSubmit = useCallback((values: any) => {
20 | console.log('Submit: ', values);
21 | }, []);
22 |
23 | const meta: AntdNiceFormMeta = {
24 | fields: [
25 | {
26 | key: 'username',
27 | label: 'Username',
28 | extra: 'Note: username nate, bood or kevin already exist',
29 | hasFeedback: true, // Show validation status icon in the right
30 | required: true, // this adds an entry to rules: [{ required: true, message: 'Username is required' }]
31 | rules: [
32 | {
33 | validator: (_, value) => {
34 | // Do async validation to check if username already exists
35 | // Use setTimeout to emulate api call
36 | return new Promise((resolve, reject) => {
37 | setTimeout(() => {
38 | if (MOCK_USERNAMES[value]) {
39 | reject(new Error(`Username "${value}" already exists.`));
40 | } else {
41 | resolve(value);
42 | }
43 | }, 1000);
44 | });
45 | },
46 | },
47 | ],
48 | },
49 | {
50 | key: 'password',
51 | label: 'Password',
52 | widget: 'password',
53 | onChange: () => {
54 | if (form.isFieldTouched('confirmPassword')) {
55 | form.validateFields(['confirmPassword']);
56 | }
57 | },
58 | rules: [
59 | // This is equivalent with "required: true"
60 | {
61 | required: true,
62 | message: 'Password is required',
63 | },
64 | ],
65 | },
66 | {
67 | key: 'confirmPassword',
68 | label: 'Confirm Passowrd',
69 | widget: 'password',
70 | required: true,
71 | rules: [
72 | {
73 | validator: (_, value) => {
74 | return new Promise((resolve, reject) => {
75 | if (value !== form.getFieldValue('password')) {
76 | reject(new Error('Two passwords are inconsistent.'));
77 | } else {
78 | resolve(value);
79 | }
80 | });
81 | },
82 | },
83 | ],
84 | },
85 | ],
86 | };
87 |
88 | return (
89 |
92 |
95 |
96 |
97 | );
98 | };
99 |
100 | describe('antd/Validation', () => {
101 | config.addAdapter(antdAdapter);
102 |
103 | it('renders Validation Nice Form using Antd', () => {
104 | render(
);
105 | const inputUsername = screen.getByLabelText('Username');
106 | expect(inputUsername).toBeInTheDocument();
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/ViewEdit.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react';
2 | import { Form, Button, message } from 'antd';
3 | import dayjs, { Dayjs } from 'dayjs';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import { render, screen } from '@testing-library/react';
7 | import antdAdapter, { AntdNiceFormMeta } from '../../src/adapters/antdAdapter';
8 | import '@testing-library/jest-dom';
9 |
10 | const MOCK_INFO = {
11 | name: { first: 'Nate', last: 'Wang' },
12 | email: 'myemail@gmail.com',
13 | gender: 'Male',
14 | dateOfBirth: dayjs('2100-01-01'),
15 | phone: '15988888888',
16 | city: 'Shanghai',
17 | address: 'No.1000 Some Road, Zhangjiang Park, Pudong New District',
18 | };
19 |
20 | const DateView = ({ value }: { value: Dayjs }) => value.format('MMM Do YYYY');
21 |
22 | const ViewEdit = () => {
23 | const [form] = Form.useForm();
24 | const [viewMode, setViewMode] = useState(true);
25 | const [pending, setPending] = useState(false);
26 | const [personalInfo, setPersonalInfo] = useState(MOCK_INFO);
27 | const handleFinish = useCallback((values: any) => {
28 | setPending(true);
29 | setTimeout(() => {
30 | setPending(false);
31 | setPersonalInfo(values);
32 | setViewMode(true);
33 | message.success('Infomation updated.');
34 | }, 1500);
35 | }, []);
36 |
37 | const getMeta = () => {
38 | const meta: AntdNiceFormMeta = {
39 | columns: 2,
40 | disabled: pending,
41 | viewMode: viewMode,
42 | initialValues: personalInfo,
43 | fields: [
44 | {
45 | key: 'name.first',
46 | name: ['name', 'first'],
47 | label: 'First Name',
48 | required: true,
49 | tooltip: 'hahahah',
50 | },
51 | { key: 'name.last', label: 'Last Name', required: true },
52 | { key: 'gender', label: 'Gender', widget: 'radio-group', options: ['Male', 'Female'] },
53 | {
54 | key: 'dateOfBirth',
55 | label: 'Date of Birth',
56 | widget: 'date-picker',
57 | viewWidget: DateView,
58 | },
59 | { key: 'email', label: 'Email' },
60 | { key: 'phone', label: 'Phone' },
61 | { key: 'address', label: 'Address', colSpan: 2, clear: 'left' },
62 | { key: 'city', label: 'City' },
63 | { key: 'zipCode', label: 'Zip Code' },
64 | ],
65 | };
66 | return meta;
67 | };
68 |
69 | return (
70 |
71 |
83 |
86 |
95 |
96 | )}
97 |
98 |
99 | );
100 | };
101 |
102 | describe('antd/ViewEdit', () => {
103 | config.addAdapter(antdAdapter);
104 |
105 | it('renders ViewEdit Nice Form using Antd', () => {
106 | render(
);
107 | const inputGender = screen.getByText('Gender');
108 | expect(inputGender).toBeInTheDocument();
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/antd/ViewMode.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import dayjs from 'dayjs';
3 | import type { Dayjs } from 'dayjs';
4 | import { Form } from 'antd';
5 | import NiceForm from '../../src/NiceForm';
6 | import config from '../../src/config';
7 | import { render, screen } from '@testing-library/react';
8 | import antdAdapter from '../../src/adapters/antdAdapter';
9 | import '@testing-library/jest-dom';
10 |
11 | const DateView = ({ value }: { value: Dayjs }) => value.format('MMM Do YYYY');
12 |
13 | const ViewMode = () => {
14 | const [form] = Form.useForm();
15 |
16 | const personalInfo = {
17 | name: { first: 'Nate', last: 'Wang' },
18 | email: 'myemail@gmail.com',
19 | gender: 'Male',
20 | dateOfBirth: dayjs('2100-01-01'),
21 | phone: '15988888888',
22 | city: 'Shanghai',
23 | address: 'No.1000 Some Road, Zhangjiang Park, Pudong New District',
24 | };
25 |
26 | const meta = {
27 | columns: 2,
28 | viewMode: true,
29 | initialValues: personalInfo,
30 | fields: [
31 | { key: 'name.first', label: 'First Name', tooltip: 'First name' },
32 | { key: 'name.last', label: 'Last Name' },
33 | { key: 'gender', label: 'Gender' },
34 | {
35 | key: 'dateOfBirth',
36 | label: 'Date of Birth',
37 | viewWidget: DateView,
38 | },
39 | { key: 'email', label: 'Email' },
40 | { key: 'phone', label: 'Phone' },
41 | { key: 'address', label: 'Address', colSpan: 2 },
42 | { key: 'city', label: 'City' },
43 | { key: 'zipCode', label: 'Zip Code' },
44 | ],
45 | };
46 |
47 | return (
48 |
49 |
50 |
Personal Infomation
51 |
54 |
55 |
56 | );
57 | };
58 |
59 | describe('antd/ViewMode', () => {
60 | config.addAdapter(antdAdapter);
61 |
62 | it('renders ViewMode Nice Form using Antd', () => {
63 | render(
);
64 | const inputGender = screen.getByText('Gender');
65 | expect(inputGender).toBeInTheDocument();
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/AsyncDataSource.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Form, Formik, useFormikContext } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import NiceForm from '../../src/NiceForm';
5 | import { render, screen, waitFor } from '@testing-library/react';
6 | import '@testing-library/jest-dom';
7 |
8 | const MOCK_DATA: {
9 | [key: string]: string[];
10 | } = {
11 | China: ['Beijing', 'Shanghai', 'Nanjing'],
12 | USA: ['New York', 'San Jose', 'Washton'],
13 | France: ['Paris', 'Marseille', 'Cannes'],
14 | };
15 |
16 | // Mock fetch
17 | const fetchCities = (country: string): Promise
=> {
18 | return new Promise((resolve, reject) => {
19 | setTimeout(() => {
20 | if (MOCK_DATA[country]) resolve(MOCK_DATA[country]);
21 | else reject(new Error('Not found'));
22 | }, 1500);
23 | });
24 | };
25 |
26 | interface FormValues {
27 | country: string;
28 | city: string;
29 | // add other fields as necessary
30 | }
31 | const MyForm = () => {
32 | const [cities, setCities] = useState<{ [key: string]: string[] }>({});
33 | const { values, setFieldValue } = useFormikContext();
34 | const country = values.country;
35 | const loading = country && !cities[country];
36 | const meta = {
37 | rowGap: 18,
38 | fields: [
39 | {
40 | key: 'country',
41 | label: 'Country',
42 | widget: 'select',
43 | options: ['China', 'USA', 'France'],
44 | placeholder: 'Select country...',
45 | initialValue: 'China',
46 | widgetProps: {
47 | fullWidth: true,
48 | onChange: () => {
49 | // Clear city value when country is changed
50 | setFieldValue('city', undefined, true);
51 | },
52 | },
53 | },
54 | {
55 | key: 'city',
56 | label: 'City',
57 | widget: 'select',
58 | options: country ? cities[country] || [] : [],
59 | widgetProps: {
60 | fullWidth: true,
61 | placeholder: loading ? 'Loading...' : 'Select city...',
62 | },
63 | disabled: loading || !country,
64 | },
65 | ],
66 | };
67 |
68 | useEffect(() => {
69 | if (country && !cities[country]) {
70 | fetchCities(country).then((arr) => {
71 | setCities((p) => ({ ...p, [country]: arr }));
72 | });
73 | }
74 | }, [country, setCities, cities]);
75 |
76 | // If country selected but no cities in store, then it's loading
77 | return (
78 |
84 | );
85 | };
86 |
87 | const AsyncDataSource = () => {
88 | return (
89 | {
92 | await new Promise((r) => setTimeout(r, 2000));
93 | alert(JSON.stringify(values, null, 2));
94 | }}
95 | >
96 |
97 |
98 | );
99 | };
100 |
101 | describe('formik/AsyncDataSource', () => {
102 | it('renders AsyncDataSource Form using formik', async () => {
103 | render();
104 | const countryField = screen.getByText('Country');
105 | waitFor(() => expect(countryField).toBeInTheDocument(), { timeout: 3000 });
106 | });
107 | });
108 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/Coordinated.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@mui/material/Button';
3 | import { Formik, Form } from 'formik';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const Coordinated = () => {
15 | const getMeta = (formik: any) => {
16 | return {
17 | rowGap: 18,
18 | fields: [
19 | {
20 | key: 'gender',
21 | label: 'Gender',
22 | widget: 'radio-group',
23 | options: ['Male', 'Female'],
24 | widgetProps: {
25 | onChange: (_: any, value: any) => {
26 | if (value === 'Male') {
27 | formik.setFieldValue('note', 'Hi, man!', true);
28 | } else {
29 | formik.setFieldValue('note', 'Hi, lady!', true);
30 | }
31 | },
32 | },
33 | },
34 | { key: 'note', widgetProps: { placeholder: 'Note' } },
35 | ],
36 | };
37 | };
38 |
39 | return (
40 | {}} initialValues={{}}>
41 | {(formik) => (
42 |
48 | )}
49 |
50 | );
51 | };
52 |
53 | describe('formik/Coordinated', () => {
54 | it('renders Coordinated Nice Form using Formik', async () => {
55 | render();
56 | const inputGender = await screen.findByLabelText('Gender');
57 | expect(inputGender).toBeInTheDocument();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/DynamicFields.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Formik, Form } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen, waitFor } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const DynamicFields = () => {
15 | const getMeta = (form: any) => {
16 | const meta = {
17 | rowGap: 18,
18 | form,
19 | fields: [
20 | {
21 | key: 'favoriteFruit',
22 | label: 'Favorite Fruit',
23 | widget: 'radio-group',
24 | options: ['Apple', 'Orange', 'Other'],
25 | initialValue: 'Apple',
26 | },
27 | ],
28 | };
29 | if (form.values.favoriteFruit === 'Other') {
30 | meta.fields.push({
31 | key: 'otherFruit',
32 | label: 'Other',
33 | widget: 'text',
34 | options: [], // Add an empty array for options
35 | initialValue: 'Apple',
36 | });
37 | }
38 | return meta;
39 | };
40 |
41 | return (
42 | {
47 | alert(JSON.stringify(values, null, 2));
48 | }}
49 | >
50 | {(form) => (
51 |
57 | )}
58 |
59 | );
60 | };
61 |
62 | describe('formik/DynamicFields', () => {
63 | it('renders DynamicFields Form using formik', async () => {
64 | render();
65 | const favoriteFruit = screen.getByText('Favorite Fruit');
66 | waitFor(() => expect(favoriteFruit).toBeInTheDocument(), { timeout: 3000 });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/FieldCondition.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Formik, Form } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen, waitFor } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const FieldCondition = () => {
15 | const getMeta = (form: any) => {
16 | const meta = {
17 | rowGap: 18,
18 | form,
19 | fields: [
20 | {
21 | key: 'favoriteFruit',
22 | label: 'Favorite Fruit',
23 | widget: 'radio-group',
24 | options: ['Apple', 'Orange', 'Other'],
25 | initialValue: 'Apple',
26 | },
27 | {
28 | key: 'otherFruit',
29 | label: 'Other',
30 | condition: () => form.values.favoriteFruit === 'Other',
31 | },
32 | {
33 | key: 'submitt',
34 | render: () => {
35 | return (
36 |
39 | );
40 | },
41 | },
42 | ],
43 | };
44 | return meta;
45 | };
46 |
47 | return (
48 | {
53 | alert(JSON.stringify(values, null, 2));
54 | }}
55 | >
56 | {(form) => (
57 |
60 | )}
61 |
62 | );
63 | };
64 |
65 | describe('formik/FieldCondition', () => {
66 | it('renders FieldCondition Form using formik', async () => {
67 | render();
68 | const favoriteFruit = screen.getByText('Favorite Fruit');
69 | waitFor(() => expect(favoriteFruit).toBeInTheDocument(), { timeout: 3000 });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/FormInModal.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react';
2 | import Button from '@mui/material/Button';
3 | import Dialog from '@mui/material/Dialog';
4 | import DialogActions from '@mui/material/DialogActions';
5 | import DialogContent from '@mui/material/DialogContent';
6 | import DialogTitle from '@mui/material/DialogTitle';
7 | import { Formik, Form } from 'formik';
8 | import { SnackbarProvider, enqueueSnackbar } from 'notistack';
9 | import NiceForm from '../../src/NiceForm';
10 | import config from '../../src/config';
11 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
12 | import formikAdapter from '../../src/adapters/formikAdapter';
13 | import { render, screen, act, within } from '@testing-library/react';
14 | import '@testing-library/jest-dom';
15 |
16 | config.addAdapter(formikAdapter);
17 | config.addAdapter(formikMuiAdapter);
18 |
19 | const FormInModal = () => {
20 | const [open, setOpen] = useState(false);
21 | const handleOpen = () => setOpen(true);
22 | const handleClose = () => setOpen(false);
23 | const [pending, setPending] = useState(false);
24 |
25 | const handleFinish = useCallback(() => {
26 | setPending(true);
27 | setTimeout(() => {
28 | setPending(false);
29 | enqueueSnackbar('Submit success!', {
30 | variant: 'success',
31 | anchorOrigin: { vertical: 'top', horizontal: 'center' },
32 | });
33 | handleClose();
34 | }, 2000);
35 | }, [setPending]);
36 |
37 | const meta = {
38 | rowGap: 18,
39 | disabled: pending,
40 | fields: [
41 | {
42 | key: 'name',
43 | label: 'Name',
44 | required: true,
45 | },
46 | {
47 | key: 'desc',
48 | label: 'Description',
49 | },
50 | ],
51 | };
52 |
53 | return (
54 |
55 |
58 |
76 |
77 | );
78 | };
79 |
80 | describe('formik/FormInModal', () => {
81 | it('renders FormInModal Form using formik', async () => {
82 | render();
83 | // click on button to open modal
84 | const button = screen.getByRole('button', { name: /New\sItem/ });
85 | expect(button).toBeTruthy();
86 |
87 | act(() => {
88 | button.click();
89 | });
90 |
91 | const modalDialog = await screen.findByRole('dialog');
92 | expect(modalDialog).toBeInTheDocument();
93 | const inputName = within(modalDialog).getByText('Name');
94 | expect(inputName).toBeInTheDocument();
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/FormList.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@mui/material/Button';
3 | import { Formik, Form } from 'formik';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen, waitFor } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const FormList = () => {
15 | const meta = {
16 | layout: 'horizontal',
17 | columns: 1,
18 | rowGap: 18,
19 | fields: [
20 | { key: 'username', label: 'User Name' },
21 | { key: 'password', label: 'Password', widgetProps: { type: 'password' } },
22 | {
23 | key: 'friends',
24 | label: 'Friends',
25 | widget: 'form-list',
26 | fullWidth: true,
27 | listItemProps: {
28 | widget: 'select',
29 | options: ['Tom', 'Jerry'],
30 | required: true,
31 | fullWidth: true,
32 | },
33 | },
34 | ],
35 | };
36 |
37 | return (
38 | {
44 | await new Promise((r) => setTimeout(r, 500));
45 | alert(JSON.stringify(values, null, 2));
46 | }}
47 | >
48 |
54 |
55 | );
56 | };
57 |
58 | describe('formik/FormList', () => {
59 | it('renders FormList Form using formik', async () => {
60 | render();
61 | const password = screen.getByLabelText('Password');
62 | waitFor(() => expect(password).toBeInTheDocument(), { timeout: 3000 });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/MultipleColumns.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from '@mui/material/Button';
3 | import { Formik, Form } from 'formik';
4 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
5 | import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
6 | import NiceForm from '../../src/NiceForm';
7 | import config from '../../src/config';
8 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
9 | import formikAdapter from '../../src/adapters/formikAdapter';
10 | import { render, screen, waitFor } from '@testing-library/react';
11 | import '@testing-library/jest-dom';
12 |
13 | config.addAdapter(formikAdapter);
14 | config.addAdapter(formikMuiAdapter);
15 |
16 | const MultipleColumns = () => {
17 | const [columns, setColumns] = useState(2);
18 |
19 | const getMeta = (form: any) => ({
20 | columns,
21 | rowGap: 18,
22 | columnGap: 18,
23 | fields: [
24 | {
25 | key: 'columns',
26 | label: 'Columns',
27 | widget: 'radio-group',
28 | widgetProps: {
29 | onChange: (evt: any, value: any) => {
30 | setColumns(value);
31 | form.values.columns = evt.target.value;
32 | },
33 | },
34 | options: [1, 2, 3, 4],
35 | help: 'Change columns to show layout change',
36 | },
37 | { key: 'input', label: 'Input', required: true, tooltip: 'This is the name.' },
38 | {
39 | key: 'checkbox',
40 | label: 'Checkbox',
41 | widget: 'checkbox',
42 | initialValue: true,
43 | },
44 | {
45 | key: 'select',
46 | label: 'Select',
47 | widget: 'select',
48 | options: ['Apple', 'Orange', 'Banana'],
49 | fullWidth: true,
50 | },
51 | {
52 | key: 'password',
53 | label: 'Password',
54 | widgetProps: {
55 | type: 'password',
56 | },
57 | },
58 | {
59 | key: 'textarea',
60 | label: 'Textarea',
61 | widgetProps: {
62 | multiline: true,
63 | fullWidth: true,
64 | rows: 4,
65 | },
66 | },
67 | {
68 | key: 'number',
69 | label: 'Number',
70 | widgetProps: {
71 | type: 'number',
72 | },
73 | },
74 | {
75 | key: 'submit',
76 | render: () => (
77 |
80 | ),
81 | },
82 | ],
83 | });
84 | return (
85 | {
88 | alert(JSON.stringify(values, null, 2));
89 | }}
90 | >
91 | {(form) => (
92 |
93 |
96 |
97 | )}
98 |
99 | );
100 | };
101 |
102 | describe('formik/MultipleColumns', () => {
103 | it('renders MultipleColumns Form using formik', async () => {
104 | render();
105 | const textarea = screen.getByLabelText('Textarea');
106 | waitFor(() => expect(textarea).toBeInTheDocument(), { timeout: 3000 });
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/MultipleSections.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Formik, Form } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import Divider from '@mui/material/Divider';
5 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
6 | import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
7 | import NiceForm from '../../src/NiceForm';
8 | import config from '../../src/config';
9 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
10 | import formikAdapter from '../../src/adapters/formikAdapter';
11 | import { render, screen, waitFor } from '@testing-library/react';
12 | import '@testing-library/jest-dom';
13 |
14 | config.addAdapter(formikAdapter);
15 | config.addAdapter(formikMuiAdapter);
16 |
17 | const MultipleSections = () => {
18 | const meta = {
19 | columns: 1,
20 | rowGap: 18,
21 | fields: [
22 | { key: 'name.first', label: 'First Name', required: true },
23 | { key: 'name.last', label: 'Last Name', required: true },
24 | {
25 | key: 'email',
26 | label: 'Email',
27 | rules: [{ type: 'email', message: 'Invalid email' }],
28 | },
29 | {
30 | key: 'security',
31 | label: 'Security Question',
32 | widget: 'select',
33 | fullWidth: true,
34 | placeholder: 'Select a question...',
35 | options: ["What's your pet's name?", 'Your nick name?'],
36 | },
37 | { key: 'answer', label: 'Security Answer' },
38 | { key: 'address', label: 'Address' },
39 | { key: 'city', label: 'City' },
40 | { key: 'phone', label: 'phone' },
41 | ],
42 | };
43 |
44 | const meta1 = {
45 | ...meta,
46 | fields: meta.fields.slice(0, 3),
47 | };
48 | const meta2 = {
49 | ...meta,
50 | fields: meta.fields.slice(3, 6),
51 | };
52 | const meta3 = {
53 | ...meta,
54 | fields: meta.fields.slice(6),
55 | };
56 |
57 | return (
58 | {
61 | await new Promise((r) => setTimeout(r, 500));
62 | alert(JSON.stringify(values, null, 2));
63 | }}
64 | >
65 |
66 |
93 |
94 |
95 | );
96 | };
97 |
98 | describe('formik/MultipleSections', () => {
99 | it('renders MultipleSections Form using formik', async () => {
100 | render();
101 | const securityQuestion = screen.getByLabelText('Security Question');
102 | waitFor(() => expect(securityQuestion).toBeInTheDocument(), { timeout: 3000 });
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/Simple.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, Formik, FormikProps } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter, { FormikMuiNiceFormMeta } from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen, waitFor } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const Simple = () => {
15 | const initialValues = {
16 | username: '',
17 | password: '',
18 | };
19 |
20 | const getMeta = (form: FormikProps) => {
21 | const formMeta: FormikMuiNiceFormMeta = {
22 | columns: 1,
23 | rowGap: 18,
24 | form,
25 | initialValues,
26 | disabled: form.isSubmitting,
27 | fields: [
28 | {
29 | key: 'username',
30 | label: 'User Name',
31 | widget: 'text',
32 | },
33 | {
34 | key: 'password',
35 | label: 'Password',
36 | widget: 'text',
37 | widgetProps: {
38 | type: 'password',
39 | },
40 | },
41 | {
42 | key: 'submit',
43 | render: () => {
44 | return (
45 |
48 | );
49 | },
50 | },
51 | ],
52 | };
53 | return formMeta;
54 | };
55 |
56 | return (
57 |
58 | {
61 | await new Promise((r) => setTimeout(r, 500));
62 | alert(JSON.stringify(values, null, 2));
63 | }}
64 | >
65 | {(form) => {
66 | return (
67 |
70 | );
71 | }}
72 |
73 |
74 | );
75 | };
76 |
77 | describe('formik/Simple', () => {
78 | it('renders Simple Form using formik', async () => {
79 | render();
80 | const password = screen.getByLabelText('Password');
81 | waitFor(() => expect(password).toBeInTheDocument(), { timeout: 3000 });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/Validation.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Formik, Form } from 'formik';
3 | import Button from '@mui/material/Button';
4 | import NiceForm from '../../src/NiceForm';
5 | import config from '../../src/config';
6 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
7 | import formikAdapter from '../../src/adapters/formikAdapter';
8 | import { render, screen, waitFor } from '@testing-library/react';
9 | import '@testing-library/jest-dom';
10 |
11 | config.addAdapter(formikAdapter);
12 | config.addAdapter(formikMuiAdapter);
13 |
14 | const MOCK_USERNAMES: {
15 | [key: string]: boolean;
16 | } = {
17 | nate: true,
18 | bood: true,
19 | kevin: true,
20 | };
21 |
22 | const Validation = () => {
23 | const getMeta = (form: any) => {
24 | const meta = {
25 | rowGap: 18,
26 | fields: [
27 | {
28 | key: 'username',
29 | label: 'Username',
30 | placeholder: 'Note: username nate, bood or kevin already exist',
31 | hasFeedback: true, // Show validation status icon in the right
32 | required: true, // this adds an entry to rules: [{ required: true, message: 'Username is required' }]
33 | validate: (value: any) => {
34 | let error;
35 | if (MOCK_USERNAMES[value]) {
36 | error = `Username "${value}" already exists.`;
37 | }
38 | return error;
39 | },
40 | },
41 | {
42 | key: 'password',
43 | label: 'Password',
44 | widgetProps: { type: 'password' },
45 | onChange: () => {
46 | if (form.isFieldTouched('confirmPassword')) {
47 | form.validateFields(['confirmPassword']);
48 | }
49 | },
50 | validate: (value: any) => {
51 | let error;
52 | if (!value) {
53 | error = `Password is required`;
54 | }
55 | return error;
56 | },
57 | },
58 | {
59 | key: 'confirmPassword',
60 | label: 'Confirm Passowrd',
61 | required: true,
62 | widgetProps: { type: 'password' },
63 | validate: (value: any) => {
64 | let error;
65 | if (form.values.password !== value) {
66 | error = `Two passwords are inconsistent.`;
67 | }
68 | return error;
69 | },
70 | },
71 | {
72 | key: 'submit',
73 | render: () => (
74 |
77 | ),
78 | },
79 | ],
80 | };
81 | return meta;
82 | };
83 |
84 | return (
85 | {
88 | await new Promise((r) => setTimeout(r, 500));
89 | alert(JSON.stringify(values, null, 2));
90 | }}
91 | >
92 | {(form) => (
93 |
96 | )}
97 |
98 | );
99 | };
100 |
101 | describe('formik/Validation', () => {
102 | it('renders Validation Form using formik', async () => {
103 | render();
104 | const username = screen.getByLabelText(/Username/);
105 | waitFor(() => expect(username).toBeInTheDocument(), { timeout: 3000 });
106 | });
107 | });
108 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/formik/ViewMode.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import dayjs from 'dayjs';
3 | import type { Dayjs } from 'dayjs';
4 | import { InputLabel } from '@mui/material';
5 | import { ReactElement } from 'react';
6 | import NiceForm from '../../src/NiceForm';
7 | import type { NiceFormMeta } from '../../src/types';
8 | import config from '../../src/config';
9 | import formikMuiAdapter from '../../src/adapters/formikMuiAdapter';
10 | import formikAdapter from '../../src/adapters/formikAdapter';
11 | import { render, screen, waitFor } from '@testing-library/react';
12 | import '@testing-library/jest-dom';
13 |
14 | config.addAdapter(formikAdapter);
15 | config.addAdapter(formikMuiAdapter);
16 |
17 | const DateView = ({ value, label }: { value: Dayjs; label: ReactElement }) => {
18 | return (
19 |
20 |
{label}
21 |
{value.format('MMM Do YYYY')}
22 |
23 | );
24 | };
25 |
26 | const ViewMode = () => {
27 | const personalInfo = {
28 | name: { first: 'Nate', last: 'Wang' },
29 | email: 'myemail@gmail.com',
30 | gender: 'Male',
31 | dateOfBirth: dayjs('2100-01-01'),
32 | // phone: '15988888888',
33 | city: 'Shanghai',
34 | address:
35 | 'No.1000 Some Road, Zhangjiang Park, Pudong New District,Zhangjiang Park, Pudong New DistrictZhangjiang Park, Pudong New District',
36 | };
37 |
38 | const meta: NiceFormMeta = {
39 | columns: 2,
40 | viewMode: true,
41 | initialValues: personalInfo,
42 | rowGap: 20,
43 | columnGap: 20,
44 | fields: [
45 | { key: 'name.first', label: 'First Name', tooltip: 'First name' },
46 | { key: 'name.last', label: 'Last Name' },
47 | { key: 'gender', label: 'Gender' },
48 | {
49 | key: 'dateOfBirth',
50 | label: 'Date of Birth',
51 | viewWidget: DateView,
52 | },
53 | { key: 'email', label: 'Email' },
54 | { key: 'phone', label: 'Phone' },
55 | { key: 'address', label: 'Address', colSpan: 2 },
56 | { key: 'city', label: 'City' },
57 | { key: 'zipCode', label: 'Zip Code' },
58 | ],
59 | };
60 |
61 | return (
62 |
63 |
Personal Infomation
64 |
65 |
66 | );
67 | };
68 |
69 | describe('formik/ViewMode', () => {
70 | it('renders ViewMode Form using formik', async () => {
71 | render();
72 | const email = screen.getByText('Email');
73 | waitFor(() => expect(email).toBeInTheDocument(), { timeout: 3000 });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tests/setupAfterEnv.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 | import './__mocks__/matchMedia.js';
--------------------------------------------------------------------------------
/packages/nice-form-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "lib/esm",
4 | "module": "esnext",
5 | "target": "es5",
6 | "lib": ["es6", "dom", "es2016", "es2017"],
7 | "jsx": "react-jsx",
8 | "declaration": true,
9 | "moduleResolution": "node",
10 | "noUnusedLocals": false,
11 | "noUnusedParameters": false,
12 | "esModuleInterop": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": true,
15 | "noImplicitAny": true,
16 | "strictNullChecks": true,
17 | "suppressImplicitAnyIndexErrors": true,
18 | "sourceMap": true,
19 | "allowSyntheticDefaultImports": true,
20 | "skipLibCheck": true,
21 | },
22 | "include": ["src"],
23 | "exclude": ["node_modules", "lib"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/nice-form-react/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/nice-form-react/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": [
3 | "src/index.ts",
4 | "src/adapters/antdAdapter.tsx",
5 | "src/adapters/formikAdapter.tsx",
6 | "src/adapters/formikMuiAdapter.tsx"
7 | ],
8 | "out": "docs/api",
9 | "excludeInternal": false,
10 | "readme": "none"
11 | }
12 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in direct subdirs of packages/
3 | - 'packages/*'
--------------------------------------------------------------------------------