Initial commit
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user