Initial commit

This commit is contained in:
zhongjin
2020-06-13 18:34:34 +08:00
commit 52aaa9f15d
655 changed files with 96796 additions and 0 deletions
@@ -0,0 +1,452 @@
var Backbone = require('backbone');
var _ = require('underscore');
var CategoryDataviewModel = require('../../../src/dataviews/category-dataview-model');
var WindshaftFiltersCategory = require('../../../src/windshaft/filters/category');
var WindshaftFiltersBoundingBox = require('../../../src/windshaft/filters/bounding-box');
var WindshaftFiltersCircle = require('../../../src/windshaft/filters/circle');
var WindshaftFiltersPolygon = require('../../../src/windshaft/filters/polygon');
var AnalysisService = require('../../../src/analysis/analysis-service');
var MapModelBoundingBoxAdapter = require('../../../src/geo/adapters/map-model-bounding-box-adapter');
var createEngine = require('../fixtures/engine.fixture.js');
describe('dataviews/category-dataview-model', function () {
var engineMock;
var apiKey = 'API_KEY';
var apiKeyQueryParam = 'api_key=' + apiKey;
beforeEach(function () {
this.map = new Backbone.Model();
this.map.getViewBounds = jasmine.createSpy();
engineMock = createEngine({ apiKey: apiKey });
this.map.getViewBounds.and.returnValue([[1, 2], [3, 4]]);
var analysisDefinition = {
id: 'a0',
type: 'source',
params: {
query: 'SELECT * FROM blairbnb_listings'
}
};
var analysisService = new AnalysisService({ engine: engineMock });
this.source = analysisService.analyse(analysisDefinition);
spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; });
this.model = new CategoryDataviewModel({
source: this.source
}, {
engine: engineMock,
filter: new WindshaftFiltersCategory(),
bboxFilter: new WindshaftFiltersBoundingBox(new MapModelBoundingBoxAdapter(this.map))
});
});
it('should reload map and force fetch on changing attrs', function () {
engineMock.reload.calls.reset();
this.model.set('column', 'random_col');
expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' });
engineMock.reload.calls.reset();
this.model.set('aggregation', 'count');
expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' });
engineMock.reload.calls.reset();
this.model.set('aggregation_column', 'other');
expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' });
});
it('should define several internal models/collections', function () {
expect(this.model._data).toBeDefined();
expect(this.model._searchModel).toBeDefined();
expect(this.model.filter).toBeDefined();
});
it('should set the api_key attribute on the internal models', function () {
this.model = new CategoryDataviewModel({
source: this.source
}, {
map: this.map,
engine: engineMock,
layer: jasmine.createSpyObj('layer', ['get']),
filter: new WindshaftFiltersCategory()
});
expect(this.model._searchModel.get('apiKey')).toEqual(apiKey);
expect(this.model._rangeModel.get('apiKey')).toEqual(apiKey);
});
describe('.url', function () {
it('should include the bbox, own_filter and categories parameters', function () {
expect(this.model.set('url', 'http://example.com'));
expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=0&categories=6&' + apiKeyQueryParam);
this.model.set('filterEnabled', true);
expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=1&categories=6&' + apiKeyQueryParam);
this.model.set('filterEnabled', false);
expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=0&categories=6&' + apiKeyQueryParam);
this.model.set('categories', 1);
expect(this.model.url()).toEqual('http://example.com?bbox=2,1,4,3&own_filter=0&categories=1&' + apiKeyQueryParam);
});
it('should include circle filter, plus other required params', function () {
var filter = new WindshaftFiltersCircle();
var circle = {lat: 1, lng: 2, radius: 3};
filter.setCircle(circle);
this.model = new CategoryDataviewModel({
source: this.source
}, {
engine: engineMock,
filter: new WindshaftFiltersCategory(),
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 + '&own_filter=0&categories=6&' + 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 CategoryDataviewModel({
source: this.source
}, {
engine: engineMock,
filter: new WindshaftFiltersCategory(),
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 + '&own_filter=0&categories=6&' + 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 CategoryDataviewModel({
source: this.source
}, {
engine: engineMock,
filter: new WindshaftFiltersCategory(),
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 + '&own_filter=0&categories=6&' + 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 CategoryDataviewModel({
source: this.source
}, {
engine: engineMock,
filter: new WindshaftFiltersCategory(),
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 + '&own_filter=0&categories=6&' + apiKeyQueryParam);
});
});
describe('binds', function () {
beforeEach(function () {
this.model.set({
url: 'http://heytest.io'
});
});
describe('url', function () {
beforeEach(function () {
spyOn(this.model, 'fetch');
spyOn(this.model._searchModel, 'fetch');
spyOn(this.model._rangeModel, 'fetch');
});
it('should set search url when it changes', function () {
expect(this.model._searchModel.get('url')).toBe('http://heytest.io');
expect(this.model._searchModel.url()).toBe('http://heytest.io/search?q=&' + apiKeyQueryParam);
});
it('should set rangeModel url when it changes', function () {
expect(this.model._rangeModel.get('url')).toBe('http://heytest.io');
expect(this.model._rangeModel.url()).toBe('http://heytest.io?' + apiKeyQueryParam);
});
});
describe('search events dispatcher', function () {
var eventNames = ['loading', 'loaded', 'error'];
_.each(eventNames, function (eventName) {
it("should re-trigger the '" + eventName + "' event", function () {
var spyObj = jasmine.createSpy(eventName);
this.model.bind(eventName, spyObj);
this.model._searchModel.trigger(eventName);
expect(spyObj).toHaveBeenCalled();
});
}, this);
describe('on search data change', function () {
beforeEach(function () {
spyOn(this.model, 'trigger');
this.model.filter.accept(['hey']);
this.model._searchModel.setData([{ name: 'hey', value: 1 }, { name: 'vamos', value: 2 }, { name: 'neno', value: 3 }]);
});
it('should check if search results are already selected or not', function () {
var data = this.model.getSearchResult();
expect(data.size()).toBe(3);
var selectedCategories = data.where({ selected: true });
var selectedCategory = selectedCategories[0];
expect(_.size(selectedCategories)).toBe(1);
expect(selectedCategory.get('name')).toBe('hey');
});
it('should trigger searchData event', function () {
expect(this.model.trigger).toHaveBeenCalledWith('change:searchData', this.model);
});
});
});
describe('range model', function () {
it('should set totalCount when rangeModel has changed', function () {
expect(this.model.get('totalCount')).toBeUndefined();
this.model._rangeModel.set({ totalCount: 1000 });
expect(this.model.get('totalCount')).toBe(1000);
});
it('should set categoriesCount when rangeModel has changed', function () {
expect(this.model.get('categoriesCount')).toBeUndefined();
this.model._rangeModel.set({ categoriesCount: 123 });
expect(this.model.get('categoriesCount')).toBe(123);
});
});
});
describe('bindings to map bounds', function () {
beforeEach(function () {
// Disable debounce
this.model._bboxFilter._stopBinds();
this.model._bboxFilter._initBinds();
this.model.fetch = function (opts) {
opts && opts.success();
};
this.model.set('url', 'http://example.com');
});
it('should fetch when the bounding box has changed', function () {
spyOn(this.model._searchModel, 'fetchIfSearchIsApplied');
this.map.getViewBounds.and.returnValue([200, 200], [300, 400]);
this.map.trigger('change:center');
expect(this.model._searchModel.fetchIfSearchIsApplied).toHaveBeenCalled();
});
});
describe('search model helpers', function () {
it('should clean search properly', function () {
spyOn(this.model._searchModel, 'resetData');
this.model.cleanSearch();
expect(this.model._searchModel.resetData).toHaveBeenCalled();
});
describe('setupSearch', function () {
beforeEach(function () {
spyOn(this.model._searchModel, 'setData').and.callThrough();
});
it('should not setup search if search is already applied', function () {
spyOn(this.model, 'isSearchApplied').and.returnValue(true);
this.model.setupSearch();
expect(this.model._searchModel.setData).not.toHaveBeenCalled();
});
it('should setup search if it is gonna be enabled', function () {
spyOn(this.model, 'isSearchApplied').and.returnValue(false);
_parseData(this.model, _generateData(3));
this.model.filter.accept(['4', '5', '6']);
this.model.setupSearch();
expect(this.model._searchModel.setData).toHaveBeenCalled();
expect(this.model.getSearchCount()).toBe(3);
});
});
});
it('should refresh its own data only if the search is not applied', function () {
spyOn(this.model, 'fetch');
spyOn(this.model._searchModel, 'fetch');
this.model.refresh();
expect(this.model.fetch.calls.count()).toEqual(1);
expect(this.model.fetch).toHaveBeenCalled();
expect(this.model._searchModel.fetch).not.toHaveBeenCalled();
spyOn(this.model, 'isSearchApplied').and.returnValue(true);
this.model.refresh();
expect(this.model._searchModel.fetch).toHaveBeenCalled();
expect(this.model.fetch.calls.count()).toEqual(1);
});
describe('filters over data', function () {
beforeEach(function () {
this.model._data.reset([{ name: 'one', value: 1 }, { name: 'buddy', value: 2 }, { name: 'neno', value: 3 }]);
});
describe('.numberOfAcceptedCategories', function () {
it('should count accepted categories over the current data', function () {
this.model.filter.accept('vamos');
expect(this.model.numberOfAcceptedCategories()).toBe(0);
this.model.filter.accept('buddy');
expect(this.model.numberOfAcceptedCategories()).toBe(1);
this.model.filter.reject('neno');
expect(this.model.numberOfAcceptedCategories()).toBe(2);
this.model._data.reset([]);
expect(this.model.numberOfAcceptedCategories()).toBe(0);
});
});
describe('.numberOfRejectedCategories', function () {
it('should count rejected categories over the current data', function () {
this.model.filter.reject('vamos');
expect(this.model.numberOfRejectedCategories()).toBe(0);
this.model.filter.reject('buddy');
expect(this.model.numberOfRejectedCategories()).toBe(1);
this.model.filter.accept('neno');
expect(this.model.numberOfRejectedCategories()).toBe(1);
this.model._data.reset([]);
expect(this.model.numberOfRejectedCategories()).toBe(0);
});
});
});
describe('.parse', function () {
it('should change internal data collection when parse is called', function () {
var resetSpy = jasmine.createSpy('reset');
this.model._data.bind('reset', resetSpy);
_parseData(this.model, _generateData(2));
expect(resetSpy).toHaveBeenCalled();
});
describe('when filter is disabled', function () {
it('should NOT add categories that are accepted when they are not present in the new categories', function () {
this.model.filter.accept('Madrid');
this.model.disableFilter();
_parseData(this.model, _.map(['Barcelona'], function (v) {
return {
category: v,
value: 1
};
}));
var categories = this.model.get('data');
expect(categories.length).toEqual(1);
expect(categories[0].name).toEqual('Barcelona');
});
});
describe('when filter is enabled', function () {
it('should add categories that are accepted when they are not present in the new categories', function () {
this.model.filter.accept('Madrid');
this.model.enableFilter();
_parseData(this.model, _.map(['Barcelona'], function (v) {
return {
category: v,
value: 1
};
}));
var categories = this.model.get('data');
expect(categories.length).toEqual(2);
expect(categories[0].name).toEqual('Barcelona');
expect(categories[1].name).toEqual('Madrid');
});
});
});
describe('.update', function () {
beforeEach(function () {
expect(this.model.get('foo')).toBeUndefined();
expect(this.model.get('sync_on_bbox_change')).toBe(true);
expect(this.model.get('aggregation')).not.toEqual('sum');
this.model.update({
sync_on_bbox_change: false,
aggregation: 'sum',
foo: 'bar'
});
});
it('should allow to set attrs but only the defined ones', function () {
expect(this.model.get('sync_on_bbox_change')).toBe(false);
expect(this.model.get('aggregation')).toEqual('sum');
expect(this.model.get('foo')).toBeUndefined();
});
});
describe('.getCount', function () {
it('returns the total number of categories', function () {
this.model.set('categoriesCount', 99999);
expect(this.model.getCount()).toEqual(99999);
});
});
});
function _generateData (n) {
return _.times(n, function (i) {
return {
category: i,
value: 2
};
});
}
function _parseData (model, categories) {
model.sync = function (method, model, options) {
options.success({
'categories': categories
});
};
model.fetch();
}
@@ -0,0 +1,53 @@
var Backbone = require('backbone');
var CategoriesCollection = require('../../../../src/dataviews/category-dataview/categories-collection');
describe('categories-collection', function () {
var aggregationModel;
var collection;
beforeEach(function () {
aggregationModel = new Backbone.Model({
aggregation: 'count'
});
collection = new CategoriesCollection(null, {
aggregationModel: aggregationModel
});
});
describe('.reset', function () {
it('should NOT filter null values when categories are aggregated by "count"', function () {
collection.reset([{
name: 'foo',
value: 1
}, {
name: 'bar',
value: 10
}, {
name: 'wadus',
value: null
}]);
expect(collection.length).toBe(3);
expect(collection.pluck('name').sort()).toEqual([ 'foo', 'bar', 'wadus' ].sort());
expect(collection.pluck('value').sort()).toEqual([ 1, 10, null ].sort());
});
it('should filter null values when categories are NOT aggregated by "count"', function () {
aggregationModel.set({aggregation: 'avg'});
collection.reset([{
name: 'foo',
value: 1
}, {
name: 'bar',
value: 10
}, {
name: 'wadus',
value: null
}]);
expect(collection.length).toBe(2);
expect(collection.pluck('name').sort()).toEqual([ 'foo', 'bar' ].sort());
expect(collection.pluck('value').sort()).toEqual([ 1, 10 ].sort());
});
});
});
@@ -0,0 +1,670 @@
var _ = require('underscore');
var Backbone = require('backbone');
var VisModel = require('../../../src/vis/vis');
var MapModel = require('../../../src/geo/map');
var DataviewModelBase = require('../../../src/dataviews/dataview-model-base');
var WindshaftFiltersBoundingBox = require('../../../src/windshaft/filters/bounding-box');
var AnalysisService = require('../../../src/analysis/analysis-service');
var MapModelBoundingBoxAdapter = require('../../../src/geo/adapters/map-model-bounding-box-adapter');
var MockFactory = require('../../helpers/mockFactory');
var createEngine = require('../fixtures/engine.fixture.js');
var fakeCamshaftReference = {
getSourceNamesForAnalysisType: function (analysisType) {
var map = {
'source': [],
'trade-area': ['source'],
'estimated-population': ['source'],
'point-in-polygon': ['points_source', 'polygons_source'],
'union': ['source']
};
if (!map[analysisType]) {
throw new Error('analysis type ' + analysisType + ' not supported');
}
return map[analysisType];
},
getParamNamesForAnalysisType: function (analysisType) {
var map = {
'source': ['query'],
'trade-area': ['kind', 'time'],
'estimated-population': ['columnName'],
'point-in-polygon': [],
'union': ['join_on']
};
if (!map[analysisType]) {
throw new Error('analysis type ' + analysisType + ' not supported');
}
return map[analysisType];
}
};
describe('dataviews/dataview-model-base', function () {
var engineMock;
var apiKeyQueryParam;
beforeEach(function () {
this.map = new MapModel(null, {
layersFactory: {}
});
this.map.setBounds([[102, 200], [300, 400]]);
this.vis = new VisModel();
engineMock = createEngine();
this.vis._layersCollection = engineMock._layersCollection;
this.vis._dataviewsCollection = engineMock._dataviewsCollection;
apiKeyQueryParam = 'api_key=' + engineMock.getApiKey();
this.vis._onMapInstantiatedForTheFirstTime();
this.analysisService = new AnalysisService({
engine: engineMock,
camshaftReference: fakeCamshaftReference
});
this.source = this.analysisService.analyse({
id: 'a0',
type: 'source'
});
this.analysisNodes = this.source.getNodesCollection();
// Disable debounce
spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; });
this.model = new DataviewModelBase({
source: this.source
}, {
engine: engineMock,
bboxFilter: new WindshaftFiltersBoundingBox(new MapModelBoundingBoxAdapter(this.map))
});
this.model.toJSON = jasmine.createSpy('toJSON').and.returnValue({});
engineMock._dataviewsCollection.add(this.model);
this.model._bboxFilter._stopBinds();
this.model._bboxFilter._initBinds();
});
describe('url', function () {
it('should include the bbox param', function () {
this.map.setBounds([['south', 'west'], ['north', 'east']]);
this.model.set('url', 'http://example.com');
expect(this.model.url()).toEqual('http://example.com?bbox=west,south,east,north&' + apiKeyQueryParam);
});
it('should allow subclasses to define specific URL params', function () {
this.map.setBounds([['south', 'west'], ['north', 'east']]);
this.model.set('url', 'http://example.com');
spyOn(this.model, '_getDataviewSpecificURLParams').and.returnValue([ 'a=b', 'c=d' ]);
expect(this.model.url()).toEqual('http://example.com?bbox=west,south,east,north&a=b&c=d&' + apiKeyQueryParam);
});
it('should append an api_key param if apiKey attr is present (and not use the auth_token)', function () {
this.map.setBounds([['south', 'west'], ['north', 'east']]);
this.model.set({
url: 'http://example.com',
apiKey: 'THE_API_KEY',
authToken: 'THE_AUTH_TOKEN'
});
spyOn(this.model, '_getDataviewSpecificURLParams').and.returnValue([ 'a=b', 'c=d' ]);
expect(this.model.url()).toEqual('http://example.com?bbox=west,south,east,north&a=b&c=d&' + apiKeyQueryParam);
});
it('should append an auth_token param if authToken is present', function () {
this.map.setBounds([['south', 'west'], ['north', 'east']]);
this.model.set({ url: 'http://example.com' });
delete this.model._engine._windshaftSettings.apiKey;
spyOn(this.model, '_getDataviewSpecificURLParams').and.returnValue([ 'a=b', 'c=d' ]);
expect(this.model.url()).toEqual('http://example.com?bbox=west,south,east,north&a=b&c=d&auth_token[]=fabada&auth_token[]=coffee');
});
});
describe('when url changes', function () {
beforeEach(function () {
spyOn(this.model, 'fetch');
spyOn(this.model, 'listenTo');
spyOn(this.model, 'on');
});
describe('when map view bounds are ready', function () {
beforeEach(function () {
this.model.set('url', 'http://example.com');
});
it('should fetch', function () {
expect(this.model.fetch).toHaveBeenCalled();
});
describe('when fetch succeeds', function () {
beforeEach(function () {
this.model.fetch.calls.argsFor(0)[0].success();
});
it('should change bounds', function () {
expect(this.model.on.calls.argsFor(0)[0]).toEqual('change:sync_on_bbox_change');
expect(this.model.on.calls.argsFor(1)[0]).toEqual('change:url');
expect(this.model.on.calls.argsFor(2)[0]).toEqual('change:enabled');
});
});
});
describe('when map view bounds are NOT ready', function () {
beforeEach(function () {
spyOn(this.model._bboxFilter, 'areBoundsAvailable').and.returnValue(false);
});
describe('when sync_on_bbox_change is true', function () {
beforeEach(function () {
this.model.set({
'sync_on_bbox_change': true,
'url': 'http://example.com'
});
});
it('should wait until view bounds are ready', function () {
expect(this.model.fetch).not.toHaveBeenCalled();
this.map.setBounds([[5, 6], [7, 8]]);
expect(this.model.fetch).toHaveBeenCalled();
});
});
describe('when sync_on_bbox_change is false', function () {
beforeEach(function () {
this.model.set({
'sync_on_bbox_change': false,
'url': 'http://example.com'
});
});
it('should fetch', function () {
expect(this.model.fetch).toHaveBeenCalled();
});
});
});
});
describe('after first successful fetch', function () {
beforeEach(function () {
this.model.fetch = function (opts) {
opts.success();
};
this.model.set('url', 'newurl');
});
it('should not fetch new data when url changes and dataview is disabled', function () {
this.model.set('enabled', false);
spyOn(this.model, 'fetch');
this.model.trigger('change:url', this.model);
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should fetch if url changes and forceFetch option is true, no matter rest of variables', function () {
this.model.set('enabled', false);
spyOn(this.model, 'fetch');
this.model.trigger('change:url', this.model, {}, { forceFetch: true });
expect(this.model.fetch).toHaveBeenCalled();
this.model.fetch.calls.reset();
this.model.trigger('change:url', this.model, {}, { forceFetch: false });
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should fetch if url changes and sourceId is not defined', function () {
spyOn(this.model, 'fetch');
this.model.set('url', 'http://somethingelese.com');
expect(this.model.fetch).toHaveBeenCalled();
});
describe('when change:url has a sourceId option', function () {
beforeEach(function () {
var analysisA = this.analysisService.analyse({
id: 'a2',
type: 'estimated-population',
params: {
columnName: 'estimated_people',
source: {
id: 'a1',
type: 'trade-area',
params: {
kind: 'walk',
time: 300,
source: {
id: 'a0',
type: 'source',
params: {
query: 'select * from subway_stops'
}
}
}
}
}
});
var analysisNodes = analysisA.getNodesCollection();
this.model.set('source', analysisNodes.get('a1'), { silent: true });
spyOn(this.model, 'fetch');
});
it("should fetch if sourceId matches the dataview's source", function () {
this.model.set('url', 'http://somethingelese.com', {
sourceId: 'a1'
});
expect(this.model.fetch).toHaveBeenCalled();
});
it("should fetch if sourceId is a node that affects the dataview's source", function () {
this.model.set('url', 'http://somethingelese.com', {
sourceId: 'a0'
});
expect(this.model.fetch).toHaveBeenCalled();
});
it("should NOT fetch if sourceId is a node that doesn't affect the dataview's source", function () {
this.model.set('url', 'http://somethingelese.com', {
sourceId: 'a2'
});
expect(this.model.fetch).not.toHaveBeenCalled();
});
});
});
describe('when enabled is changed to true from false', function () {
beforeEach(function () {
this.model.fetch = function (opts) {
opts.success();
};
this.model.set('url', 'http://example.com');
this.model.set('enabled', false);
spyOn(this.model, 'fetch');
});
it('should NOT fetch if nothing has changed', function () {
this.model.set('enabled', true);
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should fetch if the bounding box have changed while the dataview was disabled', function () {
// Map bounds have changed
this.map.setBounds([[102, 200], [300, 400]]);
this.map.trigger('change:center');
this.model.set('enabled', true);
expect(this.model.fetch).toHaveBeenCalled();
this.model.fetch.calls.reset();
// Disable and enable again
this.model.set('enabled', false);
this.model.set('enabled', true);
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should NOT fetch if the bounding box have changed while the dataview was disabled and sync_on_bbox_change is disabled', function () {
this.model.set('sync_on_bbox_change', false);
// To get the full range of data
expect(this.model.fetch).toHaveBeenCalled();
this.model.fetch.calls.reset();
// Map bounds have changed
this.map.setBounds([[102, 200], [300, 400]]);
this.map.trigger('change:center');
this.model.set('enabled', true);
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should fetch if URL has changed while the dataview was disabled', function () {
this.model.set('url', 'http://somethingelse.com');
this.model.set('enabled', true);
expect(this.model.fetch).toHaveBeenCalled();
this.model.fetch.calls.reset();
// Disable and enable again
this.model.set('enabled', false);
this.model.set('enabled', true);
expect(this.model.fetch).not.toHaveBeenCalled();
});
});
describe('bindings to map bounds', function () {
beforeEach(function () {
this.model.fetch = function (opts) {
opts.success();
};
this.model.set('url', 'http://example.com');
spyOn(this.model, 'fetch');
});
it('should fetch when the bounding box has changed', function () {
this.map.setBounds([[102, 200], [300, 400]]);
this.map.trigger('change:center');
expect(this.model.fetch).toHaveBeenCalled();
});
it('should NOT fetch when the bounding box has changed and the dataview is not enabled', function () {
this.model.set('enabled', false);
this.map.setBounds([[102, 200], [300, 400]]);
this.map.trigger('change:center');
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should NOT fetch when the bounding box has changed and the dataview has sync_on_bbox_change disabled', function () {
this.model.set('sync_on_bbox_change', false);
// To get the full range of data
expect(this.model.fetch).toHaveBeenCalled();
this.model.fetch.calls.reset();
this.map.setBounds([[102, 200], [300, 400]]);
this.map.trigger('change:center');
expect(this.model.fetch).not.toHaveBeenCalled();
});
});
describe('bindings to the filter', function () {
it('should reload the map by default when the filter changes', function () {
var filter = new Backbone.Model();
new DataviewModelBase({ // eslint-disable-line
source: this.source
}, {
map: this.map,
engine: engineMock,
filter: filter
});
// Filter changes
filter.trigger('change', filter);
expect(engineMock.reload).toHaveBeenCalledWith({ sourceId: 'a0' });
});
});
describe('.remove', function () {
beforeEach(function () {
this.removeSpy = jasmine.createSpy('remove');
this.model.once('destroy', this.removeSpy);
spyOn(this.model, 'stopListening');
spyOn(this.source, 'off').and.callThrough();
this.model.filter = jasmine.createSpyObj('filter', ['remove', 'isEmpty']);
this.model.filter.isEmpty.and.returnValue(false);
this.model.remove();
});
it('should trigger a destroy event', function () {
expect(this.removeSpy).toHaveBeenCalledWith(this.model);
});
it('should stop listening to events', function () {
expect(this.model.stopListening).toHaveBeenCalled();
expect(this.source.off).toHaveBeenCalledWith('change:status', jasmine.any(Function), this.model);
});
});
describe('.update', function () {
it('should only update the attrs set on ATTRS_NAMES', function () {
this.model.update({ foo: 'bar' });
expect(this.model.changedAttributes()).toBe(false);
expect(this.model.get('sync_on_bbox_change')).toBe(true);
this.model.update({
sync_on_bbox_change: false,
foo: 'bar'
});
expect(this.model.changedAttributes()).toEqual({
sync_on_bbox_change: false
});
});
});
describe('.fetch', function () {
it('should trigger a loading event', function () {
spyOn(this.model, 'trigger');
this.model.fetch();
expect(this.model.trigger).toHaveBeenCalledWith('loading', this.model);
});
});
describe('getSourceType', function () {
it('should return the type of the source', function () {
var dataview = new DataviewModelBase({
source: this.analysisNodes.get('a0')
}, {
map: this.map,
engine: engineMock
});
expect(dataview.getSourceType()).toEqual('source');
});
});
describe('isSourceType', function () {
it('should return true if the source type is source', function () {
var dataview = new DataviewModelBase({
source: this.analysisNodes.get('a0')
}, {
map: this.map,
engine: engineMock
});
dataview.getSourceType = function () {
return 'source';
};
expect(dataview.isSourceType()).toBe(true);
});
});
describe('when source type is not source', function () {
describe('isSourceType', function () {
it('should return false', function () {
var dataview = new DataviewModelBase({
source: this.analysisNodes.get('a0')
}, {
map: this.map,
engine: engineMock
});
dataview.getSourceType = function () {
return 'sampling';
};
expect(dataview.isSourceType()).toBe(false);
});
});
});
describe('getSourceId', function () {
it('should return the id of the source', function () {
var dataview = new DataviewModelBase({
source: this.source
}, { // eslint-disable-line
map: this.map,
engine: engineMock
});
expect(dataview.getSourceId()).toEqual('a0');
});
});
describe('when analysis changes status', function () {
beforeEach(function () {
this.source.isLoading = jasmine.createSpy('a0.isLoading');
this.source.isFailed = jasmine.createSpy('a0.isFailed');
this.model.on({
loading: this.loadingSpy = jasmine.createSpy('loading'),
statusError: this.errorSpy = jasmine.createSpy('failed')
});
});
sharedTestsForAnalysisEvents();
describe('when changed source', function () {
beforeEach(function () {
this.model.set('source', this.source);
});
sharedTestsForAnalysisEvents();
});
});
describe('source references', function () {
var source;
var dataview;
beforeEach(function () {
source = MockFactory.createAnalysisModel({ id: 'a0' });
dataview = new DataviewModelBase({
source: source
}, {
map: this.map,
engine: engineMock
});
});
describe('when dataview is initialized', function () {
it('should mark source as referenced', function () {
expect(source.isSourceOf(dataview)).toBe(true);
});
});
describe('when dataview is removed', function () {
it('should unmark source as referenced', function () {
expect(source.isSourceOf(dataview)).toBe(true);
dataview.remove();
expect(source.isSourceOf(dataview)).toBe(false);
});
});
});
describe('._parseError', function () {
var source;
var dataview;
beforeEach(function () {
source = MockFactory.createAnalysisModel({ id: 'a0' });
dataview = new DataviewModelBase({
source: source
}, {
map: this.map,
engine: engineMock
});
});
it('should pass the response directly to the parser and get the first returned error', function () {
var response = {
responseJSON: {
errors: ['an error']
}
};
var error = dataview._parseError(response);
expect(error.message).toEqual('an error');
});
});
describe('._fetch', function () {
describe('when request is success', function () {
beforeEach(function () {
this.model.fetch = function (opts) {
opts.success();
};
});
it('sets _hasBinds to true', function () {
expect(this.model._hasBinds).toBe(false);
this.model._fetch();
expect(this.model._hasBinds).toBe(true);
});
it('calls ._onChangeBinds', function () {
spyOn(this.model, '_onChangeBinds');
this.model._fetch();
expect(this.model._onChangeBinds).toHaveBeenCalled();
});
});
});
describe('._onMapBoundsChanged', function () {
describe('when _shouldFetchOnBoundingBoxChange is true', function () {
it('calls ._fetch', function () {
spyOn(this.model, '_shouldFetchOnBoundingBoxChange').and.returnValue(true);
spyOn(this.model, '_fetch');
this.model._onMapBoundsChanged();
expect(this.model._fetch).toHaveBeenCalled();
});
});
});
});
function sharedTestsForAnalysisEvents () {
describe('should trigger the event according to state', function () {
it('should trigger loading event', function () {
this.loadingSpy.calls.reset();
this.errorSpy.calls.reset();
this.source.isLoading.and.returnValue(true);
this.source.set('status', 'whatever');
expect(this.loadingSpy).toHaveBeenCalled();
expect(this.errorSpy).not.toHaveBeenCalled();
this.loadingSpy.calls.reset();
this.errorSpy.calls.reset();
this.source.isLoading.and.returnValue(false);
this.source.isFailed.and.returnValue(true);
this.source.set({
status: 'failed',
error: this.err = {}
});
expect(this.loadingSpy).not.toHaveBeenCalled();
expect(this.errorSpy).toHaveBeenCalledWith(this.model, this.err);
});
});
}
@@ -0,0 +1,22 @@
var Backbone = require('backbone');
var DataviewModel = require('../../../src/dataviews/dataview-model-base');
var MockFactory = require('../../helpers/mockFactory');
describe('dataviews/dataview-collection', function () {
beforeEach(function () {
this.collection = new Backbone.Collection();
this.source = MockFactory.createAnalysisModel({ id: 'a0' });
});
it('should remove item when removed', function () {
var map = jasmine.createSpyObj('map', ['getViewBounds', 'off']);
map.getViewBounds.and.returnValue([[0, 0], [0, 0]]);
var engineMock = jasmine.createSpyObj('engine', ['reload']);
var dataviewModel = new DataviewModel({ source: this.source }, { map: map, engine: engineMock });
this.collection.add(dataviewModel);
expect(this.collection.length).toEqual(1);
this.collection.first().remove();
expect(this.collection.length).toEqual(0);
});
});
@@ -0,0 +1,85 @@
var _ = require('underscore');
var Backbone = require('backbone');
var DataviewsFactory = require('../../../src/dataviews/dataviews-factory');
var MockFactory = require('../../helpers/mockFactory');
var createEngine = require('../fixtures/engine.fixture.js');
var source = MockFactory.createAnalysisModel({ id: 'a0' });
var generateFakeAttributes = function (attrNames) {
return _.reduce(attrNames, function (object, attributeName) {
object[attributeName] = attributeName === 'source'
? source
: 'something';
return object;
}, {});
};
var createMapMock = function () {
var map = jasmine.createSpyObj('map', ['getViewBounds', 'on']);
map.getViewBounds.and.returnValue([[40.6, -3.5], [40.3, -3.8]]);
return map;
};
describe('dataviews/dataviews-factory', function () {
beforeEach(function () {
this.dataviewsCollection = new Backbone.Collection();
this.factory = new DataviewsFactory(null, {
map: createMapMock(),
engine: createEngine(),
dataviewsCollection: this.dataviewsCollection
});
});
it('should create the factory as expected', function () {
expect(this.factory).toBeDefined();
expect(this.factory.createCategoryModel).toEqual(jasmine.any(Function));
expect(this.factory.createFormulaModel).toEqual(jasmine.any(Function));
expect(this.factory.createHistogramModel).toEqual(jasmine.any(Function));
});
var FACTORY_METHODS_AND_REQUIRED_ATTRIBUTES = [
['createCategoryModel', ['source', 'column']],
['createFormulaModel', ['source', 'column', 'operation']],
['createHistogramModel', ['source', 'column']]
];
_.each(FACTORY_METHODS_AND_REQUIRED_ATTRIBUTES, function (element) {
var factoryMethod = element[0];
var requiredAttributes = element[1];
it(factoryMethod + ' should throw an error if required attributes are not set', function () {
expect(function () {
this.factory[factoryMethod]({});
}.bind(this)).toThrowError(requiredAttributes[0] + ' is required');
});
it(factoryMethod + ' should set the engine to get the apiKey attribute on the dataview', function () {
this.factory = new DataviewsFactory({}, {
map: createMapMock(),
engine: createEngine(),
dataviewsCollection: this.dataviewsCollection
});
var attributes = generateFakeAttributes(requiredAttributes);
var model = this.factory[factoryMethod](attributes);
expect(model._engine.getApiKey()).toEqual('API_KEY');
});
it(factoryMethod + ' should set the engine to get the authToken attribute on the dataview', function () {
var engine = createEngine({ apiKey: null });
this.factory = new DataviewsFactory({}, {
map: createMapMock(),
engine: engine,
dataviewsCollection: this.dataviewsCollection
});
var attributes = generateFakeAttributes(requiredAttributes);
var model = this.factory[factoryMethod](attributes);
expect(model._engine.getAuthToken()).toEqual(engine.getAuthToken());
});
}, this);
});
@@ -0,0 +1,140 @@
var Backbone = require('backbone');
var FormulaDataviewModel = require('../../../src/dataviews/formula-dataview-model.js');
var MockFactory = require('../../helpers/mockFactory');
var WindshaftFiltersBoundingBox = require('../../../src/windshaft/filters/bounding-box');
var WindshaftFiltersCircle = require('../../../src/windshaft/filters/circle');
var WindshaftFiltersPolygon = require('../../../src/windshaft/filters/polygon');
var MapModelBoundingBoxAdapter = require('../../../src/geo/adapters/map-model-bounding-box-adapter');
var createEngine = require('../fixtures/engine.fixture.js');
describe('dataviews/formula-dataview-model', function () {
var engineMock;
var apiKey = 'API_KEY';
var apiKeyQueryParam = 'api_key=' + apiKey;
beforeEach(function () {
this.map = new Backbone.Model();
this.map.getViewBounds = jasmine.createSpy();
engineMock = createEngine({ apiKey: apiKey });
this.map.getViewBounds.and.returnValue([[1, 2], [3, 4]]);
this.layer = new Backbone.Model();
this.source = MockFactory.createAnalysisModel({ id: 'a0' });
this.model = new FormulaDataviewModel({
source: this.source,
operation: 'min'
}, {
map: this.map,
engine: engineMock,
layer: this.layer,
bboxFilter: new WindshaftFiltersBoundingBox(new MapModelBoundingBoxAdapter(this.map))
});
});
it('should reload map and force fetch on operation change', function () {
engineMock.reload.calls.reset();
this.model.set('operation', 'avg');
expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' });
});
it('should reload map and force fetch on column change', function () {
engineMock.reload.calls.reset();
this.model.set('column', 'other_col');
expect(engineMock.reload).toHaveBeenCalledWith({ forceFetch: true, sourceId: 'a0' });
});
describe('.url', function () {
it('should include the bbox parameter', function () {
expect(this.model.set('url', 'http://example.com'));
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 FormulaDataviewModel({
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 FormulaDataviewModel({
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 FormulaDataviewModel({
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 FormulaDataviewModel({
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);
});
});
});
@@ -0,0 +1,110 @@
var helper = require('../../../../src/dataviews/helpers/histogram-helper');
describe('dataview/helpers/histogram-helper', function () {
describe('add', function () {
it('should add seconds properly', function () {
var actual = helper.add(12341234, 3, 'second');
var expected = 12341237;
expect(actual).toBe(expected);
});
it('should add minutes properly', function () {
var actual = helper.add(12341234, 21, 'minute');
var expected = 12342494;
expect(actual).toBe(expected);
});
it('should add hours properly', function () {
var actual = helper.add(12341234, 37, 'hour');
var expected = 12474434;
expect(actual).toBe(expected);
});
it('should add days properly', function () {
var actual = helper.add(12341234, 23, 'day');
var expected = 14328434;
expect(actual).toBe(expected);
});
it('should add weeks properly', function () {
var actual = helper.add(12341234, 11, 'week');
var expected = 18994034;
expect(actual).toBe(expected);
});
it('should add months properly', function () {
var actual = helper.add(12341234, 192, 'month');
var expected = 517262834;
expect(actual).toBe(expected);
});
it('should add quarters properly', function () {
var actual = helper.add(12341234, 17, 'quarter');
var expected = 146520434;
expect(actual).toBe(expected);
});
it('should add years properly', function () {
var actual = helper.add(12341234, 5, 'year');
var expected = 170107634;
expect(actual).toBe(expected);
});
it('should add decades properly', function () {
var actual = helper.add(12341234, 4, 'century');
var expected = 12635122034;
expect(actual).toBe(expected);
});
it('should add centuries properly', function () {
var actual = helper.add(12341234, 5, 'century');
var expected = 15790882034;
expect(actual).toBe(expected);
});
it('should add millenniums properly', function () {
var actual = helper.add(12341234, 2, 'millennium');
var expected = 63126245234;
expect(actual).toBe(expected);
});
it('should throw an error for a wrong aggregation', function () {
expect(function () {
helper.add(12341234, 1, 'wrong');
}).toThrowError('aggregation "wrong" is not defined');
});
});
describe('fillNumericBuckets', function () {
it('should set as last bucket end the max value', function () {
var buckets = [
{ min: 0, freq: 1 },
{},
{ max: 10, freq: 1 }
];
var start = 0;
var width = 3.3333333333333;
var numberOfBins = 3;
helper.fillNumericBuckets(buckets, start, width, numberOfBins);
expect(buckets[2].end).toBe(10);
});
it('should work fine for just one bucket', function () {
var buckets = [
{ min: 0, freq: 1 }
];
var start = 0;
var width = 1;
var numberOfBins = 3;
helper.fillNumericBuckets(buckets, start, width, numberOfBins);
expect(buckets[2].end).toBe(3);
});
it('should work fine for buckets with negative values', function () {
var buckets = [
{ min: -20, freq: 1 },
{},
{ min: -10, freq: 1 },
{ min: 0, freq: 1 },
{ min: 10, freq: 2 }
];
var start = -30;
var width = 15;
var numberOfBins = 4;
helper.fillNumericBuckets(buckets, start, width, numberOfBins);
expect(buckets[2].end).toBe(15);
});
});
});
@@ -0,0 +1,986 @@
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);
});
});
});
@@ -0,0 +1,167 @@
var _ = require('underscore');
var HistogramDataModel = require('../../../../src/dataviews/histogram-dataview/histogram-data-model');
describe('dataviews/histogram-data-model', function () {
var apiKey = 'ac3560ef-78f8-45d8-b043-5544f8a76753';
var url = 'https://carto.geo';
var defaultBins = 45;
function buildUrl (params) {
var urlParams = _.map(_.keys(params), function (key) {
return key + '=' + params[key];
});
return url + '?no_filters=1&' + urlParams.join('&');
}
beforeEach(function () {
this.model = new HistogramDataModel({
apiKey: apiKey,
url: url
});
});
it('defaults', function () {
expect(_.isArray(this.model.get('data'))).toBe(true);
expect(this.model.get('data').length).toBe(0);
expect(this.model.get('localTimezone')).toBe(false);
expect(this.model.get('localOffset')).toBe(0);
expect(this.model.get('hasBeenFetched')).toBe(false);
});
describe('._initBinds', function () {
beforeEach(function () {
spyOn(this.model, 'fetch');
});
afterEach(function () {
this.model.set({
url: url,
aggregation: undefined,
bins: defaultBins
}, { silent: true });
});
it('should call to fetch when the url changes', function () {
this.model.set('url', 'https://carto.geo/aa45');
expect(this.model.fetch).toHaveBeenCalled();
});
it('should call to fetch when the aggregation changes to a defined value in a date column', function () {
this.model.set('column_type', 'date', { silent: true });
this.model.set('aggregation', 'month');
expect(this.model.fetch).toHaveBeenCalled();
});
it('should not call to fetch when the aggregation changes to an undefined value in a date column', function () {
this.model.set('column_type', 'date', { silent: true });
this.model.set('aggregation', undefined);
expect(this.model.fetch).not.toHaveBeenCalled();
});
it('should call to fetch when the bins changes to a defined value in a number column', function () {
this.model.set('column_type', 'number', { silent: true });
this.model.set('bins', defaultBins + 1);
expect(this.model.fetch).toHaveBeenCalled();
});
it('should call to fetch when localTimezone changes', function () {
var originalValue = this.model.get('localTimezone');
this.model.set('localTimezone', !originalValue);
expect(this.model.fetch).toHaveBeenCalled();
});
it('should set `hasBeenFetched` to true when a sync event is triggered', function () {
this.model.set('hasBeenFetched', false, { silent: true });
this.model.trigger('sync');
expect(this.model.get('hasBeenFetched')).toBe(true);
});
});
describe('.url', function () {
it('should return no bins param if type is number and bins is undefined', function () {
this.model.set({ column_type: 'number' });
var url = this.model.url();
expect(url).toEqual(buildUrl({ api_key: apiKey }));
});
it('should return bins param if type is number and bins is defined', function () {
this.model.set({
column_type: 'number',
bins: 48
});
var url = this.model.url();
expect(url).toEqual(buildUrl({
bins: 48,
api_key: apiKey
}));
});
it('should return aggregation auto if type is date and aggregation is undefined', function () {
this.model.set({
column_type: 'date',
aggregation: undefined
});
var url = this.model.url();
expect(url).toEqual(buildUrl({
aggregation: 'auto',
api_key: apiKey
}));
});
it('should return aggregation if type is date and aggregation is defined', function () {
this.model.set({
column_type: 'date',
aggregation: 'minute'
});
var url = this.model.url();
expect(url).toEqual(buildUrl({
aggregation: 'minute',
api_key: apiKey
}));
});
});
describe('._getCurrentOffset', function () {
beforeEach(function () {
this.model.set({
offset: 7200,
localOffset: 43200
}, { silent: true });
});
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);
});
});
});