From 6d6cbfe6b38b9aed0f7a20ba135e21c888a26c37 Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Mon, 7 Mar 2022 21:18:12 -0800 Subject: [PATCH 1/6] convert TextField to use testing library --- .../components/TextField/TextField.test.tsx | 567 ++++++++---------- 1 file changed, 252 insertions(+), 315 deletions(-) diff --git a/packages/react/src/components/TextField/TextField.test.tsx b/packages/react/src/components/TextField/TextField.test.tsx index 1227f018dd868f..db67382598f661 100644 --- a/packages/react/src/components/TextField/TextField.test.tsx +++ b/packages/react/src/components/TextField/TextField.test.tsx @@ -1,20 +1,16 @@ import * as React from 'react'; -import * as ReactTestUtils from 'react-dom/test-utils'; -import { create } from '@fluentui/utilities/lib/test'; -import { mount, ReactWrapper } from 'enzyme'; +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { renderToStaticMarkup } from 'react-dom/server'; import * as path from 'path'; import { resetIds, setWarningCallback, resetControlledWarnings } from '../../Utilities'; -import { mockEvent, flushPromises } from '../../common/testUtilities'; -import { safeMount } from '@fluentui/test-utilities'; +import { flushPromises } from '../../common/testUtilities'; import { TextField } from './TextField'; -import { TextFieldBase } from './TextField.base'; import { isConformant } from '../../common/isConformant'; import type { IRefObject } from '../../Utilities'; -import type { ITextFieldState } from './TextField.base'; -import type { ITextFieldProps, ITextFieldStyles, ITextField } from './TextField.types'; +import type { ITextFieldStyles, ITextField } from './TextField.types'; /** * The currently rendered ITextField. @@ -25,8 +21,7 @@ let textField: ITextField | undefined; const textFieldRef: IRefObject = (ref: ITextField | null) => { textField = ref!; }; -/** Wrapper of the TextField currently being tested */ -let wrapper: ReactWrapper | undefined; + const noOp = () => undefined; function sharedBeforeEach() { @@ -35,10 +30,6 @@ function sharedBeforeEach() { } function sharedAfterEach() { - if (wrapper) { - wrapper.unmount(); - wrapper = undefined; - } textField = undefined; // Do this after unmounting the wrapper to make sure any timers cleaned up on unmount are @@ -48,37 +39,37 @@ function sharedAfterEach() { } } +function getInput(): HTMLInputElement { + return screen.getByRole('textbox') as HTMLInputElement; +} + describe('TextField snapshots', () => { beforeEach(sharedBeforeEach); it('renders correctly', () => { const className = 'testClassName'; const inputClassName = 'testInputClassName'; - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders multiline non resizable correctly', () => { - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders multiline resizable correctly', () => { - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders multiline with placeholder correctly', () => { - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); it('renders multiline correctly with props affecting styling', () => { - const component = create( + const { container } = render( { suffix="test suffix" />, ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); it('renders multiline correctly with errorMessage', () => { - const component = create( + const { container } = render( { suffix="test suffix" />, ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); it('should respect user component and subcomponent styling', () => { @@ -114,7 +103,7 @@ describe('TextField snapshots', () => { }, }, }; - const component = create( + const { container } = render( { styles={styles} />, ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); it('renders with reveal password button', () => { - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const { container } = render(); + expect(container).toMatchSnapshot(); }); }); // end snapshots @@ -153,45 +140,38 @@ describe('TextField rendering values from props', () => { it('can render a value', () => { const testText = 'initial value'; // use the noOp change handler because onChange is required when value is specified - wrapper = mount(); - const input = wrapper.getDOMNode().querySelector('input'); - expect(input!.value).toEqual(testText); - expect(textField!.value).toEqual(testText); + render(); + expect(getInput().value).toEqual(testText); }); it('can render a value as a textarea', () => { const testText = 'This\nIs\nMultiline\nText\n'; - wrapper = mount(); - const textarea = wrapper.getDOMNode().querySelector('textarea'); - expect(textarea!.value).toEqual(testText); - expect(textField!.value).toEqual(testText); + render(); + expect(getInput().value).toEqual(testText); }); it('should render a value of 0 when given the number 0', () => { - wrapper = mount(); - expect(wrapper.getDOMNode().querySelector('input')!.value).toEqual('0'); - expect(textField!.value).toEqual('0'); + render(); + expect(getInput().value).toEqual('0'); }); it('can render a default value', () => { const testText = 'initial value'; - wrapper = mount(); - const input = wrapper.getDOMNode().querySelector('input'); - expect(input!.value).toEqual(testText); + render(); + expect(getInput().value).toEqual(testText); expect(textField!.value).toEqual(testText); }); it('can render a default value as a textarea', () => { const testText = 'This\nIs\nMultiline\nText\n'; - wrapper = mount(); - const textarea = wrapper.getDOMNode().querySelector('textarea'); - expect(textarea!.value).toEqual(testText); + render(); + expect(getInput().value).toEqual(testText); expect(textField!.value).toEqual(testText); }); it('should render a default value of 0 when given the number 0', () => { - wrapper = mount(); - expect(wrapper.getDOMNode().querySelector('input')!.value).toEqual('0'); + render(); + expect(getInput().value).toEqual('0'); expect(textField!.value).toEqual('0'); }); }); // end rendering values from props @@ -199,132 +179,99 @@ describe('TextField rendering values from props', () => { describe('TextField basic props', () => { beforeEach(sharedBeforeEach); afterEach(sharedAfterEach); - it('can render label', () => { const exampleLabel = 'this is label'; - - wrapper = mount(); - - expect(wrapper.getDOMNode().querySelector('label')!.textContent).toEqual(exampleLabel); + const { container } = render(); + expect(container.querySelector('label')!.textContent).toEqual(exampleLabel); }); - it('should associate the label and input box', () => { - wrapper = mount(); - - const inputDOM = wrapper.getDOMNode().querySelector('input'); - const labelDOM = wrapper.getDOMNode().querySelector('label'); - + const { container } = render(); + const inputDOM = getInput(); + const labelDOM = container.querySelector('label')!; // Assert the input ID and label FOR attribute are the same. expect(inputDOM!.id).toBeTruthy(); expect(inputDOM!.id).toEqual(labelDOM!.htmlFor); }); - it('should render prefix', () => { const examplePrefix = 'this is a prefix'; - - wrapper = mount(); - - const prefixDOM: Element = wrapper.getDOMNode().getElementsByClassName('ms-TextField-prefix')[0]; + const { container } = render(); + const prefixDOM: Element = container.getElementsByClassName('ms-TextField-prefix')[0]; expect(prefixDOM.textContent).toEqual(examplePrefix); }); - it('should render suffix', () => { const exampleSuffix = 'this is a suffix'; - - wrapper = mount(); - - const suffixDOM: Element = wrapper.getDOMNode().getElementsByClassName('ms-TextField-suffix')[0]; + const { container } = render(); + const suffixDOM: Element = container.getElementsByClassName('ms-TextField-suffix')[0]; expect(suffixDOM.textContent).toEqual(exampleSuffix); }); - it('should render both prefix and suffix', () => { const examplePrefix = 'this is a prefix'; const exampleSuffix = 'this is a suffix'; - - wrapper = mount(); - + const { container } = render(); // Assert on the prefix and suffix - const prefixDOM: Element = wrapper.getDOMNode().getElementsByClassName('ms-TextField-prefix')[0]; - const suffixDOM: Element = wrapper.getDOMNode().getElementsByClassName('ms-TextField-suffix')[0]; + const prefixDOM: Element = container.getElementsByClassName('ms-TextField-prefix')[0]; + const suffixDOM: Element = container.getElementsByClassName('ms-TextField-suffix')[0]; expect(prefixDOM.textContent).toEqual(examplePrefix); expect(suffixDOM.textContent).toEqual(exampleSuffix); }); - it('should not render reveal password button by default', () => { - wrapper = mount(); - expect(wrapper.find('.ms-TextField-reveal')).toHaveLength(0); + const { container } = render(); + expect(container.querySelectorAll('.ms-TextField-reveal')).toHaveLength(0); }); - it('should render reveal password button if canRevealPassword=true', () => { - wrapper = mount(); - expect(wrapper.find('.ms-TextField-reveal')).toHaveLength(1); + const { container } = render(); + expect(container.querySelectorAll('.ms-TextField-reveal')).toHaveLength(1); }); - it('ignores canRevealPassword if type is unspecified', () => { - wrapper = mount(); - expect(wrapper.find('.ms-TextField-reveal')).toHaveLength(0); + const { container } = render(); + expect(container.querySelectorAll('.ms-TextField-reveal')).toHaveLength(0); }); - it('ignores canRevealPassword if type is not password', () => { - wrapper = mount(); - expect(wrapper.find('.ms-TextField-reveal')).toHaveLength(0); + const { container } = render(); + expect(container.querySelectorAll('.ms-TextField-reveal')).toHaveLength(0); }); - it('should toggle reveal password on reveal button click', () => { - wrapper = mount(); - const input = wrapper.find('input'); - const reveal = wrapper.find('.ms-TextField-reveal'); - - input.simulate('input', mockEvent('Password123$')); - expect((input.getDOMNode() as HTMLInputElement).type).toEqual('password'); - reveal.simulate('click'); - expect((input.getDOMNode() as HTMLInputElement).type).toEqual('text'); - reveal.simulate('click'); - expect((input.getDOMNode() as HTMLInputElement).type).toEqual('password'); + const { container, getByRole } = render(); + const input = container.querySelector('.ms-TextField-field')! as HTMLInputElement; + const reveal = getByRole('button'); + + userEvent.tab(); + userEvent.type(input, 'Password123$'); + expect(input.type).toEqual('password'); + userEvent.click(reveal); + expect(input.type).toEqual('text'); + userEvent.click(reveal); + expect(input.type).toEqual('password'); }); - it('should not give an aria-labelledby if no label is provided', () => { - wrapper = mount(); - - expect(wrapper.getDOMNode().getAttribute('aria-labelledby')).toBeNull(); + render(); + expect(getInput().getAttribute('aria-labelledby')).toBeNull(); }); - it('should use explicitly defined aria-labelledby prop if one is given', () => { const sampleAriaLabelledby = 'sample for aria-labelledby'; - wrapper = mount(); - - const inputDOM = wrapper.getDOMNode().querySelector('input'); - expect(inputDOM!.getAttribute('aria-labelledby')).toEqual(sampleAriaLabelledby); + render(); + expect(getInput().getAttribute('aria-labelledby')).toEqual(sampleAriaLabelledby); }); - it('should render a disabled input element', () => { - wrapper = mount(); - - expect(wrapper.getDOMNode().querySelector('input')!.disabled).toEqual(true); + render(); + expect(getInput().disabled).toEqual(true); }); - it('should render a readonly input element', () => { - wrapper = mount(); - - expect(wrapper.getDOMNode().querySelector('input')!.readOnly).toEqual(true); + render(); + expect(getInput().readOnly).toEqual(true); }); - it('can render description text', () => { const testDescription = 'A custom description'; - wrapper = mount(); - - const description = wrapper.getDOMNode().querySelector('.ms-TextField-description'); + const { container } = render(); + const description = container.querySelector('.ms-TextField-description'); expect(description).toBeTruthy(); expect(description!.textContent).toEqual(testDescription); }); - it('can render a static custom description without description text', () => { const onRenderDescription = jest.fn(() => { return A custom description; }); - - wrapper = mount(); - + render(); expect(onRenderDescription).toHaveBeenCalledTimes(1); }); }); // end basic props @@ -361,17 +308,17 @@ describe('TextField with error message', () => { } } - it('should not validate on mount when validateOnLoad is false', () => { + it('should not validate on render when validateOnLoad is false', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount(); + render(); expect(validator).toHaveBeenCalledTimes(0); }); - it('should validate on mount when validateOnLoad is true', () => { + it('should validate on render when validateOnLoad is true', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount(); + render(); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); }); @@ -379,33 +326,33 @@ describe('TextField with error message', () => { it('should render error message when onGetErrorMessage returns a string', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount(); + const { container } = render(); - wrapper.find('input').simulate('input', mockEvent('also invalid')); + userEvent.type(getInput(), 'also invalid'); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper.getDOMNode(), errorMessage); + assertErrorMessage(container, errorMessage); }); it('should render error message when onGetErrorMessage returns a JSX.Element', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessageJSX : '')); - wrapper = mount(); + const { container } = render(); - wrapper.find('input').simulate('input', mockEvent('also invalid')); + userEvent.type(getInput(), 'also invalid'); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper.getDOMNode(), errorMessageJSX); + assertErrorMessage(container, errorMessageJSX); }); it('should render error message when onGetErrorMessage returns a Promise', () => { const validator = jest.fn((value: string) => Promise.resolve(value.length > 3 ? errorMessage : '')); - wrapper = mount(); + const { container } = render(); - wrapper.find('input').simulate('input', mockEvent('also invalid')); + userEvent.type(getInput(), 'also invalid'); // Extra rounds of running everything to account for the debounced validator and the promise... jest.runAllTimers(); @@ -413,64 +360,64 @@ describe('TextField with error message', () => { jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper!.getDOMNode(), errorMessage); + assertErrorMessage(container, errorMessage); }); }); it('should render error message when onGetErrorMessage returns a Promise', () => { const validator = jest.fn((value: string) => Promise.resolve(value.length > 3 ? errorMessageJSX : '')); - wrapper = mount(); + const { container } = render(); - wrapper.find('input').simulate('input', mockEvent('also invalid')); + userEvent.type(getInput(), 'also invalid'); jest.runAllTimers(); return flushPromises().then(() => { jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper!.getDOMNode(), errorMessageJSX); + assertErrorMessage(container, errorMessageJSX); }); }); it('should render error message on first render when onGetErrorMessage returns a string', () => { const validator = jest.fn(() => errorMessage); - wrapper = mount(); + const { container } = render(); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper.getDOMNode(), errorMessage); + assertErrorMessage(container, errorMessage); }); it('should render error message on first render when onGetErrorMessage returns a Promise', () => { const validator = jest.fn(() => Promise.resolve(errorMessage)); - wrapper = mount(); + const { container } = render(); jest.runAllTimers(); return flushPromises().then(() => { jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper!.getDOMNode(), errorMessage); + assertErrorMessage(container, errorMessage); }); }); it('should not render error message when onGetErrorMessage return an empty string', () => { const validator = jest.fn(() => ''); - wrapper = mount(); + const { container } = render(); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(1); - assertErrorMessage(wrapper.getDOMNode(), /* exist */ false); + assertErrorMessage(container, /* exist */ false); }); it('should not render error message when no value is provided', () => { let actualValue: string | undefined = undefined; - wrapper = mount( (actualValue = value)} />); + const { container } = render( (actualValue = value)} />); jest.runAllTimers(); - assertErrorMessage(wrapper.getDOMNode(), /* exist */ false); + assertErrorMessage(container, /* exist */ false); expect(actualValue).toEqual(''); }); @@ -479,21 +426,23 @@ describe('TextField with error message', () => { return value.length > 3 ? errorMessage : ''; } - wrapper = mount(); + const { container, rerender } = render( + , + ); jest.runAllTimers(); - assertErrorMessage(wrapper.getDOMNode(), errorMessage); + assertErrorMessage(container, errorMessage); - wrapper.setProps({ value: '' }); + rerender(); jest.runAllTimers(); - assertErrorMessage(wrapper.getDOMNode(), /* exist */ false); + assertErrorMessage(container, /* exist */ false); }); it('should not validate when receiving props when validating only on focus in', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount( + const { container, rerender } = render( { ); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(0); - assertErrorMessage(wrapper.getDOMNode(), false); + assertErrorMessage(container, false); - wrapper.setProps({ value: 'failValidationValue' }); + rerender( + , + ); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(0); - assertErrorMessage(wrapper.getDOMNode(), false); + assertErrorMessage(container, false); }); it('should not validate when receiving props when validating only on focus out', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount( + const { container, rerender } = render( { ); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(0); - assertErrorMessage(wrapper.getDOMNode(), false); + assertErrorMessage(container, false); - wrapper.setProps({ value: 'failValidationValue' }); + rerender( + , + ); jest.runAllTimers(); expect(validator).toHaveBeenCalledTimes(0); - assertErrorMessage(wrapper.getDOMNode(), false); + assertErrorMessage(container, false); }); it('should trigger validation only on focus', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount(); + const { container } = render( + , + ); - const input = wrapper.find('input'); - input.simulate('input', mockEvent('invalid value')); + const input = getInput(); + userEvent.type(input, 'invalid value', { skipClick: true }); expect(validator).toHaveBeenCalledTimes(1); - input.simulate('focus'); + userEvent.click(input); expect(validator).toHaveBeenCalledTimes(2); - input.simulate('input', mockEvent('also ')); - input.simulate('input', mockEvent('also invalid')); - input.simulate('focus'); + userEvent.click(container); + userEvent.type(input, 'also '); + userEvent.type(input, 'also invalid'); expect(validator).toHaveBeenCalledTimes(3); }); it('should trigger validation only on blur', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount(); + render(); - const input = wrapper.find('input'); - input.simulate('input', mockEvent('invalid value')); + const input = getInput(); + userEvent.type(input, 'invalid value'); expect(validator).toHaveBeenCalledTimes(1); - input.simulate('blur'); + userEvent.tab(); expect(validator).toHaveBeenCalledTimes(2); - input.simulate('input', mockEvent('also ')); - input.simulate('input', mockEvent('also invalid')); - - input.simulate('blur'); + userEvent.type(input, 'also '); + userEvent.type(input, 'also invalid'); + userEvent.tab(); expect(validator).toHaveBeenCalledTimes(3); }); it('should trigger validation on both blur and focus', () => { const validator = jest.fn((value: string) => (value.length > 3 ? errorMessage : '')); - wrapper = mount( + const { container } = render( , ); - const input = wrapper.find('input'); - input.simulate('input', mockEvent('value before focus')); + const input = getInput(); + userEvent.type(input, 'value before focus', { skipClick: true }); expect(validator).toHaveBeenCalledTimes(1); - input.simulate('focus'); + userEvent.type(input, 'value after foc '); + userEvent.type(input, 'value after focus'); expect(validator).toHaveBeenCalledTimes(2); - - input.simulate('input', mockEvent('value before foc')); - input.simulate('input', mockEvent('value before focus')); - input.simulate('focus'); + //triggers blur event + userEvent.click(container); expect(validator).toHaveBeenCalledTimes(3); - input.simulate('input', mockEvent('value before blur')); - input.simulate('blur'); + userEvent.type(input, 'value before bl'); + userEvent.type(input, 'value before blur'); expect(validator).toHaveBeenCalledTimes(4); - - input.simulate('input', mockEvent('value before bl')); - input.simulate('input', mockEvent('value before blur')); - input.simulate('blur'); + //triggers blur event + userEvent.click(container); expect(validator).toHaveBeenCalledTimes(5); }); }); // end error message @@ -611,7 +574,7 @@ describe('TextField controlled vs uncontrolled usage', () => { function verifyWarningsAndValue(warningCount: number, value: string | undefined) { expect(warnFn).toHaveBeenCalledTimes(warningCount); - const inputDOM = wrapper!.getDOMNode().querySelector('input'); + const inputDOM = getInput(); // check both the DOM and the state to ensure they match expect(textField!.value).toEqual(value); expect(inputDOM!.value).toEqual(value); @@ -628,89 +591,89 @@ describe('TextField controlled vs uncontrolled usage', () => { }); it('warns if value is provided without onChange', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(1); }); it('does not warn if defaultValue is provided without onChange', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(0); }); it('does not warn if value and onChange are provided', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(0); }); it('does not warn if value and readOnly are provided', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(0); }); it('warns if value is null', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(1); }); it('does not warn if value is empty string', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(0); }); it('does not warn if defaultValue is null', () => { - mount(); + render(); expect(warnFn).toHaveBeenCalledTimes(0); }); it('warns if both value and defaultValue are provided, uses value', () => { - wrapper = mount(); + render(); verifyWarningsAndValue(1, value1); }); it('respects updates to value', () => { - wrapper = mount(); + const { rerender } = render(); // should respect updating to non-empty string - wrapper.setProps({ value: value2, onChange: noOp }); + rerender(); verifyWarningsAndValue(0, value2); // should respect updating to empty string - wrapper.setProps({ value: '', onChange: noOp }); + rerender(); verifyWarningsAndValue(0, ''); }); it('respects update and warns if value goes from undefined to provided', () => { // React's warns in this case, but we don't - wrapper = mount(); + const { rerender } = render(); expect(warnFn).toHaveBeenCalledTimes(0); - wrapper.setProps({ value: value1, onChange: noOp }); + rerender(); verifyWarningsAndValue(1, value1); }); it('respects update and warns if value goes from provided to undefined', () => { - wrapper = mount(); + const { rerender } = render(); expect(warnFn).toHaveBeenCalledTimes(0); - wrapper.setProps({ value: undefined, onChange: noOp }); + rerender(); expect(warnFn).toHaveBeenCalledTimes(1); - const inputDOM = wrapper.getDOMNode().querySelector('input'); + const inputDOM = getInput(); expect(textField!.value).toEqual(undefined); expect(inputDOM!.value).toEqual(''); }); it('ignores updates to defaultValue', () => { - wrapper = mount(); + const { rerender } = render(); - wrapper.setProps({ defaultValue: value2 }); + rerender(); verifyWarningsAndValue(0, value1); }); it('ignores if defaultValue goes from undefined to provided', () => { - wrapper = mount(); + const { rerender } = render(); - wrapper.setProps({ defaultValue: value1 }); + rerender(); verifyWarningsAndValue(0, ''); }); }); // end controlled vs uncontrolled usage @@ -731,14 +694,12 @@ describe('TextField onChange', () => { function simulateAndVerifyChange(changeValue: string, calls: number, expectedValue?: string) { expectedValue = typeof expectedValue === 'string' ? expectedValue : changeValue; - const input = wrapper!.find('input'); - // Fire input AND change events to more realistically test - input.simulate('input', mockEvent(changeValue)); - input.simulate('change', mockEvent(changeValue)); + const input = getInput(); + fireEvent.change(input, { target: { value: changeValue } }); expect(onChange).toHaveBeenCalledTimes(calls); expect(textField!.value).toEqual(expectedValue); - expect((input.getDOMNode() as HTMLInputElement).value).toEqual(expectedValue); + expect(input.value).toEqual(expectedValue); } beforeEach(() => { @@ -746,66 +707,66 @@ describe('TextField onChange', () => { }); it('should be called for input change and apply edits if uncontrolled', () => { - wrapper = mount(); + render(); expect(onChange).toHaveBeenCalledTimes(0); - simulateAndVerifyChange('value change', 1); + simulateAndVerifyChange('a', 1); simulateAndVerifyChange('', 2); }); it('should not be called when initial value is undefined and input change is an empty string', () => { - wrapper = mount(); + render(); expect(onChange).toHaveBeenCalledTimes(0); simulateAndVerifyChange('', 0); }); it('should apply edits if implicitly uncontrolled', () => { - wrapper = mount(); + render(); - simulateAndVerifyChange('value change', 1); + simulateAndVerifyChange('a', 1); }); it('should not apply edits if controlled', () => { - wrapper = mount(); + render(); expect(onChange).toHaveBeenCalledTimes(0); - simulateAndVerifyChange('value change', 1, initialValue); + simulateAndVerifyChange('a', 1, initialValue); }); it('should not apply edits if controlled (empty initial value)', () => { - wrapper = mount(); + render(); expect(onChange).toHaveBeenCalledTimes(0); - simulateAndVerifyChange('value change', 1, ''); + simulateAndVerifyChange('a', 1, ''); }); it('respects prop updates in response to onChange', () => { onChange = jest.fn((ev: React.FormEvent, value?: string) => - wrapper!.setProps({ value }), + rerender(), ); - wrapper = mount(); + const { rerender } = render(); simulateAndVerifyChange('a', 1); }); it('should apply edits after clearing field', () => { onChange = jest.fn((ev: React.FormEvent, value?: string) => - wrapper!.setProps({ value }), + rerender(), ); - wrapper = mount(); + const { rerender } = render(); simulateAndVerifyChange('a', 1); // clear the value manually (not via a change event) - wrapper.setProps({ value: '' }); + rerender(); // updating to the same value as before should be respected simulateAndVerifyChange('a', 2); }); }); // end on change -// Other things that don't fit into a category above +// // Other things that don't fit into a category above describe('TextField', () => { beforeEach(sharedBeforeEach); afterEach(sharedAfterEach); @@ -819,113 +780,89 @@ describe('TextField', () => { expect(selectedText!.toString()).toEqual(initialValue); }; - ReactTestUtils.renderIntoDocument( - , - ); + render(); textField!.setSelectionRange(0, initialValue.length); }); it('sets focus to the input via ITextField focus', () => { - safeMount( - , - wrapper2 => { - const inputEl = wrapper2.find('input').getDOMNode(); + render(); - textField!.focus(); - - expect(document.activeElement).toBe(inputEl); - }, - true /* attach */, - ); + const input = getInput(); + textField!.focus(); + expect(document.activeElement).toBe(input); }); it('blurs the input via ITextField blur', () => { - safeMount( - , - wrapper2 => { - const inputEl = wrapper2.find('input').getDOMNode(); + render(); - textField!.focus(); - expect(document.activeElement).toBe(inputEl); + const input = getInput(); - textField!.blur(); - expect(document.activeElement).toBe(document.body); - }, - true /* attach */, - ); + textField!.focus(); + expect(document.activeElement).toBe(input); + + textField!.blur(); + expect(document.activeElement).toBe(document.body); }); it('can switch from single to multi line and back', () => { - const getInput = () => wrapper!.getDOMNode().querySelector('input'); - const getTextarea = () => wrapper!.getDOMNode().querySelector('textarea'); + const getInputField = () => container.querySelector('input'); + const getTextarea = () => container.querySelector('textarea'); // start as single line - wrapper = mount(); - expect(getInput()).toBeTruthy(); + const { container, rerender } = render(); + expect(getInputField()).toBeTruthy(); expect(getTextarea()).toBeNull(); // switch to multiline - wrapper.setProps({ multiline: true }); + rerender(); expect(getTextarea()).toBeTruthy(); - expect(getInput()).toBeNull(); + expect(getInputField()).toBeNull(); // switch back - wrapper.setProps({ multiline: false }); - expect(getInput()).toBeTruthy(); + rerender(); + expect(getInputField()).toBeTruthy(); expect(getTextarea()).toBeNull(); }); it('maintains focus when switching single to multi line and back', () => { - safeMount( - , - wrapper2 => { - // focus input - textField!.focus(); - let input = wrapper2.find('input').getDOMNode(); - expect(document.activeElement).toBe(input); - - // switch to multiline - wrapper2.setProps({ multiline: true }); - // verify still focused - const textarea = wrapper2.find('textarea').getDOMNode(); - expect(document.activeElement).toBe(textarea); - - // back to single line - wrapper2.setProps({ multiline: false }); - // verify still focused - input = wrapper2.find('input').getDOMNode(); - expect(document.activeElement).toBe(input); - }, - true /* attach */, - ); + const { rerender } = render(); + + // focus input + textField!.focus(); + expect(document.activeElement).toBe(getInput()); + + // switch to multiline + rerender(); + // verify still focused + expect(document.activeElement).toBe(getInput()); + // back to single line + rerender(); + // verify still focused + expect(document.activeElement).toBe(getInput()); }); it('maintains selection when switching single to multi line and back', () => { const start = 1; const end = 3; - safeMount( - , - wrapper2 => { - // select - textField!.focus(); - textField!.setSelectionRange(start, end); - expect(textField!.selectionStart).toBe(start); - expect(textField!.selectionEnd).toBe(end); - - // switch to multiline - wrapper2.setProps({ multiline: true }); - // verify still selected - expect(textField!.selectionStart).toBe(start); - expect(textField!.selectionEnd).toBe(end); - - // back to single line - wrapper2.setProps({ multiline: false }); - // verify still selected - expect(textField!.selectionStart).toBe(start); - expect(textField!.selectionEnd).toBe(end); - }, - true /* attach */, - ); + const { rerender } = render(); + + // select + textField!.focus(); + textField!.setSelectionRange(start, end); + expect(textField!.selectionStart).toBe(start); + expect(textField!.selectionEnd).toBe(end); + + // switch to multiline + rerender(); + // verify still selected + expect(textField!.selectionStart).toBe(start); + expect(textField!.selectionEnd).toBe(end); + + // back to single line + rerender(); + // verify still selected + expect(textField!.selectionStart).toBe(start); + expect(textField!.selectionEnd).toBe(end); }); }); From ab794c79213ec3bc357c9b64186cc40d59c0dcfc Mon Sep 17 00:00:00 2001 From: Tristan Watanabe Date: Mon, 7 Mar 2022 21:18:21 -0800 Subject: [PATCH 2/6] update snapshots --- .../__snapshots__/TextField.test.tsx.snap | 2846 ++++++++--------- 1 file changed, 1401 insertions(+), 1445 deletions(-) diff --git a/packages/react/src/components/TextField/__snapshots__/TextField.test.tsx.snap b/packages/react/src/components/TextField/__snapshots__/TextField.test.tsx.snap index 7e150951617979..a1c205910ff67f 100644 --- a/packages/react/src/components/TextField/__snapshots__/TextField.test.tsx.snap +++ b/packages/react/src/components/TextField/__snapshots__/TextField.test.tsx.snap @@ -1,1707 +1,1663 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TextField snapshots renders correctly 1`] = ` -
+
-
- + Label + +
+ > + +
`; exports[`TextField snapshots renders multiline correctly with errorMessage 1`] = ` -
+
-
-
- - test prefix - -
- + Label + +
-
- + + test prefix + +
+ +
- test suffix - + + test suffix + +
+ +
+
- -
-
`; exports[`TextField snapshots renders multiline correctly with props affecting styling 1`] = ` -
+
-
-
- - test prefix - -
- + Label + +
-
- - test suffix - + + test prefix + +
+ +
+ + test suffix + +
+ +
+
- -
-
`; exports[`TextField snapshots renders multiline non resizable correctly 1`] = ` -
+
-
-