14

So I am writing some tests for code that adds a click event on the document. I am using JSDom, ReactJS, and Mocha/Chai setup. I have tried the following code in a test:

document.addEventListener('click', function() {
  console.log('test');
});
React.addons.TestUtils.Simulate.click(document);
//also tried React.addons.TestUtils.Simulate.click(document.body);

however this code does not produce the echo that I am expecting.

Is there a way to simulate click, keyup, etc... on the document with JSDom and ReactJS?

UPDATE

To Nick answers, I have tried adding this code to the test:

document.body.addEventListener('click', function() {
  console.log('test');
});

document.body.click();

and I till don't get the console log output. I am not sure if there is some issue with JSDom and doing this type of thing.

If I can't unit test this code, that's fine, there is already some code that I can't unit test right now (code that requires a real DOM to be able to get widths, heights, etc...) but I would like to be able to unit test most of the code (and I am not interested in using PhantomJS for unit testing). My integration tests will cover that type of code.

UPDATE2

Another thing to note is that is that when I console.log(document); I see object attached to the _listeners property for click so I know the event is being attached, it just does not seem to be executing.

ryanzec
  • 27,284
  • 38
  • 112
  • 169

5 Answers5

31

Update: document.body.click will work in a browser, but for jsdom you need to manually create the event:

document.body.addEventListener('click', function() {
  console.log('test');
});

var evt = document.createEvent("HTMLEvents");
evt.initEvent("click", false, true);
document.body.dispatchEvent(evt)

The above code is working for me in Jest and should work with "solo" jsdom as well.

Autumn 2018 holiday update

The tools around simulating events have gotten a great deal better. I've currently shifted my approach to use the excellent react-testing-library to render my components and dispatch events:

import {render, fireEvent} from 'react-testing-library'

test('example', () => {
  const handleClick = jest.fn()

  const {getByText} = render(<div><button onClick={handleClick}>Click Me</button></div>)
  fireEvent.click(getByText('Click Me'))

  expect(handleClick).toHaveBeenCalled()
})

While I still believe in end to end tests with something like puppeteer or selenium (or cypress!) provide the highest level of confidence that something actually works, this provides a huge amount of value without polluting tests with manual event creation.

Nick Tomlin
  • 28,402
  • 11
  • 61
  • 90
  • Thanks, that works for click but what about events where I would need to set event data (like setting keyCode for a keyup event)? ready it seems like I would use CustomEvent or createEventObject however neither methods seem to be supported by JSDom – ryanzec Dec 19 '14 at 14:36
  • @ryanzec i'd have to do some digging to answer that for sure. Depending on how often you are going to be testing these events, it may be a case for a separate suite of phantomJS tests :| – Nick Tomlin Dec 19 '14 at 14:43
  • @ryanzec I don't think it's exactly kosher, but you can simulate keycode by just attaching a `keyCode` property to the event instance before you dispatch it with `initEvent` – Nick Tomlin Dec 19 '14 at 14:54
  • I think it is going to be somewhat uncommon for me to do this, most of the time I am going to be dealing with events on react components which react already provides tools for. This is a special case where the event is on the document in order to process any keyup event (specially to close a model window when escape is pressed). – ryanzec Dec 19 '14 at 15:36
  • 1
    Attaching a `keyCode`/`which` property before `initEvent` does work. I guess I will go with that. I am not 100% worried that it is not really kosher since I am already using jsdom which is not a really dom. I am going to have integration tests that run against real browsers, these unit tests are just design to help catch issues earlier with tests that will run a lot faster than integration tests. – ryanzec Dec 19 '14 at 15:45
  • @ryanzec sounds like you are making the right call. Especially if you have some higher level integration tests around this. You can always abstract the event generation/triggering to a helper if it's too ugly. – Nick Tomlin Dec 19 '14 at 15:46
  • yea, already abstracted that into my test helper, thanks for the help. – ryanzec Dec 19 '14 at 17:26
  • @ryanzec My event handler uses `event.target`, and trying to set `evt.target = ...` results in `TypeError: Cannot set property target of [object Object] which has only a getter`. Any ideas how to attach a `target`? – Misha Moroshko Aug 15 '16 at 08:00
  • @MishaMoroshko please see the section CustomEvent for adding fields like target at: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events – velop Jan 18 '18 at 12:52
  • Hello @Nick, and thanks for a great answer. Since it would seem that [document.createEvent() is deprecated](https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent), would you have the time to update this answer with the suggested event constructors? Otherwise, would you mind if I do? Thanks! – Félix Adriyel Gagnon-Grenier Aug 29 '19 at 15:04
  • @FélixGagnon-Grenier i'm seeing some inconsistencies with JSDOM's support of createEvent (at least within a `jest` environment) so while that is the most correct answer in terms of browser support, I don't know if it is ready for prime time yet in terms of React testing. Feel free to add another answer if you can figure it out :) – Nick Tomlin Aug 29 '19 at 16:55
6

React.addons.TestUtils.Simulate only works with virtual events. If you want to dispatch native events, you can do that with the DOM api directly.

When simulating a click, if you have a component which renders this:

<div>
   <button onClick={alert}>click me</button>
</div>

And you have a reference to the <button/> in a variable called 'buttonEl', and run this:

React.addons.TestUtils.Simulate.click(buttonEl, 'hello world');

You'd get an alert with 'hello world' in it. All test utils does is create the virtual event, and let it bubble up the virtual dom tree, calling the event handlers along the way.

Brigand
  • 84,529
  • 20
  • 165
  • 173
6

Event.initEvent() is deprecated and the Event() constructor should now be used instead.

// Add a click event listener to the document
document.addEventListener('click', function() {
    console.log('test');
});

// Create a click event with optional event initialisers: bubbles, cancelable and composed
var evt = new Event('click', { bubbles: false, cancelable: false, composed: false });

// Event can then be dispatched against any element, not only the document
document.dispatchEvent(evt);
myDiv.dispatchEvent(evt);

Browser support is good with the exception of Internet Explorer.

References:

Wayne
  • 61
  • 1
  • 3
1

Simply create an Event and dispatch it:

// Add an event listeners to the document
document.addEventListener('click', function() {
    console.log('test');
});

// Create a new `click` event and `dispatch` it
var event = new MouseEvent('click')
document.dispatchEvent(event)
Riccardo Bartoli
  • 569
  • 1
  • 4
  • 17
0

You can use Simulate function from react-dom/test-utils => Simulate.click(your_button)

Here is an example I am using mocha, chai and jsdom


import ReactDOM from "react-dom";
import {act, Simulate} from "react-dom/test-utils";
import {assert} from 'chai';
import {Folder} from "../components/Folder"

let rootContainer:any;

// Define the code that will run before and after the test
beforeEach(() => {
  rootContainer = window.document.createElement("div");
  window.document.body.appendChild(rootContainer);
});

afterEach(() => {
  window.document.body.removeChild(rootContainer);
  rootContainer = null;
});

describe('Folder', ()=>{
  
    // an empty folder
    const empty_folder = {
        "name": "Music",
        "items": []
    };


    // test that the file render automatically
    it("Renders should render a Folder", () => {
        act(() => {
            ReactDOM.render(<Folder folder_obj={empty_folder} level = {0}/> , rootContainer);
        });
        // Get the html elements of Folder components
        const folder_wrapper = rootContainer.querySelector(".wrapper");
        const foldername_span = rootContainer.querySelector(".left-part span:last-child");
        const foldericon = rootContainer.querySelector(".left-part span:first-child i");
        const folderitems_span = rootContainer.querySelector(".right-part span");

        // Check if the folder name, icons, number of items are rendred correctly
        assert.equal(foldername_span.textContent, empty_folder.name);
        assert.include(foldericon.className, "ico-folder");
        assert.equal(folderitems_span.textContent, "(empty)");

        // Check that the icon is changed after a click using Simulate function
        Simulate.click(folder_wrapper)
        assert.include(foldericon.className, "ico-folder-open");
    });
});

DINA TAKLIT
  • 7,074
  • 10
  • 69
  • 74