var _ = require('underscore'); var $ = require('jquery'); var cdb = require('internal-carto.js'); var d3 = require('d3'); var WidgetHistogramChart = require('../../../../../javascripts/deep-insights/widgets/histogram/chart'); var viewportUtils = require('../../../../../javascripts/deep-insights/viewport-utils'); var formatter = require('../../../../../javascripts/deep-insights/formatter'); function flushAllD3Transitions () { var now = Date.now; Date.now = function () { return Infinity; }; d3.timer.flush(); Date.now = now; } describe('widgets/histogram/chart', function () { var forceResizeReal; var forceResizeSpy; var generateHandlesSpy; var setupBrushSpy; var createFormatterSpy; afterEach(function () { $('.js-chart').remove(); }); beforeEach(function () { d3.select('body').append('svg').attr('class', 'js-chart'); this.width = 300; this.height = 72; d3.select($('.js-chart')[0]) .attr('width', this.width) .attr('height', this.height) .append('g') .attr('class', 'Canvas'); this.data = genHistogramData(20); this.margin = { top: 4, right: 4, bottom: 4, left: 4 }; this.originalData = genHistogramData(20); this.originalModel = new cdb.core.Model({ data: this.originalData, start: this.originalData[0].start, end: this.originalData[this.originalData.length - 1].end, bins: 20 }); this.originalModel.getData = function () { return this.originalData; }.bind(this); // override default behavior of debounce, to be able to control callback forceResizeSpy = jasmine.createSpy('forceResize'); spyOn(_, 'debounce').and.callFake(function (cb) { forceResizeReal = cb; return forceResizeSpy; }); this.widgetModel = new cdb.core.Model({ style: { auto_style: { definition: { point: { color: { attribute: 'whatever', range: ['#FABADA', '#F00000', '#000000'] } } } } } }); this.widgetModel.getWidgetColor = function () { return ''; }; this.widgetModel.getAutoStyle = function () { return this.attributes.style.auto_style; }; this.widgetModel.isAutoStyleEnabled = function () { return true; }; this.widgetModel.isAutoStyle = function () { return false; }; spyOn(WidgetHistogramChart.prototype, '_refreshBarsColor').and.callThrough(); spyOn(WidgetHistogramChart.prototype, '_setupFillColor').and.callThrough(); this.dataviewModel = new cdb.core.Model({ aggregation: 'minute', offset: 0 }); this.dataviewModel.getCurrentOffset = function () { return 0; }; this.layerModel = new cdb.core.Model(); this.view = new WidgetHistogramChart(({ el: $('.js-chart'), type: 'histogram', margin: this.margin, chartBarColor: '#9DE0AD', hasHandles: true, height: this.height, data: this.data, dataviewModel: this.dataviewModel, layerModel: this.layerModel, originalData: this.originalModel, displayShadowBars: true, widgetModel: this.widgetModel, local_timezone: false, xAxisTickFormat: function (d, i) { return d; } })); var parentSpy = jasmine.createSpyObj('view.$el.parent()', ['width']); parentSpy.width.and.returnValue(this.width); spyOn(this.view.$el, 'parent'); this.view.$el.parent.and.returnValue(parentSpy); generateHandlesSpy = spyOn(this.view, '_generateHandles'); setupBrushSpy = spyOn(this.view, '_setupBrush'); createFormatterSpy = spyOn(this.view, '_createFormatter'); spyOn(this.view, 'refresh').and.callThrough(); this.view.render(); }); it('should be hidden initially', function () { expect(this.view.$el.attr('style')).toMatch('none'); }); it('should setup the fill color initially', function () { expect(WidgetHistogramChart.prototype._setupFillColor).toHaveBeenCalled(); expect(this.view._autoStyleColorsScale).toBeUndefined(); }); describe('normalize', function () { it('should normalize', function () { spyOn(this.view, 'updateYScale'); this.view.setNormalized(true); expect(this.view.model.get('normalized')).toEqual(true); expect(this.view.updateYScale).toHaveBeenCalled(); expect(this.view.refresh).toHaveBeenCalled(); }); it('should denormalize', function () { spyOn(this.view, 'updateYScale'); this.view.setNormalized(false); expect(this.view.model.get('normalized')).toEqual(false); expect(this.view.updateYScale).toHaveBeenCalled(); expect(this.view.refresh).toHaveBeenCalled(); }); }); describe('shadow bars', function () { it('should not show shadow bars', function () { this.view.options.displayShadowBars = false; this.view.model.set('show_shadow_bars', false); expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0); this.originalModel.trigger('change:data'); expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0); this.view.showShadowBars(); expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0); }); it('should remove and generate shadow bars when original data chagnes', function () { spyOn(this.view, '_removeShadowBars'); spyOn(this.view, '_generateShadowBars'); this.originalModel.trigger('change:data'); expect(this.view._removeShadowBars).toHaveBeenCalled(); expect(this.view._generateShadowBars).toHaveBeenCalled(); }); }); describe('when view is resized but set to not be shown just yet', function () { beforeEach(function () { expect(this.view.options.showOnWidthChange).toBe(true); // assert default value, in case it's changed this.view.options.showOnWidthChange = false; }); it('should not show view', function () { expect(this.view.$el.attr('style')).toMatch('none'); }); }); describe('when view is resized', function () { beforeEach(function () { forceResizeReal.call(this); expect(this.view.$el.parent).toHaveBeenCalled(); }); it('should render the view', function () { expect(this.view.$el.attr('style')).not.toMatch('none'); }); it('should calculate the width of the bars', function () { expect(this.view.barWidth).toBe((this.width - this.margin.left - this.margin.right) / this.data.length); }); it('should draw the bars', function () { expect(this.view.$('.CDB-Chart-bar').size()).toBe(this.data.length); }); it('should draw the axis', function () { expect(this.view.$el.find('.CDB-Chart-axis').size()).toBe(1); }); it('should draw the handles', function () { generateHandlesSpy.and.callThrough(); this.view._generateHandles(); expect(this.view.$el.find('.CDB-Chart-handle').size()).toBe(2); }); it('should hide the chart', function () { expect(this.view.$el.css('display')).toBe('inline'); this.view.hide(); expect(this.view.isHidden()).toBe(true); expect(this.view.model.get('display')).toBe(false); expect(this.view.$el.css('display')).toBe('none'); }); it('should show the chart', function () { this.view.hide(); this.view.show(); expect(this.view.$el.css('display')).toBe('inline'); }); it('should set the parent width', function () { this.view.show(); this.view._resizeToParentElement(); expect(this.view.model.get('width')).toBe(this.width); }); it('should generate the shadow bars', function () { expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(1); }); it('should hide the shadow bars', function () { this.view.removeShadowBars(); expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(0); }); it('should maintain the visibility after calling _resizeToParentElement', function () { this.view.show(); this.view._resizeToParentElement(); expect(this.view.$el.css('display')).toBe('inline'); expect(this.view.model.get('display')).toBe(true); this.view.hide(); this.view._resizeToParentElement(); expect(this.view.$el.css('display')).toBe('none'); expect(this.view.model.get('display')).toBe(false); }); it('should calculate the scales', function () { expect(this.view.xScale(0)).toBe(0); expect(this.view.xScale(100)).toMatch(/\d+/); expect(this.view.xScale(100)).toEqual(this.view.chartWidth()); expect(this.view.yScale(0)).toMatch(/\d+/); expect(this.view.yScale(0)).toEqual(this.view.chartHeight()); }); it('should refresh the data', function () { this.view.show(); this.view.model.set({ data: updateHistogramData(20) }); expect(this.view.refresh).toHaveBeenCalled(); }); describe('animated', function () { it('should set the parent width', function () { var animatedWidth = 24; spyOn(this.view.$el, 'siblings').and.returnValue($('
')); spyOn($.prototype, 'width').and.returnValue(animatedWidth); this.view.model.set('animated', true); this.view.show(); this.view._resizeToParentElement(); expect(this.view.model.get('width')).toBe(this.width - animatedWidth); }); }); describe('should allow to manage the y scale', function () { beforeEach(function () { this.originalScale = this.view.yScale; this.view.model.set('data', genHistogramData(10)); }); it('should calculate the y scale on request', function () { expect(this.view.yScale).toEqual(this.originalScale); this.view.updateYScale(); expect(this.view.yScale).not.toEqual(this.originalScale); }); it('should restore the y scale on request', function () { this.view.resetYScale(); expect(this.view.yScale).toEqual(this.originalScale); }); }); }); describe('_calculateDataDomain', function () { beforeEach(function () { this.applyNewData = function (d) { this.view.model.attributes.data = d; }; }); describe('without filter', function () { beforeEach(function () { spyOn(this.view, '_hasFilterApplied').and.returnValue(false); }); describe('all bins with freq data', function () { it('should return the min and max value from the first and last bins', function () { this.applyNewData([ { min: 0, max: 2, freq: 1 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 2 } ]); expect(this.view._calculateDataDomain()).toEqual([0, 6]); }); it('should return null when there is no data', function () { this.applyNewData([]); expect(this.view._calculateDataDomain()).toEqual([null, null]); }); }); describe('not all bins with freq data', function () { it('should return start and end values from bins with frequency (1)', function () { this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 2 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 6]); }); it('should return start and end values from bins with frequency (2)', function () { this.applyNewData([ { min: 1, max: 2, freq: 2 }, { min: 2.1, max: 4, freq: 3 }, { min: 6.1, max: 8, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([1, 4]); }); it('should return start and end values from bins with frequency (3)', function () { this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 2 }, { min: 6.1, max: 8, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 6]); }); it('should return start and end values from bins with frequency (4)', function () { this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 0 }, { min: 6.1, max: 8, freq: 10 }, { min: 8.1, max: 12, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 8]); }); it('should return null when there is no data', function () { this.applyNewData([]); expect(this.view._calculateDataDomain()).toEqual([null, null]); }); }); }); describe('with filter', function () { beforeEach(function () { spyOn(this.view, '_hasFilterApplied').and.returnValue(true); }); describe('all bins with freq data', function () { it('should return the min and max value from the first and last bins', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(1); spyOn(this.view, '_getHiBarIndex').and.returnValue(2); this.applyNewData([ { min: 0, max: 2, freq: 1 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 2 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 4]); }); }); describe('not all bins with freq data', function () { it('should return start and end values from bins with frequency (1)', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(1); spyOn(this.view, '_getHiBarIndex').and.returnValue(2); this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 4]); }); it('should return start and end values from bins with frequency (2)', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(1); spyOn(this.view, '_getHiBarIndex').and.returnValue(3); this.applyNewData([ { min: 1, max: 2, freq: 2 }, { min: 2.1, max: 4, freq: 3 }, { min: 6.1, max: 8, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 4]); }); it('should return start and end values from bins with frequency (3)', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(0); spyOn(this.view, '_getHiBarIndex').and.returnValue(4); this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 2 }, { min: 6.1, max: 8, freq: 0 }, { min: 8.1, max: 10, freq: 2 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 6]); }); it('should return start and end values from bins with frequency (4)', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(1); spyOn(this.view, '_getHiBarIndex').and.returnValue(4); this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 3 }, { min: 4.1, max: 6, freq: 0 }, { min: 6.1, max: 8, freq: 10 }, { min: 8.1, max: 12, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 8]); }); it('should return start and end values from bins with frequency (5)', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(1); spyOn(this.view, '_getHiBarIndex').and.returnValue(5); this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 0 }, { min: 4.1, max: 6, freq: 0 }, { min: 6.1, max: 8, freq: 0 }, { min: 8.1, max: 12, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([2.1, 12]); }); it('should return 0, 0 when lo and hi bar indexes are NaN', function () { spyOn(this.view, '_getLoBarIndex').and.returnValue(NaN); spyOn(this.view, '_getHiBarIndex').and.returnValue(NaN); this.applyNewData([ { min: 0, max: 2, freq: 0 }, { min: 2.1, max: 4, freq: 0 }, { min: 4.1, max: 6, freq: 0 }, { min: 6.1, max: 8, freq: 0 }, { min: 8.1, max: 12, freq: 0 } ]); expect(this.view._calculateDataDomain()).toEqual([0, 0]); }); }); }); }); describe('_generateFillGradients', function () { beforeEach(function () { this.redColor = 'rgb(255, 0, 0)'; this.yellowColor = 'rgb(255, 255, 0)'; this.getLinearGradients = function () { var defs = d3.select(this.view.el).select('defs'); return defs.selectAll('linearGradient'); }; var definitionObj = { definition: { point: { color: { range: [this.redColor, 'blue', 'brown', this.yellowColor] } } } }; spyOn(this.view._widgetModel, 'getAutoStyle').and.returnValue(definitionObj); var data = [ { min: 0, max: 2, freq: 5 }, { min: 2.1, max: 4, freq: 1 }, { min: 4.1, max: 6, freq: 5 }, { min: 6.1, max: 8, freq: 8 }, { min: 8.1, max: 12, freq: 10 } ]; this.view.model.attributes.data = data; this.view._removeFillGradients(); this.view.el = document.createElement('svg'); // Hack it! }); it('should not generate any gradient if auto-style colors are not provided', function () { this.view._widgetModel.getAutoStyle.and.returnValue({}); this.view._generateFillGradients(); expect(this.getLinearGradients().length).toBe(0); }); it('should generate as many gradients as data we have', function () { this.view._generateFillGradients(); var defs = d3.select(this.view.el).select('defs'); var gradients = defs.selectAll('.gradient')[0]; expect(gradients.length).toBe(5); }); it('should generate as many hover gradients as data we have', function () { this.view._generateFillGradients(); var defs = d3.select(this.view.el).select('defs'); var gradients = defs.selectAll('.gradient-hover')[0]; expect(gradients.length).toBe(5); }); it('should generate 5 stops in each gradient', function () { this.view._generateFillGradients(); this.getLinearGradients().each(function (d) { var stops = d3.select(this).selectAll('stop'); expect(stops[0].length).toBe(5); }); }); describe('gradient generation', function () { describe('no colors ramp', function () { it('should not create the proper gradient ramp if bucket is out of domain', function () { spyOn(this.view, '_calculateDataDomain').and.returnValue([2.1, 8]); this.view._generateFillGradients(); var self = this; // Out of domain at the beginning d3.select(this.getLinearGradients()[0][0]).selectAll('stop').each(function (d) { expect(this.getAttribute('stop-color')).toBe(self.redColor); }); // Out of domain at the end d3.select(this.getLinearGradients()[0][4]).selectAll('stop').each(function (d) { expect(this.getAttribute('stop-color')).toBe(self.yellowColor); }); }); it('should not create the proper gradient ramp if bucket is empty at the beginning', function () { var self = this; this.view.model.attributes.data[0].freq = 0; this.view._generateFillGradients(); // No data in the first bucket d3.select(this.getLinearGradients()[0][0]).selectAll('stop').each(function (d) { expect(this.getAttribute('stop-color')).toBe(self.redColor); }); }); it('should not create the proper gradient ramp if bucket is empty at the end', function () { this.view.model.attributes.data[4].freq = 0; this.view._generateFillGradients(); var self = this; // No data in the last bucket d3.select(this.getLinearGradients()[0][4]).selectAll('stop').each(function (d) { expect(this.getAttribute('stop-color')).toBe(self.yellowColor); }); }); }); describe('colors ramp', function () { it('should generate the proper ramp for each gradient', function () { this.view._generateFillGradients(); var linearGradients = this.getLinearGradients(); var firstGradient = linearGradients[0][0]; var lastGradient = linearGradients[0][4]; var middleGradient = linearGradients[0][2]; var stopsInFirstGradient = d3.select(firstGradient).selectAll('stop'); var stopsInMiddleGradient = d3.select(middleGradient).selectAll('stop'); var stopsInLastGradient = d3.select(lastGradient).selectAll('stop'); expect(stopsInFirstGradient[0][0].getAttribute('stop-color')).toBe(this.redColor); expect(stopsInFirstGradient[0][1].getAttribute('stop-color')).not.toBe(this.redColor); expect(stopsInLastGradient[0][4].getAttribute('stop-color')).toBe(this.yellowColor); expect(stopsInLastGradient[0][3].getAttribute('stop-color')).not.toBe(this.yellowColor); // Checking middle bin gradient expect(stopsInMiddleGradient[0][0].getAttribute('stop-color')).toBe('rgb(69, 8, 177)'); // From blue expect(stopsInMiddleGradient[0][1].getAttribute('stop-color')).toBe('rgb(68, 11, 175)'); expect(stopsInMiddleGradient[0][2].getAttribute('stop-color')).toBe('rgb(71, 14, 168)'); expect(stopsInMiddleGradient[0][3].getAttribute('stop-color')).toBe('rgb(79, 19, 157)'); expect(stopsInMiddleGradient[0][4].getAttribute('stop-color')).toBe('rgb(90, 25, 142)'); // To purple }); it('should create the proper gradient ramp althogh all buckets are empty', function () { var data = this.view.model.attributes.data; _.each(data, function (d, i) { d.freq = 0; }); this.view._generateFillGradients(); var linearGradients = this.getLinearGradients(); var firstGradient = linearGradients[0][0]; var lastGradient = linearGradients[0][4]; var stopsInFirstGradient = d3.select(firstGradient).selectAll('stop'); var stopsInLastGradient = d3.select(lastGradient).selectAll('stop'); // No data in the last bucket expect(stopsInFirstGradient[0][0].getAttribute('stop-color')).toBe(this.redColor); expect(stopsInFirstGradient[0][1].getAttribute('stop-color')).not.toBe(this.redColor); expect(stopsInLastGradient[0][4].getAttribute('stop-color')).toBe(this.yellowColor); expect(stopsInLastGradient[0][3].getAttribute('stop-color')).not.toBe(this.yellowColor); }); }); }); }); describe('on dataview layer cartocss change', function () { it('should generate bar gradients if they were not defined before', function () { this.view._setupFillColor.calls.reset(); spyOn(this.view, '_areGradientsAlreadyGenerated').and.returnValue(false); this.layerModel.set('cartocss', '#dummy {}'); expect(this.view._setupFillColor.calls.count()).toBe(1); }); it('should not generate bar gradients if they were defined before', function () { this.view._setupFillColor.calls.reset(); spyOn(this.view, '_areGradientsAlreadyGenerated').and.returnValue(true); this.layerModel.set('cartocss', '#dummy {}'); expect(this.view._setupFillColor.calls.count()).toBe(0); }); }); describe('._getDataForScales', function () { it('should calculate (x|y)Scale depending originalData if it is provided and view is not bounded', function () { var data = this.view._getDataForScales(); expect(data).toBe(this.originalData); expect(data).not.toBe(this.data); }); it('should calculate (x|y)Scale depending current data if view is bounded', function () { this.view.model.set('bounded', true); var data = this.view._getDataForScales(); expect(data).not.toBe(this.originalData); expect(data).toBe(this.data); }); it('should calculate (x|y)Scale depending current data if originalData is not defined', function () { delete this.view._originalData; var data = this.view._getDataForScales(); expect(data).not.toBe(this.originalData); expect(data).toBe(this.data); }); }); describe('autostyle changes', function () { beforeEach(function () { this.widgetModel.set('autoStyle', true); }); it('should trigger a refresh of the bars fill color', function () { expect(WidgetHistogramChart.prototype._refreshBarsColor).toHaveBeenCalled(); }); }); describe('style changes', function () { beforeEach(function () { WidgetHistogramChart.prototype._setupFillColor.calls.reset(); WidgetHistogramChart.prototype._refreshBarsColor.calls.reset(); this.widgetModel.set('style', {}); }); it('should trigger a refresh of the bars fill color', function () { expect(WidgetHistogramChart.prototype._refreshBarsColor.calls.count()).toEqual(1); }); it('should setup the colors scale', function () { expect(WidgetHistogramChart.prototype._setupFillColor.calls.count()).toEqual(1); }); }); describe('color bar', function () { it('should be green by default', function () { expect(this.view.$('.CDB-Chart-bar').attr('fill')).toEqual('#9DE0AD'); }); it('should be custom if widget color is different', function () { spyOn(this.widgetModel, 'getWidgetColor').and.returnValue('red'); this.view._refreshBarsColor(); expect(this.view.$('.CDB-Chart-bar').attr('fill')).toEqual('red'); }); it('should be colored by linear gradients when autostyle is applied', function () { spyOn(this.widgetModel, 'isAutoStyle').and.returnValue(true); this.widgetModel.set({ style: { auto_style: { definition: { point: { color: { attribute: 'whatever', range: ['red', 'blue', 'green'] } } } } } }); var dataSize = this.view.model.get('data').length; _.times(dataSize, function (i) { expect(this.view.$('.CDB-Chart-bar:eq(' + i + ')').attr('fill')).toContain('url(#bar-'); }, this); }); }); describe('touch interfaces fixes', function () { beforeEach(function () { setupBrushSpy.and.callThrough(); this.view._setupBrush(); }); afterEach(function () { $('.Brush').remove(); }); it('should mark all brush elements with ps-prevent-touchmove', function () { var brush = this.view.$('.Brush'); var hasProperClass = function (e) { return e.attributes.class.value.indexOf('ps-prevent-touchmove') !== 1; }; var checkAllChildren = function (child) { expect(hasProperClass(child)).toBe(true); if (child.children && child.children.length > 0) { _.forEach(child.children, checkAllChildren); } }; _.forEach(brush.children(), checkAllChildren); }); }); describe('._isTabletViewport', function () { it('should return true if viewport is tablet', function () { spyOn($.prototype, 'width').and.returnValue(100); spyOn(viewportUtils, 'isTabletViewport'); this.view._isTabletViewport(); expect(viewportUtils.isTabletViewport).toHaveBeenCalled(); }); }); describe('._isMobileViewport', function () { it('should return true if viewport is mobile', function () { spyOn($.prototype, 'width').and.returnValue(100); spyOn(viewportUtils, 'isMobileViewport'); this.view._isMobileViewport(); expect(viewportUtils.isMobileViewport).toHaveBeenCalled(); }); }); describe('._isTimeSeries', function () { it('should return true if option type is time', function () { expect(this.view._isTimeSeries()).toBe(false); this.view.options.type = 'time-date'; expect(this.view._isTimeSeries()).toBe(true); }); }); describe('._generateChartContent', function () { it('should generate axis, lines, bars, bottom line, handles, and setup brush', function () { spyOn(this.view, '_generateAxis'); spyOn(this.view, '_generateLines'); spyOn(this.view, '_generateBars'); spyOn(this.view, '_generateBottomLine'); this.view._generateChartContent(); expect(this.view._generateAxis).toHaveBeenCalled(); expect(this.view._generateLines).toHaveBeenCalled(); expect(this.view._generateBars).toHaveBeenCalled(); expect(this.view._generateBottomLine).toHaveBeenCalled(); expect(this.view._generateHandles).toHaveBeenCalled(); expect(this.view._setupBrush).toHaveBeenCalled(); }); describe('time-series', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; }); describe('mobile', function () { beforeEach(function () { this.view._isMobileViewport = function () { return true; }; }); it('should not generate lines, bottom line', function () { spyOn(this.view, '_generateBottomLine'); this.view._generateChartContent(); expect(this.view._generateBottomLine).not.toHaveBeenCalled(); }); }); describe('tablet', function () { beforeEach(function () { this.view._isTabletViewport = function () { return true; }; }); it('should not generate lines', function () { spyOn(this.view, '_generateLines'); this.view._generateChartContent(); expect(this.view._generateLines).not.toHaveBeenCalled(); }); }); }); }); describe('._generateHandle', function () { beforeEach(function () { generateHandlesSpy.and.callThrough(); var generateHandleSpy = spyOn(this.view, '_generateHandle'); this.view._generateHandles(); generateHandleSpy.and.callThrough(); }); afterEach(function () { $('.CDB-Chart-handles').remove(); }); it('should generate handle', function () { this.view._generateHandle('left'); expect(this.view.$('.CDB-Chart-handle-left .CDB-Chart-handleRect').attr('height')).toBe('48'); }); describe('time-series', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; }); describe('tablet', function () { beforeEach(function () { this.view._isTabletViewport = function () { return true; }; this.view.model.set({ height: 16, margin: _.extend({}, this.view.model.get('margin'), { top: 0 }), showLabels: false }, { silent: true }); }); it('should generate handle', function () { this.view._generateHandle('left'); expect(this.view.$('.CDB-Chart-handle-left .CDB-Chart-handleRect').attr('height')).toBe('24'); }); }); }); }); describe('._generateAxisTip', function () { beforeEach(function () { generateHandlesSpy.and.callThrough(); this.view._generateHandles(); }); afterEach(function () { $('.CDB-Chart-handles').remove(); }); describe('left', function () { it('should generate left axis tip', function () { this.view._generateAxisTip('left'); var textLabel = this.view.$('.CDB-Chart-axisTipText.CDB-Chart-axisTip-left'); var rectLabel = this.view.$('.CDB-Chart-axisTipRect.CDB-Chart-axisTip-left'); var axisTip = this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-left'); var triangle = this.view.$('.CDB-Chart-handle-left .CDB-Chart-axisTipTriangle'); expect(textLabel.attr('opacity')).toBe('1'); expect(rectLabel.attr('opacity')).toBe('1'); expect(triangle.attr('style')).toContain('opacity: 1;'); expect(axisTip.length).toBe(1); expect(axisTip.attr('transform')).toBe('translate(0,-26)'); expect(triangle.attr('transform')).toBe('translate(-3, -11)'); }); }); describe('right', function () { it('should generate right axis tip', function () { this.view._generateAxisTip('right'); var textLabel = this.view.$('.CDB-Chart-axisTipText.CDB-Chart-axisTip-right'); var rectLabel = this.view.$('.CDB-Chart-axisTipRect.CDB-Chart-axisTip-right'); var axisTip = this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-right'); var triangle = this.view.$('.CDB-Chart-handle-right .CDB-Chart-axisTipTriangle'); expect(textLabel.attr('opacity')).toBe('1'); expect(rectLabel.attr('opacity')).toBe('1'); expect(triangle.attr('style')).toContain('opacity: 1;'); expect(axisTip.length).toBe(1); expect(axisTip.attr('transform')).toBe('translate(0,57)'); expect(triangle.attr('transform')).toBe('translate(-3, 59.1)'); }); }); describe('time-series', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; }); describe('mobile', function () { beforeEach(function () { this.view._isMobileViewport = function () { return true; }; }); describe('right', function () { it('should generate right axis tip', function () { this.view._generateAxisTip('right'); expect(this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-right').attr('transform')).toBe('translate(0,-26)'); expect(this.view.$('.CDB-Chart-handle-right .CDB-Chart-axisTipTriangle').attr('transform')).toBe('translate(-3, -11)'); }); }); }); }); }); describe('._updateTriangle', function () { beforeEach(function () { generateHandlesSpy.and.callThrough(); this.view.options.hasAxisTip = true; this.view._generateHandles(); }); it('should update triangle', function () { var triangle = this.view.$('.CDB-Chart-handle-left .CDB-Chart-axisTipTriangle'); this.view._updateTriangle(false, triangle, 42, 60, 100); expect(triangle.attr('transform')).toBe('translate(53,-11)rotate(0)skewX(0)scale(1,1)'); }); describe('time-series, tablet', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; this.view._isTabletViewport = function () { return true; }; this.view.model.set('right_axis_tip', 42); }); it('should update right axis tip', function () { var triangle = this.view.$('.CDB-Chart-handle-right .CDB-Chart-axisTipTriangle'); this.view._updateTriangle(true, triangle, 42, 60, 100); expect(triangle.attr('transform')).toBe('translate(53,59.099998474121094)rotate(0)skewX(0)scale(1,1)'); }); }); }); describe('._updateAxisTip', function () { var time = 1501751014; beforeEach(function () { generateHandlesSpy.and.callThrough(); this.view.options.hasAxisTip = true; this.view._generateHandles(); }); afterEach(function () { $('.CDB-Chart-handles').remove(); }); it('should update axis tip', function () { this.view.model.set('left_axis_tip', 42); this.view._updateAxisTip('left'); expect(this.view.$('.CDB-Chart-axisTipText.CDB-Chart-axisTip-left').text()).toBe('42'); }); describe('left', function () { it('should update left axis tip', function () { this.view.model.set('left_axis_tip', 42); this.view._updateAxisTip('left'); var axisTip = this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-left'); expect(axisTip.attr('transform')).toBe('translate(-2, -26)'); }); }); describe('right', function () { it('should update right axis tip', function () { this.view.model.set('right_axis_tip', 42); this.view._updateAxisTip('right'); var axisTip = this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-right'); expect(axisTip.attr('transform')).toBe('translate(-2, 56)'); }); }); describe('datetime', function () { beforeEach(function () { createFormatterSpy.and.callThrough(); this.view._isDateTimeSeries = function () { return true; }; this.view._createFormatter(); this.view.model.set('left_axis_tip', time); }); it('should update axis tip', function () { this.view._updateAxisTip('left'); expect(this.view.$('.CDB-Chart-axisTipText.CDB-Chart-axisTip-left').text()).toBe('09:03 - Aug 3rd, 2017'); }); }); describe('time-series, mobile', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; this.view._isMobileViewport = function () { return true; }; this.view.model.set('right_axis_tip', 42); }); it('should update right axis tip', function () { this.view._updateAxisTip('right'); var axisTip = this.view.$('.CDB-Chart-axisTip.CDB-Chart-axisTip-right'); expect(axisTip.attr('transform')).toBe('translate(-2, -26)'); }); }); }); describe('._onChangeDragging', function () { it('should update model', function () { this.view.model.set('dragging', true); expect(this.view.chart.classed('is-dragging')).toBe(true); this.view.model.set('dragging', false); expect(this.view.chart.classed('is-dragging')).toBe(false); }); describe('if is mobile, time-series, and is not dragging', function () { beforeEach(function () { this.view._isMobileViewport = function () { return true; }; this.view._isTimeSeries = function () { return true; }; this.view.model.set('dragging', true); }); it('should hide axis tips', function () { spyOn(this.view, '_showAxisTip'); this.view.model.set('dragging', false); expect(this.view._showAxisTip).toHaveBeenCalledWith('right', false); expect(this.view._showAxisTip).toHaveBeenCalledWith('left', false); }); }); }); describe('._toggleAxisTip', function () { beforeEach(function () { jasmine.clock().install(); generateHandlesSpy.and.callThrough(); this.view.options.hasAxisTip = true; this.view._generateHandles(); }); afterEach(function () { jasmine.clock().uninstall(); $('.CDB-Chart-handles').remove(); }); it('should toggle axis tip', function () { var textLabel = this.view.$('.CDB-Chart-axisTipText.CDB-Chart-axisTip-left'); var rectLabel = this.view.$('.CDB-Chart-axisTipRect.CDB-Chart-axisTip-left'); var triangle = this.view.$('.CDB-Chart-handle-left .CDB-Chart-axisTipTriangle'); this.view._toggleAxisTip('left', 1); flushAllD3Transitions(); expect(textLabel.attr('opacity')).toBe('1'); expect(rectLabel.attr('opacity')).toBe('1'); expect(triangle.attr('style')).toContain('opacity: 1;'); this.view._toggleAxisTip('left', 0); flushAllD3Transitions(); expect(textLabel.attr('opacity')).toBe('0'); expect(rectLabel.attr('opacity')).toBe('0'); expect(triangle.attr('style')).toContain('opacity: 0;'); }); }); describe('._showAxisTip', function () { it('should show axis tip', function () { var tip = 'left'; spyOn(this.view, '_toggleAxisTip'); this.view._showAxisTip(tip, true); expect(this.view._toggleAxisTip).toHaveBeenCalledWith(tip, 1); }); it('should hide axis tip', function () { var tip = 'left'; spyOn(this.view, '_toggleAxisTip'); this.view._showAxisTip(tip, false); expect(this.view._toggleAxisTip).toHaveBeenCalledWith(tip, 0); }); }); describe('._setupBrush', function () { beforeEach(function () { setupBrushSpy.and.callThrough(); this.view._setupBrush(); }); afterEach(function () { $('.Brush').remove(); }); it('should setup brush', function () { expect(this.view.$('.Brush rect').attr('height')).toBe('48'); }); describe('time-series', function () { beforeEach(function () { this.view._isTimeSeries = function () { return true; }; }); it('should generate handle', function () { expect(this.view.$('.Brush rect').attr('height')).toBe('48'); }); }); }); describe('._generateBars', function () { beforeEach(function () { generateHandlesSpy.and.callThrough(); this.view._generateHandles(); }); afterEach(function () { $('.CDB-Chart-handles').remove(); }); it('should update chart', function () { this.view._updateChart(); flushAllD3Transitions(); expect(this.view.$('.CDB-Chart-bar').first().attr('height')).toBe('0'); expect(this.view.$('.CDB-Chart-bar').last().attr('height')).toBe('48'); expect(this.view.$('.CDB-Chart-bar').first().attr('y')).toBe('48'); expect(this.view.$('.CDB-Chart-bar').last().attr('y')).toBe('0'); }); describe('mobile, time-series', function () { beforeEach(function () { this.view._isMobileViewport = function () { return true; }; this.view._isTimeSeries = function () { return true; }; this.view.model.set({ height: 16, margin: _.extend({}, this.view.model.get('margin'), { top: 0 }), showLabels: false }, { silent: true }); }); it('should update chart', function () { this.view._updateChart(); flushAllD3Transitions(); expect(this.view.$('.CDB-Chart-bar').first().attr('height')).toBe('3'); expect(this.view.$('.CDB-Chart-bar').last().attr('height')).toBe('3'); expect(this.view.$('.CDB-Chart-bar').first().attr('y')).toBe('9'); expect(this.view.$('.CDB-Chart-bar').last().attr('y')).toBe('9'); }); }); }); describe('._updateChart', function () { beforeEach(function () { generateHandlesSpy.and.callThrough(); this.view._generateHandles(); this.view.model.set({ data: updateHistogramData(20) }); flushAllD3Transitions(); }); afterEach(function () { $('.CDB-Chart-handles').remove(); }); it('should update chart', function () { expect(this.view.$('.CDB-Chart-bar').first().attr('height')).toBe('48'); expect(this.view.$('.CDB-Chart-bar').last().attr('height')).toBe('0'); expect(this.view.$('.CDB-Chart-bar').first().attr('y')).toBe('0'); expect(this.view.$('.CDB-Chart-bar').last().attr('y')).toBe('48'); }); describe('mobile, time-series', function () { beforeEach(function () { this.view._isMobileViewport = function () { return true; }; this.view._isTimeSeries = function () { return true; }; this.view.model.set({ height: 16, margin: _.extend({}, this.view.model.get('margin'), { top: 0 }), showLabels: false }, { silent: true }); }); it('should update chart', function () { this.view._updateChart(); flushAllD3Transitions(); expect(this.view.$('.CDB-Chart-bar').first().attr('height')).toBe('3'); expect(this.view.$('.CDB-Chart-bar').last().attr('height')).toBe('3'); expect(this.view.$('.CDB-Chart-bar').first().attr('y')).toBe('9'); expect(this.view.$('.CDB-Chart-bar').last().attr('y')).toBe('9'); }); }); }); describe('._createFormatter', function () { beforeEach(function () { createFormatterSpy.and.callThrough(); spyOn(formatter, 'timestampFactory'); spyOn(this.view, '_calculateDivisionWithByAggregation'); }); it('should setup formatter', function () { this.view._createFormatter(); expect(formatter.timestampFactory).not.toHaveBeenCalledWith(); expect(this.view._calculateDivisionWithByAggregation).not.toHaveBeenCalledWith(); expect(this.view.formatter).toBe(formatter.formatNumber); }); describe('datetime', function () { it('should setup formatter', function () { this.view._isDateTimeSeries = function () { return true; }; this.view._createFormatter(); expect(formatter.timestampFactory).toHaveBeenCalledWith('minute', 0); expect(this.view._calculateDivisionWithByAggregation).toHaveBeenCalled(); expect(this.view.formatter).not.toBe(formatter.formatNumber); }); }); }); }); function genHistogramData (n) { n = n || 1; var arr = []; _.times(n, function (i) { var start = i; var end = i + 1; var obj = { bin: i, freq: i, start: start, end: end, max: end, min: start }; arr.push(obj); }); return arr; } function updateHistogramData (n) { n = n || 1; var arr = []; _.times(n, function (i) { var start = i; var end = i + 1; var obj = { bin: i, freq: n - end, start: start, end: end, max: end, min: start }; arr.push(obj); }); return arr; }