var Backbone = require('backbone'); var WindshaftFiltersRange = require('../../../src/windshaft/filters/range'); var WindshaftFiltersBoundingBox = require('../../../src/windshaft/filters/bounding-box'); var WindshaftFiltersCircle = require('../../../src/windshaft/filters/circle'); var WindshaftFiltersPolygon = require('../../../src/windshaft/filters/polygon'); var HistogramDataviewModel = require('../../../src/dataviews/histogram-dataview-model'); var MapModelBoundingBoxAdapter = require('../../../src/geo/adapters/map-model-bounding-box-adapter'); var helper = require('../../../src/dataviews/helpers/histogram-helper'); var MockFactory = require('../../helpers/mockFactory'); var createEngine = require('../fixtures/engine.fixture.js'); function randomString (length, chars) { var result = ''; for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; return result; } describe('dataviews/histogram-dataview-model', function () { var engineMock; var apiKeyQueryParam; beforeEach(function () { this.map = jasmine.createSpyObj('map', ['getViewBounds', 'on']); this.map.getViewBounds.and.returnValue([[1, 2], [3, 4]]); engineMock = createEngine({}); apiKeyQueryParam = 'api_key=' + engineMock.getApiKey(); this.filter = new WindshaftFiltersRange(); this.bboxFilter = new WindshaftFiltersBoundingBox(new MapModelBoundingBoxAdapter(this.map)); spyOn(HistogramDataviewModel.prototype, 'listenTo').and.callThrough(); spyOn(HistogramDataviewModel.prototype, 'fetch').and.callThrough(); spyOn(HistogramDataviewModel.prototype, '_updateBindings'); spyOn(HistogramDataviewModel.prototype, '_resetFilterAndFetch'); this.source = MockFactory.createAnalysisModel({ id: 'a0' }); this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, filter: this.filter, bboxFilter: this.bboxFilter }); }); it('defaults', function () { expect(this.model.get('type')).toBe('histogram'); expect(this.model.get('totalAmount')).toBe(0); expect(this.model.get('filteredAmount')).toBe(0); expect(this.model.get('hasNulls')).toBe(false); expect(this.model.get('localTimezone')).toBe(false); }); it('after calling _initBinds, we must listen to changes in URL', function () { spyOn(this.model, '_onUrlChanged'); this.model._initBinds(); this.model.set('url', randomString(32, 'abcdefghijk')); expect(this.model._onUrlChanged).toHaveBeenCalled(); }); it('should not listen any url change from the beginning', function () { this.model.set('url', 'https://carto.com'); expect(this.model.fetch).not.toHaveBeenCalled(); }); it('should set unfiltered model url when model has changed it', function () { spyOn(this.model._totals, 'setUrl'); this.model.set('url', 'hey!'); expect(this.model._totals.setUrl).toHaveBeenCalled(); }); it('should set the api_key attribute on the internal models', function () { this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, filter: this.filter, bboxFilter: this.bboxFilter }); expect(this.model._totals.get('apiKey')).toEqual(engineMock.getApiKey()); }); describe('should get the correct histogram shape', function () { beforeEach(function () { this.model.set('bins', 6); }); it('when it is flat', function () { this.model.set('data', [ { bin: 0, freq: 25 }, { bin: 1, freq: 26 }, { bin: 2, freq: 25 }, { bin: 3, freq: 26 }, { bin: 4, freq: 26 }, { bin: 5, freq: 25 } ]); expect(this.model.getDistributionType()).toEqual('F'); }); it('when it is A', function () { this.model.set('data', [ { bin: 0, freq: 0 }, { bin: 1, freq: 5 }, { bin: 2, freq: 25 }, { bin: 3, freq: 18 }, { bin: 4, freq: 8 }, { bin: 5, freq: 2 } ]); expect(this.model.getDistributionType()).toEqual('A'); }); it('when it is J', function () { this.model.set('data', [ { bin: 0, freq: 0 }, { bin: 1, freq: 2 }, { bin: 2, freq: 5 }, { bin: 3, freq: 8 }, { bin: 4, freq: 18 }, { bin: 5, freq: 25 } ]); expect(this.model.getDistributionType()).toEqual('J'); }); it('when it is L', function () { this.model.set('data', [ { bin: 0, freq: 25 }, { bin: 1, freq: 18 }, { bin: 4, freq: 8 }, { bin: 2, freq: 5 }, { bin: 5, freq: 2 }, { bin: 3, freq: 0 } ]); expect(this.model.getDistributionType()).toEqual('L'); }); xit('when it is clustered', function () { this.model.set('data', [ { bin: 0, freq: 20 }, { bin: 1, freq: 18 }, { bin: 2, freq: 5 }, { bin: 3, freq: 0 }, { bin: 4, freq: 32 }, { bin: 5, freq: 16 } ]); expect(this.model.getDistributionType()).toEqual('C'); }); }); describe('when _totals changes:data', function () { beforeEach(function () { var histogramData = { bin_width: 10, bins_count: 3, bins_start: 12, nulls: 0, aggregation: 'quarter' }; spyOn(this.model._totals, 'sync').and.callFake(function (method, model, options) { options.success(histogramData); }); }); it('should set start, end, bins and aggregation', function () { expect(this.model.get('start')).toBeUndefined(); expect(this.model.get('end')).toBeUndefined(); this.model._totals.fetch(); expect(this.model.get('start')).toEqual(12); expect(this.model.get('end')).toEqual(42); expect(this.model.get('bins')).toEqual(3); expect(this.model.get('aggregation')).toEqual('quarter'); }); it('should call _updateBindings only once', function () { this.model._totals.fetch(); expect(this.model._updateBindings).toHaveBeenCalled(); this.model._updateBindings.calls.reset(); this.model._totals.fetch(); expect(this.model._updateBindings).not.toHaveBeenCalled(); }); }); describe('when totals has an error', function () { it('dataview status is set to error and status error is properly triggered', function () { var ajaxResponse = { readyState: 4, responseText: '{"errors":["column unknown_column does not exist"],"errors_with_context":[{"type":"unknown","message":"column unknown_column does not exist"}]}', responseJSON: { errors: [ 'column "unknown_column" does not exist' ], errors_with_context: [ { type: 'unknown', message: 'column unknown_column does not exist' } ] }, status: 404, statusText: 'Not Found' }; var capturedError = null; this.model.on('statusError', function (model, error) { capturedError = error; }); this.model._totals.trigger('error', this.model._totals, ajaxResponse); expect(this.model.get('status')).toEqual('error'); expect(capturedError.message).toEqual('column unknown_column does not exist'); }); }); describe('when column changes', function () { it('should set column_type to original data, set undefined aggregation, reload map and call _onUrlChanged', function () { engineMock.reload.calls.reset(); this.model.set({ aggregation: 'quarter', column: 'random_col', column_type: 'aColumnType' }); expect(this.model._totals.get('column_type')).toEqual('aColumnType'); expect(this.model.get('aggregation')).toBeUndefined(); expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' }); }); }); describe('parse', function () { it('should parse the bins', function () { var data = { bin_width: 14490.25, bins: [ { bin: 0, freq: 2, max: 70151, min: 55611 }, { bin: 1, freq: 2, max: 79017, min: 78448 }, { bin: 3, freq: 1, max: 113572, min: 113572 } ], bins_count: 4, bins_start: 55611, nulls: 1, type: 'histogram' }; this.model.parse(data); var parsedData = this.model.getData(); expect(data.nulls).toBe(1); expect(parsedData.length).toBe(4); expect(JSON.stringify(parsedData)).toBe('[{"bin":0,"start":55611,"end":70101.25,"freq":2,"max":70151,"min":55611},{"bin":1,"start":70101.25,"end":84591.5,"freq":2,"max":79017,"min":78448},{"bin":2,"start":84591.5,"end":99081.75,"freq":0},{"bin":3,"start":99081.75,"end":113572,"freq":1,"max":113572,"min":113572}]'); }); it('should set hasNulls to true if null is set in the response', function () { var data = { bin_width: 14490.25, bins: [ { bin: 0, freq: 2, max: 70151, min: 55611 }, { bin: 1, freq: 2, max: 79017, min: 78448 }, { bin: 3, freq: 1, max: 113572, min: 113572 } ], bins_count: 4, bins_start: 55611, nulls: 0, type: 'histogram' }; var model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, filter: this.filter, bboxFilter: this.bboxFilter }); model.set(model.parse(data)); expect(model.hasNulls()).toBe(true); }); it('should set hasNulls to false if null is undefined in the response', function () { var data = { bin_width: 14490.25, bins: [ { bin: 0, freq: 2, max: 70151, min: 55611 }, { bin: 1, freq: 2, max: 79017, min: 78448 }, { bin: 3, freq: 1, max: 113572, min: 113572 } ], bins_count: 4, bins_start: 55611, type: 'histogram' }; var model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, filter: this.filter, bboxFilter: this.bboxFilter }); model.set(model.parse(data)); model._totals = new Backbone.Model({ aggregation: 'quarter' }); expect(model.hasNulls()).toBe(false); }); it('should calculate total amount and filtered amount in parse when a filter is present', function () { var data = { bin_width: 1, bins: [ { bin: 0, freq: 2 }, { bin: 1, freq: 3 }, { bin: 2, freq: 7 } ], bins_count: 3, bins_start: 1, nulls: 0, type: 'histogram' }; this.model.filter = new WindshaftFiltersRange({ min: 1, max: 3 }); var parsedData = this.model.parse(data); expect(parsedData.totalAmount).toBe(12); expect(parsedData.filteredAmount).toBe(5); }); it('should calculate only total amount in parse when there is no filter', function () { var data = { bin_width: 1, bins: [ { bin: 0, freq: 2 }, { bin: 1, freq: 3 }, { bin: 2, freq: 7 } ], bins_count: 3, bins_start: 1, nulls: 0, type: 'histogram' }; var parsedData = this.model.parse(data); expect(parsedData.totalAmount).toBe(12); expect(parsedData.filteredAmount).toBe(0); }); it('parser do not fails when there are no bins', function () { var data = { bin_width: 0, bins: [], bins_count: 0, bins_start: 0, nulls: 0, type: 'histogram' }; this.model.parse(data); var parsedData = this.model.getData(); expect(data.nulls).toBe(0); expect(parsedData.length).toBe(0); }); it('should parse the bins and fix end bucket issues', function () { var data = { bin_width: 1041.66645833333, bins_count: 48, bins_start: 0.01, nulls: 0, avg: 55.5007561961441, bins: [{ bin: 47, min: 50000, max: 50000, avg: 50000, freq: 6 // NOTE - The end of this bucket is 48 * 1041.66645833333 = 49999.98999999984 // but it must be corrected to 50.000. }], type: 'histogram' }; this.model.parse(data); var parsedData = this.model.getData(); expect(data.nulls).toBe(0); expect(parsedData.length).toBe(48); expect(parsedData[47].end).not.toBeLessThan(parsedData[47].max); }); it('should call .fillNumericBuckets if aggregation is not present', function () { spyOn(helper, 'fillNumericBuckets'); this.model._initBinds(); this.model.set('column_type', 'number'); var data = { bin_width: 0, bins: [], bins_count: 0, bins_start: 0, nulls: 0, type: 'histogram' }; this.model.parse(data); expect(helper.fillNumericBuckets).toHaveBeenCalled(); }); it('should call .fillTimestampBuckets if aggregation is present', function () { spyOn(helper, 'fillTimestampBuckets').and.callThrough(); this.model._initBinds(); this.model.set({ aggregation: 'minute', column_type: 'date' }, { silent: true }); var data = { aggregation: 'minute', offset: 3600, timestamp_start: 1496690940, bin_width: 59.5833333333333, bins_count: 2, bins_start: 1496690940, nulls: 0, bins: [ { bin: 0, timestamp: 1496690940, min: 1496690944, max: 1496690999, avg: 1496690971.58824, freq: 17 }, { bin: 1, timestamp: 1496691000, min: 1496691003, max: 1496691059, avg: 1496691031.22222, freq: 18 } ], type: 'histogram' }; var parsedData = this.model.parse(data); expect(helper.fillTimestampBuckets).toHaveBeenCalled(); expect(JSON.stringify(parsedData)).toBe('{"data":[{"bin":0,"start":1496690940,"end":1496690999,"next":1496691000,"freq":17,"min":1496690944,"max":1496690999,"avg":1496690971.58824},{"bin":1,"start":1496691000,"end":1496691059,"next":1496691060,"freq":18,"min":1496691003,"max":1496691059,"avg":1496691031.22222}],"filteredAmount":0,"nulls":0,"totalAmount":35,"bins":2,"hasNulls":true}'); }); }); describe('when column_type changes', function () { beforeEach(function () { expect(this.model.filter.get('column_type')).not.toEqual('date'); this.model.set('column_type', 'date'); }); it('should change the filter column_type', function () { expect(this.model.filter.get('column_type')).toEqual('date'); }); }); describe('.url', function () { beforeEach(function () { this.model.set('url', 'http://example.com'); }); it('should include bbox', function () { expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&' + apiKeyQueryParam); }); it('should include circle filter', function () { var filter = new WindshaftFiltersCircle(); var circle = {lat: 1, lng: 2, radius: 3}; filter.setCircle(circle); this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, circleFilter: filter }); // DataviewModel defaults set this prop to true, even for cases like this not requiring passing a bbox filter this.model.set('sync_on_bbox_change', false); var circleEncoded = encodeURIComponent(JSON.stringify(circle)); expect(this.model.set('url', 'http://example.com')); expect(this.model.url()).toEqual('http://example.com?circle=' + circleEncoded + '&' + apiKeyQueryParam); }); it('should update circle filter', function () { var filter = new WindshaftFiltersCircle(); var circle = {lat: 1, lng: 2, radius: 3}; filter.setCircle(circle); this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, circleFilter: filter }); // DataviewModel defaults set this prop to true, even for cases like this not requiring passing a bbox filter this.model.set('sync_on_bbox_change', false); // updated! var updatedCircle = {lat: 10, lng: 20, radius: 30}; filter.setCircle(updatedCircle); var updatedCircleEncoded = encodeURIComponent(JSON.stringify(updatedCircle)); expect(this.model.set('url', 'http://example.com')); expect(this.model.url()).toEqual('http://example.com?circle=' + updatedCircleEncoded + '&' + apiKeyQueryParam); }); it('should include polygon filter, plus other required params', function () { var filter = new WindshaftFiltersPolygon(); var polygon = { type: 'Polygon', coordinates: [[1, 2], [3, 4], [5, 6], [1, 2]] }; filter.setPolygon(polygon); this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, polygonFilter: filter }); // DataviewModel defaults set this prop to true, even for cases like this not requiring passing a bbox filter this.model.set('sync_on_bbox_change', false); var polygonEncoded = encodeURIComponent(JSON.stringify(polygon)); expect(this.model.set('url', 'http://example.com')); expect(this.model.url()).toEqual('http://example.com?polygon=' + polygonEncoded + '&' + apiKeyQueryParam); }); it('should update polygon filter', function () { var filter = new WindshaftFiltersPolygon(); var polygon = { type: 'Polygon', coordinates: [[1, 2], [3, 4], [5, 6], [1, 2]] }; filter.setPolygon(polygon); this.model = new HistogramDataviewModel({ source: this.source }, { engine: engineMock, polygonFilter: filter }); // DataviewModel defaults set this prop to true, even for cases like this not requiring passing a bbox filter this.model.set('sync_on_bbox_change', false); // updated! var updatedPolygon = { type: 'Polygon', coordinates: [[10, 20], [30, 40], [50, 60], [10, 20]] }; filter.setPolygon(updatedPolygon); var updatedPolygonEncoded = encodeURIComponent(JSON.stringify(updatedPolygon)); expect(this.model.set('url', 'http://example.com')); expect(this.model.url()).toEqual('http://example.com?polygon=' + updatedPolygonEncoded + '&' + apiKeyQueryParam); }); describe('column type is number', function () { describe('if bins present', function () { it('should include start and end if present', function () { this.model.set({ bins: 33, start: 11, end: 22, column_type: 'number' }); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&bins=33&start=11&end=22&' + apiKeyQueryParam); }); it('should include bins', function () { this.model.set({ bins: 33, column_type: 'number' }); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&bins=33&' + apiKeyQueryParam); }); }); it('should not include start, end and bins when own_filter is enabled', function () { this.model.set({ url: 'http://example.com', start: 0, end: 10, bins: 25, column_type: 'number' }); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&bins=25&start=0&end=10&' + apiKeyQueryParam); this.model.enableFilter(); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=1&bins=25&' + apiKeyQueryParam); }); }); describe('column type is date', function () { it('should only include aggregation if aggregation and bins present', function () { this.model.set({ aggregation: 'month', bins: 33, column_type: 'date' }); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&aggregation=month&' + apiKeyQueryParam); }); it('should include aggregation auto if column type is date and no aggregation set', function () { this.model.set({ aggregation: undefined, column_type: 'date' }); expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&aggregation=auto&' + apiKeyQueryParam); }); it('should use offset if present', function () { this.model.set({ aggregation: 'month', column_type: 'date', offset: 7200, localTimezone: false }, { silent: true }); var url = this.model.url(); expect(url).toEqual('http://example.com?bbox=2,1,4,3&aggregation=month&offset=7200&' + apiKeyQueryParam); }); it('should use local offset if localTimezone is true', function () { this.model.set({ aggregation: 'month', column_type: 'date', offset: 7200, localTimezone: true }, { silent: true }); this.model._localOffset = 43200; var url = this.model.url(); expect(url).toEqual('http://example.com?bbox=2,1,4,3&aggregation=month&offset=43200&' + apiKeyQueryParam); }); }); }); describe('.toJSON', function () { beforeEach(function () { this.model.set('column', 'updated_at', { silent: true }); spyOn(this.model, 'getSourceId').and.returnValue('g4'); }); it('should return no bins if column is number and bins undefined', function () { this.model.set({ column_type: 'number', bins: undefined }, { silent: true }); var json = this.model.toJSON(); expect(json).toEqual({ type: 'histogram', source: { id: 'g4' }, options: { column: 'updated_at' } }); }); it('should return bins if column is number and bins defined', function () { this.model.set({ column_type: 'number', bins: 808 }, { silent: true }); var json = this.model.toJSON(); expect(json).toEqual({ type: 'histogram', source: { id: 'g4' }, options: { column: 'updated_at', bins: 808 } }); }); it('should return auto if column is date and aggregation undefined', function () { this.model.set({ column_type: 'date', aggregation: undefined }, { silent: true }); var json = this.model.toJSON(); expect(json).toEqual({ type: 'histogram', source: { id: 'g4' }, options: { column: 'updated_at', aggregation: 'auto' } }); }); it('should return aggregation if column is date and aggregation defined', function () { this.model.set({ column_type: 'date', aggregation: 'minute' }, { silent: true }); var json = this.model.toJSON(); expect(json).toEqual({ type: 'histogram', source: { id: 'g4' }, options: { column: 'updated_at', aggregation: 'minute' } }); }); }); describe('.enableFilter', function () { it('should set the own_filter attribute', function () { expect(this.model.get('own_filter')).toBeUndefined(); this.model.enableFilter(); expect(this.model.get('own_filter')).toEqual(1); }); }); describe('.disableFilter', function () { it('should unset the own_filter attribute', function () { this.model.enableFilter(); this.model.disableFilter(); expect(this.model.get('own_filter')).toBeUndefined(); }); }); describe('._onColumnChanged', function () { it('should unset aggregation, and call _reloadAndForceFetch', function () { engineMock.reload.calls.reset(); this.model.set({ column: 'time', aggregation: 'week', offset: 3600 }); this.model._onColumnChanged(); expect(engineMock.reload).toHaveBeenCalled(); expect(this.model.get('aggregation')).toBeUndefined(); }); it('should reset totals start and end values', function () { spyOn(this.model._totals, 'sync').and.callFake(function (method, model, options) { options.success({ bin_width: 10, bins_count: 3, bins_start: 12, nulls: 0, aggregation: 'quarter' }); }); this.model._totals.fetch(); expect(this.model._totals.get('start')).toEqual(12); expect(this.model._totals.get('end')).toEqual(42); this.model.set({ column: 'time', aggregation: 'week', offset: 3600 }); this.model._onColumnChanged(); expect(this.model._totals.get('start')).toBeNull(); expect(this.model._totals.get('end')).toBeNull(); }); }); describe('._calculateTotalAmount', function () { it('should aggregate all bucket frequencies', function () { var buckets = [ { freq: 8 }, { freq: 7 }, { freq: 0 }, { freq: 3 } ]; var result = this.model._calculateTotalAmount(buckets); expect(result).toEqual(18); }); it('should return 0 if no buckets present', function () { var buckets = []; var result = this.model._calculateTotalAmount(buckets); expect(result).toEqual(0); }); it('should calculate totals properly even if no bucket is present in the middle', function () { var buckets = [ { freq: 8 }, null, { freq: 0 }, { max: 6 }, { freq: 3 } ]; var result = this.model._calculateTotalAmount(buckets); expect(result).toEqual(11); }); }); describe('._onTotalsDataFetched', function () { beforeEach(function () { }); it('should be called callwhen totals data has been fetched', function () { spyOn(this.model, '_onTotalsDataFetched'); this.model._totals.off('loadModelCompleted', null, this.model); this.model._initBinds(); this.model._totals.trigger('loadModelCompleted'); expect(this.model._onTotalsDataFetched).toHaveBeenCalled(); }); it('should call _resetFilterAndFetch if column is date and aggregation', function () { var model = new Backbone.Model({ aggregation: 'week' }); this.model.set('column_type', 'date', { silent: true }); this.model._onTotalsDataFetched(null, model); expect(this.model._resetFilterAndFetch).toHaveBeenCalled(); }); it('should call _resetFilterAndFetch if column is date and offset changes', function () { var model = new Backbone.Model({ offset: 3600 }); this.model.set('column_type', 'date', { silent: true }); this.model._onTotalsDataFetched(null, model); expect(this.model._resetFilterAndFetch).toHaveBeenCalled(); }); it('should call _resetFilterAndFetch if column is number and bins changes', function () { var model = new Backbone.Model({ bins: 5 }); this.model.set('column_type', 'number', { silent: true }); this.model._onTotalsDataFetched(null, model); expect(this.model._resetFilterAndFetch).toHaveBeenCalled(); }); it('should call only fetch in the rest of cases', function () { var model = new Backbone.Model({ start: this.model.get('start') + 1, end: 22 }); this.model._onTotalsDataFetched(null, model); expect(this.model.fetch).toHaveBeenCalled(); }); it('should set the data fetched', function () { var model = new Backbone.Model({ bins: 5, start: 11, end: 22 }); this.model._onTotalsDataFetched(null, model); expect(this.model.get('start')).toEqual(11); expect(this.model.get('end')).toEqual(22); expect(this.model.get('bins')).toEqual(5); }); }); describe('change local timezone', function () { it('should set the same value to originalData', function () { var originalValue = this.model.get('localTimezone'); this.model._totals.set('localTimezone', originalValue, { silent: true }); this.model.set('localTimezone', !originalValue); expect(this.model._totals.get('localTimezone')).toBe(this.model.get('localTimezone')); }); }); describe('.getCurrentOffset', function () { beforeEach(function () { this.model.set('offset', 7200, { silent: true }); this.model._localOffset = 43200; }); it('should return offset if `localTimezone` is not set', function () { this.model.set('localTimezone', false, { silent: true }); var offset = this.model.getCurrentOffset(); expect(offset).toBe(7200); }); it('should return local offset if `localTimezone` is set', function () { this.model.set('localTimezone', true, { silent: true }); var offset = this.model.getCurrentOffset(); expect(offset).toBe(43200); }); }); describe('_.onFieldsChanged', function () { it('should set bins of totals if bins are changed in a number column', function () { this.model.set({ bins: 808, column_type: 'number' }, { silent: true }); this.model._totals.set({ bins: 808 }, { silent: true }); this.model.set({ bins: 303 }); expect(this.model._totals.get('bins')).toBe(303); }); it('should not set bins of totals if bins are changed because of a column change', function () { this.model.set({ bins: 808, aggregation: 'week', column_type: 'number' }, { silent: true }); this.model._totals.set({ bins: 808 }, { silent: true }); this.model.set({ bins: 303, aggregation: undefined }); expect(this.model._totals.get('bins')).toBe(808); }); it('should set offset and aggregation of totals if bins are changed in a date column', function () { this.model.set({ offset: 7200, aggregation: 'week', column_type: 'date' }, { silent: true }); this.model._totals.set({ offset: 7200, aggregation: 'week' }, { silent: true }); this.model.set({ aggregation: 'month', offset: 3600 }); expect(this.model._totals.get('aggregation')).toBe('month'); expect(this.model._totals.get('offset')).toBe(3600); }); }); });