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;
}