From 2294beb3c792fe348b18a7209a77f769a95c29d8 Mon Sep 17 00:00:00 2001 From: Ernest Teluk <49727996+ErnestTeluk@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:53:10 +0100 Subject: [PATCH] Migrated core tests from Enzyme to React Testing Library. --- packages/uniforms/__suites__/render.tsx | 81 +- packages/uniforms/__tests__/AutoForm.tsx | 212 +++--- packages/uniforms/__tests__/ValidatedForm.tsx | 703 ++++++++++++------ packages/uniforms/__tests__/useField.tsx | 32 +- 4 files changed, 670 insertions(+), 358 deletions(-) diff --git a/packages/uniforms/__suites__/render.tsx b/packages/uniforms/__suites__/render.tsx index 2b03237c0..a3086072c 100644 --- a/packages/uniforms/__suites__/render.tsx +++ b/packages/uniforms/__suites__/render.tsx @@ -1,47 +1,56 @@ -import { render as renderOnScreen } from '@testing-library/react'; -import React, { ReactElement } from 'react'; +import { render as renderOnScreen, RenderResult } from '@testing-library/react'; +import React, { ReactElement, cloneElement } from 'react'; import SimpleSchema, { SimpleSchemaDefinition } from 'simpl-schema'; import { BaseForm, Context, UnknownObject, context, randomIds } from 'uniforms'; import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; const randomId = randomIds(); - -export function render( - element: ReactElement, - schema: SimpleSchemaDefinition, +export function render( + element: ReactElement

, + schema?: SimpleSchemaDefinition, contextValueExtension?: Partial>, model = {} as Model, -) { - const contextValue = { - changed: false, - changedMap: {}, - error: null, - model, - name: [], - onChange() {}, - onSubmit() {}, - randomId, - submitted: false, - submitting: false, - validating: false, - ...contextValueExtension, - schema: new SimpleSchema2Bridge(new SimpleSchema(schema)), - state: { - disabled: false, - label: false, - placeholder: false, - readOnly: false, - showInlineError: false, - ...contextValueExtension?.state, - }, - formRef: {} as BaseForm, - }; - - return renderOnScreen(element, { +): RenderResult & { rerenderWithProps: (props: P) => void } { + const renderResult = renderOnScreen(element, { wrapper({ children }) { - return ( - {children} - ); + if (schema) { + const contextValue = { + changed: false, + changedMap: {}, + error: null, + model, + name: [], + onChange() {}, + onSubmit() {}, + randomId, + submitted: false, + submitting: false, + validating: false, + ...contextValueExtension, + schema: new SimpleSchema2Bridge(new SimpleSchema(schema)), + state: { + disabled: false, + label: false, + placeholder: false, + readOnly: false, + showInlineError: false, + ...contextValueExtension?.state, + }, + formRef: {} as BaseForm, + }; + return ( + {children} + ); + } + return <>{children}; }, }); + + const { rerender } = renderResult; + + const rerenderWithProps = (props: P) => { + rerender(cloneElement(element, props)); + }; + + return { rerenderWithProps, ...renderResult }; } diff --git a/packages/uniforms/__tests__/AutoForm.tsx b/packages/uniforms/__tests__/AutoForm.tsx index 81b43ef15..d701ddcdc 100644 --- a/packages/uniforms/__tests__/AutoForm.tsx +++ b/packages/uniforms/__tests__/AutoForm.tsx @@ -1,92 +1,103 @@ -import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import React, { ReactNode } from 'react'; import SimpleSchema from 'simpl-schema'; -import { AutoForm, connectField } from 'uniforms'; +import { AutoForm, connectField, Context, context } from 'uniforms'; import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; +import { AutoFields } from 'uniforms-unstyled'; -import mount from './_mount'; +import { render } from '../__suites__'; -describe('AutoForm', () => { - const onChangeModel = jest.fn(); - const validator = jest.fn(); +describe('', () => { const onChange = jest.fn(); + const onChangeModel = jest.fn(); const onSubmit = jest.fn(); + const validator = jest.fn(); + const contextSpy = jest.fn | null]>(); const model = { a: '1' }; - const schema = new SimpleSchema2Bridge( - new SimpleSchema({ - a: { type: String, defaultValue: '' }, - b: { type: String, defaultValue: '' }, - c: { type: String, defaultValue: '' }, - }), - ); + const schemaDefinition = { + a: { type: String, defaultValue: '' }, + b: { type: String, defaultValue: '' }, + c: { type: String, defaultValue: '' }, + }; + const schema = new SimpleSchema2Bridge(new SimpleSchema(schemaDefinition)); + jest.spyOn(schema.schema, 'validator').mockImplementation(() => validator); - beforeEach(() => { - onChange.mockClear(); - onChangeModel.mockClear(); - onSubmit.mockClear(); - validator.mockClear(); - }); + beforeEach(() => jest.clearAllMocks()); describe('when changed', () => { it('updates', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + render( + + + , + schemaDefinition, + { onChange }, ); - - wrapper.instance().getContext().onChange('a', '2'); - - expect(onChange).toHaveBeenCalledTimes(1); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: '2' } }); + expect(onChange).toHaveBeenCalledTimes(4); expect(onChange).toHaveBeenLastCalledWith('a', '2'); }); - it('validates', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + render( + + + , ); - wrapper.instance().submit(); + const form = screen.getByRole('form'); + const input = screen.getByLabelText('A'); + fireEvent.submit(form); expect(validator).toHaveBeenCalledTimes(1); - expect(validator).toHaveBeenLastCalledWith({}); + expect(validator).toHaveBeenLastCalledWith({ a: '', b: '', c: '' }); - wrapper.instance().getContext().onChange('a', '1'); + fireEvent.change(input, { target: { value: '2' } }); expect(validator).toHaveBeenCalledTimes(2); - expect(validator).toHaveBeenLastCalledWith({ a: '1' }); + expect(validator).toHaveBeenLastCalledWith({ a: '2', b: '', c: '' }); }); it('calls `onChangeModel`', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + render( + , ); - wrapper.instance().getContext().onChange('a', '2'); + const form = screen.getByRole('form'); + fireEvent.change(form, onChangeModel({ a: '2' })); expect(onChangeModel).toHaveBeenCalledTimes(1); expect(onChangeModel).toHaveBeenLastCalledWith({ a: '2' }); }); - it('updates `changed` and `changedMap`', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount(); - - const context1 = wrapper.instance().getContext(); - expect(context1).toHaveProperty('changed', false); - expect(context1).toHaveProperty('changedMap', {}); - - wrapper.instance().getContext().onChange('a', '2'); + render( + + + + , + schemaDefinition, + ); - const context2 = wrapper.instance().getContext(); - expect(context2).toHaveProperty('changed', true); - expect(context2).toHaveProperty('changedMap.a'); - expect(context2.changedMap.a).toBeTruthy(); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + changed: true, + changedMap: { a: {}, b: {}, c: {} }, + }), + ); }); }); - - describe('when rendered', () => { + describe('when render', () => { it('calls `onChange` before render', () => { const field = () => null; const Field = connectField(field); @@ -98,14 +109,13 @@ describe('AutoForm', () => { } } - // FIXME: AutoForm is not a valid Component. - mount( + render( // @ts-expect-error Convoluted AutoForm types , ); @@ -113,71 +123,91 @@ describe('AutoForm', () => { expect(onChange.mock.calls[0]).toEqual(expect.arrayContaining(['b', ''])); expect(onChange.mock.calls[1]).toEqual(expect.arrayContaining(['c', ''])); }); - it('skips `onSubmit` until rendered (`autosave` = true)', async () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + render( + + + , ); expect(onSubmit).not.toBeCalled(); - wrapper.instance().getContext().onChange('a', 1); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: '1' } }); await new Promise(resolve => setTimeout(resolve)); - expect(onSubmit).toHaveBeenCalledTimes(1); - expect(onSubmit).toHaveBeenLastCalledWith({ a: 1 }); + expect(onSubmit).toHaveBeenLastCalledWith({ a: '1', b: '', c: '' }); expect(validator).toHaveBeenCalledTimes(1); - expect(validator).toHaveBeenLastCalledWith({ a: 1 }); + expect(validator).toHaveBeenLastCalledWith({ a: '1', b: '', c: '' }); }); }); describe('when reset', () => { it('reset `model`', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + const Component = () => ( + + + ); + const { rerender } = render(, schemaDefinition); + + rerender(); - wrapper.instance().reset(); - expect(wrapper.instance().getContext().model).toEqual(model); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ model }), + ); }); it('resets state `changedMap`', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + const Component = () => ( + + + ); - wrapper.instance().reset(); - expect(wrapper.instance().getContext().changedMap).toEqual({}); + const { rerender } = render(, schemaDefinition); + + rerender(); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ changedMap: {} }), + ); }); it('resets state `changed`', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount( - , + const Component = () => ( + + + ); + const { rerender } = render(, schemaDefinition); + + rerender(); - wrapper.instance().reset(); - expect(wrapper.instance().getContext().changed).toEqual(false); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ changed: false }), + ); }); }); + describe('when update', () => { + it(', updates', () => { + const { rerenderWithProps } = render( + + + , + schemaDefinition, + ); - describe('when updated', () => { - it('updates', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount(); - - wrapper.setProps({ model: {} }); - expect(wrapper.instance().props.model).toEqual({}); + rerenderWithProps({ model: {} }); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ model: {} }), + ); }); - it('validates', () => { - // FIXME: AutoForm is not a valid Component. - const wrapper = mount(); + it(', validates', () => { + const { rerenderWithProps } = render(); - wrapper.setProps({ model, validate: 'onChange' }); + rerenderWithProps({ model, validate: 'onChange' }); expect(validator).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/uniforms/__tests__/ValidatedForm.tsx b/packages/uniforms/__tests__/ValidatedForm.tsx index 196993f40..9ae48486c 100644 --- a/packages/uniforms/__tests__/ValidatedForm.tsx +++ b/packages/uniforms/__tests__/ValidatedForm.tsx @@ -1,9 +1,18 @@ -import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import React, { ReactNode } from 'react'; import SimpleSchema from 'simpl-schema'; -import { ModelTransformMode, UnknownObject, ValidatedForm } from 'uniforms'; +import { + ModelTransformMode, + UnknownObject, + ValidatedForm, + context, + Context, + useForm, +} from 'uniforms'; import { SimpleSchema2Bridge } from 'uniforms-bridge-simple-schema-2'; +import { AutoField } from 'uniforms-unstyled'; -import mount from './_mount'; +import { render } from '../__suites__'; describe('ValidatedForm', () => { const onChange = jest.fn(); @@ -11,183 +20,361 @@ describe('ValidatedForm', () => { const onValidate = jest.fn((model, error) => error); const validator = jest.fn(); const validatorForSchema = jest.fn(() => validator); + const contextSpy = jest.fn | null]>(); const error = new Error(); const model = { a: 1 }; - const schema = new SimpleSchema2Bridge( - new SimpleSchema({ - a: { type: String, defaultValue: '' }, - b: { type: String, defaultValue: '' }, - c: { type: String, defaultValue: '' }, - }), - ); + const schemaDefinition = { + a: { type: String, defaultValue: '' }, + b: { type: String, defaultValue: '' }, + c: { type: String, defaultValue: '' }, + }; + const schema = new SimpleSchema2Bridge(new SimpleSchema(schemaDefinition)); jest.spyOn(schema.schema, 'validator').mockImplementation(validatorForSchema); - beforeEach(() => { - onChange.mockClear(); - onSubmit.mockClear(); - onValidate.mockClear(); - validator.mockClear(); - validatorForSchema.mockClear(); - }); + beforeEach(() => jest.clearAllMocks()); describe('on validation', () => { // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount(); - let form = wrapper.instance(); - - beforeEach(() => { - wrapper = mount( - , - ); - form = wrapper.instance(); - }); it('validates (when `.validate` is called)', () => { - form.validate(); + render( + , + ); + const form = screen.getByRole('form'); + fireEvent.submit(form); expect(validator).toHaveBeenCalledTimes(1); }); it('correctly calls `validator`', () => { - form.validate(); + render( + , + ); + const form = screen.getByRole('form'); + fireEvent.submit(form); expect(validator).toHaveBeenCalledTimes(1); expect(validator).toHaveBeenLastCalledWith(model); }); - it('updates error state with errors from `validator`', async () => { + render( + , + ); + const form = screen.getByRole('form'); + validator.mockImplementationOnce(() => { throw error; }); - form.validate(); + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); - expect(wrapper.instance().getContext().error).toBe(error); + expect(onValidate).toHaveBeenLastCalledWith(model, error); }); it('correctly calls `onValidate` when validation succeeds', () => { - form.validate(); + render( + , + ); + const form = screen.getByRole('form'); + + fireEvent.submit(form); expect(onValidate).toHaveBeenCalledTimes(1); expect(onValidate).toHaveBeenLastCalledWith(model, null); }); it('correctly calls `onValidate` when validation fails ', () => { + render( + , + ); + const form = screen.getByRole('form'); + validator.mockImplementationOnce(() => { throw error; }); - form.validate(); + fireEvent.submit(form); expect(onValidate).toHaveBeenCalledTimes(1); expect(onValidate).toHaveBeenLastCalledWith(model, error); }); it('updates error state with async errors from `onValidate`', async () => { - onValidate.mockImplementationOnce(() => error); + render( + + + , + schemaDefinition, + ); + const form = screen.getByRole('form'); - form.validate(); + onValidate.mockImplementationOnce(() => error); - expect(wrapper.instance().getContext().error).toBe(error); + fireEvent.submit(form); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error }), + ); }); - it('leaves error state alone when `onValidate` suppress `validator` errors', async () => { + render( + + + , + schemaDefinition, + ); + const form = screen.getByRole('form'); + validator.mockImplementationOnce(() => { throw error; }); onValidate.mockImplementationOnce(() => null); - form.validate(); + fireEvent.submit(form); expect(validator).toHaveBeenCalled(); expect(onValidate).toHaveBeenCalled(); - expect(wrapper.instance().getContext()).not.toHaveProperty( - 'uniforms.error', - error, + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error: null }), ); }); - it('has `validating` context variable, default `false`', () => { - expect(wrapper.instance().getContext().validating).toBe(false); + render( + + + , + schemaDefinition, + ); + + expect(contextSpy).toHaveBeenCalledWith( + expect.objectContaining({ validating: false }), + ); }); it('uses `modelTransform`s `validate` mode', () => { const transformedModel = { b: 1 }; const modelTransform = (mode: ModelTransformMode, model: UnknownObject) => mode === 'validate' ? transformedModel : model; - wrapper.setProps({ modelTransform }); - form.validate(); + render( + , + ); + const form = screen.getByRole('form'); + fireEvent.submit(form); expect(validator).toHaveBeenLastCalledWith(transformedModel); expect(onValidate).toHaveBeenLastCalledWith(transformedModel, null); }); }); describe('when submitted', () => { - // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount( - , - ); - - beforeEach(() => { - wrapper = mount( + it('calls `onSubmit` when validation succeeds', async () => { + render( + // FIXME: ValidatedForm is not a valid Component. , ); - }); - it('calls `onSubmit` when validation succeeds', async () => { - wrapper.find('form').simulate('submit'); + const form = screen.getByRole('form'); + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); expect(onSubmit).toHaveBeenCalledTimes(1); }); it('skips `onSubmit` when validation fails', async () => { + render( + // FIXME: ValidatedForm is not a valid Component. + , + ); + validator.mockImplementationOnce(() => { throw error; }); - wrapper.find('form').simulate('submit'); + + const form = screen.getByRole('form'); + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); expect(onSubmit).not.toBeCalled(); }); it('sets submitted to true, when form is submitted and validation succeeds', () => { - const instance = wrapper.instance(); - expect(instance.getContext().submitted).toBe(false); - wrapper.find('form').simulate('submit'); - expect(instance.getContext().submitted).toBe(true); + render( + // FIXME: ValidatedForm is not a valid Component. + + + , + schemaDefinition, + ); + const form = screen.getByRole('form'); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitted: false }), + ); + + fireEvent.submit(form); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitted: true }), + ); }); it('sets submitted to true, when form is submitted and validation fails', () => { + render( + // FIXME: ValidatedForm is not a valid Component. + + + , + schemaDefinition, + ); + validator.mockImplementationOnce(() => { throw error; }); - const instance = wrapper.instance(); - expect(instance.getContext().submitted).toBe(false); - wrapper.find('form').simulate('submit'); - expect(instance.getContext().submitted).toBe(true); + + const form = screen.getByRole('form'); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitted: false }), + ); + + fireEvent.submit(form); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitted: true }), + ); }); it('updates error state with async errors from `onSubmit`', async () => { + render( + // FIXME: ValidatedForm is not a valid Component. + + + , + schemaDefinition, + ); + onSubmit.mockImplementationOnce(() => Promise.reject(error)); - wrapper.find('form').simulate('submit'); + const form = screen.getByRole('form'); + + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); expect(onSubmit).toHaveBeenCalled(); - expect(wrapper.instance().getContext().error).toBe(error); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error }), + ); }); it('works if unmounts on submit', async () => { - onSubmit.mockImplementationOnce(() => wrapper.unmount()); - wrapper.find('form').simulate('submit'); + const { unmount } = render( + // FIXME: ValidatedForm is not a valid Component. + , + ); + const form = screen.getByRole('form'); + onSubmit.mockImplementationOnce(() => unmount()); + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); }); }); @@ -196,11 +383,18 @@ describe('ValidatedForm', () => { describe('in `onChange` mode', () => { it('validates', () => { // FIXME: ValidatedForm is not a valid Component. - const wrapper = mount( - , + render( + + + , ); - wrapper.instance().getContext().onChange('key', 'value'); - + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); expect(validator).toHaveBeenCalledTimes(1); }); }); @@ -208,183 +402,246 @@ describe('ValidatedForm', () => { describe('in `onSubmit` mode', () => { it('does not validate', () => { // FIXME: ValidatedForm is not a valid Component. - const wrapper = mount( - , + render( + + + , ); - wrapper.instance().getContext().onChange('key', 'value'); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); expect(validator).not.toHaveBeenCalled(); }); }); describe('in `onChangeAfterSubmit` mode', () => { - // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount( - , - ); - - beforeEach(() => { - wrapper = mount( + it('does not validates before submit', () => { + render( + // FIXME: ValidatedForm is not a valid Component. , + > + + , ); - }); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); - it('does not validates before submit', () => { - wrapper.instance().getContext().onChange('key', 'value'); expect(validator).not.toHaveBeenCalled(); }); it('validates after submit', async () => { - wrapper.find('form').simulate('submit'); + render( + // FIXME: ValidatedForm is not a valid Component. + + + , + ); + + const form = screen.getByRole('form'); + fireEvent.submit(form); await new Promise(resolve => process.nextTick(resolve)); validator.mockClear(); - wrapper.instance().getContext().onChange('key', 'value'); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value: 'test' } }); + expect(validator).toHaveBeenCalledTimes(1); }); }); }); describe('on reset', () => { - it('removes `error`', () => { - // FIXME: ValidatedForm is not a valid Component. - const wrapper = mount( - , + it('removes `error`', async () => { + const FormControls = () => { + const { formRef } = useForm(); + + return ( + <> + + + ); + }; + + render( + // FIXME: ValidatedForm is not a valid Component. + + + + , + schemaDefinition, ); validator.mockImplementationOnce(() => { - throw new Error(); + throw error; }); - wrapper.find('form').simulate('submit'); - expect(wrapper.instance().getContext().error).toBeTruthy(); - wrapper.instance().reset(); - expect(wrapper.instance().getContext().error).toBeNull(); + const form = screen.getByRole('form'); + const resetButton = screen.getByText('Reset'); + + fireEvent.submit(form); + await new Promise(resolve => process.nextTick(resolve)); + + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error }), + ); + + fireEvent.click(resetButton); + await new Promise(resolve => process.nextTick(resolve)); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error: null }), + ); }); }); describe('when props are changed', () => { const anotherModel = { x: 2 }; - describe('in `onChange` mode', () => { - // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount( - , - ); - - beforeEach(() => { - wrapper = mount( - , - ); - }); + // FIXME: ValidatedForm is not a valid Component. + const Component = (props: Record) => ( + + ); - it('does not revalidate arbitrarily', () => { - wrapper.setProps({ anything: 'anything' }); - expect(validator).not.toBeCalled(); - }); + it('does not revalidate arbitrarily', () => { + const { rerenderWithProps } = render(); + rerenderWithProps({ anything: 'anything' }); + expect(validator).not.toBeCalled(); + }); - it('revalidates if `model` changes', () => { - wrapper.setProps({ model: anotherModel }); - expect(validator).toHaveBeenCalledTimes(1); - }); + it('revalidates if `model` changes', () => { + const { rerenderWithProps } = render(); + rerenderWithProps({ model: anotherModel }); + expect(validator).toHaveBeenCalledTimes(1); + }); - it('revalidates if `validator` changes', () => { - wrapper.setProps({ validator: {} }); - expect(validator).toHaveBeenCalledTimes(1); - }); + it('revalidates if `validator` changes', () => { + const { rerenderWithProps } = render(); + rerenderWithProps({ validator: {} }); + expect(validator).toHaveBeenCalledTimes(1); + }); - it('revalidate if `schema` changes', () => { - wrapper.setProps({ schema: new SimpleSchema2Bridge(schema.schema) }); - expect(validator).toHaveBeenCalledTimes(1); - }); + it('revalidate if `schema` changes', () => { + const anotherSchema = new SimpleSchema2Bridge(schema.schema); + const { rerenderWithProps } = render(); + rerenderWithProps({ schema: anotherSchema }); + expect(validator).toHaveBeenCalledTimes(1); }); + }); - describe('in `onSubmit` mode', () => { - // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount( - , - ); + describe('in `onSubmit` mode', () => { + // FIXME: ValidatedForm is not a valid Component. + const Component = () => ( + + ); - beforeEach(() => { - wrapper = mount( - , - ); - }); + it('does not revalidate when `model` changes', () => { + const { rerenderWithProps } = render(, schemaDefinition); + rerenderWithProps({ model: {} }); + expect(validator).not.toBeCalled(); + }); - it('does not revalidate when `model` changes', () => { - wrapper.setProps({ model: {} }); - expect(validator).not.toBeCalled(); - }); + it('does not revalidate when validator `options` change', () => { + const { rerenderWithProps } = render(, schemaDefinition); + rerenderWithProps({ validator: {} }); + expect(validator).not.toBeCalled(); + }); - it('does not revalidate when validator `options` change', () => { - wrapper.setProps({ validator: {} }); - expect(validator).not.toBeCalled(); - }); + it('does not revalidate when `schema` changes', () => { + const anotherSchema = new SimpleSchema2Bridge(schema.schema); + const { rerenderWithProps } = render(, schemaDefinition); + rerenderWithProps({ schema: anotherSchema }); + expect(validator).not.toBeCalled(); + }); + }); - it('does not revalidate when `schema` changes', () => { - wrapper.setProps({ schema: new SimpleSchema2Bridge(schema.schema) }); - expect(validator).not.toBeCalled(); + describe('in any mode', () => { + it('reuses the validator between validations', () => { + render( + // FIXME: ValidatedForm is not a valid Component. + + + , + ); + ['1', '2', '3'].forEach(value => { + const form = screen.getByRole('form'); + const input = screen.getByLabelText('A'); + fireEvent.change(input, { target: { value } }); + fireEvent.submit(form); }); + + expect(validatorForSchema).toHaveBeenCalledTimes(1); }); - describe('in any mode', () => { - // FIXME: ValidatedForm is not a valid Component. - let wrapper = mount( + it('uses the new validator settings if `validator` changes', () => { + const validatorA = Symbol(); + const validatorB = Symbol(); + const { rerenderWithProps } = render( + // FIXME: ValidatedForm is not a valid Component. , ); - beforeEach(() => { - wrapper = mount( - , - ); - }); - - it('reuses the validator between validations', () => { - ['1', '2', '3'].forEach(value => { - wrapper.instance().getContext().onChange('key', value); - wrapper.find('form').simulate('submit'); - }); + rerenderWithProps({ validator: validatorA }); + expect(validatorForSchema).toHaveBeenCalledTimes(2); + expect(validatorForSchema).toHaveBeenNthCalledWith(2, validatorA); - expect(validatorForSchema).toHaveBeenCalledTimes(1); - }); - - it('uses the new validator settings if `validator` changes', () => { - const validatorA = Symbol(); - const validatorB = Symbol(); + rerenderWithProps({ validator: validatorB }); + expect(validatorForSchema).toHaveBeenCalledTimes(3); + expect(validatorForSchema).toHaveBeenNthCalledWith(3, validatorB); - wrapper.setProps({ validator: validatorA }); - expect(validatorForSchema).toHaveBeenCalledTimes(2); - expect(validatorForSchema).toHaveBeenNthCalledWith(2, validatorA); + rerenderWithProps({ validator: validatorA }); + expect(validatorForSchema).toHaveBeenCalledTimes(4); + expect(validatorForSchema).toHaveBeenNthCalledWith(4, validatorA); + }); - wrapper.setProps({ validator: validatorB }); - expect(validatorForSchema).toHaveBeenCalledTimes(3); - expect(validatorForSchema).toHaveBeenNthCalledWith(3, validatorB); + it('uses the new validator if `schema` changes', () => { + const alternativeValidator = jest.fn(); + const alternativeSchema = new SimpleSchema2Bridge(schema.schema); + jest + .spyOn(alternativeSchema, 'getValidator') + .mockImplementation(() => alternativeValidator); + const { rerenderWithProps } = render( + // FIXME: ValidatedForm is not a valid Component. + + + , + ); - wrapper.setProps({ validator: validatorA }); - expect(validatorForSchema).toHaveBeenCalledTimes(4); - expect(validatorForSchema).toHaveBeenNthCalledWith(4, validatorA); + rerenderWithProps({ + schema: alternativeSchema, }); + const form = screen.getByRole('form'); + fireEvent.submit(form); - it('uses the new validator if `schema` changes', () => { - const alternativeValidator = jest.fn(); - const alternativeSchema = new SimpleSchema2Bridge(schema.schema); - jest - .spyOn(alternativeSchema, 'getValidator') - .mockImplementation(() => alternativeValidator); - - wrapper.setProps({ schema: alternativeSchema }); - wrapper.find('form').simulate('submit'); - - expect(validator).not.toBeCalled(); - expect(alternativeValidator).toHaveBeenCalledTimes(1); - }); + expect(validator).not.toBeCalled(); + expect(alternativeValidator).toHaveBeenCalledTimes(1); }); }); @@ -445,14 +702,20 @@ describe('ValidatedForm', () => { it.each(cases.map(flatPair4))('works for %p/%p/%p/%p', async (...modes) => { const [hasError, validatorMode, onValidateMode, onSubmitMode] = modes; - // FIXME: ValidatedForm is not a valid Component. - const wrapper = mount( + + render( + // FIXME: ValidatedForm is not a valid Component. , + > + + , + schemaDefinition, ); const asyncSubmission = onSubmitMode.includes('async'); @@ -471,13 +734,18 @@ describe('ValidatedForm', () => { onValidate.mockImplementationOnce(variantGroups[1][onValidateMode]); onSubmit.mockImplementationOnce(variantGroups[2][onSubmitMode]); - const result = wrapper.instance().submit(); + const form = screen.getByRole('form'); + fireEvent.submit(form); expect(validator).toHaveBeenCalledTimes(run); if (asyncValidation) { - expect(wrapper.instance().getContext().validating).toBe(true); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ validating: true }), + ); await new Promise(resolve => process.nextTick(resolve)); - expect(wrapper.instance().getContext().validating).toBe(false); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ validating: false }), + ); } await new Promise(resolve => process.nextTick(resolve)); @@ -486,27 +754,36 @@ describe('ValidatedForm', () => { if (hasValidationError) { expect(onSubmit).not.toHaveBeenCalled(); - expect(wrapper.instance().getContext().error).toBe(error); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error }), + ); } else { expect(onSubmit).toHaveBeenCalledTimes(run); - expect(wrapper.instance().getContext().error).toBe(null); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error: null }), + ); if (asyncSubmission) { - expect(wrapper.instance().getContext().submitting).toBe(true); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitting: true }), + ); await new Promise(resolve => setTimeout(resolve)); - expect(wrapper.instance().getContext().submitting).toBe(false); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ submitting: false }), + ); } } await new Promise(resolve => setTimeout(resolve)); if (hasSubmissionError) { - expect(wrapper.instance().getContext().error).toBe(error); - await expect(result).rejects.toEqual(error); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error }), + ); } else { - expect(wrapper.instance().getContext().error).toBe(null); - const submissionResult = asyncSubmission ? 'ok' : undefined; - await expect(result).resolves.toEqual(submissionResult); + expect(contextSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ error: null }), + ); } } }); diff --git a/packages/uniforms/__tests__/useField.tsx b/packages/uniforms/__tests__/useField.tsx index 545a9217a..ef0fa7b6a 100644 --- a/packages/uniforms/__tests__/useField.tsx +++ b/packages/uniforms/__tests__/useField.tsx @@ -1,8 +1,9 @@ +import { fireEvent, screen } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { AutoForm, BaseForm, connectField, useField } from 'uniforms'; import { JSONSchemaBridge } from 'uniforms-bridge-json-schema'; -import mount from './_mount'; +import { render } from '../__suites__'; describe('useField', () => { const bridge = new JSONSchemaBridge( @@ -44,41 +45,36 @@ describe('useField', () => { }); it('applies default value', () => { - const wrapper = mount( + render( , ); + const input = screen.getByRole('textbox'); - expect(wrapper.find('input').prop('value')).toBe(4); - - expect( - wrapper - .find('input') - .simulate('change', { target: { value: undefined } }), - ).toBeTruthy(); + expect(input).toHaveAttribute('value', '4'); + fireEvent.change(input, { target: { value: null } }); + expect(input).toHaveAttribute('value', ''); }); it('does not apply default value after first change', () => { - const wrapper = mount( + render( , ); - expect( - wrapper - .find('input') - .simulate('change', { target: { value: undefined } }), - ).toBeTruthy(); + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: null } }); - expect(wrapper.find('input').prop('value')).toBe(''); + expect(input).toHaveAttribute('value', ''); }); }); describe('when called with `absoluteName`', () => { it('works on top-level', () => { - mount( + render( @@ -88,7 +84,7 @@ describe('useField', () => { }); it('works nested', () => { - mount( + render(