var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var d3 = require('d3');
var Plots = require('@src/plots/plots');
var Parcoords = require('@src/traces/parcoords');
var attributes = require('@src/traces/parcoords/attributes');

var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var mouseEvent = require('../assets/mouse_event');

// mock with two dimensions (one panel); special case, e.g. left and right panel is obv. the same
var mock2 = require('@mocks/gl2d_parcoords_2.json');

// mock with one dimension (zero panels); special case, as no panel can be rendered
var mock1 = require('@mocks/gl2d_parcoords_1.json');

// mock with zero dimensions; special case, as no dimension can be rendered
var mock0 = Lib.extendDeep({}, mock1);
mock0.data[0].dimensions = [];

var mock = require('@mocks/gl2d_parcoords_large.json');

var lineStart = 30;
var lineCount = 10;

describe('parcoords initialization tests', function() {

    'use strict';

    describe('parcoords global defaults', function() {

        it('should not coerce trace opacity', function() {
            var gd = Lib.extendDeep({}, mock1);

            Plots.supplyDefaults(gd);

            expect(gd._fullData[0].opacity).toBeUndefined();
        });

        it('should use global font as label, tick and range font defaults', function() {
            var gd = Lib.extendDeep({}, mock1);
            gd.layout.font = {
                family: 'Gravitas',
                size: 20,
                color: 'blue'
            };

            Plots.supplyDefaults(gd);

            var expected = {
                family: 'Gravitas',
                size: 17,
                color: 'blue'
            };

            expect(gd._fullData[0].labelfont).toEqual(expected);
            expect(gd._fullData[0].tickfont).toEqual(expected);
            expect(gd._fullData[0].rangefont).toEqual(expected);
        });
    });

    describe('parcoords defaults', function() {

        function _supply(traceIn) {
            var traceOut = { visible: true },
                defaultColor = '#444',
                layout = { font: Plots.layoutAttributes.font };

            Parcoords.supplyDefaults(traceIn, traceOut, defaultColor, layout);

            return traceOut;
        }

        it('\'line\' specification should yield a default color', function() {
            var fullTrace = _supply({});
            expect(fullTrace.line.color).toEqual('#444');
        });

        it('\'colorscale\' should assume a default value if the \'color\' array is specified', function() {
            var fullTrace = _supply({
                line: {
                    color: [35, 63, 21, 42]
                },
                dimensions: [
                    {values: [321, 534, 542, 674]},
                    {values: [562, 124, 942, 189]},
                    {values: [287, 183, 385, 884]},
                    {values: [113, 489, 731, 454]}
                ]
            });
            expect(fullTrace.line).toEqual({
                color: [35, 63, 21, 42],
                colorscale: attributes.line.colorscale.dflt,
                cauto: true,
                autocolorscale: false,
                reversescale: false,
                showscale: false
            });
        });

        it('\'domain\' specification should have a default', function() {
            var fullTrace = _supply({});
            expect(fullTrace.domain).toEqual({x: [0, 1], y: [0, 1]});
        });

        it('\'dimension\' specification should have a default of an empty array', function() {
            var fullTrace = _supply({});
            expect(fullTrace.dimensions).toEqual([]);
        });

        it('\'dimension\' should be used with default values where attributes are not provided', function() {
            var fullTrace = _supply({
                dimensions: [{
                    values: [1],
                    alienProperty: 'Alpha Centauri'
                }]
            });
            expect(fullTrace.dimensions).toEqual([{values: [1], visible: true, tickformat: '3s', _index: 0}]);
        });

        it('\'dimension.visible\' should be set to false, and other props just passed through if \'values\' is not provided', function() {
            var fullTrace = _supply({
                dimensions: [{
                    alienProperty: 'Alpha Centauri'
                }]
            });
            expect(fullTrace.dimensions).toEqual([{visible: false, values: [], _index: 0}]);
        });

        it('\'dimension.visible\' should be set to false, and other props just passed through if \'values\' is an empty array', function() {
            var fullTrace = _supply({
                dimensions: [{
                    values: [],
                    alienProperty: 'Alpha Centauri'
                }]
            });
            expect(fullTrace.dimensions).toEqual([{visible: false, values: [], _index: 0}]);
        });

        it('\'dimension.visible\' should be set to false, and other props just passed through if \'values\' is not an array', function() {
            var fullTrace = _supply({
                dimensions: [{
                    values: null,
                    alienProperty: 'Alpha Centauri'
                }]
            });
            expect(fullTrace.dimensions).toEqual([{visible: false, values: [], _index: 0}]);
        });

        it('\'dimension.values\' should get truncated to a common shortest length', function() {
            var fullTrace = _supply({dimensions: [
                {values: [321, 534, 542, 674]},
                {values: [562, 124, 942]},
                {values: [], visible: true},
                {values: [1, 2], visible: false} // shouldn't be truncated to as false
            ]});
            expect(fullTrace.dimensions).toEqual([
                {values: [], visible: true, tickformat: '3s', _index: 0},
                {values: [], visible: true, tickformat: '3s', _index: 1},
                {values: [], visible: true, tickformat: '3s', _index: 2},
                {values: [1, 2], visible: false, _index: 3}
            ]);
        });
    });

    describe('parcoords calc', function() {

        function _calc(trace) {
            var gd = { data: [trace] };

            Plots.supplyDefaults(gd);

            var fullTrace = gd._fullData[0];
            Parcoords.calc(gd, fullTrace);
            return fullTrace;
        }

        var base = { type: 'parcoords' };

        it('\'colorscale\' should assume a default value if the \'color\' array is specified', function() {

            var fullTrace = _calc(Lib.extendDeep({}, base, {
                line: {
                    color: [35, 63, 21, 42]
                },
                dimensions: [
                    {values: [321, 534, 542, 674]},
                    {values: [562, 124, 942, 189]},
                    {values: [287, 183, 385, 884]},
                    {values: [113, 489, 731, 454]}
                ]
            }));

            expect(fullTrace.line).toEqual({
                color: [35, 63, 21, 42],
                colorscale: attributes.line.colorscale.dflt,
                cauto: true,
                cmin: 21,
                cmax: 63,
                autocolorscale: false,
                reversescale: false,
                showscale: false
            });
        });

        it('use a singular \'color\' if it is not an array', function() {

            var fullTrace = _calc(Lib.extendDeep({}, base, {
                line: {
                    color: '#444'
                },
                dimensions: [
                    {values: [321, 534, 542, 674]},
                    {values: [562, 124, 942, 189]}
                ]
            }));

            expect(fullTrace.line).toEqual({
                color: '#444'
            });
        });

        it('use a singular \'color\' even if a \'colorscale\' is supplied as \'color\' is not an array', function() {

            var fullTrace = _calc(Lib.extendDeep({}, base, {
                line: {
                    color: '#444',
                    colorscale: 'Jet'
                },
                dimensions: [
                    {values: [321, 534, 542, 674]},
                    {values: [562, 124, 942, 189]}
                ]
            }));

            expect(fullTrace.line).toEqual({
                color: '#444'
            });
        });
    });
});

describe('@noCI parcoords', function() {

    beforeAll(function() {
        mock.data[0].dimensions.forEach(function(d) {
            d.values = d.values.slice(lineStart, lineStart + lineCount);
        });
        mock.data[0].line.color = mock.data[0].line.color.slice(lineStart, lineStart + lineCount);
    });

    afterEach(destroyGraphDiv);

    describe('edge cases', function() {

        it('Works fine with one panel only', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);
            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(2);
                expect(document.querySelectorAll('.axis').length).toEqual(2);
                expect(gd.data[0].dimensions[0].visible).not.toBeDefined();
                expect(gd.data[0].dimensions[0].range).not.toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([200, 700]);
                expect(gd.data[0].dimensions[1].range).toBeDefined();
                expect(gd.data[0].dimensions[1].range).toEqual([0, 700000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                done();
            });
        });

        it('Do something sensible if there is no panel i.e. dimension count is less than 2', function(done) {

            var mockCopy = Lib.extendDeep({}, mock1);
            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(1);
                expect(document.querySelectorAll('.axis').length).toEqual(1); // sole axis still shows up
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].visible).not.toBeDefined();
                expect(gd.data[0].dimensions[0].range).not.toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([200, 700]);

                done();
            });
        });

        it('Does not error with zero dimensions', function(done) {

            var mockCopy = Lib.extendDeep({}, mock0);
            var gd = createGraphDiv();

            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(0);
                expect(document.querySelectorAll('.axis').length).toEqual(0);
                done();
            });
        });

        it('Works with duplicate dimension labels', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);

            mockCopy.layout.width = 320;
            mockCopy.data[0].dimensions[1].label = mockCopy.data[0].dimensions[0].label;

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(2);
                expect(document.querySelectorAll('.axis').length).toEqual(2);
                done();
            });
        });

        it('Works with a single line; also, use a longer color array than the number of lines', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);
            var dim, i, j;

            mockCopy.layout.width = 320;
            for(i = 0; i < mockCopy.data[0].dimensions.length; i++) {
                dim = mockCopy.data[0].dimensions[i];
                delete dim.constraintrange;
                dim.range = [1, 2];
                dim.values = [];
                for(j = 0; j < 1; j++) {
                    dim.values[j] = 1 + Math.random();
                }
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(2);
                expect(document.querySelectorAll('.axis').length).toEqual(2);
                expect(gd.data[0].dimensions[0].values.length).toEqual(1);
                done();
            });
        });

        it('Does not raise an error with zero lines and no specified range', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);
            var dim, i;

            mockCopy.layout.width = 320;
            for(i = 0; i < mockCopy.data[0].dimensions.length; i++) {
                dim = mockCopy.data[0].dimensions[i];
                delete dim.range;
                delete dim.constraintrange;
                dim.values = [];
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(2);
                expect(document.querySelectorAll('.axis').length).toEqual(0);
                expect(gd.data[0].dimensions[0].values.length).toEqual(0);
                done();
            });
        });

        it('Works with non-finite `values` elements', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);
            var dim, i, j;
            var values = [[0, 1, 2, 3, 4], [Infinity, NaN, void(0), null, 1]];

            mockCopy.layout.width = 320;
            for(i = 0; i < values.length; i++) {
                dim = mockCopy.data[0].dimensions[i];
                delete dim.range;
                delete dim.constraintrange;
                dim.values = [];
                for(j = 0; j < values[0].length; j++) {
                    dim.values[j] = values[i][j];
                }
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(2);
                expect(document.querySelectorAll('.axis').length).toEqual(2);
                expect(gd.data[0].dimensions[0].values.length).toEqual(values[0].length);
                done();
            });
        });

        it('Works with 60 dimensions', function(done) {

            var mockCopy = Lib.extendDeep({}, mock1);
            var newDimension, i, j;

            mockCopy.layout.width = 1680;
            mockCopy.data[0].dimensions = [];
            for(i = 0; i < 60; i++) {
                newDimension = Lib.extendDeep({}, mock1.data[0].dimensions[0]);
                newDimension.id = 'S' + i;
                newDimension.label = 'S' + i;
                delete newDimension.constraintrange;
                newDimension.range = [1, 2];
                newDimension.values = [];
                for(j = 0; j < 100; j++) {
                    newDimension.values[j] = 1 + Math.random();
                }
                mockCopy.data[0].dimensions[i] = newDimension;
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(60);
                expect(document.querySelectorAll('.axis').length).toEqual(60);
                done();
            });
        });

        it('Truncates 60+ dimensions to 60', function(done) {

            var mockCopy = Lib.extendDeep({}, mock1);
            var newDimension, i, j;

            mockCopy.layout.width = 1680;
            for(i = 0; i < 70; i++) {
                newDimension = Lib.extendDeep({}, mock1.data[0].dimensions[0]);
                newDimension.id = 'S' + i;
                newDimension.label = 'S' + i;
                delete newDimension.constraintrange;
                newDimension.range = [0, 999];
                for(j = 0; j < 10; j++) {
                    newDimension.values[j] = Math.floor(1000 * Math.random());
                }
                mockCopy.data[0].dimensions[i] = newDimension;
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(60);
                expect(document.querySelectorAll('.axis').length).toEqual(60);
                done();
            });
        });

        it('Truncates dimension values to the shortest array, retaining only 3 lines', function(done) {

            var mockCopy = Lib.extendDeep({}, mock1);
            var newDimension, i, j;

            mockCopy.layout.width = 1680;
            for(i = 0; i < 60; i++) {
                newDimension = Lib.extendDeep({}, mock1.data[0].dimensions[0]);
                newDimension.id = 'S' + i;
                newDimension.label = 'S' + i;
                delete newDimension.constraintrange;
                newDimension.range = [0, 999];
                newDimension.values = [];
                for(j = 0; j < 65 - i; j++) {
                    newDimension.values[j] = Math.floor(1000 * Math.random());
                }
                mockCopy.data[0].dimensions[i] = newDimension;
            }

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(60);
                expect(document.querySelectorAll('.axis').length).toEqual(60);
                done();
            });
        });

        it('Skip dimensions which are not plain objects or whose `values` is not an array', function(done) {

            var mockCopy = Lib.extendDeep({}, mock1);
            var newDimension, i, j;

            mockCopy.layout.width = 680;
            mockCopy.data[0].dimensions = [];
            for(i = 0; i < 5; i++) {
                newDimension = Lib.extendDeep({}, mock1.data[0].dimensions[0]);
                newDimension.id = 'S' + i;
                newDimension.label = 'S' + i;
                delete newDimension.constraintrange;
                newDimension.range = [1, 2];
                newDimension.values = [];
                for(j = 0; j < 100; j++) {
                    newDimension.values[j] = 1 + Math.random();
                }
                mockCopy.data[0].dimensions[i] = newDimension;
            }

            mockCopy.data[0].dimensions[0] = 'This is not a plain object';
            mockCopy.data[0].dimensions[1].values = 'This is not an array';

            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(1);
                expect(gd.data[0].dimensions.length).toEqual(5); // it's still five, but ...
                expect(document.querySelectorAll('.axis').length).toEqual(3); // only 3 axes shown
                done();
            });
        });


    });

    describe('basic use', function() {
        var mockCopy,
            gd;

        beforeEach(function(done) {
            mockCopy = Lib.extendDeep({}, mock);
            mockCopy.data[0].domain = {
                x: [0.1, 0.9],
                y: [0.05, 0.85]
            };
            gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
        });

        it('`Plotly.plot` should have proper fields on `gd.data` on initial rendering', function() {

            expect(gd.data.length).toEqual(1);
            expect(gd.data[0].dimensions.length).toEqual(11);
            expect(document.querySelectorAll('.axis').length).toEqual(10); // one dimension is `visible: false`
            expect(gd.data[0].line.cmin).toEqual(-4000);
            expect(gd.data[0].dimensions[0].visible).not.toBeDefined();
            expect(gd.data[0].dimensions[4].visible).toEqual(true);
            expect(gd.data[0].dimensions[5].visible).toEqual(false);
            expect(gd.data[0].dimensions[0].range).not.toBeDefined();
            expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
            expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
            expect(gd.data[0].dimensions[1].range).toBeDefined();
            expect(gd.data[0].dimensions[1].range).toEqual([0, 700000]);
            expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

        });

        it('Calling `Plotly.plot` again should add the new parcoords', function(done) {

            var reversedMockCopy = Lib.extendDeep({}, mockCopy);
            reversedMockCopy.data[0].dimensions = reversedMockCopy.data[0].dimensions.slice().reverse();
            reversedMockCopy.data[0].dimensions.forEach(function(d) {d.id = 'R_' + d.id;});
            reversedMockCopy.data[0].dimensions.forEach(function(d) {d.label = 'R_' + d.label;});

            Plotly.plot(gd, reversedMockCopy.data, reversedMockCopy.layout).then(function() {

                expect(gd.data.length).toEqual(2);

                expect(gd.data[0].dimensions.length).toEqual(11);
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                expect(gd.data[1].dimensions.length).toEqual(11);
                expect(gd.data[1].line.cmin).toEqual(-4000);
                expect(gd.data[1].dimensions[10].constraintrange).toBeDefined();
                expect(gd.data[1].dimensions[10].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[1].dimensions[1].constraintrange).not.toBeDefined();

                expect(document.querySelectorAll('.axis').length).toEqual(20);  // one dimension is `visible: false`

                done();
            });

        });

        it('Calling `Plotly.restyle` with a string path should amend the preexisting parcoords', function(done) {

            expect(gd.data.length).toEqual(1);

            Plotly.restyle(gd, 'line.colorscale', 'Viridis').then(function() {

                expect(gd.data.length).toEqual(1);

                expect(gd.data[0].line.colorscale).toEqual('Viridis');
                expect(gd.data[0].dimensions.length).toEqual(11);
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                done();
            });

        });

        it('Calling `Plotly.restyle` for a dimension should amend the preexisting dimension', function(done) {

            function restyleDimension(key, setterValue) {

                // array values need to be wrapped in an array; unwrapping here for value comparison
                var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue;

                return function() {
                    return Plotly.restyle(gd, 'dimensions[2].' + key, setterValue).then(function() {
                        expect(gd.data[0].dimensions[2][key]).toEqual(value, 'for dimension attribute \'' + key + '\'');
                    });
                };
            }

            restyleDimension('label', 'new label')()
                .then(restyleDimension('tickvals', [[0, 0.1, 0.4, 1, 2]]))
                .then(restyleDimension('ticktext', [['alpha', 'gamma', 'beta', 'omega', 'epsilon']]))
                .then(restyleDimension('tickformat', '4s'))
                .then(restyleDimension('range', [[0, 2]]))
                .then(restyleDimension('constraintrange', [[0, 1]]))
                .then(restyleDimension('values', [[0, 0.1, 0.4, 1, 2, 0, 0.1, 0.4, 1, 2]]))
                .then(restyleDimension('visible', false))
                .then(done);
        });

        it('Calling `Plotly.restyle` with an object should amend the preexisting parcoords', function(done) {

            var newStyle = Lib.extendDeep({}, mockCopy.data[0].line);
            newStyle.colorscale = 'Viridis';
            newStyle.reversescale = false;

            Plotly.restyle(gd, {line: newStyle}).then(function() {

                expect(gd.data.length).toEqual(1);

                expect(gd.data[0].line.colorscale).toEqual('Viridis');
                expect(gd.data[0].line.reversescale).toEqual(false);
                expect(gd.data[0].dimensions.length).toEqual(11);
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                done();
            });


        });

        it('Should emit a \'plotly_restyle\' event', function(done) {

            var tester = (function() {

                var eventCalled = false;

                return {
                    set: function(d) {eventCalled = d;},
                    get: function() {return eventCalled;}
                };
            })();

            gd.on('plotly_restyle', function() {
                tester.set(true);
            });

            expect(tester.get()).toBe(false);
            Plotly.restyle(gd, 'line.colorscale', 'Viridis')
                .then(window.setTimeout(function() {
                    expect(tester.get()).toBe(true);
                    done();
                }, 0));

        });

        it('Should emit a \'plotly_hover\' event', function(done) {

            function testMaker() {

                var eventCalled = false;

                return {
                    set: function() {eventCalled = eventCalled || true;},
                    get: function() {return eventCalled;}
                };
            }

            var hoverTester = testMaker();
            var unhoverTester = testMaker();

            gd.on('plotly_hover', function(d) {
                hoverTester.set({hover: d});
            });

            gd.on('plotly_unhover', function(d) {
                unhoverTester.set({unhover: d});
            });

            expect(hoverTester.get()).toBe(false);
            expect(unhoverTester.get()).toBe(false);

            mouseEvent('mousemove', 324, 216);
            mouseEvent('mouseover', 324, 216);
            mouseEvent('mousemove', 315, 218);
            mouseEvent('mouseover', 315, 218);

            window.setTimeout(function() {

                expect(hoverTester.get()).toBe(true);

                mouseEvent('mousemove', 329, 153);
                mouseEvent('mouseover', 329, 153);

                window.setTimeout(function() {

                    expect(unhoverTester.get()).toBe(true);
                    done();
                }, 20);

            }, 20);

        });

        it('Calling `Plotly.relayout` with string should amend the preexisting parcoords', function(done) {

            expect(gd.layout.width).toEqual(1184);

            Plotly.relayout(gd, 'width', 500).then(function() {

                expect(gd.data.length).toEqual(1);

                expect(gd.layout.width).toEqual(500);
                expect(gd.data[0].line.colorscale).toEqual('Jet');
                expect(gd.data[0].dimensions.length).toEqual(11);
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                done();
            });

        });

        it('Calling `Plotly.relayout`with object should amend the preexisting parcoords', function(done) {

            expect(gd.layout.width).toEqual(1184);

            Plotly.relayout(gd, {width: 500}).then(function() {

                expect(gd.data.length).toEqual(1);

                expect(gd.layout.width).toEqual(500);
                expect(gd.data[0].line.colorscale).toEqual('Jet');
                expect(gd.data[0].dimensions.length).toEqual(11);
                expect(gd.data[0].line.cmin).toEqual(-4000);
                expect(gd.data[0].dimensions[0].constraintrange).toBeDefined();
                expect(gd.data[0].dimensions[0].constraintrange).toEqual([100000, 150000]);
                expect(gd.data[0].dimensions[1].constraintrange).not.toBeDefined();

                done();
            });

        });

    });

    describe('Lifecycle methods', function() {

        it('Plotly.deleteTraces with one trace removes the plot', function(done) {

            var gd = createGraphDiv();
            var mockCopy = Lib.extendDeep({}, mock);

            mockCopy.data[0].line.showscale = false;

            Plotly.plot(gd, mockCopy).then(function() {

                expect(gd.data.length).toEqual(1);

                Plotly.deleteTraces(gd, 0).then(function() {
                    expect(d3.selectAll('.gl-canvas').node(0)).toEqual(null);
                    expect(gd.data.length).toEqual(0);
                    done();
                });
            });
        });

        it('Plotly.deleteTraces with two traces removes the deleted plot', function(done) {

            var gd = createGraphDiv();
            var mockCopy = Lib.extendDeep({}, mock);
            var mockCopy2 = Lib.extendDeep({}, mock);
            mockCopy2.data[0].dimensions.splice(3, 4);
            mockCopy.data[0].line.showscale = false;

            Plotly.plot(gd, mockCopy)
                .then(function() {
                    expect(gd.data.length).toEqual(1);
                    expect(document.querySelectorAll('.y-axis').length).toEqual(10);
                    return Plotly.plot(gd, mockCopy2);
                })
                .then(function() {
                    expect(gd.data.length).toEqual(2);
                    expect(document.querySelectorAll('.y-axis').length).toEqual(10 + 7);
                    return Plotly.deleteTraces(gd, [0]);
                })
                .then(function() {
                    expect(document.querySelectorAll('.gl-canvas').length).toEqual(3);
                    expect(document.querySelectorAll('.y-axis').length).toEqual(7);
                    expect(gd.data.length).toEqual(1);
                    return Plotly.deleteTraces(gd, 0);
                })
                .then(function() {
                    expect(document.querySelectorAll('.gl-canvas').length).toEqual(0);
                    expect(document.querySelectorAll('.y-axis').length).toEqual(0);
                    expect(gd.data.length).toEqual(0);
                    done();
                });
        });

        it('Calling `Plotly.restyle` with zero panels left should erase lines', function(done) {

            var mockCopy = Lib.extendDeep({}, mock2);
            var gd = createGraphDiv();
            Plotly.plot(gd, mockCopy.data, mockCopy.layout);

            function restyleDimension(key, dimIndex, setterValue) {
                var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue;
                return function() {
                    return Plotly.restyle(gd, 'dimensions[' + dimIndex + '].' + key, setterValue).then(function() {
                        expect(gd.data[0].dimensions[dimIndex][key]).toEqual(value, 'for dimension attribute \'' + key + '\'');
                    });
                };
            }

            restyleDimension('values', 1, [[]])()
                .then(function() {
                    d3.selectAll('.parcoords-lines').each(function(d) {
                        var imageArray = d.lineLayer.readPixels(0, 0, d.model.canvasWidth, d.model.canvasHeight);
                        var foundPixel = false;
                        var i = 0;
                        do {
                            foundPixel = foundPixel || imageArray[i++] !== 0;
                        } while(!foundPixel && i < imageArray.length);
                        expect(foundPixel).toEqual(false);
                    });
                    done();
                });
        });

        describe('Having two datasets', function() {

            it('Two subsequent calls to Plotly.plot should create two parcoords rows', function(done) {

                var gd = createGraphDiv();
                var mockCopy = Lib.extendDeep({}, mock);
                var mockCopy2 = Lib.extendDeep({}, mock);
                mockCopy.data[0].domain = {x: [0, 0.45]};
                mockCopy2.data[0].domain = {x: [0.55, 1]};
                mockCopy2.data[0].dimensions.splice(3, 4);

                expect(document.querySelectorAll('.gl-container').length).toEqual(0);

                Plotly.plot(gd, mockCopy)
                    .then(function() {

                        expect(1).toEqual(1);
                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(1);

                        return Plotly.plot(gd, mockCopy2);
                    })
                    .then(function() {

                        expect(1).toEqual(1);
                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(2);

                        done();
                    });
            });

            it('Plotly.addTraces should add a new parcoords row', function(done) {

                var gd = createGraphDiv();
                var mockCopy = Lib.extendDeep({}, mock);
                var mockCopy2 = Lib.extendDeep({}, mock);
                mockCopy.data[0].domain = {y: [0, 0.35]};
                mockCopy2.data[0].domain = {y: [0.65, 1]};
                mockCopy2.data[0].dimensions.splice(3, 4);

                expect(document.querySelectorAll('.gl-container').length).toEqual(0);

                Plotly.plot(gd, mockCopy)
                    .then(function() {

                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(1);

                        return Plotly.addTraces(gd, [mockCopy2.data[0]]);
                    })
                    .then(function() {
                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(2);

                        done();
                    });

            });

            it('Plotly.restyle should update the existing parcoords row', function(done) {

                var gd = createGraphDiv();
                var mockCopy = Lib.extendDeep({}, mock);
                var mockCopy2 = Lib.extendDeep({}, mock);

                delete mockCopy.data[0].dimensions[0].constraintrange;
                delete mockCopy2.data[0].dimensions[0].constraintrange;

                // in this example, the brush range doesn't change...
                mockCopy.data[0].dimensions[2].constraintrange = [0, 2];
                mockCopy2.data[0].dimensions[2].constraintrange = [0, 2];

                // .. but what's inside the brush does:
                function numberUpdater(v) {
                    switch(v) {
                        case 0.5: return 2.5;
                        default: return v;
                    }
                }

                // shuffle around categorical values
                mockCopy2.data[0].dimensions[2].ticktext = ['A', 'B', 'Y', 'AB', 'Z'];
                mockCopy2.data[0].dimensions[2].tickvals = [0, 1, 2, 2.5, 3];
                mockCopy2.data[0].dimensions[2].values = mockCopy2.data[0].dimensions[2].values.map(numberUpdater);

                expect(document.querySelectorAll('.gl-container').length).toEqual(0);

                Plotly.plot(gd, mockCopy)
                    .then(function() {

                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(1);

                        return Plotly.restyle(gd, {
                            // wrap the `dimensions` array
                            dimensions: [mockCopy2.data[0].dimensions]
                        });
                    })
                    .then(function() {

                        expect(document.querySelectorAll('.gl-container').length).toEqual(1);
                        expect(gd.data.length).toEqual(1);

                        done();
                    });

            });
        });
    });
});
