Best JavaScript code snippet using playwright-internal
select_test.js
Source:select_test.js  
1var d3Select = require('../../strict-d3').select;2var d3SelectAll = require('../../strict-d3').selectAll;3var Plotly = require('@lib/index');4var Lib = require('@src/lib');5var click = require('../assets/click');6var doubleClick = require('../assets/double_click');7var DBLCLICKDELAY = require('@src/plot_api/plot_config').dfltConfig.doubleClickDelay;8var createGraphDiv = require('../assets/create_graph_div');9var destroyGraphDiv = require('../assets/destroy_graph_div');10var mouseEvent = require('../assets/mouse_event');11var touchEvent = require('../assets/touch_event');12var LONG_TIMEOUT_INTERVAL = 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL;13var delay = require('../assets/delay');14var sankeyConstants = require('@src/traces/sankey/constants');15function drag(path, options) {16    var len = path.length;17    if(!options) options = {type: 'mouse'};18    Lib.clearThrottle();19    if(options.type === 'touch') {20        touchEvent('touchstart', path[0][0], path[0][1], options);21        path.slice(1, len).forEach(function(pt) {22            Lib.clearThrottle();23            touchEvent('touchmove', pt[0], pt[1], options);24        });25        touchEvent('touchend', path[len - 1][0], path[len - 1][1], options);26        return;27    }28    mouseEvent('mousemove', path[0][0], path[0][1], options);29    mouseEvent('mousedown', path[0][0], path[0][1], options);30    path.slice(1, len).forEach(function(pt) {31        Lib.clearThrottle();32        mouseEvent('mousemove', pt[0], pt[1], options);33    });34    mouseEvent('mouseup', path[len - 1][0], path[len - 1][1], options);35}36function assertSelectionNodes(cornerCnt, outlineCnt, _msg) {37    var msg = _msg ? ' - ' + _msg : '';38    expect(d3SelectAll('.zoomlayer > .zoombox-corners').size())39        .toBe(cornerCnt, 'selection corner count' + msg);40    expect(d3SelectAll('.zoomlayer > .select-outline').size())41        .toBe(outlineCnt, 'selection outline count' + msg);42}43var selectingCnt, selectingData, selectedCnt, selectedData, deselectCnt, doubleClickData;44var selectedPromise, deselectPromise, clickedPromise;45function resetEvents(gd) {46    selectingCnt = 0;47    selectedCnt = 0;48    deselectCnt = 0;49    doubleClickData = null;50    gd.removeAllListeners();51    selectedPromise = new Promise(function(resolve) {52        gd.on('plotly_selecting', function(data) {53            // note that since all of these events test node counts,54            // and all of the other tests at some point check that each of55            // these event handlers was called (via assertEventCounts),56            // we no longer need separate tests that these nodes are created57            // and this way *all* subplot variants get the test.58            assertSelectionNodes(1, 2);59            selectingCnt++;60            selectingData = data;61        });62        gd.on('plotly_selected', function(data) {63            // With click-to-select supported, selection nodes are only64            // in the DOM in certain circumstances.65            if(data &&66              gd._fullLayout.dragmode.indexOf('select') > -1 &&67              gd._fullLayout.dragmode.indexOf('lasso') > -1) {68                assertSelectionNodes(0, 2);69            }70            selectedCnt++;71            selectedData = data;72            resolve();73        });74    });75    deselectPromise = new Promise(function(resolve) {76        gd.on('plotly_deselect', function(data) {77            assertSelectionNodes(0, 0);78            deselectCnt++;79            doubleClickData = data;80            resolve();81        });82    });83    clickedPromise = new Promise(function(resolve) {84        gd.on('plotly_click', function() {85            resolve();86        });87    });88}89function assertEventCounts(selecting, selected, deselect, msg) {90    expect(selectingCnt).toBe(selecting, 'plotly_selecting call count: ' + msg);91    expect(selectedCnt).toBe(selected, 'plotly_selected call count: ' + msg);92    expect(deselectCnt).toBe(deselect, 'plotly_deselect call count: ' + msg);93}94// TODO: in v3, when we get rid of the `plotly_selected->undefined` event, these will95// change to BOXEVENTS = [1, 1, 1], LASSOEVENTS = [4, 1, 1]. See also _run down below96//97// events for box or lasso select mouse moves then a doubleclick98var NOEVENTS = [0, 0, 0];99// deselect used to give an extra plotly_selected event on the first click100// with undefined event data - but now that's gone, since `clickFn` handles this.101var BOXEVENTS = [1, 2, 1];102// assumes 5 points in the lasso path103var LASSOEVENTS = [4, 2, 1];104var SELECT_PATH = [[93, 193], [143, 193]];105var LASSO_PATH = [[316, 171], [318, 239], [335, 243], [328, 169]];106describe('Click-to-select', function() {107    var mock14Pts = {108        '1': { x: 134, y: 116 },109        '7': { x: 270, y: 160 },110        '10': { x: 324, y: 198 },111        '35': { x: 685, y: 341 }112    };113    var gd;114    beforeEach(function() {115        gd = createGraphDiv();116    });117    afterEach(destroyGraphDiv);118    function plotMock14(layoutOpts) {119        var mock = require('@mocks/14.json');120        var defaultLayoutOpts = {121            layout: {122                clickmode: 'event+select',123                dragmode: 'select',124                hovermode: 'closest'125            }126        };127        var mockCopy = Lib.extendDeep(128          {},129          mock,130          defaultLayoutOpts,131          { layout: layoutOpts });132        return Plotly.newPlot(gd, mockCopy.data, mockCopy.layout);133    }134    /**135     * Executes a click and before resets selection event handlers.136     * By default, click is executed with a delay to prevent unwanted double clicks.137     * Returns the `selectedPromise` promise for convenience.138     */139    function _click(x, y, clickOpts, immediate) {140        resetEvents(gd);141        // Too fast subsequent calls of `click` would142        // produce an unwanted double click, thus we need143        // to delay the click.144        if(immediate) {145            click(x, y, clickOpts);146        } else {147            setTimeout(function() {148                click(x, y, clickOpts);149            }, DBLCLICKDELAY * 1.03);150        }151        return selectedPromise;152    }153    function _clickPt(coords, clickOpts, immediate) {154        expect(coords).toBeDefined('coords needs to be defined');155        expect(coords.x).toBeDefined('coords.x needs to be defined');156        expect(coords.y).toBeDefined('coords.y needs to be defined');157        return _click(coords.x, coords.y, clickOpts, immediate);158    }159    /**160     * Convenient helper to execute a click immediately.161     */162    function _immediateClickPt(coords, clickOpts) {163        return _clickPt(coords, clickOpts, true);164    }165    /**166     * Asserting selected points.167     *168     * @param expected can be a point number, an array169     * of point numbers (for a single trace) or an array of point number170     * arrays in case of multiple traces. undefined in an array of arrays171     * is also allowed, e.g. useful when not all traces support selection.172     */173    function assertSelectedPoints(expected) {174        var expectedPtsPerTrace = toArrayOfArrays(expected);175        var expectedPts, traceNum;176        for(traceNum = 0; traceNum < expectedPtsPerTrace.length; traceNum++) {177            expectedPts = expectedPtsPerTrace[traceNum];178            expect(gd._fullData[traceNum].selectedpoints).toEqual(expectedPts);179            expect(gd.data[traceNum].selectedpoints).toEqual(expectedPts);180        }181        function toArrayOfArrays(expected) {182            var isArrayInArray, i;183            if(Array.isArray(expected)) {184                isArrayInArray = false;185                for(i = 0; i < expected.length; i++) {186                    if(Array.isArray(expected[i])) {187                        isArrayInArray = true;188                        break;189                    }190                }191                return isArrayInArray ? expected : [expected];192            } else {193                return [[expected]];194            }195        }196    }197    function assertSelectionCleared() {198        gd._fullData.forEach(function(fullDataItem) {199            expect(fullDataItem.selectedpoints).toBeUndefined();200        });201    }202    it('selects a single data point when being clicked', function(done) {203        plotMock14()204          .then(function() { return _immediateClickPt(mock14Pts[7]); })205          .then(function() { assertSelectedPoints(7); })206          .then(done, done.fail);207    });208    describe('clears entire selection when the last selected data point', function() {209        [{210            desc: 'is clicked',211            clickOpts: {}212        }, {213            desc: 'is clicked while add/subtract modifier keys are active',214            clickOpts: { shiftKey: true }215        }].forEach(function(testData) {216            it('' + testData.desc, function(done) {217                plotMock14()218                  .then(function() { return _immediateClickPt(mock14Pts[7]); })219                  .then(function() {220                      assertSelectedPoints(7);221                      _clickPt(mock14Pts[7], testData.clickOpts);222                      return deselectPromise;223                  })224                  .then(function() {225                      assertSelectionCleared();226                      return _clickPt(mock14Pts[35], testData.clickOpts);227                  })228                  .then(function() {229                      assertSelectedPoints(35);230                  })231                  .then(done, done.fail);232            });233        });234    });235    it('cleanly clears and starts selections although add/subtract mode on', function(done) {236        plotMock14()237          .then(function() {238              return _immediateClickPt(mock14Pts[7]);239          })240          .then(function() {241              assertSelectedPoints(7);242              _clickPt(mock14Pts[7], { shiftKey: true });243              return deselectPromise;244          })245          .then(function() {246              assertSelectionCleared();247              return _clickPt(mock14Pts[35], { shiftKey: true });248          })249          .then(function() {250              assertSelectedPoints(35);251          })252          .then(done, done.fail);253    });254    it('supports adding to an existing selection', function(done) {255        plotMock14()256          .then(function() { return _immediateClickPt(mock14Pts[7]); })257          .then(function() {258              assertSelectedPoints(7);259              return _clickPt(mock14Pts[35], { shiftKey: true });260          })261          .then(function() { assertSelectedPoints([7, 35]); })262          .then(done, done.fail);263    });264    it('supports subtracting from an existing selection', function(done) {265        plotMock14()266          .then(function() { return _immediateClickPt(mock14Pts[7]); })267          .then(function() {268              assertSelectedPoints(7);269              return _clickPt(mock14Pts[35], { shiftKey: true });270          })271          .then(function() {272              assertSelectedPoints([7, 35]);273              return _clickPt(mock14Pts[7], { shiftKey: true });274          })275          .then(function() { assertSelectedPoints(35); })276          .then(done, done.fail);277    });278    it('can be used interchangeably with lasso/box select', function(done) {279        plotMock14()280          .then(function() {281              return _immediateClickPt(mock14Pts[35]);282          })283          .then(function() {284              assertSelectedPoints(35);285              drag(SELECT_PATH, { shiftKey: true });286          })287          .then(function() {288              assertSelectedPoints([0, 1, 35]);289              return _immediateClickPt(mock14Pts[7], { shiftKey: true });290          })291          .then(function() {292              assertSelectedPoints([0, 1, 7, 35]);293              return _clickPt(mock14Pts[1], { shiftKey: true });294          })295          .then(function() {296              assertSelectedPoints([0, 7, 35]);297              return Plotly.relayout(gd, 'dragmode', 'lasso');298          })299          .then(function() {300              assertSelectedPoints([0, 7, 35]);301              drag(LASSO_PATH, { shiftKey: true });302          })303          .then(function() {304              assertSelectedPoints([0, 7, 10, 35]);305              return _clickPt(mock14Pts[10], { shiftKey: true });306          })307          .then(function() {308              assertSelectedPoints([0, 7, 35]);309              drag([[670, 330], [695, 330], [695, 350], [670, 350]],310                { shiftKey: true, altKey: true });311          })312          .then(function() {313              assertSelectedPoints([0, 7]);314              return _clickPt(mock14Pts[35], { shiftKey: true });315          })316          .then(function() {317              assertSelectedPoints([0, 7, 35]);318              return _clickPt(mock14Pts[7]);319          })320          .then(function() {321              assertSelectedPoints([7]);322              return doubleClick(650, 100);323          })324          .then(function() {325              assertSelectionCleared();326          })327          .then(done, done.fail);328    });329    it('@gl works in a multi-trace plot', function(done) {330        Plotly.newPlot(gd, [331            {332                x: [1, 3, 5, 4, 10, 12, 12, 7],333                y: [2, 7, 6, 1, 0, 13, 6, 12],334                type: 'scatter',335                mode: 'markers',336                marker: { size: 20 }337            }, {338                x: [1, 7, 6, 2],339                y: [2, 3, 5, 4],340                type: 'bar'341            }, {342                x: [7, 8, 9, 10],343                y: [7, 9, 13, 21],344                type: 'scattergl',345                mode: 'markers',346                marker: { size: 20 }347            }348        ], {349            width: 400,350            height: 600,351            hovermode: 'closest',352            dragmode: 'select',353            clickmode: 'event+select'354        })355          .then(function() {356              return _click(136, 369, {}, true);357          })358          .then(function() {359              assertSelectedPoints([[1], [], []]);360              return _click(245, 136, { shiftKey: true });361          })362          .then(function() {363              assertSelectedPoints([[1], [], [3]]);364              return _click(183, 470, { shiftKey: true });365          })366          .then(function() {367              assertSelectedPoints([[1], [2], [3]]);368          })369          .then(done, done.fail);370    });371    it('is supported in pan/zoom mode', function(done) {372        plotMock14({ dragmode: 'zoom' })373          .then(function() {374              return _immediateClickPt(mock14Pts[35]);375          })376          .then(function() {377              assertSelectedPoints(35);378              return _clickPt(mock14Pts[7], { shiftKey: true });379          })380          .then(function() {381              assertSelectedPoints([7, 35]);382              return _clickPt(mock14Pts[7], { shiftKey: true });383          })384          .then(function() {385              assertSelectedPoints(35);386              _clickPt(mock14Pts[35], { shiftKey: true });387              return deselectPromise;388          })389          .then(function() {390              assertSelectionCleared();391              return _clickPt(mock14Pts[7], { shiftKey: true });392          })393          .then(function() {394              assertSelectedPoints(7);395              drag([[110, 100], [300, 300]]);396          })397          .then(delay(100))398          .then(function() {399              // persist after zoombox400              assertSelectedPoints(7);401          })402          .then(done, done.fail);403    });404    it('retains selected points when switching between pan and zoom mode', function(done) {405        plotMock14({ dragmode: 'zoom' })406          .then(function() {407              return _immediateClickPt(mock14Pts[35]);408          })409          .then(function() {410              assertSelectedPoints(35);411              return Plotly.relayout(gd, 'dragmode', 'pan');412          })413          .then(function() {414              assertSelectedPoints(35);415              return _clickPt(mock14Pts[7], { shiftKey: true });416          })417          .then(function() {418              assertSelectedPoints([7, 35]);419              return Plotly.relayout(gd, 'dragmode', 'zoom');420          })421          .then(function() {422              assertSelectedPoints([7, 35]);423              return _clickPt(mock14Pts[7], { shiftKey: true });424          })425          .then(function() {426              assertSelectedPoints(35);427          })428          .then(done, done.fail);429    });430    it('@gl is supported by scattergl in pan/zoom mode', function(done) {431        Plotly.newPlot(gd, [432            {433                x: [7, 8, 9, 10],434                y: [7, 9, 13, 21],435                type: 'scattergl',436                mode: 'markers',437                marker: { size: 20 }438            }439        ], {440            width: 400,441            height: 600,442            hovermode: 'closest',443            dragmode: 'zoom',444            clickmode: 'event+select'445        })446          .then(function() {447              return _click(230, 340, {}, true);448          })449          .then(function() {450              assertSelectedPoints(2);451          })452          .then(done, done.fail);453    });454    it('deals correctly with histogram\'s binning in the persistent selection case', function(done) {455        var mock = require('@mocks/histogram_colorscale.json');456        var firstBinPts = [0];457        var secondBinPts = [1, 2];458        var thirdBinPts = [3, 4, 5];459        mock.layout.clickmode = 'event+select';460        Plotly.newPlot(gd, mock.data, mock.layout)461          .then(function() {462              return clickFirstBinImmediately();463          })464          .then(function() {465              assertSelectedPoints(firstBinPts);466              return shiftClickSecondBin();467          })468          .then(function() {469              assertSelectedPoints([].concat(firstBinPts, secondBinPts));470              return shiftClickThirdBin();471          })472          .then(function() {473              assertSelectedPoints([].concat(firstBinPts, secondBinPts, thirdBinPts));474              return clickFirstBin();475          })476          .then(function() {477              assertSelectedPoints([].concat(firstBinPts));478              clickFirstBin();479              return deselectPromise;480          })481          .then(function() {482              assertSelectionCleared();483          })484          .then(done, done.fail);485        function clickFirstBinImmediately() { return _immediateClickPt({ x: 141, y: 358 }); }486        function clickFirstBin() { return _click(141, 358); }487        function shiftClickSecondBin() { return _click(239, 330, { shiftKey: true }); }488        function shiftClickThirdBin() { return _click(351, 347, { shiftKey: true }); }489    });490    it('ignores clicks on boxes in a box trace type', function(done) {491        var mock = Lib.extendDeep({}, require('@mocks/box_grouped_horz.json'));492        mock.layout.clickmode = 'event+select';493        mock.layout.width = 1100;494        mock.layout.height = 450;495        Plotly.newPlot(gd, mock.data, mock.layout)496          .then(function() {497              return clickPtImmediately();498          })499          .then(function() {500              assertSelectedPoints(2);501              clickPt();502              return deselectPromise;503          })504          .then(function() {505              assertSelectionCleared();506              clickBox();507              return clickedPromise;508          })509          .then(function() {510              assertSelectionCleared();511          })512          .then(done, done.fail);513        function clickPtImmediately() { return _immediateClickPt({ x: 610, y: 342 }); }514        function clickPt() { return _clickPt({ x: 610, y: 342 }); }515        function clickBox() { return _clickPt({ x: 565, y: 329 }); }516    });517    describe('is disabled when clickmode does not include \'select\'', function() {518        ['select', 'lasso']519          .forEach(function(dragmode) {520              it('and dragmode is ' + dragmode, function(done) {521                  plotMock14({ clickmode: 'event', dragmode: dragmode })522                    .then(function() {523                        // Still, the plotly_selected event should be thrown,524                        // so return promise here525                        return _immediateClickPt(mock14Pts[1]);526                    })527                    .then(function() {528                        assertSelectionCleared();529                    })530                    .then(done, done.fail);531              });532          });533    });534    describe('is disabled when clickmode does not include \'select\'', function() {535        ['pan', 'zoom']536          .forEach(function(dragmode) {537              it('and dragmode is ' + dragmode, function(done) {538                  plotMock14({ clickmode: 'event', dragmode: dragmode })539                    .then(function() {540                        _immediateClickPt(mock14Pts[1]);541                        return clickedPromise;542                    })543                    .then(function() {544                        assertSelectionCleared();545                    })546                    .then(done, done.fail);547              });548          });549    });550    describe('is supported by', function() {551        // On loading mocks:552        // - Note, that `require` function calls are resolved at compile time553        //   and thus dynamically concatenated mock paths won't work.554        // - Some mocks don't specify a width and height, so this needs555        //   to be set explicitly to ensure click coordinates fit.556        // The non-gl traces: use CI annotation557        [558            testCase('histrogram', require('@mocks/histogram_colorscale.json'), 355, 301, [3, 4, 5]),559            testCase('box', require('@mocks/box_grouped_horz.json'), 610, 342, [[2], [], []],560              { width: 1100, height: 450 }),561            testCase('violin', require('@mocks/violin_grouped.json'), 166, 187, [[3], [], []],562              { width: 1100, height: 450 }),563            testCase('ohlc', require('@mocks/ohlc_first.json'), 669, 165, [9]),564            testCase('candlestick', require('@mocks/finance_style.json'), 331, 162, [[], [5]]),565            testCase('choropleth', require('@mocks/geo_choropleth-text.json'), 440, 163, [6]),566            testCase('scattergeo', require('@mocks/geo_scattergeo-locations.json'), 285, 240, [1]),567            testCase('scatterternary', require('@mocks/ternary_markers.json'), 485, 335, [7]),568            // Note that first trace (carpet) in mock doesn't support selection,569            // thus undefined is expected570            testCase('scattercarpet', require('@mocks/scattercarpet.json'), 532, 178,571              [undefined, [], [], [], [], [], [2]], { width: 1100, height: 450 }),572            // scatterpolar and scatterpolargl do not support pan (the default),573            // so set dragmode to zoom574            testCase('scatterpolar', require('@mocks/polar_scatter.json'), 130, 290,575              [[], [], [], [19], [], []], { dragmode: 'zoom' }),576        ]577          .forEach(function(testCase) {578              it('trace type ' + testCase.label, function(done) {579                  _run(testCase, done);580              });581          });582        [583            testCase('scatterpolargl', require('@mocks/glpolar_scatter.json'), 130, 290,584              [[], [], [], [19], [], []], { dragmode: 'zoom' }),585            testCase('splom', require('@mocks/splom_lower.json'), 427, 400, [[], [7], []])586        ]587          .forEach(function(testCase) {588              it('@gl trace type ' + testCase.label, function(done) {589                  _run(testCase, done);590              });591          });592        [593            testCase('scattermapbox', require('@mocks/mapbox_0.json'), 650, 195, [[2], []], {},594              { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }),595            testCase('choroplethmapbox', require('@mocks/mapbox_choropleth0.json'), 270, 220, [[0]], {},596              { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN })597        ]598          .forEach(function(testCase) {599              it('@gl trace type ' + testCase.label, function(done) {600                  _run(testCase, done);601              });602          });603        function _run(testCase, doneFn) {604            Plotly.newPlot(gd, testCase.mock.data, testCase.mock.layout, testCase.mock.config)605              .then(function() {606                  return _immediateClickPt(testCase);607              })608              .then(function() {609                  assertSelectedPoints(testCase.expectedPts);610                  return Plotly.relayout(gd, 'dragmode', 'lasso');611              })612              .then(function() {613                  _clickPt(testCase);614                  return deselectPromise;615              })616              .then(function() {617                  assertSelectionCleared();618                  return _clickPt(testCase);619              })620              .then(function() {621                  assertSelectedPoints(testCase.expectedPts);622              })623              .then(doneFn, doneFn.fail);624        }625    });626    it('should maintain style of errorbars after double click cleared selection (bar case)', function(done) {627        Plotly.newPlot(gd, { // Note: this call should be newPlot not plot628            data: [{629                x: [0, 1, 2],630                y: [100, 200, 400],631                type: 'bar',632                marker: {633                    color: 'yellow'634                },635                error_y: {636                    type: 'sqrt'637                }638            }],639            layout: {640                dragmode: 'select'641            }642        })643        .then(function() {644            var x = 100;645            var y = 100;646            drag([[x, y], [x, y]]); // first empty drag647            return doubleClick(x, y); // then double click648        })649        .then(function() {650            assertSelectionCleared();651        })652        .then(function() {653            d3Select(gd).select('g.plot').each(function() {654                d3Select(this).selectAll('g.errorbar').selectAll('path').each(function() {655                    expect(d3Select(this).attr('style'))656                        .toBe('vector-effect: non-scaling-stroke; stroke-width: 2px; stroke: rgb(68, 68, 68); stroke-opacity: 1; opacity: 1; fill: rgb(255, 255, 0); fill-opacity: 1;', 'to be visible'657                    );658                });659            });660        })661        .then(done, done.fail);662    });663    describe('triggers \'plotly_selected\' before \'plotly_click\'', function() {664        [665            testCase('cartesian', require('@mocks/14.json'), 270, 160, [7]),666            testCase('geo', require('@mocks/geo_scattergeo-locations.json'), 285, 240, [1]),667            testCase('ternary', require('@mocks/ternary_markers.json'), 485, 335, [7]),668            testCase('polar', require('@mocks/polar_scatter.json'), 130, 290,669              [[], [], [], [19], [], []], { dragmode: 'zoom' })670        ].forEach(function(testCase) {671            it('for base plot ' + testCase.label, function(done) {672                _run(testCase, done);673            });674        });675        [676            testCase('mapbox', require('@mocks/mapbox_0.json'), 650, 195, [[2], []], {},677              { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }),678            testCase('mapbox', require('@mocks/mapbox_choropleth0.json'), 270, 220, [[0], []], {},679              { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN })680        ].forEach(function(testCase) {681            it('@gl for base plot ' + testCase.label, function(done) {682                _run(testCase, done);683            });684        });685        function _run(testCase, doneFn) {686            Plotly.newPlot(gd, testCase.mock.data, testCase.mock.layout, testCase.mock.config)687              .then(function() {688                  var clickHandlerCalled = false;689                  var selectedHandlerCalled = false;690                  gd.on('plotly_selected', function() {691                      expect(clickHandlerCalled).toBe(false);692                      selectedHandlerCalled = true;693                  });694                  gd.on('plotly_click', function() {695                      clickHandlerCalled = true;696                      expect(selectedHandlerCalled).toBe(true);697                      doneFn();698                  });699                  return click(testCase.x, testCase.y);700              })701              .then(doneFn, doneFn.fail);702        }703    });704    function testCase(label, mock, x, y, expectedPts, layoutOptions, configOptions) {705        var defaultLayoutOpts = {706            layout: {707                clickmode: 'event+select',708                dragmode: 'pan',709                hovermode: 'closest'710            }711        };712        var customLayoutOptions = {713            layout: layoutOptions714        };715        var customConfigOptions = {716            config: configOptions717        };718        var mockCopy = Lib.extendDeep(719          {},720          mock,721          defaultLayoutOpts,722          customLayoutOptions,723          customConfigOptions);724        return {725            label: label,726            mock: mockCopy,727            layoutOptions: layoutOptions,728            x: x,729            y: y,730            expectedPts: expectedPts,731            configOptions: configOptions732        };733    }734});735describe('Test select box and lasso in general:', function() {736    var mock = require('@mocks/14.json');737    var selectPath = [[93, 193], [143, 193]];738    var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]];739    afterEach(destroyGraphDiv);740    function assertRange(actual, expected) {741        var PRECISION = 4;742        expect(actual.x).toBeCloseToArray(expected.x, PRECISION);743        expect(actual.y).toBeCloseToArray(expected.y, PRECISION);744    }745    function assertEventData(actual, expected, msg) {746        expect(actual.length).toBe(expected.length, msg + ' same number of pts');747        expected.forEach(function(e, i) {748            var a = actual[i];749            var m = msg + ' (pt ' + i + ')';750            expect(a.data).toBeDefined(m + ' has data ref');751            expect(a.fullData).toBeDefined(m + ' has fullData ref');752            expect(Object.keys(a).length - 2).toBe(Object.keys(e).length, m + ' has correct number of keys');753            Object.keys(e).forEach(function(k) {754                expect(a[k]).toBe(e[k], m + ' ' + k);755            });756        });757    }758    describe('select events', function() {759        var mockCopy = Lib.extendDeep({}, mock);760        mockCopy.layout.dragmode = 'select';761        mockCopy.layout.hovermode = 'closest';762        mockCopy.data[0].ids = mockCopy.data[0].x763            .map(function(v) { return 'id-' + v; });764        mockCopy.data[0].customdata = mockCopy.data[0].y765            .map(function(v) { return 'customdata-' + v; });766        addInvisible(mockCopy);767        var gd;768        beforeEach(function(done) {769            gd = createGraphDiv();770            Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)771                .then(done);772        });773        it('should trigger selecting/selected/deselect events', function(done) {774            resetEvents(gd);775            drag(selectPath);776            selectedPromise.then(function() {777                expect(selectedCnt).toBe(1, 'with the correct selected count');778                assertEventData(selectedData.points, [{779                    curveNumber: 0,780                    pointNumber: 0,781                    pointIndex: 0,782                    x: 0.002,783                    y: 16.25,784                    id: 'id-0.002',785                    customdata: 'customdata-16.25'786                }, {787                    curveNumber: 0,788                    pointNumber: 1,789                    pointIndex: 1,790                    x: 0.004,791                    y: 12.5,792                    id: 'id-0.004',793                    customdata: 'customdata-12.5'794                }], 'with the correct selected points (2)');795                assertRange(selectedData.range, {796                    x: [0.002000, 0.0046236],797                    y: [0.10209191961595454, 24.512223978291406]798                }, 'with the correct selected range');799                return doubleClick(250, 200);800            })801            .then(deselectPromise)802            .then(function() {803                expect(doubleClickData).toBe(null, 'with the correct deselect data');804            })805            .then(done, done.fail);806        });807        it('should handle add/sub selection', function(done) {808            resetEvents(gd);809            drag(selectPath);810            selectedPromise.then(function() {811                expect(selectingCnt).toBe(1, 'with the correct selecting count');812                assertEventData(selectingData.points, [{813                    curveNumber: 0,814                    pointNumber: 0,815                    pointIndex: 0,816                    x: 0.002,817                    y: 16.25,818                    id: 'id-0.002',819                    customdata: 'customdata-16.25'820                }, {821                    curveNumber: 0,822                    pointNumber: 1,823                    pointIndex: 1,824                    x: 0.004,825                    y: 12.5,826                    id: 'id-0.004',827                    customdata: 'customdata-12.5'828                }], 'with the correct selecting points (1)');829                assertRange(selectingData.range, {830                    x: [0.002000, 0.0046236],831                    y: [0.10209191961595454, 24.512223978291406]832                }, 'with the correct selecting range');833            })834            .then(function() {835                // add selection836                drag([[193, 193], [213, 193]], {shiftKey: true});837            })838            .then(function() {839                expect(selectingCnt).toBe(2, 'with the correct selecting count');840                assertEventData(selectingData.points, [{841                    curveNumber: 0,842                    pointNumber: 0,843                    pointIndex: 0,844                    x: 0.002,845                    y: 16.25,846                    id: 'id-0.002',847                    customdata: 'customdata-16.25'848                }, {849                    curveNumber: 0,850                    pointNumber: 1,851                    pointIndex: 1,852                    x: 0.004,853                    y: 12.5,854                    id: 'id-0.004',855                    customdata: 'customdata-12.5'856                }, {857                    curveNumber: 0,858                    pointNumber: 4,859                    pointIndex: 4,860                    x: 0.013,861                    y: 6.875,862                    id: 'id-0.013',863                    customdata: 'customdata-6.875'864                }], 'with the correct selecting points (1)');865            })866            .then(function() {867                // sub selection868                drag([[219, 143], [219, 183]], {altKey: true});869            }).then(function() {870                assertEventData(selectingData.points, [{871                    curveNumber: 0,872                    pointNumber: 0,873                    pointIndex: 0,874                    x: 0.002,875                    y: 16.25,876                    id: 'id-0.002',877                    customdata: 'customdata-16.25'878                }, {879                    curveNumber: 0,880                    pointNumber: 1,881                    pointIndex: 1,882                    x: 0.004,883                    y: 12.5,884                    id: 'id-0.004',885                    customdata: 'customdata-12.5'886                }], 'with the correct selecting points (1)');887                return doubleClick(250, 200);888            })889            .then(function() {890                expect(doubleClickData).toBe(null, 'with the correct deselect data');891            })892            .then(done, done.fail);893        });894    });895    describe('lasso events', function() {896        var mockCopy = Lib.extendDeep({}, mock);897        mockCopy.layout.dragmode = 'lasso';898        mockCopy.layout.hovermode = 'closest';899        addInvisible(mockCopy);900        var gd;901        beforeEach(function(done) {902            gd = createGraphDiv();903            Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)904                .then(done);905        });906        it('should trigger selecting/selected/deselect events', function(done) {907            resetEvents(gd);908            drag(lassoPath);909            selectedPromise.then(function() {910                expect(selectingCnt).toBe(3, 'with the correct selecting count');911                assertEventData(selectingData.points, [{912                    curveNumber: 0,913                    pointNumber: 10,914                    pointIndex: 10,915                    x: 0.099,916                    y: 2.75917                }], 'with the correct selecting points (1)');918                expect(selectedCnt).toBe(1, 'with the correct selected count');919                assertEventData(selectedData.points, [{920                    curveNumber: 0,921                    pointNumber: 10,922                    pointIndex: 10,923                    x: 0.099,924                    y: 2.75,925                }], 'with the correct selected points (2)');926                expect(selectedData.lassoPoints.x).toBeCloseToArray(927                    [0.084, 0.087, 0.115, 0.103], 'lasso points x coords');928                expect(selectedData.lassoPoints.y).toBeCloseToArray(929                    [4.648, 1.342, 1.247, 4.821], 'lasso points y coords');930                return doubleClick(250, 200);931            })932            .then(deselectPromise)933            .then(function() {934                expect(doubleClickData).toBe(null, 'with the correct deselect data');935            })936            .then(done, done.fail);937        });938        it('should set selected points in graph data', function(done) {939            resetEvents(gd);940            drag(lassoPath);941            selectedPromise.then(function() {942                expect(selectingCnt).toBe(3, 'with the correct selecting count');943                expect(gd.data[0].selectedpoints).toEqual([10]);944                return doubleClick(250, 200);945            })946            .then(deselectPromise)947            .then(function() {948                expect(gd.data[0].selectedpoints).toBeUndefined();949            })950            .then(done, done.fail);951        });952        it('should set selected points in full data', function(done) {953            resetEvents(gd);954            drag(lassoPath);955            selectedPromise.then(function() {956                expect(selectingCnt).toBe(3, 'with the correct selecting count');957                expect(gd._fullData[0].selectedpoints).toEqual([10]);958                return doubleClick(250, 200);959            })960            .then(deselectPromise)961            .then(function() {962                expect(gd._fullData[0].selectedpoints).toBeUndefined();963            })964            .then(done, done.fail);965        });966        it('should trigger selecting/selected/deselect events for touches', function(done) {967            resetEvents(gd);968            drag(lassoPath, {type: 'touch'});969            selectedPromise.then(function() {970                expect(selectingCnt).toBe(3, 'with the correct selecting count');971                assertEventData(selectingData.points, [{972                    curveNumber: 0,973                    pointNumber: 10,974                    pointIndex: 10,975                    x: 0.099,976                    y: 2.75977                }], 'with the correct selecting points (1)');978                expect(selectedCnt).toBe(1, 'with the correct selected count');979                assertEventData(selectedData.points, [{980                    curveNumber: 0,981                    pointNumber: 10,982                    pointIndex: 10,983                    x: 0.099,984                    y: 2.75,985                }], 'with the correct selected points (2)');986                return doubleClick(250, 200);987            })988            .then(deselectPromise)989            .then(function() {990                expect(doubleClickData).toBe(null, 'with the correct deselect data');991            })992            .then(done, done.fail);993        });994    });995    it('should skip over non-visible traces', function(done) {996        // note: this tests a mock with one or several invisible traces997        // the invisible traces in the other tests test for multiple998        // traces, with some visible and some not.999        var mockCopy = Lib.extendDeep({}, mock);1000        mockCopy.layout.dragmode = 'select';1001        var gd = createGraphDiv();1002        function resetAndSelect() {1003            resetEvents(gd);1004            drag(selectPath);1005            return selectedPromise;1006        }1007        function resetAndLasso() {1008            resetEvents(gd);1009            drag(lassoPath);1010            return selectedPromise;1011        }1012        function checkPointCount(cnt, msg) {1013            expect((selectedData.points || []).length).toBe(cnt, msg);1014        }1015        Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)1016        .then(resetAndSelect)1017        .then(function() {1018            checkPointCount(2, '(case 0)');1019            return Plotly.restyle(gd, 'visible', 'legendonly');1020        })1021        .then(resetAndSelect)1022        .then(function() {1023            checkPointCount(0, '(legendonly case)');1024            return Plotly.restyle(gd, 'visible', true);1025        })1026        .then(resetAndSelect)1027        .then(function() {1028            checkPointCount(2, '(back to case 0)');1029            return Plotly.relayout(gd, 'dragmode', 'lasso');1030        })1031        .then(resetAndLasso)1032        .then(function() {1033            checkPointCount(1, '(case 0 lasso)');1034            return Plotly.restyle(gd, 'visible', 'legendonly');1035        })1036        .then(resetAndSelect)1037        .then(function() {1038            checkPointCount(0, '(lasso legendonly case)');1039            return Plotly.restyle(gd, 'visible', true);1040        })1041        .then(resetAndLasso)1042        .then(function() {1043            checkPointCount(1, '(back to lasso case 0)');1044            mockCopy = Lib.extendDeep({}, mock);1045            mockCopy.layout.dragmode = 'select';1046            mockCopy.data[0].visible = false;1047            addInvisible(mockCopy);1048            return Plotly.newPlot(gd, mockCopy);1049        })1050        .then(resetAndSelect)1051        .then(function() {1052            checkPointCount(0, '(multiple invisible traces select)');1053            return Plotly.relayout(gd, 'dragmode', 'lasso');1054        })1055        .then(resetAndLasso)1056        .then(function() {1057            checkPointCount(0, '(multiple invisible traces lasso)');1058        })1059        .then(done, done.fail);1060    });1061    it('should skip over BADNUM items', function(done) {1062        var data = [{1063            mode: 'markers',1064            x: [null, undefined, NaN, 0, 'NA'],1065            y: [NaN, null, undefined, 0, 'NA']1066        }];1067        var layout = {1068            dragmode: 'select',1069            width: 400,1070            heigth: 400,1071        };1072        var gd = createGraphDiv();1073        Plotly.newPlot(gd, data, layout).then(function() {1074            resetEvents(gd);1075            drag([[100, 100], [300, 300]]);1076            return selectedPromise;1077        })1078        .then(function() {1079            expect(selectedData.points.length).toBe(1);1080            expect(selectedData.points[0].x).toBe(0);1081            expect(selectedData.points[0].y).toBe(0);1082            return Plotly.relayout(gd, 'dragmode', 'lasso');1083        })1084        .then(function() {1085            resetEvents(gd);1086            drag([[100, 100], [100, 300], [300, 300], [300, 100], [100, 100]]);1087            return selectedPromise;1088        })1089        .then(function() {1090            expect(selectedData.points.length).toBe(1);1091            expect(selectedData.points[0].x).toBe(0);1092            expect(selectedData.points[0].y).toBe(0);1093        })1094        .then(done, done.fail);1095    });1096    it('scroll zoom should clear selection regions', function(done) {1097        var gd = createGraphDiv();1098        var mockCopy = Lib.extendDeep({}, mock);1099        mockCopy.layout.dragmode = 'select';1100        mockCopy.config = {scrollZoom: true};1101        function _drag() {1102            resetEvents(gd);1103            drag(selectPath);1104            return selectedPromise;1105        }1106        function _scroll() {1107            mouseEvent('mousemove', selectPath[0][0], selectPath[0][1]);1108            mouseEvent('scroll', selectPath[0][0], selectPath[0][1], {deltaX: 0, deltaY: -20});1109        }1110        Plotly.newPlot(gd, mockCopy)1111        .then(_drag)1112        .then(_scroll)1113        .then(function() {1114            assertSelectionNodes(0, 0);1115        })1116        .then(_drag)1117        .then(_scroll)1118        .then(function() {1119            // make sure it works the 2nd time aroung1120            assertSelectionNodes(0, 0);1121        })1122        .then(done, done.fail);1123    });1124    describe('should return correct range data on dragmode *select*', function() {1125        var specs = [{1126            axType: 'linear',1127            rng: [-0.6208, 0.8375]1128        }, {1129            axType: 'log',1130            rng: [0.2394, 6.8785]1131        }, {1132            axType: 'date',1133            rng: ['2000-01-20 19:48', '2000-04-06 01:48']1134        }, {1135            axType: 'category',1136            rng: [-0.6208, 0.8375]1137        }, {1138            axType: 'multicategory',1139            rng: [-0.6208, 0.8375]1140        }];1141        specs.forEach(function(s) {1142            it('- on ' + s.axType + ' axes', function(done) {1143                var gd = createGraphDiv();1144                Plotly.newPlot(gd, [], {1145                    xaxis: {type: s.axType},1146                    dragmode: 'select',1147                    width: 400,1148                    height: 4001149                })1150                .then(function() {1151                    resetEvents(gd);1152                    drag(selectPath);1153                    return selectedPromise;1154                })1155                .then(function() {1156                    expect(selectedData.range.x).toBeCloseToArray(s.rng, 2);1157                })1158                .then(done, done.fail);1159            });1160        });1161    });1162    describe('should return correct range data on dragmode *lasso*', function() {1163        var specs = [{1164            axType: 'linear',1165            pts: [5.883, 5.941, 6, 6]1166        }, {1167            axType: 'log',1168            pts: [764422.2742, 874312.4580, 1000000, 1000000]1169        }, {1170            axType: 'date',1171            pts: ['2000-12-25 21:36', '2000-12-28 22:48', '2001-01-01', '2001-01-01']1172        }, {1173            axType: 'category',1174            pts: [5.8833, 5.9416, 6, 6]1175        }, {1176            axType: 'multicategory',1177            pts: [5.8833, 5.9416, 6, 6]1178        }];1179        specs.forEach(function(s) {1180            it('- on ' + s.axType + ' axes', function(done) {1181                var gd = createGraphDiv();1182                Plotly.newPlot(gd, [], {1183                    xaxis: {type: s.axType},1184                    dragmode: 'lasso',1185                    width: 400,1186                    height: 4001187                })1188                .then(function() {1189                    resetEvents(gd);1190                    drag(lassoPath);1191                    return selectedPromise;1192                })1193                .then(function() {1194                    expect(selectedData.lassoPoints.x).toBeCloseToArray(s.pts, 2);1195                })1196                .then(done, done.fail);1197            });1198        });1199    });1200    it('should have their selection outlines cleared during *axrange* relayout calls', function(done) {1201        var gd = createGraphDiv();1202        var fig = Lib.extendDeep({}, mock);1203        fig.layout.dragmode = 'select';1204        function _drag() {1205            resetEvents(gd);1206            drag(selectPath);1207            return selectedPromise;1208        }1209        Plotly.newPlot(gd, fig)1210        .then(_drag)1211        .then(function() { assertSelectionNodes(0, 2, 'after drag 1'); })1212        .then(function() { return Plotly.relayout(gd, 'xaxis.range', [-5, 5]); })1213        .then(function() { assertSelectionNodes(0, 0, 'after axrange relayout'); })1214        .then(_drag)1215        .then(function() { assertSelectionNodes(0, 2, 'after drag 2'); })1216        .then(done, done.fail);1217    });1218    it('should select the right data with the corresponding select direction', function(done) {1219        var gd = createGraphDiv();1220        // drag around just the center point, but if we have a selectdirection we may1221        // get either the ones to the left and right or above and below1222        var selectPath = [[175, 175], [225, 225]];1223        function selectDrag() {1224            resetEvents(gd);1225            drag(selectPath);1226            return selectedPromise;1227        }1228        function assertSelectedPointNumbers(pointNumbers) {1229            var pts = selectedData.points;1230            expect(pts.length).toBe(pointNumbers.length);1231            pointNumbers.forEach(function(pointNumber, i) {1232                expect(pts[i].pointNumber).toBe(pointNumber);1233            });1234        }1235        Plotly.newPlot(gd, [{1236            x: [1, 1, 1, 2, 2, 2, 3, 3, 3],1237            y: [1, 2, 3, 1, 2, 3, 1, 2, 3],1238            mode: 'markers'1239        }], {1240            width: 400,1241            height: 400,1242            dragmode: 'select',1243            margin: {l: 100, r: 100, t: 100, b: 100},1244            xaxis: {range: [0, 4]},1245            yaxis: {range: [0, 4]}1246        })1247        .then(selectDrag)1248        .then(function() {1249            expect(gd._fullLayout.selectdirection).toBe('any');1250            assertSelectedPointNumbers([4]);1251            return Plotly.relayout(gd, {selectdirection: 'h'});1252        })1253        .then(selectDrag)1254        .then(function() {1255            assertSelectedPointNumbers([3, 4, 5]);1256            return Plotly.relayout(gd, {selectdirection: 'v'});1257        })1258        .then(selectDrag)1259        .then(function() {1260            assertSelectedPointNumbers([1, 4, 7]);1261            return Plotly.relayout(gd, {selectdirection: 'd'});1262        })1263        .then(selectDrag)1264        .then(function() {1265            assertSelectedPointNumbers([4]);1266        })1267        .then(done, done.fail);1268    });1269    it('@flaky should cleanly clear and restart selections on double click when add/subtract mode on', function(done) {1270        var gd = createGraphDiv();1271        var fig = Lib.extendDeep({}, require('@mocks/0.json'));1272        fig.layout.dragmode = 'select';1273        Plotly.newPlot(gd, fig)1274          .then(function() {1275              return drag([[350, 100], [400, 400]]);1276          })1277          .then(function() {1278              _assertSelectedPoints([49, 50, 51, 52, 53, 54, 55, 56, 57]);1279              // Note: although Shift has no behavioral effect on clearing a selection1280              // with a double click, users might hold the Shift key by accident.1281              // This test ensures selection is cleared as expected although1282              // the Shift key is held and no selection state is retained in any way.1283              return doubleClick(500, 200, { shiftKey: true });1284          })1285          .then(function() {1286              _assertSelectedPoints(null);1287              return drag([[450, 100], [500, 400]], { shiftKey: true });1288          })1289          .then(function() {1290              _assertSelectedPoints([67, 68, 69, 70, 71, 72, 73, 74]);1291          })1292          .then(done, done.fail);1293        function _assertSelectedPoints(selPts) {1294            if(selPts) {1295                expect(gd.data[0].selectedpoints).toEqual(selPts);1296            } else {1297                expect('selectedpoints' in gd.data[0]).toBe(false);1298            }1299        }1300    });1301    it('should clear selected points on double click only on pan/lasso modes', function(done) {1302        var gd = createGraphDiv();1303        var fig = Lib.extendDeep({}, require('@mocks/0.json'));1304        fig.data = [fig.data[0]];1305        fig.layout.xaxis.autorange = false;1306        fig.layout.xaxis.range = [2, 8];1307        fig.layout.yaxis.autorange = false;1308        fig.layout.yaxis.range = [0, 3];1309        fig.layout.hovermode = 'closest';1310        function _assert(msg, exp) {1311            expect(gd.layout.xaxis.range)1312                .toBeCloseToArray(exp.xrng, 2, 'xaxis range - ' + msg);1313            expect(gd.layout.yaxis.range)1314                .toBeCloseToArray(exp.yrng, 2, 'yaxis range - ' + msg);1315            if(exp.selpts === null) {1316                expect('selectedpoints' in gd.data[0])1317                    .toBe(false, 'cleared selectedpoints - ' + msg);1318            } else {1319                expect(gd.data[0].selectedpoints)1320                    .toBeCloseToArray(exp.selpts, 2, 'selectedpoints - ' + msg);1321            }1322        }1323        Plotly.newPlot(gd, fig).then(function() {1324            _assert('base', {1325                xrng: [2, 8],1326                yrng: [0, 3],1327                selpts: null1328            });1329            return Plotly.relayout(gd, 'xaxis.range', [0, 10]);1330        })1331        .then(function() {1332            _assert('after xrng relayout', {1333                xrng: [0, 10],1334                yrng: [0, 3],1335                selpts: null1336            });1337            return doubleClick(200, 200);1338        })1339        .then(function() {1340            _assert('after double-click under dragmode zoom', {1341                xrng: [2, 8],1342                yrng: [0, 3],1343                selpts: null1344            });1345            return Plotly.relayout(gd, 'dragmode', 'select');1346        })1347        .then(function() {1348            _assert('after relayout to select', {1349                xrng: [2, 8],1350                yrng: [0, 3],1351                selpts: null1352            });1353            return drag([[100, 100], [400, 400]]);1354        })1355        .then(function() {1356            _assert('after selection', {1357                xrng: [2, 8],1358                yrng: [0, 3],1359                selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1360            });1361            return doubleClick(200, 200);1362        })1363        .then(function() {1364            _assert('after double-click under dragmode select', {1365                xrng: [2, 8],1366                yrng: [0, 3],1367                selpts: null1368            });1369            return drag([[100, 100], [400, 400]]);1370        })1371        .then(function() {1372            _assert('after selection 2', {1373                xrng: [2, 8],1374                yrng: [0, 3],1375                selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1376            });1377            return Plotly.relayout(gd, 'dragmode', 'pan');1378        })1379        .then(function() {1380            _assert('after relayout to pan', {1381                xrng: [2, 8],1382                yrng: [0, 3],1383                selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1384            });1385            return Plotly.relayout(gd, 'yaxis.range', [0, 20]);1386        })1387        .then(function() {1388            _assert('after yrng relayout', {1389                xrng: [2, 8],1390                yrng: [0, 20],1391                selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1392            });1393            return doubleClick(200, 200);1394        })1395        .then(function() {1396            _assert('after double-click under dragmode pan', {1397                xrng: [2, 8],1398                yrng: [0, 3],1399                // N.B. does not clear selection!1400                selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1401            });1402        })1403        .then(done, done.fail);1404    });1405    it('should remember selection polygons from previous select/lasso mode', function(done) {1406        var gd = createGraphDiv();1407        var path1 = [[150, 150], [170, 170]];1408        var path2 = [[193, 193], [213, 193]];1409        var fig = Lib.extendDeep({}, mock);1410        fig.layout.margin = {l: 0, t: 0, r: 0, b: 0};1411        fig.layout.width = 500;1412        fig.layout.height = 500;1413        fig.layout.dragmode = 'select';1414        fig.config = {scrollZoom: true};1415        // d attr to array of segment [x,y]1416        function outline2coords(outline) {1417            if(!outline.size()) return [[]];1418            return outline.attr('d')1419                .replace(/Z/g, '')1420                .split('M')1421                .filter(Boolean)1422                .map(function(s) {1423                    return s.split('L')1424                        .map(function(s) { return s.split(',').map(Number); });1425                })1426                .reduce(function(a, b) { return a.concat(b); });1427        }1428        function _assert(msg, exp) {1429            var outline = d3Select(gd).select('.zoomlayer').select('.select-outline-1');1430            if(exp.outline) {1431                expect(outline2coords(outline)).toBeCloseTo2DArray(exp.outline, 2, msg);1432            } else {1433                assertSelectionNodes(0, 0, msg);1434            }1435        }1436        function _drag(path, opts) {1437            return function() {1438                resetEvents(gd);1439                drag(path, opts);1440                return selectedPromise;1441            };1442        }1443        Plotly.newPlot(gd, fig)1444        .then(function() { _assert('base', {outline: false}); })1445        .then(_drag(path1))1446        .then(function() {1447            _assert('select path1', {1448                outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1449            });1450        })1451        .then(_drag(path2))1452        .then(function() {1453            _assert('select path2', {1454                outline: [[193, 0], [193, 500], [213, 500], [213, 0]]1455            });1456        })1457        .then(_drag(path1))1458        .then(_drag(path2, {shiftKey: true}))1459        .then(function() {1460            _assert('select path1+path2', {1461                outline: [1462                    [170, 170], [170, 150], [150, 150], [150, 170],1463                    [213, 500], [213, 0], [193, 0], [193, 500]1464                ]1465            });1466        })1467        .then(function() {1468            return Plotly.relayout(gd, 'dragmode', 'lasso');1469        })1470        .then(function() {1471            // N.B. all relayout calls clear the selection outline at the moment,1472            // perhaps we could make an exception for select <-> lasso ?1473            _assert('after relayout -> lasso', {outline: false});1474        })1475        .then(_drag(lassoPath, {shiftKey: true}))1476        .then(function() {1477            // merged with previous 'select' polygon1478            _assert('after shift lasso', {1479                outline: [1480                    [170, 170], [170, 150], [150, 150], [150, 170],1481                    [213, 500], [213, 0], [193, 0], [193, 500],1482                    [335, 243], [328, 169], [316, 171], [318, 239]1483                ]1484            });1485        })1486        .then(_drag(lassoPath))1487        .then(function() {1488            _assert('after lasso (no-shift)', {1489                outline: [[316, 171], [318, 239], [335, 243], [328, 169]]1490            });1491        })1492        .then(function() {1493            return Plotly.relayout(gd, 'dragmode', 'pan');1494        })1495        .then(function() {1496            _assert('after relayout -> pan', {outline: false});1497            drag(path2);1498            _assert('after pan', {outline: false});1499            return Plotly.relayout(gd, 'dragmode', 'select');1500        })1501        .then(function() {1502            _assert('after relayout back to select', {outline: false});1503        })1504        .then(_drag(path1, {shiftKey: true}))1505        .then(function() {1506            // this used to merged 'lasso' polygons before (see #2669)1507            _assert('shift select path1 after pan', {1508                outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1509            });1510        })1511        .then(_drag(path2, {shiftKey: true}))1512        .then(function() {1513            _assert('shift select path1+path2 after pan', {1514                outline: [1515                    [170, 170], [170, 150], [150, 150], [150, 170],1516                    [213, 500], [213, 0], [193, 0], [193, 500]1517                ]1518            });1519        })1520        .then(function() {1521            mouseEvent('mousemove', 200, 200);1522            mouseEvent('scroll', 200, 200, {deltaX: 0, deltaY: -20});1523        })1524        .then(_drag(path1, {shiftKey: true}))1525        .then(function() {1526            _assert('shift select path1 after scroll', {1527                outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1528            });1529        })1530        .then(done, done.fail);1531    });1532});1533describe('Test select box and lasso per trace:', function() {1534    var gd;1535    beforeEach(function() {1536        gd = createGraphDiv();1537        spyOn(Lib, 'error');1538    });1539    afterEach(destroyGraphDiv);1540    function makeAssertPoints(keys) {1541        var callNumber = 0;1542        return function(expected) {1543            var msg = '(call #' + callNumber + ') ';1544            var pts = (selectedData || {}).points || [];1545            expect(pts.length).toBe(expected.length, msg + 'selected points length');1546            pts.forEach(function(p, i) {1547                var e = expected[i] || [];1548                keys.forEach(function(k, j) {1549                    var msgFull = msg + 'selected pt ' + i + ' - ' + k + ' val';1550                    if(typeof p[k] === 'number' && typeof e[j] === 'number') {1551                        expect(p[k]).toBeCloseTo(e[j], 1, msgFull);1552                    } else if(Array.isArray(p[k]) && Array.isArray(e[j])) {1553                        expect(p[k]).toBeCloseToArray(e[j], 1, msgFull);1554                    } else {1555                        expect(p[k]).toBe(e[j], msgFull);1556                    }1557                });1558            });1559            callNumber++;1560        };1561    }1562    function makeAssertSelectedPoints() {1563        var callNumber = 0;1564        return function(expected) {1565            var msg = '(call #' + callNumber + ') ';1566            gd.data.forEach(function(trace, i) {1567                var msgFull = msg + 'selectedpoints array for trace ' + i;1568                var actual = trace.selectedpoints;1569                if(expected[i]) {1570                    expect(actual).toBeCloseToArray(expected[i], 1, msgFull);1571                } else {1572                    expect(actual).toBe(undefined, 1, msgFull);1573                }1574            });1575            callNumber++;1576        };1577    }1578    function makeAssertRanges(subplot, tol) {1579        tol = tol || 1;1580        var callNumber = 0;1581        return function(expected) {1582            var msg = '(call #' + callNumber + ') select box range ';1583            var ranges = selectedData.range || {};1584            if(subplot) {1585                expect(ranges[subplot] || [])1586                    .toBeCloseTo2DArray(expected, tol, msg + 'for ' + subplot);1587            } else {1588                expect(ranges.x || [])1589                    .toBeCloseToArray(expected[0], tol, msg + 'x coords');1590                expect(ranges.y || [])1591                    .toBeCloseToArray(expected[1], tol, msg + 'y coords');1592            }1593            callNumber++;1594        };1595    }1596    function makeAssertLassoPoints(subplot, tol) {1597        tol = tol || 1;1598        var callNumber = 0;1599        return function(expected) {1600            var msg = '(call #' + callNumber + ') lasso points ';1601            var lassoPoints = selectedData.lassoPoints || {};1602            if(subplot) {1603                expect(lassoPoints[subplot] || [])1604                    .toBeCloseTo2DArray(expected, tol, msg + 'for ' + subplot);1605            } else {1606                expect(lassoPoints.x || [])1607                    .toBeCloseToArray(expected[0], tol, msg + 'x coords');1608                expect(lassoPoints.y || [])1609                    .toBeCloseToArray(expected[1], tol, msg + 'y coords');1610            }1611            callNumber++;1612        };1613    }1614    function transformPlot(gd, transformString) {1615        gd.style.webkitTransform = transformString;1616        gd.style.MozTransform = transformString;1617        gd.style.msTransform = transformString;1618        gd.style.OTransform = transformString;1619        gd.style.transform = transformString;1620    }1621    var cssTransform = 'translate(-25%, -25%) scale(0.5)';1622    function _run(hasCssTransform, dragPath, afterDragFn, dblClickPos, eventCounts, msg) {1623        afterDragFn = afterDragFn || function() {};1624        dblClickPos = dblClickPos || [250, 200];1625        var scale = 1;1626        if(hasCssTransform) {1627            scale = 0.5;1628        }1629        dblClickPos[0] *= scale;1630        dblClickPos[1] *= scale;1631        for(var i = 0; i < dragPath.length; i++) {1632            for(var j = 0; j < dragPath[i].length; j++) {1633                dragPath[i][j] *= scale;1634            }1635        }1636        resetEvents(gd);1637        assertSelectionNodes(0, 0);1638        drag(dragPath);1639        return (eventCounts[0] ? selectedPromise : Promise.resolve())1640            .then(afterDragFn)1641            .then(function() {1642                // TODO: in v3 when we remove the `plotly_selecting->undefined` the Math.max(...)1643                // in the middle here will turn into just eventCounts[1].1644                // It's just here because one of the selected events is generated during1645                // doubleclick so hasn't happened yet when we're testing this.1646                assertEventCounts(eventCounts[0], Math.max(0, eventCounts[1] - 1), 0, msg + ' (before dblclick)');1647                return doubleClick(dblClickPos[0], dblClickPos[1]);1648            })1649            .then(eventCounts[2] ? deselectPromise : Promise.resolve())1650            .then(function() {1651                assertEventCounts(eventCounts[0], eventCounts[1], eventCounts[2], msg + ' (after dblclick)');1652                expect(Lib.error).not.toHaveBeenCalled();1653            });1654    }1655    [false, true].forEach(function(hasCssTransform) {1656        it('should work on scatterternary traces, hasCssTransform: ' + hasCssTransform, function(done) {1657            var assertPoints = makeAssertPoints(['a', 'b', 'c']);1658            var assertSelectedPoints = makeAssertSelectedPoints();1659            var fig = Lib.extendDeep({}, require('@mocks/ternary_simple'));1660            fig.layout.width = 800;1661            fig.layout.dragmode = 'select';1662            addInvisible(fig);1663            Plotly.newPlot(gd, fig)1664            .then(function() {1665                if(hasCssTransform) transformPlot(gd, cssTransform);1666                return _run(hasCssTransform,1667                    [[400, 200], [445, 235]],1668                    function() {1669                        assertPoints([[0.5, 0.25, 0.25]]);1670                        assertSelectedPoints({0: [0]});1671                    },1672                    [380, 180],1673                    BOXEVENTS, 'scatterternary select'1674                );1675            })1676            .then(function() {1677                return Plotly.relayout(gd, 'dragmode', 'lasso');1678            })1679            .then(function() {1680                return _run(hasCssTransform,1681                    [[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]],1682                    function() {1683                        assertPoints([[0.5, 0.25, 0.25]]);1684                        assertSelectedPoints({0: [0]});1685                    },1686                    [380, 180],1687                    LASSOEVENTS, 'scatterternary lasso'1688                );1689            })1690            .then(function() {1691                // should work after a relayout too1692                return Plotly.relayout(gd, 'width', 400);1693            })1694            .then(function() {1695                return _run(hasCssTransform,1696                    [[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]],1697                    function() {1698                        assertPoints([[0.5, 0.25, 0.25]]);1699                        assertSelectedPoints({0: [0]});1700                    },1701                    [180, 180],1702                    LASSOEVENTS, 'scatterternary lasso after relayout'1703                );1704            })1705            .then(done, done.fail);1706        });1707    });1708    [false, true].forEach(function(hasCssTransform) {1709        it('should work on scattercarpet traces, hasCssTransform: ' + hasCssTransform, function(done) {1710            var assertPoints = makeAssertPoints(['a', 'b']);1711            var assertSelectedPoints = makeAssertSelectedPoints();1712            var fig = Lib.extendDeep({}, require('@mocks/scattercarpet'));1713            delete fig.data[6].selectedpoints;1714            fig.layout.dragmode = 'select';1715            addInvisible(fig);1716            Plotly.newPlot(gd, fig)1717            .then(function() {1718                if(hasCssTransform) transformPlot(gd, cssTransform);1719                return _run(hasCssTransform,1720                    [[300, 200], [400, 250]],1721                    function() {1722                        assertPoints([[0.2, 1.5]]);1723                        assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []});1724                    },1725                    null, BOXEVENTS, 'scattercarpet select'1726                );1727            })1728            .then(function() {1729                return Plotly.relayout(gd, 'dragmode', 'lasso');1730            })1731            .then(function() {1732                return _run(hasCssTransform,1733                    [[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]],1734                    function() {1735                        assertPoints([[0.2, 1.5]]);1736                        assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []});1737                    },1738                    null, LASSOEVENTS, 'scattercarpet lasso'1739                );1740            })1741            .then(done, done.fail);1742        });1743    });1744    [false, true].forEach(function(hasCssTransform) {1745        it('@gl should work on scattermapbox traces, hasCssTransform: ' + hasCssTransform, function(done) {1746            var assertPoints = makeAssertPoints(['lon', 'lat']);1747            var assertRanges = makeAssertRanges('mapbox');1748            var assertLassoPoints = makeAssertLassoPoints('mapbox');1749            var assertSelectedPoints = makeAssertSelectedPoints();1750            var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text'));1751            fig.data[0].lon.push(null);1752            fig.data[0].lat.push(null);1753            fig.layout.dragmode = 'select';1754            fig.config = {1755                mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN1756            };1757            addInvisible(fig);1758            Plotly.newPlot(gd, fig)1759            .then(function() {1760                if(hasCssTransform) transformPlot(gd, cssTransform);1761                return _run(hasCssTransform,1762                    [[370, 120], [500, 200]],1763                    function() {1764                        assertPoints([[30, 30]]);1765                        assertRanges([[21.99, 34.55], [38.14, 25.98]]);1766                        assertSelectedPoints({0: [2]});1767                    },1768                    null, BOXEVENTS, 'scattermapbox select'1769                );1770            })1771            .then(function() {1772                return Plotly.relayout(gd, 'dragmode', 'lasso');1773            })1774            .then(function() {1775                return _run(hasCssTransform,1776                    [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1777                    function() {1778                        assertPoints([[20, 20]]);1779                        assertSelectedPoints({0: [1]});1780                        assertLassoPoints([1781                            [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97]1782                        ]);1783                    },1784                    null, LASSOEVENTS, 'scattermapbox lasso'1785                );1786            })1787            .then(function() {1788                // make selection handlers don't get called in 'pan' dragmode1789                return Plotly.relayout(gd, 'dragmode', 'pan');1790            })1791            .then(function() {1792                return _run(hasCssTransform,1793                    [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattermapbox pan'1794                );1795            })1796            .then(done, done.fail);1797        }, LONG_TIMEOUT_INTERVAL);1798    });1799    [false, true].forEach(function(hasCssTransform) {1800        it('@gl should work on choroplethmapbox traces, hasCssTransform: ' + hasCssTransform, function(done) {1801            var assertPoints = makeAssertPoints(['location', 'z']);1802            var assertRanges = makeAssertRanges('mapbox');1803            var assertLassoPoints = makeAssertLassoPoints('mapbox');1804            var assertSelectedPoints = makeAssertSelectedPoints();1805            var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth0.json'));1806            fig.data[0].locations.push(null);1807            fig.layout.dragmode = 'select';1808            fig.config = {1809                mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN1810            };1811            addInvisible(fig);1812            Plotly.newPlot(gd, fig)1813            .then(function() {1814                if(hasCssTransform) transformPlot(gd, cssTransform);1815                return _run(hasCssTransform,1816                    [[150, 150], [300, 300]],1817                    function() {1818                        assertPoints([['NY', 10]]);1819                        assertRanges([[-83.38, 46.13], [-74.06, 39.29]]);1820                        assertSelectedPoints({0: [0]});1821                    },1822                    null, BOXEVENTS, 'choroplethmapbox select'1823                );1824            })1825            .then(function() {1826                return Plotly.relayout(gd, 'dragmode', 'lasso');1827            })1828            .then(function() {1829                return _run(hasCssTransform,1830                    [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1831                    function() {1832                        assertPoints([['MA', 20]]);1833                        assertSelectedPoints({0: [1]});1834                        assertLassoPoints([1835                            [-74.06, 43.936], [-74.06, 39.293], [-67.84, 39.293],1836                            [-67.84, 43.936], [-74.06, 43.936]1837                        ]);1838                    },1839                    null, LASSOEVENTS, 'choroplethmapbox lasso'1840                );1841            })1842            .then(done, done.fail);1843        }, LONG_TIMEOUT_INTERVAL);1844    });1845    [false, true].forEach(function(hasCssTransform) {1846        it('should work on scattergeo traces, hasCssTransform: ' + hasCssTransform, function(done) {1847            var assertPoints = makeAssertPoints(['lon', 'lat']);1848            var assertSelectedPoints = makeAssertSelectedPoints();1849            var assertRanges = makeAssertRanges('geo');1850            var assertLassoPoints = makeAssertLassoPoints('geo');1851            function assertNodeOpacity(exp) {1852                var traces = d3Select(gd).selectAll('.scatterlayer > .trace');1853                expect(traces.size()).toBe(Object.keys(exp).length, 'correct # of trace <g>');1854                traces.each(function(_, i) {1855                    d3Select(this).selectAll('path.point').each(function(_, j) {1856                        expect(Number(this.style.opacity))1857                            .toBe(exp[i][j], 'node opacity - trace ' + i + ' pt ' + j);1858                    });1859                });1860            }1861            var fig = {1862                data: [{1863                    type: 'scattergeo',1864                    lon: [10, 20, 30, null],1865                    lat: [10, 20, 30, null]1866                }, {1867                    type: 'scattergeo',1868                    lon: [-10, -20, -30],1869                    lat: [10, 20, 30]1870                }],1871                layout: {1872                    showlegend: false,1873                    dragmode: 'select',1874                    width: 800,1875                    height: 6001876                }1877            };1878            addInvisible(fig);1879            Plotly.newPlot(gd, fig)1880            .then(function() {1881                if(hasCssTransform) transformPlot(gd, cssTransform);1882                return _run(hasCssTransform,1883                    [[350, 200], [450, 400]],1884                    function() {1885                        assertPoints([[10, 10], [20, 20], [-10, 10], [-20, 20]]);1886                        assertSelectedPoints({0: [0, 1], 1: [0, 1]});1887                        assertNodeOpacity({0: [1, 1, 0.2], 1: [1, 1, 0.2]});1888                        assertRanges([[-28.13, 61.88], [28.13, -50.64]]);1889                    },1890                    null, BOXEVENTS, 'scattergeo select'1891                );1892            })1893            .then(function() {1894                return Plotly.relayout(gd, 'dragmode', 'lasso');1895            })1896            .then(function() {1897                return _run(hasCssTransform,1898                    [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1899                    function() {1900                        assertPoints([[-10, 10], [-20, 20], [-30, 30]]);1901                        assertSelectedPoints({0: [], 1: [0, 1, 2]});1902                        assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]});1903                        assertLassoPoints([1904                            [-56.25, 61.88], [-56.24, 5.63], [0, 5.63], [0, 61.88], [-56.25, 61.88]1905                        ]);1906                    },1907                    null, LASSOEVENTS, 'scattergeo lasso'1908                );1909            })1910            .then(function() {1911                // some projection types can't handle BADNUM during c2p,1912                // make they are skipped here1913                return Plotly.relayout(gd, 'geo.projection.type', 'robinson');1914            })1915            .then(function() {1916                return _run(hasCssTransform,1917                    [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1918                    function() {1919                        assertPoints([[-10, 10], [-20, 20], [-30, 30]]);1920                        assertSelectedPoints({0: [], 1: [0, 1, 2]});1921                        assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]});1922                        assertLassoPoints([1923                            [-67.40, 55.07], [-56.33, 4.968], [0, 4.968], [0, 55.07], [-67.40, 55.07]1924                        ]);1925                    },1926                    null, LASSOEVENTS, 'scattergeo lasso (on robinson projection)'1927                );1928            })1929            .then(function() {1930                // make sure selection handlers don't get called in 'pan' dragmode1931                return Plotly.relayout(gd, 'dragmode', 'pan');1932            })1933            .then(function() {1934                return _run(hasCssTransform,1935                    [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattergeo pan'1936                );1937            })1938            .then(done, done.fail);1939        }, LONG_TIMEOUT_INTERVAL);1940    });1941    [false, true].forEach(function(hasCssTransform) {1942        it('should work on scatterpolar traces, hasCssTransform: ' + hasCssTransform, function(done) {1943            var assertPoints = makeAssertPoints(['r', 'theta']);1944            var assertSelectedPoints = makeAssertSelectedPoints();1945            var fig = Lib.extendDeep({}, require('@mocks/polar_subplots'));1946            fig.layout.width = 800;1947            fig.layout.dragmode = 'select';1948            addInvisible(fig);1949            Plotly.newPlot(gd, fig)1950            .then(function() {1951                if(hasCssTransform) transformPlot(gd, cssTransform);1952                return _run(hasCssTransform,1953                    [[150, 150], [350, 250]],1954                    function() {1955                        assertPoints([[1, 0], [2, 45]]);1956                        assertSelectedPoints({0: [0, 1]});1957                    },1958                    [200, 200],1959                    BOXEVENTS, 'scatterpolar select'1960                );1961            })1962            .then(function() {1963                return Plotly.relayout(gd, 'dragmode', 'lasso');1964            })1965            .then(function() {1966                return _run(hasCssTransform,1967                    [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]],1968                    function() {1969                        assertPoints([[1, 0], [2, 45]]);1970                        assertSelectedPoints({0: [0, 1]});1971                    },1972                    [200, 200],1973                    LASSOEVENTS, 'scatterpolar lasso'1974                );1975            })1976            .then(done, done.fail);1977        });1978    });1979    [false, true].forEach(function(hasCssTransform) {1980        it('should work on scattersmith traces, hasCssTransform: ' + hasCssTransform, function(done) {1981            var assertPoints = makeAssertPoints(['real', 'imag']);1982            var assertSelectedPoints = makeAssertSelectedPoints();1983            var fig = Lib.extendDeep({}, require('@mocks/smith_basic.json'));1984            fig.layout.dragmode = 'select';1985            addInvisible(fig);1986            Plotly.newPlot(gd, fig)1987            .then(function() {1988                if(hasCssTransform) transformPlot(gd, cssTransform);1989                return _run(hasCssTransform,1990                    [[260, 260], [460, 460]],1991                    function() {1992                        assertPoints([[1, 0]]);1993                        assertSelectedPoints({0: [2]});1994                    },1995                    [360, 360],1996                    BOXEVENTS, 'scattersmith select'1997                );1998            })1999            .then(function() {2000                return Plotly.relayout(gd, 'dragmode', 'lasso');2001            })2002            .then(function() {2003                return _run(hasCssTransform,2004                    [[260, 260], [260, 460], [460, 460], [460, 260], [260, 260]],2005                    function() {2006                        assertPoints([[1, 0]]);2007                        assertSelectedPoints({0: [2]});2008                    },2009                    [360, 360],2010                    LASSOEVENTS, 'scattersmith lasso'2011                );2012            })2013            .then(done, done.fail);2014        });2015    });2016    [false, true].forEach(function(hasCssTransform) {2017        it('should work on barpolar traces, hasCssTransform: ' + hasCssTransform, function(done) {2018            var assertPoints = makeAssertPoints(['r', 'theta']);2019            var assertSelectedPoints = makeAssertSelectedPoints();2020            var fig = Lib.extendDeep({}, require('@mocks/polar_wind-rose.json'));2021            fig.layout.showlegend = false;2022            fig.layout.width = 500;2023            fig.layout.height = 500;2024            fig.layout.dragmode = 'select';2025            addInvisible(fig);2026            Plotly.newPlot(gd, fig)2027            .then(function() {2028                if(hasCssTransform) transformPlot(gd, cssTransform);2029                return _run(hasCssTransform,2030                    [[150, 150], [250, 250]],2031                    function() {2032                        assertPoints([2033                            [62.5, 'N-W'], [55, 'N-W'], [40, 'North'],2034                            [40, 'N-W'], [20, 'North'], [22.5, 'N-W']2035                        ]);2036                        assertSelectedPoints({2037                            0: [7],2038                            1: [7],2039                            2: [0, 7],2040                            3: [0, 7]2041                        });2042                    },2043                    [200, 200],2044                    BOXEVENTS, 'barpolar select'2045                );2046            })2047            .then(function() {2048                return Plotly.relayout(gd, 'dragmode', 'lasso');2049            })2050            .then(function() {2051                return _run(hasCssTransform,2052                    [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]],2053                    function() {2054                        assertPoints([2055                            [62.5, 'N-W'], [50, 'N-E'], [55, 'N-W'], [40, 'North'],2056                            [30, 'N-E'], [40, 'N-W'], [20, 'North'], [7.5, 'N-E'], [22.5, 'N-W']2057                        ]);2058                        assertSelectedPoints({2059                            0: [7],2060                            1: [1, 7],2061                            2: [0, 1, 7],2062                            3: [0, 1, 7]2063                        });2064                    },2065                    [200, 200],2066                    LASSOEVENTS, 'barpolar lasso'2067                );2068            })2069            .then(done, done.fail);2070        });2071    });2072    [false, true].forEach(function(hasCssTransform) {2073        it('should work on choropleth traces, hasCssTransform: ' + hasCssTransform, function(done) {2074            var assertPoints = makeAssertPoints(['location', 'z']);2075            var assertSelectedPoints = makeAssertSelectedPoints();2076            var assertRanges = makeAssertRanges('geo', -0.5);2077            var assertLassoPoints = makeAssertLassoPoints('geo', -0.5);2078            var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-text'));2079            fig.layout.width = 870;2080            fig.layout.height = 450;2081            fig.layout.dragmode = 'select';2082            fig.layout.geo.scope = 'europe';2083            addInvisible(fig, false);2084            // add a trace with no locations which will then make trace invisible, lacking DOM elements2085            var emptyChoroplethTrace = Lib.extendDeep({}, fig.data[0]);2086            emptyChoroplethTrace.text = [];2087            emptyChoroplethTrace.locations = [];2088            emptyChoroplethTrace.z = [];2089            fig.data.push(emptyChoroplethTrace);2090            Plotly.newPlot(gd, fig)2091            .then(function() {2092                if(hasCssTransform) transformPlot(gd, cssTransform);2093                return _run(hasCssTransform,2094                    [[350, 200], [400, 250]],2095                    function() {2096                        assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]);2097                        assertSelectedPoints({0: [43, 54]});2098                        assertRanges([[-19.11, 63.06], [7.31, 53.72]]);2099                    },2100                    [280, 190],2101                    BOXEVENTS, 'choropleth select'2102                );2103            })2104            .then(function() {2105                return Plotly.relayout(gd, 'dragmode', 'lasso');2106            })2107            .then(function() {2108                return _run(hasCssTransform,2109                    [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]],2110                    function() {2111                        assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]);2112                        assertSelectedPoints({0: [43, 54]});2113                        assertLassoPoints([2114                            [-19.11, 63.06], [5.50, 65.25], [7.31, 53.72], [-12.90, 51.70], [-19.11, 63.06]2115                        ]);2116                    },2117                    [280, 190],2118                    LASSOEVENTS, 'choropleth lasso'2119                );2120            })2121            .then(function() {2122                // make selection handlers don't get called in 'pan' dragmode2123                return Plotly.relayout(gd, 'dragmode', 'pan');2124            })2125            .then(function() {2126                return _run(hasCssTransform,2127                    [[370, 120], [500, 200]], null, [200, 180], NOEVENTS, 'choropleth pan'2128                );2129            })2130            .then(done, done.fail);2131        }, LONG_TIMEOUT_INTERVAL);2132    });2133    [false, true].forEach(function(hasCssTransform) {2134        it('should work for waterfall traces, hasCssTransform: ' + hasCssTransform, function(done) {2135            var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2136            var assertSelectedPoints = makeAssertSelectedPoints();2137            var assertRanges = makeAssertRanges();2138            var assertLassoPoints = makeAssertLassoPoints();2139            var fig = Lib.extendDeep({}, require('@mocks/waterfall_profit-loss_2018_positive-negative'));2140            fig.layout.dragmode = 'lasso';2141            addInvisible(fig);2142            Plotly.newPlot(gd, fig)2143            .then(function() {2144                if(hasCssTransform) transformPlot(gd, cssTransform);2145                return _run(hasCssTransform,2146                    [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]],2147                    function() {2148                        assertPoints([2149                            [0, 281, 'Purchases'],2150                            [0, 269, 'Material expenses'],2151                            [0, 191, 'Personnel expenses'],2152                            [0, 179, 'Other expenses']2153                        ]);2154                        assertSelectedPoints({2155                            0: [5, 6, 7, 8]2156                        });2157                        assertLassoPoints([2158                            [288.8086, 57.7617, 288.8086, 519.8555, 404.3321],2159                            [4.33870, 6.7580, 9.1774, 6.75806, 5.54838]2160                        ]);2161                    },2162                    null, LASSOEVENTS, 'waterfall lasso'2163                );2164            })2165            .then(function() {2166                return Plotly.relayout(gd, 'dragmode', 'select');2167            })2168            .then(function() {2169                return _run(hasCssTransform,2170                    [[300, 300], [400, 400]],2171                    function() {2172                        assertPoints([2173                            [0, 281, 'Purchases'],2174                            [0, 269, 'Material expenses']2175                        ]);2176                        assertSelectedPoints({2177                            0: [5, 6]2178                        });2179                        assertRanges([2180                            [173.28519, 288.8086],2181                            [4.3387, 6.7580]2182                        ]);2183                    },2184                    null, BOXEVENTS, 'waterfall select'2185                );2186            })2187            .then(done, done.fail);2188        });2189    });2190    [false, true].forEach(function(hasCssTransform) {2191        it('should work for funnel traces, hasCssTransform: ' + hasCssTransform, function(done) {2192            var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2193            var assertSelectedPoints = makeAssertSelectedPoints();2194            var assertRanges = makeAssertRanges();2195            var assertLassoPoints = makeAssertLassoPoints();2196            var fig = Lib.extendDeep({}, require('@mocks/funnel_horizontal_group_basic'));2197            fig.layout.dragmode = 'lasso';2198            addInvisible(fig);2199            Plotly.newPlot(gd, fig)2200            .then(function() {2201                if(hasCssTransform) transformPlot(gd, cssTransform);2202                return _run(hasCssTransform,2203                    [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]],2204                    function() {2205                        assertPoints([2206                            [0, 331.5, 'Author: etpinard'],2207                            [1, 53.5, 'Pull requests'],2208                            [1, 15.5, 'Author: etpinard'],2209                        ]);2210                        assertSelectedPoints({2211                            0: [2],2212                            1: [1, 2]2213                        });2214                        assertLassoPoints([2215                            [-161.6974, -1701.6728, -161.6974, 1378.2779, 608.2902],2216                            [1.1129, 1.9193, 2.7258, 1.9193, 1.5161]2217                        ]);2218                    },2219                    null, LASSOEVENTS, 'funnel lasso'2220                );2221            })2222            .then(function() {2223                return Plotly.relayout(gd, 'dragmode', 'select');2224            })2225            .then(function() {2226                return _run(hasCssTransform,2227                    [[300, 300], [500, 500]],2228                    function() {2229                        assertPoints([2230                            [0, 331.5, 'Author: etpinard'],2231                            [1, 53.5, 'Pull requests'],2232                            [1, 15.5, 'Author: etpinard']2233                        ]);2234                        assertSelectedPoints({2235                            0: [2],2236                            1: [1, 2]2237                        });2238                        assertRanges([2239                            [-931.6851, 608.2902],2240                            [1.1129, 2.7258]2241                        ]);2242                    },2243                    null, BOXEVENTS, 'funnel select'2244                );2245            })2246            .then(done, done.fail);2247        });2248    });2249    [false].forEach(function(hasCssTransform) {2250        it('@flaky should work for bar traces, hasCssTransform: ' + hasCssTransform, function(done) {2251            var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2252            var assertSelectedPoints = makeAssertSelectedPoints();2253            var assertRanges = makeAssertRanges();2254            var assertLassoPoints = makeAssertLassoPoints();2255            var fig = Lib.extendDeep({}, require('@mocks/0'));2256            fig.layout.dragmode = 'lasso';2257            addInvisible(fig);2258            Plotly.newPlot(gd, fig)2259            .then(function() {2260                if(hasCssTransform) transformPlot(gd, cssTransform);2261                return _run(hasCssTransform,2262                    [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]],2263                    function() {2264                        assertPoints([2265                            [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336],2266                            [0, 5.3, 0.309], [0, 5.4, 0.275], [0, 5.5, 0.235], [0, 5.6, 0.192],2267                            [0, 5.7, 0.145],2268                            [1, 5.1, 0.485], [1, 5.2, 0.409], [1, 5.3, 0.327],2269                            [1, 5.4, 0.24], [1, 5.5, 0.149], [1, 5.6, 0.059],2270                            [2, 4.9, 0.473], [2, 5, 0.368], [2, 5.1, 0.258],2271                            [2, 5.2, 0.146], [2, 5.3, 0.036]2272                        ]);2273                        assertSelectedPoints({2274                            0: [49, 50, 51, 52, 53, 54, 55, 56, 57],2275                            1: [51, 52, 53, 54, 55, 56],2276                            2: [49, 50, 51, 52, 53]2277                        });2278                        assertLassoPoints([2279                            [4.87, 5.74, 5.74, 4.87, 4.87],2280                            [0.53, 0.53, -0.02, -0.02, 0.53]2281                        ]);2282                    },2283                    null, LASSOEVENTS, 'bar lasso'2284                );2285            })2286            .then(function() {2287                return Plotly.relayout(gd, 'dragmode', 'select');2288            })2289            .then(delay(100))2290            .then(function() {2291                return _run(hasCssTransform,2292                    [[350, 200], [370, 220]],2293                    function() {2294                        assertPoints([2295                            [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336],2296                            [1, 5.1, 0.485], [1, 5.2, 0.41],2297                            [2, 4.9, 0.473], [2, 5, 0.37]2298                        ]);2299                        assertSelectedPoints({2300                            0: [49, 50, 51, 52],2301                            1: [51, 52],2302                            2: [49, 50]2303                        });2304                        assertRanges([[4.87, 5.22], [0.31, 0.53]]);2305                    },2306                    null, BOXEVENTS, 'bar select'2307                );2308            })2309            .then(function() {2310                // mimic https://github.com/plotly/plotly.js/issues/37952311                return Plotly.relayout(gd, {2312                    'xaxis.rangeslider.visible': true,2313                    'xaxis.range': [0, 6]2314                });2315            })2316            .then(function() {2317                return _run(hasCssTransform,2318                    [[350, 200], [360, 200]],2319                    function() {2320                        assertPoints([2321                            [0, 2.5, -0.429], [1, 2.5, -1.015], [2, 2.5, -1.172],2322                        ]);2323                        assertSelectedPoints({2324                            0: [25],2325                            1: [25],2326                            2: [25]2327                        });2328                        assertRanges([[2.434, 2.521], [-1.4355, 2.0555]]);2329                    },2330                    null, BOXEVENTS, 'bar select (after xaxis.range relayout)'2331                );2332            })2333            .then(done, done.fail);2334        });2335    });2336    [false, true].forEach(function(hasCssTransform) {2337        it('should work for date/category traces, hasCssTransform: ' + hasCssTransform, function(done) {2338            var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2339            var assertSelectedPoints = makeAssertSelectedPoints();2340            var fig = {2341                data: [{2342                    x: ['2017-01-01', '2017-02-01', '2017-03-01'],2343                    y: ['a', 'b', 'c']2344                }, {2345                    type: 'bar',2346                    x: ['2017-01-01', '2017-02-02', '2017-03-01'],2347                    y: ['a', 'b', 'c']2348                }],2349                layout: {2350                    dragmode: 'lasso',2351                    width: 400,2352                    height: 4002353                }2354            };2355            addInvisible(fig);2356            var x0 = 100;2357            var y0 = 100;2358            var x1 = 250;2359            var y1 = 250;2360            Plotly.newPlot(gd, fig)2361            .then(function() {2362                if(hasCssTransform) transformPlot(gd, cssTransform);2363                return _run(hasCssTransform,2364                    [[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]],2365                    function() {2366                        assertPoints([2367                            [0, '2017-02-01', 'b'],2368                            [1, '2017-02-02', 'b']2369                        ]);2370                        assertSelectedPoints({0: [1], 1: [1]});2371                    },2372                    null, LASSOEVENTS, 'date/category lasso'2373                );2374            })2375            .then(function() {2376                return Plotly.relayout(gd, 'dragmode', 'select');2377            })2378            .then(function() {2379                return _run(hasCssTransform,2380                    [[x0, y0], [x1, y1]],2381                    function() {2382                        assertPoints([2383                            [0, '2017-02-01', 'b'],2384                            [1, '2017-02-02', 'b']2385                        ]);2386                        assertSelectedPoints({0: [1], 1: [1]});2387                    },2388                    null, BOXEVENTS, 'date/category select'2389                );2390            })2391            .then(done, done.fail);2392        });2393    });2394    [false, true].forEach(function(hasCssTransform) {2395        it('should work for histogram traces, hasCssTransform: ' + hasCssTransform, function(done) {2396            var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y', 'pointIndices']);2397            var assertSelectedPoints = makeAssertSelectedPoints();2398            var assertRanges = makeAssertRanges();2399            var assertLassoPoints = makeAssertLassoPoints();2400            var fig = Lib.extendDeep({}, require('@mocks/hist_grouped'));2401            fig.layout.dragmode = 'lasso';2402            fig.layout.width = 600;2403            fig.layout.height = 500;2404            addInvisible(fig);2405            Plotly.newPlot(gd, fig)2406            .then(function() {2407                if(hasCssTransform) transformPlot(gd, cssTransform);2408                return _run(hasCssTransform,2409                    [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2410                    function() {2411                        assertPoints([2412                            [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]]2413                        ]);2414                        assertSelectedPoints({0: [3, 4], 1: [1, 2]});2415                        assertLassoPoints([2416                            [1.66, 3.59, 3.59, 1.66, 1.66],2417                            [2.17, 2.17, 0.69, 0.69, 2.17]2418                        ]);2419                    },2420                    null, LASSOEVENTS, 'histogram lasso'2421                );2422            })2423            .then(function() {2424                return Plotly.relayout(gd, 'dragmode', 'select');2425            })2426            .then(function() {2427                return _run(hasCssTransform,2428                    [[200, 200], [400, 350]],2429                    function() {2430                        assertPoints([2431                            [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]]2432                        ]);2433                        assertSelectedPoints({0: [3, 4], 1: [1, 2]});2434                        assertRanges([[1.66, 3.59], [0.69, 2.17]]);2435                    },2436                    null, BOXEVENTS, 'histogram select'2437                );2438            })2439            .then(done, done.fail);2440        });2441    });2442    [false, true].forEach(function(hasCssTransform) {2443        it('should work for box traces, hasCssTransform: ' + hasCssTransform, function(done) {2444            var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2445            var assertSelectedPoints = makeAssertSelectedPoints();2446            var assertRanges = makeAssertRanges();2447            var assertLassoPoints = makeAssertLassoPoints();2448            var fig = Lib.extendDeep({}, require('@mocks/box_grouped'));2449            fig.data.forEach(function(trace) {2450                trace.boxpoints = 'all';2451            });2452            fig.layout.dragmode = 'lasso';2453            fig.layout.width = 600;2454            fig.layout.height = 500;2455            fig.layout.xaxis = {range: [-0.565, 1.5]};2456            addInvisible(fig);2457            Plotly.newPlot(gd, fig)2458            .then(function() {2459                if(hasCssTransform) transformPlot(gd, cssTransform);2460                return _run(hasCssTransform,2461                    [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2462                    function() {2463                        assertPoints([2464                            [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'],2465                            [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'],2466                            [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1']2467                        ]);2468                        assertSelectedPoints({2469                            0: [6, 11, 10, 7],2470                            1: [11, 8, 6, 10],2471                            2: [1, 4, 5]2472                        });2473                        assertLassoPoints([2474                            [0.0423, 1.0546, 1.0546, 0.0423, 0.0423],2475                            [0.71, 0.71, 0.1875, 0.1875, 0.71]2476                        ]);2477                    },2478                    null, LASSOEVENTS, 'box lasso'2479                );2480            })2481            .then(function() {2482                return Plotly.relayout(gd, 'dragmode', 'select');2483            })2484            .then(function() {2485                return _run(hasCssTransform,2486                    [[200, 200], [400, 350]],2487                    function() {2488                        assertPoints([2489                            [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'],2490                            [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'],2491                            [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1']2492                        ]);2493                        assertSelectedPoints({2494                            0: [6, 11, 10, 7],2495                            1: [11, 8, 6, 10],2496                            2: [1, 4, 5]2497                        });2498                        assertRanges([[0.04235, 1.0546], [0.1875, 0.71]]);2499                    },2500                    null, BOXEVENTS, 'box select'2501                );2502            })2503            .then(done, done.fail);2504        });2505    });2506    [false, true].forEach(function(hasCssTransform) {2507        it('should work for box traces (q1/median/q3 case), hasCssTransform: ' + hasCssTransform, function(done) {2508            var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2509            var assertSelectedPoints = makeAssertSelectedPoints();2510            var fig = {2511                data: [{2512                    type: 'box',2513                    x0: 'A',2514                    q1: [1],2515                    median: [2],2516                    q3: [3],2517                    y: [[0, 1, 2, 3, 4]],2518                    pointpos: 0,2519                }],2520                layout: {2521                    width: 500,2522                    height: 500,2523                    dragmode: 'lasso'2524                }2525            };2526            Plotly.newPlot(gd, fig)2527            .then(function() {2528                if(hasCssTransform) transformPlot(gd, cssTransform);2529                return _run(hasCssTransform,2530                    [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2531                    function() {2532                        assertPoints([ [0, 1, undefined], [0, 2, undefined] ]);2533                        assertSelectedPoints({ 0: [[0, 1], [0, 2]] });2534                    },2535                    null, LASSOEVENTS, 'box lasso'2536                );2537            })2538            .then(function() {2539                return Plotly.relayout(gd, 'dragmode', 'select');2540            })2541            .then(function() {2542                return _run(hasCssTransform,2543                    [[200, 200], [400, 300]],2544                    function() {2545                        assertPoints([ [0, 2, undefined] ]);2546                        assertSelectedPoints({ 0: [[0, 2]] });2547                    },2548                    null, BOXEVENTS, 'box select'2549                );2550            })2551            .then(done, done.fail);2552        });2553    });2554    [false, true].forEach(function(hasCssTransform) {2555        it('should work for violin traces, hasCssTransform: ' + hasCssTransform, function(done) {2556            var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2557            var assertSelectedPoints = makeAssertSelectedPoints();2558            var assertRanges = makeAssertRanges();2559            var assertLassoPoints = makeAssertLassoPoints();2560            var fig = Lib.extendDeep({}, require('@mocks/violin_grouped'));2561            fig.layout.dragmode = 'lasso';2562            fig.layout.width = 600;2563            fig.layout.height = 500;2564            addInvisible(fig);2565            Plotly.newPlot(gd, fig)2566            .then(function() {2567                if(hasCssTransform) transformPlot(gd, cssTransform);2568                return _run(hasCssTransform,2569                    [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2570                    function() {2571                        assertPoints([2572                            [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'],2573                            [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'],2574                            [1, 0.9, 'day 2'],2575                            [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1']2576                        ]);2577                        assertSelectedPoints({2578                            0: [11, 10, 7, 8],2579                            1: [8, 6, 10, 9, 7],2580                            2: [1, 4, 5, 3]2581                        });2582                        assertLassoPoints([2583                            [0.07777, 1.0654, 1.0654, 0.07777, 0.07777],2584                            [1.02, 1.02, 0.27, 0.27, 1.02]2585                        ]);2586                    },2587                    null, LASSOEVENTS, 'violin lasso'2588                );2589            })2590            .then(function() {2591                return Plotly.relayout(gd, 'dragmode', 'select');2592            })2593            .then(function() {2594                return _run(hasCssTransform,2595                    [[200, 200], [400, 350]],2596                    function() {2597                        assertPoints([2598                            [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'],2599                            [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'],2600                            [1, 0.9, 'day 2'],2601                            [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1']2602                        ]);2603                        assertSelectedPoints({2604                            0: [11, 10, 7, 8],2605                            1: [8, 6, 10, 9, 7],2606                            2: [1, 4, 5, 3]2607                        });2608                        assertRanges([[0.07777, 1.0654], [0.27, 1.02]]);2609                    },2610                    null, BOXEVENTS, 'violin select'2611                );2612            })2613            .then(done, done.fail);2614        });2615    });2616    [false].forEach(function(hasCssTransform) {2617        ['ohlc', 'candlestick'].forEach(function(type) {2618            it('should work for ' + type + ' traces, hasCssTransform: ' + hasCssTransform, function(done) {2619                var assertPoints = makeAssertPoints(['curveNumber', 'x', 'open', 'high', 'low', 'close']);2620                var assertSelectedPoints = makeAssertSelectedPoints();2621                var assertRanges = makeAssertRanges();2622                var assertLassoPoints = makeAssertLassoPoints();2623                var l0 = 275;2624                var lv0 = '2011-01-03 18:00';2625                var r0 = 325;2626                var rv0 = '2011-01-04 06:00';2627                var l1 = 75;2628                var lv1 = '2011-01-01 18:00';2629                var r1 = 125;2630                var rv1 = '2011-01-02 06:00';2631                var t = 75;2632                var tv = 7.565;2633                var b = 225;2634                var bv = -1.048;2635                function countUnSelectedPaths(selector) {2636                    var unselected = 0;2637                    d3Select(gd).selectAll(selector).each(function() {2638                        var opacity = this.style.opacity;2639                        if(opacity < 1) unselected++;2640                    });2641                    return unselected;2642                }2643                Plotly.newPlot(gd, [{2644                    type: type,2645                    x: ['2011-01-02', '2011-01-03', '2011-01-04'],2646                    open: [1, 2, 3],2647                    high: [3, 4, 5],2648                    low: [0, 1, 2],2649                    close: [0, 3, 2]2650                }], {2651                    width: 400,2652                    height: 400,2653                    margin: {l: 50, r: 50, t: 50, b: 50},2654                    yaxis: {range: [-3, 9]},2655                    dragmode: 'lasso'2656                })2657                .then(function() {2658                    if(hasCssTransform) transformPlot(gd, cssTransform);2659                    return _run(hasCssTransform,2660                        [[l0, t], [l0, b], [r0, b], [r0, t], [l0, t]],2661                        function() {2662                            assertPoints([[0, '2011-01-04', 3, 5, 2, 2]]);2663                            assertSelectedPoints([[2]]);2664                            assertLassoPoints([2665                                [lv0, lv0, rv0, rv0, lv0],2666                                [tv, bv, bv, tv, tv]2667                            ]);2668                            expect(countUnSelectedPaths('.cartesianlayer .trace path')).toBe(2);2669                            expect(countUnSelectedPaths('.rangeslider-rangeplot .trace path')).toBe(2);2670                        },2671                        null, LASSOEVENTS, type + ' lasso'2672                    );2673                })2674                .then(function() {2675                    return Plotly.relayout(gd, 'dragmode', 'select');2676                })2677                .then(function() {2678                    return _run(hasCssTransform,2679                        [[l1, t], [r1, b]],2680                        function() {2681                            assertPoints([[0, '2011-01-02', 1, 3, 0, 0]]);2682                            assertSelectedPoints([[0]]);2683                            assertRanges([[lv1, rv1], [bv, tv]]);2684                        },2685                        null, BOXEVENTS, type + ' select'2686                    );2687                })2688                .then(done, done.fail);2689            });2690        });2691    });2692    [false, true].forEach(function(hasCssTransform) {2693        it('should work on traces with enabled transforms, hasCssTransform: ' + hasCssTransform, function(done) {2694            var assertSelectedPoints = makeAssertSelectedPoints();2695            Plotly.newPlot(gd, [{2696                x: [1, 2, 3, 4, 5],2697                y: [2, 3, 1, 7, 9],2698                marker: {size: [10, 20, 20, 20, 10]},2699                transforms: [{2700                    type: 'filter',2701                    operation: '>',2702                    value: 2,2703                    target: 'y'2704                }, {2705                    type: 'aggregate',2706                    groups: 'marker.size',2707                    aggregations: [2708                        // 20: 6, 10: 52709                        {target: 'x', func: 'sum'},2710                        // 20: 5, 10: 92711                        {target: 'y', func: 'avg'}2712                    ]2713                }]2714            }], {2715                dragmode: 'select',2716                showlegend: false,2717                width: 400,2718                height: 400,2719                margin: {l: 0, t: 0, r: 0, b: 0}2720            })2721            .then(function() {2722                if(hasCssTransform) transformPlot(gd, cssTransform);2723                return _run(hasCssTransform,2724                    [[5, 5], [395, 395]],2725                    function() {2726                        assertSelectedPoints({0: [1, 3, 4]});2727                    },2728                    [380, 180],2729                    BOXEVENTS, 'transformed trace select (all points selected)'2730                );2731            })2732            .then(done, done.fail);2733        });2734    });2735    [false, true].forEach(function(hasCssTransform) {2736        it('should work on scatter/bar traces with text nodes, hasCssTransform: ' + hasCssTransform, function(done) {2737            var assertSelectedPoints = makeAssertSelectedPoints();2738            function assertFillOpacity(exp, msg) {2739                var txtPts = d3Select(gd).select('g.plot').selectAll('text');2740                expect(txtPts.size()).toBe(exp.length, '# of text nodes: ' + msg);2741                txtPts.each(function(_, i) {2742                    var act = Number(this.style['fill-opacity']);2743                    expect(act).toBe(exp[i], 'node ' + i + ' fill opacity: ' + msg);2744                });2745            }2746            Plotly.newPlot(gd, [{2747                mode: 'markers+text',2748                x: [1, 2, 3],2749                y: [1, 2, 1],2750                text: ['a', 'b', 'c']2751            }, {2752                type: 'bar',2753                x: [1, 2, 3],2754                y: [1, 2, 1],2755                text: ['A', 'B', 'C'],2756                textposition: 'outside'2757            }], {2758                dragmode: 'select',2759                hovermode: 'closest',2760                showlegend: false,2761                width: 400,2762                height: 400,2763                margin: {l: 0, t: 0, r: 0, b: 0}2764            })2765            .then(function() {2766                if(hasCssTransform) transformPlot(gd, cssTransform);2767                return _run(hasCssTransform,2768                    [[10, 10], [100, 300]],2769                    function() {2770                        assertSelectedPoints({0: [0], 1: [0]});2771                        assertFillOpacity([1, 0.2, 0.2, 1, 0.2, 0.2], '_run');2772                    },2773                    [10, 10], BOXEVENTS, 'selecting first scatter/bar text nodes'2774                );2775            })2776            .then(function() {2777                assertFillOpacity([1, 1, 1, 1, 1, 1], 'final');2778            })2779            .then(done, done.fail);2780        });2781    });2782    describe('should work on sankey traces', function() {2783        var waitingTime = sankeyConstants.duration * 2;2784        [false].forEach(function(hasCssTransform) {2785            it('select, hasCssTransform: ' + hasCssTransform, function(done) {2786                var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json'));2787                fig.layout.dragmode = 'select';2788                var dblClickPos = [250, 400];2789                Plotly.newPlot(gd, fig)2790                .then(function() {2791                    if(hasCssTransform) transformPlot(gd, cssTransform);2792                    // No groups initially2793                    expect(gd._fullData[0].node.groups).toEqual([]);2794                })2795                .then(function() {2796                    // Grouping the two nodes on the top right2797                    return _run(hasCssTransform,2798                        [[640, 130], [400, 450]],2799                        function() {2800                            expect(gd._fullData[0].node.groups).toEqual([[2, 3]], 'failed to group #2 + #3');2801                        },2802                        dblClickPos, BOXEVENTS, 'for top right nodes #2 and #3'2803                    );2804                })2805                .then(delay(waitingTime))2806                .then(function() {2807                    // Grouping node #4 and the previous group2808                    drag([[715, 400], [300, 110]]);2809                })2810                .then(delay(waitingTime))2811                .then(function() {2812                    expect(gd._fullData[0].node.groups).toEqual([[4, 3, 2]], 'failed to group #4 + existing group of #2 and #3');2813                })2814                .then(done, done.fail);2815            });2816        });2817        it('should not work when dragmode is undefined', function(done) {2818            var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json'));2819            fig.layout.dragmode = undefined;2820            Plotly.newPlot(gd, fig)2821            .then(function() {2822                // No groups initially2823                expect(gd._fullData[0].node.groups).toEqual([]);2824            })2825            .then(function() {2826                // Grouping the two nodes on the top right2827                drag([[640, 130], [400, 450]]);2828            })2829            .then(delay(waitingTime))2830            .then(function() {2831                expect(gd._fullData[0].node.groups).toEqual([]);2832            })2833            .then(done, done.fail);2834        });2835    });2836});2837describe('Test that selections persist:', function() {2838    var gd;2839    beforeEach(function() {2840        gd = createGraphDiv();2841    });2842    afterEach(destroyGraphDiv);2843    function assertPtOpacity(selector, expected) {2844        d3SelectAll(selector).each(function(_, i) {2845            var style = Number(this.style.opacity);2846            expect(style).toBe(expected.style[i], 'style for pt ' + i);2847        });2848    }2849    it('should persist for scatter', function(done) {2850        function _assert(expected) {2851            var selected = gd.calcdata[0].map(function(d) { return d.selected; });2852            expect(selected).toBeCloseToArray(expected.selected, 'selected vals');2853            assertPtOpacity('.point', expected);2854        }2855        Plotly.newPlot(gd, [{2856            x: [1, 2, 3],2857            y: [1, 2, 1]2858        }], {2859            dragmode: 'select',2860            width: 400,2861            height: 400,2862            margin: {l: 0, t: 0, r: 0, b: 0}2863        })2864        .then(function() {2865            resetEvents(gd);2866            drag([[5, 5], [250, 350]]);2867            return selectedPromise;2868        })2869        .then(function() {2870            _assert({2871                selected: [0, 1, 0],2872                style: [0.2, 1, 0.2]2873            });2874            // trigger a recalc2875            Plotly.restyle(gd, 'x', [[10, 20, 30]]);2876        })2877        .then(function() {2878            _assert({2879                selected: [undefined, 1, undefined],2880                style: [0.2, 1, 0.2]2881            });2882        })2883        .then(done, done.fail);2884    });2885    it('should persist for box', function(done) {2886        function _assert(expected) {2887            var selected = gd.calcdata[0][0].pts.map(function(d) { return d.selected; });2888            expect(selected).toBeCloseToArray(expected.cd, 'selected calcdata vals');2889            expect(gd.data[0].selectedpoints).toBeCloseToArray(expected.selectedpoints, 'selectedpoints array');2890            assertPtOpacity('.point', expected);2891        }2892        Plotly.newPlot(gd, [{2893            type: 'box',2894            x0: 0,2895            y: [5, 4, 4, 1, 2, 2, 2, 2, 2, 3, 3, 3],2896            boxpoints: 'all'2897        }], {2898            dragmode: 'select',2899            width: 400,2900            height: 400,2901            margin: {l: 0, t: 0, r: 0, b: 0}2902        })2903        .then(function() {2904            resetEvents(gd);2905            drag([[5, 5], [400, 150]]);2906            return selectedPromise;2907        })2908        .then(function() {2909            _assert({2910                // N.B. pts in calcdata are sorted2911                cd: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],2912                selectedpoints: [1, 2, 0],2913                style: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 1, 1, 1],2914            });2915            // trigger a recalc2916            return Plotly.restyle(gd, 'x0', 1);2917        })2918        .then(function() {2919            _assert({2920                cd: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, 1, 1],2921                selectedpoints: [1, 2, 0],2922                style: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 1, 1, 1],2923            });2924        })2925        .then(done, done.fail);2926    });2927    it('should persist for histogram', function(done) {2928        function _assert(expected) {2929            var selected = gd.calcdata[0].map(function(d) { return d.selected; });2930            expect(selected).toBeCloseToArray(expected.selected, 'selected vals');2931            assertPtOpacity('.point > path', expected);2932        }2933        Plotly.newPlot(gd, [{2934            type: 'histogram',2935            x: [1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5],2936            boxpoints: 'all'2937        }], {2938            dragmode: 'select',2939            width: 400,2940            height: 400,2941            margin: {l: 0, t: 0, r: 0, b: 0}2942        })2943        .then(function() {2944            resetEvents(gd);2945            drag([[5, 5], [400, 150]]);2946            return selectedPromise;2947        })2948        .then(function() {2949            _assert({2950                selected: [0, 1, 0, 0, 0],2951                style: [0.2, 1, 0.2, 0.2, 0.2],2952            });2953            // trigger a recalc2954            return Plotly.restyle(gd, 'histfunc', 'sum');2955        })2956        .then(function() {2957            _assert({2958                selected: [undefined, 1, undefined, undefined, undefined],2959                style: [0.2, 1, 0.2, 0.2, 0.2],2960            });2961        })2962        .then(done, done.fail);2963    });2964});2965describe('Test that selection styles propagate to range-slider plot:', function() {2966    var gd;2967    beforeEach(function() {2968        gd = createGraphDiv();2969    });2970    afterEach(destroyGraphDiv);2971    function makeAssertFn(query) {2972        return function(msg, expected) {2973            var gd3 = d3Select(gd);2974            var mainPlot3 = gd3.select('.cartesianlayer');2975            var rangePlot3 = gd3.select('.rangeslider-rangeplot');2976            mainPlot3.selectAll(query).each(function(_, i) {2977                expect(this.style.opacity).toBe(String(expected[i]), msg + ' opacity for mainPlot pt ' + i);2978            });2979            rangePlot3.selectAll(query).each(function(_, i) {2980                expect(this.style.opacity).toBe(String(expected[i]), msg + ' opacity for rangePlot pt ' + i);2981            });2982        };2983    }2984    it('- svg points case', function(done) {2985        var _assert = makeAssertFn('path.point,.point>path');2986        Plotly.newPlot(gd, [2987            { mode: 'markers', x: [1], y: [1] },2988            { type: 'bar', x: [2], y: [2], },2989            { type: 'histogram', x: [3, 3, 3] },2990            { type: 'box', x: [4], y: [4], boxpoints: 'all' },2991            { type: 'violin', x: [5], y: [5], points: 'all' },2992            { type: 'waterfall', x: [6], y: [6]},2993            { type: 'funnel', x: [7], y: [7], orientation: 'v'}2994        ], {2995            dragmode: 'select',2996            xaxis: { rangeslider: {visible: true} },2997            width: 500,2998            height: 500,2999            margin: {l: 10, t: 10, r: 10, b: 10},3000            showlegend: false3001        })3002        .then(function() {3003            _assert('base', [1, 1, 1, 1, 1, 1, 1]);3004        })3005        .then(function() {3006            resetEvents(gd);3007            drag([[20, 20], [40, 40]]);3008            return selectedPromise;3009        })3010        .then(function() {3011            _assert('after empty selection', [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]);3012        })3013        .then(function() { return doubleClick(200, 200); })3014        .then(function() {3015            _assert('after double-click reset', [1, 1, 1, 1, 1, 1, 1]);3016        })3017        .then(done, done.fail);3018    });3019    it('- svg finance case', function(done) {3020        var _assert = makeAssertFn('path.box,.ohlc>path');3021        Plotly.newPlot(gd, [3022            { type: 'ohlc', x: [6], open: [6], high: [6], low: [6], close: [6] },3023            { type: 'candlestick', x: [7], open: [7], high: [7], low: [7], close: [7] },3024        ], {3025            dragmode: 'select',3026            width: 500,3027            height: 500,3028            margin: {l: 10, t: 10, r: 10, b: 10},3029            showlegend: false3030        })3031        .then(function() {3032            _assert('base', [1, 1]);3033        })3034        .then(function() {3035            resetEvents(gd);3036            drag([[20, 20], [40, 40]]);3037            return selectedPromise;3038        })3039        .then(function() {3040            _assert('after empty selection', [0.3, 0.3]);3041        })3042        .then(function() { return doubleClick(200, 200); })3043        .then(function() {3044            _assert('after double-click reset', [1, 1]);3045        })3046        .then(done, done.fail);3047    });3048});3049// to make sure none of the above tests fail with extraneous invisible traces,3050// add a bunch of them here3051function addInvisible(fig, canHaveLegend) {3052    var data = fig.data;3053    var inputData = Lib.extendDeep([], data);3054    for(var i = 0; i < inputData.length; i++) {3055        data.push(Lib.extendDeep({}, inputData[i], {visible: false}));3056        if(canHaveLegend !== false) data.push(Lib.extendDeep({}, inputData[i], {visible: 'legendonly'}));3057    }3058    if(inputData.length === 1 && fig.layout.showlegend !== true) fig.layout.showlegend = false;...runtime-dom.cjs.js
Source:runtime-dom.cjs.js  
...967            const moveClass = props.moveClass || `${props.name || 'v'}-move`;968            // Check if move transition is needed. This check is cached per-instance.969            hasMove =970                hasMove === null971                    ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))972                    : hasMove;973            if (!hasMove) {974                return;975            }976            // we divide the work into three loops to avoid mixing DOM reads and writes977            // in each iteration - which helps prevent layout thrashing.978            prevChildren.forEach(callPendingCbs);979            prevChildren.forEach(recordPosition);980            const movedChildren = prevChildren.filter(applyTranslation);981            // force reflow to put everything in position982            forceReflow();983            movedChildren.forEach(c => {984                const el = c.el;985                const style = el.style;986                addTransitionClass(el, moveClass);987                style.transform = style.WebkitTransform = style.transitionDuration = '';988                const cb = (el._moveCb = (e) => {989                    if (e && e.target !== el) {990                        return;991                    }992                    if (!e || /transform$/.test(e.propertyName)) {993                        el.removeEventListener('transitionend', cb);994                        el._moveCb = null;995                        removeTransitionClass(el, moveClass);996                    }997                });998                el.addEventListener('transitionend', cb);999            });1000        });1001        return () => {1002            const rawProps = runtimeCore.toRaw(props);1003            const cssTransitionProps = resolveTransitionProps(rawProps);1004            const tag = rawProps.tag || runtimeCore.Fragment;1005            prevChildren = children;1006            children = slots.default ? slots.default() : [];1007            // handle fragment children case, e.g. v-for1008            if (children.length === 1 && children[0].type === runtimeCore.Fragment) {1009                children = children[0].children;1010            }1011            for (let i = 0; i < children.length; i++) {1012                const child = children[i];1013                if (child.key != null) {1014                    runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));1015                }1016                else {1017                    runtimeCore.warn(`<TransitionGroup> children must be keyed.`);1018                }1019            }1020            if (prevChildren) {1021                for (let i = 0; i < prevChildren.length; i++) {1022                    const child = prevChildren[i];1023                    runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));1024                    positionMap.set(child, child.el.getBoundingClientRect());1025                }1026            }1027            return runtimeCore.createVNode(tag, null, children);1028        };1029    }1030};1031const TransitionGroup = TransitionGroupImpl;1032{1033    const props = (TransitionGroup.props = {1034        ...TransitionPropsValidators,1035        tag: String,1036        moveClass: String1037    });1038    delete props.mode;1039}1040function callPendingCbs(c) {1041    if (c.el._moveCb) {1042        c.el._moveCb();1043    }1044    if (c.el._enterCb) {1045        c.el._enterCb();1046    }1047}1048function recordPosition(c) {1049    newPositionMap.set(c, c.el.getBoundingClientRect());1050}1051function applyTranslation(c) {1052    const oldPos = positionMap.get(c);1053    const newPos = newPositionMap.get(c);1054    const dx = oldPos.left - newPos.left;1055    const dy = oldPos.top - newPos.top;1056    if (dx || dy) {1057        const s = c.el.style;1058        s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`;1059        s.transitionDuration = '0s';1060        return c;1061    }1062}1063// this is put in a dedicated function to avoid the line from being treeshaken1064function forceReflow() {1065    return document.body.offsetHeight;1066}1067function hasCSSTransform(el, root, moveClass) {1068    // Detect whether an element with the move class applied has1069    // CSS transitions. Since the element may be inside an entering1070    // transition at this very moment, we make a clone of it and remove1071    // all other transition classes applied to ensure only the move class1072    // is applied.1073    const clone = el.cloneNode();1074    if (el._vtc) {1075        el._vtc.forEach(cls => {1076            cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));1077        });1078    }1079    moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));1080    clone.style.display = 'none';1081    const container = (root.nodeType === 1
...runtime-dom.esm-bundler.js
Source:runtime-dom.esm-bundler.js  
...600            const moveClass = props.moveClass || `${props.name || 'v'}-move`;601            // Check if move transition is needed. This check is cached per-instance.602            hasMove =603                hasMove === null604                    ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))605                    : hasMove;606            if (!hasMove) {607                return;608            }609            // we divide the work into three loops to avoid mixing DOM reads and writes610            // in each iteration - which helps prevent layout thrashing.611            prevChildren.forEach(callPendingCbs);612            prevChildren.forEach(recordPosition);613            const movedChildren = prevChildren.filter(applyTranslation);614            // force reflow to put everything in position615            forceReflow();616            movedChildren.forEach(c => {617                const el = c.el;618                const style = el.style;619                addTransitionClass(el, moveClass);620                style.transform = style.webkitTransform = style.transitionDuration = '';621                const cb = (el._moveCb = (e) => {622                    if (e && e.target !== el) {623                        return;624                    }625                    if (!e || /transform$/.test(e.propertyName)) {626                        el.removeEventListener('transitionend', cb);627                        el._moveCb = null;628                        removeTransitionClass(el, moveClass);629                    }630                });631                el.addEventListener('transitionend', cb);632            });633        });634        return () => {635            const rawProps = toRaw(props);636            const cssTransitionProps = resolveTransitionProps(rawProps);637            const tag = rawProps.tag || Fragment;638            prevChildren = children;639            children = getTransitionRawChildren(slots.default ? slots.default() : []);640            for (let i = 0; i < children.length; i++) {641                const child = children[i];642                if (child.key != null) {643                    setTransitionHooks(child, resolveTransitionHooks(child, cssTransitionProps, state, instance));644                }645                else if ((process.env.NODE_ENV !== 'production') && child.type !== Comment) {646                    warn(`<TransitionGroup> children must be keyed.`);647                }648            }649            if (prevChildren) {650                for (let i = 0; i < prevChildren.length; i++) {651                    const child = prevChildren[i];652                    setTransitionHooks(child, resolveTransitionHooks(child, cssTransitionProps, state, instance));653                    positionMap.set(child, child.el.getBoundingClientRect());654                }655            }656            return createVNode(tag, null, children);657        };658    }659};660function getTransitionRawChildren(children) {661    let ret = [];662    for (let i = 0; i < children.length; i++) {663        const child = children[i];664        // handle fragment children case, e.g. v-for665        if (child.type === Fragment) {666            ret = ret.concat(getTransitionRawChildren(child.children));667        }668        else {669            ret.push(child);670        }671    }672    return ret;673}674// remove mode props as TransitionGroup doesn't support it675delete TransitionGroupImpl.props.mode;676const TransitionGroup = TransitionGroupImpl;677function callPendingCbs(c) {678    const el = c.el;679    if (el._moveCb) {680        el._moveCb();681    }682    if (el._enterCb) {683        el._enterCb();684    }685}686function recordPosition(c) {687    newPositionMap.set(c, c.el.getBoundingClientRect());688}689function applyTranslation(c) {690    const oldPos = positionMap.get(c);691    const newPos = newPositionMap.get(c);692    const dx = oldPos.left - newPos.left;693    const dy = oldPos.top - newPos.top;694    if (dx || dy) {695        const s = c.el.style;696        s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`;697        s.transitionDuration = '0s';698        return c;699    }700}701// this is put in a dedicated function to avoid the line from being treeshaken702function forceReflow() {703    return document.body.offsetHeight;704}705function hasCSSTransform(el, root, moveClass) {706    // Detect whether an element with the move class applied has707    // CSS transitions. Since the element may be inside an entering708    // transition at this very moment, we make a clone of it and remove709    // all other transition classes applied to ensure only the move class710    // is applied.711    const clone = el.cloneNode();712    if (el._vtc) {713        el._vtc.forEach(cls => {714            cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));715        });716    }717    moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));718    clone.style.display = 'none';719    const container = (root.nodeType === 1
...runtime-dom.cjs.prod.js
Source:runtime-dom.cjs.prod.js  
...925            const moveClass = props.moveClass || `${props.name || 'v'}-move`;926            // Check if move transition is needed. This check is cached per-instance.927            hasMove =928                hasMove === null929                    ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))930                    : hasMove;931            if (!hasMove) {932                return;933            }934            // we divide the work into three loops to avoid mixing DOM reads and writes935            // in each iteration - which helps prevent layout thrashing.936            prevChildren.forEach(callPendingCbs);937            prevChildren.forEach(recordPosition);938            const movedChildren = prevChildren.filter(applyTranslation);939            // force reflow to put everything in position940            forceReflow();941            movedChildren.forEach(c => {942                const el = c.el;943                const style = el.style;944                addTransitionClass(el, moveClass);945                style.transform = style.WebkitTransform = style.transitionDuration = '';946                const cb = (el._moveCb = (e) => {947                    if (e && e.target !== el) {948                        return;949                    }950                    if (!e || /transform$/.test(e.propertyName)) {951                        el.removeEventListener('transitionend', cb);952                        el._moveCb = null;953                        removeTransitionClass(el, moveClass);954                    }955                });956                el.addEventListener('transitionend', cb);957            });958        });959        return () => {960            const rawProps = runtimeCore.toRaw(props);961            const cssTransitionProps = resolveTransitionProps(rawProps);962            const tag = rawProps.tag || runtimeCore.Fragment;963            prevChildren = children;964            children = slots.default ? slots.default() : [];965            // handle fragment children case, e.g. v-for966            if (children.length === 1 && children[0].type === runtimeCore.Fragment) {967                children = children[0].children;968            }969            for (let i = 0; i < children.length; i++) {970                const child = children[i];971                if (child.key != null) {972                    runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));973                }974            }975            if (prevChildren) {976                for (let i = 0; i < prevChildren.length; i++) {977                    const child = prevChildren[i];978                    runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));979                    positionMap.set(child, child.el.getBoundingClientRect());980                }981            }982            return runtimeCore.createVNode(tag, null, children);983        };984    }985};986const TransitionGroup = TransitionGroupImpl;987function callPendingCbs(c) {988    if (c.el._moveCb) {989        c.el._moveCb();990    }991    if (c.el._enterCb) {992        c.el._enterCb();993    }994}995function recordPosition(c) {996    newPositionMap.set(c, c.el.getBoundingClientRect());997}998function applyTranslation(c) {999    const oldPos = positionMap.get(c);1000    const newPos = newPositionMap.get(c);1001    const dx = oldPos.left - newPos.left;1002    const dy = oldPos.top - newPos.top;1003    if (dx || dy) {1004        const s = c.el.style;1005        s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`;1006        s.transitionDuration = '0s';1007        return c;1008    }1009}1010// this is put in a dedicated function to avoid the line from being treeshaken1011function forceReflow() {1012    return document.body.offsetHeight;1013}1014function hasCSSTransform(el, root, moveClass) {1015    // Detect whether an element with the move class applied has1016    // CSS transitions. Since the element may be inside an entering1017    // transition at this very moment, we make a clone of it and remove1018    // all other transition classes applied to ensure only the move class1019    // is applied.1020    const clone = el.cloneNode();1021    if (el._vtc) {1022        el._vtc.forEach(cls => {1023            cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));1024        });1025    }1026    moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));1027    clone.style.display = 'none';1028    const container = (root.nodeType === 1
...choroplethmapbox_test.js
Source:choroplethmapbox_test.js  
1var Plotly = require('@lib/index');2var Plots = require('@src/plots/plots');3var Lib = require('@src/lib');4var loggers = require('@src/lib/loggers');5var convertModule = require('@src/traces/choroplethmapbox/convert');6var MAPBOX_ACCESS_TOKEN = require('@build/credentials.json').MAPBOX_ACCESS_TOKEN;7var createGraphDiv = require('../assets/create_graph_div');8var destroyGraphDiv = require('../assets/destroy_graph_div');9var failTest = require('../assets/fail_test');10var mouseEvent = require('../assets/mouse_event');11var supplyAllDefaults = require('../assets/supply_defaults');12var assertHoverLabelContent = require('../assets/custom_assertions').assertHoverLabelContent;13describe('Test choroplethmapbox defaults:', function() {14    var gd;15    var fullData;16    function _supply(opts, layout) {17        gd = {};18        opts = Array.isArray(opts) ? opts : [opts];19        gd.data = opts.map(function(o) {20            return Lib.extendFlat({type: 'choroplethmapbox'}, o || {});21        });22        gd.layout = layout || {};23        supplyAllDefaults(gd);24        fullData = gd._fullData;25    }26    function expectVisibleFalse(msg) {27        fullData.forEach(function(trace, i) {28            expect(trace.visible).toBe(false, 'visible |trace #' + i + '- ' + msg);29            expect(trace._length).toBe(undefined, '_length |trace #' + i + '- ' + msg);30        });31    }32    it('should set *visible:false* when locations or z or geojson is missing', function() {33        _supply([34            {z: [1], geojson: 'url'},35            {locations: ['a'], geojson: 'url'},36            {locations: ['a'], z: [1]}37        ]);38        expectVisibleFalse();39    });40    it('should set *visible:false* when locations or z is empty', function() {41        _supply([42            {locations: [], z: [1], geojson: 'url'},43            {locations: ['a'], z: [], geojson: 'url'},44            {locations: [], z: [], geojson: 'url'}45        ]);46        expectVisibleFalse();47    });48    it('should accept string (URL) and object *geojson*', function() {49        _supply([50            {name: 'ok during defaults, will fail later', locations: ['a'], z: [1], geojson: 'url'},51            {name: 'ok during defaults, will fail later', locations: ['a'], z: [1], geojson: {}},52        ]);53        fullData.forEach(function(trace, i) {54            expect(trace.visible).toBe(true, 'visible |trace #' + i);55            expect(trace._length).toBe(1, '_length |trace #' + i);56        });57        _supply([58            {name: 'no', locations: ['a'], z: [1], geojson: ''},59            {name: 'no', locations: ['a'], z: [1], geojson: []},60            {name: 'no', locations: ['a'], z: [1], geojson: true}61        ]);62        expectVisibleFalse();63    });64    it('should not coerce *marker.line.color* when *marker.line.width* is *0*', function() {65        _supply([{66            locations: ['CAN', 'USA'],67            z: [1, 2],68            geojson: 'url',69            marker: {70                line: {71                    color: 'red',72                    width: 073                }74            }75        }]);76        expect(fullData[0].marker.line.width).toBe(0, 'mlw');77        expect(fullData[0].marker.line.color).toBe(undefined, 'mlc');78    });79});80describe('Test choroplethmapbox convert:', function() {81    var geojson0 = function() {82        return {83            type: 'FeatureCollection',84            features: [85                {type: 'Feature', id: 'a', geometry: {type: 'Polygon', coordinates: []}},86                {type: 'Feature', id: 'b', geometry: {type: 'Polygon', coordinates: []}},87                {type: 'Feature', id: 'c', geometry: {type: 'Polygon', coordinates: []}}88            ]89        };90    };91    var base = function() {92        return {93            locations: ['a', 'b', 'c'],94            z: [10, 20, 5],95            geojson: geojson0()96        };97    };98    function pre(trace, layout) {99        var gd = {data: [Lib.extendFlat({type: 'choroplethmapbox'}, trace)]};100        if(layout) gd.layout = layout;101        supplyAllDefaults(gd);102        Plots.doCalcdata(gd, gd._fullData[0]);103        return gd.calcdata[0];104    }105    function _convert(trace) {106        return convertModule.convert(pre(trace));107    }108    function expectBlank(opts, msg) {109        expect(opts.fill.layout.visibility).toBe('none', msg);110        expect(opts.line.layout.visibility).toBe('none', msg);111        expect(opts.geojson).toEqual({type: 'Point', coordinates: []}, msg);112    }113    function extract(opts, k) {114        return opts.geojson.features.map(function(f) { return f.properties[k]; });115    }116    it('should return early when trace is *visible:false*', function() {117        var opts = _convert(Lib.extendFlat(base(), {visible: false}));118        expectBlank(opts);119    });120    it('should return early when trace is has no *_length*', function() {121        var opts = _convert({122            locations: [],123            z: [],124            geojson: geojson0125        });126        expectBlank(opts);127    });128    it('should return early if something goes wrong while fetching a GeoJSON', function() {129        spyOn(loggers, 'error');130        var opts = _convert({131            locations: ['a'], z: [1],132            geojson: 'url'133        });134        expect(loggers.error).toHaveBeenCalledWith('Oops ... something went wrong when fetching url');135        expectBlank(opts);136    });137    describe('should warn when set GeoJSON is not a *FeatureCollection* or a *Feature* type and return early', function() {138        beforeEach(function() { spyOn(loggers, 'warn'); });139        it('- case missing *type* key', function() {140            var opts = _convert({141                locations: ['a'], z: [1],142                geojson: {143                    missingType: ''144                }145            });146            expectBlank(opts);147            expect(loggers.warn).toHaveBeenCalledWith([148                'Invalid GeoJSON type none.',149                'Traces with locationmode *geojson-id* only support *FeatureCollection* and *Feature* types.'150            ].join(' '));151        });152        it('- case invalid *type*', function() {153            var opts = _convert({154                locations: ['a'], z: [1],155                geojson: {156                    type: 'nop!'157                }158            });159            expectBlank(opts);160            expect(loggers.warn).toHaveBeenCalledWith([161                'Invalid GeoJSON type nop!.',162                'Traces with locationmode *geojson-id* only support *FeatureCollection* and *Feature* types.'163            ].join(' '));164        });165    });166    describe('should log when crossing a GeoJSON geometry that is not a *Polygon* or a *MultiPolygon* type', function() {167        beforeEach(function() { spyOn(loggers, 'log'); });168        it('- case missing geometry *type*', function() {169            var trace = base();170            delete trace.geojson.features[1].geometry.type;171            var opts = _convert(trace);172            expect(opts.geojson.features.length).toBe(2, '# of feature to be rendered');173            expect(loggers.log).toHaveBeenCalledWith([174                'Location b does not have a valid GeoJSON geometry.',175                'Traces with locationmode *geojson-id* only support *Polygon* and *MultiPolygon* geometries.'176            ].join(' '));177        });178        it('- case invalid geometry *type*', function() {179            var trace = base();180            trace.geojson.features[2].geometry.type = 'not-gonna-work';181            var opts = _convert(trace);182            expect(opts.geojson.features.length).toBe(2, '# of feature to be rendered');183            expect(loggers.log).toHaveBeenCalledWith([184                'Location c does not have a valid GeoJSON geometry.',185                'Traces with locationmode *geojson-id* only support *Polygon* and *MultiPolygon* geometries.'186            ].join(' '));187        });188    });189    it('should log when an entry set in *locations* does not a matching feature in the GeoJSON', function() {190        spyOn(loggers, 'log');191        var trace = base();192        trace.locations = ['a', 'b', 'c', 'd'];193        trace.z = [1, 2, 3, 1];194        var opts = _convert(trace);195        expect(opts.geojson.features.length).toBe(3, '# of features to be rendered');196        expect(loggers.log).toHaveBeenCalledWith('Location *d* does not have a matching feature with id-key *id*.');197    });198    describe('should accept numbers as *locations* items', function() {199        function _assert(act) {200            expect(act.fill.layout.visibility).toBe('visible', 'fill layer visibility');201            expect(act.line.layout.visibility).toBe('visible', 'line layer visibility');202            expect(act.geojson.features.length).toBe(3, '# of visible features');203            expect(extract(act, 'fc'))204                .toEqual(['rgb(178, 10, 28)', 'rgb(220, 220, 220)', 'rgb(240, 149, 99)']);205        }206        it('- regular array case', function() {207            var trace = {208                locations: [1, 2, 3],209                z: [20, 10, 2],210                geojson: {211                    type: 'FeatureCollection',212                    features: [213                        {type: 'Feature', id: '1', geometry: {type: 'Polygon', coordinates: []}},214                        {type: 'Feature', id: '3', geometry: {type: 'Polygon', coordinates: []}},215                        {type: 'Feature', id: '2', geometry: {type: 'Polygon', coordinates: []}}216                    ]217                }218            };219            _assert(_convert(trace));220        });221        it('- typed array case', function() {222            var trace = {223                locations: new Float32Array([1, 2, 3]),224                z: new Float32Array([20, 10, 2]),225                geojson: {226                    type: 'FeatureCollection',227                    features: [228                        {type: 'Feature', id: 1, geometry: {type: 'Polygon', coordinates: []}},229                        {type: 'Feature', id: 3, geometry: {type: 'Polygon', coordinates: []}},230                        {type: 'Feature', id: 2, geometry: {type: 'Polygon', coordinates: []}}231                    ]232                }233            };234            _assert(_convert(trace));235        });236    });237    it('should handle *Feature* on 1-item *FeatureCollection* the same way', function() {238        var locations = ['a'];239        var z = [1];240        var feature = {241            type: 'Feature',242            id: 'a',243            geometry: {type: 'Polygon', coordinates: []}244        };245        var opts = _convert({246            locations: locations,247            z: z,248            geojson: feature249        });250        var opts2 = _convert({251            locations: locations,252            z: z,253            geojson: {254                type: 'FeatureCollection',255                features: [feature]256            }257        });258        expect(opts).toEqual(opts2);259    });260    it('should fill stuff in corresponding calcdata items', function() {261        var calcTrace = pre(base());262        var opts = convertModule.convert(calcTrace);263        var fullTrace = calcTrace[0].trace;264        expect(fullTrace._opts).toBe(opts, 'opts ref');265        for(var i = 0; i < calcTrace.length; i++) {266            var cdi = calcTrace[i];267            expect(typeof cdi._polygons).toBe('object', '_polygons |' + i);268            expect(Array.isArray(cdi.ct)).toBe(true, 'ct|' + i);269            expect(typeof cdi.fIn).toBe('object', 'fIn |' + i);270            expect(typeof cdi.fOut).toBe('object', 'fOut |' + i);271        }272    });273    describe('should fill *fill-color* correctly', function() {274        function _assert(act, exp) {275            expect(act.fill.paint['fill-color'])276                .toEqual({type: 'identity', property: 'fc'});277            expect(extract(act, 'fc')).toEqual(exp);278        }279        it('- base case', function() {280            _assert(_convert(base()), [281                'rgb(245, 172, 122)',282                'rgb(178, 10, 28)',283                'rgb(220, 220, 220)'284            ]);285        });286        it('- custom colorscale case', function() {287            var trace = base();288            trace.colorscale = [[0, 'rgb(0, 255, 0)'], [1, 'rgb(0, 0, 255)']];289            trace.zmid = 10;290            _assert(_convert(trace), [291                'rgb(0, 128, 128)',292                'rgb(0, 0, 255)',293                'rgb(0, 191, 64)'294            ]);295        });296    });297    describe('should fill *fill-opacity* correctly', function() {298        function _assertScalar(act, exp) {299            expect(act.fill.paint['fill-opacity']).toBe(exp);300            expect(act.line.paint['line-opacity']).toBe(exp);301            expect(extract(act, 'mo')).toEqual([undefined, undefined, undefined]);302        }303        function _assertArray(act, k, exp) {304            expect(act.fill.paint['fill-opacity']).toEqual({type: 'identity', property: k});305            expect(act.line.paint['line-opacity']).toEqual({type: 'identity', property: k});306            expect(extract(act, k)).toBeCloseToArray(exp, 2);307        }308        function fakeSelect(calcTrace, selectedpoints) {309            if(selectedpoints === null) {310                delete calcTrace[0].trace.selectedpoints;311            } else {312                calcTrace[0].trace.selectedpoints = selectedpoints;313            }314            for(var i = 0; i < calcTrace.length; i++) {315                var cdi = calcTrace[i];316                if(selectedpoints) {317                    if(selectedpoints.indexOf(i) !== -1) {318                        cdi.selected = 1;319                    } else {320                        cdi.selected = 0;321                    }322                } else {323                    delete cdi.selected;324                }325            }326        }327        it('- base case', function() {328            var trace = base();329            trace.marker = {opacity: 0.4};330            _assertScalar(_convert(trace), 0.4);331        });332        it('- arrayOk case', function() {333            var trace = base();334            trace.marker = {opacity: [null, 0.2, -10]};335            _assertArray(_convert(trace), 'mo', [0, 0.2, 0]);336        });337        it('- arrayOk case + bad coordinates', function() {338            var trace = base();339            trace.locations = ['a', null, 'c'];340            trace.marker = {opacity: [-1, 0.2, 0.9]};341            _assertArray(_convert(trace), 'mo', [0, 0.9]);342        });343        it('- selection (base)', function() {344            var trace = base();345            trace.selectedpoints = [1];346            var calcTrace = pre(trace);347            _assertArray(convertModule.convert(calcTrace), 'mo2', [0.2, 1, 0.2]);348            fakeSelect(calcTrace, [1, 2]);349            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.2, 1, 1]);350            fakeSelect(calcTrace, []);351            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.2, 0.2, 0.2]);352            calcTrace[0].trace.unselected = {marker: {opacity: 0}};353            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0, 0, 0]);354            fakeSelect(calcTrace, null);355            _assertScalar(convertModule.convertOnSelect(calcTrace), 1);356        });357        it('- selection of arrayOk marker.opacity', function() {358            var trace = base();359            trace.marker = {opacity: [0.4, 1, 0.8]};360            trace.selectedpoints = [1];361            var calcTrace = pre(trace);362            _assertArray(convertModule.convert(calcTrace), 'mo2', [0.08, 1, 0.16]);363            fakeSelect(calcTrace, [1, 2]);364            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 1, 0.8]);365            calcTrace[0].trace.selected = {marker: {opacity: 1}};366            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 1, 1]);367            fakeSelect(calcTrace, []);368            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 0.2, 0.16]);369            calcTrace[0].trace.unselected = {marker: {opacity: 0}};370            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0, 0, 0]);371            fakeSelect(calcTrace, null);372            _assertArray(convertModule.convertOnSelect(calcTrace), 'mo', [0.4, 1, 0.8]);373        });374    });375    describe('should fill *line-color*, *line-width* correctly', function() {376        it('- base case', function() {377            var trace = base();378            trace.marker = {line: {color: 'blue', width: 3}};379            var opts = _convert(trace);380            expect(opts.line.paint['line-color']).toBe('blue');381            expect(opts.line.paint['line-width']).toBe(3);382            expect(extract(opts, 'mlc')).toEqual([undefined, undefined, undefined]);383            expect(extract(opts, 'mlw')).toEqual([undefined, undefined, undefined]);384        });385        it('- arrayOk case', function() {386            var trace = base();387            trace.marker = {388                line: {389                    color: ['blue', 'red', 'black'],390                    width: [0.1, 2, 10]391                }392            };393            var opts = _convert(trace);394            expect(opts.line.paint['line-color']).toEqual({type: 'identity', property: 'mlc'});395            expect(opts.line.paint['line-width']).toEqual({type: 'identity', property: 'mlw'});396            expect(extract(opts, 'mlc')).toEqual(['blue', 'red', 'black']);397            expect(extract(opts, 'mlw')).toEqual([0.1, 2, 10]);398        });399    });400    it('should find correct centroid (single polygon case)', function() {401        var trace = base();402        var coordsIn = [403            [404                [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],405                [100.0, 1.0], [100.0, 0.0]406            ]407        ];408        trace.geojson.features[0].geometry.coordinates = coordsIn;409        var calcTrace = pre(trace);410        var opts = convertModule.convert(calcTrace);411        expect(opts.geojson.features[0].geometry.coordinates).toBe(coordsIn);412        expect(calcTrace[0].ct).toEqual([100.4, 0.4]);413    });414    it('should find correct centroid (multi-polygon case)', function() {415        var trace = base();416        var coordsIn = [417            [418                // this one has the bigger area419                [[30, 20], [45, 40], [10, 40], [30, 20]]420            ],421            [422                [[15, 5], [40, 10], [10, 20], [5, 10], [15, 5]]423            ]424        ];425        trace.geojson.features[0].geometry.type = 'MultiPolygon';426        trace.geojson.features[0].geometry.coordinates = coordsIn;427        var calcTrace = pre(trace);428        var opts = convertModule.convert(calcTrace);429        expect(opts.geojson.features[0].geometry.coordinates).toBe(coordsIn);430        expect(calcTrace[0].ct).toEqual([28.75, 30]);431    });432});433describe('Test choroplethmapbox hover:', function() {434    var gd;435    afterEach(function(done) {436        Plotly.purge(gd);437        destroyGraphDiv();438        setTimeout(done, 200);439    });440    function transformPlot(gd, transformString) {441        gd.style.webkitTransform = transformString;442        gd.style.MozTransform = transformString;443        gd.style.msTransform = transformString;444        gd.style.OTransform = transformString;445        gd.style.transform = transformString;446    }447    function run(hasCssTransform, s, done) {448        gd = createGraphDiv();449        var scale = 1;450        if(hasCssTransform) {451            scale = 0.5;452        }453        var fig = Lib.extendDeep({},454            s.mock || require('@mocks/mapbox_choropleth0.json')455        );456        if(s.patch) {457            fig = s.patch(fig);458        }459        if(!fig.layout) fig.layout = {};460        if(!fig.layout.mapbox) fig.layout.mapbox = {};461        fig.layout.mapbox.accesstoken = MAPBOX_ACCESS_TOKEN;462        var pos = s.pos || [270, 220];463        return Plotly.newPlot(gd, fig).then(function() {464            if(hasCssTransform) transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');465            var to = setTimeout(function() {466                failTest('no event data received');467                done();468            }, 100);469            gd.on('plotly_hover', function(d) {470                clearTimeout(to);471                assertHoverLabelContent(s);472                var msg = ' - event data ' + s.desc;473                var actual = d.points || [];474                var exp = s.evtPts;475                expect(actual.length).toBe(exp.length, 'pt length' + msg);476                for(var i = 0; i < exp.length; i++) {477                    for(var k in exp[i]) {478                        var m = 'key ' + k + ' in pt ' + i + msg;479                        var matcher = k === 'properties' ? 'toEqual' : 'toBe';480                        expect(actual[i][k])[matcher](exp[i][k], m);481                    }482                }483                // w/o this purge gets called before484                // hover throttle is complete485                setTimeout(done, 0);486            });487            mouseEvent('mousemove', scale * pos[0], scale * pos[1]);488        })489        .catch(failTest);490    }491    var specs = [{492        desc: 'basic',493        nums: '10',494        name: 'NY',495        evtPts: [{location: 'NY', z: 10, pointNumber: 0, curveNumber: 0, properties: {name: 'New York'}}]496    }, {497        desc: 'with a hovertemplate using values in *properties*',498        patch: function(fig) {499            fig.data.forEach(function(t) {500                t.hovertemplate = '%{z:.3f}<extra>PROP::%{properties.name}</extra>';501            });502            return fig;503        },504        nums: '10.000',505        name: 'PROP::New York',506        evtPts: [{location: 'NY', z: 10, pointNumber: 0, curveNumber: 0, properties: {name: 'New York'}}]507    }, {508        desc: 'with "typeof number" locations[i] and feature id (in *name* label case)',509        patch: function() {510            var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));511            fig.data = [fig.data[1]];512            fig.data[0].locations = [100];513            fig.data[0].geojson.id = 100;514            return fig;515        },516        nums: '10',517        name: '100',518        evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]519    }, {520        desc: 'with "typeof number" locations[i] and feature id (in *nums* label case)',521        patch: function() {522            var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));523            fig.data = [fig.data[1]];524            fig.data[0].locations = [100];525            fig.data[0].geojson.id = 100;526            fig.data[0].hoverinfo = 'location+name';527            return fig;528        },529        nums: '100',530        name: 'trace 0',531        evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]532    }, {533        desc: 'with "typeof number" locations[i] and feature id (hovertemplate case)',534        patch: function() {535            var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));536            fig.data = [fig.data[1]];537            fig.data[0].locations = [100];538            fig.data[0].geojson.id = 100;539            fig.data[0].hovertemplate = '### %{location}<extra>%{ct[0]:.1f} | %{ct[1]:.1f} ###</extra>';540            return fig;541        },542        nums: '### 100',543        name: '-86.7 | 32.0 ###',544        evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]545    }];546    specs.forEach(function(s) {547        [false, true].forEach(function(hasCssTransform) {548            it('@gl should generate correct hover labels ' + s.desc + ', hasCssTransform: ' + hasCssTransform, function(done) {549                run(hasCssTransform, s, done);550            });551        });552    });553});554describe('Test choroplethmapbox interactions:', function() {555    var gd;556    var geojson = {557        type: 'Feature',558        id: 'AL',559        geometry: {560            type: 'Polygon',561            coordinates: [[562                [-87.359296, 35.00118 ], [-85.606675, 34.984749 ], [-85.431413, 34.124869 ], [-85.184951, 32.859696 ],563                [-85.069935, 32.580372 ], [-84.960397, 32.421541 ], [-85.004212, 32.322956 ], [-84.889196, 32.262709 ],564                [-85.058981, 32.13674 ], [-85.053504, 32.01077 ], [-85.141136, 31.840985 ], [-85.042551, 31.539753 ],565                [-85.113751, 31.27686 ], [-85.004212, 31.003013 ], [-85.497137, 30.997536 ], [-87.600282, 30.997536 ],566                [-87.633143, 30.86609 ], [-87.408589, 30.674397 ], [-87.446927, 30.510088 ], [-87.37025, 30.427934 ],567                [-87.518128, 30.280057 ], [-87.655051, 30.247195 ], [-87.90699, 30.411504 ], [-87.934375, 30.657966 ],568                [-88.011052, 30.685351 ], [-88.10416, 30.499135 ], [-88.137022, 30.318396 ], [-88.394438, 30.367688 ],569                [-88.471115, 31.895754 ], [-88.241084, 33.796253 ], [-88.098683, 34.891641 ], [-88.202745, 34.995703 ],570                [-87.359296, 35.00118 ]571            ]]572        }573    };574    beforeEach(function() {575        gd = createGraphDiv();576    });577    afterEach(function(done) {578        Plotly.purge(gd);579        destroyGraphDiv();580        setTimeout(done, 200);581    });582    it('@gl should be able to add and remove traces', function(done) {583        function _assert(msg, exp) {584            var map = gd._fullLayout.mapbox._subplot.map;585            var layers = map.getStyle().layers;586            expect(layers.length).toBe(exp.layerCnt, 'total # of layers |' + msg);587        }588        var trace0 = {589            type: 'choroplethmapbox',590            locations: ['AL'],591            z: [10],592            geojson: geojson593        };594        var trace1 = {595            type: 'choroplethmapbox',596            locations: ['AL'],597            z: [1],598            geojson: geojson,599            marker: {opacity: 0.3}600        };601        Plotly.newPlot(gd,602            [trace0, trace1],603            {mapbox: {style: 'basic'}},604            {mapboxAccessToken: MAPBOX_ACCESS_TOKEN}605        )606        .then(function() {607            _assert('base', { layerCnt: 24 });608        })609        .then(function() { return Plotly.deleteTraces(gd, [0]); })610        .then(function() {611            _assert('w/o trace0', { layerCnt: 22 });612        })613        .then(function() { return Plotly.addTraces(gd, [trace0]); })614        .then(function() {615            _assert('after adding trace0', { layerCnt: 24 });616        })617        .then(done, done.fail);618    });619    it('@gl should be able to restyle *below*', function(done) {620        function getLayerIds() {621            var subplot = gd._fullLayout.mapbox._subplot;622            var layers = subplot.map.getStyle().layers;623            var layerIds = layers.map(function(l) { return l.id; });624            return layerIds;625        }626        Plotly.newPlot(gd, [{627            type: 'choroplethmapbox',628            locations: ['AL'],629            z: [10],630            geojson: geojson,631            uid: 'a'632        }], {}, {mapboxAccessToken: MAPBOX_ACCESS_TOKEN})633        .then(function() {634            expect(getLayerIds()).withContext('default *below*').toEqual([635                'background', 'landuse_overlay_national_park', 'landuse_park',636                'waterway', 'water',637                'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',638                'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',639                'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',640                'admin_country', 'poi_label', 'road_major_label',641                'place_label_other', 'place_label_city', 'country_label'642            ]);643        })644        .then(function() { return Plotly.restyle(gd, 'below', ''); })645        .then(function() {646            expect(getLayerIds()).withContext('*below* set to \'\'').toEqual([647                'background', 'landuse_overlay_national_park', 'landuse_park',648                'waterway', 'water',649                'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',650                'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',651                'admin_country', 'poi_label', 'road_major_label',652                'place_label_other', 'place_label_city', 'country_label',653                'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line'654            ]);655        })656        .then(function() { return Plotly.restyle(gd, 'below', 'place_label_other'); })657        .then(function() {658            expect(getLayerIds()).withContext('*below* set to same base layer').toEqual([659                'background', 'landuse_overlay_national_park', 'landuse_park',660                'waterway', 'water',661                'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',662                'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',663                'admin_country', 'poi_label', 'road_major_label',664                'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',665                'place_label_other', 'place_label_city', 'country_label',666            ]);667        })668        .then(function() { return Plotly.restyle(gd, 'below', null); })669        .then(function() {670            expect(getLayerIds()).withContext('back to default *below*').toEqual([671                'background', 'landuse_overlay_national_park', 'landuse_park',672                'waterway', 'water',673                'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',674                'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',675                'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',676                'admin_country', 'poi_label', 'road_major_label',677                'place_label_other', 'place_label_city', 'country_label'678            ]);679        })680        .then(done, done.fail);681    }, 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL);...choropleth_test.js
Source:choropleth_test.js  
1var Choropleth = require('@src/traces/choropleth');2var Plotly = require('@lib/index');3var Plots = require('@src/plots/plots');4var Lib = require('@src/lib');5var loggers = require('@src/lib/loggers');6var d3Select = require('../../strict-d3').select;7var d3SelectAll = require('../../strict-d3').selectAll;8var createGraphDiv = require('../assets/create_graph_div');9var destroyGraphDiv = require('../assets/destroy_graph_div');10var mouseEvent = require('../assets/mouse_event');11var customAssertions = require('../assets/custom_assertions');12var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;13var assertHoverLabelContent = customAssertions.assertHoverLabelContent;14describe('Test choropleth', function() {15    'use strict';16    describe('supplyDefaults', function() {17        var traceIn;18        var traceOut;19        var defaultColor = '#444';20        var layout = {21            font: Plots.layoutAttributes.font,22            _dfltTitle: {colorbar: 'cb'}23        };24        beforeEach(function() {25            traceOut = {};26        });27        it('should set _length based on locations and z but not slice', function() {28            traceIn = {29                locations: ['CAN', 'USA'],30                z: [1, 2, 3]31            };32            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);33            expect(traceOut.z).toEqual([1, 2, 3]);34            expect(traceOut.locations).toEqual(['CAN', 'USA']);35            expect(traceOut._length).toBe(2);36            traceIn = {37                locations: ['CAN', 'USA', 'ALB'],38                z: [1, 2]39            };40            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);41            expect(traceOut.z).toEqual([1, 2]);42            expect(traceOut.locations).toEqual(['CAN', 'USA', 'ALB']);43            expect(traceOut._length).toBe(2);44        });45        it('should make trace invisible if locations is not defined', function() {46            traceIn = {47                z: [1, 2, 3]48            };49            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);50            expect(traceOut.visible).toBe(false);51        });52        it('should make trace invisible if z is not an array', function() {53            traceIn = {54                locations: ['CAN', 'USA'],55                z: 'no gonna work'56            };57            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);58            expect(traceOut.visible).toBe(false);59        });60        it('should not coerce *marker.line.color* when *marker.line.width* is *0*', function() {61            traceIn = {62                locations: ['CAN', 'USA'],63                z: [1, 2],64                marker: {65                    line: {66                        color: 'red',67                        width: 068                    }69                }70            };71            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);72            expect(traceOut.marker.line.width).toBe(0, 'mlw');73            expect(traceOut.marker.line.color).toBe(undefined, 'mlc');74        });75        it('should default locationmode to *geojson-id* when a valid *geojson* is provided', function() {76            traceIn = {77                locations: ['CAN', 'USA'],78                z: [1, 2],79                geojson: 'url'80            };81            traceOut = {};82            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);83            expect(traceOut.locationmode).toBe('geojson-id', 'valid url string');84            traceIn = {85                locations: ['CAN', 'USA'],86                z: [1, 2],87                geojson: {}88            };89            traceOut = {};90            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);91            expect(traceOut.locationmode).toBe('geojson-id', 'valid object');92            traceIn = {93                locations: ['CAN', 'USA'],94                z: [1, 2],95                geojson: ''96            };97            traceOut = {};98            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);99            expect(traceOut.locationmode).toBe('ISO-3', 'invalid sting');100            traceIn = {101                locations: ['CAN', 'USA'],102                z: [1, 2],103                geojson: []104            };105            traceOut = {};106            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);107            expect(traceOut.locationmode).toBe('ISO-3', 'invalid object');108        });109        it('should only coerce *featureidkey* when locationmode is *geojson-id', function() {110            traceIn = {111                locations: ['CAN', 'USA'],112                z: [1, 2],113                geojson: 'url',114                featureidkey: 'properties.name'115            };116            traceOut = {};117            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);118            expect(traceOut.featureidkey).toBe('properties.name', 'coerced');119            traceIn = {120                locations: ['CAN', 'USA'],121                z: [1, 2],122                featureidkey: 'properties.name'123            };124            traceOut = {};125            Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);126            expect(traceOut.featureidkey).toBe(undefined, 'NOT coerced');127        });128    });129});130describe('Test choropleth hover:', function() {131    var gd;132    afterEach(destroyGraphDiv);133    function transformPlot(gd, transformString) {134        gd.style.webkitTransform = transformString;135        gd.style.MozTransform = transformString;136        gd.style.msTransform = transformString;137        gd.style.OTransform = transformString;138        gd.style.transform = transformString;139    }140    function run(hasCssTransform, pos, fig, content, style) {141        gd = createGraphDiv();142        var scale = 1;143        style = style || {144            bgcolor: 'rgb(68, 68, 68)',145            bordercolor: 'rgb(255, 255, 255)',146            fontColor: 'rgb(255, 255, 255)',147            fontSize: 13,148            fontFamily: 'Arial'149        };150        return Plotly.newPlot(gd, fig)151        .then(function() {152            if(hasCssTransform) {153                scale = 0.5;154                transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');155            }156            mouseEvent('mousemove', scale * pos[0], scale * pos[1]);157            assertHoverLabelContent({158                nums: content[0],159                name: content[1]160            });161            assertHoverLabelStyle(162                d3Select('g.hovertext'),163                style164            );165        });166    }167    [false, true].forEach(function(hasCssTransform) {168        it('should generate hover label info (base), hasCssTransform: ' + hasCssTransform, function(done) {169            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));170            run(171                hasCssTransform,172                [400, 160],173                fig,174                ['RUS\n10', 'trace 1']175            )176            .then(done, done.fail);177        });178    });179    [false, true].forEach(function(hasCssTransform) {180        it('should use the hovertemplate, hasCssTransform: ' + hasCssTransform, function(done) {181            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));182            fig.data[1].hovertemplate = 'tpl %{z}<extra>x</extra>';183            run(184                hasCssTransform,185                [400, 160],186                fig,187                ['tpl 10', 'x']188            )189            .then(done, done.fail);190        });191    });192    [false, true].forEach(function(hasCssTransform) {193        it('should generate hover label info (\'text\' single value case), hasCssTransform: ' + hasCssTransform, function(done) {194            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));195            fig.data[1].text = 'tExT';196            fig.data[1].hoverinfo = 'text';197            run(198                hasCssTransform,199                [400, 160],200                fig,201                ['tExT', null]202            )203            .then(done, done.fail);204        });205    });206    [false, true].forEach(function(hasCssTransform) {207        it('should generate hover label info (\'text\' array case), hasCssTransform: ' + hasCssTransform, function(done) {208            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));209            fig.data[1].text = ['tExT', 'TeXt', '-text-'];210            fig.data[1].hoverinfo = 'text';211            run(212                hasCssTransform,213                [400, 160],214                fig,215                ['-text-', null]216            )217            .then(done, done.fail);218        });219    });220    [false, true].forEach(function(hasCssTransform) {221        it('should generate hover labels from `hovertext`, hasCssTransform: ' + hasCssTransform, function(done) {222            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));223            fig.data[1].hovertext = ['tExT', 'TeXt', '-text-'];224            fig.data[1].text = ['N', 'O', 'P'];225            fig.data[1].hoverinfo = 'text';226            run(227                hasCssTransform,228                [400, 160],229                fig,230                ['-text-', null]231            )232            .then(done, done.fail);233        });234    });235    [false, true].forEach(function(hasCssTransform) {236        it('should generate hover label with custom styling, hasCssTransform: ' + hasCssTransform, function(done) {237            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));238            fig.data[1].hoverlabel = {239                bgcolor: 'red',240                bordercolor: ['blue', 'black', 'green'],241                font: {family: 'Roboto'}242            };243            run(244                hasCssTransform,245                [400, 160],246                fig,247                ['RUS\n10', 'trace 1'],248                {249                    bgcolor: 'rgb(255, 0, 0)',250                    bordercolor: 'rgb(0, 128, 0)',251                    fontColor: 'rgb(0, 128, 0)',252                    fontSize: 13,253                    fontFamily: 'Roboto'254                }255            )256            .then(done, done.fail);257        });258    });259    [false, true].forEach(function(hasCssTransform) {260        it('should generate hover label with arrayOk \'hoverinfo\' settings, hasCssTransform: ' + hasCssTransform, function(done) {261            var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));262            fig.data[1].hoverinfo = ['location', 'z', 'location+name'];263            run(264                hasCssTransform,265                [400, 160],266                fig,267                ['RUS', 'trace 1']268            )269            .then(done, done.fail);270        });271    });272    describe('should preserve z formatting hovetemplate equivalence', function() {273        var base = function() {274            return {275                data: [{276                    type: 'choropleth',277                    locations: ['RUS'],278                    z: [10.021321321432143]279                }]280            };281        };282        var pos = [400, 160];283        var exp = ['10.02132', 'RUS'];284        [false, true].forEach(function(hasCssTransform) {285            it('- base case (truncate z decimals), hasCssTransform: ' + hasCssTransform, function(done) {286                run(hasCssTransform, pos, base(), exp)287                .then(done, done.fail);288            });289        });290        [false, true].forEach(function(hasCssTransform) {291            it('- hovertemplate case (same z truncation), hasCssTransform: ' + hasCssTransform, function(done) {292                var fig = base();293                fig.hovertemplate = '%{z}<extra>%{location}</extra>';294                run(hasCssTransform, pos, fig, exp)295                .then(done, done.fail);296            });297        });298    });299    [false, true].forEach(function(hasCssTransform) {300        it('should include *properties* from input custom geojson, hasCssTransform: ' + hasCssTransform, function(done) {301            var fig = Lib.extendDeep({}, require('@mocks/geo_custom-geojson.json'));302            fig.data = [fig.data[1]];303            fig.data[0].hovertemplate = '%{properties.name}<extra>%{ct[0]:.1f} | %{ct[1]:.1f}</extra>';304            fig.layout.geo.projection = {scale: 20};305            run(hasCssTransform, [300, 200], fig, ['New York', '-75.1 | 42.6'])306            .then(done, done.fail);307        });308    });309});310describe('choropleth drawing', function() {311    var gd;312    beforeEach(function() {313        gd = createGraphDiv();314    });315    afterEach(destroyGraphDiv);316    it('should not throw an error with bad locations', function(done) {317        spyOn(loggers, 'log');318        Plotly.newPlot(gd, [{319            locations: ['canada', 0, null, '', 'utopia'],320            z: [1, 2, 3, 4, 5],321            locationmode: 'country names',322            type: 'choropleth'323        }])324        .then(function() {325            // only utopia logs - others are silently ignored326            expect(loggers.log).toHaveBeenCalledTimes(1);327        })328        .then(done, done.fail);329    });330    it('preserves order after hide/show', function(done) {331        function getIndices() {332            var out = [];333            d3SelectAll('.choropleth').each(function(d) { out.push(d[0].trace.index); });334            return out;335        }336        Plotly.newPlot(gd, [{337            type: 'choropleth',338            locations: ['CAN', 'USA'],339            z: [1, 2]340        }, {341            type: 'choropleth',342            locations: ['CAN', 'USA'],343            z: [2, 1]344        }])345        .then(function() {346            expect(getIndices()).toEqual([0, 1]);347            return Plotly.restyle(gd, 'visible', false, [0]);348        })349        .then(function() {350            expect(getIndices()).toEqual([1]);351            return Plotly.restyle(gd, 'visible', true, [0]);352        })353        .then(function() {354            expect(getIndices()).toEqual([0, 1]);355        })356        .then(done, done.fail);357    });...TransitionGroup.js
Source:TransitionGroup.js  
...36      if (!prevChildren.length) {37        return38      }39      const moveClass = props.moveClass || `${props.name || 'v'}-move`40      if (!hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass)) {41        return42      }43      // we divide the work into three loops to avoid mixing DOM reads and writes44      // in each iteration - which helps prevent layout thrashing.45      prevChildren.forEach(callPendingCbs)46      prevChildren.forEach(recordPosition)47      const movedChildren = prevChildren.filter(applyTranslation)48      // force reflow to put everything in position49      forceReflow()50      movedChildren.forEach(c => {51        const el = c.el52        const style = el.style53        addTransitionClass(el, moveClass)54        style.transform = style.webkitTransform = style.transitionDuration = ''...Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/chromium/crPage');2const { chromium } = require('playwright');3(async () => {4  const browser = await chromium.launch();5  const context = await browser.newContext();6  const page = await context.newPage();7  const hasTransform = await hasCSSTransform(page, 'body', 'translate(0px, 0px)');8  console.log(`Has transform: ${hasTransform}`);9  await browser.close();10})();11const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');12const { chromium } = require('playwright');13(async () => {14  const browser = await chromium.launch();15  const context = await browser.newContext();16  const page = await context.newPage();17  const hasTransform = await hasCSSProperty(page, 'body', 'transform', 'translate(0px, 0px)');18  console.log(`Has transform: ${hasTransform}`);19  await browser.close();20})();21const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');22const { chromium } = require('playwright');23(async () => {24  const browser = await chromium.launch();25  const context = await browser.newContext();26  const page = await context.newPage();27  const hasTransform = await hasCSSProperty(page, 'body', 'transform', 'translate(0px, 0px)');28  console.log(`Has transform: ${hasTransform}`);29  await browser.close();30})();31const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');32const { chromium } = require('playwright');33(async () => {34  const browser = await chromium.launch();35  const context = await browser.newContext();Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/chromium/crPage');2const { chromium } = require('playwright');3(async () => {4  const browser = await chromium.launch();5  const context = await browser.newContext();6  const page = await context.newPage();7  await page.waitForSelector('text="Get started"');8  const element = await page.$('text="Get started"');9  const result = await hasCSSTransform(element);10  console.log(result);11  await browser.close();12})();13    at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/webkit/wkConnection.js:41:23)14    at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/chromium/crConnection.js:65:39)15    at ExecutionContext._evaluateInternal (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:132:48)16    at ExecutionContext.evaluateHandle (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:69:17)17    at ExecutionContext.evaluate (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:56:31)18    at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/webkit/wkConnection.js:41:23)19    at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/chromium/crConnection.js:65:39)20    at Page._evaluateInternal (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/Page.js:171:48)21    at Page.evaluateHandle (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/Page.js:108:17)Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/dom.js');2const { chromium } = require('playwright');3(async () => {4  const browser = await chromium.launch();5  const context = await browser.newContext();6  const page = await context.newPage();7  const element = await page.$('#iframeResult');8  const frame = await element.contentFrame();9  const div = await frame.$('div');10  const result = await hasCSSTransform(div, 'rotateX(50deg)');11  console.log(result);12  await browser.close();13})();Using AI Code Generation
1const { hasCSSTransform } = require('playwright-core/lib/server/dom.js');2const { chromium } = require('playwright-core');3(async () => {4  const browser = await chromium.launch();5  const page = await browser.newPage();6  const element = await page.$('text=Learn more');7  const result = await hasCSSTransform(element);8  console.log(result);9  await browser.close();10})();Using AI Code Generation
1const { hasCSSTransform } = require('playwright-core/lib/server/dom.js');2const { chromium } = require('playwright-core');3(async () => {4  const browser = await chromium.launch();5  const page = await browser.newPage();6  const element = await page.$('input[title="Search"]');7  const result = await hasCSSTransform(element);8  console.log(result);9  await browser.close();10})();Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const assert = require('assert');3(async () => {4  const browser = await chromium.launch();5  const page = await browser.newPage();6  const hasTransform = await hasCSSTransform(page, 'input[name="q"]');7  assert.equal(hasTransform, true);8  await browser.close();9})();Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2(async () => {3    const browser = await chromium.launch({headless: false});4    const page = await browser.newPage();5    const element = await page.$('input[name="q"]');6    console.log(await hasCSSTransform(page, element));7    await browser.close();8})();9const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');10(async () => {11    const browser = await chromium.launch({headless: false});12    const page = await browser.newPage();13    const element = await page.$('input[name="q"]');14    await element.evaluate(element => element.style.transform = 'none');15    console.log(await hasCSSTransform(page, element));16    await browser.close();17})();18const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');19(async () => {20    const browser = await chromium.launch({headless: false});21    const page = await browser.newPage();22    const element = await page.$('input[name="q"]');23    await element.evaluate(element => element.style.transform = 'rotate(20deg)');24    console.log(await hasCSSTransform(page, element));25    await browser.close();26})();27const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recUsing AI Code Generation
1const { InternalUtils } = require('playwright/lib/utils/internal-utils');2const { hasCSSTransform } = InternalUtils;3const { chromium } = require('playwright');4(async () => {5  const browser = await chromium.launch();6  const context = await browser.newContext();7  const page = await context.newPage();8  const element = await page.$('input');9  console.log(hasCSSTransform(element));10  await browser.close();11})();12const { InternalUtils } = require('playwright/lib/utils/internal-utils');13const { hasCSSTransform } = InternalUtils;14const { chromium } = require('playwright');15(async () => {16  const browser = await chromium.launch();17  const context = await browser.newContext();18  const page = await context.newPage();19  const element = await page.$('input');20  console.log(hasCSSTransform(element));21  await browser.close();22})();23const { InternalUtils } = require('playwright/lib/utils/internal-utils');24const { hasCSSTransform } = InternalUtils;25const { chromium } = require('playwright');26(async () => {27  const browser = await chromium.launch();28  const context = await browser.newContext();29  const page = await context.newPage();30  const element = await page.$('input');31  console.log(hasCSSTransform(element));32  await browser.close();33})();34const { InternalUtils } = require('playwright/lib/utils/internal-utils');35const { hasCSSTransform } = InternalUtils;36const { chromium } = require('playwright');37(async () => {38  const browser = await chromium.launch();39  const context = await browser.newContext();40  const page = await context.newPage();41  const element = await page.$('input');42  console.log(hasCSSTransform(element));43  await browser.close();44})();Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/dom.js');2const page = await browser.newPage();3const element = await page.$('input[name="q"]');4const supportsCSSTransforms = await hasCSSTransform(element);5console.log(supportsCSSTransforms);6const { BrowserContext } = require('playwright/lib/server/browserContext.js');7const page = await browser.newPage();8const browserName = await page.context().browserName();9const browserVersion = await page.context().browserVersion();10console.log(browserName);11console.log(browserVersion);12const { hasCSSTransform } = require('playwright/lib/server/dom.js');13const page = await browser.newPage();14const element = await page.$('input[name="q"]');15const supportsCSSTransforms = await hasCSSTransform(element);16console.log(supportsCSSTransforms);Using AI Code Generation
1const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');2const hasCSSTransform = hasCSSTransform();3const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');4const hasCSSTransform = hasCSSTransform();5const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');6const hasCSSTransform = hasCSSTransform();7const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');8const hasCSSTransform = hasCSSTransform();9const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');10const hasCSSTransform = hasCSSTransform();11const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');12const hasCSSTransform = hasCSSTransform();13const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');14const hasCSSTransform = hasCSSTransform();15const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');16const hasCSSTransform = hasCSSTransform();17const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');18const hasCSSTransform = hasCSSTransform();19const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');20const hasCSSTransform = hasCSSTransform();21const { hasCSSTransform } = require('@playwright/test/lib/server/domLambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!
