25

How do I test input.focus() in enzyme. I am writing the script with react. My code is below:

public inputBox: any;

componentDidUpdate = () => {
    setTimeout(() => {
        this.inputBox.focus();
    }, 200);
}

render() {
    return (
        <div>
            <input
                type = 'number'
                ref = {element => this.inputBox = element } />
        </div>
    );
}
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Sachin
  • 306
  • 1
  • 3
  • 8

8 Answers8

24

You can use mount instead of shallow. Then you can compare document.activeElement and the input DOM node for equality.

const output = mount(<MyFocusingComponent/>);

assert(output.find('input').node === document.activeElement);

See https://github.com/airbnb/enzyme/issues/316 for more details.

whatknight
  • 303
  • 1
  • 7
  • 1
    Worked for me. Just to be clear, one needs global `window` and `document` objects for mount to work. I used following code before my first `describe` for that purpose `import jsdom from 'jsdom'; const doc = jsdom.jsdom(' '); global.document = doc; global.window = doc.defaultView;` – Ejaz Mar 07 '17 at 13:00
  • For Jest, make sure you set `--env=jsdom` at CLI, or `"testEnvironment": "jsdom"` in jest config, then you don't need to import it – WickyNilliams Sep 12 '17 at 12:46
  • This is a great answer because it's the only thing I've found that also works with `useRef` where I haven't found a way to get ahold of the ref inside a function component. – Sanborn May 01 '20 at 02:01
12

Per React 16.3 updates... using createRef for anyone visiting this post today, if you rearrange the original component to use the new ref api

class InputBox extends PureComponent {
    constructor(props) {
        super(props);
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        this.inputRef.current.focus();
    }
    render() {
        return (
            <input
                ref={this.inputRef}
            />
        );
    }
}

Then in your test spec

it("Gives immediate focus on to name field on load", () => {
    const wrapper = mount(<InputBox />);
    const { inputRef } = wrapper.instance();

    jest.spyOn(inputRef.current, "focus");

    wrapper.instance().componentDidMount();
    expect(inputRef.current.focus).toHaveBeenCalledTimes(1);
});

Notice the use of the inputRef.current attribute which references the currently assigned DOM node.

Skaronator
  • 33
  • 1
  • 9
Prancer
  • 3,336
  • 2
  • 32
  • 38
10

Other approach is to test if element gains focus, i.e. focus() is called on node element. To achieve this, focused element need to be referenced via ref tag like it takes place in your example – reference was assigned to this.inputBox. Consider example below:

const wrapper = mount(<FocusingInput />);
const element = wrapper.instance().inputBox; // This is your input ref

spyOn(element, 'focus');

wrapper.simulate('mouseEnter', eventStub());

setTimeout(() => expect(element.focus).toHaveBeenCalled(), 250);

This example uses Jasmine's spyOn, though you can use any spy you like.

mckomo
  • 164
  • 1
  • 6
  • 3
    Where is `eventStub()` coming from? – Ralph David Abernathy Jul 28 '17 at 15:10
  • 1
    `eventStub` is anything that makes your test pass. In this example you can assume that component `FocusingInput` is listening to 'mouseEnter' event and when this event is triggered (`wrapper.simulate('mouseEnter', ...)` does that) event handler can perform some operations on event like `preventDefault`. To make this work, you need to prepare event stub that will handle this operation and send this stub as second argument of `simulate`. Here is example of my [event stub](https://gist.github.com/mckomo/128bdb43434749ca1a2299d456ed7d7c). – mckomo Aug 02 '17 at 12:56
  • 4
    Wrapping your `expect` in a `setTimeout` is dangerous because your test runner (in my case Jest) might say the test has passed, regardless of what happened inside `setTimeout`. Try it out by putting a `.not` in front of `.toHaveBeenCalled`. – horstwilhelm Sep 06 '19 at 08:14
3

I just had the same issue and solved using the following approach:

My setup is Jest (react-create-app) + Enzyme:

    it('should set the focus after render', () => {
      // If you don't create this element you can not access the 
      // document.activeElement or simply returns <body/>
      document.body.innerHTML = '<div></div>'

      // You have to tell Enzyme to attach the component to this
      // newly created element
      wrapper = mount(<MyTextFieldComponent />, {
        attachTo: document.getElementsByName('div')[0]
      })

      // In my case was easy to compare using id 
      // than using the whole element
      expect(wrapper.find('input').props().id).toEqual(
        document.activeElement.id
      )
    })
thiagoxvo
  • 347
  • 2
  • 10
  • Followed your example but I seem to get a string back from `document.activeElement` rather than an Element object so the test results in "Object is possibly 'null'". Any ideas where I might be going wrong? – Jimmy Wilson Jan 04 '19 at 18:41
  • Thank you for this piece of information. `getElementsByName` should be `getElementsByTagName` – Ritesh Jagga Dec 11 '19 at 12:05
2

This worked for me when using mount and useRef hook:

expect(wrapper.find('input').get(0).ref.current).toEqual(document.activeElement)
Emi
  • 4,597
  • 2
  • 31
  • 34
2

Focus on the particular element can be checked using selectors.

const wrapper = mount(<MyComponent />);

const input = wrapper.find('input');
expect(input.is(':focus')).toBe(true);
0

Selecting by data-test attribute or something similar was the most straight forward solution I could come up with.

import React, { Component } from 'react'
import { mount } from 'enzyme'

class MyComponent extends Component {
  componentDidMount() {
    if (this.inputRef) {
      this.inputRef.focus()
    }
  }

  render() {
    return (
      <input data-test="my-data-test" ref={input => { this.inputRef = input } } />
    )
  }
}

it('should set focus on mount', () => {
  mount(<MyComponent />)
  expect(document.activeElement.dataset.test).toBe('my-data-test')
})
0

This should work

const wrapper = mount(<MyComponent />);

const input = wrapper.find('input');

expect(input).toHaveFocus();
VnoitKumar
  • 1,350
  • 15
  • 29