describe('Chart.controllers.line', function() {
  describe('auto', jasmine.fixture.specs('controller.line'));

  it('should be registered as dataset controller', function() {
    expect(typeof Chart.controllers.line).toBe('function');
  });

  it('should be constructed', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: []
        }],
        labels: []
      }
    });

    var meta = chart.getDatasetMeta(0);
    expect(meta.type).toBe('line');
    expect(meta.controller).not.toBe(undefined);
    expect(meta.controller.index).toBe(0);
    expect(meta.data).toEqual([]);

    meta.controller.updateIndex(1);
    expect(meta.controller.index).toBe(1);
  });

  it('Should use the first scale IDs if the dataset does not specify them', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: []
        }],
        labels: []
      },
    });

    var meta = chart.getDatasetMeta(0);
    expect(meta.xAxisID).toBe('x');
    expect(meta.yAxisID).toBe('y');
  });

  it('Should not throw with empty dataset when tension is non-zero', function() {
    // https://github.com/chartjs/Chart.js/issues/8676
    function createChart() {
      return window.acquireChart({
        type: 'line',
        data: {
          datasets: [{
            data: [],
            tension: 0.5
          }],
          labels: []
        },
      });
    }
    expect(createChart).not.toThrow();
  });

  it('should find min and max for stacked chart', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 11, 12, 13]
        }, {
          data: [1, 2, 3, 4]
        }],
        labels: ['a', 'b', 'c', 'd']
      },
      options: {
        scales: {
          y: {
            stacked: true
          }
        }
      }
    });
    expect(chart.getDatasetMeta(0).controller.getMinMax(chart.scales.y, true)).toEqual({min: 10, max: 13});
    expect(chart.getDatasetMeta(1).controller.getMinMax(chart.scales.y, true)).toEqual({min: 11, max: 17});
    chart.hide(0);
    expect(chart.getDatasetMeta(0).controller.getMinMax(chart.scales.y, true)).toEqual({min: 10, max: 13});
    expect(chart.getDatasetMeta(1).controller.getMinMax(chart.scales.y, true)).toEqual({min: 1, max: 4});
  });

  it('Should create line elements and point elements for each data item during initialization', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset1'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      }
    });

    var meta = chart.getDatasetMeta(0);
    expect(meta.data.length).toBe(4); // 4 points created
    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // 1 line element
  });

  it('should draw all elements', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset1'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        showLine: true
      }
    });

    var meta = chart.getDatasetMeta(0);
    spyOn(meta.dataset, 'updateControlPoints');
    spyOn(meta.dataset, 'draw');
    spyOn(meta.data[0], 'draw');
    spyOn(meta.data[1], 'draw');
    spyOn(meta.data[2], 'draw');
    spyOn(meta.data[3], 'draw');

    chart.update();

    expect(meta.dataset.updateControlPoints.calls.count()).toBeGreaterThanOrEqual(1);
    expect(meta.dataset.draw.calls.count()).toBe(1);
    expect(meta.data[0].draw.calls.count()).toBe(1);
    expect(meta.data[1].draw.calls.count()).toBe(1);
    expect(meta.data[2].draw.calls.count()).toBe(1);
    expect(meta.data[3].draw.calls.count()).toBe(1);
  });

  it('should update elements when modifying data', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset',
          xAxisID: 'x',
          yAxisID: 'y'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        showLine: true,
        plugins: {
          legend: false,
          title: false
        },
        elements: {
          point: {
            backgroundColor: 'red',
            borderColor: 'blue',
          }
        },
        scales: {
          x: {
            display: false
          },
          y: {
            display: false
          }
        }
      },
    });

    var meta = chart.getDatasetMeta(0);
    expect(meta.data.length).toBe(4);

    chart.data.datasets[0].data = [1, 2]; // remove 2 items
    chart.data.datasets[0].borderWidth = 1;
    chart.update();

    expect(meta.data.length).toBe(2);
    expect(meta._parsed.length).toBe(2);

    [
      {x: 5, y: 507},
      {x: 171, y: 5}
    ].forEach(function(expected, i) {
      expect(meta.data[i].x).toBeCloseToPixel(expected.x);
      expect(meta.data[i].y).toBeCloseToPixel(expected.y);
      expect(meta.data[i].options).toEqual(jasmine.objectContaining({
        backgroundColor: 'red',
        borderColor: 'blue',
      }));
    });

    chart.data.datasets[0].data = [1, 2, 3]; // add 1 items
    chart.update();

    expect(meta.data.length).toBe(3); // should add a new meta data item
  });

  it('should correctly calculate x scale for label and point', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        labels: ['One'],
        datasets: [{
          data: [1],
        }]
      },
      options: {
        plugins: {
          legend: false,
          title: false
        },
        hover: {
          mode: 'nearest',
          intersect: true
        },
        scales: {
          x: {
            display: false,
          },
          y: {
            display: false,
            beginAtZero: true
          }
        }
      }
    });

    var meta = chart.getDatasetMeta(0);
    // 1 point
    var point = meta.data[0];
    expect(point.x).toBeCloseToPixel(5);

    // 2 points
    chart.data.labels = ['One', 'Two'];
    chart.data.datasets[0].data = [1, 2];
    chart.update();

    var points = meta.data;

    expect(points[0].x).toBeCloseToPixel(5);
    expect(points[1].x).toBeCloseToPixel(507);

    // 3 points
    chart.data.labels = ['One', 'Two', 'Three'];
    chart.data.datasets[0].data = [1, 2, 3];
    chart.update();

    points = meta.data;

    expect(points[0].x).toBeCloseToPixel(5);
    expect(points[1].x).toBeCloseToPixel(256);
    expect(points[2].x).toBeCloseToPixel(507);

    // 4 points
    chart.data.labels = ['One', 'Two', 'Three', 'Four'];
    chart.data.datasets[0].data = [1, 2, 3, 4];
    chart.update();

    points = meta.data;

    expect(points[0].x).toBeCloseToPixel(5);
    expect(points[1].x).toBeCloseToPixel(171);
    expect(points[2].x).toBeCloseToPixel(340);
    expect(points[3].x).toBeCloseToPixel(507);
  });

  it('should update elements when the y scale is stacked', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, -10, 10, -10],
          label: 'dataset1'
        }, {
          data: [10, 15, 0, -4],
          label: 'dataset2'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        plugins: {
          legend: false,
          title: false
        },
        scales: {
          x: {
            display: false,
          },
          y: {
            display: false,
            stacked: true
          }
        }
      }
    });

    var meta0 = chart.getDatasetMeta(0);

    [
      {x: 5, y: 148},
      {x: 171, y: 435},
      {x: 341, y: 148},
      {x: 507, y: 435}
    ].forEach(function(values, i) {
      expect(meta0.data[i].x).toBeCloseToPixel(values.x);
      expect(meta0.data[i].y).toBeCloseToPixel(values.y);
    });

    var meta1 = chart.getDatasetMeta(1);

    [
      {x: 5, y: 5},
      {x: 171, y: 76},
      {x: 341, y: 148},
      {x: 507, y: 492}
    ].forEach(function(values, i) {
      expect(meta1.data[i].x).toBeCloseToPixel(values.x);
      expect(meta1.data[i].y).toBeCloseToPixel(values.y);
    });

  });

  it('should update elements when the y scale is stacked with multiple axes', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, -10, 10, -10],
          label: 'dataset1'
        }, {
          data: [10, 15, 0, -4],
          label: 'dataset2'
        }, {
          data: [10, 10, -10, -10],
          label: 'dataset3',
          yAxisID: 'y2'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        plugins: {
          legend: false,
          title: false,
        },
        scales: {
          x: {
            display: false,
          },
          y: {
            display: false,
            stacked: true
          },
          y2: {
            type: 'linear',
            position: 'right',
            display: false
          }
        }
      }
    });

    var meta0 = chart.getDatasetMeta(0);

    [
      {x: 5, y: 148},
      {x: 171, y: 435},
      {x: 341, y: 148},
      {x: 507, y: 435}
    ].forEach(function(values, i) {
      expect(meta0.data[i].x).toBeCloseToPixel(values.x);
      expect(meta0.data[i].y).toBeCloseToPixel(values.y);
    });

    var meta1 = chart.getDatasetMeta(1);

    [
      {x: 5, y: 5},
      {x: 171, y: 76},
      {x: 341, y: 148},
      {x: 507, y: 492}
    ].forEach(function(values, i) {
      expect(meta1.data[i].x).toBeCloseToPixel(values.x);
      expect(meta1.data[i].y).toBeCloseToPixel(values.y);
    });

  });

  it('should update elements when the y scale is stacked and datasets is scatter data', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [{
            x: 0,
            y: 10
          }, {
            x: 1,
            y: -10
          }, {
            x: 2,
            y: 10
          }, {
            x: 3,
            y: -10
          }],
          label: 'dataset1'
        }, {
          data: [{
            x: 0,
            y: 10
          }, {
            x: 1,
            y: 15
          }, {
            x: 2,
            y: 0
          }, {
            x: 3,
            y: -4
          }],
          label: 'dataset2'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        plugins: {
          legend: false,
          title: false
        },
        scales: {
          x: {
            display: false,
          },
          y: {
            display: false,
            stacked: true
          }
        }
      }
    });

    var meta0 = chart.getDatasetMeta(0);

    [
      {x: 5, y: 148},
      {x: 171, y: 435},
      {x: 341, y: 148},
      {x: 507, y: 435}
    ].forEach(function(values, i) {
      expect(meta0.data[i].x).toBeCloseToPixel(values.x);
      expect(meta0.data[i].y).toBeCloseToPixel(values.y);
    });

    var meta1 = chart.getDatasetMeta(1);

    [
      {x: 5, y: 5},
      {x: 171, y: 76},
      {x: 341, y: 148},
      {x: 507, y: 492}
    ].forEach(function(values, i) {
      expect(meta1.data[i].x).toBeCloseToPixel(values.x);
      expect(meta1.data[i].y).toBeCloseToPixel(values.y);
    });

  });

  it('should update elements when the y scale is stacked and data is strings', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: ['10', '-10', '10', '-10'],
          label: 'dataset1'
        }, {
          data: ['10', '15', '0', '-4'],
          label: 'dataset2'
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      },
      options: {
        plugins: {
          legend: false,
          title: false
        },
        scales: {
          x: {
            display: false,
          },
          y: {
            display: false,
            stacked: true
          }
        }
      }
    });

    var meta0 = chart.getDatasetMeta(0);

    [
      {x: 5, y: 148},
      {x: 171, y: 435},
      {x: 341, y: 148},
      {x: 507, y: 435}
    ].forEach(function(values, i) {
      expect(meta0.data[i].x).toBeCloseToPixel(values.x);
      expect(meta0.data[i].y).toBeCloseToPixel(values.y);
    });

    var meta1 = chart.getDatasetMeta(1);

    [
      {x: 5, y: 5},
      {x: 171, y: 76},
      {x: 341, y: 148},
      {x: 507, y: 492}
    ].forEach(function(values, i) {
      expect(meta1.data[i].x).toBeCloseToPixel(values.x);
      expect(meta1.data[i].y).toBeCloseToPixel(values.y);
    });

  });

  it('should fall back to the line styles for points', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [0, 0],
          label: 'dataset1',

          // line styles
          backgroundColor: 'rgb(98, 98, 98)',
          borderColor: 'rgb(8, 8, 8)',
          borderWidth: 0.55,
        }],
        labels: ['label1', 'label2']
      }
    });

    var meta = chart.getDatasetMeta(0);

    expect(meta.dataset.options.backgroundColor).toBe('rgb(98, 98, 98)');
    expect(meta.dataset.options.borderColor).toBe('rgb(8, 8, 8)');
    expect(meta.dataset.options.borderWidth).toBe(0.55);
  });

  describe('dataset global defaults', function() {
    beforeEach(function() {
      this._defaults = Chart.helpers.clone(Chart.defaults.datasets.line);
    });

    afterEach(function() {
      Chart.defaults.datasets.line = this._defaults;
      delete this._defaults;
    });

    it('should utilize the dataset global default options', function() {
      Chart.helpers.merge(Chart.defaults.datasets.line, {
        spanGaps: true,
        tension: 0.231,
        backgroundColor: '#add',
        borderWidth: '#daa',
        borderColor: '#dad',
        borderCapStyle: 'round',
        borderDash: [0],
        borderDashOffset: 0.871,
        borderJoinStyle: 'miter',
        fill: 'start',
        cubicInterpolationMode: 'monotone'
      });

      var chart = window.acquireChart({
        type: 'line',
        data: {
          datasets: [{
            data: [0, 0],
            label: 'dataset1'
          }],
          labels: ['label1', 'label2']
        }
      });

      var options = chart.getDatasetMeta(0).dataset.options;

      expect(options.spanGaps).toBe(true);
      expect(options.tension).toBe(0.231);
      expect(options.backgroundColor).toBe('#add');
      expect(options.borderWidth).toBe('#daa');
      expect(options.borderColor).toBe('#dad');
      expect(options.borderCapStyle).toBe('round');
      expect(options.borderDash).toEqual([0]);
      expect(options.borderDashOffset).toBe(0.871);
      expect(options.borderJoinStyle).toBe('miter');
      expect(options.fill).toBe('start');
      expect(options.cubicInterpolationMode).toBe('monotone');
    });

    it('should be overridden by user-supplied values', function() {
      Chart.helpers.merge(Chart.defaults.datasets.line, {
        spanGaps: true,
        tension: 0.231
      });

      var chart = window.acquireChart({
        type: 'line',
        data: {
          datasets: [{
            data: [0, 0],
            label: 'dataset1',
            spanGaps: true,
            backgroundColor: '#dad'
          }],
          labels: ['label1', 'label2']
        },
        options: {
          datasets: {
            line: {
              tension: 0.345,
              backgroundColor: '#add'
            }
          }
        }
      });

      var options = chart.getDatasetMeta(0).dataset.options;

      // dataset-level option overrides global default
      expect(options.spanGaps).toBe(true);
      // chart-level default overrides global default
      expect(options.tension).toBe(0.345);
      // dataset-level option overrides chart-level default
      expect(options.backgroundColor).toBe('#dad');
    });
  });

  it('should obey the chart-level dataset options', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [0, 0],
          label: 'dataset1'
        }],
        labels: ['label1', 'label2']
      },
      options: {
        datasets: {
          line: {
            spanGaps: true,
            tension: 0.231,
            backgroundColor: '#add',
            borderWidth: '#daa',
            borderColor: '#dad',
            borderCapStyle: 'round',
            borderDash: [0],
            borderDashOffset: 0.871,
            borderJoinStyle: 'miter',
            fill: 'start',
            cubicInterpolationMode: 'monotone'
          }
        }
      }
    });

    var options = chart.getDatasetMeta(0).dataset.options;

    expect(options.spanGaps).toBe(true);
    expect(options.tension).toBe(0.231);
    expect(options.backgroundColor).toBe('#add');
    expect(options.borderWidth).toBe('#daa');
    expect(options.borderColor).toBe('#dad');
    expect(options.borderCapStyle).toBe('round');
    expect(options.borderDash).toEqual([0]);
    expect(options.borderDashOffset).toBe(0.871);
    expect(options.borderJoinStyle).toBe('miter');
    expect(options.fill).toBe('start');
    expect(options.cubicInterpolationMode).toBe('monotone');
  });

  it('should obey the dataset options', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [0, 0],
          label: 'dataset1',
          spanGaps: true,
          tension: 0.231,
          backgroundColor: '#add',
          borderWidth: '#daa',
          borderColor: '#dad',
          borderCapStyle: 'round',
          borderDash: [0],
          borderDashOffset: 0.871,
          borderJoinStyle: 'miter',
          fill: 'start',
          cubicInterpolationMode: 'monotone'
        }],
        labels: ['label1', 'label2']
      }
    });

    var options = chart.getDatasetMeta(0).dataset.options;

    expect(options.spanGaps).toBe(true);
    expect(options.tension).toBe(0.231);
    expect(options.backgroundColor).toBe('#add');
    expect(options.borderWidth).toBe('#daa');
    expect(options.borderColor).toBe('#dad');
    expect(options.borderCapStyle).toBe('round');
    expect(options.borderDash).toEqual([0]);
    expect(options.borderDashOffset).toBe(0.871);
    expect(options.borderJoinStyle).toBe('miter');
    expect(options.fill).toBe('start');
    expect(options.cubicInterpolationMode).toBe('monotone');
  });

  it('should handle number of data point changes in update', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset1',
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      }
    });

    var meta = chart.getDatasetMeta(0);

    chart.data.datasets[0].data = [1, 2]; // remove 2 items
    chart.update();
    expect(meta.data.length).toBe(2);
    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);

    chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items
    chart.update();
    expect(meta.data.length).toBe(5);
    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);
    expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true);
  });

  describe('Interactions', function() {
    beforeEach(function() {
      this.chart = window.acquireChart({
        type: 'line',
        data: {
          labels: ['label1', 'label2', 'label3', 'label4'],
          datasets: [{
            data: [10, 15, 0, -4]
          }]
        },
        options: {
          scales: {
            x: {
              offset: true
            }
          },
          elements: {
            point: {
              backgroundColor: 'rgb(100, 150, 200)',
              borderColor: 'rgb(50, 100, 150)',
              borderWidth: 2,
              radius: 3
            }
          }
        }
      });
    });

    it ('should handle default hover styles', async function() {
      var chart = this.chart;
      var point = chart.getDatasetMeta(0).data[0];

      await jasmine.triggerMouseEvent(chart, 'mousemove', point);
      expect(point.options.backgroundColor).toBe('#3187DD');
      expect(point.options.borderColor).toBe('#175A9D');
      expect(point.options.borderWidth).toBe(1);
      expect(point.options.radius).toBe(4);

      await jasmine.triggerMouseEvent(chart, 'mouseout', point);
      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
      expect(point.options.borderWidth).toBe(2);
      expect(point.options.radius).toBe(3);
    });

    it ('should handle hover styles defined via dataset properties', async function() {
      var chart = this.chart;
      var point = chart.getDatasetMeta(0).data[0];

      Chart.helpers.merge(chart.data.datasets[0], {
        hoverBackgroundColor: 'rgb(200, 100, 150)',
        hoverBorderColor: 'rgb(150, 50, 100)',
        hoverBorderWidth: 8.4,
        hoverRadius: 4.2
      });

      chart.update();

      await jasmine.triggerMouseEvent(chart, 'mousemove', point);
      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
      expect(point.options.borderWidth).toBe(8.4);
      expect(point.options.radius).toBe(4.2);

      await jasmine.triggerMouseEvent(chart, 'mouseout', point);
      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
      expect(point.options.borderWidth).toBe(2);
      expect(point.options.radius).toBe(3);
    });

    it('should handle hover styles defined via element options', async function() {
      var chart = this.chart;
      var point = chart.getDatasetMeta(0).data[0];

      Chart.helpers.merge(chart.options.elements.point, {
        hoverBackgroundColor: 'rgb(200, 100, 150)',
        hoverBorderColor: 'rgb(150, 50, 100)',
        hoverBorderWidth: 8.4,
        hoverRadius: 4.2
      });

      chart.update();

      await jasmine.triggerMouseEvent(chart, 'mousemove', point);
      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
      expect(point.options.borderWidth).toBe(8.4);
      expect(point.options.radius).toBe(4.2);

      await jasmine.triggerMouseEvent(chart, 'mouseout', point);
      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
      expect(point.options.borderWidth).toBe(2);
      expect(point.options.radius).toBe(3);
    });

    it('should handle dataset hover styles defined via dataset properties', async function() {
      var chart = this.chart;
      var point = chart.getDatasetMeta(0).data[0];
      var dataset = chart.getDatasetMeta(0).dataset;

      Chart.helpers.merge(chart.data.datasets[0], {
        backgroundColor: '#AAA',
        borderColor: '#BBB',
        borderWidth: 6,
        hoverBackgroundColor: '#000',
        hoverBorderColor: '#111',
        hoverBorderWidth: 12
      });

      chart.options.hover = {mode: 'dataset'};
      chart.update();

      await jasmine.triggerMouseEvent(chart, 'mousemove', point);
      expect(dataset.options.backgroundColor).toBe('#000');
      expect(dataset.options.borderColor).toBe('#111');
      expect(dataset.options.borderWidth).toBe(12);

      await jasmine.triggerMouseEvent(chart, 'mouseout', point);
      expect(dataset.options.backgroundColor).toBe('#AAA');
      expect(dataset.options.borderColor).toBe('#BBB');
      expect(dataset.options.borderWidth).toBe(6);
    });
  });

  it('should allow 0 as a point border width', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset1',
          pointBorderWidth: 0
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      }
    });

    var meta = chart.getDatasetMeta(0);
    var point = meta.data[0];

    expect(point.options.borderWidth).toBe(0);
  });

  it('should allow an array as the point border width setting', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, 15, 0, -4],
          label: 'dataset1',
          pointBorderWidth: [1, 2, 3, 4]
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      }
    });

    var meta = chart.getDatasetMeta(0);
    expect(meta.data[0].options.borderWidth).toBe(1);
    expect(meta.data[1].options.borderWidth).toBe(2);
    expect(meta.data[2].options.borderWidth).toBe(3);
    expect(meta.data[3].options.borderWidth).toBe(4);
  });

  it('should render a million points', function() {
    var data = [];
    for (let x = 0; x < 1e6; x++) {
      data.push({x, y: Math.sin(x / 10000)});
    }
    function createChart() {
      window.acquireChart({
        type: 'line',
        data: {
          datasets: [{
            data,
            borderWidth: 1,
            radius: 0
          }],
        },
        options: {
          scales: {
            x: {type: 'linear'},
            y: {type: 'linear'}
          }
        }
      });
    }
    expect(createChart).not.toThrow();
  });

  it('should set skipped points to the reset state', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [10, null, 0, -4],
          label: 'dataset1',
          pointBorderWidth: [1, 2, 3, 4]
        }],
        labels: ['label1', 'label2', 'label3', 'label4']
      }
    });

    var meta = chart.getDatasetMeta(0);
    var point = meta.data[1];
    var {x, y} = point.getProps(['x', 'y'], true);
    expect(point.skip).toBe(true);
    expect(isNaN(x)).toBe(false);
    expect(isNaN(y)).toBe(false);
  });

  it('should honor spangap interval forwards', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          spanGaps: 10,
          data: [{x: 10, y: 123}, {x: 15, y: 124}, {x: 26, y: 125}, {x: 30, y: 126}, {x: 35, y: 127}],
          label: 'dataset1',
        }],
      },
      options: {
        scales: {
          x: {
            type: 'linear',
          }
        }
      }
    });

    var meta = chart.getDatasetMeta(0);
    for (var i = 0; i < meta.data.length; ++i) {
      var point = meta.data[i];
      expect(point.stop).toBe(i === 2);
    }
  });

  it('should honor spangap interval backwards', function() {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          spanGaps: 10,
          data: [{x: 35, y: 123}, {x: 30, y: 124}, {x: 26, y: 125}, {x: 15, y: 126}, {x: 10, y: 127}],
          label: 'dataset1',
        }],
      },
      options: {
        scales: {
          x: {
            type: 'linear',
          }
        }
      }
    });

    var meta = chart.getDatasetMeta(0);
    for (var i = 0; i < meta.data.length; ++i) {
      var point = meta.data[i];
      expect(point.stop).toBe(i === 3);
    }
  });

  it('should correctly calc visible points on update', async() => {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        datasets: [{
          data: [
            {x: 10, y: 20},
            {x: 15, y: 19},
          ]
        }],
      },
      options: {
        scales: {
          y: {
            type: 'linear',
            min: 0,
            max: 25,
          },
          x: {
            type: 'linear',
            min: 0,
            max: 50
          },
        }
      }
    });

    chart.data.datasets[0].data = [
      {x: 10, y: 20},
      {x: 15, y: 19},
      {x: 17, y: 12},
      {x: 50, y: 9},
      {x: 50, y: 9},
      {x: 50, y: 9},
      {x: 51, y: 9},
      {x: 52, y: 9},
      {x: 52, y: 9},
    ];
    chart.update();

    var point = chart.getDatasetMeta(0).data[0];
    var event = {
      type: 'mousemove',
      native: true,
      ...point
    };

    chart._handleEvent(event, false, true);

    const visiblePoints = chart.getSortedVisibleDatasetMetas()[0].data.filter(_ => !_.skip);

    expect(visiblePoints.length).toBe(6);
  }, 500);

  it('should correctly calc _drawStart and _drawCount when first points beyond scale limits are null and spanGaps=true', async() => {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        labels: [0, 10, 20, 30, 40, 50],
        datasets: [{
          data: [3, null, 2, 3, null, 1.5],
          spanGaps: true,
          tension: 0.4
        }]
      },
      options: {
        scales: {
          x: {
            type: 'linear',
            min: 11,
            max: 40,
          }
        }
      }
    });

    chart.update();
    var controller = chart.getDatasetMeta(0).controller;

    expect(controller._drawStart).toBe(0);
    expect(controller._drawCount).toBe(6);
  }, 500);

  it('should correctly calc _drawStart and _drawCount when all points beyond scale limits are null and spanGaps=true', async() => {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        labels: [0, 10, 20, 30, 40, 50],
        datasets: [{
          data: [null, null, 2, 3, null, null],
          spanGaps: true,
          tension: 0.4
        }]
      },
      options: {
        scales: {
          x: {
            type: 'linear',
            min: 11,
            max: 40,
          }
        }
      }
    });

    chart.update();
    var controller = chart.getDatasetMeta(0).controller;

    expect(controller._drawStart).toBe(1);
    expect(controller._drawCount).toBe(4);
  }, 500);

  it('should correctly calc _drawStart and _drawCount when spanGaps=false', async() => {
    var chart = window.acquireChart({
      type: 'line',
      data: {
        labels: [0, 10, 20, 30, 40, 50],
        datasets: [{
          data: [3, null, 2, 3, null, 1.5],
          spanGaps: false,
          tension: 0.4
        }]
      },
      options: {
        scales: {
          x: {
            type: 'linear',
            min: 11,
            max: 40,
          }
        }
      }
    });

    chart.update();
    var controller = chart.getDatasetMeta(0).controller;

    expect(controller._drawStart).toBe(1);
    expect(controller._drawCount).toBe(4);
  }, 500);

  it('should not override tooltip title and label callbacks', async() => {
    const chart = window.acquireChart({
      type: 'line',
      data: {
        labels: ['Label 1', 'Label 2'],
        datasets: [{
          data: [21, 79],
          label: 'Dataset 1'
        }, {
          data: [33, 67],
          label: 'Dataset 2'
        }]
      },
      options: {
        responsive: true,
        maintainAspectRatio: true,
      }
    });
    const {tooltip} = chart;
    const point = chart.getDatasetMeta(0).data[0];

    await jasmine.triggerMouseEvent(chart, 'mousemove', point);

    expect(tooltip.title).toEqual(['Label 1']);
    expect(tooltip.body).toEqual([{
      before: [],
      lines: ['Dataset 1: 21'],
      after: []
    }]);

    chart.options.plugins.tooltip = {mode: 'dataset'};
    chart.update();
    await jasmine.triggerMouseEvent(chart, 'mousemove', point);

    expect(tooltip.title).toEqual(['Dataset 1']);
    expect(tooltip.body).toEqual([{
      before: [],
      lines: ['Label 1: 21'],
      after: []
    }, {
      before: [],
      lines: ['Label 2: 79'],
      after: []
    }]);
  });
});
