From 6d65e024c7af7f8b90eb57f505b816d83867bb04 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Fri, 21 Jun 2024 12:08:29 +0200 Subject: [PATCH] Migrated `DateField` tests to @testing-library/react (#1340) --- .../uniforms-antd/__tests__/DateField.tsx | 195 ++++--------- packages/uniforms-antd/__tests__/index.ts | 3 + packages/uniforms-mui/__tests__/DateField.tsx | 45 +-- packages/uniforms/__suites__/DateField.tsx | 258 ++++++++++++++---- 4 files changed, 287 insertions(+), 214 deletions(-) diff --git a/packages/uniforms-antd/__tests__/DateField.tsx b/packages/uniforms-antd/__tests__/DateField.tsx index 28abf3d5e..94f2aef74 100644 --- a/packages/uniforms-antd/__tests__/DateField.tsx +++ b/packages/uniforms-antd/__tests__/DateField.tsx @@ -1,149 +1,74 @@ -import DatePicker from 'antd/lib/date-picker'; +import { screen } from '@testing-library/react'; +import userEvent, { + PointerEventsCheckLevel, +} from '@testing-library/user-event'; import moment from 'moment'; import React from 'react'; import { DateField } from 'uniforms-antd'; - -import createContext from './_createContext'; -import mount from './_mount'; - -test(' - renders an input', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); -}); - -test(' - default props override', () => { - const pickerProps = { showTime: false, style: {} }; - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker).props()).toEqual( - expect.objectContaining(pickerProps), - ); +import { renderWithZod } from 'uniforms/__suites__'; +import { z } from 'zod'; + +const getClosestInput = (text: string) => + screen.getByText(text).closest('.ant-row')?.querySelector('input'); + +test(' - default props override', async () => { + const pickerProps = { showTime: false, style: { background: 'red' } }; + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + }); + const body = screen.getByText('X').closest('body'); + const input = body?.querySelector('.ant-picker'); + expect(input).toBeInTheDocument(); + expect(input).toHaveStyle('background: red'); + + await userEvent.click(input!); + expect(body?.querySelector('.ant-picker-time-panel')).not.toBeInTheDocument(); }); -test(' - renders a input with correct id (inherited)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('id')).toBeTruthy(); -}); - -test(' - renders a input with correct id (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('id')).toBe('y'); -}); - -test(' - renders a input with correct name', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('name')).toBe('x'); -}); - -test(' - renders an input with correct disabled state', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('disabled')).toBe(true); -}); - -test(' - renders an input with correct readOnly state', () => { +test(' - renders a input which correctly reacts on change (empty)', async () => { const onChange = jest.fn(); - - const now = moment(); - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Date } }, { onChange }), - ); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - // @ts-expect-error - expect(wrapper.find(DatePicker).prop('onChange')(now)).toBeFalsy(); - expect(onChange).not.toHaveBeenCalled(); -}); - -test(' - renders a input with correct label (specified)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find('label')).toHaveLength(1); - expect(wrapper.find('label').text()).toBe('DateFieldLabel'); -}); - -test(' - renders a input with correct value (default)', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('value')).toBe(undefined); -}); - -test(' - renders a input with correct value (model)', () => { - const now = moment(); - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Date } }, { model: { x: now } }), - ); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('value')).toEqual(now); -}); - -test(' - renders a input with correct value (specified)', () => { - const now = moment(); - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - expect(wrapper.find(DatePicker).prop('value')).toEqual(now); + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + + const input = getClosestInput('X'); + expect(input).toBeInTheDocument(); + await userEvent.click(input!); + const clear = input?.parentElement?.querySelector('.ant-picker-clear'); + await userEvent.click(clear!); + expect(onChange).toHaveBeenLastCalledWith('x', undefined); }); -test(' - renders a input which correctly reacts on change', () => { +test(' - renders a input which correctly reacts on change', async () => { const onChange = jest.fn(); - - const now = moment(); - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Date } }, { onChange }), - ); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - // @ts-expect-error - expect(wrapper.find(DatePicker).prop('onChange')(now)).toBeFalsy(); + const now = moment('2024-01-01 12:00:00'); + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + + const input = getClosestInput('X'); + expect(input).toBeInTheDocument(); + await userEvent.click(input!); + await userEvent.type(input!, now.format('YYYY-MM-DD HH:mm:ss')); + const ok = screen.getByText('Ok'); + await userEvent.click(ok, { + pointerEventsCheck: PointerEventsCheckLevel.Never, + }); expect(onChange).toHaveBeenLastCalledWith('x', now.toDate()); }); -test(' - renders a input which correctly reacts on change (empty)', () => { - const onChange = jest.fn(); - - const element = ; - const wrapper = mount( - element, - createContext({ x: { type: Date } }, { onChange }), - ); - - expect(wrapper.find(DatePicker)).toHaveLength(1); - // @ts-expect-error - expect(wrapper.find(DatePicker).prop('onChange')(undefined)).toBeFalsy(); - expect(onChange).toHaveBeenLastCalledWith('x', undefined); -}); - test(' - renders a wrapper with unknown props', () => { - const element = ; - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(DatePicker).prop('data-x')).toBe('x'); - expect(wrapper.find(DatePicker).prop('data-y')).toBe('y'); - expect(wrapper.find(DatePicker).prop('data-z')).toBe('z'); + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + }); + const input = getClosestInput('X'); + expect(input?.getAttribute('data-x')).toBe('x'); + expect(input?.getAttribute('data-y')).toBe('y'); + expect(input?.getAttribute('data-z')).toBe('z'); }); diff --git a/packages/uniforms-antd/__tests__/index.ts b/packages/uniforms-antd/__tests__/index.ts index b7db67e0c..92ea2adb7 100644 --- a/packages/uniforms-antd/__tests__/index.ts +++ b/packages/uniforms-antd/__tests__/index.ts @@ -64,6 +64,9 @@ describe('@RTL AntD', () => { suites.testSelectField(theme.SelectField, { theme: 'antd' }); suites.testTextField(theme.TextField); suites.testValidatedForm(theme.ValidatedForm); + suites.testDateField(theme.DateField, { + theme: 'antd', + }); suites.testValidatedQuickForm(theme.ValidatedQuickForm); suites.testWrapField(theme.wrapField, { helpPropsName: 'help', diff --git a/packages/uniforms-mui/__tests__/DateField.tsx b/packages/uniforms-mui/__tests__/DateField.tsx index a22294fde..de92f34ee 100644 --- a/packages/uniforms-mui/__tests__/DateField.tsx +++ b/packages/uniforms-mui/__tests__/DateField.tsx @@ -1,31 +1,34 @@ -import FormHelperText from '@mui/material/FormHelperText'; +import { screen } from '@testing-library/react'; import React from 'react'; import { DateField } from 'uniforms-mui'; - -import createContext from './_createContext'; -import mount from './_mount'; +import { renderWithZod } from 'uniforms/__suites__'; +import { z } from 'zod'; test(' - renders a Input with correct error text (specified)', () => { const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(FormHelperText).text()).toBe('Error'); + renderWithZod({ + element: ( + + ), + schema: z.object({ x: z.date() }), + }); + expect(screen.getByText('Error')).toBeInTheDocument(); }); test(' - renders a Input with correct error text (showInlineError=false)', () => { const error = new Error(); - const element = ( - - ); - const wrapper = mount(element, createContext({ x: { type: Date } })); - - expect(wrapper.find(FormHelperText)).toHaveLength(0); + renderWithZod({ + element: ( + + ), + schema: z.object({ x: z.date() }), + }); + expect( + screen.getByText('X').nextElementSibling?.classList.contains('Mui-error'), + ).toBe(true); }); diff --git a/packages/uniforms/__suites__/DateField.tsx b/packages/uniforms/__suites__/DateField.tsx index 65e597f6a..6bc28b9c1 100644 --- a/packages/uniforms/__suites__/DateField.tsx +++ b/packages/uniforms/__suites__/DateField.tsx @@ -1,106 +1,248 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import moment from 'moment'; import React, { ComponentType } from 'react'; import z from 'zod'; import { renderWithZod } from './render-zod'; - -export function testDateField(DateField: ComponentType) { - test(' - handles "date" type correctly (empty)', async () => { - const date = '2021-01-01'; - const onChange = jest.fn(); +import { skipTestIf } from './skipTestIf'; + +const getClosestInputWithTestId = (testId: string) => + screen.getByTestId(testId).closest('body')?.querySelector('input'); + +const getDateString = (date: Date, useISOFormat: boolean) => + useISOFormat + ? date.toISOString().slice(0, 16) + : moment(date).format('YYYY-MM-DD HH:mm:ss'); + +export function testDateField( + DateField: ComponentType, + { + theme, + }: { + theme?: 'antd'; + } = {}, +) { + const useISOFormat = theme !== 'antd'; + + test(' - renders a input with correct id (inherited)', () => { renderWithZod({ - element: , - model: { x: new Date(`${date}Z`) }, - onChange, + element: , schema: z.object({ x: z.date() }), }); - - const input = screen.getByLabelText(/^X/); - expect(input).toHaveValue(date); - - await userEvent.clear(input); - expect(onChange).toHaveBeenLastCalledWith('x', undefined); + expect(getClosestInputWithTestId('y')?.id).toBeTruthy(); }); - test(' - handles "date" type correctly (fill)', async () => { - const date = '2022-02-02'; - const onChange = jest.fn(); + test(' - renders a input with correct id (specified)', () => { renderWithZod({ - element: , - onChange, + element: , schema: z.object({ x: z.date() }), }); - - const input = screen.getByLabelText(/^X/); - expect(input).toHaveValue(''); - - await userEvent.type(input, date); - expect(onChange).toHaveBeenLastCalledWith('x', new Date(`${date}Z`)); + expect(getClosestInputWithTestId('z')?.id).toBe('y'); }); - test(' - handles "date" type correctly (overflow)', async () => { - const date = '12345-06-07'; - const onChange = jest.fn(); + test(' - renders a input with correct name', () => { renderWithZod({ - element: , - onChange, + element: , schema: z.object({ x: z.date() }), }); + expect(getClosestInputWithTestId('z')?.name).toBe('x'); + }); - const input = screen.getByLabelText(/^X/); - expect(input).toHaveValue(''); - - await userEvent.type(input, date); - expect(onChange).not.toHaveBeenCalled(); + test(' - renders an input with correct disabled state', () => { + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + }); + expect(getClosestInputWithTestId('z')?.disabled).toBe(true); }); - test(' - handles "datetime-local" type correctly (empty)', async () => { - const date = '2021-01-01T11:11'; - const onChange = jest.fn(); + test(' - renders a input with correct label (specified)', () => { renderWithZod({ - element: , - model: { x: new Date(`${date}Z`) }, - onChange, + element: , schema: z.object({ x: z.date() }), }); + expect(screen.getByText('DateFieldLabel')).toBeInTheDocument(); + }); - const input = screen.getByLabelText(/^X/); - expect(input).toHaveValue(date); + test(' - renders a input with correct value (default)', () => { + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + }); + const input = getClosestInputWithTestId('z'); + expect(input).toBeInTheDocument(); + expect(input?.value).toBe(''); + }); - await userEvent.clear(input); - expect(onChange).toHaveBeenLastCalledWith('x', undefined); + test(' - renders a input with correct value (specified)', () => { + const now = new Date('2024-01-01 12:00:00'); + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + }); + const input = getClosestInputWithTestId('z'); + expect(input).toBeInTheDocument(); + expect(input!.value).toBe(getDateString(now, useISOFormat)); }); - test(' - handles "datetime-local" type correctly (fill)', async () => { - const date = '2022-02-02T22:22'; + test(' - renders an input with correct readOnly state', async () => { const onChange = jest.fn(); renderWithZod({ - element: , + element: , onChange, schema: z.object({ x: z.date() }), }); + const now = moment(); + const input = getClosestInputWithTestId('z'); + expect(input).toBeInTheDocument(); + await userEvent.click(input!); + await userEvent.type(input!, `${now.format('YYYY-MM-DD HH:mm:ss')}{Enter}`); + expect(onChange).not.toHaveBeenCalled(); + }); - const input = screen.getByLabelText(/^X/); - expect(input).toHaveValue(''); - - await userEvent.type(input, date); - expect(onChange).toHaveBeenLastCalledWith('x', new Date(`${date}Z`)); + test(' - renders a input with correct value (model)', () => { + const now = new Date(); + renderWithZod({ + element: , + schema: z.object({ x: z.date() }), + model: { x: now }, + }); + const input = getClosestInputWithTestId('z'); + expect(input).toBeInTheDocument(); + expect(input?.value).toBe(getDateString(now, useISOFormat)); }); - test(' - handles "datetime-local" type correctly (overflow)', async () => { - const date = '12345-06-07T08:09'; + skipTestIf(theme === 'antd')( + ' - renders a input which correctly reacts on change', + async () => { + const onChange = jest.fn(); + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + const input = getClosestInputWithTestId('z'); + expect(input).toBeInTheDocument(); + const now = new Date('2024-01-01 12:00:00'); + await userEvent.click(input!); + await userEvent.type( + input!, + `${now.toISOString().slice(0, 16).replace('T', ' ')}{Enter}`, + ); + expect(onChange).toHaveBeenLastCalledWith('x', now); + }, + ); + + skipTestIf(theme === 'antd')( + ' - handles "date" type correctly (empty)', + async () => { + const date = '2021-01-01 11:00:00'; + const onChange = jest.fn(); + renderWithZod({ + element: , + model: { x: new Date(date) }, + onChange, + schema: z.object({ x: z.date() }), + }); + const input = getClosestInputWithTestId('x'); + expect(input).toHaveValue(date.slice(0, 10)); + await userEvent.clear(input!); + expect(onChange).toHaveBeenLastCalledWith('x', undefined); + }, + ); + + skipTestIf(theme === 'antd')( + ' - handles "date" type correctly (fill)', + async () => { + const date = '2022-02-02'; + const onChange = jest.fn(); + const extraProps = + theme === 'antd' ? { showTime: false } : { type: 'date' }; + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + const input = getClosestInputWithTestId('x'); + expect(input).toHaveValue(''); + await userEvent.click(input!); + await userEvent.type(input!, `${date}{Enter}`); + expect(onChange).toHaveBeenLastCalledWith('x', new Date(`${date}Z`)); + }, + ); + + test(' - handles "date" type correctly (overflow)', async () => { + const date = '12345-06-07'; const onChange = jest.fn(); renderWithZod({ - element: , + element: , onChange, schema: z.object({ x: z.date() }), }); - const input = screen.getByLabelText(/^X/); + const input = getClosestInputWithTestId('x'); expect(input).toHaveValue(''); - await userEvent.type(input, date); + await userEvent.type(input!, date); expect(onChange).not.toHaveBeenCalled(); }); + + skipTestIf(theme === 'antd')( + ' - handles "datetime-local" type correctly (empty)', + async () => { + const date = '2021-01-01T11:11'; + const onChange = jest.fn(); + renderWithZod({ + element: , + model: { x: new Date(`${date}Z`) }, + onChange, + schema: z.object({ x: z.date() }), + }); + + const input = screen.getByLabelText(/^X/); + expect(input).toHaveValue(date); + + await userEvent.clear(input); + expect(onChange).toHaveBeenLastCalledWith('x', undefined); + }, + ); + + skipTestIf(theme === 'antd')( + ' - handles "datetime-local" type correctly (fill)', + async () => { + const date = '2022-02-02T22:22'; + const onChange = jest.fn(); + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + + const input = screen.getByLabelText(/^X/); + expect(input).toHaveValue(''); + + await userEvent.type(input, date); + expect(onChange).toHaveBeenLastCalledWith('x', new Date(`${date}Z`)); + }, + ); + + skipTestIf(theme === 'antd')( + ' - handles "datetime-local" type correctly (overflow)', + async () => { + const date = '12345-06-07T08:09'; + const onChange = jest.fn(); + renderWithZod({ + element: , + onChange, + schema: z.object({ x: z.date() }), + }); + + const input = screen.getByLabelText(/^X/); + expect(input).toHaveValue(''); + + await userEvent.type(input, date); + expect(onChange).not.toHaveBeenCalled(); + }, + ); }