text: var Plotly = require('@lib');
  var Lib = require('@src/lib');
  var Plots = require('@src/plots/plots');
  
  var Violin = require('@src/traces/violin');
  
  var d3 = require('d3');
  var createGraphDiv = require('../assets/create_graph_div');
  var destroyGraphDiv = require('../assets/destroy_graph_div');
  var fail = require('../assets/fail_test');
  var mouseEvent = require('../assets/mouse_event');
  var supplyAllDefaults = require('../assets/supply_defaults');
  
  var customAssertions = require('../assets/custom_assertions');
  var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
  
  describe('Test violin defaults', function() {
      var traceOut;
  
      function _supply(traceIn, layout) {
          traceOut = {};
          Violin.supplyDefaults(traceIn, traceOut, '#444', layout || {});
      }
  
      it('should set visible to false when x and y are empty', function() {
          _supply({
              x: [],
              y: []
          });
  
          expect(traceOut.visible).toBe(false);
      });
  
      it('should inherit layout.calendar', function() {
          _supply({y: [1, 2, 3]}, {calendar: 'islamic'});
  
          // we always fill calendar attributes, because it's hard to tell if
          // we're on a date axis at this point.
          expect(traceOut.xcalendar).toBe('islamic');
          expect(traceOut.ycalendar).toBe('islamic');
      });
  
      it('should take its own calendars', function() {
          _supply({
              y: [1, 2, 3],
              xcalendar: 'coptic',
              ycalendar: 'ethiopian'
          }, {
              calendar: 'islamic'
          });
  
          // we always fill calendar attributes, because it's hard to tell if
          // we're on a date axis at this point.
          expect(traceOut.xcalendar).toBe('coptic');
          expect(traceOut.ycalendar).toBe('ethiopian');
      });
  
      it('should not coerce point attributes when *points* is false', function() {
          _supply({
              y: [1, 1, 2],
              points: false
          });
  
          expect(traceOut.points).toBe(false);
          expect(traceOut.jitter).toBeUndefined();
          expect(traceOut.pointpos).toBeUndefined();
          expect(traceOut.marker).toBeUndefined();
          expect(traceOut.text).toBeUndefined();
      });
  
      it('should default *points* to suspectedoutliers when marker.outliercolor is set & valid', function() {
          _supply({
              y: [1, 1, 2],
              marker: { outliercolor: 'blue' }
          });
  
          expect(traceOut.points).toBe('suspectedoutliers');
      });
  
      it('should default *points* to suspectedoutliers when marker.line.outliercolor is set & valid', function() {
          _supply({
              y: [1, 1, 2],
              marker: { line: {outliercolor: 'blue'} }
          });
  
          expect(traceOut.points).toBe('suspectedoutliers');
          expect(traceOut.marker).toBeDefined();
          expect(traceOut.text).toBeDefined();
      });
  
      it('should default *spanmode* to manual when *span* is set to an array', function() {
          _supply({
              y: [1, 1, 2],
              span: [0, 1]
          });
          expect(traceOut.span).toEqual([0, 1]);
          expect(traceOut.spanmode).toBe('manual');
  
          _supply({
              x: [1, 1, 2],
              span: 'not-gonna-work'
          });
          expect(traceOut.span).toBeUndefined();
          expect(traceOut.spanmode).toBe('soft');
      });
  
      it('should default *.visible attributes when one of their corresponding style attributes is set & valid', function() {
          _supply({
              y: [1, 2, 1],
              box: { width: 0.1 },
              meanline: { color: 'red' }
          });
          expect(traceOut.box.visible).toBe(true);
          expect(traceOut.meanline.visible).toBe(true);
  
          _supply({
              y: [1, 2, 1],
              box: {
                  visible: false,
                  width: 0.1
              },
              meanline: {
                  visible: false,
                  color: 'red'
              }
          });
          expect(traceOut.box).toBeUndefined();
          expect(traceOut.meanline).toBeUndefined();
      });
  
      it('should use violin style settings to default inner style attribute', function() {
          _supply({
              y: [1, 2, 1],
              fillcolor: 'red',
              line: {color: 'blue', width: 10},
              box: {visible: true},
              meanline: {visible: true}
          });
          expect(traceOut.box.fillcolor).toBe('red');
          expect(traceOut.box.line.color).toBe('blue');
          expect(traceOut.box.line.width).toBe(10);
          expect(traceOut.meanline.color).toBe('blue');
          expect(traceOut.meanline.width).toBe(10);
      });
  });
  
  describe('Test violin calc:', function() {
      var cd, fullLayout;
  
      function _calc(attrs, layout) {
          var gd = {
              data: [Lib.extendFlat({type: 'violin'}, attrs)],
              layout: layout || {},
              calcdata: []
          };
          supplyAllDefaults(gd);
          Plots.doCalcdata(gd);
          cd = gd.calcdata[0];
          fullLayout = gd._fullLayout;
      }
  
      it('should compute bandwidth and span based on the sample and *spanmode*', function() {
          var y = [1, 1, 2, 2, 3];
  
          _calc({y: y});
          expect(cd[0].bandwidth).toBeCloseTo(0.64);
          expect(cd[0].span).toBeCloseToArray([-0.28, 4.28]);
  
          _calc({
              y: y,
              spanmode: 'hard'
          });
          expect(cd[0].span).toBeCloseToArray([1, 3]);
  
          _calc({
              y: y,
              span: [0, 0]
          });
          expect(cd[0].span).toBeCloseToArray([-1, 1], 'cleans up invalid range');
  
          _calc({
              y: y,
              span: [null, 5]
          });
          expect(cd[0].span).toBeCloseToArray([-0.28, 5], 'defaults to soft bound');
  
          _calc({
              y: y,
              span: [0, null]
          });
          expect(cd[0].span).toBeCloseToArray([0, 4.28], 'defaults to soft bound');
      });
  
      it('should honor set bandwidth in span calculations', function() {
          var y = [1, 1, 2, 2, 3];
          var bw = 0.1;
  
          _calc({
              y: y,
              bandwidth: bw
          });
          expect(cd[0].bandwidth).toBeCloseTo(0.1);
          expect(cd[0].span).toBeCloseToArray([0.8, 3.2]);
  
          _calc({
              y: y,
              bandwidth: bw,
              spanmode: 'hard'
          });
          expect(cd[0].span).toBeCloseToArray([1, 3]);
  
          _calc({
              y: y,
              bandwidth: bw,
              span: [0, 0]
          });
          expect(cd[0].span).toBeCloseToArray([-1, 1], 'cleans up invalid range');
  
          _calc({
              y: y,
              bandwidth: bw,
              span: [null, 5]
          });
          expect(cd[0].span).toBeCloseToArray([0.8, 5], 'defaults to soft bound');
  
          _calc({
              y: y,
              bandwidth: bw,
              span: [0, null]
          });
          expect(cd[0].span).toBeCloseToArray([0, 3.2], 'defaults to soft bound');
      });
  
      it('should fill in scale-group stats', function() {
          _calc({
              name: 'one',
              y: [0, 0, 0, 0, 10, 10, 10, 10]
          });
          expect(fullLayout._violinScaleGroupStats.one.maxWidth).toBeCloseTo(0.055);
          expect(fullLayout._violinScaleGroupStats.one.maxCount).toBe(8);
      });
  });
  
  describe('Test violin hover:', function() {
      var gd;
  
      afterEach(destroyGraphDiv);
  
      function run(specs) {
          gd = createGraphDiv();
  
          var fig = Lib.extendDeep(
              {width: 700, height: 500},
              specs.mock || require('@mocks/violin_grouped.json')
          );
  
          if(specs.patch) {
              fig = specs.patch(fig);
          }
  
          var pos = specs.pos || [200, 200];
  
          return Plotly.plot(gd, fig).then(function() {
              mouseEvent('mousemove', pos[0], pos[1]);
              assertHoverLabelContent(specs);
          });
      }
  
      [{
          desc: 'base',
          nums: [
              'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7',
              'y: 0.9266848, kde: 0.182'
          ],
          name: ['radishes', '', '', '', '', ''],
          axis: 'day 1'
      }, {
          desc: 'with mean',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.meanline = {visible: true};
              });
              return fig;
          },
          nums: [
              'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'mean: 0.45',
              'y: 0.9266848, kde: 0.182'
          ],
          name: ['radishes', '', '', '', '', '', ''],
          axis: 'day 1'
      }, {
          desc: 'with overlaid violins',
          patch: function(fig) {
              fig.layout.violinmode = 'overlay';
              return fig;
          },
          nums: [
              'q3: 0.6', 'median: 0.45', 'q3: 0.6', 'max: 1', 'y: 0.9266848, kde: 0.383',
              'median: 0.55', 'min: 0', 'q1: 0.3', 'min: 0.2', 'max: 0.7', 'y: 0.9266848, kde: 0.182',
              'median: 0.45', 'min: 0.1', 'q3: 0.6', 'max: 0.9', 'y: 0.9266848, kde: 0.435'
          ],
          name: [
              '', 'kale', '', '', '', 'radishes', '', '', '', '',
              '', 'carrots', '', '', '', ''
          ],
          axis: 'day 1'
      }, {
          desc: 'hoveron points | hovermode closest',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points';
              });
              fig.layout.hovermode = 'closest';
              return fig;
          },
          pos: [220, 200],
          nums: '(day 1, 0.9)',
          name: 'carrots'
      }, {
          desc: 'hoveron points | hovermode x',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points';
              });
              fig.layout.hovermode = 'x';
              return fig;
          },
          pos: [220, 200],
          nums: '0.9',
          name: 'carrots',
          axis: 'day 1'
      }, {
          desc: 'hoveron violins+points | hovermode x (hover on violin only - same result as base)',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points+violins';
              });
              fig.layout.hovermode = 'x';
              return fig;
          },
          nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
          name: ['radishes', '', '', '', ''],
          axis: 'day 1'
      }, {
          desc: 'hoveron violins+points | hovermode x (violin AND closest point)',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points+violins';
                  trace.pointpos = 0;
              });
              fig.layout.hovermode = 'x';
              return fig;
          },
          pos: [207, 240],
          nums: ['0.7', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
          name: ['radishes', 'radishes', '', '', '', ''],
          axis: 'day 1'
      }, {
          desc: 'text items on hover',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points';
                  trace.text = trace.y.map(function(v) { return 'look:' + v; });
              });
              fig.layout.hovermode = 'closest';
              return fig;
          },
          pos: [180, 240],
          nums: '(day 1, 0.7)\nlook:0.7',
          name: 'radishes'
      }, {
          desc: 'only text items on hover',
          patch: function(fig) {
              fig.data.forEach(function(trace) {
                  trace.points = 'all';
                  trace.hoveron = 'points';
                  trace.text = trace.y.map(function(v) { return 'look:' + v; });
                  trace.hoverinfo = 'text';
              });
              fig.layout.hovermode = 'closest';
              return fig;
          },
          pos: [180, 240],
          nums: 'look:0.7',
          name: ''
      }, {
          desc: 'one-sided violin under hovermode closest',
          // hoveron: 'kde+points'
          // hovermode: 'closest'
          // width: 400
          // height: 700
          mock: require('@mocks/violin_side-by-side.json'),
          pos: [250, 300],
          nums: '(x: 42.43046, kde: 0.083, Saturday)',
          name: ''
      }, {
          desc: 'one-sided violin under hovermode y',
          // hoveron: 'kde+points'
          // width: 400
          // height: 700
          mock: require('@mocks/violin_side-by-side.json'),
          patch: function(fig) {
              fig.layout.hovermode = 'y';
              return fig;
          },
          pos: [250, 300],
          nums: 'x: 42.43046, kde: 0.083',
          name: '',
          axis: 'Saturday'
      }]
      .forEach(function(specs) {
          it('should generate correct hover labels ' + specs.desc, function(done) {
              run(specs).catch(fail).then(done);
          });
      });
  
      describe('KDE lines inside violin under *kde* hoveron flag', function() {
          var fig;
  
          beforeEach(function() {
              gd = createGraphDiv();
  
              fig = Lib.extendDeep({}, require('@mocks/violin_old-faithful.json'), {
                  layout: {width: 500, height: 500}
              });
              fig.data[0].points = false;
          });
  
          function assertViolinHoverLine(pos) {
              var line = d3.select('.hoverlayer').selectAll('line');
  
              expect(line.size()).toBe(1, 'only one violin line at a time');
              expect(line.attr('class').indexOf('violinline')).toBe(0, 'correct class name');
              expect([
                  line.attr('x1'), line.attr('y1'),
                  line.attr('x2'), line.attr('y2')
              ]).toBeCloseToArray(pos, 'line position');
          }
  
          it('should show in two-sided base case', function(done) {
              Plotly.plot(gd, fig).then(function() {
                  mouseEvent('mousemove', 250, 250);
                  assertViolinHoverLine([299.35, 250, 200.65, 250]);
              })
              .catch(fail)
              .then(done);
          });
  
          it('should show in one-sided positive case', function(done) {
              fig.data[0].side = 'positive';
  
              Plotly.plot(gd, fig).then(function() {
                  mouseEvent('mousemove', 300, 250);
                  assertViolinHoverLine([299.35, 250, 250, 250]);
              })
              .catch(fail)
              .then(done);
          });
  
          it('should show in one-sided negative case', function(done) {
              fig.data[0].side = 'negative';
  
              Plotly.plot(gd, fig).then(function() {
                  mouseEvent('mousemove', 200, 250);
                  assertViolinHoverLine([200.65, 250, 250, 250]);
              })
              .catch(fail)
              .then(done);
          });
      });
  });
  