Search code examples
javascriptdrag-and-dropreactjskarma-jasminereactjs-testutils

Simulating drag drop with react testutils not working


I'm try to test my ReactJS mixin for drag and drop functionality using jasmine, karma and React TestUtils.

No exception is thrown but when debugging it seems that the function bound to the event listener not being executed when the event is simulated.

You can clone the it here: https://github.com/itsh01/react-dragdrop/tree/testing-simutale-events

Thank you very much in advance.

Here is my test:

beforeEach(function () {

    var CompDrag = React.createClass({
        mixins: [DragDropMixin],
        dragDrop: function dragDrop() {
            return {
                draggable: true,
                dropType: 'test',
                dataTransfer: {
                    test: true
                }
            };
        },
        render: function render() {
            return React.createElement('div', {});
        }
    });

    var CompDrop = React.createClass({
        mixins: [DragDropMixin],
        dragDrop: function dragDrop() {
            var self = this;
            return {
                droppable: true,
                acceptableTypes: ['test'],
                drop: function (data) {
                    self.setState(data);
                }
            };
        },
        render: function render() {
            return React.createElement('div', {});
        }
    });

    elementDrag = React.createElement(CompDrag, {});
    elementDrop = React.createElement(CompDrop, {});

});

...

it('should attach drop functionality when configured', function () {
    var renderedDrag = TestUtils.renderIntoDocument(elementDrag);
    var renderedDrop = TestUtils.renderIntoDocument(elementDrop);
    var nodeDrag = renderedDrag.getDOMNode();
    var nodeDrop = renderedDrop.getDOMNode();
    var mockEvent = {
        preventDefault: function () {},
        dataTransfer: {
            types: ["objtopass"],
            setData: function () {},
            getData: function () {
                return JSON.parse({
                    dropType: 'test',
                    data: {
                        test: true
                    }
                });
            }
        }
    };

    TestUtils.SimulateNative.dragStart(nodeDrag, mockEvent);
    TestUtils.Simulate.dragOver(nodeDrop, mockEvent);
    TestUtils.Simulate.drop(nodeDrop, mockEvent);

    expect(renderedDrop.state).not.toBeNull();
});

Here is the mixin:

'use strict';

var _ = lodash;

var DragDropMixin = {
    /*
     *  usage:
     *
     *  mixins: [DragDropMixin],
     *  dragDrop: function () {
     *
     *     return {
     *
     *         // when dragging an item
     *         draggable: true,
     *         dropType: 'myItem',
     *         dataTransfer: { myItemData: property }
     *
     *         // when dropping an item:
     *         droppable: true,
     *         acceptableDrops: ['myItem'],
     *         drop: function (myItem) {},
     *     };
     *  }
     *
     */
    isAttrEnabled: function (attr) {
        return this.dragDropData && this.dragDropData[attr];
    },
    isDroppable: function () {
        return this.isAttrEnabled('droppable');
    },
    isDraggable: function () {
        return this.isAttrEnabled('draggable');
    },
    componentDidMount: function () {
        var node = this.getDOMNode();

        this.dragDropData = this.dragDrop();

        if (this.isDroppable()) {
            node.addEventListener('dragover', this.handleDragOver, this);
            node.addEventListener('drop', this.handleDrop, this);
        }

        if (this.isDraggable()) {
            node.draggable = true;
            node.addEventListener('dragstart', this.handleDragStart, this);
        }
    },
    componentWillUnmount: function () {
        var node = this.getDOMNode();

        if (this.isDroppable()) {
            node.removeEventListener('dragover', this.handleDragOver);
            node.removeEventListener('drop', this.handleDrop);
        }

        if (this.isDraggable()) {
            node.removeEventListener('dragstart', this.handleDragStart);
        }
    },
    handleDragOver: function (e) {
        e.preventDefault();
    },
    handleDrop: function (e) {
        var jsonData = e.dataTransfer.getData('objToPass'),
            passedObj = JSON.parse(jsonData),
            acceptableDrops = this.dragDropData.acceptableDrops;

        e.preventDefault();

        if (!this.dragDropData.drop) {
            throw new Error('Must define drop function when using droppable');
        }

        if (_.includes(acceptableDrops, passedObj.dropType)) {
            this.dragDropData.drop(passedObj.data);
        }

    },
    handleDragStart: function (e) {
        var objToPass = {
            data: this.dragDropData.dataTransfer,
            dropType: this.dragDropData.dropType
        };

        e.dataTransfer.setData('objToPass', JSON.stringify(objToPass));
    }
};

Thanks again.


Solution

  • OK, got it.

    I was actually listening to native events and simulating React synthetic events.

    Fixed it by changing the mixin:

    componentDidMount: function () {
        var node = this.getDOMNode();
    
        this.dragDropData = this.dragDrop();
    
        if (this.isDroppable()) {
            node.ondragover = this.handleDragOver;
            node.ondrop = this.handleDrop;
        }
    
        if (this.isDraggable()) {
            node.draggable = true;
            node.ondragstart = this.handleDragStart;
        }
    },
    

    And testing by triggering a native event

        nodeDrag.ondragstart(mockEvent);
        nodeDrop.ondragover(mockEvent);
        nodeDrop.ondrop(mockEvent);
    
        expect(DragDropMixin.handleDrop).toHaveBeenCalled();
        expect(renderedDrop.state).toBeNull();