blob: 51ed3b4d75d62f98ea1535b40a2756fcaae2d782 [file] [log] [blame]
/*eslint no-unused-vars:0, no-console:0*/
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
import ShallowRenderer from 'react-test-renderer/shallow';
import Draggable, {DraggableCore} from '../lib/Draggable';
import FrameComponent from 'react-frame-component';
import assert from 'assert';
import _ from 'lodash';
import {getPrefix, browserPrefixToKey, browserPrefixToStyle} from '../lib/utils/getPrefix';
const transformStyle = browserPrefixToStyle('transform', getPrefix('transform'));
const transformKey = browserPrefixToKey('transform', getPrefix('transform'));
const userSelectStyle = browserPrefixToStyle('user-select', getPrefix('user-select'));
describe('react-draggable', function () {
var drag;
// Remove body margin so offsetParent calculations work properly
beforeAll(function() {
const styleNode = document.createElement('style');
// browser detection (based on prototype.js)
const styleText = document.createTextNode('body {margin: 0;}');
styleNode.appendChild(styleText);
document.getElementsByTagName('head')[0].appendChild(styleNode);
});
beforeEach(function() {
spyOn(console, 'error');
});
afterEach(function() {
try {
TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); // reset user-select
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(drag).parentNode);
} catch(e) { return; }
});
describe('props', function () {
it('should have default properties', function () {
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
assert.equal(drag.props.axis, 'both');
assert(drag.props.bounds == false);
assert.equal(typeof drag.props.onStart, 'function');
assert.equal(typeof drag.props.onDrag, 'function');
assert.equal(typeof drag.props.onStop, 'function');
});
it('should pass style and className properly from child', function () {
drag = (<Draggable><div className="foo" style={{color: 'black'}}/></Draggable>);
const node = renderToNode(drag);
// Touch-action hack has been removed
if ('touchAction' in document.body.style) {
assert.equal(node.getAttribute('style').indexOf('touch-action: none'), -1);
}
assert(node.getAttribute('style').indexOf('color: black') >= 0);
assert(new RegExp(transformStyle + ': translate\\(0px(?:, 0px)?\\)').test(node.getAttribute('style')));
assert.equal(node.getAttribute('class'), 'foo react-draggable');
});
it('should set the appropriate custom className when dragging or dragged', function () {
drag = TestUtils.renderIntoDocument(
<Draggable
defaultClassName='foo'
defaultClassNameDragging='bar'
defaultClassNameDragged='baz'
>
<div/>
</Draggable>
);
var node = ReactDOM.findDOMNode(drag);
assert(node.getAttribute('class').indexOf('foo') >= 0);
TestUtils.Simulate.mouseDown(node);
assert(node.getAttribute('class').indexOf('bar') >= 0);
TestUtils.Simulate.mouseUp(node);
assert(node.getAttribute('class').indexOf('baz') >= 0);
});
// NOTE: this runs a shallow renderer, which DOES NOT actually render <DraggableCore>
it('should pass handle on to <DraggableCore>', function () {
drag = <Draggable handle=".foo"><div /></Draggable>;
const renderer = new ShallowRenderer();
renderer.render(drag);
const output = renderer.getRenderOutput();
const expected = (
<DraggableCore handle=".foo">
<div
className="react-draggable"
style={{
[transformKey]: 'translate(0px, 0px)'
}}
transform={null} />
</DraggableCore>
);
// Not easy to actually test equality here. The functions are bound as static props so we can't test those easily.
const toOmit = ['onStart', 'onStop', 'onDrag', 'onMouseDown', 'children'];
assert.deepEqual(
_.omit(output.props, toOmit),
_.omit(expected.props, toOmit)
);
});
it('should honor props', function () {
function handleStart() {}
function handleDrag() {}
function handleStop() {}
drag = TestUtils.renderIntoDocument(
<Draggable
axis="y"
handle=".handle"
cancel=".cancel"
grid={[10, 10]}
onStart={handleStart}
onDrag={handleDrag}
onStop={handleStop}>
<div className="foo bar">
<div className="handle"/>
<div className="cancel"/>
</div>
</Draggable>
);
assert.equal(drag.props.axis, 'y');
assert.equal(drag.props.handle, '.handle');
assert.equal(drag.props.cancel, '.cancel');
assert(_.isEqual(drag.props.grid, [10, 10]));
assert.equal(drag.props.onStart, handleStart);
assert.equal(drag.props.onDrag, handleDrag);
assert.equal(drag.props.onStop, handleStop);
});
it('should adjust draggable data output when `scale` prop supplied', function () {
function onDrag(event, data) {
assert.equal(data.x, 200);
assert.equal(data.y, 200);
assert.equal(data.deltaX, 200);
assert.equal(data.deltaY, 200);
}
drag = TestUtils.renderIntoDocument(
<Draggable
scale={0.5}
onDrag={onDrag}>
<div />
</Draggable>
);
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should throw when setting className', function () {
drag = (<Draggable className="foo"><span /></Draggable>);
TestUtils.renderIntoDocument(drag);
expect(
console.error.calls.argsFor(0)[0].replace('propType:', 'prop type:').split('\n')[0]
).toBe(
'Warning: Failed prop type: Invalid prop className passed to Draggable - do not set this, set it on the child.'
);
});
it('should throw when setting style', function () {
drag = (<Draggable style={{color: 'red'}}><span /></Draggable>);
TestUtils.renderIntoDocument(drag);
expect(
console.error.calls.argsFor(0)[0].replace('propType:', 'prop type:').split('\n')[0]
).toBe(
'Warning: Failed prop type: Invalid prop style passed to Draggable - do not set this, set it on the child.'
);
});
it('should throw when setting transform', function () {
drag = (<Draggable transform="translate(100, 100)"><span /></Draggable>);
TestUtils.renderIntoDocument(drag);
expect(
console.error.calls.argsFor(0)[0].replace('propType:', 'prop type:').split('\n')[0]
).toBe(
'Warning: Failed prop type: Invalid prop transform passed to Draggable - do not set this, set it on the child.'
);
});
it('should call onStart when dragging begins', function () {
let called = false;
drag = TestUtils.renderIntoDocument(
<Draggable onStart={function () { called = true; }}>
<div/>
</Draggable>
);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
assert.equal(called, true);
});
it('should call onStop when dragging ends', function () {
let called = false;
drag = TestUtils.renderIntoDocument(
<Draggable onStop={function () { called = true; }}>
<div/>
</Draggable>
);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag));
assert.equal(called, true);
});
it('should not call onStart when dragging begins and disabled', function () {
let called = false;
drag = TestUtils.renderIntoDocument(
<Draggable onStart={function () { called = true; }} disabled={true}>
<div/>
</Draggable>
);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
assert.equal(called, false);
});
it('should immediately call onStop if onDrag returns false', function () {
let called = false;
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={function () { return false; }} onStop={function () { called = true; }}>
<div/>
</Draggable>
);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
assert.equal(called, false);
mouseMove(10, 10);
assert.equal(called, true);
assert.equal(drag.state.x, 0);
assert.equal(drag.state.y, 0);
});
it('should render with style translate() for DOM nodes', function () {
let dragged = false;
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={function() { dragged = true; }}>
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(style.indexOf('transform: translate(100px, 100px);') >= 0);
});
it('should render with positionOffset set as string transform and handle subsequent translate() for DOM nodes', function () {
let dragged = false;
drag = TestUtils.renderIntoDocument(
<Draggable positionOffset={{x: '10%', y: '10%'}} onDrag={function() { dragged = true; }}>
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(style.indexOf('translate(10%, 10%) translate(100px, 100px);') >= 0);
});
it('should honor "x" axis', function () {
let dragged = false;
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={function() { dragged = true; }} axis="x">
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(/transform: translate\(100px(?:, 0px)?\);/.test(style));
});
it('should honor "y" axis', function () {
let dragged = false;
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={function() { dragged = true; }} axis="y">
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(style.indexOf('transform: translate(0px, 100px);') >= 0);
});
it('should honor "none" axis', function () {
let dragged = false;
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={function() { dragged = true; }} axis="none">
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(/transform: translate\(0px(?:, 0px)?\);/.test(style));
});
it('should detect if an element is instanceof SVGElement and set state.isElementSVG to true', function() {
drag = TestUtils.renderIntoDocument(
<Draggable>
<svg />
</Draggable>
);
assert.equal(drag.state.isElementSVG, true);
});
it('should detect if an element is NOT an instanceof SVGElement and set state.isElementSVG to false', function() {
drag = TestUtils.renderIntoDocument(
<Draggable>
<div />
</Draggable>
);
assert.equal(drag.state.isElementSVG, false);
});
it('should render with transform translate() for SVG nodes', function () {
drag = TestUtils.renderIntoDocument(
<Draggable>
<svg />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
simulateMovementFromTo(drag, 0, 0, 100, 100);
const transform = node.getAttribute('transform');
assert(transform.indexOf('translate(100,100)') >= 0);
});
it('should add and remove transparent selection class', function () {
drag = TestUtils.renderIntoDocument(
<Draggable>
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
assert(document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseUp(node);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
});
it('should not add and remove transparent selection class when disabled', function () {
drag = TestUtils.renderIntoDocument(
<Draggable enableUserSelectHack={false}>
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseUp(node);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
});
it('should not add and remove transparent selection class when onStart returns false', function () {
function onStart() { return false; }
drag = TestUtils.renderIntoDocument(
<Draggable onStart={onStart}>
<div />
</Draggable>
);
const node = ReactDOM.findDOMNode(drag);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseUp(node);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
});
it('should not defocus inputs when unmounting', function () {
// Have only successfully gotten this to run on Chrome unfortunately, otherwise the initial
// select does not work.
// As of April 2020 we have verified this works in other browsers manually
if (!/Chrome/.test(window.navigator.userAgent)) {
return pending();
}
class TestCase extends React.Component {
constructor() {
super();
this.state = {text: false};
}
render() {
return (
<div>
<input type="text" onChange={(e) => this.setState({text: e.target.value})} size={5} />
{!this.state.text && (
<Draggable>
<div />
</Draggable>
)}
</div>
);
}
}
drag = TestUtils.renderIntoDocument(<TestCase/>);
const dragEl = ReactDOM.findDOMNode(drag);
// Need to append to a real document to test focus/selection, can't just be a fragment
document.body.appendChild(dragEl);
const input = dragEl.querySelector('input');
input.focus();
assert.equal(window.getSelection().type, 'Caret', 'Element should be focused before draggable unmounts');
TestUtils.Simulate.keyDown(input, {key: 'a', keyCode: 65, which: 65});
assert.equal(window.getSelection().type, 'Caret', 'Element should be focused after draggable unmounts');
document.body.removeChild(dragEl);
});
it('should be draggable when in an iframe', function (done) {
let dragged = false;
const dragElement = (
<Draggable onDrag={function() { dragged = true; }}>
<div />
</Draggable>
);
const renderRoot = document.body.appendChild(document.createElement('div'));
const ref = React.createRef();
const frame = ReactDOM.render(<FrameComponent ref={ref}>{ dragElement }</FrameComponent>, renderRoot);
setTimeout(function checkIframe() {
const iframeDoc = ref.current?.contentDocument;
if (!iframeDoc) return setTimeout(checkIframe, 50);
const node = iframeDoc.querySelector('.react-draggable');
if (!node) return setTimeout(checkIframe, 50);
simulateMovementFromTo(node, 0, 0, 100, 100);
const style = node.getAttribute('style');
assert.equal(dragged, true);
assert(style.indexOf('transform: translate(100px, 100px);') >= 0);
renderRoot.parentNode.removeChild(renderRoot);
done();
}, 0);
});
it('should add and remove transparent selection class to iframe\'s body when in an iframe', function (done) {
const dragElement = (
<Draggable>
<div />
</Draggable>
);
const renderRoot = document.body.appendChild(document.createElement('div'));
const ref = React.createRef();
ReactDOM.render(<FrameComponent ref={ref}>{ dragElement }</FrameComponent>, renderRoot);
setTimeout(function checkIframe() {
const iframeDoc = ref.current?.contentDocument;
if (!iframeDoc) return setTimeout(checkIframe, 50);
const node = iframeDoc.querySelector('.react-draggable');
if (!node) return setTimeout(checkIframe, 50);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
assert(!iframeDoc.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
assert(iframeDoc.body.classList.contains('react-draggable-transparent-selection'));
TestUtils.Simulate.mouseUp(node);
assert(!document.body.classList.contains('react-draggable-transparent-selection'));
assert(!iframeDoc.body.classList.contains('react-draggable-transparent-selection'));
renderRoot.parentNode.removeChild(renderRoot);
done();
}, 0);
});
});
describe('interaction', function () {
function mouseDownOn(drag, selector, shouldDrag) {
resetDragging(drag);
const node = ReactDOM.findDOMNode(drag).querySelector(selector);
if (!node) throw new Error(`Selector not found: ${selector}`);
TestUtils.Simulate.mouseDown(node);
assert.equal(drag.state.dragging, shouldDrag);
}
function resetDragging(drag) {
TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag));
assert.equal(drag.state.dragging, false);
}
it('should initialize dragging onmousedown', function () {
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
assert.equal(drag.state.dragging, true);
});
it('should only initialize dragging onmousedown of handle', function () {
drag = TestUtils.renderIntoDocument(
<Draggable handle=".handle">
<div>
<div className="handle">Handle</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);
mouseDownOn(drag, '.content', false);
mouseDownOn(drag, '.handle', true);
});
it('should only initialize dragging onmousedown of handle, even if children fire event', function () {
drag = TestUtils.renderIntoDocument(
<Draggable handle=".handle">
<div>
<div className="handle">
<div><span><div className="deep">Handle</div></span></div>
</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);
mouseDownOn(drag, '.content', false);
mouseDownOn(drag, '.deep', true);
mouseDownOn(drag, '.handle > div', true);
mouseDownOn(drag, '.handle span', true);
mouseDownOn(drag, '.handle', true);
});
it('should not initialize dragging onmousedown of cancel', function () {
drag = TestUtils.renderIntoDocument(
<Draggable cancel=".cancel">
<div>
<div className="cancel">Cancel</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);
mouseDownOn(drag, '.cancel', false);
mouseDownOn(drag, '.content', true);
});
it('should not initialize dragging onmousedown of handle, even if children fire event', function () {
drag = TestUtils.renderIntoDocument(
<Draggable cancel=".cancel">
<div>
<div className="cancel">
<div><span><div className="deep">Cancel</div></span></div>
</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);
mouseDownOn(drag, '.content', true);
mouseDownOn(drag, '.deep', false);
mouseDownOn(drag, '.cancel > div', false);
mouseDownOn(drag, '.cancel span', false);
mouseDownOn(drag, '.cancel', false);
});
it('should discontinue dragging onmouseup', function () {
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
assert.equal(drag.state.dragging, true);
resetDragging(drag);
});
it('should initialize dragging ontouchstart', function () {
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
// Need to dispatch this ourselves as there is no onTouchStart handler (due to passive)
// so TestUtils.Simulate will not work
const e = new Event('touchstart');
ReactDOM.findDOMNode(drag).dispatchEvent(e);
assert.equal(drag.state.dragging, true);
});
it('should call preventDefault on touchStart event', function () {
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
const e = new Event('touchstart');
// Oddly `e.defaultPrevented` is not changing here. Maybe because we're not mounted to a real doc?
let pdCalled = false;
e.preventDefault = function() { pdCalled = true; };
ReactDOM.findDOMNode(drag).dispatchEvent(e);
assert(pdCalled);
assert.equal(drag.state.dragging, true);
});
it('should not call preventDefault on touchStart event if not on handle', function () {
drag = TestUtils.renderIntoDocument(
<Draggable handle=".handle">
<div>
<div className="handle">
<div><span><div className="deep">Handle</div></span></div>
</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);
const e = new Event('touchstart');
let pdCalled = false;
e.preventDefault = function() { pdCalled = true; };
ReactDOM.findDOMNode(drag).querySelector('.content').dispatchEvent(e);
assert(!pdCalled);
assert(drag.state.dragging !== true);
});
it('should modulate position on scroll', function (done) {
let dragCalled = false;
function onDrag(e, coreEvent) {
assert.equal(Math.round(coreEvent.deltaY), 500);
dragCalled = true;
}
drag = TestUtils.renderIntoDocument(<Draggable onDrag={onDrag}><div/></Draggable>);
const node = ReactDOM.findDOMNode(drag);
// Create a container we can scroll. I'm doing it this way so we can still access <Draggable>.
// Enzyme (airbnb project) would make this a lot easier.
const fragment = fragmentFromString(`
<div style="overflow: auto; height: 100px;">
<div style="height: 10000px; position: relative;">
</div>
</div>
`);
transplantNodeInto(node, fragment, (f) => f.children[0]);
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
assert.equal(drag.state.dragging, true);
// Scroll the inner container & trigger a scroll
fragment.scrollTop = 500;
mouseMove(0, 0);
TestUtils.Simulate.mouseUp(node);
setTimeout(function() {
assert.equal(drag.state.dragging, false);
assert.equal(dragCalled, true);
assert.equal(drag.state.y, 500);
// Cleanup
document.body.removeChild(fragment);
done();
}, 50);
});
it('should respect offsetParent on nested div scroll', function (done) {
let dragCalled = false;
function onDrag(e, coreEvent) {
dragCalled = true;
// Because the offsetParent is the body, we technically haven't moved at all relative to it
assert.equal(coreEvent.deltaY, 0);
}
drag = TestUtils.renderIntoDocument(<Draggable onDrag={onDrag} offsetParent={document.body}><div/></Draggable>);
const node = ReactDOM.findDOMNode(drag);
// Create a container we can scroll. I'm doing it this way so we can still access <Draggable>.
// Enzyme (airbnb project) would make this a lot easier.
const fragment = fragmentFromString(`
<div style="overflow: auto; height: 100px;">
<div style="height: 10000px; position: relative;">
</div>
</div>
`);
transplantNodeInto(node, fragment, (f) => f.children[0]);
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
fragment.scrollTop = 500;
mouseMove(0, 0);
TestUtils.Simulate.mouseUp(node);
setTimeout(function() {
assert(dragCalled);
// Cleanup
document.body.removeChild(fragment);
done();
}, 50);
});
});
describe('draggable callbacks', function () {
it('should call back on drag', function (done) {
function onDrag(event, data) {
assert.equal(data.x, 100);
assert.equal(data.y, 100);
assert.equal(data.deltaX, 100);
assert.equal(data.deltaY, 100);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag}>
<div />
</Draggable>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with correct dom node with nodeRef', function (done) {
function onDrag(event, data) {
// Being tricky here and installing the ref on the inner child, to ensure it's working
// and not just falling back on ReactDOM.findDOMNode()
assert.equal(data.node, ReactDOM.findDOMNode(drag).firstChild);
done();
}
const nodeRef = React.createRef();
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag} nodeRef={nodeRef}>
<span>
<div ref={nodeRef} />
</span>
</Draggable>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with correct dom node with nodeRef (forwardRef)', function (done) {
const Component1 = React.forwardRef(function (props, ref) {
return <div {...props} ref={ref}>Nested component</div>;
});
function onDrag(event, data) {
assert.equal(data.node, ReactDOM.findDOMNode(drag));
assert.equal(data.node.innerText, 'Nested component');
done();
}
const nodeRef = React.createRef();
drag = TestUtils.renderIntoDocument(
<DraggableCore onDrag={onDrag} nodeRef={nodeRef}>
<Component1 ref={nodeRef} />
</DraggableCore>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back on drag, with values within the defined bounds', function(done){
function onDrag(event, data) {
assert.equal(data.x, 90);
assert.equal(data.y, 90);
assert.equal(data.deltaX, 90);
assert.equal(data.deltaY, 90);
done();
}
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag} bounds={{left: 0, right: 90, top: 0, bottom: 90}}>
<div />
</Draggable>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with offset left/top, not client', function(done) {
function onDrag(event, data) {
assert.equal(data.x, 100);
assert.equal(data.y, 100);
assert.equal(data.deltaX, 100);
assert.equal(data.deltaY, 100);
done();
}
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag} >
<div style={{position: 'relative', top: '200px', left: '200px'}} />
</Draggable>
);
simulateMovementFromTo(drag, 200, 200, 300, 300);
});
it('should call back with correct position when parent element is 2x scaled', function(done) {
function onDrag(event, data) {
// visually it will look like 100, because parent is 2x scaled
assert.equal(data.x, 50);
assert.equal(data.y, 50);
assert.equal(data.deltaX, 50);
assert.equal(data.deltaY, 50);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag} scale={2}>
<div />
</Draggable>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with correct position when parent element is 0.5x scaled', function(done) {
function onDrag(event, data) {
// visually it will look like 100, because parent is 0.5x scaled
assert.equal(data.x, 200);
assert.equal(data.y, 200);
assert.equal(data.deltaX, 200);
assert.equal(data.deltaY, 200);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<Draggable onDrag={onDrag} scale={0.5}>
<div />
</Draggable>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should not throw an error if unmounted during a callback', function () {
function App(props) {
const [firstVisible, setFirstVisible] = React.useState(true);
// Callback with ref to draggable
const dragRef = React.useRef(null);
props.draggableRefCb(dragRef);
return (
<div className="App">
<button onClick={() => setFirstVisible(true)}>Show draggables</button>
{firstVisible && (
<Draggable onStop={() => setFirstVisible(false)} ref={dragRef}>
<h2>1. Drag me!</h2>
</Draggable>
)}
</div>
);
}
let dragRef;
const appContainer = TestUtils.renderIntoDocument(
<App draggableRefCb={(_ref) => {dragRef = _ref;}}/>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(dragRef.current, 0, 0, 100, 100);
// ok, was a setstate warning thrown?
// Assert unmounted
assert.equal(dragRef.current, null);
});
});
describe('DraggableCore callbacks', function () {
it('should call back with node on drag', function(done) {
function onDrag(event, data) {
assert.equal(data.x, 100);
assert.equal(data.y, 100);
assert.equal(data.deltaX, 100);
assert.equal(data.deltaY, 100);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<DraggableCore onDrag={onDrag}>
<div />
</DraggableCore>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with correct position when parent element is 2x scaled', function(done) {
function onDrag(event, data) {
// visually it will look like 100, because parent is 2x scaled
assert.equal(data.x, 50);
assert.equal(data.y, 50);
assert.equal(data.deltaX, 50);
assert.equal(data.deltaY, 50);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<DraggableCore onDrag={onDrag} scale={2}>
<div />
</DraggableCore>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with correct position when parent element is 0.5x scaled', function(done) {
function onDrag(event, data) {
// visually it will look like 100, because parent is 0.5x scaled
assert.equal(data.x, 200);
assert.equal(data.y, 200);
assert.equal(data.deltaX, 200);
assert.equal(data.deltaY, 200);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<DraggableCore onDrag={onDrag} scale={0.5}>
<div />
</DraggableCore>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
it('should call back with snapped data output when grid prop is provided', function(done) {
function onDrag(event, data) {
assert.equal(data.x, 99);
assert.equal(data.y, 96);
assert.equal(data.deltaX, 99);
assert.equal(data.deltaY, 96);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
}
function onStop(event, data) {
assert.equal(data.x, 99);
assert.equal(data.y, 96);
// Single drag-and-stop so stop {x, y} is same as drag {x, y}.
assert.equal(data.deltaX, 0);
assert.equal(data.deltaY, 0);
assert.equal(data.node, ReactDOM.findDOMNode(drag));
done();
}
drag = TestUtils.renderIntoDocument(
<DraggableCore onDrag={onDrag} onStop={onStop} grid={[9, 16]}>
<div />
</DraggableCore>
);
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});
});
describe('validation', function () {
it('should result with invariant when there isn\'t a child', function () {
const renderer = new ShallowRenderer();
assert.throws(() => renderer.render(<Draggable />));
});
it('should result with invariant if there\'s more than a single child', function () {
const renderer = new ShallowRenderer();
assert.throws(() => renderer.render(<Draggable><div/><div/></Draggable>));
});
});
});
function renderToHTML(component) {
return renderToNode(component).outerHTML;
}
function renderToNode(component) {
return ReactDOM.findDOMNode(TestUtils.renderIntoDocument(component));
}
// Simulate a movement; can't use TestUtils here because it uses react's event system only,
// but <DraggableCore> attaches event listeners directly to the document.
// Would love to new MouseEvent() here but it doesn't work with PhantomJS / old browsers.
// var e = new MouseEvent('mousemove', {clientX: 100, clientY: 100});
function mouseMove(x, y, node) {
const doc = node ? node.ownerDocument : document;
const evt = doc.createEvent('MouseEvents');
evt.initMouseEvent('mousemove', true, true, window,
0, 0, 0, x, y, false, false, false, false, 0, null);
doc.dispatchEvent(evt);
return evt;
}
function simulateMovementFromTo(drag, fromX, fromY, toX, toY) {
const node = ReactDOM.findDOMNode(drag);
TestUtils.Simulate.mouseDown(node, {clientX: fromX, clientY: fromY});
mouseMove(toX, toY, node);
TestUtils.Simulate.mouseUp(node);
}
function fragmentFromString(strHTML) {
var temp = document.createElement('div');
temp.innerHTML = strHTML;
return temp.children[0];
}
function transplantNodeInto(node, into, selector) {
node.parentNode.removeChild(node);
selector(into).appendChild(node);
document.body.appendChild(into);
}