testing-library / dom-testing-library

🐙 Simple and complete DOM testing utilities that encourage good testing practices.
https://testing-library.com/dom
MIT License
3.26k stars 466 forks source link

`fireEvent.blur(HTMLInputElement)` not working as expected: Selection on focus is not being deselected on blur #1178

Closed jayantasamaddar closed 1 year ago

jayantasamaddar commented 1 year ago

What you did:

I have a Input Text Component setup which selects the entire value of the input field on focus. It is expected to when it loses focus or is blurred, it automatically deselects the selection.


Relevant code or config:

it('When blurred, deselect', () => {
      const inputEl = screen.getByLabelText("Search") as HTMLInputElement;
      const inputVal = "Placeholder"; // The input value
      /** Fire an focus event */
      inputEl.focus();    // // alternatively: fireEvent.focus(inputEl)   - Tried both
      const afterFocusSelection = inputVal.slice(
        inputEl.selectionStart,
        inputEl.selectionEnd
      );
      expect(afterFocusSelection).toBe(inputVal); // Compare to input value: Selected
      console.log({ afterFocusSelection });   // "Placeholder"
      /** Fire a blur event */
      act(() => {
        fireEvent.blur(inputEl); // alternatively: fireEvent.blur(inputEl)   - Tried both
      }); 
      expect(inputEl).not.toHaveFocus(); // Passes Test
      const afterBlurSelection = inputVal.slice(
        inputEl.selectionStart,
        inputEl.selectionEnd
      );
      console.log({ afterBlurSelection });    // Still returns "Placeholder"
      expect(afterBlurSelection).not.toBe(inputVal); // Compare to input value: Not selected   | Doesn't pass Test
 });

What happened:

Getting the following error.

  console.log
    { afterFocusSelection: 'Placeholder' }

      at Object.log (src/components/Textfield/Textfield.test.tsx:162:15)

  console.log
    { afterBlurSelection: 'Placeholder' }

expect(received).not.toBe(expected) // Object.is equality

    Expected: not "Placeholder"

      171 |       );
      172 |       console.log({ afterBlurSelection });
    > 173 |       expect(afterBlurSelection).not.toBe(inputVal); // Compare to input value: Not selected
          |                                      ^
      174 |     });
      175 |   });

Problem description:

Not able to test whether the functionality of select, deselect on focus and blur is possible.

This is unexpected behaviour, because the On Focus test works,

it('On focus, Select the Textfield value', () => {
      const inputEl = screen.getByLabelText("Search") as HTMLInputElement;
      const inputVal = "Placeholder"; // The input value
      /** Get selection before focus event */
      const beforeFocusSelection = inputVal.slice(
        inputEl.selectionStart,
        inputEl.selectionEnd
      );
      console.log({ beforeFocusSelection });   // Returns "" (empty string), i.e. nothing is selected
      expect(beforeFocusSelection).not.toBe(inputVal); // Compare to input value: Not selected | Passes Test
      /** Fire an focus event */
      act(() => {
        fireEvent.focus(inputEl);
      });
      expect(inputEl).toHaveFocus(); // Expect focus event to work |  Passes Test
      /** Get selection after focus event */
      const afterFocusSelection = inputVal.slice(
        inputEl.selectionStart,
        inputEl.selectionEnd
      );
      expect(afterFocusSelection).toBe(inputVal); // Compare to input value: Selected | Passes Test
});

Reproduction:

This is the component. Copy this and import it into the test. The tests are already above.

 import { useState, useRef, useEffect, FocusEvent, ChangeEvent, forwardRef } from 'react;
 import { TextfieldProps } from './types';

 interface TextfieldProps {
   value?: string;
   onBlur?: (e?: FocusEvent<HTMLInputElement>) => void;
   onFocus?: (e?: FocusEvent<HTMLInputElement>) => void;
   onChange?: (e: ChangeEvent<HTMLInputElement>) => void
}

 const Textfield = forwardRef<HTMLInputElement, TextfieldProps>(({ value = "Placeholder", onBlur, onFocus, onChange }) => {
    const inputRef = useRef<HTMLInputElement>(null);
    const [ focus, setFocus ] = useState(false)

   useEffect(() => {
      const target = inputRef.current;
      focus ? target?.focus() : target?.blur();
   }, [focus]);

    const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
         setFocus(false);
         onBlur && onBlur(e);
    });

    const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
         setFocus(true);
         inputRef.current?.select(); // Selects on Focus
         onFocus && onFocus(e);
    });

    return (
       <div className="TextfieldContainer">
         <label id="test-textfield-label" htmlFor="test-textfield-id">Search</label>
         <input 
             id="test-textfield-id"
             data-testid="test-textfield" 
             className="test-textfield" 
             onBlur={handleBlur} 
             onFocus={handleFocus} 
             onChange={onChange}
             value={value} 
             aria-labelledby="test-textfield-label"
             />
       </div>
    )
});

I'll try making a repo later in the day and update this...but this should be extremely simple to reproduce.


eps1lon commented 1 year ago

Have you tested how this test code behaves in a browser and Jest test (JSDOM)? I suspect this is an issue with JSDOM not Testing Library.

jayantasamaddar commented 1 year ago

Works correctly on Storybook as expected. Just not passing the test.

eps1lon commented 1 year ago

Gotcha, thanks for confirming. I would suggest opening a reduced reproduction against JSDOM.