6

The component I am writing needs to change its behaviour depending on whether ctrl is pressed or not.

I use a window.onkeydown event but Simulate from React Test Utils doesn't allow me to dispatch events against window. I've also tried window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 17 })); but mocha/node does not recognize KeyboardEvent.

Is there a way to test window.onkeydown using React Test Utils? if not, is there a better way to do it in mocha for node?

Here is some code to ilustrate the issue:

describe('On Keydown', () => {
    it('fires the event', () => {
        // Component
        const Component = class extends React.Component {
            constructor(props) {
                super(props);
                this.state = { key: false };
                window.addEventListener('keydown', e => this.setState({ key: true }));
                window.addEventListener('keyup', e => this.setState({ key: false }));
            }
            render() {
                return <span>test</span>
            };
        };
        // Rendering
        const rendered = renderIntoDocument(<Component/>);
        // Firing event
        expect(rendered.state.key).to.equal(false);
        // Error here
        Simulate.keyDown(window, { keyCode: 17 });
        expect(rendered.state.key).to.equal(true);
    });
});
Javier Conde
  • 2,553
  • 17
  • 25

2 Answers2

1

If you set up your listener like window.addEventListener('keydown', myFunc) then you only need to test myFunc, you don't actually need to test that addEventListener calls your function when a keydown happens.

By always binding events to functions (rather than doing work in a callback) testing is more direct (you're testing your code) and also you can remove event listeners when you're done with them.

David Gilbertson
  • 4,219
  • 1
  • 26
  • 32
  • 1
    What about testing the fact that the compononent should listen for keydown event? Perhaps that should be the part of the spec. If not, one could remove the `addEventListener`, and the component would still pass the test. – jokka Aug 06 '16 at 08:23
  • Valid point. Personally I rely on automation tests for those sorts of things and let the unit tests test the logic. – David Gilbertson Aug 06 '16 at 09:33
  • Well enough. It just bugs me a bit that the event handler is basically a private method, and the test relies on it. But it’s not a big deal. – jokka Aug 06 '16 at 09:50
  • 1
    Out of interest, did you try manually dispatching a plain `Event` instead of `KeyboardEvent`? – David Gilbertson Aug 06 '16 at 09:57
  • I needed the `keydown` event to trigger a state change that would affect another method's behavior but you gave me 2 ideas, first bypass the event using `rendered.setState({key: true})`, and second create a class in test_helper.js to reproduce events on window using node `Events`. I'm adding the second solution as an alternate answer. – Javier Conde Aug 08 '16 at 14:54
1

I solved it thanks to David's comment just by ignoring the event and setting the state to what I needed for the test. I also found out a different way to test window events in the future. Creating a window class that extends EventEmitter you can receive keydown/keyup events like ctrl through window.emit('keydown',{keyCode: 17}).

This is the code of my_test_helper.js:

import jsdom from 'jsdom';
import chai from 'chai';
import EventEmitter from 'events';

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');

const windowClass = class extends EventEmitter {
    constructor() {
        super(doc.defaultView);
        this.__defineSetter__('onkeydown', f => this.on('keydown', f));
        this.__defineSetter__('onkeyup', f => this.on('keyup', f));
    }
    addEventListener (e,f) {
        this.on(e,f);
    }
};

const win = new windowClass();

global.document = doc;
global.window = win;

Object.keys(window).forEach((key) => {
  if (!(key in global)) {
    global[key] = window[key];
  }
});
Javier Conde
  • 2,553
  • 17
  • 25