cartodb-4.42/lib/assets/test/spec/deep-insights/widgets/histogram/content-view.spec.js
2024-04-06 05:25:13 +00:00

485 lines
16 KiB
JavaScript

var _ = require('underscore');
var specHelper = require('../../spec-helper');
var HistogramContentView = require('../../../../../javascripts/deep-insights/widgets/histogram/content-view');
var HistogramWidgetModel = require('../../../../../javascripts/deep-insights/widgets/histogram/histogram-widget-model');
var HistogramChartView = require('../../../../../javascripts/deep-insights/widgets/histogram/chart');
describe('widgets/histogram/content-view', function () {
var nodeId = 'a0';
function getNumberOfListeners (listeners) {
return _.chain(listeners)
.pluck('count')
.reduce(function (memo, num) {
return memo + num;
}, 0)
.value();
}
function createView (options) {
options = options || {};
var vis = specHelper.createDefaultVis();
this.layerModel = vis.map.layers.first();
this.layerModel.set('layer_name', '< & ><h1>Hello</h1>');
var source = vis.analysis.findNodeById(nodeId);
this.dataviewModel = vis.dataviews.createHistogramModel({
id: 'widget_3',
column: 'col',
column_type: 'number',
source: source
});
this.originalData = this.dataviewModel.getUnfilteredDataModel();
this.originalData.set({
data: [{ bin: 10, max: 0 }, { bin: 3, max: 10 }],
start: 0,
end: 256,
bins: 2
});
if (options.originalDataFetched) {
this.originalData.set('hasBeenFetched', true, { silent: true });
}
this.widgetModel = new HistogramWidgetModel({
title: 'Howdy',
attrsNames: ['title']
}, {
dataviewModel: this.dataviewModel,
layerModel: this.layerModel
});
var oldFtech = this.dataviewModel.fetch;
var widgetModel = this.widgetModel;
this.dataviewModel.fetch = function (options) {
oldFtech.call(this, options);
widgetModel.attributes.hasInitialState = true;
options && options.complete && options.complete();
};
spyOn(this.dataviewModel, 'getData').and.returnValue(['0', '1']);
spyOn(this.dataviewModel, 'fetch').and.callThrough();
if (options.spyPrototype) {
spyOn(HistogramChartView.prototype, '_setupFillColor').and.returnValue('red');
spyOn(HistogramContentView.prototype, 'render').and.callThrough();
spyOn(HistogramContentView.prototype, '_onNormalizedChanged').and.callThrough();
}
this.view = new HistogramContentView({
model: this.widgetModel,
dataviewModel: this.dataviewModel
});
}
beforeEach(function () {
createView.call(this, { spyPrototype: true });
});
describe('when originalData', function () {
it('is not loaded should call _initBinds when hasBeenFetched changes the first time', function () {
spyOn(this.view, '_initBinds');
this.originalData.trigger('change:hasBeenFetched', true);
expect(this.view._initBinds).toHaveBeenCalled();
});
it('is loaded should call _initBinds immediately', function () {
createView.call(this, {
originalDataFetched: true,
spyPrototype: false
});
// If _initBinds has been called, then we're listening at least to 3 events.
expect(getNumberOfListeners(this.view._listeningTo)).toBeGreaterThan(2);
});
});
describe('update stats', function () {
it('should update the stats values', function () {
expect(this.widgetModel.get('min')).toBe(undefined);
expect(this.widgetModel.get('max')).toBe(undefined);
expect(this.widgetModel.get('avg')).toBe(undefined);
expect(this.widgetModel.get('total')).toBe(undefined);
this.dataviewModel.getData.and.returnValue(JSON.stringify(genHistogramData(20)));
this.dataviewModel.trigger('change:data');
expect(this.widgetModel.get('min')).not.toBe(0);
expect(this.widgetModel.get('max')).not.toBe(0);
expect(this.widgetModel.get('avg')).not.toBe(0);
expect(this.widgetModel.get('total')).not.toBe(0);
});
});
describe('when originalData is loaded', function () {
beforeEach(function () {
this.originalData.trigger('change:data', this.originalData);
this.originalData.trigger('change:hasBeenFetched', true);
this.view.model.set('hasInitialState', true);
});
describe('when collapsed is true', function () {
describe('render', function () {
it('should hide the content', function () {
pending('Fix the DOM children leak');
expect(this.view.$('.CDB-Widget-content').length).toBe(1);
this.widgetModel.set('collapsed', true);
expect(this.view.$('.CDB-Widget-content').length).toBe(0);
});
});
});
describe('when show_stats is true', function () {
describe('render', function () {
it('should show stats', function () {
expect(this.view.$('.CDB-Widget-info').length).toBe(0);
this.widgetModel.set('show_stats', true);
this.view.render();
expect(this.view.$('.CDB-Widget-info').length).toBe(1);
});
});
});
describe('when nulls is not defined in dataview', function () {
describe('.render', function () {
it('should hide nulls', function () {
spyOn(this.view._dataviewModel, 'hasNulls').and.returnValue(false);
this.widgetModel.set('show_stats', true);
this.view.render();
expect(this.view.$('.js-nulls').length).toBe(0);
});
});
});
describe('when show_source is true', function () {
var tableName = 'table_name';
var sourceType = 'sampling';
var layerName = 'Test Layer Name';
beforeEach(function () {
this.widgetModel.set({
show_source: true,
table_name: tableName
});
});
describe('when dataViewModel is sourceType', function () {
describe('.render', function () {
it('should render properly', function () {
this.view.render();
expect(this.view.$el.html()).toContain(nodeId);
expect(this.view.$el.html()).toContain('Source');
expect(this.view.$el.html()).toContain(tableName);
});
});
});
describe('when dataViewModel is not sourceType', function () {
beforeEach(function () {
spyOn(this.dataviewModel, 'getSourceType').and.returnValue(sourceType);
spyOn(this.dataviewModel, 'isSourceType').and.returnValue(false);
this.layerModel.set('layer_name', layerName, { silent: true });
});
describe('.render', function () {
it('should render properly', function () {
this.view.render();
expect(this.view.$('.CDB-IconFont-ray').length).toBe(1);
expect(this.view.$el.html()).toContain(nodeId);
expect(this.view.$el.html()).toContain('Subsample');
expect(this.view.$el.html()).toContain(layerName);
});
});
});
});
describe('.initBinds', function () {
beforeEach(function () {
createView.call(this, { spyPrototype: false });
this.originalData.trigger('change:hasBeenFetched', true);
});
it('should render the widget when the layer name changes', function () {
this.layerModel.set('layer_name', 'Hello');
expect(this.view.render).toHaveBeenCalled();
});
it('should render the widget normalized changes', function () {
this.view.render();
this.widgetModel.set('normalized', !this.widgetModel.get('normalized'));
expect(this.view._onNormalizedChanged).toHaveBeenCalled();
});
});
it('should revert the lockedByUser state when the model is changed', function () {
spyOn(this.view, '_unsetRange').and.callThrough();
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
this.widgetModel.set('zoomed', true);
this.dataviewModel.fetch();
expect(this.view._unsetRange).not.toHaveBeenCalled();
this.view.lockedByUser = true;
this.dataviewModel.set('url', 'test');
expect(this.view.lockedByUser).toBe(true);
});
it("shouldn't revert the lockedByUser state when the url is changed and the histogram is zoomed", function () {
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
this.view.lockedByUser = true;
this.dataviewModel.fetch();
this.dataviewModel.trigger('change:data');
expect(this.view.lockedByUser).toBe(false);
});
it('should unset the range when the data is changed', function () {
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
spyOn(this.view, '_unsetRange').and.callThrough();
this.view.unsettingRange = true;
this.dataviewModel.fetch();
this.dataviewModel._data.reset(genHistogramData(20));
this.dataviewModel.trigger('change:data');
expect(this.view._unsetRange).toHaveBeenCalled();
});
it("shouldn't unset the range when the url is changed and is zoomed", function () {
spyOn(this.view, '_unsetRange').and.callThrough();
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
this.widgetModel.set('zoomed', true);
this.dataviewModel.fetch();
expect(this.view._unsetRange).not.toHaveBeenCalled();
this.view.unsettingRange = true;
this.dataviewModel.set('url', 'test');
expect(this.view._unsetRange).not.toHaveBeenCalled();
});
it('should update the stats when the data of the model has changed', function () {
spyOn(this.view, '_updateStats').and.callThrough();
this.dataviewModel._data.reset(genHistogramData(20));
this.dataviewModel.trigger('change:data');
expect(this.view._updateStats).toHaveBeenCalled();
expect(this.dataviewModel.getData).toHaveBeenCalled();
});
it('should update the title when the model is updated', function () {
this.view.render();
expect(this.view.$el.html().indexOf('Howdy') !== -1).toBe(true);
this.widgetModel.update({title: 'Cloudy'});
this.view.render();
expect(this.view.$el.html().indexOf('Cloudy') !== -1).toBe(true);
});
it('should enable and disable filters on the dataviewModel when zooming in and out', function () {
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
this.dataviewModel.fetch();
spyOn(this.dataviewModel, 'enableFilter');
this.view.$('.js-zoom').click();
expect(this.dataviewModel.enableFilter).toHaveBeenCalled();
spyOn(this.dataviewModel, 'disableFilter');
this.view.$('.js-clear').click();
expect(this.dataviewModel.disableFilter).toHaveBeenCalled();
});
it('should set and unset bounds for the histogram view when chart is zoomed in and zoomed out', function () {
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': 1,
'nulls': 0,
'bins': []
});
};
this.dataviewModel.fetch();
spyOn(this.view.histogramChartView, 'setBounds');
spyOn(this.view.histogramChartView, 'unsetBounds');
this.view.$('.js-zoom').click();
expect(this.view.histogramChartView.setBounds).toHaveBeenCalled();
this.view.$('.js-clear').click();
expect(this.view.histogramChartView.unsetBounds).toHaveBeenCalled();
});
it('should replace histogram chart data with dataview model data when unsets range', function () {
this.view.render();
spyOn(this.view._originalData, 'toJSON');
spyOn(this.view.histogramChartView, 'replaceData');
this.view._unsetRange();
expect(this.view.histogramChartView.replaceData).toHaveBeenCalled();
expect(this.dataviewModel.getData).toHaveBeenCalled();
expect(this.view._originalData.toJSON).not.toHaveBeenCalled();
});
it('should replace the data of the histogramChartView when user zooms in', function () {
var i = 0;
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': i++,
'nulls': 0,
'bins': [{ bin: 0 }, { bin: 1 }]
});
};
spyOn(this.view, '_onChangeZoomed').and.callThrough();
this.view.render();
spyOn(this.view.histogramChartView, 'replaceData');
this.view.$('.js-zoom').click();
expect(this.view._onChangeZoomed).toHaveBeenCalled();
expect(this.view.histogramChartView.replaceData).toHaveBeenCalled();
});
it('should update data and original data of the mini histogram if there is a data change and it is not zoomed', function () {
var i = 0;
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': i++,
'nulls': 0,
'bins': []
});
};
this.dataviewModel.fetch();
spyOn(this.view, '_isZoomed').and.returnValue(false);
spyOn(this.view.miniHistogramChartView, 'replaceData');
// Change data
this.originalData.trigger('loadModelCompleted', null, this.originalData);
expect(this.view.miniHistogramChartView.replaceData).toHaveBeenCalled();
});
it('should remove previous filter if there is a data change', function () {
var i = 0;
this.dataviewModel.sync = function (method, model, options) {
options.success({
'bin_width': 10,
'bins_count': 2,
'bins_start': i++,
'nulls': 0,
'bins': []
});
};
this.dataviewModel.fetch();
spyOn(this.dataviewModel, 'disableFilter');
spyOn(this.view.filter, 'unsetRange');
// Change data
this.originalData.set('bins', 122, { silent: true });
this.originalData.trigger('loadModelCompleted', null, this.originalData);
expect(this.dataviewModel.disableFilter).toHaveBeenCalled();
expect(this.view.filter.unsetRange).toHaveBeenCalled();
expect(this.view.model.get('filter_enabled')).toBeFalsy();
expect(this.view.model.get('lo_index')).toBeFalsy();
expect(this.view.model.get('hi_index')).toBeFalsy();
});
it('should reset widget if number of bins is changed', function () {
this.view._unbinds();
spyOn(this.view, '_resetWidget');
this.view._setupBindings();
var prevBins = this.view._dataviewModel.get('bins') || 10;
this.view._dataviewModel.set('bins', prevBins + 1);
expect(this.view._resetWidget).toHaveBeenCalled();
});
});
describe('_onNormalizedChanged', function () {
it('should set normalized in histogram and miniHistogram views to our value', function () {
this.originalData.trigger('change:hasBeenFetched', true);
this.view.render();
var prevNormalized = this.view.model.get('normalized');
spyOn(this.view.histogramChartView, 'setNormalized');
spyOn(this.view.miniHistogramChartView, 'setNormalized');
this.view.model.set('normalized', !prevNormalized);
expect(this.view.histogramChartView.setNormalized).toHaveBeenCalledWith(!prevNormalized);
expect(this.view.miniHistogramChartView.setNormalized).toHaveBeenCalledWith(!prevNormalized);
});
});
afterEach(function () {
this.view.clean();
});
});
function genHistogramData (n) {
n = n || 1;
var arr = [];
_.times(n, function (i) {
var start = (100 * i) + Math.round(Math.random() * 1000);
var end = start + 100;
var obj = {
bin: i,
freq: Math.round(Math.random() * 10),
avg: Math.round(Math.random() * 10),
start: start,
end: end,
max: end,
min: start
};
arr.push(obj);
});
return arr;
}