| /*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); |
| } |