diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fe84dfe90..191a56dc9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @radekmie @wadamek65 +* @wadamek65 @kestarumper diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 23d5cfac2..d5ea7fe2f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,5 +1,6 @@ name: CI on: + workflow_dispatch: pull_request: push: branches: diff --git a/docs/uth-autofield-algorithm.md b/docs/uth-autofield-algorithm.md index 158bea9d0..8e14e70a0 100644 --- a/docs/uth-autofield-algorithm.md +++ b/docs/uth-autofield-algorithm.md @@ -46,7 +46,7 @@ const AutoField = createAutoField(props => { ## Overriding `AutoField` -To make it possible, all `AutoFields` created with the `createAutoField` are configurable. To adjust the components, use the React context available in `AutoField.componentDetectorContext`. You can use it as often as needed - in most apps once will be enough. Example: +If you want to alter the default behavior of `AutoField` and render a different component based on the props, you can do it using the React context available in `AutoField.componentDetectorContext`. You can use it as often as needed - once will be enough in most apps. Example: ```tsx /* ... */}> @@ -54,7 +54,7 @@ To make it possible, all `AutoFields` created with the `createAutoField` are con ``` -If you want to add an exception and then fallback to the existing algorithm, use `AutoField.defaultComponentDetector`. Example: +If you want to change the detector only partially, i.e., to render one additional field, and in other cases, use the default algorithm as a fallback, return `AutoField.defaultComponentDetector`. Example: ```tsx - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label and info (specified)', () => { - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(Tooltip)).toHaveLength(1); - expect(wrapper.find(Tooltip).prop('title')).toBe('ListFieldInfo'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('div > div').at(0).text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct numer of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array, label: '' }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('div > div').at(0).text()).toBe('Error'); -}); - -test(' - renders correct error style', () => { - const error = new Error(); - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('div').at(0).prop('style')).toHaveProperty( - 'borderColor', - 'rgb(255, 85, 0)', - ); -}); - -test(' - renders correct error style (with specified style prop)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('div').at(0).prop('style')).toHaveProperty( - 'marginLeft', - '8px', - ); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-antd/__tests__/ListItemField.tsx b/packages/uniforms-antd/__tests__/ListItemField.tsx deleted file mode 100644 index 1a68cea18..000000000 --- a/packages/uniforms-antd/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-antd'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-antd/__tests__/TextField.tsx b/packages/uniforms-antd/__tests__/TextField.tsx deleted file mode 100644 index fcaaa22ec..000000000 --- a/packages/uniforms-antd/__tests__/TextField.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { screen } from '@testing-library/react'; -import Input from 'antd/lib/input'; -import React from 'react'; -import { TextField } from 'uniforms-antd'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders component with unknown props', () => { - const props = { - 'data-x': 'x', - 'data-y': 'y', - 'data-z': 'z', - }; - render(, { x: String }); - - const input = screen.getByRole('textbox'); - Object.entries(props).forEach(([key, value]) => - expect(input).toHaveAttribute(key, value), - ); - }); -}); - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input)).toHaveLength(1); - expect(wrapper.find(Input).prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(Input)).toHaveLength(1); - expect( - wrapper.find(Input).simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(Input)).toHaveLength(1); - expect( - wrapper.find(Input).simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find(Input)).toHaveLength(1); - expect( - wrapper.find(Input).simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input).prop('data-x')).toBe('x'); - expect(wrapper.find(Input).prop('data-y')).toBe('y'); - expect(wrapper.find(Input).prop('data-z')).toBe('z'); -}); - -test(' - renders a input with correct type prop', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input).prop('type')).toBe('password'); -}); - -test(' - renders a input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(Input).prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-antd/__tests__/index.ts b/packages/uniforms-antd/__tests__/index.ts index 152e18a14..717b7a7a2 100644 --- a/packages/uniforms-antd/__tests__/index.ts +++ b/packages/uniforms-antd/__tests__/index.ts @@ -47,7 +47,10 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('img', { name: 'plus-square' }), + testTooltip: true, + testStyle: true, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField); // FIXME: AntD number input doesn't work with new RTL test implementation // suites.testNumField(antd.NumField); diff --git a/packages/uniforms-bootstrap3/__tests__/ListField.tsx b/packages/uniforms-bootstrap3/__tests__/ListField.tsx deleted file mode 100644 index 81e67ced7..000000000 --- a/packages/uniforms-bootstrap3/__tests__/ListField.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-bootstrap3'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct numer of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.help-block').text()).toBe('Error'); -}); - -test(' - renders correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.help-block')).toHaveLength(0); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-bootstrap3/__tests__/ListItemField.tsx b/packages/uniforms-bootstrap3/__tests__/ListItemField.tsx deleted file mode 100644 index b8f55cafb..000000000 --- a/packages/uniforms-bootstrap3/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-bootstrap3'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-bootstrap3/__tests__/TextField.tsx b/packages/uniforms-bootstrap3/__tests__/TextField.tsx deleted file mode 100644 index 749c200de..000000000 --- a/packages/uniforms-bootstrap3/__tests__/TextField.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { screen } from '@testing-library/react'; -import React from 'react'; -import { TextField } from 'uniforms-bootstrap3'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders a wrapper with unknown props', () => { - const props = { - 'data-x': 'x', - 'data-y': 'y', - 'data-z': 'z', - }; - render(, { x: String }); - - const wrapper = screen.getByRole('textbox').closest('div'); - Object.entries(props).forEach(([key, value]) => - expect(wrapper).toHaveAttribute(key, value), - ); - }); -}); - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); - expect(wrapper.find('label').prop('htmlFor')).toBe( - wrapper.find('input').prop('id'), - ); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('data-x')).toBe('x'); - expect(wrapper.find('div').at(0).prop('data-y')).toBe('y'); - expect(wrapper.find('div').at(0).prop('data-z')).toBe('z'); -}); - -test(' - renders a input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('autoComplete')).toBeFalsy(); - expect(wrapper.find('input').prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-bootstrap3/__tests__/index.ts b/packages/uniforms-bootstrap3/__tests__/index.ts index e2fb15f9e..73501bc3f 100644 --- a/packages/uniforms-bootstrap3/__tests__/index.ts +++ b/packages/uniforms-bootstrap3/__tests__/index.ts @@ -47,7 +47,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('button'), + disableInlineError: true, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField); suites.testNestField(theme.NestField); suites.testNumField(theme.NumField); @@ -58,7 +60,7 @@ describe('@RTL', () => { screen.getByLabelText('a').closest('.checkbox-inline'), }); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { testWrapClassName: true }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms-bootstrap4/__tests__/ListField.tsx b/packages/uniforms-bootstrap4/__tests__/ListField.tsx deleted file mode 100644 index 4f6215222..000000000 --- a/packages/uniforms-bootstrap4/__tests__/ListField.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-bootstrap4'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct numer of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.text-danger').text()).toBe('Error'); -}); - -test(' - renders correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.text-danger')).toHaveLength(0); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-bootstrap4/__tests__/ListItemField.tsx b/packages/uniforms-bootstrap4/__tests__/ListItemField.tsx deleted file mode 100644 index 3bc76866e..000000000 --- a/packages/uniforms-bootstrap4/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-bootstrap4'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-bootstrap4/__tests__/TextField.tsx b/packages/uniforms-bootstrap4/__tests__/TextField.tsx deleted file mode 100644 index 666bfc861..000000000 --- a/packages/uniforms-bootstrap4/__tests__/TextField.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { screen } from '@testing-library/react'; -import React from 'react'; -import { TextField } from 'uniforms-bootstrap4'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders a wrapper with unknown props', () => { - const props = { - 'data-x': 'x', - 'data-y': 'y', - 'data-z': 'z', - }; - render(, { x: String }); - - const wrapper = screen.getByRole('textbox').closest('div'); - Object.entries(props).forEach(([key, value]) => - expect(wrapper).toHaveAttribute(key, value), - ); - }); -}); - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); - expect(wrapper.find('label').prop('htmlFor')).toBe( - wrapper.find('input').prop('id'), - ); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('data-x')).toBe('x'); - expect(wrapper.find('div').at(0).prop('data-y')).toBe('y'); - expect(wrapper.find('div').at(0).prop('data-z')).toBe('z'); -}); - -test(' - renders a input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('autoComplete')).toBeFalsy(); - expect(wrapper.find('input').prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-bootstrap4/__tests__/index.ts b/packages/uniforms-bootstrap4/__tests__/index.ts index b52f247ab..74eb7b3de 100644 --- a/packages/uniforms-bootstrap4/__tests__/index.ts +++ b/packages/uniforms-bootstrap4/__tests__/index.ts @@ -47,7 +47,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('button'), + disableInlineError: true, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField); suites.testNestField(theme.NestField); suites.testNumField(theme.NumField); @@ -58,7 +60,7 @@ describe('@RTL', () => { screen.getByLabelText('a').closest('.checkbox-inline'), }); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { testWrapClassName: true }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms-bootstrap5/__tests__/ListField.tsx b/packages/uniforms-bootstrap5/__tests__/ListField.tsx deleted file mode 100644 index 72efa14fc..000000000 --- a/packages/uniforms-bootstrap5/__tests__/ListField.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-bootstrap5'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct numer of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.text-danger').text()).toBe('Error'); -}); - -test(' - renders correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('.text-danger')).toHaveLength(0); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-bootstrap5/__tests__/ListItemField.tsx b/packages/uniforms-bootstrap5/__tests__/ListItemField.tsx deleted file mode 100644 index c105a0ba2..000000000 --- a/packages/uniforms-bootstrap5/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-bootstrap5'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-bootstrap5/__tests__/TextField.tsx b/packages/uniforms-bootstrap5/__tests__/TextField.tsx deleted file mode 100644 index aee886918..000000000 --- a/packages/uniforms-bootstrap5/__tests__/TextField.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { screen } from '@testing-library/react'; -import React from 'react'; -import { TextField } from 'uniforms-bootstrap5'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders a wrapper with unknown props', () => { - const props = { - 'data-x': 'x', - 'data-y': 'y', - 'data-z': 'z', - }; - render(, { x: String }); - - const wrapper = screen.getByRole('textbox').closest('div'); - Object.entries(props).forEach(([key, value]) => - expect(wrapper).toHaveAttribute(key, value), - ); - }); -}); - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); - expect(wrapper.find('label').prop('htmlFor')).toBe( - wrapper.find('input').prop('id'), - ); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('data-x')).toBe('x'); - expect(wrapper.find('div').at(0).prop('data-y')).toBe('y'); - expect(wrapper.find('div').at(0).prop('data-z')).toBe('z'); -}); - -test(' - renders a input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('autoComplete')).toBeFalsy(); - expect(wrapper.find('input').prop('autoComplete')).toBe('off'); -}); - -test(' - renders a input with minLength and maxLength', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('minLength')).toBeFalsy(); - expect(wrapper.find('input').prop('minLength')).toBe(1); - - expect(wrapper.find('div').at(0).prop('maxLength')).toBeFalsy(); - expect(wrapper.find('input').prop('maxLength')).toBe(10); -}); diff --git a/packages/uniforms-bootstrap5/__tests__/index.ts b/packages/uniforms-bootstrap5/__tests__/index.ts index d155ffd98..374958d94 100644 --- a/packages/uniforms-bootstrap5/__tests__/index.ts +++ b/packages/uniforms-bootstrap5/__tests__/index.ts @@ -47,7 +47,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('button'), + disableInlineError: true, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField, { testMinMaxLength: true, }); @@ -60,7 +62,10 @@ describe('@RTL', () => { screen.getByLabelText('a').closest('.form-check-inline'), }); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { + testWrapClassName: true, + testMinMaxLength: true, + }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms-material/__tests__/ListField.tsx b/packages/uniforms-material/__tests__/ListField.tsx deleted file mode 100644 index 046235db6..000000000 --- a/packages/uniforms-material/__tests__/ListField.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import IconButton from '@material-ui/core/IconButton'; -import ListSubheader from '@material-ui/core/ListSubheader'; -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-material'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListSubheader)).toHaveLength(1); - expect(wrapper.find(ListSubheader).text()).toBe('ListFieldLabel'); -}); - -test(' - renders correct number of items with model', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - expect( - wrapper.find(ListAddField).find(IconButton).simulate('click'), - ).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-material/__tests__/ListItemField.tsx b/packages/uniforms-material/__tests__/ListItemField.tsx deleted file mode 100644 index 996d39dbb..000000000 --- a/packages/uniforms-material/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-material'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-material/__tests__/LongTextField.tsx b/packages/uniforms-material/__tests__/LongTextField.tsx deleted file mode 100644 index 9c2708667..000000000 --- a/packages/uniforms-material/__tests__/LongTextField.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; -import ThemeProvider from '@material-ui/styles/ThemeProvider/ThemeProvider'; -import React from 'react'; -import { LongTextField } from 'uniforms-material'; -import { renderWithZod } from 'uniforms/__suites__'; -import z from 'zod'; - -describe('@RTL - LongTextField tests', () => { - test(' - default props are not passed when MUI theme props are specified', () => { - const theme = createMuiTheme({ - props: { MuiTextField: { fullWidth: false, margin: 'normal' } }, - }); - - const { container } = renderWithZod({ - element: ( - - - - ), - schema: z.object({ x: z.string() }), - }); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginNormal', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); - }); - - test(' - default props are passed when MUI theme props are absent', () => { - const theme = createMuiTheme({}); - - const { container } = renderWithZod({ - element: ( - - - - ), - schema: z.object({ x: z.string() }), - }); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginDense', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).toHaveClass('MuiFormControl-fullWidth'); - }); - - test(' - explicit props are passed when MUI theme props are specified', () => { - const theme = createMuiTheme({ - props: { MuiTextField: { fullWidth: true, margin: 'dense' } }, - }); - const explicitProps = { - fullWidth: false, - margin: 'normal' as const, - }; - - const { container } = renderWithZod({ - element: ( - - - - ), - schema: z.object({ x: z.string() }), - }); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginNormal', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); - }); -}); diff --git a/packages/uniforms-material/__tests__/TextField.tsx b/packages/uniforms-material/__tests__/TextField.tsx deleted file mode 100644 index bda931460..000000000 --- a/packages/uniforms-material/__tests__/TextField.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import TextFieldMaterial from '@material-ui/core/TextField'; -import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; -import ThemeProvider from '@material-ui/styles/ThemeProvider/ThemeProvider'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { TextField } from 'uniforms-material'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders a TextField with correct error text (specified)', () => { - const errorMessage = 'Error'; - render( - , - { x: String }, - ); - - expect(screen.getByText(errorMessage)).toBeInTheDocument(); - }); - - test(' - renders a TextField with correct error text (showInlineError=false)', () => { - const errorMessage = 'Error'; - render( - , - { x: String }, - ); - - expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); - }); - - test(' - default props are not passed when MUI theme props are specified', () => { - const theme = createMuiTheme({ - props: { MuiTextField: { fullWidth: false, margin: 'normal' } }, - }); - const { container } = render( - - - , - { x: { type: String } }, - ); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginNormal', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); - }); - - test(' - default props are passed when MUI theme props are absent', () => { - const theme = createMuiTheme({}); - const { container } = render( - - - , - { x: { type: String } }, - ); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginDense', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).toHaveClass('MuiFormControl-fullWidth'); - }); - - test(' - explicit props are passed when MUI theme props are specified', () => { - const theme = createMuiTheme({ - props: { MuiTextField: { fullWidth: true, margin: 'dense' } }, - }); - const explicitProps = { - fullWidth: false, - margin: 'normal' as const, - }; - - const { container } = render( - - - , - { x: { type: String } }, - ); - - const elements = container.getElementsByClassName( - 'MuiFormControl-marginNormal', - ); - expect(elements).toHaveLength(1); - expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); - }); -}); - -test(' - renders an TextField', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); -}); - -test(' - renders a TextField with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('disabled')).toBe(true); -}); - -test(' - renders a TextField with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('inputProps')!.readOnly).toBe( - true, - ); -}); - -test(' - renders a TextField with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('id')).toBeTruthy(); -}); - -test(' - renders a TextField with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('id')).toBe('y'); -}); - -test(' - renders a TextField with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('name')).toBe('x'); -}); - -test(' - renders a TextField with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('placeholder')).toBe('y'); -}); - -test(' - renders a TextField with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe(''); -}); - -test(' - renders a TextField with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe('y'); -}); - -test(' - renders a TextField with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe('y'); -}); - -test(' - renders a TextField which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a TextField which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders a TextField which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('label')).toBe('y'); -}); - -test(' - renders a TextField with correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial).prop('helperText')).toBe('Error'); -}); - -test(' - renders a TextField with correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial).prop('helperText')).toBeUndefined(); -}); - -test(' - renders a input with autocomplete off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-material/__tests__/index.ts b/packages/uniforms-material/__tests__/index.tsx similarity index 68% rename from packages/uniforms-material/__tests__/index.ts rename to packages/uniforms-material/__tests__/index.tsx index fbe8f1a7b..647461ce5 100644 --- a/packages/uniforms-material/__tests__/index.ts +++ b/packages/uniforms-material/__tests__/index.tsx @@ -1,3 +1,8 @@ +import createMuiTheme, { + ThemeOptions, +} from '@material-ui/core/styles/createMuiTheme'; +import ThemeProvider from '@material-ui/styles/ThemeProvider/ThemeProvider'; +import React, { PropsWithChildren } from 'react'; import * as theme from 'uniforms-material'; import * as suites from 'uniforms/__suites__'; @@ -49,8 +54,23 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByText(/\+/), + testError: false, + }); + suites.testListItemField(theme.ListItemField); + suites.testLongTextField(theme.LongTextField, { + testPassThemeProps: { + ThemeProvider({ + themeOptions, + ...props + }: PropsWithChildren<{ themeOptions: ThemeOptions }>) { + return ( + + {props.children} + + ); + }, + }, }); - suites.testLongTextField(theme.LongTextField); suites.testNestField(theme.NestField, { skipInMuiTests: true }); suites.testNumField(theme.NumField); suites.testQuickForm(theme.QuickForm); @@ -58,7 +78,21 @@ describe('@RTL', () => { // FIXME: MUI select does not work with new RTL test implementation // suites.testSelectField(theme.SelectField); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { + testShowInlineError: true, + testPassThemeProps: { + ThemeProvider({ + themeOptions, + ...props + }: PropsWithChildren<{ themeOptions: ThemeOptions }>) { + return ( + + {props.children} + + ); + }, + }, + }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms-mui/__tests__/ListField.tsx b/packages/uniforms-mui/__tests__/ListField.tsx deleted file mode 100644 index 4c10074d0..000000000 --- a/packages/uniforms-mui/__tests__/ListField.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import IconButton from '@mui/material/IconButton'; -import ListSubheader from '@mui/material/ListSubheader'; -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-mui'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListSubheader)).toHaveLength(1); - expect(wrapper.find(ListSubheader).text()).toBe('ListFieldLabel'); -}); - -test(' - renders correct number of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -// Strange enzyme behavior -// TypeError: Cannot read properties of null (reading '__reactFiber$my72orhzzz9') -test.skip(' - renders proper number of optional values after add new value (with initialCount)', async () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - ), - ); - - expect( - wrapper.find(ListAddField).find(IconButton).simulate('click'), - ).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-mui/__tests__/ListItemField.tsx b/packages/uniforms-mui/__tests__/ListItemField.tsx deleted file mode 100644 index 25de0fd8e..000000000 --- a/packages/uniforms-mui/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-mui'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-mui/__tests__/TextField.tsx b/packages/uniforms-mui/__tests__/TextField.tsx deleted file mode 100644 index ae18aca3d..000000000 --- a/packages/uniforms-mui/__tests__/TextField.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import TextFieldMaterial from '@mui/material/TextField'; -import React from 'react'; -import { TextField } from 'uniforms-mui'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - renders an TextField', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); -}); - -test(' - renders a TextField with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('disabled')).toBe(true); -}); - -test(' - renders a TextField with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('inputProps')!.readOnly).toBe( - true, - ); -}); - -test(' - renders a TextField with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('id')).toBeTruthy(); -}); - -test(' - renders a TextField with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('id')).toBe('y'); -}); - -test(' - renders a TextField with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('name')).toBe('x'); -}); - -test(' - renders a TextField with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('placeholder')).toBe('y'); -}); - -test(' - renders a TextField with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe(''); -}); - -test(' - renders a TextField with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe('y'); -}); - -test(' - renders a TextField with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('value')).toBe('y'); -}); - -test(' - renders a TextField which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a TextField which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders a TextField which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('label')).toBe('y'); -}); - -test(' - renders a TextField with correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial).prop('helperText')).toBe('Error'); -}); - -test(' - renders a TextField with correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial).prop('helperText')).toBeUndefined(); -}); - -test(' - renders a input with autocomplete off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find(TextFieldMaterial)).toHaveLength(1); - expect(wrapper.find(TextFieldMaterial).prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-mui/__tests__/index.ts b/packages/uniforms-mui/__tests__/index.ts index 2b7520252..634b6f46f 100644 --- a/packages/uniforms-mui/__tests__/index.ts +++ b/packages/uniforms-mui/__tests__/index.ts @@ -46,7 +46,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByText(/\+/), + testError: false, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField); suites.testNestField(theme.NestField, { skipInMuiTests: true }); suites.testNumField(theme.NumField); diff --git a/packages/uniforms-semantic/__tests__/ListField.tsx b/packages/uniforms-semantic/__tests__/ListField.tsx deleted file mode 100644 index 2efb22d0f..000000000 --- a/packages/uniforms-semantic/__tests__/ListField.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-semantic'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct number of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array, label: '' }, 'x.$': { type: String } }), - ); - - expect(wrapper.children().first().text()).toBe('Error'); -}); - -test(' - renders correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.children().first().text()).not.toBe('Error'); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-semantic/__tests__/ListItemField.tsx b/packages/uniforms-semantic/__tests__/ListItemField.tsx deleted file mode 100644 index 2c4224268..000000000 --- a/packages/uniforms-semantic/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-semantic'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-semantic/__tests__/TextField.tsx b/packages/uniforms-semantic/__tests__/TextField.tsx deleted file mode 100644 index 7f482bfed..000000000 --- a/packages/uniforms-semantic/__tests__/TextField.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import { screen } from '@testing-library/react'; -import React from 'react'; -import { TextField } from 'uniforms-semantic'; -import { render } from 'uniforms/__suites__'; - -import createContext from './_createContext'; -import mount from './_mount'; - -describe('@RTL - TextField tests', () => { - test(' - renders a wrapper with unknown props', () => { - const props = { - 'data-x': 'x', - 'data-y': 'y', - 'data-z': 'z', - }; - render(, { x: String }); - - const wrapper = screen.getByRole('textbox').closest('div')?.parentElement; - Object.entries(props).forEach(([key, value]) => - expect(wrapper).toHaveAttribute(key, value), - ); - }); - - test(' - renders a TextField with correct error text (specified)', () => { - const errorMessage = 'Error'; - render( - , - { x: String }, - ); - - expect(screen.getByText(errorMessage)).toBeInTheDocument(); - }); - - test(' - renders a TextField with correct error text (showInlineError=false)', () => { - const errorMessage = 'Error'; - render( - , - { x: String }, - ); - - expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); - }); - - test(' - renders an icon', () => { - const { container } = render(, { - x: String, - }); - - expect(container.querySelector('i')).toBeInTheDocument(); - }); - - test(' - renders with a custom wrapClassName', () => { - const testClassName = 'test-class-name'; - render(, { x: String }); - - expect(screen.getByRole('textbox').closest('div')).toHaveClass( - testClassName, - ); - }); -}); - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); - expect(wrapper.find('label').prop('htmlFor')).toBe( - wrapper.find('input').prop('id'), - ); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('data-x')).toBe('x'); - expect(wrapper.find('div').at(0).prop('data-y')).toBe('y'); - expect(wrapper.find('div').at(0).prop('data-z')).toBe('z'); -}); - -test(' - renders correct error text (specified)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount( - element, - createContext({ x: { type: String, label: '' } }), - ); - - expect(wrapper.children().last().text()).toBe('Error'); -}); - -test(' - renders correct error text (showInlineError=false)', () => { - const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.children().last().text()).not.toBe('Error'); -}); - -test(' - renders a icon', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('i')).toHaveLength(1); -}); - -test(' - renders a icon', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('i')).toHaveLength(1); -}); - -test(' - renders with a custom wrapClassName', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('.ui.input.test-class-name')).toHaveLength(1); -}); - -test(' - renders a input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input').prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-semantic/__tests__/index.ts b/packages/uniforms-semantic/__tests__/index.ts index 5e300107c..87c1de6f0 100644 --- a/packages/uniforms-semantic/__tests__/index.ts +++ b/packages/uniforms-semantic/__tests__/index.ts @@ -45,7 +45,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('button'), + disableInlineError: true, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField); suites.testNestField(theme.NestField); suites.testNumField(theme.NumField); @@ -53,7 +55,11 @@ describe('@RTL', () => { suites.testRadioField(theme.RadioField); suites.testSelectField(theme.SelectField); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { + testWrapClassName: true, + testRenderIcon: true, + testShowInlineError: true, + }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms-unstyled/__tests__/ListField.tsx b/packages/uniforms-unstyled/__tests__/ListField.tsx deleted file mode 100644 index 7c8dd726a..000000000 --- a/packages/uniforms-unstyled/__tests__/ListField.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import { ListAddField, ListField, ListItemField } from 'uniforms-unstyled'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListField)).toHaveLength(1); -}); - -test(' - renders ListAddField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListAddField)).toHaveLength(1); - expect(wrapper.find(ListAddField).prop('name')).toBe('$'); -}); - -test(' - renders correct label (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toEqual( - expect.stringContaining('ListFieldLabel'), - ); -}); - -test(' - renders correct numer of items with model (specified)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined, undefined], - }), - ); - - expect(wrapper.find('input')).toHaveLength(3); -}); - -test(' - passes itemProps to its children', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined], - }), - ); - - expect(wrapper.find(ListItemField).first().prop('data-xyz')).toBe(1); -}); - -test(' - renders children (specified)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - PlainText - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(Child).toHaveBeenCalledTimes(2); -}); - -test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as React.FC; - - const element = ( - - - - ); - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(Child).at(0).prop('name')).toBe('0'); - expect(wrapper.find(Child).at(1).prop('name')).toBe('1'); -}); - -test(' - renders children with correct name (value)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }, undefined, { - x: [undefined, undefined], - }), - ); - - expect(wrapper.find(ListItemField).at(0).prop('name')).toBe('0'); - expect(wrapper.find(ListItemField).at(1).prop('name')).toBe('1'); -}); - -test(' - renders proper number of optional values after add new value', () => { - const element = ; - const onChange = jest.fn(); - const wrapper = mount( - element, - createContext( - { x: { type: Array, optional: true }, 'x.$': { type: String } }, - { onChange }, - { - x: [undefined, undefined, undefined], - }, - ), - ); - expect(wrapper.find(ListAddField).simulate('click')).toBeTruthy(); - expect(onChange).toHaveBeenNthCalledWith(1, 'x', [ - undefined, - undefined, - undefined, - undefined, - ]); -}); diff --git a/packages/uniforms-unstyled/__tests__/ListItemField.tsx b/packages/uniforms-unstyled/__tests__/ListItemField.tsx deleted file mode 100644 index 6e9970e9b..000000000 --- a/packages/uniforms-unstyled/__tests__/ListItemField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { AutoField, ListDelField, ListItemField } from 'uniforms-unstyled'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - works', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListItemField)).toHaveLength(1); -}); - -test(' - renders ListDelField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(ListDelField)).toHaveLength(1); - expect(wrapper.find(ListDelField).childAt(0).prop('name')).toBe('x.1'); -}); - -test(' - renders AutoField', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(wrapper.find(AutoField)).toHaveLength(1); -}); - -test(' - renders children if specified', () => { - const Child: () => null = jest.fn(() => null); - - const element = ( - - - - ); - mount( - element, - createContext({ x: { type: Array }, 'x.$': { type: String } }), - ); - - expect(Child).toHaveBeenCalledTimes(1); -}); diff --git a/packages/uniforms-unstyled/__tests__/TextField.tsx b/packages/uniforms-unstyled/__tests__/TextField.tsx deleted file mode 100644 index 16240266f..000000000 --- a/packages/uniforms-unstyled/__tests__/TextField.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import { TextField } from 'uniforms-unstyled'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('readOnly')).toBe(true); -}); - -test(' - renders an input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBeTruthy(); -}); - -test(' - renders an input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('id')).toBe('y'); -}); - -test(' - renders an input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('name')).toBe('x'); -}); - -test(' - renders an input with correct placeholder', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('y'); -}); - -test(' - renders an input with correct type', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('type')).toBe('text'); -}); - -test(' - renders an input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe(''); -}); - -test(' - renders an input with correct value (model)', () => { - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' } }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input with correct value (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('value')).toBe('y'); -}); - -test(' - renders an input which correctly reacts on change', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders an input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: '' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', ''); -}); - -test(' - renders an input which correctly reacts on change (same value)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: String } }, { model: { x: 'y' }, onChange }), - ); - - expect(wrapper.find('input')).toHaveLength(1); - expect( - wrapper.find('input').simulate('change', { target: { value: 'y' } }), - ).toBeTruthy(); - expect(onChange).toHaveBeenLastCalledWith('x', 'y'); -}); - -test(' - renders a label', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('y'); - expect(wrapper.find('label').prop('htmlFor')).toBe( - wrapper.find('input').prop('id'), - ); -}); - -test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('div').at(0).prop('data-x')).toBe('x'); - expect(wrapper.find('div').at(0).prop('data-y')).toBe('y'); - expect(wrapper.find('div').at(0).prop('data-z')).toBe('z'); -}); - -test(' - renders an input with autocomplete turned off', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: String } })); - - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('autoComplete')).toBe('off'); -}); diff --git a/packages/uniforms-unstyled/__tests__/index.ts b/packages/uniforms-unstyled/__tests__/index.ts index 2abef5bdf..097876b9d 100644 --- a/packages/uniforms-unstyled/__tests__/index.ts +++ b/packages/uniforms-unstyled/__tests__/index.ts @@ -45,7 +45,9 @@ describe('@RTL', () => { suites.testListDelField(theme.ListDelField); suites.testListField(theme.ListField, { getListAddField: screen => screen.getByRole('button'), + testError: false, }); + suites.testListItemField(theme.ListItemField); suites.testLongTextField(theme.LongTextField, { skipShowInlineErrorTests: true, }); @@ -58,7 +60,7 @@ describe('@RTL', () => { suites.testRadioField(theme.RadioField); suites.testSelectField(theme.SelectField); suites.testSubmitField(theme.SubmitField); - suites.testTextField(theme.TextField); + suites.testTextField(theme.TextField, { testShowInlineError: false }); suites.testValidatedForm(theme.ValidatedForm); suites.testValidatedQuickForm(theme.ValidatedQuickForm); }); diff --git a/packages/uniforms/__suites__/BaseForm.tsx b/packages/uniforms/__suites__/BaseForm.tsx index 1a2f49e9e..89c887748 100644 --- a/packages/uniforms/__suites__/BaseForm.tsx +++ b/packages/uniforms/__suites__/BaseForm.tsx @@ -4,10 +4,10 @@ import { ZodBridge } from 'uniforms-bridge-zod'; import z from 'zod'; export function testBaseForm(BaseForm: ComponentType) { + const schema = new ZodBridge({ schema: z.object({}) }); + test(' - renders', () => { - const schema = z.object({}); - const bridge = new ZodBridge({ schema }); - const screen = render(); - expect(screen.getByTestId('form')).toBeInTheDocument(); + const { container } = render(); + expect(container.getElementsByTagName('form')).toHaveLength(1); }); } diff --git a/packages/uniforms/__suites__/ListField.tsx b/packages/uniforms/__suites__/ListField.tsx index 1ff7fd040..3c534102d 100644 --- a/packages/uniforms/__suites__/ListField.tsx +++ b/packages/uniforms/__suites__/ListField.tsx @@ -6,7 +6,19 @@ import { render } from './render'; export function testListField( ListField: ComponentType, - { getListAddField }: { getListAddField: (screen: Screen) => HTMLElement }, + { + getListAddField, + disableInlineError, + testError = true, + testStyle, + testTooltip, + }: { + getListAddField: (screen: Screen) => HTMLElement; + disableInlineError?: boolean; + testError?: boolean; + testStyle?: boolean; + testTooltip?: boolean; + }, ) { test(' - renders ListAddField', () => { render(, { @@ -27,7 +39,66 @@ export function testListField( expect(screen.getByText(/ListFieldLabel.*/)).toBeInTheDocument(); }); - test(' - renders correct numer of items with model (specified)', () => { + if (testStyle) { + test(' - renders correct error style', () => { + const error = new Error(); + + render( + , + { + x: Array, + 'x.$': String, + }, + ); + + expect(screen.getByTestId('field')).toHaveStyle( + 'borderColor: rgb(255, 85, 0)', + ); + }); + + test(' - renders correct error style (with specified style prop)', () => { + const error = new Error(); + + render( + , + { + x: Array, + 'x.$': String, + }, + ); + + expect(screen.getByTestId('field')).toHaveStyle('marginLeft: 8px'); + }); + } + + if (testTooltip) { + test(' - renders correct info (specified)', () => { + const { container } = render( + , + { + x: Array, + 'x.$': String, + }, + ); + + expect( + container.getElementsByClassName('anticon-question-circle').length, + ).toBe(1); + }); + } + + test(' - renders correct number of items with model (specified)', () => { render( , { @@ -74,7 +145,7 @@ export function testListField( }); test(' - renders children with correct name (children)', () => { - const Child = jest.fn(() =>
) as FC; + const Child = jest.fn(() =>
) as FC; render( @@ -116,4 +187,46 @@ export function testListField( expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenLastCalledWith('x', [undefined]); }); + + if (testError) { + test(' - renders correct error text (specified)', async () => { + const error = new Error(); + + render( + , + { + x: Array, + 'x.$': String, + }, + ); + + expect(screen.getByText(/^Error$/)).toBeInTheDocument(); + }); + } + + if (disableInlineError) { + test(' - renders correct error text (showInlineError=false)', async () => { + const error = new Error(); + + render( + , + { + x: Array, + 'x.$': String, + }, + ); + + expect(screen.queryByText(/^Error$/)).not.toBeInTheDocument(); + }); + } } diff --git a/packages/uniforms/__suites__/ListItemField.tsx b/packages/uniforms/__suites__/ListItemField.tsx new file mode 100644 index 000000000..14eb4b34a --- /dev/null +++ b/packages/uniforms/__suites__/ListItemField.tsx @@ -0,0 +1,40 @@ +import { screen } from '@testing-library/react'; +import React, { ComponentType } from 'react'; +import z from 'zod'; + +import { renderWithZod } from './render-zod'; + +export function testListItemField(ListItemField: ComponentType) { + test(' - works', () => { + renderWithZod({ + element: , + schema: z.object({ field: z.string().optional() }), + }); + + expect(screen.getByLabelText('Field')).toBeInTheDocument(); + }); + + test(' - renders ListDelField', () => { + renderWithZod({ + element: , + schema: z.object({ field: z.string() }), + }); + + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + test(' - renders children if specified', () => { + const Child = jest.fn(() =>
) as React.FC; + + renderWithZod({ + element: ( + + + + ), + schema: z.object({ field: z.string() }), + }); + + expect(Child).toHaveBeenCalledTimes(1); + }); +} diff --git a/packages/uniforms/__suites__/LongTextField.tsx b/packages/uniforms/__suites__/LongTextField.tsx index 21cad4259..fced6b2e8 100644 --- a/packages/uniforms/__suites__/LongTextField.tsx +++ b/packages/uniforms/__suites__/LongTextField.tsx @@ -1,6 +1,6 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import React, { ComponentType } from 'react'; +import React, { ComponentType, PropsWithChildren } from 'react'; import z from 'zod'; import { renderWithZod } from './render-zod'; @@ -10,6 +10,11 @@ export function testLongTextField( options?: { skipShowInlineErrorTests?: boolean; testMinMaxLength?: boolean; + testPassThemeProps?: { + ThemeProvider: ( + props: PropsWithChildren<{ themeOptions: any }>, + ) => JSX.Element; + }; }, ) { test(' - renders a textarea with correct disabled state', () => { @@ -135,6 +140,73 @@ export function testLongTextField( }); } + if (options?.testPassThemeProps) { + const { ThemeProvider } = options.testPassThemeProps; + + test(' - default props are not passed when MUI theme props are specified', () => { + const themeOptions = { + props: { MuiTextField: { fullWidth: false, margin: 'normal' } }, + }; + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginNormal', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); + }); + + test(' - default props are passed when MUI theme props are absent', () => { + const themeOptions = {}; + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginDense', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).toHaveClass('MuiFormControl-fullWidth'); + }); + + test(' - explicit props are passed when MUI theme props are specified', () => { + const themeOptions = { + props: { MuiTextField: { fullWidth: true, margin: 'dense' } }, + }; + const explicitProps = { + fullWidth: false, + margin: 'normal' as const, + }; + + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginNormal', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); + }); + } + if (options?.testMinMaxLength) { test(' - renders a textarea with minLength and maxLength', () => { renderWithZod({ diff --git a/packages/uniforms/__suites__/QuickForm.tsx b/packages/uniforms/__suites__/QuickForm.tsx index a13b0a6ef..c0f17e639 100644 --- a/packages/uniforms/__suites__/QuickForm.tsx +++ b/packages/uniforms/__suites__/QuickForm.tsx @@ -4,10 +4,10 @@ import { ZodBridge } from 'uniforms-bridge-zod'; import z from 'zod'; export function testQuickForm(QuickForm: ComponentType) { + const bridge = new ZodBridge({ schema: z.object({}) }); + test(' - renders', () => { - const schema = z.object({}); - const bridge = new ZodBridge({ schema }); - const screen = render(); - expect(screen.getByTestId('form')).toBeInTheDocument(); + const { container } = render(); + expect(container.getElementsByTagName('form')).toHaveLength(1); }); } diff --git a/packages/uniforms/__suites__/TextField.tsx b/packages/uniforms/__suites__/TextField.tsx index 718bd8d06..9040865f4 100644 --- a/packages/uniforms/__suites__/TextField.tsx +++ b/packages/uniforms/__suites__/TextField.tsx @@ -1,11 +1,36 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import React, { ComponentType } from 'react'; +import React, { ComponentType, PropsWithChildren } from 'react'; import z from 'zod'; import { renderWithZod } from './render-zod'; -export function testTextField(TextField: ComponentType) { +type TestTextFieldOptions = { + testShowInlineError?: boolean; + testPassThemeProps?: { + ThemeProvider: ( + props: PropsWithChildren<{ themeOptions: any }>, + ) => JSX.Element; + }; + testWrapClassName?: boolean; + testRenderIcon?: boolean; + testMinMaxLength?: boolean; +}; + +export function testTextField( + TextField: ComponentType, + options: TestTextFieldOptions = { + testShowInlineError: true, + }, +) { + test(' - renders an input', () => { + renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + expect(screen.getByRole('textbox')).toBeTruthy(); + }); + test(' - renders an input with correct disabled state', () => { renderWithZod({ element: , @@ -133,4 +158,184 @@ export function testTextField(TextField: ComponentType) { }); expect(screen.getByLabelText(/^Y/)).toBeInTheDocument(); }); + + test(' - renders a wrapper with unknown props', () => { + const props = { + 'data-x': 'x', + 'data-y': 'y', + 'data-z': 'z', + }; + renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + + const querySelectorParams = Object.entries(props) + .map(([key, value]) => `[${key}="${value}"]`) + .join(''); + const wrapper = screen.getByRole('textbox').closest(querySelectorParams); + expect(wrapper).toBeTruthy(); + }); + + test(' - renders a input with correct type prop (password)', () => { + renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + const wrapper = screen.getByLabelText(/^X( \*)?$/); + expect(wrapper).toHaveAttribute('type', 'password'); + }); + + if (options.testShowInlineError) { + test(' - renders a TextField with correct error text (specified)', () => { + const errorMessage = 'Error'; + renderWithZod({ + element: ( + + ), + schema: z.object({ x: z.string() }), + }); + + expect(screen.getByText(errorMessage)).toBeInTheDocument(); + }); + + test(' - renders a TextField with correct error text (showInlineError=false)', () => { + const errorMessage = 'Error'; + renderWithZod({ + element: ( + + ), + schema: z.object({ x: z.string() }), + }); + + expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + }); + } + + if (options.testPassThemeProps) { + const { ThemeProvider } = options.testPassThemeProps; + + test(' - default props are not passed when MUI theme props are specified', () => { + const themeOptions = { + props: { MuiTextField: { fullWidth: false, margin: 'normal' } }, + }; + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginNormal', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); + }); + + test(' - default props are passed when MUI theme props are absent', () => { + const themeOptions = {}; + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginDense', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).toHaveClass('MuiFormControl-fullWidth'); + }); + + test(' - explicit props are passed when MUI theme props are specified', () => { + const themeOptions = { + props: { MuiTextField: { fullWidth: true, margin: 'dense' } }, + }; + const explicitProps = { + fullWidth: false, + margin: 'normal' as const, + }; + + const { container } = renderWithZod({ + element: ( + + + + ), + schema: z.object({ x: z.string() }), + }); + + const elements = container.getElementsByClassName( + 'MuiFormControl-marginNormal', + ); + expect(elements).toHaveLength(1); + expect(elements[0]).not.toHaveClass('MuiFormControl-fullWidth'); + }); + } + + if (options.testRenderIcon) { + test(' - renders an icon', () => { + const { container } = renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + + expect(container.querySelector('i')).toBeInTheDocument(); + }); + test(' - renders an iconLeft', () => { + const { container } = renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + + expect(container.querySelector('i')).toBeInTheDocument(); + }); + } + + if (options.testWrapClassName) { + test(' - renders with a custom wrapClassName', () => { + const testClassName = 'test-class-name'; + renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + + expect(screen.getByRole('textbox').closest('div')).toHaveClass( + testClassName, + ); + }); + } + + if (options.testMinMaxLength) { + test(' - renders a input with minLength and maxLength', () => { + renderWithZod({ + element: , + schema: z.object({ x: z.string() }), + }); + + const inputElement = screen.getByRole('textbox'); + const wrapperElement = inputElement.parentNode; + + expect(inputElement).toHaveAttribute('minLength', '1'); + expect(inputElement).toHaveAttribute('maxLength', '10'); + expect(wrapperElement).not.toHaveAttribute('minLength'); + expect(wrapperElement).not.toHaveAttribute('maxLength'); + }); + } } diff --git a/packages/uniforms/__suites__/index.ts b/packages/uniforms/__suites__/index.ts index de6b16372..e63c78281 100644 --- a/packages/uniforms/__suites__/index.ts +++ b/packages/uniforms/__suites__/index.ts @@ -10,6 +10,7 @@ export * from './HiddenField'; export * from './ListAddField'; export * from './ListDelField'; export * from './ListField'; +export * from './ListItemField'; export * from './LongTextField'; export * from './NestField'; export * from './NumField'; diff --git a/packages/uniforms/__tests__/BaseForm.tsx b/packages/uniforms/__tests__/BaseForm.tsx index be29a930f..4ab9cdb62 100644 --- a/packages/uniforms/__tests__/BaseForm.tsx +++ b/packages/uniforms/__tests__/BaseForm.tsx @@ -1,182 +1,68 @@ -import { ReactWrapper } from 'enzyme'; -import React from 'react'; -import { BaseForm, Bridge, Context, useField } from 'uniforms'; - -import mount from './_mount'; - -jest.mock('meteor/aldeed:simple-schema'); -jest.mock('meteor/check'); +import { fireEvent, render, screen } from '@testing-library/react'; +import React, { useContext } from 'react'; +import { BaseForm, context } from 'uniforms'; +import { ZodBridge } from 'uniforms-bridge-zod'; +import { AutoField } from 'uniforms-unstyled'; +import z from 'zod'; describe('BaseForm', () => { - const error = new Error(); const model = { $: [1], _: 1 }; - const schema: Bridge = { - getError() {}, - getErrorMessage: () => '', - getErrorMessages: () => [], - getField: () => ({}), - getInitialValue() {}, - getProps: () => ({}), - getSubfields: () => [], - getType() {}, - getValidator: () => () => {}, - }; + const schema = new ZodBridge({ + schema: z.object({ a: z.string().optional() }), + }); const onChange = jest.fn(); const onSubmit = jest.fn(); - afterEach(() => { onChange.mockClear(); onSubmit.mockClear(); }); - it('have correct context', () => { - const wrapper = mount>( - , - ); - - const context = wrapper.instance().getContext(); - expect(context).toEqual>({ - changed: false, - changedMap: {}, - error, - model, - name: [], - onChange: expect.any(Function), - onSubmit: expect.any(Function), - randomId: expect.any(Function), - schema, - state: { - disabled: false, - readOnly: false, - showInlineError: false, - }, - submitted: false, - submitting: false, - validating: false, - formRef: expect.any(BaseForm), - }); - }); - describe('when rendered', () => { - const wrapper = mount>( - -
-
-
- , - ); - it('is
', () => { - expect(wrapper.find('form')).toHaveLength(1); - }); - - it('have correct props', () => { - expect(wrapper.props()).toHaveProperty('noValidate', true); + const { container } = render(); + expect(container.getElementsByTagName('form')).toHaveLength(1); }); it('have correct children', () => { - expect(wrapper).toContainEqual(expect.anything()); - expect(wrapper.find('div')).toHaveLength(3); - }); - - it('have correct `resetCount`', () => { - expect(wrapper.state('resetCount')).toBe(0); - }); - - it('have correct `state`', () => { - const context = wrapper.instance().getContext(); - expect(context.state).toEqual['state']>({ - disabled: true, - readOnly: false, - showInlineError: true, - }); - }); - - it('updates schema bridge', () => { - const schema2 = { ...(schema as Omit), getType: () => {} }; - - wrapper.setProps({ schema: schema2 }); - - const context = wrapper.instance().getContext(); - - expect(context).toHaveProperty('schema', schema2); - }); - - it('ignores changes made on first render', () => { - function Field() { - const [props] = useField('name', {}); - props.onChange(123); - return null; - } - - const wrapper = mount>( - - + const { container } = render( + +
+
+
, ); - const context = wrapper.instance().getContext(); - expect(context).toHaveProperty('changed', false); - expect(context).toHaveProperty('changedMap', {}); - - expect(onChange).toHaveBeenCalledTimes(1); - expect(onChange).toHaveBeenCalledWith('name', 123); + expect(container.getElementsByTagName('div')).toHaveLength(3); }); }); describe('when changed', () => { - type Form = BaseForm; - let wrapper: ReactWrapper; - - beforeEach(() => { - wrapper = mount( - , + it('autosaves correctly (`autosave` = false)', async () => { + render( + + + , ); - }); - - it('updates `changed` and `changedMap`', () => { - const context1 = wrapper.instance().getContext(); - expect(context1).toHaveProperty('changed', false); - expect(context1).toHaveProperty('changedMap', {}); - - wrapper.instance().getContext().onChange('$', [1, 2]); - const context2 = wrapper.instance().getContext(); - expect(context2).toHaveProperty('changed', true); - expect(context2).toHaveProperty('changedMap.$'); - expect(context2.changedMap.$).toBeTruthy(); - expect(context2).toHaveProperty('changedMap.$.1'); - expect(context2.changedMap.$?.[1]).toBeTruthy(); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); - wrapper.instance().getContext().onChange('$', [1]); + await new Promise(resolve => setTimeout(resolve)); - const context3 = wrapper.instance().getContext(); - expect(context3).toHaveProperty('changed', true); - expect(context3).toHaveProperty('changedMap.$'); - expect(context3.changedMap.$).toBeTruthy(); - expect(context3).toHaveProperty('changedMap.$.1'); - expect(context3.changedMap.$?.[1]).toBeTruthy(); + expect(onSubmit).not.toBeCalled(); }); it('autosaves correctly (`autosave` = true)', async () => { - wrapper.setProps({ autosave: true }); - wrapper.instance().getContext().onChange('a', 1); - await new Promise(resolve => setTimeout(resolve)); - const context = wrapper.instance().getContext(); - expect(onSubmit).toHaveBeenCalledTimes(1); - expect(context.submitted).toBe(true); - expect(onSubmit).toHaveBeenLastCalledWith(model); - }); + render( + + + , + ); + + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); - it('autosaves are not delayed', async () => { - wrapper.setProps({ autosave: true }); - wrapper.instance().getContext().onChange('a', 1); await new Promise(resolve => setTimeout(resolve)); expect(onSubmit).toHaveBeenCalledTimes(1); @@ -184,10 +70,23 @@ describe('BaseForm', () => { }); it('autosaves can be delayed', async () => { - wrapper.setProps({ autosave: true, autosaveDelay: 25 }); - wrapper.instance().getContext().onChange('a', 1); - wrapper.instance().getContext().onChange('a', 2); - wrapper.instance().getContext().onChange('a', 3); + render( + + + , + ); + + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test 1' } }); + fireEvent.change(input, { target: { value: 'test 2' } }); + fireEvent.change(input, { target: { value: 'test 3' } }); + await new Promise(resolve => setTimeout(resolve)); expect(onSubmit).not.toHaveBeenCalled(); @@ -199,16 +98,28 @@ describe('BaseForm', () => { }); it('autosaves can be delayed (longer)', async () => { - wrapper.setProps({ autosave: true, autosaveDelay: 10 }); - wrapper.instance().getContext().onChange('a', 1); - wrapper.instance().getContext().onChange('a', 2); - wrapper.instance().getContext().onChange('a', 3); + render( + + + , + ); + + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test 1' } }); + fireEvent.change(input, { target: { value: 'test 2' } }); + fireEvent.change(input, { target: { value: 'test 3' } }); await new Promise(resolve => setTimeout(resolve, 25)); - wrapper.instance().getContext().onChange('a', 1); - wrapper.instance().getContext().onChange('a', 2); - wrapper.instance().getContext().onChange('a', 3); + fireEvent.change(input, { target: { value: 'test 1' } }); + fireEvent.change(input, { target: { value: 'test 2' } }); + fireEvent.change(input, { target: { value: 'test 3' } }); await new Promise(resolve => setTimeout(resolve, 25)); @@ -217,171 +128,136 @@ describe('BaseForm', () => { }); it('clears autosave correctly', () => { - wrapper.setProps({ autosave: true, autosaveDelay: 100 }); - wrapper.instance().getContext().onChange('a', 1); - wrapper.unmount(); + const { unmount } = render( + + + , + ); - expect(onSubmit).not.toBeCalled(); - }); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test 1' } }); - it('autosaves correctly (`autosave` = false)', () => { - wrapper.setProps({ autosave: true }); - wrapper.setProps({ autosave: false }); - wrapper.instance().getContext().onChange('a', 1); + unmount(); expect(onSubmit).not.toBeCalled(); }); it('calls `onChange` with correct name and value', () => { - wrapper.instance().getContext().onChange('a', 1); - - expect(onChange).toHaveBeenCalledTimes(1); - expect(onChange).toHaveBeenLastCalledWith('a', 1); - }); - - it('cancels `onChange` event', () => { - wrapper.find('form').simulate('change'); - - expect(onChange).not.toBeCalled(); - }); - - it('does nothing without `onChange`', () => { - wrapper.setProps({ onChange: undefined }); - wrapper.instance().getContext().onChange('a', 1); - - expect(onChange).not.toBeCalled(); - }); - }); - - describe('when reset', () => { - const createWrapper = () => - mount>( - , + render( + + + , ); - it('increase `resetCount`', () => { - const wrapper = createWrapper(); - wrapper.instance().reset(); - - expect(wrapper.state('resetCount')).toBe(1); - }); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test 1' } }); - it('sets submitted back to false', async () => { - const wrapper = createWrapper(); - const instance = wrapper.instance(); - expect(instance.getContext().submitted).toBe(false); - wrapper.find('form').simulate('submit'); - expect(instance.getContext().submitted).toBe(true); - instance.reset(); - expect(instance.getContext().submitted).toBe(false); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenLastCalledWith('a', 'test 1'); }); }); describe('when submitted', () => { - const createWrapper = () => - mount>( - , - ); - it('calls `onSubmit` once', () => { - const wrapper = createWrapper(); - wrapper.find('form').simulate('submit'); + render( + , + ); + fireEvent.submit(screen.getByTestId('form')); expect(onSubmit).toHaveBeenCalledTimes(1); }); it('calls `onSubmit` with correct model', () => { - const wrapper = createWrapper(); - wrapper.find('form').simulate('submit'); + render( + , + ); + fireEvent.submit(screen.getByTestId('form')); expect(onSubmit).toHaveBeenLastCalledWith(model); }); it('calls `onSubmit` with the correctly `modelTransform`ed model', () => { - const wrapper = createWrapper(); - wrapper.setProps({ - modelTransform(mode, model) { - return mode === 'submit' ? { submit: 1 } : model; - }, - }); - - wrapper.find('form').simulate('submit'); + render( + + mode === 'submit' ? { submit: 1 } : model + } + />, + ); + fireEvent.submit(screen.getByTestId('form')); expect(onSubmit).toHaveBeenLastCalledWith({ submit: 1 }); - - wrapper.setProps({ modelTransform: undefined }); }); it('sets `submitted` to true', async () => { - const wrapper = createWrapper(); - const instance = wrapper.instance(); - expect(instance.getContext().submitted).toBe(false); - wrapper.find('form').simulate('submit'); - expect(instance.getContext().submitted).toBe(true); - }); + let submitted: boolean | undefined; - it('sets `submitting` state while submitting', async () => { - const wrapper = createWrapper(); - // FIXME: It should say `() => void`. - let resolveSubmit: (...args: any[]) => void = () => {}; - wrapper.setProps({ - onSubmit: () => new Promise(resolve => (resolveSubmit = resolve)), - }); + function Field() { + const test = useContext(context); + submitted = test?.submitted; + return null; + } - const context1 = wrapper.instance().getContext(); - expect(context1).toHaveProperty('submitting', false); + render( + + + , + ); - wrapper.find('form').simulate('submit'); - await new Promise(resolve => process.nextTick(resolve)); + expect(submitted).toBe(false); + fireEvent.submit(screen.getByTestId('form')); + expect(submitted).toBe(true); + }); - const context2 = wrapper.instance().getContext(); - expect(context2).toHaveProperty('submitting', true); + it('sets `submitting` state while submitting', async () => { + let submitting: boolean | undefined; - resolveSubmit(); - await new Promise(resolve => process.nextTick(resolve)); + function Field() { + const test = useContext(context); + submitting = test?.submitting; + return null; + } - const context3 = wrapper.instance().getContext(); - expect(context3).toHaveProperty('submitting', false); - }); + let resolveSubmit: (...args: any[]) => void = () => {}; + const test = () => new Promise(resolve => (resolveSubmit = resolve)); - it('ignores synchronous errors', async () => { - const wrapper = createWrapper(); - const error = new Error(); - wrapper.setProps({ - onSubmit() { - throw error; - }, - }); - - try { - wrapper.instance().submit(); - throw new Error('Unreachable.'); - } catch (catched) { - expect(catched).toBe(error); - } - }); + render( + + + , + ); - it('returns asynchronous results', async () => { - const wrapper = createWrapper(); - const value = 42; - wrapper.setProps({ - async onSubmit() { - return value; - }, - }); + expect(submitting).toBe(false); - await expect(wrapper.instance().submit()).resolves.toBe(value); - }); + const form = screen.getByTestId('form'); + fireEvent.submit(form); - it('works when unmounted on submit', () => { - const spy = jest.spyOn(console, 'error'); - const wrapper = createWrapper(); - onSubmit.mockImplementationOnce(async () => wrapper.unmount()); - wrapper.find('form').simulate('submit'); + expect(submitting).toBe(true); - expect(spy).not.toHaveBeenCalled(); + resolveSubmit(); + await new Promise(resolve => process.nextTick(resolve)); - spy.mockRestore(); + expect(submitting).toBe(false); }); }); }); diff --git a/packages/uniforms/__tests__/QuickForm.tsx b/packages/uniforms/__tests__/QuickForm.tsx index b1a0cb6cd..f0b9ff76d 100644 --- a/packages/uniforms/__tests__/QuickForm.tsx +++ b/packages/uniforms/__tests__/QuickForm.tsx @@ -1,82 +1,96 @@ +import { render } from '@testing-library/react'; import React from 'react'; -import SimpleSchema from 'simpl-schema'; import { QuickForm } from 'uniforms'; -import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; - -import mount from './_mount'; +import { ZodBridge } from 'uniforms-bridge-zod'; +import { z } from 'zod'; describe('QuickForm', () => { + const AutoField = jest.fn(() => null) as any; + const ErrorsField = jest.fn(() => null) as any; + const SubmitField = jest.fn(() => null) as any; + // @ts-expect-error QuickForm is not a valid Component. class TestForm extends QuickForm { // eslint-disable-next-line react/display-name - getAutoField = () => () => ; + getAutoField = () => () => ; // eslint-disable-next-line react/display-name - getErrorsField = () => () => ; + getErrorsField = () => () => ; // eslint-disable-next-line react/display-name - getSubmitField = () => () => ; + getSubmitField = () => () => ; } - const schema = new SimpleSchema2Bridge({ - schema: new SimpleSchema({ - a: String, - b: String, - c: String, - }), + const schema = z.object({ + a: z.string(), + b: z.string(), + c: z.string(), + }); + const bridge = new ZodBridge({ schema }); + + afterEach(() => { + AutoField.mockClear(); + ErrorsField.mockClear(); + SubmitField.mockClear(); }); describe('when rendered with custom fields', () => { it('renders `AutoField` for each field', () => { - const wrapper = mount(); + render(); - expect(wrapper.find('.auto').length).toBeGreaterThan(0); + expect(AutoField).toHaveBeenCalledTimes(3); }); it('renders `ErrorsField`', () => { - const wrapper = mount(); + render(); - expect(wrapper.find('.errors').length).toBeGreaterThan(0); + expect(ErrorsField).toHaveBeenCalledTimes(1); }); it('renders `SubmitField`', () => { - const wrapper = mount(); + render(); - expect(wrapper.find('.submit').length).toBeGreaterThan(0); + expect(SubmitField).toHaveBeenCalledTimes(1); }); }); describe('when rendered with custom fields in `props`', () => { it('renders `ErrorsField`', () => { - const wrapper = mount( + const ErrorsOverrideField = jest.fn(() =>
) as React.FC; + + render( } + schema={bridge} + errorsField={() => } />, ); - expect(wrapper.find('.errorsOverride').length).toBeGreaterThan(0); + expect(ErrorsOverrideField).toHaveBeenCalledTimes(1); }); it('renders `SubmitField`', () => { - const wrapper = mount( + const SubmitOverrideField = jest.fn(() =>
) as React.FC; + + render( } + schema={bridge} + submitField={() => } />, ); - expect(wrapper.find('.submitOverride').length).toBeGreaterThan(0); + expect(SubmitOverrideField).toHaveBeenCalledTimes(1); }); }); describe('when rendered with children', () => { - const wrapper = mount( - -
- , - ); - it('renders children', () => { - expect(wrapper.find('div')).toHaveLength(1); + const Child = jest.fn(() =>
) as React.FC; + + render( + + + , + ); + + expect(Child).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/uniforms/__tests__/connectField.tsx b/packages/uniforms/__tests__/connectField.tsx index 4d44066e3..6865b8fff 100644 --- a/packages/uniforms/__tests__/connectField.tsx +++ b/packages/uniforms/__tests__/connectField.tsx @@ -1,54 +1,85 @@ -import React, { ReactNode } from 'react'; -import SimpleSchema from 'simpl-schema'; +import { fireEvent, screen } from '@testing-library/react'; +import omit from 'lodash/omit'; +import React from 'react'; import { BaseForm, Context, + OnChange, UnknownObject, connectField, + filterDOMProps, randomIds, } from 'uniforms'; -import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; -import mount from './_mount'; +import { render } from '../__suites__/render'; describe('connectField', () => { const onChange = jest.fn(); - const schema = new SimpleSchema2Bridge({ - schema: new SimpleSchema({ - another: { type: String, optional: true }, - field: { type: Object, label: 'Field' }, - 'field.subfield': { type: Number, label: 'Subfield' }, - }), - }); - const reactContext = { - context: { - changed: false, - changedMap: {}, - error: undefined, - model: {}, - name: [], - onChange, - onSubmit() {}, - randomId: randomIds(), - schema, - state: { - disabled: false, - placeholder: false, - readOnly: false, - showInlineError: true, - }, - submitted: false, - submitting: false, - validating: false, - formRef: {} as BaseForm, - } as Context, + const schema = { + another: { type: String, optional: true }, + field: { type: Object, label: 'Field' }, + 'field.subfield': { type: String, label: 'Subfield' }, }; - const Test = jest.fn(props => props.children || null); + const reactContext = { + changed: false, + changedMap: {}, + error: undefined, + model: {}, + name: [], + onChange, + onSubmit() {}, + randomId: randomIds(), + state: { + disabled: false, + readOnly: false, + showInlineError: true, + }, + submitted: false, + submitting: false, + validating: false, + formRef: {} as BaseForm, + } as Partial>; + + const Test = ( + props: UnknownObject & { + onChange: OnChange; + label?: string | React.ReactNode; + id: string; + }, + ) => { + return props.children ? ( + <> + {props.label && ( + + )} + props.onChange(event.target.value)} + /> + {props.children} + + ) : ( + <> + {props.label ? ( + + ) : null} + props.onChange(event.target.value)} + /> + + ); + }; - beforeEach(() => { - Test.mockClear(); + afterEach(() => { onChange.mockClear(); }); @@ -85,7 +116,7 @@ describe('connectField', () => { it('includes default value (true)', () => { const Field = connectField(Test, { initialValue: true }); - mount(, reactContext); + render(, schema, reactContext); expect(onChange).toBeCalledWith('field', {}); }); @@ -93,7 +124,7 @@ describe('connectField', () => { it('does nothing (false)', () => { const Field = connectField(Test, { initialValue: false }); - mount(, reactContext); + render(, schema, reactContext); expect(onChange).not.toBeCalled(); }); @@ -101,7 +132,7 @@ describe('connectField', () => { it('respects `required` (props)', () => { const Field = connectField(Test, { initialValue: true }); - mount(, reactContext); + render(, schema, reactContext); expect(onChange).not.toBeCalled(); }); @@ -109,7 +140,7 @@ describe('connectField', () => { it('respects `required` (schema)', () => { const Field = connectField(Test, { initialValue: true }); - mount(, reactContext); + render(, schema, reactContext); expect(onChange).not.toBeCalled(); }); @@ -119,7 +150,12 @@ describe('connectField', () => { it('treats value as initial value', async () => { const Field = connectField(Test); - mount(, reactContext); + render( + , + schema, + reactContext, + ); + await new Promise(resolve => setTimeout(resolve, 10)); expect(onChange).toBeCalledWith('field', 'initialValueExample'); @@ -127,17 +163,15 @@ describe('connectField', () => { }); describe('when rendered with label', () => { - const labelA = Error; - const labelB = OK; + const labelA = Label A; + const labelB = Label B; it.each([ ['Props', '', 'Props'], ['Props', 'Schema', 'Props'], - ['Props', labelB, 'Props'], ['Props', undefined, 'Props'], ['', undefined, ''], ['', 'Schema', ''], - ['', labelB, ''], ['', undefined, ''], [labelA, '', labelA], [labelA, 'Schema', labelA], @@ -147,48 +181,76 @@ describe('connectField', () => { [undefined, 'Schema', 'Schema'], [undefined, labelB, labelB], [undefined, undefined, ''], - ] as [ReactNode, ReactNode, ReactNode][])( - 'resolves it correctly (%#)', - (prop, schema, result) => { - const context: typeof reactContext = { - context: { - ...reactContext.context, - state: { ...reactContext.context.state }, - }, - }; - - jest - .spyOn(context.context.schema, 'getProps') - .mockReturnValueOnce({ label: schema }); - - const Field = connectField(Test); - const wrapper = mount(, context); - expect(wrapper.find(Test).prop('label')).toBe(result); - }, - ); + ])('resolves it correctly (%#)', (propLabel, schemaLabel, resultLabel) => { + const schema = { + another: { type: String, optional: true }, + field: { type: Object, label: schemaLabel }, + 'field.subfield': { type: String, label: 'Subfield' }, + }; + + const Field = connectField(Test); + render( + , + // @ts-expect-error + schema, + reactContext, + ); + + if (resultLabel === labelA) { + expect(screen.getByText('Label A')).toBeInTheDocument(); + } else if (resultLabel === labelB) { + expect(screen.getByText('Label B')).toBeInTheDocument(); + } else { + const result = resultLabel as string; + + if (result) { + expect(screen.getByText(result)).toBeInTheDocument(); + } else { + expect(screen.queryByTestId('label')).toBe(null); + } + } + }); }); describe('when rendered provides correct onChange', () => { it('is defaults to field name', () => { const Field = connectField(Test); + + render(, schema, reactContext); + const value = 'some value'; - const wrapper = mount(, reactContext); - wrapper.find(Test).prop('onChange')(value); + const input = screen.getByTestId('field'); + fireEvent.change(input, { target: { value } }); + expect(onChange).toBeCalledWith('another', value); }); it('is able to set another field value', () => { - const Field = connectField(Test); - const value = { subfield: 123 }; - const wrapper = mount(, reactContext); - wrapper.find(Test).prop('onChange')(value, 'field'); - expect(onChange).toBeCalledWith('field', value); + const Field = connectField((props: any) => ( + + props.onChange(event.target.value, 'field.subfield') + } + /> + )); + + render(, schema, reactContext); + + const input = screen.getByTestId('field'); + const value = 'test'; + + fireEvent.change(input, { target: { value } }); + + expect(onChange).toBeCalledWith('field.subfield', value); }); }); it('works with nested labels', () => { const Field = connectField(Test); - const wrapper = mount( + + render( @@ -202,17 +264,12 @@ describe('connectField', () => { , + schema, reactContext, ); - expect(wrapper.find(Test).at(0).prop('label')).toBe('Field'); - expect(wrapper.find(Test).at(1).prop('label')).toBe(''); - expect(wrapper.find(Test).at(2).prop('label')).toBe('Field'); - expect(wrapper.find(Test).at(3).prop('label')).toBe('Other'); - expect(wrapper.find(Test).at(4).prop('label')).toBe('Subfield'); - expect(wrapper.find(Test).at(5).prop('label')).toBe('Subfield'); - expect(wrapper.find(Test).at(6).prop('label')).toBe('Subfield'); - expect(wrapper.find(Test).at(7).prop('label')).toBe('Subfield'); - expect(wrapper.find(Test).at(8).prop('label')).toBe(''); + expect(screen.getAllByText('Field')).toHaveLength(2); + expect(screen.getAllByText('Subfield')).toHaveLength(4); + expect(screen.getAllByText('Other')).toHaveLength(1); }); });