diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 1d0f554da53..3548cc7faab 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -1822,6 +1822,7 @@ function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { } } + gd.emit('plotly_doubleclick', null); Plotly.relayout(gd, attrs); } diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 3754efce6a7..255b0f9e448 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -171,6 +171,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { searchInfo = searchTraces[i]; searchInfo.selectPoints(searchInfo, false); } + + gd.emit('plotly_deselect', null); } else { dragOptions.gd.emit('plotly_selected', eventData); diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index 18c49542b06..f680d2c6963 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -1,44 +1,62 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); -describe('click event', function() { - var mock = require('@mocks/14.json'); +describe('click interactions', function() { + var mock = require('@mocks/14.json'), + mockCopy = Lib.extendDeep({}, mock), + gd; - describe('event data', function() { - var mockCopy = Lib.extendDeep({}, mock), - clientX = 351, - clientY = 223, - gd; + var pointPos = [351, 223], + blankPos = [70, 363]; - function click() { - mouseEvent('mousemove', clientX, clientY); - mouseEvent('mousedown', clientX, clientY); - mouseEvent('mouseup', clientX, clientY); - } + afterEach(destroyGraphDiv); - beforeEach(function(done) { - gd = createGraphDiv(); + // cartesian click events events use the hover data + // from the mousemove events and then simulate + // a click event on mouseup + function click(x, y) { + mouseEvent('mousemove', x, y); + mouseEvent('mousedown', x, y); + mouseEvent('mouseup', x, y); + } - Plotly.plot(gd, mockCopy.data, mockCopy.layout) - .then(done); - }); + function doubleClick(x, y, cb) { + click(x, y); + setTimeout(function() { + click(x, y); + cb(); + }, DBLCLICKDELAY / 2); + } - afterEach(destroyGraphDiv); + beforeEach(function(done) { + gd = createGraphDiv(); - it('should contain the correct fields', function() { - var futureData; + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + describe('click events', function() { + var futureData; + beforeEach(function() { gd.on('plotly_click', function(data) { futureData = data; }); + }); - click(); + it('should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + it('should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); expect(futureData.points.length).toEqual(1); var pt = futureData.points[0]; @@ -52,4 +70,21 @@ describe('click event', function() { expect(pt.y).toEqual(2.125); }); }); + + describe('double click events', function() { + var futureData; + + beforeEach(function() { + gd.on('plotly_doubleclick', function(data) { + futureData = data; + }); + }); + + it('should return null', function(done) { + doubleClick(pointPos[0], pointPos[1], function() { + expect(futureData).toBe(null); + done(); + }); + }); + }); }); diff --git a/test/jasmine/tests/plot_promise_test.js b/test/jasmine/tests/plot_promise_test.js index 8efe48ab920..9e326625ba9 100644 --- a/test/jasmine/tests/plot_promise_test.js +++ b/test/jasmine/tests/plot_promise_test.js @@ -3,9 +3,12 @@ var Events = require('@src/lib/events'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); + describe('Plotly.___ methods', function() { 'use strict'; + afterEach(destroyGraphDiv); + describe('Plotly.plot promise', function() { var promise, promiseGd; @@ -20,7 +23,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -54,7 +56,6 @@ describe('Plotly.___ methods', function() { }); }); - afterEach(destroyGraphDiv); it('should be rejected when plotly_beforeplot event handlers return false', function() { expect(promiseRejected).toBe(true); @@ -81,7 +82,6 @@ describe('Plotly.___ methods', function() { }); }); - afterEach(destroyGraphDiv); it('should reject the promise when graph is being dragged', function() { expect(promiseRejected).toBe(true); @@ -105,7 +105,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -129,7 +128,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -156,7 +154,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -183,7 +180,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -210,7 +206,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -237,7 +232,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -264,7 +258,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -294,7 +287,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -321,7 +313,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -348,7 +339,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be rejected when the attribute is missing', function() { expect(promiseRejected).toBe(true); @@ -373,7 +363,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -401,7 +390,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div as an argument', function() { expect(promiseGd).toBeDefined(); @@ -430,7 +418,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be returned with the graph div unchanged when the framework is polar', function() { expect(promiseGd).toBeDefined(); @@ -457,7 +444,6 @@ describe('Plotly.___ methods', function() { done(); }); }); - afterEach(destroyGraphDiv); it('should be rejected when the attribute is missing', function() { expect(promiseRejected).toBe(true); diff --git a/test/jasmine/tests/register_test.js b/test/jasmine/tests/register_test.js index 80a2657f3e4..0e5a436ad71 100644 --- a/test/jasmine/tests/register_test.js +++ b/test/jasmine/tests/register_test.js @@ -1,6 +1,27 @@ var Plotly = require('@lib/index'); describe('the register function', function() { + 'use strict'; + + var Plots = Plotly.Plots; + + beforeEach(function() { + this.modulesKeys = Object.keys(Plots.modules); + this.allTypesKeys = Object.keys(Plots.allTypes); + this.allCategoriesKeys = Object.keys(Plots.allCategories); + }); + + afterEach(function() { + function revertObj(obj, initialKeys) { + Object.keys(obj).forEach(function(k) { + if(initialKeys.indexOf(k) === -1) delete obj[k]; + }); + } + + revertObj(Plots.modules, this.modulesKeys); + revertObj(Plots.allTypes, this.allTypesKeys); + revertObj(Plots.allCategories, this.allCategoriesKeys); + }); it('should throw an error when no argument is given', function() { expect(function() { diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js new file mode 100644 index 00000000000..6eb5b27231e --- /dev/null +++ b/test/jasmine/tests/select_test.js @@ -0,0 +1,189 @@ +var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); +var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY; + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); +var customMatchers = require('../assets/custom_matchers'); + + +describe('select box and lasso', function() { + var mock = require('@mocks/14.json'); + + beforeEach(function() { + jasmine.addMatchers(customMatchers); + }); + + afterEach(destroyGraphDiv); + + function drag(path) { + var len = path.length; + + mouseEvent('mousemove', path[0][0], path[0][1]); + mouseEvent('mousedown', path[0][0], path[0][1]); + + path.slice(1, len).forEach(function(pt) { + mouseEvent('mousemove', pt[0], pt[1]); + }); + + mouseEvent('mouseup', path[len - 1][0], path[len - 1][1]); + } + + // cartesian click events events use the hover data + // from the mousemove events and then simulate + // a click event on mouseup + function click(x, y) { + mouseEvent('mousemove', x, y); + mouseEvent('mousedown', x, y); + mouseEvent('mouseup', x, y); + } + + function doubleClick(x, y, cb) { + click(x, y); + setTimeout(function() { + click(x, y); + cb(); + }, DBLCLICKDELAY / 2); + } + + function assertRange(actual, expected) { + var PRECISION = 4; + + expect(actual.x).toBeCloseToArray(expected.x, PRECISION); + expect(actual.y).toBeCloseToArray(expected.y, PRECISION); + } + + describe('select events', function() { + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.dragmode = 'select'; + + var gd; + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + it('should trigger selecting/selected/deselect events', function(done) { + var selectingCnt = 0, + selectingData; + gd.on('plotly_selecting', function(data) { + selectingCnt++; + selectingData = data; + }); + + var selectedCnt = 0, + selectedData; + gd.on('plotly_selected', function(data) { + selectedCnt++; + selectedData = data; + }); + + var doubleClickData; + gd.on('plotly_deselect', function(data) { + doubleClickData = data; + }); + + drag([[100, 200], [150, 200]]); + + expect(selectingCnt).toEqual(1, 'with the correct selecting count'); + expect(selectingData.points).toEqual([{ + curveNumber: 0, + pointNumber: 0, + x: 0.002, + y: 16.25 + }, { + curveNumber: 0, + pointNumber: 1, + x: 0.004, + y: 12.5 + }], 'with the correct selecting points'); + assertRange(selectingData.range, { + x: [0.0019667582669138295, 0.004546754982054625], + y: [0.10209191961595454, 24.512223978291406] + }, 'with the correct selecting range'); + + expect(selectedCnt).toEqual(1, 'with the correct selected count'); + expect(selectedData.points).toEqual([{ + curveNumber: 0, + pointNumber: 0, + x: 0.002, + y: 16.25 + }, { + curveNumber: 0, + pointNumber: 1, + x: 0.004, + y: 12.5 + }], 'with the correct selected points'); + assertRange(selectedData.range, { + x: [0.0019667582669138295, 0.004546754982054625], + y: [0.10209191961595454, 24.512223978291406] + }, 'with the correct selected range'); + + doubleClick(250, 200, function() { + expect(doubleClickData).toBe(null, 'with the correct deselect data'); + done(); + }); + }); + + }); + + describe('lasso events', function() { + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.dragmode = 'lasso'; + + var gd; + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + it('should trigger selecting/selected/deselect events', function(done) { + var selectingCnt = 0, + selectingData; + gd.on('plotly_selecting', function(data) { + selectingCnt++; + selectingData = data; + }); + + var selectedCnt = 0, + selectedData; + gd.on('plotly_selected', function(data) { + selectedCnt++; + selectedData = data; + }); + + var doubleClickData; + gd.on('plotly_deselect', function(data) { + doubleClickData = data; + }); + + drag([[331, 178], [333, 246], [350, 250], [343, 176]]); + + expect(selectingCnt).toEqual(3, 'with the correct selecting count'); + expect(selectingData.points).toEqual([{ + curveNumber: 0, + pointNumber: 10, + x: 0.099, + y: 2.75 + }], 'with the correct selecting points'); + + expect(selectedCnt).toEqual(1, 'with the correct selected count'); + expect(selectedData.points).toEqual([{ + curveNumber: 0, + pointNumber: 10, + x: 0.099, + y: 2.75 + }], 'with the correct selected points'); + + doubleClick(250, 200, function() { + expect(doubleClickData).toBe(null, 'with the correct deselect data'); + done(); + }); + }); + }); +});