Initial commit

This commit is contained in:
zhongjin
2020-06-13 18:34:34 +08:00
commit 52aaa9f15d
655 changed files with 96796 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
// Catch any eventual errors that happens when test suite is setup, and re-throw once the test runner is ready
var _ = require('underscore');
var orgOnError = window.onerror;
var onErrorArguments = [];
window.onerror = function () {
onErrorArguments.push(arguments);
if (_.isFunction(orgOnError)) {
return orgOnError.apply(window, arguments);
}
};
describe('errors thrown when loading src files', function () {
it('should never ever happen', function () {
onErrorArguments.forEach(function (args) {
// args = {0: errorMsg, 1: srcFilepath, 2: column, 3: row, 4: error}
throw args[4]; // actual err
});
expect(onErrorArguments).toEqual([]);
});
});

View File

@@ -0,0 +1 @@
__ENV__ = 'test';

View File

@@ -0,0 +1,59 @@
var _ = require('underscore');
var VisModel = require('../../src/vis/vis');
var AnalysisModel = require('../../src/analysis/analysis-model');
// We use a "fake" reference instead of the one in src/analysis/camshaft-reference
// to ensure that tests won't break if the real thing changes
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];
}
};
var createAnalysisModel = function (attrs) {
if (!_.has(attrs, 'type')) {
attrs.type = 'source';
}
var model = new AnalysisModel(attrs, {
camshaftReference: fakeCamshaftReference,
engine: {
reload: function () {}
}
});
return model;
};
function createVisModel () {
return new VisModel();
}
module.exports = {
createAnalysisModel: createAnalysisModel,
createVisModel: createVisModel
};

View File

@@ -0,0 +1,6 @@
/**
* See https://github.com/evanw/node-source-map-support#browser-support
* This is expected to be included in a browserify-module to give proper stack traces, based on browserify's source maps.
*/
/* global sourceMapSupport */
sourceMapSupport.install();

View File

@@ -0,0 +1,516 @@
var _ = require('underscore');
var Backbone = require('backbone');
var AnalysisModel = require('../../../src/analysis/analysis-model.js');
var AnalysisService = require('../../../src/analysis/analysis-service.js');
var fakeCamshaftReference = {
getSourceNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['source1', 'source2'],
'trade-area': ['source'],
'estimated-population': ['source'],
'sql-function': ['source', 'target']
};
return map[analysisType];
},
getParamNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['attribute1', 'attribute2'],
'trade-area': ['kind', 'time'],
'estimated-population': ['columnName']
};
return map[analysisType];
}
};
var createFakeEngine = function () {
var engine = new Backbone.Model();
engine.reload = jasmine.createSpy('reload');
return engine;
};
var createFakeAnalysis = function (attrs, engine) {
return new AnalysisModel(attrs, {
engine: engine,
camshaftReference: fakeCamshaftReference
});
};
describe('src/analysis/analysis-model.js', function () {
var engineMock;
beforeEach(function () {
engineMock = createFakeEngine();
this.analysisModel = createFakeAnalysis({
type: 'analysis-type-1',
attribute1: 'value1',
attribute2: 'value2'
}, engineMock);
});
describe('.url', function () {
it('should append the api_key param if present (and not use the authToken)', function () {
this.analysisModel.set({
url: 'http://example.com',
apiKey: 'THE_API_KEY',
authToken: 'THE_AUTH_TOKEN'
});
expect(this.analysisModel.url()).toEqual('http://example.com?api_key=THE_API_KEY');
});
it('should append the auth_token param if present (and not use the authToken)', function () {
this.analysisModel.set({
url: 'http://example.com',
authToken: 'THE_AUTH_TOKEN'
});
expect(this.analysisModel.url()).toEqual('http://example.com?auth_token=THE_AUTH_TOKEN');
});
});
describe('bindings', function () {
describe('on params change', function () {
it('should reload the map', function () {
this.analysisModel.set({
attribute1: 'newValue1'
});
expect(engineMock.reload).toHaveBeenCalled();
engineMock.reload.calls.reset();
this.analysisModel.set({
attribute2: 'newValue2'
});
expect(engineMock.reload).toHaveBeenCalled();
engineMock.reload.calls.reset();
this.analysisModel.set({
attribute900: 'something'
});
expect(engineMock.reload).not.toHaveBeenCalled();
});
it('should be marked as failed if request to reload the map fails', function () {
this.analysisModel.set({
attribute1: 'newValue1',
status: AnalysisModel.STATUS.READY
});
// Request to the Maps API fails and error callback is invoked...
engineMock.reload.calls.argsFor(0)[0].error('something bad just happened');
expect(this.analysisModel.get('status')).toEqual(AnalysisModel.STATUS.FAILED);
});
});
describe('on type change', function () {
it('should unbind old params and bind new params', function () {
spyOn(this.analysisModel, '_initBinds').and.callThrough();
spyOn(this.analysisModel, 'unbind').and.callThrough();
this.analysisModel.set('type', 'new!');
expect(this.analysisModel.unbind).toHaveBeenCalled();
expect(this.analysisModel._initBinds).toHaveBeenCalled();
});
it('should reload the map', function () {
this.analysisModel.set('type', 'something');
expect(engineMock.reload).toHaveBeenCalled();
});
it('should keep listening type change again', function () {
this.analysisModel.set('type', 'something');
expect(engineMock.reload).toHaveBeenCalled();
engineMock.reload.calls.reset();
this.analysisModel.set('type', 'something else');
expect(engineMock.reload).toHaveBeenCalled();
});
});
describe('on status change', function () {
var createAnalysisModelNoStatusNoReferences = function (engine) {
var analysisModel = createFakeAnalysis({ id: 'a0' }, engine);
return analysisModel;
};
var createAnalysisModelNoStatusWithReferences = function (engine) {
var analysisModel = createFakeAnalysis({ id: 'a0' }, engine);
analysisModel.markAsSourceOf(new Backbone.Model());
return analysisModel;
};
var createAnalysisModelWithStatusNoReferences = function (engine) {
var analysisModel = createFakeAnalysis({ id: 'a0', status: 'foo' }, engine);
return analysisModel;
};
var createAnalysisModelWithStatusWithReferences = function (engine) {
var analysisModel = createFakeAnalysis({ id: 'a0', status: 'foo' }, engine);
analysisModel.markAsSourceOf(new Backbone.Model());
return analysisModel;
};
var testCases = [
{
testName: 'analysis with no previous status and no references',
createAnalysisFn: createAnalysisModelNoStatusNoReferences,
expectedVisReloadWhenStatusIn: [] // no relaod is expected
},
{
testName: 'analysis with no previous status and some references',
createAnalysisFn: createAnalysisModelNoStatusWithReferences,
expectedVisReloadWhenStatusIn: [] // no reload is expected
},
{
testName: 'analysis with previous status and no references',
createAnalysisFn: createAnalysisModelWithStatusNoReferences,
expectedVisReloadWhenStatusIn: [] // no reload is expected
},
{
testName: 'analysis with previous status and references',
createAnalysisFn: createAnalysisModelWithStatusWithReferences,
expectedVisReloadWhenStatusIn: [ AnalysisModel.STATUS.READY ]
}
];
_.forEach(testCases, function (testCase) {
var testName = testCase.testName;
var createAnalysisFn = testCase.createAnalysisFn;
var expectedVisReloadWhenStatusIn = testCase.expectedVisReloadWhenStatusIn;
var notExpectedVisReloadWhenStatusIn = [];
describe(testName, function () {
var analysisModel;
var engineMock;
beforeEach(function () {
engineMock = createFakeEngine();
analysisModel = createAnalysisFn(engineMock);
});
_.forEach(AnalysisModel.STATUS, function (status) {
if (expectedVisReloadWhenStatusIn.indexOf(status) < 0) {
notExpectedVisReloadWhenStatusIn.push(status);
}
});
_.each(expectedVisReloadWhenStatusIn, function (status) {
it("should reload the engine if analysis is now '" + status + "'", function () {
expect(engineMock.reload).not.toHaveBeenCalled();
analysisModel.set('status', status);
expect(engineMock.reload).toHaveBeenCalled();
});
}, this);
_.each(notExpectedVisReloadWhenStatusIn, function (status) {
it("should NOT reload the engine if analysis is now '" + status + "'", function () {
expect(engineMock.reload).not.toHaveBeenCalled();
analysisModel.set('status', status);
expect(engineMock.reload).not.toHaveBeenCalled();
});
}, this);
});
});
});
});
describe('.findAnalysisById', function () {
it('should find a node in the graph', function () {
var fakeCamshaftReference = {
getSourceNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['source1', 'source2'],
'analysis-type-2': [],
'analysis-type-3': ['source3'],
'analysis-type-4': [],
'analysis-type-5': ['source4', 'source5']
};
return map[analysisType];
},
getParamNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['a'],
'analysis-type-2': [],
'analysis-type-3': [],
'analysis-type-4': ['a4'],
'analysis-type-5': []
};
return map[analysisType];
}
};
var analysisService = new AnalysisService({
engine: engineMock,
camshaftReference: fakeCamshaftReference
});
var analysisModel = analysisService.analyse({
id: 'a1',
type: 'analysis-type-1',
params: {
a: 1,
source1: {
id: 'a2',
type: 'analysis-type-2',
params: {
a2: 2
}
},
source2: {
id: 'a3',
type: 'analysis-type-3',
params: {
source3: {
id: 'a5',
type: 'analysis-type-5',
params: {
source4: {
id: 'a4',
type: 'analysis-type-4',
params: {
a4: 4
}
}
}
}
}
}
}
});
expect(analysisModel.findAnalysisById('a1')).toEqual(analysisModel);
expect(analysisModel.findAnalysisById('a2').get('id')).toEqual('a2');
expect(analysisModel.findAnalysisById('a3').get('id')).toEqual('a3');
expect(analysisModel.findAnalysisById('a5').get('id')).toEqual('a5');
expect(analysisModel.findAnalysisById('b9')).toBeUndefined();
});
});
describe('.toJSON', function () {
it('should serialize the graph', function () {
var fakeCamshaftReference = {
getSourceNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['source1', 'source2'],
'analysis-type-2': [],
'analysis-type-3': ['source3'],
'analysis-type-4': [],
'analysis-type-5': ['source4', 'source5']
};
return map[analysisType];
},
getParamNamesForAnalysisType: function (analysisType) {
var map = {
'analysis-type-1': ['a'],
'analysis-type-2': ['a2'],
'analysis-type-3': [],
'analysis-type-4': ['a4'],
'analysis-type-5': []
};
return map[analysisType];
},
isSourceNameOptionalForAnalysisType: function (analysisType, sourceName) {
return (analysisType === 'analysis-type-5' && sourceName === 'source5');
}
};
var analysisService = new AnalysisService({
engine: engineMock,
camshaftReference: fakeCamshaftReference
});
var analysisModel = analysisService.analyse({
id: 'a1',
type: 'analysis-type-1',
params: {
a: 1,
source1: {
id: 'a2',
type: 'analysis-type-2',
params: {
a2: 2
}
},
source2: {
id: 'a3',
type: 'analysis-type-3',
params: {
source3: {
id: 'a4',
type: 'analysis-type-4',
params: {
a4: {
id: 'a5',
type: 'analysis-type-5',
params: {
source4: {
id: 'a6',
type: 'analysis-type-2',
params: {
a2: 2
}
}
}
}
}
}
}
}
}
});
expect(analysisModel.toJSON()).toEqual({
id: 'a1',
type: 'analysis-type-1',
params: {
a: 1,
source1: {
id: 'a2',
type: 'analysis-type-2',
params: {
a2: 2
}
},
source2: {
id: 'a3',
type: 'analysis-type-3',
params: {
source3: {
id: 'a4',
type: 'analysis-type-4',
params: {
a4: {
id: 'a5',
type: 'analysis-type-5',
params: {
source4: {
id: 'a6',
type: 'analysis-type-2',
params: {
a2: 2
}
}
}
}
}
}
}
}
}
});
});
});
describe('.isDone', function () {
it('should return true if analysis has been calculated', function () {
this.analysisModel.set('status', AnalysisModel.STATUS.READY);
expect(this.analysisModel.isDone()).toEqual(true);
this.analysisModel.set('status', AnalysisModel.STATUS.FAILED);
expect(this.analysisModel.isDone()).toEqual(true);
});
it('should return false if analysis has NOT been calculated', function () {
this.analysisModel.set('status', AnalysisModel.STATUS.PENDING);
expect(this.analysisModel.isDone()).toEqual(false);
this.analysisModel.set('status', AnalysisModel.STATUS.WAITING);
expect(this.analysisModel.isDone()).toEqual(false);
this.analysisModel.set('status', AnalysisModel.STATUS.RUNNING);
expect(this.analysisModel.isDone()).toEqual(false);
});
});
describe('.setOk', function () {
it('should unset error attribute', function () {
this.analysisModel.set('error', 'error');
this.analysisModel.setOk();
expect(this.analysisModel.get('error')).toBeUndefined();
});
});
describe('.setError', function () {
it('should set error attribute', function () {
this.analysisModel.setError('wadus');
expect(this.analysisModel.get('error')).toEqual('wadus');
});
it('should set analyis as failed', function () {
this.analysisModel.setError('wadus');
expect(this.analysisModel.get('status')).toEqual(AnalysisModel.STATUS.FAILED);
});
});
describe('.getNodes', function () {
var analysisService;
beforeEach(function () {
analysisService = new AnalysisService({
engine: engineMock,
camshaftReference: fakeCamshaftReference
});
});
it('Should return a list of nodes from an analysis', function () {
var analysis = 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 actual = analysis.getNodes();
expect(actual.length).toEqual(3);
});
});
describe('references tracking', function () {
it('should allow keeping track of models that reference this object', function () {
var model1 = new Backbone.Model();
var model2 = new Backbone.Model();
expect(this.analysisModel.isSourceOfAnyModel()).toBe(false);
this.analysisModel.markAsSourceOf(model1);
expect(this.analysisModel.isSourceOfAnyModel()).toBe(true);
this.analysisModel.markAsSourceOf(model1);
expect(this.analysisModel.isSourceOfAnyModel()).toBe(true);
this.analysisModel.markAsSourceOf(model2);
expect(this.analysisModel.isSourceOfAnyModel()).toBe(true);
this.analysisModel.unmarkAsSourceOf(model1);
expect(this.analysisModel.isSourceOfAnyModel()).toBe(true);
this.analysisModel.unmarkAsSourceOf(model2);
expect(this.analysisModel.isSourceOfAnyModel()).toBe(false);
});
});
});

View File

@@ -0,0 +1,105 @@
var _ = require('underscore');
var Backbone = require('backbone');
var AnalysisModel = require('../../../src/analysis/analysis-model');
var AnalysisPoller = require('../../../src/analysis/analysis-poller');
describe('src/analysis/analysis-poller', function () {
beforeEach(function () {
jasmine.clock().install();
var engineMock = new Backbone.Model();
this.reference = jasmine.createSpyObj('reference', ['getParamNamesForAnalysisType']);
this.analysisModel1 = new AnalysisModel({
id: 'a1',
url: 'http://carto.com/foo/bar'
}, { engine: engineMock, camshaftReference: this.reference });
this.analysisPoller = new AnalysisPoller();
});
afterEach(function () {
jasmine.clock().uninstall();
});
describe('.resetAnalysisNodes', function () {
_.each([AnalysisModel.STATUS.PENDING, AnalysisModel.STATUS.WAITING, AnalysisModel.STATUS.RUNNING], function (status) {
it('should start polling if status of an analysis is "' + status + '"', function () {
this.analysisModel1.set({
'status': status
});
spyOn(this.analysisModel1, 'fetch').and.callFake(function (options) {
options.success();
});
this.analysisPoller.resetAnalysisNodes([ this.analysisModel1 ]);
expect(this.analysisModel1.fetch).toHaveBeenCalled();
expect(this.analysisModel1.fetch.calls.count()).toEqual(1);
// Wait until next fetch is triggered
jasmine.clock().tick(AnalysisPoller.CONFIG.START_DELAY + 1);
expect(this.analysisModel1.fetch.calls.count()).toEqual(2);
// Wait until next fetch is triggered
jasmine.clock().tick(AnalysisPoller.CONFIG.START_DELAY * AnalysisPoller.CONFIG.DELAY_MULTIPLIER + 1);
expect(this.analysisModel1.fetch.calls.count()).toEqual(3);
});
});
_.each([AnalysisModel.STATUS.READY, AnalysisModel.STATUS.FAILED], function (newStatus) {
it('should stop polling if status of an analysis changes to "' + newStatus + '"', function () {
spyOn(this.analysisModel1, 'fetch').and.callFake(function (options) {
this.analysisModel1.set('status', newStatus, { silent: true });
options.success();
}.bind(this));
this.analysisModel1.set({
'status': 'pending'
});
this.analysisPoller.resetAnalysisNodes([ this.analysisModel1 ]);
expect(this.analysisModel1.fetch).toHaveBeenCalled();
expect(this.analysisModel1.fetch.calls.count()).toEqual(1);
// Wait until next fetch is triggered
jasmine.clock().tick(AnalysisPoller.CONFIG.START_DELAY + 1);
expect(this.analysisModel1.fetch.calls.count()).toEqual(1);
});
});
});
describe('.reset', function () {
it('should reset all pollers', function () {
this.analysisModel1.set({
'status': 'pending'
});
spyOn(this.analysisModel1, 'fetch').and.callFake(function (options) {
options.success();
});
this.analysisPoller.resetAnalysisNodes([ this.analysisModel1 ]);
expect(this.analysisModel1.fetch).toHaveBeenCalled();
expect(this.analysisModel1.fetch.calls.count()).toEqual(1);
// Wait until next fetch is triggered
jasmine.clock().tick(AnalysisPoller.CONFIG.START_DELAY + 1);
// Polling is working
expect(this.analysisModel1.fetch.calls.count()).toEqual(2);
this.analysisPoller.reset();
// Wait until next fetch is supposed to be triggered
jasmine.clock().tick(AnalysisPoller.CONFIG.START_DELAY * AnalysisPoller.CONFIG.DELAY_MULTIPLIER + 1);
// Polling has been stopped
expect(this.analysisModel1.fetch.calls.count()).toEqual(2);
});
});
});

View File

@@ -0,0 +1,357 @@
var Backbone = require('backbone');
var _ = require('underscore');
var AnalysisService = require('../../../src/analysis/analysis-service');
var CartoDBLayer = require('../../../src/geo/map/cartodb-layer');
var Dataview = require('../../../src/dataviews/dataview-model-base');
describe('src/analysis/analysis-service.js', function () {
var engineMock = new Backbone.Model();
var fakeCamshaftReference = {
getSourceNamesForAnalysisType: function (analysisType) {
var map = {
'trade-area': ['source'],
'estimated-population': ['source'],
'sql-function': ['source', 'target']
};
return map[analysisType];
},
getParamNamesForAnalysisType: function (analysisType) {
var map = {
'trade-area': ['kind', 'time'],
'estimated-population': ['columnName']
};
return map[analysisType];
}
};
beforeEach(function () {
this.analysisService = new AnalysisService({
engine: engineMock,
camshaftReference: fakeCamshaftReference
});
});
describe('.analyse', function () {
it('should generate and return a new analysis', function () {
var subwayStops = this.analysisService.analyse({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
expect(subwayStops.attributes).toEqual({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
});
it('should set attrs on the analysis models', function () {
var analysisService = new AnalysisService({
engine: new Backbone.Model(),
apiKey: 'THE_API_KEY',
authToken: 'THE_AUTH_TOKEN',
camshaftReference: fakeCamshaftReference
});
var analysisModel = analysisService.analyse({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
expect(analysisModel.get('apiKey')).toEqual('THE_API_KEY');
expect(analysisModel.get('authToken')).toEqual('THE_AUTH_TOKEN');
});
it('should recursively build the analysis graph', function () {
var estimatedPopulation = 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 tradeArea = estimatedPopulation.get('source');
var subwayStops = tradeArea.get('source');
expect(tradeArea.get('id')).toEqual('a1');
expect(subwayStops.get('id')).toEqual('a0');
});
it('analysis should be re-created after it has been removed', function () {
var subwayStops1 = this.analysisService.analyse({
id: 'a0',
type: 'source',
params: {
query: 'SELECT * FROM subway_stops'
}
});
subwayStops1.remove();
var subwayStops2 = this.analysisService.analyse({
id: 'a0',
type: 'source',
params: {
query: 'SELECT * FROM subway_stops '
}
});
expect(subwayStops1.cid).not.toEqual(subwayStops2.cid);
});
});
describe('.findNodeById', function () {
it('should traverse the analysis and return an existing node', 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 analysisANodes = analysisA.getNodesCollection();
var analysisB = this.analysisService.analyse(
{
id: 'b0',
type: 'source',
params: {
query: 'SELECT * FROM bus_stops'
}
}
);
// This specs make easy to know what went wrong when the test fails
expect(this.analysisService.findNodeById('a2').get('id')).toBe('a2');
expect(this.analysisService.findNodeById('a1').get('id')).toBe('a1');
expect(this.analysisService.findNodeById('a0').get('id')).toBe('a0');
expect(this.analysisService.findNodeById('b0').get('id')).toBe('b0');
expect(this.analysisService.findNodeById('a2')).toBe(analysisANodes.get('a2'));
expect(this.analysisService.findNodeById('a1')).toBe(analysisANodes.get('a1'));
expect(this.analysisService.findNodeById('a0')).toBe(analysisANodes.get('a0'));
expect(this.analysisService.findNodeById('b0')).toBe(analysisB);
expect(this.analysisService.findNodeById('c0')).toBeUndefined();
});
it('should return undefined if node is not found', function () {
pending('Included in previous tests. TODO: create new test for this');
});
});
describe('._getAnalysisAttributesFromAnalysisDefinition', function () {
it('should analyse all source nodes if everyone has params', function () {
var analysisDefinition = {
type: 'trade-area',
params: {
source: 'a0'
}
};
spyOn(this.analysisService, 'analyse').and.returnValue('node');
var result = this.analysisService._getAnalysisAttributesFromAnalysisDefinition(analysisDefinition, this.analysisService.analyse.bind(this));
expect(this.analysisService.analyse.calls.count()).toEqual(1);
expect(this.analysisService.analyse).toHaveBeenCalledWith('a0');
expect(result).toEqual({
type: 'trade-area',
source: 'node'
});
});
it('should analyse only source nodes that has params', function () {
var analysisDefinition = {
type: 'sql-function',
params: {
source: 'a0'
}
};
spyOn(this.analysisService, 'analyse').and.returnValue('node');
var result = this.analysisService._getAnalysisAttributesFromAnalysisDefinition(analysisDefinition, this.analysisService.analyse.bind(this));
expect(this.analysisService.analyse.calls.count()).toEqual(1);
expect(this.analysisService.analyse).toHaveBeenCalledWith('a0');
expect(result).toEqual({
type: 'sql-function',
source: 'node'
});
});
});
describe('.getUniqueAnalysisNodes', function () {
it('should return the analysis nodes: (single analysis node in a single layer)', function () {
var analysis = this.analysisService.analyse({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
var layer = new CartoDBLayer({ source: analysis }, { engine: engineMock });
var layersCollection = new Backbone.Collection([layer]);
var dataviewsCollection = new Backbone.Collection();
var expected = analysis;
var actual = AnalysisService.getUniqueAnalysisNodes(layersCollection, dataviewsCollection);
expect(actual[0]).toEqual(expected);
});
it('should return the analysis nodes: (2 analysis nodes, 1 dataview, 1 layer)', function () {
var analysis0 = this.analysisService.analyse({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
var analysis1 = this.analysisService.analyse({
id: 'a1',
type: 'source',
query: 'SELECT * FROM bus_stops'
});
var layer = new CartoDBLayer({ source: analysis0 }, { engine: engineMock });
var dataview = new Dataview({ id: 'dataview1', source: analysis1 }, { map: {}, engine: engineMock });
var layersCollection = new Backbone.Collection([layer]);
var dataviewsCollection = new Backbone.Collection([dataview]);
var expected = [analysis0, analysis1];
var actual = AnalysisService.getUniqueAnalysisNodes(layersCollection, dataviewsCollection);
expect(actual.length).toEqual(expected.length);
expect(actual).toEqual(expected);
});
it('should return the analysis nodes: (2 analysis nodes, 1 dataview, 1 layer)', function () {
var analysis0 = this.analysisService.analyse({
id: 'a0',
type: 'source',
query: 'SELECT * FROM subway_stops'
});
var analysis1 = this.analysisService.analyse({
id: 'a1',
type: 'source',
query: 'SELECT * FROM bus_stops'
});
var layer = new CartoDBLayer({ source: analysis0 }, { engine: engineMock });
var dataview = new Dataview({ id: 'dataview1', source: analysis1 }, { map: {}, engine: engineMock });
var layersCollection = new Backbone.Collection([layer]);
var dataviewsCollection = new Backbone.Collection([dataview]);
var expected = [analysis0, analysis1];
var actual = AnalysisService.getUniqueAnalysisNodes(layersCollection, dataviewsCollection);
expect(actual.length).toEqual(expected.length);
expect(actual).toEqual(expected);
});
it('Should return the analysis nodes: (3 analysis nodes, 1 dataview, 2 layers)', 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();
var analysis0 = analysisNodes.get('a0');
var analysis1 = analysisNodes.get('a1');
var analysis2 = analysisNodes.get('a2');
var layer0 = new CartoDBLayer({ source: analysis0 }, { engine: engineMock });
var layer1 = new CartoDBLayer({ source: analysis2 }, { engine: engineMock });
var dataview = new Dataview({ id: 'dataview1', source: analysis1 }, { map: {}, engine: engineMock });
var layersCollection = new Backbone.Collection([layer0, layer1]);
var dataviewsCollection = new Backbone.Collection([dataview]);
var expected = [analysis0, analysis2, analysis1];
var actual = AnalysisService.getUniqueAnalysisNodes(layersCollection, dataviewsCollection);
// This specs make easy to know what went wrong when the test fails.
expect(actual.length).toEqual(expected.length);
expect(actual[0].id).toEqual(expected[0].id);
expect(actual[1].id).toEqual(expected[1].id);
expect(actual[2].id).toEqual(expected[2].id);
expect(actual).toEqual(expected);
});
it('should compact layers and dataviews if they do not have sources. It happens in named maps.', function () {
var analysis = this.analysisService.analyse({
id: 'a0',
type: 'source',
params: {
query: 'SELECT * FROM subway_stops'
}
});
var layer = new CartoDBLayer({ source: analysis }, { engine: engineMock });
var dataview = new Dataview({ id: 'dataview1', source: analysis }, { map: {}, engine: engineMock });
layer.set('source', undefined, { silent: true });
dataview.set('source', undefined, { silent: true });
var layersCollection = new Backbone.Collection([layer]);
var dataviewsCollection = new Backbone.Collection([dataview]);
var nodes = AnalysisService.getUniqueAnalysisNodes(layersCollection, dataviewsCollection);
expect(_.isArray(nodes)).toBe(true);
expect(nodes.length).toBe(0);
});
});
});

View File

@@ -0,0 +1,19 @@
var camshaftReference = require('../../../src/analysis/camshaft-reference');
describe('src/analysis/camshaft-reference', function () {
describe('.getSourceNamesForAnalysisType', function () {
it('should return the source names for a given analyses type', function () {
expect(camshaftReference.getSourceNamesForAnalysisType('source')).toEqual([]);
expect(camshaftReference.getSourceNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
expect(camshaftReference.getSourceNamesForAnalysisType('trade-area')).toEqual(['source']);
});
});
describe('.getParamNamesForAnalysisType', function () {
it('should return the params names for a given analyses type', function () {
expect(camshaftReference.getParamNamesForAnalysisType('source')).toEqual(['query']);
expect(camshaftReference.getParamNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
expect(camshaftReference.getParamNamesForAnalysisType('trade-area')).toEqual([ 'source', 'kind', 'time', 'isolines', 'dissolved' ]);
});
});
});

View File

@@ -0,0 +1,247 @@
var $ = require('jquery');
var createVis = require('../../../../src/api/create-vis');
var scenarios = require('./scenarios');
var Loader = require('../../../../src/core/loader');
describe('create-vis:', function () {
beforeEach(function () {
this.container = $('<div id="map">').css('height', '200px');
this.containerId = this.container[0].id;
$('body').append(this.container);
});
afterEach(function () {
this.container.remove();
});
it('should throw errors when required parameters are missing', function () {
expect(function () {
createVis();
}).toThrowError('a valid DOM element or selector must be provided');
expect(function () {
createVis('something');
}).toThrowError('a valid DOM element or selector must be provided');
expect(function () {
createVis(this.containerId);
}.bind(this)).toThrowError('a vizjson URL or object must be provided');
expect(function () {
createVis(this.container[0], 'vizjson');
}.bind(this)).not.toThrowError();
expect(function () {
createVis(this.containerId, 'vizjson');
}.bind(this)).not.toThrowError();
});
it('should use the given vis.json (instead downloading) when the visjson parameter is provided', function () {
spyOn(Loader, 'get');
var visJson = scenarios.load('basic');
createVis(this.containerId, visJson);
expect(Loader.get).not.toHaveBeenCalled();
});
it('should download the vizjson file from a URL when the visjson parameter is provided and is a string', function () {
spyOn(Loader, 'get');
createVis(this.containerId, 'www.example.com/fake_vis.json');
expect(Loader.get).toHaveBeenCalledWith('www.example.com/fake_vis.json', jasmine.any(Function));
});
describe('Default (no Options)', function () {
it('should get the map center from the visJson', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('center')).toEqual(visJson.center);
});
it('should get the title from the visJson', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.get('title')).toEqual(visJson.title);
});
it('should get the description from the visJson', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.get('description')).toEqual(visJson.description);
});
it('should initialize the right protocol (https:false)', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.get('https')).toEqual(false);
});
it('should not have interactive features by default', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.get('interactiveFeatures')).toEqual(false);
});
it('should display the "loader" overlay by default [loaderControl, tiles_loader]', function () {
// loaderControl and tiles_loader appear to do the same.
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'loader' })).toBeDefined();
});
it('should display the "logo" by default', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'logo' })).toBeDefined();
});
it('should not display empty infowindow fields by default', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.get('showEmptyInfowindowFields')).toEqual(false);
});
it('should have legends', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.settings.get('showLegends')).toEqual(true);
});
it('should show layer selector', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.settings.get('showLayerSelector')).toEqual(true);
expect(visModel.settings.get('layerSelectorEnabled')).toEqual(true);
});
it('should allow scrollwheel by default', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('scrollwheel')).toEqual(true);
});
});
describe('Options', function () {
describe('skipMapInstantiation', function () {
it('should instantiate map when skipMapInstantiation option is falsy', function (done) {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
setTimeout(function () {
expect(visModel._instantiateMapWasCalled).toEqual(true);
done();
}, 25);
});
it('should NOT instantiate map when skipMapInstantiation option is truthy', function (done) {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson, { skipMapInstantiation: true });
setTimeout(function () {
expect(visModel._instantiateMapWasCalled).toEqual(false);
done();
}, 25);
});
});
});
describe('VisModel.map', function () {
it('should have the right title', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('title')).toEqual(visJson.title);
});
it('should have the right description', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('description')).toEqual(visJson.description);
});
it('should have the right bounds', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('view_bounds_sw')).toEqual(visJson.bounds[0]);
expect(visModel.map.get('view_bounds_ne')).toEqual(visJson.bounds[1]);
});
it('should have the right zoom', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('zoom')).toEqual(visJson.zoom);
});
it('should have the right scrollwheel', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('scrollwheel')).toEqual(visJson.options.scrollwheel);
});
it('should have the right drag', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('drag')).toEqual(true);
});
it('should have the right provider', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('provider')).toEqual('leaflet');
});
it('should have the right feature interactivity', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('isFeatureInteractivityEnabled')).toEqual(false);
});
it('should have the right render mode', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.map.get('renderMode')).toEqual(visJson.vector ? 'vector' : 'raster');
});
});
describe('VisModel.overlays', function () {
it('should have a share overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
// TODO: Review if this overlay is still supported!
expect(visModel.overlaysCollection.findWhere({ type: 'share' })).toBeDefined();
});
it('should have a search overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'search' })).toBeDefined();
});
it('should have a zoom overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'zoom' })).toBeDefined();
});
it('should have a loader overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'loader' })).toBeDefined();
});
it('should have a logo overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'logo' })).toBeDefined();
});
it('should have a attribution overlay', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel.overlaysCollection.findWhere({ type: 'attribution' })).toBeDefined();
});
});
describe('VisModel._dataviewsCollection', function () {
it('should not have dataviews', function () {
var visJson = scenarios.load('basic');
var visModel = createVis(this.containerId, visJson);
expect(visModel._dataviewsCollection.length).toEqual(0);
});
});
});

View File

@@ -0,0 +1,314 @@
module.exports = {
'bounds': [
[
38.994,
-8.74622
],
[
42.3508,
-1.86658
]
],
'center': [
40.67241595,
-5.306396485
],
'datasource': {
'user_name': 'iago-carto',
'maps_api_template': 'https://{user}.carto.com:443',
'stat_tag': 'd71f6316-b2df-4a33-8109-fa80e8fc793d',
'template_name': 'tpl_d71f6316_b2df_4a33_8109_fa80e8fc793d'
},
'description': null,
'options': {
'legends': true,
'scrollwheel': true,
'layer_selector': true,
'dashboard_menu': true
},
'id': 'd71f6316-b2df-4a33-8109-fa80e8fc793d',
'layers': [
{
'id': '5bdcb792-78ce-4875-8ab1-869561916426',
'type': 'tiled',
'options': {
'default': 'true',
'url': 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'name': 'Positron',
'className': 'positron_rainbow_labels',
'attribution': "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
'labels': {
'url': 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}.png'
},
'urlTemplate': 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png'
}
},
{
'id': '1b65ba49-e202-4a0a-bb4d-5d8a416788cd',
'type': 'CartoDB',
'visible': true,
'options': {
'layer_name': 'lugares',
'attribution': '',
'cartocss': '#layer {\n marker-width: ramp([population], range(6, 33), quantiles(5));\n marker-fill: #279aff;\n marker-fill-opacity: 0.9;\n marker-allow-overlap: true;\n marker-line-width: 1;\n marker-line-color: #FFF;\n marker-line-opacity: 1;\n}',
'source': 'a0'
},
'infowindow': {
'template_name': 'infowindow_color',
'fields': [
{
'name': 'population',
'title': true,
'position': null
},
{
'name': 'description',
'title': true,
'position': null
}
],
'maxHeight': 180,
'template': "<div class='CDB-infowindow CDB-infowindow--light js-infowindow'>\n <div class='CDB-infowindow-close js-close'></div>\n <div class='CDB-infowindow-container'>\n <div class='CDB-infowindow-header CDB-infowindow-headerBg CDB-infowindow-headerBg--light js-header' style='background: #35AAE5;'>\n {{#loading}}\u2026{{/loading}}\n <ul class='CDB-infowindow-list'>\n {{#content.fields}}\n {{^index}}\n <li class='CDB-infowindow-listItem'>\n {{#title}}<h5 class='CDB-infowindow-subtitle'>{{title}}</h5>{{/title}}\n {{#value}}<h4 class='CDB-infowindow-title {{#type}}{{ type }}{{/type}}'>{{{ value }}}</h4>{{/value}}\n </li>\n {{^value}}{{/value}}\n {{/index}}\n {{/content.fields}}\n </ul>\n </div>\n <div class='CDB-infowindow-inner js-inner'>\n {{#loading}}\n <div class='CDB-Loader js-loader is-visible'></div>\n {{/loading}}\n <ul class='CDB-infowindow-list js-content'>\n {{#content.fields}}\n {{#index}}\n <li class='CDB-infowindow-listItem'>\n {{#title}}\n <h5 class='CDB-infowindow-subtitle'>{{title}}</h5>\n {{/title}}\n {{#value}}\n <h4 class='CDB-infowindow-title'>{{{ value }}}</h4>\n {{/value}}\n {{^value}}\n <h4 class='CDB-infowindow-title'>NULL</h4>\n {{/value}}\n </li>\n {{/index}}\n {{/content.fields}}\n </ul>\n </div>\n <div class='CDB-hook'>\n <div class='CDB-hook-inner'></div>\n </div>\n </div>\n</div>\n",
'alternative_names': {
'name': '',
'description': ''
},
'width': 226,
'headerColor': {
'color': {
'fixed': '#35AAE5',
'opacity': 1
}
},
'template_type': 'mustache'
},
'tooltip': {
'fields': [
{
'name': 'name',
'title': true,
'position': null
},
{
'name': 'description',
'title': true,
'position': null
}
],
'template_name': 'tooltip_dark',
'template': "<div class='CDB-Tooltip CDB-Tooltip--isDark'>\n <ul class='CDB-Tooltip-list'>\n {{#fields}}\n <li class='CDB-Tooltip-listItem'>\n {{#title}}\n <h3 class='CDB-Tooltip-listTitle'>{{{ title }}}</h3>\n {{/title}}\n <h4 class='CDB-Tooltip-listText'>{{{ value }}}</h4>\n </li>\n {{/fields}}\n </ul>\n</div>\n",
'template_type': 'mustache'
},
'legends': [
{
'conf': {
'columns': [
'title'
]
},
'created_at': '2017-08-31T14:58:44+00:00',
'definition': {
'categories': [
{
'title': 'Iago',
'color': '#279aff'
},
{
'title': 'Pablo',
'color': '#e7c5ce'
},
{
'title': 'Untitled',
'color': '#528995'
}
]
},
'id': 'cc81d782-0037-4cba-9575-abe817147bfa',
'layer_id': '1b65ba49-e202-4a0a-bb4d-5d8a416788cd',
'post_html': '',
'pre_html': '',
'title': 'Wadus',
'type': 'custom',
'updated_at': '2017-08-31T14:59:29+00:00'
}
]
},
{
'id': 'a58f47f9-7fb2-4539-a7e6-dcedd7adc9e4',
'type': 'tiled',
'options': {
'default': 'true',
'url': 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'attribution': "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
'urlTemplate': 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}.png',
'type': 'Tiled',
'name': 'Positron Labels'
}
}
],
'likes': 0,
'map_provider': 'leaflet',
'overlays': [
{
'type': 'share',
'order': 2,
'options': {
'display': true,
'x': 20,
'y': 20
},
'template': ''
},
{
'type': 'search',
'order': 3,
'options': null,
'template': null
},
{
'type': 'zoom',
'order': 6,
'options': null,
'template': null
},
{
'type': 'loader',
'order': 8,
'options': {
'display': true,
'x': 20,
'y': 150
},
'template': "<div class='loader' original-title=''></div>"
},
{
'type': 'logo',
'order': 10,
'options': null,
'template': null
}
],
'title': 'Cities',
'updated_at': '2017-08-31T15:36:12+00:00',
'user': {
'fullname': 'Iago Lastra',
'avatar_url': 'https://s3.amazonaws.com/com.cartodb.users-assets.production/production/iago-carto/assets/20170720105148Avatar250.png',
'profile_url': 'https://team.carto.com/u/iago-carto'
},
'version': '3.0.0',
'widgets': [
{
'id': '30a96a5d-349d-49c4-875a-c6eba7485635',
'type': 'category',
'title': 'name',
'order': 0,
'layer_id': '1b65ba49-e202-4a0a-bb4d-5d8a416788cd',
'options': {
'column': 'name',
'aggregation_column': 'name',
'aggregation': 'count',
'column_type': 'string',
'sync_on_bbox_change': true
},
'style': {
'widget_style': {
'definition': {
'color': {
'fixed': '#9DE0AD',
'opacity': 1
}
},
'widget_color_changed': false
},
'auto_style': {
'custom': false,
'allowed': true
}
},
'source': {
'id': 'a0'
}
},
{
'id': '3d26c92f-0244-4479-9fc0-fb0715283ecd',
'type': 'histogram',
'title': 'population',
'order': 2,
'layer_id': '1b65ba49-e202-4a0a-bb4d-5d8a416788cd',
'options': {
'column': 'population',
'bins': 10,
'column_type': 'number',
'sync_on_bbox_change': true
},
'style': {
'widget_style': {
'definition': {
'color': {
'fixed': '#9DE0AD',
'opacity': 1
}
},
'widget_color_changed': false
},
'auto_style': {
'custom': false,
'allowed': true
}
},
'source': {
'id': 'a0'
}
},
{
'id': 'd0d44271-43ef-468f-b2e5-64f4a266daa4',
'type': 'category',
'title': 'name',
'order': 3,
'layer_id': '1b65ba49-e202-4a0a-bb4d-5d8a416788cd',
'options': {
'column': 'name',
'aggregation_column': 'name',
'aggregation': 'count',
'column_type': 'string',
'sync_on_bbox_change': true
},
'style': {
'widget_style': {
'definition': {
'color': {
'fixed': '#9DE0AD',
'opacity': 1
}
},
'widget_color_changed': false
},
'auto_style': {
'custom': false,
'allowed': true
}
},
'source': {
'id': 'a0'
}
}
],
'zoom': 6,
'analyses': [
{
'id': 'a0',
'type': 'source',
'options': {
'table_name': "'iago-carto'.lugares",
'simple_geom': 'point'
}
}
],
'vector': false
};

View File

@@ -0,0 +1,19 @@
/**
* Load a vis.json from the scenarios folder
* It returns a copy from the object to easy reusing.
*/
function load (index) {
// We use a switch because our current build system doesn't support variables in the require.
switch (index) {
case 'basic':
return clone(require('./basic_vis.json.js'));
}
}
function clone (object) {
return JSON.parse(JSON.stringify(object));
}
module.exports = {
load: load
};

499
test/spec/api/sql.spec.js Normal file
View File

@@ -0,0 +1,499 @@
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var SQL = require('../../../src/api/sql');
describe('api/sql', function () {
var USER = 'cartojs-test';
var sql;
var ajax;
var ajaxParams;
var TEST_DATA = { test: 'good' };
var NO_BOUNDS = { 'rows': [
{ 'maxx': null }
]};
var throwError;
var abort = jasmine.createSpy('abort');
beforeEach(function () {
jasmine.clock().install();
ajaxParams = null;
ajax = function (params) {
ajaxParams = params;
_.defer(function () {
params.complete && params.complete();
if (!throwError && params.success) params.success(TEST_DATA, 200);
throwError && params.error && params.error({
responseText: JSON.stringify({
error: ['jaja']
})
});
});
return {
abort: abort
};
};
spyOn($, 'ajax').and.callFake(ajax);
sql = new SQL({
user: USER,
protocol: 'https'
});
});
afterEach(function () {
jasmine.clock().uninstall();
});
it('should compile the url if not completeDomain passed', function () {
expect(sql._host()).toEqual('https://cartojs-test.carto.com/api/v2/sql');
});
it('should compile the url if completeDomain passed', function () {
var sqlBis = new SQL({
user: USER,
protocol: 'https',
completeDomain: 'http://troloroloro.com'
});
expect(sqlBis._host()).toEqual('http://troloroloro.com/api/v2/sql');
});
it('should execute a query', function () {
sql.execute('select * from table');
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent('select * from table')
);
expect(ajaxParams.type).toEqual('get');
expect(ajaxParams.dataType).toEqual('json');
expect(ajaxParams.crossDomain).toEqual(true);
});
it('should be abortable', function () {
sql = new SQL({
user: USER,
protocol: 'https',
abortable: true
});
sql.execute('select * from table');
expect(abort).not.toHaveBeenCalled();
sql.execute('select * from table limit 10');
expect(abort).toHaveBeenCalled();
});
it('should parse template', function () {
sql.execute('select * from {{table}}', {
table: 'cartojs-test'
});
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent('select * from cartojs-test')
);
});
it('should execute a long query', function () {
// Generating a giant query
var longSQL = [];
var i = 2000;
while (--i) longSQL.push('10000');
var longQuery = 'SELECT * ' + longSQL;
// required to have jquery as transport, is checked in the execute method
sql.execute(longQuery);
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql'
);
expect(ajaxParams.data.q).toEqual(longQuery);
expect(ajaxParams.type).toEqual('post');
expect(ajaxParams.dataType).toEqual('json');
expect(ajaxParams.crossDomain).toEqual(true);
});
it('should execute a long query with params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test'
});
// Generating a giant query
var longSQL = [];
var i = 2000;
while (--i) longSQL.push('10000');
var longQuery = 'SELECT * ' + longSQL;
s.execute(longQuery, null, {
dp: 0
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
// Check that we don't have params in the URI
expect(ajaxParams.url.indexOf('&format=geojson')).toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test')).toEqual(-1);
// Check that we have the params in the body
expect(ajaxParams.data.q).toEqual(longQuery);
expect(ajaxParams.data.format).toEqual('geojson');
expect(ajaxParams.data.api_key).toEqual('testkey');
expect(ajaxParams.data.dp).toEqual(0);
expect(ajaxParams['cartojs-test']).toEqual('test');
});
it('should substitute mapnik tokens', function () {
sql.execute('select !pixel_width! as w, !pixel_height! as h, !bbox! as b from {{table}}', {
table: 't'
});
var earthCircumference = 40075017;
var tileSize = 256;
var srid = 3857;
var fullResolution = earthCircumference / tileSize;
var shift = earthCircumference / 2.0;
var pw = fullResolution;
var ph = pw;
var bbox = 'ST_MakeEnvelope(' + (-shift) + ',' + (-shift) + ',' +
shift + ',' + shift + ',' + srid + ')';
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent(
'select ' + pw + ' as w, ' + ph + ' as h, ' +
bbox + ' as b from t')
);
});
it('should call promise', function () {
var data;
var dataCallback;
sql.execute('select * from bla', function (e, data) {
dataCallback = data;
}).done(function (d) {
data = d;
});
jasmine.clock().tick(100);
expect(data).toEqual(TEST_DATA);
expect(dataCallback).toEqual(TEST_DATA);
});
it('should call promise on error', function () {
throwError = true;
var err = false;
sql.execute('select * from bla').error(function () {
err = true;
});
jasmine.clock().tick(10);
expect(err).toEqual(true);
});
it('should include url params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test'
});
s.execute('select * from cartojs-test', null, {
dp: 2
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&format=geojson')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test')).toEqual(-1);
});
it('should include extra url params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test',
extra_params: ['cartojs-test']
});
s.execute('select * from cartojs-test', null, {
dp: 2
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&format=geojson')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test=test')).not.toEqual(-1);
s.execute('select * from cartojs-test', null, {
dp: 2,
'cartojs-test': 'test2'
});
expect(ajaxParams.url.indexOf('&cartojs-test=test2')).not.toEqual(-1);
});
it('should use jsonp if browser does not support cors', function () {
var corsPrev = $.support.cors;
$.support.cors = false;
var s = new SQL({ user: 'jaja' });
expect(s.options.jsonp).toEqual(true);
s.execute('select * from cartojs-test', null, {
dp: 2,
jsonpCallback: 'test_callback',
cache: false
});
expect(ajaxParams.dataType).toEqual('jsonp');
expect(ajaxParams.crossDomain).toEqual(undefined);
expect(ajaxParams.jsonp).toEqual(undefined);
expect(ajaxParams.jsonpCallback).toEqual('test_callback');
expect(ajaxParams.cache).toEqual(false);
$.support.cors = corsPrev;
});
describe('.getBounds', function () {
it('should get bounds for query', function () {
var sql = 'SELECT ST_XMin(ST_Extent(the_geom)) as minx,' +
' ST_YMin(ST_Extent(the_geom)) as miny,' +
' ST_XMax(ST_Extent(the_geom)) as maxx,' +
' ST_YMax(ST_Extent(the_geom)) as maxy' +
' from (select * from cartojs-test where id=2) as subq';
var s = new SQL({ user: 'jaja' });
s.getBounds('select * from cartojs-test where id={{id}}', {id: 2});
expect(ajaxParams.url.indexOf(encodeURIComponent(sql))).not.toEqual(-1);
});
it('should get bounds for query with appostrophes', function () {
var s = new SQL({ user: 'jaja' });
s.getBounds('select * from country where name={{ name }}', {name: "'Spain'"});
expect(ajaxParams.url.indexOf('%26amp%3B%2339%3B')).toEqual(-1);
});
it('should resolve promise as error in case there are no bounds', function () {
var prevTestData = TEST_DATA;
var actualErrors = null;
TEST_DATA = NO_BOUNDS;
throwError = false;
var s = new SQL({ user: 'jaja' });
s.getBounds('SELECT * FROM somewhere')
.error(function (err) {
actualErrors = err;
});
jasmine.clock().tick(10);
expect(actualErrors).not.toBeNull();
expect(actualErrors.length).toBe(1);
expect(actualErrors[0]).toEqual('No bounds');
// Cleaning
TEST_DATA = prevTestData;
});
it('should trigger the error callback in case there are no bounds', function () {
var prevTestData = TEST_DATA;
var actualErrors = null;
TEST_DATA = NO_BOUNDS;
throwError = false;
function cb (err) {
actualErrors = err;
}
var s = new SQL({ user: 'jaja' });
s.getBounds('SELECT * FROM somewhere', null, null, cb);
jasmine.clock().tick(10);
expect(actualErrors).not.toBeNull();
expect(actualErrors.length).toBe(1);
expect(actualErrors[0]).toEqual('No bounds');
// Cleaning
TEST_DATA = prevTestData;
});
});
});
describe('api/sql.table', function () {
var USER = 'cartojs-test';
var sql;
beforeEach(function () {
sql = new SQL({
user: USER,
protocol: 'https'
});
});
it('sql', function () {
var s = sql.table('test');
expect(s.sql()).toEqual('select * from test');
s.columns(['age', 'jeta']);
expect(s.sql()).toEqual('select age,jeta from test');
s.filter('age < 10');
expect(s.sql()).toEqual('select age,jeta from test where age < 10');
s.limit(15);
expect(s.sql()).toEqual('select age,jeta from test where age < 10 limit 15');
s.order_by('age');
expect(s.sql()).toEqual('select age,jeta from test where age < 10 limit 15 order by age');
});
});
describe('api/sql column descriptions', function () {
var USER = 'manolo';
var sql;
beforeAll(function () {
this.colDate = new Backbone.Model(JSON.parse('{"name":"object_postedtime","type":"date","geometry_type":"point","bbox":[[-28.92163128242129,-201.09375],[75.84516854027044,196.875]],"analyzed":true,"success":true,"stats":{"type":"date","start_time":"2015-02-19T15:13:16.000Z","end_time":"2015-02-22T04:34:05.000Z","range":220849000,"steps":1024,"null_ratio":0,"column":"object_postedtime"}}'));
this.colFloat = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"number","geometry_type":"point"}'));
this.colString = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"string","geometry_type":"point"}'));
this.colGeom = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"geometry","geometry_type":"point"}'));
this.colBoolean = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"boolean","geometry_type":"point"}'));
this.query = 'SELECT * FROM whatevs';
sql = new SQL({
user: USER,
protocol: 'https'
});
sql.execute = function (sql, callback) {
callback(null, {});
};
});
it('should deduct correct describe method', function () {
spyOn(sql, 'describeDate');
sql.describe(this.query, this.colDate, {type: this.colDate.get('type')}, function () {});
expect(sql.describeDate).toHaveBeenCalled();
spyOn(sql, 'describeFloat');
sql.describe(this.query, this.colFloat, {type: this.colFloat.get('type')}, function () {});
expect(sql.describeFloat).toHaveBeenCalled();
spyOn(sql, 'describeString');
sql.describe(this.query, this.colString, {type: this.colString.get('type')}, function () {});
expect(sql.describeString).toHaveBeenCalled();
spyOn(sql, 'describeGeom');
sql.describe(this.query, this.colGeom, {type: this.colGeom.get('type')}, function () {});
expect(sql.describeGeom).toHaveBeenCalled();
spyOn(sql, 'describeBoolean');
sql.describe(this.query, this.colBoolean, {type: this.colBoolean.get('type')}, function () {});
expect(sql.describeBoolean).toHaveBeenCalled();
});
describe('string describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = JSON.parse('{"rows":[{"uniq":462,"cnt":487,"null_count":1,"null_ratio":0.002053388090349076,"skew":0.043121149897330596,"array_agg":""}],"time":0.01,"fields":{"uniq":{"type":"number"},"cnt":{"type":"number"},"null_count":{"type":"number"},"null_ratio":{"type":"number"},"skew":{"type":"number"},"array_agg":{"type":"unknown(2287)"}},"total_rows":1}');
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeString(sql, this.colString, callback); // THE COLS DON'T MATCH!!!
});
it('should return correct properties', function () {
expect(description.hist.constructor).toEqual(Array); // Right now it's an empty array because JSON.parse doesn't like our way of notating histograms
expect(description.type).toEqual('string');
expect(typeof description.null_count).toEqual('number');
expect(typeof description.distinct).toEqual('number');
expect(typeof description.null_ratio).toEqual('number');
expect(typeof description.skew).toEqual('number');
expect(typeof description.weight).toEqual('number');
});
});
describe('geometry describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = {'rows': [{'geometry_type': 'ST_Point'}], 'time': 0.035, 'fields': {'geometry_type': {'type': 'string'}}, 'total_rows': 1};
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeGeom(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('geom');
expect(['ST_Point', 'ST_Line', 'ST_Polygon'].indexOf(description.geometry_type) > -1).toBe(true);
});
});
describe('number describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = JSON.parse('{"rows":[{"hist":"{\\"(1,empty,69368)\\",\\"(25,empty,11063)\\"}","min":0,"max":4,"avg":0.3745819397993311,"cnt":89401,"uniq":5,"null_ratio":0,"stddev":0.000009057366328792043,"stddevmean":2.1617223091836313,"dist_type":"U","quantiles":[0,1,2,2,3,4,4],"equalint":[0,0,0,0,0,0,0],"jenks":[0,1,2,3,4],"headtails":[0,1,2,3,4],"cat_hist":"{\\"(1,empty,69368)\\",\\"(25,empty,11063)\\"}"}],"time":1.442,"fields":{"hist":{"type":"unknown(2287)"},"min":{"type":"number"},"max":{"type":"number"},"avg":{"type":"number"},"cnt":{"type":"number"},"uniq":{"type":"number"},"null_ratio":{"type":"number"},"stddev":{"type":"number"},"stddevmean":{"type":"number"},"dist_type":{"type":"string"},"quantiles":{"type":"number[]"},"equalint":{"type":"number[]"},"jenks":{"type":"number[]"},"headtails":{"type":"number[]"},"cat_hist":{"type":"unknown(2287)"}},"total_rows":1}');
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeFloat(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('number');
expect(['A', 'U', 'F', 'J'].indexOf(description.dist_type) > -1).toBe(true);
var i;
var numTypes = ['avg', 'max', 'min', 'stddevmean', 'weight', 'stddev', 'null_ratio', 'count'];
for (i = 0; i < numTypes.length; i++) {
expect(typeof description[numTypes[i]]).toEqual('number');
}
var arrayTypes = ['quantiles', 'equalint', 'jenks', 'headtails', 'cat_hist', 'hist'];
for (i = 0; i < arrayTypes.length; i++) {
expect(description[arrayTypes[i]].constructor).toEqual(Array);
}
});
});
describe('boolean describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = {'rows': [
{'true_ratio': 0.3377926421404682, 'null_ratio': 0, 'uniq': 2, 'cnt': 89401}
],
'time': 0.251,
'fields': {'true_ratio': {'type': 'number'}, 'null_ratio': {'type': 'number'}, 'uniq': {'type': 'number'}, 'cnt': {'type': 'number'}},
'total_rows': 1
};
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeBoolean(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('boolean');
expect(typeof description.true_ratio).toEqual('number');
expect(typeof description.distinct).toEqual('number');
expect(typeof description.count).toEqual('number');
expect(typeof description.null_ratio).toEqual('number');
});
});
});

View File

@@ -0,0 +1,459 @@
/* global L */
/* global google */
var _ = require('underscore');
var carto = require('../../../../src/api/v4');
var LeafletLayer = require('../../../../src/api/v4/native/leaflet-layer');
var GoogleMapsMapType = require('../../../../src/api/v4/native/google-maps-map-type');
var Engine = require('../../../../src/engine');
var Events = require('../../../../src/api/v4/events');
describe('api/v4/client', function () {
var client;
beforeEach(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
serverUrl: 'https://cartojs-test.carto.com',
username: 'cartojs-test'
});
});
describe('constructor', function () {
it('should build a new client', function () {
expect(client).toBeDefined();
expect(client.getLayers()).toEqual([]);
});
it('should autogenerate the carto url when is not given', function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
expect(client._engine._windshaftSettings.urlTemplate).toEqual('https://cartojs-test.carto.com');
});
it('should autogenerate the carto url when a template is given', function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'https://{username}.mycarto.com'
});
expect(client._engine._windshaftSettings.urlTemplate).toEqual('https://cartojs-test.mycarto.com');
});
it('should accept a ipv4/user/{username} as a valid serverURL', function () {
expect(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'https://192.168.0.1/user/cartojs-test'
});
}).not.toThrow();
});
it('should reject a valid ip adress with no /user/{username} as serverURL', function () {
expect(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'https://10.10.0.1'
});
}).toThrow();
});
it('should reject an invalid ip adress with no /user/{username}', function () {
expect(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'https://192.168.1'
});
}).toThrow();
});
describe('error handling', function () {
describe('apiKey', function () {
it('should throw a descriptive error when apikey is not given', function () {
expect(function () {
new carto.Client({ username: "cartojs-test" }); // eslint-disable-line
}).toThrowError('apiKey property is required.');
});
it('should throw a descriptive error when apikey is not a string', function () {
expect(function () {
new carto.Client({ apiKey: 1234, username: "cartojs-test" }); // eslint-disable-line
}).toThrowError('apiKey property must be a string.');
});
it('should throw a descriptive error when apikey is not a string', function () {
expect(function () {
new carto.Client({ apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18' }); // eslint-disable-line
}).toThrowError('username property is required.');
});
});
describe('username', function () {
it('should throw a descriptive error when username is not a string', function () {
expect(function () {
new carto.Client({ apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18', username: 1234 }); // eslint-disable-line
}).toThrowError('username property must be a string.');
});
});
describe('serverUrl', function () {
it('should throw a descriptive error when serverUrl is given and is not valid', function () {
expect(function () {
// eslint-disable-next-line
new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'invalid-url'
});
}).toThrowError('serverUrl is not a valid URL.');
});
it("should throw a descriptive error when serverUrl doesn't match the username", function () {
expect(function () {
// eslint-disable-next-line
new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test',
serverUrl: 'https://invald-username.carto.com'
});
}).toThrowError("serverUrl doesn't match the username.");
});
});
});
});
describe('.addLayer', function () {
var source;
var style;
var layer;
beforeEach(function () {
source = new carto.source.Dataset('ne_10m_populated_places_simple', {
id: 'a0'
});
style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
layer = new carto.layer.Layer(source, style, {});
});
it('should add a new layer', function () {
client.addLayer(layer);
expect(client.getLayers()[0]).toEqual(layer);
});
it('should add a new layer triggering a reload cycle by default', function (done) {
spyOn(client._engine, 'reload').and.callThrough();
client.addLayer(layer).then(function () {
expect(client._engine.reload).toHaveBeenCalled();
expect(client._engine.reload.calls.count()).toEqual(1);
done();
});
});
it('should return a rejected promise when some error happened', function (done) {
var errorMock = new Error('Error-Mock');
spyOn(client._engine, 'reload').and.returnValue(
Promise.reject(errorMock)
);
client.addLayer(layer).catch(function (error) {
expect(error.message).toEqual(errorMock.message);
done();
});
});
it('should return a significative error when layer parameter is not a valid layer', function () {
expect(function () {
client.addLayer([]);
}).toThrowError('The given object is not a layer.');
});
it('should throw a descriptive error when two layers with the same id are added', function () {
expect(function () {
client.addLayer(layer);
client.addLayer(
new carto.layer.Layer(source, style, { id: layer.getId() })
);
}).toThrowError('A layer with the same ID already exists in the client.');
});
});
describe('.addLayers', function () {
it('should add a layers array', function () { });
it('should add a layer array triggering ONE reload cycle by default', function () { });
it('should add a layers array without triggering a reload cycle when opts.reload is false', function () { });
it('should return a rejected promise when some error happened', function () { });
});
describe('.getLayers', function () {
it('should return an empty array when there are no layers', function () {
expect(client.getLayers()).toEqual([]);
});
xit('should return the layers stored in the client', function (done) {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(source, style, {});
client.addLayer(layer).then(function () {
expect(client.getLayers()[0]).toEqual(layer);
done();
});
});
});
describe('.removeLayer', function () {
it('should throw a descriptive error when the parameter is invalid', function () {
expect(function () {
client.removeLayer({});
}).toThrowError('The given object is not a layer.');
});
it('¿should throw a descriptive error when layer is not in the client?', function () {
pending('We should decide if this makes sense.');
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(source, style, {});
expect(function () {
client.removeLayer(layer);
}).toThrowError('The layer is not in the client');
});
it('should remove the layer when is in the client', function () {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(source, style, {});
client.addLayer(layer);
expect(client.getLayers().length).toEqual(1);
client.removeLayer(layer);
expect(client.getLayers().length).toEqual(0);
});
});
describe('.removeLayers', function () {
it('must remove all layers', function () {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layerA = new carto.layer.Layer(source, style, {});
var layerB = new carto.layer.Layer(source, style, {});
var layerC = new carto.layer.Layer(source, style, {});
client.addLayers([layerA, layerB, layerC]);
expect(client.getLayers().length).toEqual(3);
client.removeLayers(client.getLayers());
expect(client.getLayers().length).toEqual(0);
});
});
describe('.removeDataview', function () {
var categoryDataview, populationDataview;
beforeEach(function () {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
categoryDataview = new carto.dataview.Category(source, 'adm0name', {
limit: 10,
operation: carto.operation.SUM,
operationColumn: 'pop_max'
});
populationDataview = new carto.dataview.Category(source, 'adm1name', {
limit: 10,
operation: carto.operation.SUM,
operationColumn: 'pop_max'
});
client.addDataview(categoryDataview);
client.addDataview(populationDataview);
spyOn(client._engine, 'removeDataview');
spyOn(categoryDataview, 'disable');
spyOn(client, '_reload');
});
it('removes the dataview', function () {
expect(client._dataviews.length).toBe(2);
client.removeDataview(categoryDataview);
expect(client._dataviews.length).toBe(1);
});
it('disables the dataview', function () {
client.removeDataview(categoryDataview);
expect(client._engine.removeDataview).toHaveBeenCalled();
});
it('triggers a reload cycle', function () {
client.removeDataview(categoryDataview);
expect(client._reload).toHaveBeenCalled();
});
});
describe('.moveLayer', function () {
it('should throw a descriptive error when the parameter is invalid', function () {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(source, style, {});
expect(function () {
client.moveLayer({}, 0);
}).toThrowError('The given object is not a layer.');
expect(function () {
client.moveLayer(layer, false);
}).toThrowError('index property must be a number.');
expect(function () {
client.moveLayer(layer, 1234);
}).toThrowError('index is out of range.');
});
it('should move the layer when is in the client', function () {
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer0 = new carto.layer.Layer(source, style, {});
var layer1 = new carto.layer.Layer(source, style, {});
client.addLayers([layer0, layer1]);
expect(client.getLayers()[0]).toEqual(layer0);
expect(client.getLayers()[1]).toEqual(layer1);
client.moveLayer(layer0, 1);
expect(client.getLayers()[1]).toEqual(layer0);
expect(client.getLayers()[0]).toEqual(layer1);
});
});
describe('.getLeafletLayer', function () {
var leafletLayer;
beforeEach(function () {
leafletLayer = client.getLeafletLayer();
});
it('should return an instance of LeafletLayer', function () {
expect(leafletLayer instanceof LeafletLayer).toBe(true);
});
it('should return the same object', function () {
expect(leafletLayer === client.getLeafletLayer()).toBe(true);
});
it('should return a L.TileLayer', function () {
expect(leafletLayer instanceof L.TileLayer).toBe(true);
});
it('should have the OpenStreetMap / Carto attribution', function () {
expect(leafletLayer.getAttribution()).toBe(
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attribution">CARTO</a>'
);
});
it('should throw an error if Leaflet is not loaded', function () {
var L = _.clone(window.L);
window.L = undefined;
expect(function () {
client.getLeafletLayer();
}).toThrowError('Leaflet is required');
// Restore window.L
window.L = L;
});
it('should throw an error if Leaflet version is <1.0', function () {
var L = _.clone(window.L);
window.L = { version: '0.7' };
expect(function () {
client.getLeafletLayer();
}).toThrowError('Leaflet +1.0 is required');
// Restore window.L
window.L = L;
});
});
describe('.getGoogleMapsMapType', function () {
var element;
var mapType;
beforeEach(function () {
element = document.createElement('div');
mapType = client.getGoogleMapsMapType(new google.maps.Map(element));
});
afterEach(function () {
element.remove();
});
it('should return an instance of GoogleMapsMapType', function () {
expect(mapType instanceof GoogleMapsMapType).toBe(true);
});
it('should return the same object', function () {
expect(mapType === client.getGoogleMapsMapType()).toBe(true);
});
it('should return an object with a MapType interface', function () {
expect(mapType.tileSize).toBeDefined();
expect(mapType.getTile).toBeDefined();
});
it('should throw an error if Google Maps is not loaded', function () {
var google = _.clone(window.google);
window.google = undefined;
expect(function () {
client.getGoogleMapsMapType();
}).toThrowError('Google Maps is required');
window.google = { maps: undefined };
expect(function () {
client.getGoogleMapsMapType();
}).toThrowError('Google Maps is required');
// Restore window.google
window.google = google;
});
it('should throw an error if Google Maps version is < 3.31', function () {
var google = _.clone(window.google);
window.google.maps = { version: '2.4' };
expect(function () {
client.getGoogleMapsMapType();
}).toThrowError('Google Maps version should be >= 3.31');
// Restore window.google
window.google = google;
});
});
describe('engine bindings', function () {
it('should capture engine LAYER_ERROR and trigger own error', function () {
var capturedError;
client.on(Events.ERROR, function (error) {
capturedError = error;
});
client._engine._eventEmmitter.trigger(Engine.Events.LAYER_ERROR);
expect(capturedError).toBeDefined();
expect(capturedError.name).toEqual('CartoError');
});
});
});

View File

@@ -0,0 +1,243 @@
var DataviewBase = require('../../../../../src/api/v4/dataview/base');
var status = require('../../../../../src/api/v4/constants').status;
var carto = require('../../../../../src/api/v4/index');
function createSourceMock () {
return new carto.source.Dataset('foo');
}
function createEngineMock () {
var engine = {
name: 'Engine mock',
reload: function () {}
};
spyOn(engine, 'reload');
return engine;
}
describe('api/v4/dataview/base', function () {
var base = new DataviewBase();
it('.getStatus should return the internal status', function () {
expect(base.getStatus()).toEqual(base._status);
});
describe('.isLoading', function () {
it('should return true if loading and false otherwise', function () {
base._status = status.NOT_LOADED;
expect(base.isLoading()).toBe(false);
base._status = status.LOADING;
expect(base.isLoading()).toBe(true);
});
it('should return true if loading and false otherwise', function () {
base._status = status.NOT_LOADED;
expect(base.isLoaded()).toBe(false);
base._status = status.LOADED;
expect(base.isLoaded()).toBe(true);
});
});
describe('.hasError', function () {
it('should return true if loading and false otherwise', function () {
base._status = status.NOT_LOADED;
expect(base.hasError()).toBe(false);
base._status = status.ERROR;
expect(base.hasError()).toBe(true);
});
});
describe('.enable', function () {
it('should enable the dataview', function () {
base.enable();
expect(base._enabled).toBe(true);
});
it('should return the dataview', function () {
expect(base.enable()).toBe(base);
});
});
describe('.disable', function () {
it('should disable the dataview', function () {
base.disable();
expect(base._enabled).toBe(false);
});
it('should return the dataview', function () {
expect(base.disable()).toBe(base);
});
});
describe('.isEnabled', function () {
it('should return true if enabled and false otherwise', function () {
base.disable();
expect(base.isEnabled()).toBe(false);
base.enable();
expect(base.isEnabled()).toBe(true);
});
});
describe('.getSource', function () {
it('should return the source object', function () {
var source = new carto.source.Dataset('table_name');
base._source = source;
expect(base.getSource()).toBe(source);
});
});
describe('.setColumn', function () {
it('should set the column name as string', function () {
var column = 'column-test';
base.setColumn(column);
expect(base._column).toBe(column);
});
it('should throw an error if the argument is not string or undefined', function () {
var requiredColumnError;
var stringColumnError;
var emptyColumnError;
try { base.setColumn(); } catch (error) { requiredColumnError = error; }
try { base.setColumn(12); } catch (error) { stringColumnError = error; }
try { base.setColumn(''); } catch (error) { emptyColumnError = error; }
expect(requiredColumnError).toEqual(jasmine.objectContaining({
message: 'Column property is required.',
type: 'dataview',
errorCode: 'validation:dataview:column-required'
}));
expect(stringColumnError).toEqual(jasmine.objectContaining({
message: 'Column property must be a string.',
type: 'dataview',
errorCode: 'validation:dataview:column-string'
}));
expect(emptyColumnError).toEqual(jasmine.objectContaining({
message: 'Column property must be not empty.',
type: 'dataview',
errorCode: 'validation:dataview:empty-column'
}));
});
it('should return the dataview', function () {
var column = 'column-test';
expect(base.setColumn(column)).toBe(base);
});
});
describe('.getColumn', function () {
it('should return the column name', function () {
var column = 'column-test2';
base._column = column;
expect(base.getColumn()).toBe(column);
});
});
describe('.getData', function () {
it('.getData should not be defined in the base dataview', function () {
expect(function () { base.getData(); }).toThrowError(Error, 'getData must be implemented by the particular dataview.');
});
});
describe('._changeProperty', function () {
it('should set internal property', function () {
base._example = 'something';
base._changeProperty('example', 'whatever');
expect(base._example).toEqual('whatever');
});
it('should trigger change is there is no internal model', function () {
var eventValue = '';
base._example = 'something';
base.on('exampleChanged', function (newValue) {
eventValue = newValue;
});
base._changeProperty('example', 'whatever');
expect(eventValue).toEqual('whatever');
});
it('should update internal model and trigger a change when the internalModel exists', function () {
var internalModelSpy = jasmine.createSpyObj('internalModelSpy', ['set']);
base._example = 'something';
base._internalModel = internalModelSpy;
spyOn(base, '_triggerChange');
base._changeProperty('example', 'whatever');
expect(internalModelSpy.set).toHaveBeenCalledWith('example', 'whatever');
expect(base._triggerChange).toHaveBeenCalled();
});
});
describe('.$setEngine', function () {
var engine;
var dataview;
beforeEach(function () {
// We use Formula for these tests. Any other dataview could be used instead.
dataview = new carto.dataview.Formula(createSourceMock(), 'population', {
operation: carto.operation.MIN
});
engine = createEngineMock();
});
it('internalModel events should be properly hooked up', function () {
dataview.$setEngine(engine);
var internalModel = dataview._internalModel;
var eventStatus = null;
var eventError = null;
var dataviewError = null;
dataview.on('statusChanged', function (newStatus, error) {
eventStatus = newStatus;
eventError = error;
});
dataview.on('error', function (error) {
dataviewError = error;
});
// Loading
internalModel.trigger('loading');
expect(dataview.getStatus()).toEqual('loading');
expect(eventStatus).toEqual('loading');
// Loaded
internalModel.trigger('loaded');
expect(dataview.getStatus()).toEqual('loaded');
expect(eventStatus).toEqual('loaded');
// Error
internalModel.trigger('statusError', internalModel, 'an error');
expect(dataview.getStatus()).toEqual('error');
expect(eventStatus).toEqual('error');
expect(eventError).toEqual('an error');
expect(dataviewError.name).toEqual('CartoError');
});
});
describe('add bbox filter', function () {
it('should check if it is a proper object', function () {
function test () {
base.addFilter('invalid_filter');
}
expect(test).toThrowError('Filter property is required.');
});
it('should throw an error if an SQL filter is passed', function () {
function test () {
var categoryFilter = new carto.filter.Category('fake_column', { in: ['category_value'] });
base.addFilter(categoryFilter);
}
expect(test).toThrowError('Filter property is required.');
});
});
});

View File

@@ -0,0 +1,438 @@
var Backbone = require('backbone');
var _ = require('underscore');
var carto = require('../../../../../src/api/v4/index');
var createEngine = require('../../../fixtures/engine.fixture.js');
function createInternalModelMock () {
var internalModelMock = {
set: function () {},
get: function () {}
};
spyOn(internalModelMock, 'set');
spyOn(internalModelMock, 'get').and.callFake(function (key) {
switch (key) {
case 'count': return 42;
case 'max': return 9;
case 'min': return 1;
case 'nulls': return 0;
case 'data': return [
{
name: 'cat1',
value: 1,
agg: false
},
{
name: 'others',
value: 9,
agg: true
}
];
}
});
_.extend(internalModelMock, Backbone.Events);
return internalModelMock;
}
function createSourceMock () {
return new carto.source.Dataset('foo');
}
describe('api/v4/dataview/category', function () {
var source = createSourceMock();
describe('initialization', function () {
it('source must be provided', function () {
var error;
try { new carto.dataview.Category(); } catch (err) { error = err; } // eslint-disable-line no-new
expect(error).toEqual(jasmine.objectContaining({
message: 'Source property is required.',
type: 'dataview',
errorCode: 'validation:dataview:source-required'
}));
});
it('column must be provided', function () {
var error;
try { new carto.dataview.Category(source); } catch (err) { error = err; } // eslint-disable-line no-new
expect(error).toEqual(jasmine.objectContaining({
message: 'Column property is required.',
type: 'dataview',
errorCode: 'validation:dataview:column-required'
}));
});
it('options set to default if not provided', function () {
var column = 'population';
var dataview = new carto.dataview.Category(source, column);
expect(dataview._limit).toEqual(6);
expect(dataview._operation).toEqual(carto.operation.COUNT);
expect(dataview._operationColumn).toEqual('population');
});
it('options set to the provided value', function () {
var dataview = new carto.dataview.Category(source, 'population', {
limit: 10,
operation: carto.operation.AVG,
operationColumn: 'column-test'
});
expect(dataview._limit).toEqual(10);
expect(dataview._operation).toEqual(carto.operation.AVG);
expect(dataview._operationColumn).toEqual('column-test');
});
it('throw error if no correct operation is provided', function () {
var error;
var test = function () {
new carto.dataview.Category(source, 'population', { // eslint-disable-line no-new
operation: 'exponential'
});
};
try { test(); } catch (err) { error = err; }
expect(error).toEqual(jasmine.objectContaining({
message: 'Operation for category dataview is not valid. Use carto.operation',
type: 'dataview',
errorCode: 'validation:dataview:category-invalid-operation'
}));
});
});
describe('.setLimit', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Category(source, 'population');
});
it('checks if limit is valid', function () {
var requiredError;
var numberError;
var positiveError;
try { dataview.setLimit(); } catch (err) { requiredError = err; }
try { dataview.setLimit('12'); } catch (err) { numberError = err; }
try { dataview.setLimit(0); } catch (err) { positiveError = err; }
expect(requiredError).toEqual(jasmine.objectContaining({
message: 'Limit for category dataview is required.',
type: 'dataview',
errorCode: 'validation:dataview:category-limit-required'
}));
expect(numberError).toEqual(jasmine.objectContaining({
message: 'Limit for category dataview must be a number.',
type: 'dataview',
errorCode: 'validation:dataview:category-limit-number'
}));
expect(positiveError).toEqual(jasmine.objectContaining({
message: 'Limit for category dataview must be greater than 0.',
type: 'dataview',
errorCode: 'validation:dataview:category-limit-positive'
}));
});
it('if limit is valid, it assigns it to property, returns this and nothing else if there is no internaModel', function () {
var returnedObject = dataview.setLimit(10);
expect(dataview.getLimit()).toEqual(10);
expect(returnedObject).toBe(dataview);
});
it('sets limit in internal model if exists', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
dataview.setLimit(1);
var operationArgs = internalModelMock.set.calls.mostRecent().args;
expect(operationArgs[0]).toEqual('categories');
expect(operationArgs[1]).toEqual(1);
});
it('should trigger a limitChanged event', function () {
var limitChangedSpy = jasmine.createSpy('operationaChangedSpy');
dataview.on('limitChanged', limitChangedSpy);
expect(limitChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setLimit(7);
expect(limitChangedSpy).toHaveBeenCalledWith(7);
});
});
describe('.setOperation', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Category(source, 'population');
});
it('checks if operation is valid', function () {
var error;
var test = function () {
dataview.setOperation('swordfish');
};
try { test(); } catch (err) { error = err; }
expect(error).toEqual(jasmine.objectContaining({
message: 'Operation for category dataview is not valid. Use carto.operation',
type: 'dataview',
errorCode: 'validation:dataview:category-invalid-operation'
}));
});
it('if operation is valid, it assigns it to property, returns this and nothing else if there is no internaModel', function () {
var returnedObject = dataview.setOperation(carto.operation.AVG);
expect(dataview.getOperation()).toEqual(carto.operation.AVG);
expect(returnedObject).toBe(dataview);
});
it('sets operation in internal model if exists', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
dataview.setOperation(carto.operation.AVG);
var operationArgs = internalModelMock.set.calls.mostRecent().args;
expect(operationArgs[0]).toEqual('aggregation');
expect(operationArgs[1]).toEqual(carto.operation.AVG);
});
it('should trigger a operationChanged event', function () {
var operationChangedSpy = jasmine.createSpy('operationaChangedSpy');
dataview.on('operationChanged', operationChangedSpy);
expect(operationChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setOperation(carto.operation.AVG);
expect(operationChangedSpy).toHaveBeenCalledWith(carto.operation.AVG);
});
});
describe('.setOperationColumn', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Category(source, 'population');
});
it('checks if operation is valid', function () {
var requiredError;
var numberError;
var emptyError;
try { dataview.setOperationColumn(); } catch (err) { requiredError = err; }
try { dataview.setOperationColumn(12); } catch (err) { numberError = err; }
try { dataview.setOperationColumn(''); } catch (err) { emptyError = err; }
expect(requiredError).toEqual(jasmine.objectContaining({
message: 'Operation column for category dataview is required.',
type: 'dataview',
errorCode: 'validation:dataview:category-operation-required'
}));
expect(numberError).toEqual(jasmine.objectContaining({
message: 'Operation column for category dataview must be a string.',
type: 'dataview',
errorCode: 'validation:dataview:category-operation-string'
}));
expect(emptyError).toEqual(jasmine.objectContaining({
message: 'Operation column for category dataview must be not empty.',
type: 'dataview',
errorCode: 'validation:dataview:category-operation-empty'
}));
});
it('if operation is valid, it assigns it to property, returns this and nothing else if there is no internaModel', function () {
var returnedObject = dataview.setOperationColumn('columnA');
expect(dataview.getOperationColumn()).toEqual('columnA');
expect(returnedObject).toBe(dataview);
});
it('sets operation in internal model if exists', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
dataview.setOperationColumn('columnB');
var operationArgs = internalModelMock.set.calls.mostRecent().args;
expect(operationArgs[0]).toEqual('aggregation_column');
expect(operationArgs[1]).toEqual('columnB');
});
it('should trigger a operationColumnChanged event', function () {
var operationColumnChangedSpy = jasmine.createSpy('operationaChangedSpy');
dataview.on('operationColumnChanged', operationColumnChangedSpy);
expect(operationColumnChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setOperationColumn('column2');
expect(operationColumnChangedSpy).toHaveBeenCalledWith('column2');
});
});
describe('.getData', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Category(source, 'population', {
operation: carto.operation.SUM,
operationColumn: 'column-test'
});
});
it('returns null if there is no internalModel', function () {
var data = dataview.getData();
expect(data).toBeNull();
});
it('returns data from internalModel', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
var data = dataview.getData();
expect(data).toEqual({
count: 42,
max: 9,
min: 1,
nulls: 0,
operation: carto.operation.SUM,
categories: [
{
name: 'cat1',
value: 1,
group: false
},
{
name: 'others',
value: 9,
group: true
}
]
});
});
});
describe('.$setEngine', function () {
var engine;
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Category(source, 'population', {
operation: carto.operation.MIN,
operationColumn: 'column-test'
});
engine = createEngine();
});
it('creates the internal model', function () {
dataview.disable(); // To test that it passes the ._enabled property to the internal model
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel.get('source')).toBe(dataview._source.$getInternalModel());
expect(internalModel.get('column')).toEqual(dataview._column);
expect(internalModel.get('categories')).toEqual(dataview._limit);
expect(internalModel.get('aggregation')).toEqual(dataview._operation);
expect(internalModel.get('aggregation_column')).toEqual(dataview._operationColumn);
expect(internalModel.isEnabled()).toBe(false);
expect(internalModel._engine).toBe(engine);
});
it('calling twice to $setEngine does not create another internalModel', function () {
spyOn(dataview, '_createInternalModel').and.callThrough();
dataview.$setEngine(engine);
dataview.$setEngine(engine);
expect(dataview._createInternalModel.calls.count()).toBe(1);
});
describe('spatial filters', function () {
it('creates the internal model with BoundingBox filter if provided', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeDefined();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(true);
});
it('allows removing a BoundingBox filter', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeNull();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(false);
});
it('creates the internal model with Circle filter if provided', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeDefined();
expect(internalModel.syncsOnCircleChanges()).toBe(true);
});
it('allows removing a Circle filter', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeNull();
expect(internalModel.syncsOnCircleChanges()).toBe(false);
});
it('creates the internal model with Polygon filter if provided', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeDefined();
expect(internalModel.syncsOnPolygonChanges()).toBe(true);
});
it('allows removing a Polygon filter', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeNull();
expect(internalModel.syncsOnPolygonChanges()).toBe(false);
});
});
});
});

View File

@@ -0,0 +1,265 @@
var Backbone = require('backbone');
var _ = require('underscore');
var carto = require('../../../../../src/api/v4/index');
function createInternalModelMock () {
var internalModelMock = {
set: function () {},
get: function () {}
};
spyOn(internalModelMock, 'set');
spyOn(internalModelMock, 'get').and.callFake(function (key) {
if (key === 'data') {
return 1234;
}
if (key === 'nulls') {
return 42;
}
});
_.extend(internalModelMock, Backbone.Events);
return internalModelMock;
}
function createSourceMock () {
return new carto.source.Dataset('foo');
}
function createEngineMock () {
var engine = {
name: 'Engine mock',
reload: function () {}
};
spyOn(engine, 'reload');
return engine;
}
describe('api/v4/dataview/formula', function () {
var source = createSourceMock();
describe('initialization', function () {
it('source must be provided', function () {
var test = function () {
new carto.dataview.Formula(); // eslint-disable-line no-new
};
expect(test).toThrowError(Error, 'Source property is required.');
});
it('column must be provided', function () {
var test = function () {
new carto.dataview.Formula(source); // eslint-disable-line no-new
};
expect(test).toThrowError('Column property is required.');
});
it('options set to default if not provided', function () {
var column = 'population';
var dataview = new carto.dataview.Formula(source, column);
expect(dataview._operation).toEqual(carto.operation.COUNT);
});
it('options set to the provided value', function () {
var dataview = new carto.dataview.Formula(source, 'population', {
operation: carto.operation.AVG
});
expect(dataview._operation).toEqual(carto.operation.AVG);
});
it('throw error if no correct operation is provided', function () {
var test = function () {
new carto.dataview.Formula(source, 'population', { // eslint-disable-line no-new
operation: 'exponential'
});
};
expect(test).toThrowError(Error, 'Operation for formula dataview is not valid. Use carto.operation');
});
});
describe('.setOperation', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Formula(source, 'population');
});
it('checks if operation is valid', function () {
var test = function () {
dataview.setOperation('swordfish');
};
expect(test).toThrowError(Error, 'Operation for formula dataview is not valid. Use carto.operation');
});
it('if operation is valid, it assigns it to property, returns this and nothing else if there is no internaModel', function () {
var returnedObject = dataview.setOperation(carto.operation.AVG);
expect(dataview.getOperation()).toEqual(carto.operation.AVG);
expect(returnedObject).toBe(dataview);
});
it('sets operation in internal model if exists', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
dataview.setOperation(carto.operation.AVG);
var operationArgs = internalModelMock.set.calls.mostRecent().args;
expect(operationArgs[0]).toEqual('operation');
expect(operationArgs[1]).toEqual(carto.operation.AVG);
});
it('should Trigger a operationChanged event when the operation is changed', function () {
var operationChangedSpy = jasmine.createSpy('operationaChangedSpy');
dataview.on('operationChanged', operationChangedSpy);
expect(operationChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngineMock());
dataview.setOperation(carto.operation.MAX);
expect(operationChangedSpy).toHaveBeenCalledWith(carto.operation.MAX);
});
});
describe('.getData', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Formula(source, 'population', {
operation: carto.operation.SUM
});
});
it('returns null if there is no internalModel', function () {
var data = dataview.getData();
expect(data).toBeNull();
});
it('returns data from internalModel', function () {
var internalModelMock = createInternalModelMock();
dataview._internalModel = internalModelMock;
var data = dataview.getData();
expect(data).toEqual({
nulls: 42,
operation: carto.operation.SUM,
result: 1234
});
});
});
describe('.$setEngine', function () {
var engine;
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Formula(source, 'population', {
operation: carto.operation.MIN
});
engine = createEngineMock();
});
it('creates the internal model', function () {
dataview.disable(); // To test that it passes the ._enabled property to the internal model
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel.get('source')).toBe(dataview._source.$getInternalModel());
expect(internalModel.get('column')).toEqual(dataview._column);
expect(internalModel.get('operation')).toEqual(dataview._operation);
expect(internalModel.isEnabled()).toBe(false);
expect(internalModel._engine.name).toEqual('Engine mock');
});
it('calling twice to $setEngine does not create another internalModel', function () {
spyOn(dataview, '_createInternalModel').and.callThrough();
dataview.$setEngine(engine);
dataview.$setEngine(engine);
expect(dataview._createInternalModel.calls.count()).toBe(1);
});
describe('spatial filters', function () {
it('creates the internal model with BoundingBox filter if provided', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeDefined();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(true);
});
it('allows removing a BoundingBox filter', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeNull();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(false);
});
it('creates the internal model with Circle filter if provided', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeDefined();
expect(internalModel.syncsOnCircleChanges()).toBe(true);
});
it('allows removing a Circle filter', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeNull();
expect(internalModel.syncsOnCircleChanges()).toBe(false);
});
it('creates the internal model with Polygon filter if provided', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeDefined();
expect(internalModel.syncsOnPolygonChanges()).toBe(true);
});
it('allows removing a Polygon filter', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeNull();
expect(internalModel.syncsOnPolygonChanges()).toBe(false);
});
});
});
});

View File

@@ -0,0 +1,452 @@
var Backbone = require('backbone');
var _ = require('underscore');
var carto = require('../../../../../src/api/v4/index');
var createEngine = require('../../../fixtures/engine.fixture.js');
function createHistogramInternalModelMock (options) {
options = options || {};
_.extend({
data: null,
nulls: null
}, options);
var internalModelMock = {
set: function () {},
get: function () {},
getUnfilteredDataModel: function () {},
getUnfilteredData: function () {
return [
{
freq: 23
}, {
freq: 46
}, {
}
];
}
};
spyOn(internalModelMock, 'set');
spyOn(internalModelMock, 'get').and.callFake(function (key) {
if (key === 'data') {
return options.data;
}
if (key === 'nulls') {
return options.nulls;
}
if (key === 'totalAmount') {
return 7654;
}
});
spyOn(internalModelMock, 'getUnfilteredDataModel').and.returnValue({
get: function (key) {
if (key === 'nulls') {
return 12;
}
if (key === 'totalAmount') {
return 707;
}
}
});
_.extend(internalModelMock, Backbone.Events);
return internalModelMock;
}
function createSourceMock () {
return new carto.source.Dataset('ne_10m_populated_places_simple');
}
describe('api/v4/dataview/histogram', function () {
var source = createSourceMock();
describe('initialization', function () {
it('source must be provided', function () {
var test = function () {
new carto.dataview.Histogram(); // eslint-disable-line no-new
};
expect(test).toThrowError(Error, 'Source property is required.');
});
it('column must be provided', function () {
var test = function () {
new carto.dataview.Histogram(source); // eslint-disable-line no-new
};
expect(test).toThrowError(Error, 'Column property is required.');
});
it('options set to default if not provided', function () {
var column = 'population';
var dataview = new carto.dataview.Histogram(source, column);
expect(dataview._bins).toEqual(10);
});
it('options set to the provided value', function () {
var dataview = new carto.dataview.Histogram(source, 'population', {
bins: 808
});
expect(dataview._bins).toEqual(808);
});
it('throw error if bins is not a positive integer value', function () {
var test = function () {
new carto.dataview.Histogram(source, 'population', { // eslint-disable-line no-new
bins: 0
});
};
expect(test).toThrowError(Error, 'Bins must be a positive integer value.');
});
it('throw error if start is present but not end', function () {
var test = function () {
new carto.dataview.Histogram(source, 'population', { // eslint-disable-line no-new
start: 10
});
};
expect(test).toThrowError(Error, 'Both start and end values must be a number or null.');
});
it('throw error if end is present but not start', function () {
var test = function () {
new carto.dataview.Histogram(source, 'population', { // eslint-disable-line no-new
end: 10
});
};
expect(test).toThrowError(Error, 'Both start and end values must be a number or null.');
});
});
describe('.getData', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Histogram(source, 'population');
});
it('returns null if there is no internalModel', function () {
var data = dataview.getData();
expect(data).toBeNull();
});
it('returns parsed data from the internal model', function () {
dataview._internalModel = createHistogramInternalModelMock({
data: [
{
freq: 35
}, {
freq: 50
}, {
}
],
nulls: 42
});
var data = dataview.getData();
expect(data.bins.length).toBe(3);
expect(data.bins[0].freq).toBe(35);
expect(data.bins[1].freq).toBe(50);
expect(data.bins[2].freq).toBeUndefined();
expect(data.bins[0].normalized).toBe(0.7);
expect(data.bins[1].normalized).toBe(1);
expect(data.bins[2].normalized).toBe(0);
expect(data.nulls).toBe(42);
expect(data.totalAmount).toBe(7654);
});
it('returns nulls as 0 in case the internal model has no nulls', function () {
dataview._internalModel = createHistogramInternalModelMock({
data: [
{
freq: 35
}, {
freq: 50
}, {
}
],
nulls: undefined
});
var data = dataview.getData();
expect(data.bins.length).toBe(3);
expect(data.bins[0].freq).toBe(35);
expect(data.bins[1].freq).toBe(50);
expect(data.bins[2].freq).toBeUndefined();
expect(data.bins[0].normalized).toBe(0.7);
expect(data.bins[1].normalized).toBe(1);
expect(data.bins[2].normalized).toBe(0);
expect(data.nulls).toBe(0);
expect(data.totalAmount).toBe(7654);
});
it('returns null if internal model has no data', function () {
dataview._internalModel = createHistogramInternalModelMock();
var data = dataview.getData();
expect(data).toBe(null);
});
});
describe('.setBins', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Histogram(source, 'population');
});
it('should validate bins', function () {
var test = function () {
dataview.setBins(-1);
};
expect(test).toThrowError(Error, 'Bins must be a positive integer value.');
});
it('should throw error if called with a float number', function () {
var test = function () {
dataview.setBins(15.7);
};
expect(test).toThrowError(Error, 'Bins must be a positive integer value.');
});
it('should set bins to internal model as well', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.setBins(16);
expect(dataview._internalModel.set).toHaveBeenCalledWith('bins', 16);
expect(dataview.getBins()).toBe(16); // We assert .getBins() as well
// Clean
dataview._internalModel = null;
});
it('should Trigger a binsChanged event when the bins are changed', function () {
var binsChangedSpy = jasmine.createSpy('binsChangedSpy');
dataview.on('binsChanged', binsChangedSpy);
expect(binsChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setBins(11);
expect(binsChangedSpy).toHaveBeenCalledWith(11);
});
});
describe('.setStartEnd', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Histogram(source, 'population');
});
it('should throw an error if only start is present', function () {
var test = function () {
dataview.setStartEnd(20, null);
};
expect(test).toThrowError(Error, 'Both start and end values must be a number or null.');
});
it('should throw an error if only end is present', function () {
var test = function () {
dataview.setStartEnd(null, 30);
};
expect(test).toThrowError(Error, 'Both start and end values must be a number or null.');
});
it('should set start and end with a number', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.setStartEnd(20, 30);
expect(dataview._internalModel.set).toHaveBeenCalledWith({ start: 20, end: 30 });
expect(dataview.getStart()).toBe(20); // We assert .getStart() as well
expect(dataview.getEnd()).toBe(30); // We assert .getEnd() as well
// Clean
dataview._internalModel = null;
});
it('should set start and end with null', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.setStartEnd(null, null);
expect(dataview._internalModel.set).toHaveBeenCalledWith({ start: null, end: null });
expect(dataview.getStart()).toBe(undefined); // We assert .getStart() as well
expect(dataview.getEnd()).toBe(undefined); // We assert .getEnd() as well
// Clean
dataview._internalModel = null;
});
it('should trigger a startChanged event when the start is changed', function () {
var startChangedSpy = jasmine.createSpy('startChangedSpy');
dataview.on('startChanged', startChangedSpy);
expect(startChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setStartEnd(20, 30);
expect(startChangedSpy).toHaveBeenCalledWith(20);
});
it('should trigger a endChanged event when the end is changed', function () {
var endChangedSpy = jasmine.createSpy('endChangedSpy');
dataview.on('endChanged', endChangedSpy);
expect(endChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setStartEnd(20, 30);
expect(endChangedSpy).toHaveBeenCalledWith(30);
});
});
describe('.$setEngine', function () {
var engine;
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Histogram(source, 'population');
engine = createEngine();
});
it('creates the internal model', function () {
dataview.disable(); // To test that it passes the ._enabled property to the internal model
dataview.setBins(15);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel.get('source')).toBe(dataview._source.$getInternalModel());
expect(internalModel.get('column')).toEqual(dataview._column);
expect(internalModel.get('bins')).toBe(15);
expect(internalModel.isEnabled()).toBe(false);
expect(internalModel._engine).toBe(engine);
});
it('calling twice to $setEngine does not create another internalModel', function () {
spyOn(dataview, '_createInternalModel').and.callThrough();
dataview.$setEngine(engine);
dataview.$setEngine(engine);
expect(dataview._createInternalModel.calls.count()).toBe(1);
});
describe('spatial filters', function () {
it('creates the internal model with BoundingBox filter if provided', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeDefined();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(true);
});
it('allows removing a BoundingBox filter', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeNull();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(false);
});
it('creates the internal model with Circle filter if provided', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeDefined();
expect(internalModel.syncsOnCircleChanges()).toBe(true);
});
it('allows removing a Circle filter', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeNull();
expect(internalModel.syncsOnCircleChanges()).toBe(false);
});
it('creates the internal model with Polygon filter if provided', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeDefined();
expect(internalModel.syncsOnPolygonChanges()).toBe(true);
});
it('allows removing a Polygon filter', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeNull();
expect(internalModel.syncsOnPolygonChanges()).toBe(false);
});
});
});
describe('.getDistributionType', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.Histogram(source, 'population');
});
it('should return null if there is no internal model', function () {
var distribution = dataview.getDistributionType();
expect(distribution).toBe(null);
});
it('should call to the proper method in internal model', function () {
var internalModel = {
getData: function () {},
getDistributionType: function () {}
};
spyOn(internalModel, 'getData').and.returnValue('token');
spyOn(internalModel, 'getDistributionType').and.returnValue('a');
dataview._internalModel = internalModel;
var distribution = dataview.getDistributionType();
expect(internalModel.getDistributionType).toHaveBeenCalledWith('token');
expect(distribution).toEqual('a');
});
});
});

View File

@@ -0,0 +1,427 @@
var Backbone = require('backbone');
var _ = require('underscore');
var carto = require('../../../../../src/api/v4/index');
var createEngine = require('../../../fixtures/engine.fixture.js');
function createHistogramInternalModelMock (options) {
options = options || {};
_.extend({
data: null,
nulls: null
}, options);
var internalModelMock = {
set: function () {},
get: function () {},
getUnfilteredDataModel: function () {},
getUnfilteredData: function () {
return [
{
freq: 23
}, {
freq: 46
}, {
}
];
},
getCurrentOffset: function () {
return 7200;
}
};
spyOn(internalModelMock, 'set');
spyOn(internalModelMock, 'get').and.callFake(function (key) {
if (key === 'data') {
return options.data;
}
if (key === 'nulls') {
return options.nulls;
}
if (key === 'totalAmount') {
return 7654;
}
});
spyOn(internalModelMock, 'getUnfilteredDataModel').and.returnValue({
get: function (key) {
if (key === 'nulls') {
return 12;
}
if (key === 'totalAmount') {
return 707;
}
}
});
_.extend(internalModelMock, Backbone.Events);
return internalModelMock;
}
function createSourceMock () {
return new carto.source.Dataset('ne_10m_populated_places_simple');
}
describe('api/v4/dataview/time-series', function () {
var source = createSourceMock();
describe('initialization', function () {
it('source must be provided', function () {
var test = function () {
new carto.dataview.TimeSeries(); // eslint-disable-line no-new
};
expect(test).toThrowError(Error, 'Source property is required.');
});
it('column must be provided', function () {
var test = function () {
new carto.dataview.TimeSeries(source); // eslint-disable-line no-new
};
expect(test).toThrowError(Error, 'Column property is required.');
});
it('options set to default if not provided', function () {
var column = 'population';
var dataview = new carto.dataview.TimeSeries(source, column);
expect(dataview._aggregation).toEqual(carto.dataview.timeAggregation.AUTO);
expect(dataview._offset).toBe(0);
expect(dataview._localTimezone).toBe(false);
});
it('options set to the provided value', function () {
var dataview = new carto.dataview.TimeSeries(source, 'population', {
aggregation: carto.dataview.timeAggregation.QUARTER,
offset: -7,
useLocalTimezone: true
});
expect(dataview._aggregation).toEqual(carto.dataview.timeAggregation.QUARTER);
expect(dataview._offset).toBe(-7 * 3600); // Internaly stored in seconds
expect(dataview._localTimezone).toBe(true);
});
it('throw error if aggregation is not a proper value', function () {
var test = function () {
new carto.dataview.TimeSeries(source, 'population', { // eslint-disable-line no-new
aggregation: 'terasecond'
});
};
expect(test).toThrowError(Error, 'Time aggregation must be a valid value. Use carto.dataview.timeAggregation.');
});
it('throw error if offset is not a valid hour', function () {
var test = function () {
new carto.dataview.TimeSeries(source, 'population', { // eslint-disable-line no-new
offset: 34
});
};
expect(test).toThrowError(Error, 'Offset must an integer value between -12 and 14.');
test = function () {
new carto.dataview.TimeSeries(source, 'population', { // eslint-disable-line no-new
offset: 10.45
});
};
expect(test).toThrowError(Error, 'Offset must an integer value between -12 and 14.');
});
it('throw error if localTimezone is not a binary value', function () {
var test = function () {
new carto.dataview.TimeSeries(source, 'population', { // eslint-disable-line no-new
useLocalTimezone: 'Los Angeles'
});
};
expect(test).toThrowError(Error, 'useLocalTimezone must be a boolean value.');
});
});
describe('.getData', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.TimeSeries(source, 'population');
});
it('returns null if there is no internalModel', function () {
var data = dataview.getData();
expect(data).toBeNull();
});
it('returns parsed data from the internal model', function () {
dataview._internalModel = createHistogramInternalModelMock({
data: [
{
freq: 35
}, {
freq: 50
}, {
}
],
nulls: 42
});
var data = dataview.getData();
expect(data.bins.length).toBe(3);
expect(data.bins[0].freq).toBe(35);
expect(data.bins[1].freq).toBe(50);
expect(data.bins[2].freq).toBeUndefined();
expect(data.bins[0].normalized).toBe(0.7);
expect(data.bins[1].normalized).toBe(1);
expect(data.bins[2].normalized).toBe(0);
expect(data.nulls).toBe(42);
expect(data.totalAmount).toBe(7654);
expect(data.offset).toBe(2);
});
it('returns nulls as 0 in case the internal model has no nulls', function () {
dataview._internalModel = createHistogramInternalModelMock({
data: [
{
freq: 35
}, {
freq: 50
}, {
}
],
nulls: undefined
});
var data = dataview.getData();
expect(data.bins.length).toBe(3);
expect(data.bins[0].freq).toBe(35);
expect(data.bins[1].freq).toBe(50);
expect(data.bins[2].freq).toBeUndefined();
expect(data.bins[0].normalized).toBe(0.7);
expect(data.bins[1].normalized).toBe(1);
expect(data.bins[2].normalized).toBe(0);
expect(data.nulls).toBe(0);
expect(data.totalAmount).toBe(7654);
});
it('returns null if internal model has no data', function () {
dataview._internalModel = createHistogramInternalModelMock();
var data = dataview.getData();
expect(data).toBe(null);
});
});
describe('.setAggregation', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.TimeSeries(source, 'population');
});
it('should validate aggregation', function () {
var test = function () {
dataview.setAggregation('terasecond');
};
expect(test).toThrowError(Error, 'Time aggregation must be a valid value. Use carto.dataview.timeAggregation.');
});
it('should set aggregation to internal model as well', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.setAggregation(carto.dataview.timeAggregation.HOUR);
expect(dataview._internalModel.set).toHaveBeenCalledWith('aggregation', carto.dataview.timeAggregation.HOUR);
expect(dataview.getAggregation()).toBe('hour');
// Clean
dataview._internalModel = null;
});
it('should Trigger a aggregationChanged event when the aggregation are changed', function () {
var aggregationChangedSpy = jasmine.createSpy('aggregationChangedSpy');
dataview.on('aggregationChanged', aggregationChangedSpy);
expect(aggregationChangedSpy).not.toHaveBeenCalled();
dataview.$setEngine(createEngine());
dataview.setAggregation(carto.dataview.timeAggregation.HOUR);
expect(aggregationChangedSpy).toHaveBeenCalledWith(carto.dataview.timeAggregation.HOUR);
});
});
describe('.setOffset', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.TimeSeries(source, 'population');
});
it('should validate offset', function () {
var test = function () {
dataview.setOffset(32);
};
expect(test).toThrowError(Error, 'Offset must an integer value between -12 and 14.');
test = function () {
dataview.setOffset(10.7);
};
expect(test).toThrowError(Error, 'Offset must an integer value between -12 and 14.');
});
it('should set offset to internal model as well translated to seconds', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.setOffset(-5);
expect(dataview._internalModel.set).toHaveBeenCalledWith('offset', -5 * 3600);
expect(dataview.getOffset()).toBe(-5);
// Clean
dataview._internalModel = null;
});
});
describe('.useLocalTimezone', function () {
var dataview;
beforeEach(function () {
dataview = new carto.dataview.TimeSeries(source, 'population');
});
it('should validate localTimezone', function () {
var test = function () {
dataview.useLocalTimezone('Compton');
};
expect(test).toThrowError(Error, 'useLocalTimezone must be a boolean value.');
});
it('should set localTimezone to internal model as well', function () {
dataview._internalModel = createHistogramInternalModelMock();
dataview.useLocalTimezone(true);
expect(dataview._internalModel.set).toHaveBeenCalledWith('localTimezone', true);
expect(dataview.isUsingLocalTimezone()).toBe(true);
// Clean
dataview._internalModel = null;
});
});
describe('.$setEngine', function () {
var engine;
var dataview;
beforeEach(function () {
dataview = new carto.dataview.TimeSeries(source, 'population');
engine = createEngine();
});
it('creates the internal model', function () {
dataview.disable(); // To test that it passes the ._enabled property to the internal model
dataview.setAggregation(carto.dataview.timeAggregation.WEEK);
dataview.setOffset(6);
dataview.useLocalTimezone(true);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel.get('source')).toBe(dataview._source.$getInternalModel());
expect(internalModel.get('column')).toEqual(dataview._column);
expect(internalModel.get('aggregation')).toBe('week');
expect(internalModel.get('localTimezone')).toBe(true);
expect(internalModel.get('offset')).toBe(6 * 3600);
expect(internalModel.isEnabled()).toBe(false);
expect(internalModel._engine).toBe(engine);
});
it('calling twice to $setEngine does not create another internalModel', function () {
spyOn(dataview, '_createInternalModel').and.callThrough();
dataview.$setEngine(engine);
dataview.$setEngine(engine);
expect(dataview._createInternalModel.calls.count()).toBe(1);
});
describe('spatial filters', function () {
it('creates the internal model with BoundingBox filter if provided', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeDefined();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(true);
});
it('allows removing a BoundingBox filter', function () {
var filter = new carto.filter.BoundingBox();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._bboxFilter).toBeNull();
expect(internalModel.syncsOnBoundingBoxChanges()).toBe(false);
});
it('creates the internal model with Circle filter if provided', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeDefined();
expect(internalModel.syncsOnCircleChanges()).toBe(true);
});
it('allows removing a Circle filter', function () {
var filter = new carto.filter.Circle();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._circleFilter).toBeNull();
expect(internalModel.syncsOnCircleChanges()).toBe(false);
});
it('creates the internal model with Polygon filter if provided', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeDefined();
expect(internalModel.syncsOnPolygonChanges()).toBe(true);
});
it('allows removing a Polygon filter', function () {
var filter = new carto.filter.Polygon();
dataview.addFilter(filter);
dataview.$setEngine(engine);
expect(dataview.hasFilter(filter)).toBe(true);
dataview.removeFilter(filter);
expect(dataview.hasFilter(filter)).toBe(false);
var internalModel = dataview.$getInternalModel();
expect(internalModel._polygonFilter).toBeNull();
expect(internalModel.syncsOnPolygonChanges()).toBe(false);
});
});
});
});

View File

@@ -0,0 +1,49 @@
var getExtraFields = require('../../../../../src/api/v4/error-handling/carto-error-extender').getExtraFields;
describe('api/v4/error-handling/carto-error-extender', function () {
it('extending an error with no entry in the error list should return default values', function () {
var extendedError = getExtraFields({});
expect(extendedError.friendlyMessage).toEqual('');
expect(extendedError.errorCode).toEqual('unknown-error');
});
it('extending an error with an entry in the error list that needs a regex replacement should return proper code and friendly message', function () {
var error = {
origin: 'windshaft',
type: 'analysis',
message: 'relation "invalid_value" does not exist'
};
var extendedError = getExtraFields(error);
expect(extendedError.friendlyMessage).toEqual('Invalid dataset name used. Dataset "invalid_value" does not exist.');
expect(extendedError.errorCode).toEqual('windshaft:analysis:invalid-dataset');
});
it('extending an error with an entry that needs two regex replacements ($0 and $1) should return proper code and friendly message', function () {
var error = {
origin: 'validation',
type: 'layer',
message: 'wrongInteractivityColumns[column1, column2]#featureClick'
};
var extendedError = getExtraFields(error);
expect(extendedError.friendlyMessage).toEqual('Columns [column1, column2] set on `featureClick` do not match the columns set in aggregation options.');
expect(extendedError.errorCode).toEqual('validation:layer:wrong-interactivity-columns');
});
it('extending an error with an entry in the error list that does not have friendly message should return proper code and original message', function () {
var error = {
origin: 'windshaft',
type: 'analysis',
message: 'syntax error: thruster is not a valid SQL word'
};
var extendedError = getExtraFields(error);
expect(extendedError.friendlyMessage).toEqual('syntax error: thruster is not a valid SQL word');
expect(extendedError.errorCode).toEqual('windshaft:analysis:sql-syntax-error');
});
});

View File

@@ -0,0 +1,107 @@
var CartoError = require('../../../../../src/api/v4/error-handling/carto-error');
describe('v4/error-handling/carto-error', function () {
it('should return default values if the error is not qualified', function () {
var cartoError = new CartoError({
someProperty: 'some value'
});
expect(cartoError instanceof Error).toBe(true);
expect(cartoError.name).toEqual('CartoError');
expect(cartoError.message).toEqual('unexpected error');
expect(cartoError.origin).toEqual('generic');
expect(cartoError.type).toEqual('');
expect(cartoError.errorCode).toEqual('generic:unknown-error');
expect(cartoError.originalError).toEqual(jasmine.objectContaining({
someProperty: 'some value'
}));
});
describe('windshaft error', function () {
it('should return the original values and proper friendly message if it is a windshaft error', function () {
var cartoError = new CartoError({
origin: 'windshaft',
type: 'layer',
message: 'column "jonica" does not exist'
});
expect(cartoError instanceof Error).toBe(true);
expect(cartoError.name).toEqual('CartoError');
expect(cartoError.message).toEqual('Invalid column name. Column "jonica" does not exist.');
expect(cartoError.origin).toEqual('windshaft');
expect(cartoError.type).toEqual('layer');
expect(cartoError.errorCode).toEqual('windshaft:layer:column-does-not-exist');
expect(cartoError.originalError).toEqual(jasmine.objectContaining({
origin: 'windshaft',
type: 'layer',
message: 'column "jonica" does not exist'
}));
});
it('should return the source id if it is a windshaft analysis error and analysis is provided', function () {
var cartoError = new CartoError({
origin: 'windshaft',
type: 'analysis',
message: 'column "jonica" does not exist'
}, {
analysis: {
aKey: 'a value',
getId: function () { return 'S1'; }
}
});
expect(cartoError instanceof Error).toBe(true);
expect(cartoError.source).toBeDefined();
expect(cartoError.source.aKey).toEqual('a value');
expect(cartoError.sourceId).toEqual('S1');
});
it('should return the source id if the windshaft analysis error has that value', function () {
var cartoError = new CartoError({
origin: 'windshaft',
type: 'analysis',
message: 'column "jonica" does not exist',
analysisId: 'A1'
});
expect(cartoError instanceof Error).toBe(true);
expect(cartoError.sourceId).toEqual('A1');
});
});
it('should parse ajax error if original error is an ajax response', function () {
var cartoError = new CartoError({
responseText: '{ "errors": ["an error"] }',
statusText: '404'
});
expect(cartoError instanceof Error).toBe(true);
expect(cartoError.name).toEqual('CartoError');
expect(cartoError.message).toEqual('an error');
expect(cartoError.origin).toEqual('ajax');
expect(cartoError.type).toEqual('404');
expect(cartoError.errorCode).toEqual('ajax:404:unknown-error');
expect(cartoError.originalError).toEqual(jasmine.objectContaining({
responseText: '{ "errors": ["an error"] }',
statusText: '404'
}));
});
/**
* There's a bug in PhantomJS that don't fill the stack property
* of an error. We need to assert that the `stack` property gets
* defined so we restrict the spec to run in the browser.
*/
it('should create an error with a stack trace', function () {
var cartoError = new CartoError({
someProperty: 'some value'
});
var userAgent = navigator.userAgent;
if (userAgent.toLowerCase().indexOf('phantomjs') < 0) {
expect(cartoError.stack).toBeDefined();
} else {
expect(true).toBe(true);
}
});
});

View File

@@ -0,0 +1,12 @@
var CartoValidationError = require('../../../../../src/api/v4/error-handling/carto-validation-error');
describe('v4/error-handling/carto-validation-error', function () {
it('should return a CartoError with validation as type', function () {
var validationError = new CartoValidationError('layer', 'a message');
expect(validationError.name).toEqual('CartoError');
expect(validationError.origin).toEqual('validation');
expect(validationError.type).toEqual('layer');
expect(validationError.message).toEqual('a message');
});
});

View File

@@ -0,0 +1,250 @@
const SQLBase = require('../../../../../src/api/v4/filter/base-sql');
const PARAMETER_SPECIFICATION = {
in: { parameters: [{ name: 'in', allowedTypes: ['Array', 'String'] }] },
notIn: { parameters: [{ name: 'notIn', allowedTypes: ['Array', 'String'] }] },
like: { parameters: [{ name: 'like', allowedTypes: ['Array', 'String'] }] }
};
const SQL_TEMPLATES = {
'in': '<%= column %> IN (<%= value %>)',
'like': '<%= column %> LIKE <%= value %>'
};
const column = 'fake_column';
describe('api/v4/filter/base-sql', function () {
describe('constructor', function () {
it('should throw a descriptive error when column is undefined, not a string, or empty', function () {
expect(function () {
new SQLBase(undefined); // eslint-disable-line
}).toThrowError('Column property is required.');
expect(function () {
new SQLBase(1); // eslint-disable-line
}).toThrowError('Column property must be a string.');
expect(function () {
new SQLBase(''); // eslint-disable-line
}).toThrowError('Column property must be not empty.');
});
it('should throw a descriptive error when there is an invalid option', function () {
expect(function () {
new SQLBase('fake_column', { unknown_option: false }); // eslint-disable-line
}).toThrowError("'unknown_option' is not a valid option for this filter.");
});
it('should set column and options as class properties', function () {
const options = { includeNull: true };
const sqlFilter = new SQLBase(column, options);
expect(sqlFilter._column).toBe(column);
expect(sqlFilter._options).toBe(options);
});
});
describe('.set', function () {
it('should throw a descriptive error when an unknown filter has been passed', function () {
const sqlFilter = new SQLBase('fake_column');
expect(function () {
sqlFilter.set('unknown_filter', 'test_filter');
}).toThrowError("'unknown_filter' is not a valid filter. Please check documentation.");
});
it('should set the new filter to the filters object', function () {
const sqlFilter = new SQLBase(column);
sqlFilter.ALLOWED_FILTERS = ['in'];
sqlFilter.PARAMETER_SPECIFICATION = { in: PARAMETER_SPECIFICATION.in };
sqlFilter.set('in', ['test_filter']);
expect(sqlFilter._filters).toEqual({ in: ['test_filter'] });
});
it("should trigger a 'change:filters' event", function () {
const spy = jasmine.createSpy();
const sqlFilter = new SQLBase('fake_column');
sqlFilter.ALLOWED_FILTERS = ['in'];
sqlFilter.PARAMETER_SPECIFICATION = { in: PARAMETER_SPECIFICATION.in };
sqlFilter.on('change:filters', spy);
sqlFilter.set('in', ['test_filter']);
expect(spy).toHaveBeenCalled();
});
});
describe('.setFilters', function () {
it('should throw a descriptive error when an unknown filter has been passed', function () {
const sqlFilter = new SQLBase('fake_column');
expect(function () {
sqlFilter.setFilters({ unknown_filter: 'test_filter' });
}).toThrowError("'unknown_filter' is not a valid filter. Please check documentation.");
});
it('should set the new filters and override previous ones', function () {
const newFilters = { notIn: 'test_filter2' };
const sqlFilter = new SQLBase(column);
sqlFilter.ALLOWED_FILTERS = ['in', 'notIn'];
sqlFilter.PARAMETER_SPECIFICATION = {
in: PARAMETER_SPECIFICATION.in,
notIn: PARAMETER_SPECIFICATION.notIn
};
sqlFilter.set('in', ['test_filter']);
sqlFilter.setFilters(newFilters);
expect(sqlFilter._filters).toEqual(newFilters);
});
it("should trigger a 'change:filters' event", function () {
const newFilters = { notIn: 'test_filter2' };
const spy = jasmine.createSpy();
const sqlFilter = new SQLBase(column);
sqlFilter.ALLOWED_FILTERS = ['in', 'notIn'];
sqlFilter.PARAMETER_SPECIFICATION = {
in: PARAMETER_SPECIFICATION.in,
notIn: PARAMETER_SPECIFICATION.notIn
};
sqlFilter.set('in', ['test_filter']);
sqlFilter.on('change:filters', spy);
sqlFilter.setFilters(newFilters);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('.resetFilters', function () {
it('should reset applied filters', function () {
const sqlFilter = new SQLBase(column);
sqlFilter.ALLOWED_FILTERS = ['in'];
sqlFilter.PARAMETER_SPECIFICATION = { in: PARAMETER_SPECIFICATION.in };
sqlFilter.set('in', ['test_filter']);
sqlFilter.resetFilters();
expect(sqlFilter._filters).toEqual({});
});
});
describe('.$getSQL', function () {
it('should return SQL string containing all the filters joined by AND clause', function () {
const sqlFilter = new SQLBase(column);
sqlFilter.ALLOWED_FILTERS = ['in', 'like'];
sqlFilter.PARAMETER_SPECIFICATION = {
in: PARAMETER_SPECIFICATION.in,
like: PARAMETER_SPECIFICATION.like
};
sqlFilter.SQL_TEMPLATES = {
in: SQL_TEMPLATES.in,
like: SQL_TEMPLATES.like
};
sqlFilter.setFilters({ in: ['category 1', 'category 2'], like: '%category%' });
expect(sqlFilter.$getSQL()).toBe("(fake_column IN ('category 1','category 2') AND fake_column LIKE '%category%')");
});
it('should call _includeNullInQuery if includeNull option is set', function () {
const sqlFilter = new SQLBase(column, { includeNull: true });
sqlFilter.ALLOWED_FILTERS = ['in'];
sqlFilter.PARAMETER_SPECIFICATION = { in: PARAMETER_SPECIFICATION.in };
sqlFilter.SQL_TEMPLATES = { in: SQL_TEMPLATES.in };
sqlFilter.setFilters({ in: ['category 1', 'category 2'] });
spyOn(sqlFilter, '_includeNullInQuery');
sqlFilter.$getSQL();
expect(sqlFilter._includeNullInQuery).toHaveBeenCalled();
});
});
describe('.checkFilters', function () {
let sqlFilter;
beforeEach(function () {
sqlFilter = new SQLBase(column);
});
it('should throw an error when an invalid filter is passed', function () {
expect(function () {
sqlFilter._checkFilters({ unknown_filter: 'filter' });
}).toThrowError("'unknown_filter' is not a valid filter. Please check documentation.");
});
it("should throw an error when there's a type mismatching in filter parameters", function () {
expect(function () {
sqlFilter.ALLOWED_FILTERS = ['in'];
sqlFilter.PARAMETER_SPECIFICATION = { in: PARAMETER_SPECIFICATION.in };
sqlFilter._checkFilters({ in: 1 });
}).toThrowError("Invalid parameter type for 'in'. Please check filters documentation.");
});
});
describe('._convertValueToSQLString', function () {
it('should format date to ISO8601 string', function () {
const sqlFilter = new SQLBase(column);
const fakeDate = new Date('Thu Jun 28 2018 15:04:31 GMT+0200 (Central European Summer Time)');
expect(sqlFilter._convertValueToSQLString(fakeDate)).toBe('\'2018-06-28T13:04:31.000Z\'');
});
it('should convert array to a comma-separated string wrapped by single comma', function () {
const sqlFilter = new SQLBase(column);
const fakeArray = ['Element 1', 'Element 2'];
expect(sqlFilter._convertValueToSQLString(fakeArray)).toBe("'Element 1','Element 2'");
});
it('should return number without modifying', function () {
const sqlFilter = new SQLBase(column);
expect(sqlFilter._convertValueToSQLString(1)).toBe(1);
});
it('should return object with string values without modifying', function () {
const sqlFilter = new SQLBase(column);
const fakeObject = { fakeProperty: 'fakeValue' };
expect(sqlFilter._convertValueToSQLString(fakeObject)).toEqual(fakeObject);
});
it('should return object with date values parsed properly', function () {
const sqlFilter = new SQLBase(column);
const fakeObject = { fakeDate: new Date('2014-01-01T00:00:00.00Z') };
expect(sqlFilter._convertValueToSQLString(fakeObject)).toEqual({ fakeDate: '\'2014-01-01T00:00:00.000Z\'' });
});
it('should wrap strings in single-quotes', function () {
const sqlFilter = new SQLBase(column);
const fakeString = 'fake_string';
expect(sqlFilter._convertValueToSQLString(fakeString)).toBe(`'${fakeString}'`);
});
});
describe('._interpolateFilterIntoTemplate', function () {
it('should inject filter values into SQL template', function () {
const sqlFilter = new SQLBase(column);
sqlFilter.SQL_TEMPLATES = {
gte: '<%= column %> > <%= value %>'
};
expect(sqlFilter._interpolateFilter('gte', 10)).toBe('fake_column > 10');
});
});
});

View File

@@ -0,0 +1,11 @@
var carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/bounding-box-gmaps', function () {
describe('constructor', function () {
it('should throw a descriptive error when initialized with invalid parameters', function () {
expect(function () {
new carto.filter.BoundingBoxGoogleMaps(undefined); // eslint-disable-line
}).toThrowError('Bounding box requires a Google Maps map but got: undefined');
});
});
});

View File

@@ -0,0 +1,11 @@
var carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/bounding-box-leaflet', function () {
describe('constructor', function () {
it('should throw a descriptive error when initialized with invalid parameters', function () {
expect(function () {
new carto.filter.BoundingBoxLeaflet(undefined); // eslint-disable-line
}).toThrowError('Bounding box requires a Leaflet map but got: undefined');
});
});
});

View File

@@ -0,0 +1,50 @@
var carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/bounding-box', function () {
describe('initialization', function () {
it('should create the internalModel', function () {
var bboxFilter = new carto.filter.BoundingBox();
expect(bboxFilter.$getInternalModel()).toBeDefined();
});
});
describe('.setBounds', function () {
var bboxFilter;
beforeEach(function () {
bboxFilter = new carto.filter.BoundingBox();
});
it('checks if bounds are valid', function () {
var test = function () {
bboxFilter.setBounds({ west: 0 });
};
expect(test).toThrowError(Error, 'Bounds object is not valid. Use a carto.filter.Bounds object');
});
it('if bounds are valid, it assigns it to property, triggers the boundsChanged event and returns this', function () {
spyOn(bboxFilter, 'trigger');
var bounds = { west: 1, south: 2, east: 3, north: 4 };
var returnedObject = bboxFilter.setBounds(bounds);
expect(bboxFilter.getBounds()).toEqual(bounds);
expect(bboxFilter.trigger).toHaveBeenCalledWith('boundsChanged', bounds);
expect(returnedObject).toBe(bboxFilter);
});
});
describe('.resetBounds', function () {
it('sets the bounds to 0,0,0,0', function () {
var bboxFilter = new carto.filter.BoundingBox();
bboxFilter.setBounds({ west: 1, south: 2, east: 3, north: 4 });
expect(bboxFilter.getBounds()).toEqual({ west: 1, south: 2, east: 3, north: 4 });
bboxFilter.resetBounds();
expect(bboxFilter.getBounds()).toEqual({ west: 0, south: 0, east: 0, north: 0 });
});
});
});

View File

@@ -0,0 +1,63 @@
const carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/category', function () {
describe('constructor', function () {
it('should throw a descriptive error when an unknown filter has been passed', function () {
expect(function () {
new carto.filter.Category('fake_column', { unknown_filter: '' }); // eslint-disable-line
}).toThrowError("'unknown_filter' is not a valid filter. Please check documentation.");
});
});
describe('SQL Templates', function () {
it('IN', function () {
const categoryFilter = new carto.filter.Category('fake_column', { in: ['Category 1'] });
expect(categoryFilter.$getSQL()).toBe("fake_column IN ('Category 1')");
});
it('IN with subquery', function () {
const categoryFilter = new carto.filter.Category('fake_column', { in: { query: 'SELECT name FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column IN (SELECT name FROM neighbourhoods)');
});
it('NOT IN', function () {
const categoryFilter = new carto.filter.Category('fake_column', { notIn: ['Category 1'] });
expect(categoryFilter.$getSQL()).toBe("fake_column NOT IN ('Category 1')");
});
it('NOT IN with subquery', function () {
const categoryFilter = new carto.filter.Category('fake_column', { notIn: { query: 'SELECT name FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column NOT IN (SELECT name FROM neighbourhoods)');
});
it('EQ', function () {
const categoryFilter = new carto.filter.Category('fake_column', { eq: 'Category 1' });
expect(categoryFilter.$getSQL()).toBe("fake_column = 'Category 1'");
});
it('EQ with subquery', function () {
const categoryFilter = new carto.filter.Category('fake_column', { eq: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column = (SELECT avg(price) FROM neighbourhoods)');
});
it('NOT EQ', function () {
const categoryFilter = new carto.filter.Category('fake_column', { notEq: 'Category 1' });
expect(categoryFilter.$getSQL()).toBe("fake_column != 'Category 1'");
});
it('NOT EQ with subquery', function () {
const categoryFilter = new carto.filter.Category('fake_column', { notEq: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column != (SELECT avg(price) FROM neighbourhoods)');
});
it('LIKE', function () {
const categoryFilter = new carto.filter.Category('fake_column', { like: '%Category%' });
expect(categoryFilter.$getSQL()).toBe("fake_column LIKE '%Category%'");
});
it('SIMILAR TO', function () {
const categoryFilter = new carto.filter.Category('fake_column', { similarTo: '%Category%' });
expect(categoryFilter.$getSQL()).toBe("fake_column SIMILAR TO '%Category%'");
});
});
});

View File

@@ -0,0 +1,56 @@
var carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/circle', function () {
describe('initialization', function () {
it('should create the internalModel', function () {
var circleFilter = new carto.filter.Circle();
expect(circleFilter.$getInternalModel()).toBeDefined();
});
});
describe('.setCircle', function () {
var circleFilter;
beforeEach(function () {
circleFilter = new carto.filter.Circle();
});
it('checks if circle is valid', function () {
var test = function () {
circleFilter.setCircle({ lng: 0 });
};
var test2 = function () {
circleFilter.setCircle({ lng: 'a' });
};
const expected = 'Circle object is not valid. Use a carto.filter.CircleData object';
expect(test).toThrowError(Error, expected);
expect(test2).toThrowError(Error, expected);
});
it('if circle is valid, it assigns it to property, triggers the circleChanged event and returns this', function () {
spyOn(circleFilter, 'trigger');
var circle = { lat: 1, lng: 2, radius: 3 };
var returnedObject = circleFilter.setCircle(circle);
expect(circleFilter.getCircle()).toEqual(circle);
expect(circleFilter.trigger).toHaveBeenCalledWith('circleChanged', circle);
expect(returnedObject).toBe(circleFilter);
});
});
describe('.resetCircle', function () {
it('sets the circle to 0,0,0', function () {
var circleFilter = new carto.filter.Circle();
circleFilter.setCircle({ lat: 1, lng: 2, radius: 3 });
expect(circleFilter.getCircle()).toEqual({ lat: 1, lng: 2, radius: 3 });
circleFilter.resetCircle();
expect(circleFilter.getCircle()).toEqual({ lat: 0, lng: 0, radius: 0 });
});
});
});

View File

@@ -0,0 +1,174 @@
const FiltersCollection = require('../../../../../src/api/v4/filter/filters-collection');
const carto = require('../../../../../src/api/v4/index');
const column = 'fake_column';
describe('api/v4/filter/filters-collection', function () {
describe('constructor', function () {
it('should call _initialize', function () {
spyOn(FiltersCollection.prototype, '_initialize');
new FiltersCollection(); // eslint-disable-line
expect(FiltersCollection.prototype._initialize).toHaveBeenCalled();
});
});
describe('._initialize', function () {
it('should set an empty array to filters', function () {
const filtersCollection = new FiltersCollection();
expect(filtersCollection._filters).toEqual([]);
});
it('should set provided filters and call add()', function () {
spyOn(FiltersCollection.prototype, 'addFilter').and.callThrough();
const filters = [
new carto.filter.Range(column, { lt: 1 }),
new carto.filter.Category(column, { in: ['category'] })
];
const filtersCollection = new FiltersCollection(filters);
expect(filtersCollection._filters).toEqual(filters);
expect(FiltersCollection.prototype.addFilter).toHaveBeenCalledTimes(2);
});
});
describe('.addFilter', function () {
let filtersCollection, rangeFilter, triggerFilterChangeSpy, listenToChangeSpy;
beforeEach(function () {
triggerFilterChangeSpy = spyOn(FiltersCollection.prototype, '_triggerFilterChange');
listenToChangeSpy = spyOn(FiltersCollection.prototype, 'listenTo');
filtersCollection = new FiltersCollection();
rangeFilter = new carto.filter.Range(column, { lt: 1 });
});
it('should throw an error if filter is not an instance of SQLBase or FiltersCollection', function () {
expect(function () {
filtersCollection.addFilter({});
}).toThrowError('Filters need to extend from carto.filter.SQLBase. Please use carto.filter.Category or carto.filter.Range.');
});
it('should not readd a filter if it is already added', function () {
filtersCollection.addFilter(rangeFilter);
filtersCollection.addFilter(rangeFilter);
expect(filtersCollection._filters.length).toBe(1);
expect(triggerFilterChangeSpy).toHaveBeenCalledTimes(1);
});
it('should add new filter and trigger change:filters event', function () {
filtersCollection.addFilter(rangeFilter);
expect(filtersCollection._filters.length).toBe(1);
expect(triggerFilterChangeSpy).toHaveBeenCalledTimes(1);
});
it('should register listener to change:filters event in the added filter', function () {
filtersCollection.addFilter(rangeFilter);
expect(listenToChangeSpy).toHaveBeenCalled();
expect(listenToChangeSpy.calls.mostRecent().args[0]).toBe(rangeFilter);
expect(listenToChangeSpy.calls.mostRecent().args[1]).toEqual('change:filters');
});
});
describe('.removeFilter', function () {
let filtersCollection, rangeFilter, triggerFilterChangeSpy;
beforeEach(function () {
triggerFilterChangeSpy = spyOn(FiltersCollection.prototype, '_triggerFilterChange');
filtersCollection = new FiltersCollection();
rangeFilter = new carto.filter.Range(column, { lt: 1 });
});
it('should not remove the filter if it was not already added', function () {
const removedElement = filtersCollection.removeFilter(rangeFilter);
expect(removedElement).toBeUndefined();
expect(triggerFilterChangeSpy).not.toHaveBeenCalled();
});
it('should remove the filter if it was already added', function () {
filtersCollection.addFilter(rangeFilter);
const removedElement = filtersCollection.removeFilter(rangeFilter);
expect(removedElement).toBe(rangeFilter);
expect(triggerFilterChangeSpy).toHaveBeenCalled();
});
});
describe('.count', function () {
let filtersCollection, rangeFilter;
beforeEach(function () {
filtersCollection = new FiltersCollection();
rangeFilter = new carto.filter.Range(column, { lt: 1 });
filtersCollection.addFilter(rangeFilter);
});
it('should return filters length', function () {
expect(filtersCollection.count()).toBe(1);
});
});
describe('.getFilters', function () {
let filtersCollection, rangeFilter;
beforeEach(function () {
filtersCollection = new FiltersCollection();
rangeFilter = new carto.filter.Range(column, { lt: 1 });
filtersCollection.addFilter(rangeFilter);
});
it('should return added filters', function () {
expect(filtersCollection.getFilters()).toEqual([rangeFilter]);
});
});
describe('.$getSQL', function () {
let filtersCollection;
beforeEach(function () {
let rangeFilter = new carto.filter.Range(column, { lt: 1 });
let categoryFilter = new carto.filter.Category(column, { in: ['category'] });
filtersCollection = new FiltersCollection();
filtersCollection.addFilter(rangeFilter);
filtersCollection.addFilter(categoryFilter);
});
it('should build the SQL string and join filters', function () {
expect(filtersCollection.$getSQL()).toEqual("(fake_column < 1 AND fake_column IN ('category'))");
});
it('should not take empty filters into account', function () {
let customRangeFilter = new carto.filter.Range(column, { lt: 1 });
let emptyFilter = new carto.filter.Category(column, {});
filtersCollection = new FiltersCollection();
filtersCollection.addFilter(customRangeFilter);
filtersCollection.addFilter(emptyFilter);
expect(filtersCollection.$getSQL()).toEqual('fake_column < 1');
});
});
describe('._triggerFilterChange', function () {
let filtersCollection;
beforeEach(function () {
filtersCollection = new FiltersCollection();
});
it('should trigger change:filters', function () {
spyOn(filtersCollection, 'trigger');
const filters = [];
filtersCollection._triggerFilterChange(filters);
expect(filtersCollection.trigger).toHaveBeenCalledWith('change:filters', filters);
});
});
});

View File

@@ -0,0 +1,75 @@
var carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/polygon', function () {
describe('initialization', function () {
it('should create the internalModel', function () {
var polygonFilter = new carto.filter.Polygon();
expect(polygonFilter.$getInternalModel()).toBeDefined();
});
});
describe('.setPolygon', function () {
var polygonFilter;
beforeEach(function () {
polygonFilter = new carto.filter.Polygon();
});
it('checks if polygon is valid', function () {
var test = function () {
polygonFilter.setPolygon({
ups: 'notExpected'
});
};
var test2 = function () {
polygonFilter.setPolygon({
type: 'NotAPolygon',
coordinates: {
content: 'isNotAnArrayOfCoords'
}
});
};
const expected = 'Polygon object is not valid. Use a carto.filter.PolygonData object';
expect(test).toThrowError(Error, expected);
expect(test2).toThrowError(Error, expected);
});
it('if polygon is valid, it assigns it to property, triggers the polygonChanged event and returns this', function () {
spyOn(polygonFilter, 'trigger');
var polygon = {
type: 'Polygon',
coordinates: [[1, 2], [3, 4], [5, 6], [1, 2]]
};
var returnedObject = polygonFilter.setPolygon(polygon);
expect(polygonFilter.getPolygon()).toEqual(polygon);
expect(polygonFilter.trigger).toHaveBeenCalledWith('polygonChanged', polygon);
expect(returnedObject).toBe(polygonFilter);
});
});
describe('.resetPolygon', function () {
it('sets the polygon as empty', function () {
var polygonFilter = new carto.filter.Polygon();
polygonFilter.setPolygon({
type: 'Polygon',
coordinates: [[1, 2], [3, 4], [5, 6], [1, 2]]
});
expect(polygonFilter.getPolygon()).toEqual({
type: 'Polygon',
coordinates: [[1, 2], [3, 4], [5, 6], [1, 2]]
});
polygonFilter.resetPolygon();
expect(polygonFilter.getPolygon()).toEqual({
type: 'Polygon',
coordinates: []
});
});
});
});

View File

@@ -0,0 +1,73 @@
const carto = require('../../../../../src/api/v4/index');
describe('api/v4/filter/range', function () {
describe('constructor', function () {
it('should throw a descriptive error when an unknown filter has been passed', function () {
expect(function () {
new carto.filter.Range('fake_column', { unknown_filter: '' }); // eslint-disable-line
}).toThrowError("'unknown_filter' is not a valid filter. Please check documentation.");
});
});
describe('SQL Templates', function () {
it('LT', function () {
const categoryFilter = new carto.filter.Range('fake_column', { lt: 10 });
expect(categoryFilter.$getSQL()).toBe('fake_column < 10');
});
it('LT with subquery', function () {
const categoryFilter = new carto.filter.Range('fake_column', { lt: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column < (SELECT avg(price) FROM neighbourhoods)');
});
it('LTE', function () {
const categoryFilter = new carto.filter.Range('fake_column', { lte: 10 });
expect(categoryFilter.$getSQL()).toBe('fake_column <= 10');
});
it('LTE with subquery', function () {
const categoryFilter = new carto.filter.Range('fake_column', { lte: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column <= (SELECT avg(price) FROM neighbourhoods)');
});
it('GT', function () {
const categoryFilter = new carto.filter.Range('fake_column', { gt: 10 });
expect(categoryFilter.$getSQL()).toBe('fake_column > 10');
});
it('GT with subquery', function () {
const categoryFilter = new carto.filter.Range('fake_column', { gt: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column > (SELECT avg(price) FROM neighbourhoods)');
});
it('GTE', function () {
const categoryFilter = new carto.filter.Range('fake_column', { gte: 10 });
expect(categoryFilter.$getSQL()).toBe('fake_column >= 10');
});
it('GTE with subquery', function () {
const categoryFilter = new carto.filter.Range('fake_column', { gte: { query: 'SELECT avg(price) FROM neighbourhoods' } });
expect(categoryFilter.$getSQL()).toBe('fake_column >= (SELECT avg(price) FROM neighbourhoods)');
});
it('BETWEEN', function () {
const categoryFilter = new carto.filter.Range('fake_column', { between: { min: 1, max: 10 } });
expect(categoryFilter.$getSQL()).toBe('fake_column BETWEEN 1 AND 10');
});
it('NOT BETWEEN', function () {
const categoryFilter = new carto.filter.Range('fake_column', { notBetween: { min: 1, max: 10 } });
expect(categoryFilter.$getSQL()).toBe('fake_column NOT BETWEEN 1 AND 10');
});
it('BETWEEN SYMMETRIC', function () {
const categoryFilter = new carto.filter.Range('fake_column', { betweenSymmetric: { min: 1, max: 10 } });
expect(categoryFilter.$getSQL()).toBe('fake_column BETWEEN SYMMETRIC 1 AND 10');
});
it('NOT BETWEEN SYMMETRIC', function () {
const categoryFilter = new carto.filter.Range('fake_column', { notBetweenSymmetric: { min: 1, max: 10 } });
expect(categoryFilter.$getSQL()).toBe('fake_column NOT BETWEEN SYMMETRIC 1 AND 10');
});
});
});

View File

@@ -0,0 +1,171 @@
var Aggregation = require('../../../../../src/api/v4/layer/aggregation');
describe('layer-aggregation', function () {
var options;
beforeEach(function () {
options = {
threshold: 10000,
resolution: 1,
placement: 'point-sample',
columns: {
fake_name_0: {
aggregateFunction: 'sum',
aggregatedColumn: 'fake_column_0'
},
fake_name_1: {
aggregateFunction: 'avg',
aggregatedColumn: 'fake_column_1'
}
}
};
});
describe('constructor', function () {
it('should return a simple object when the parameters are valid', function () {
var aggregation = new Aggregation(options);
// Multiple specs for easy debugging
expect(aggregation.threshold).toEqual(options.threshold);
expect(aggregation.resolution).toEqual(options.resolution);
expect(aggregation.placement).toEqual(options.placement);
expect(aggregation.columns.fake_name_0).toEqual({
aggregate_function: 'sum',
aggregated_column: 'fake_column_0'
});
expect(aggregation.columns.fake_name_1).toEqual({
aggregate_function: 'avg',
aggregated_column: 'fake_column_1'
});
});
describe('errors', function () {
describe('threshold', function () {
it('should throw an error when threshold is not defined', function () {
delete options.threshold;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation threshold is required.');
});
it('should throw an error when threshold is not a positive integer', function () {
options.threshold = 0;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation threshold must be an integer value greater than 0.');
options.threshold = -1;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation threshold must be an integer value greater than 0.');
options.threshold = 2.5;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation threshold must be an integer value greater than 0.');
});
});
describe('resolution', function () {
it('should throw an error when resolution is not defined', function () {
options.resolution = undefined;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation resolution is required.');
});
it('should throw an error when resolution is not an integer between 1 and 16', function () {
var expectedErrorMessage = 'Aggregation resolution must be 0.5, 1 or powers of 2 up to 256 (2, 4, 8, 16, 32, 64, 128, 256).';
options.resolution = 0;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError(expectedErrorMessage);
options.resolution = 17;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError(expectedErrorMessage);
var validOnes = [0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256];
validOnes.forEach(function (resolution) {
expect(function () {
options.resolution = resolution;
new Aggregation(options); // eslint-disable-line
}).not.toThrowError();
});
});
});
describe('placement', function () {
it('should throw an error when placement is not one of our three valid placements', function () {
options.placement = 'invalid_placement';
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError('Aggregation placement is not valid. Must be one of these values: `point-sample`, `point-grid`, `centroid`');
});
});
describe('columns', function () {
it('should thrown an error when column.aggregateFunction is not defined', function () {
options.columns = {
fake_name_0: {
aggregatedColumn: 'fake_column_0'
}
};
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError("Aggregation function for column 'fake_name_0' is required.");
});
it('should thrown an error when column.aggregateFunction is not a valid function', function () {
options.columns = {
fake_name_0: {
aggregatedColumn: 'fake_column_0',
aggregateFunction: 'invalid_function'
}
};
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError("Aggregation function for column 'fake_name_0' is not valid. Use carto.aggregation.function");
});
it('should thrown an error when column.aggregatedColumn is not defined', function () {
options.columns = {
fake_name_0: {
aggregateFunction: 'sum'
}
};
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError("Column to be aggregated to 'fake_name_0' is required.");
});
it('should thrown an error when column.aggregatedColumn is not a string', function () {
options.columns = {
fake_name_0: {
aggregatedColumn: 4500,
aggregateFunction: 'sum'
}
};
expect(function () {
new Aggregation(options); // eslint-disable-line
}).toThrowError("Column to be aggregated to 'fake_name_0' must be a string.");
});
});
describe('optional placement and columns', function () {
it('should now throw an error when neither placement nor columns appear', function () {
delete options.columns;
delete options.placement;
expect(function () {
new Aggregation(options); // eslint-disable-line
}).not.toThrow();
});
});
});
});
});

View File

@@ -0,0 +1,677 @@
var carto = require('../../../../../src/api/v4');
describe('api/v4/layer', function () {
var source;
var style;
var originalTimeout;
beforeEach(function () {
source = new carto.source.Dataset('ne_10m_populated_places_simple');
style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
});
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
describe('constructor', function () {
it('should build a new Layer params: (source, style)', function () {
var layer = new carto.layer.Layer(source, style);
expect(layer.getSource()).toEqual(source);
expect(layer.getStyle()).toEqual(style);
});
it('should assign a unique layer ID string', function () {
var layer1 = new carto.layer.Layer(source, style);
var layer2 = new carto.layer.Layer(source, style);
var id1 = layer1.getId();
var id2 = layer2.getId();
expect(id1).toMatch(/L\d+/);
expect(id2).toMatch(/L\d+/);
expect(id1).not.toEqual(id2);
});
it('should be able to create a hidden layer', function () {
var layer = new carto.layer.Layer(source, style, {
visible: false
});
expect(layer.isHidden()).toBe(true);
});
it('should build a new Layer params: (source, style, options)', function () {
var layer = new carto.layer.Layer(source, style, {
featureClickColumns: ['a', 'b'],
featureOverColumns: ['c', 'd']
});
expect(layer.getSource()).toEqual(source);
expect(layer.getStyle()).toEqual(style);
expect(layer.getFeatureClickColumns()).toEqual(['a', 'b']);
expect(layer.getFeatureOverColumns()).toEqual(['c', 'd']);
});
it('should throw an error if source is not valid', function () {
expect(function () {
new carto.layer.Layer({}, style); // eslint-disable-line
}).toThrowError('The given object is not a valid source. See "carto.source.Base".');
});
it('should throw an error if style is not valid', function () {
expect(function () {
new carto.layer.Layer(source, {}); // eslint-disable-line
}).toThrowError('The given object is not a valid style. See "carto.style.Base".');
});
it('should allow custom layer id as an option', function () {
var layer = new carto.layer.Layer(source, style, { id: 'fake_id' });
expect(layer.getId()).toEqual('fake_id');
});
describe('columns validation', function () {
var aggregation = new carto.layer.Aggregation({
threshold: 1,
resolution: 4,
columns: {
population: {
aggregateFunction: 'sum',
aggregatedColumn: 'pop_max'
}
}
});
it('should validate that featureClick columns are contained in aggregation columns', function () {
expect(function () {
new carto.layer.Layer(source, style, { // eslint-disable-line
featureClickColumns: ['a', 'b'],
aggregation: aggregation
});
}).toThrowError('Columns [a, b] set on `featureClick` do not match the columns set in aggregation options.');
});
it('should validate that featureOver columns are contained in aggregation columns', function () {
expect(function () {
new carto.layer.Layer(source, style, { // eslint-disable-line
featureOverColumns: ['a', 'b'],
aggregation: aggregation
});
}).toThrowError('Columns [a, b] set on `featureOver` do not match the columns set in aggregation options.');
});
});
});
describe('.setStyle', function () {
var layer;
var newStyle;
beforeEach(function () {
layer = new carto.layer.Layer(source, style);
newStyle = new carto.style.CartoCSS('#layer { marker-fill: green; }');
});
it('should throw an error when the parameter is not a valid style', function () {
expect(function () {
layer.setStyle('bad-style');
}).toThrowError('The given object is not a valid style. See "carto.style.Base".');
});
describe('when the layer has no engine', function () {
it('should set the layer style', function () {
layer.setStyle(newStyle);
expect(layer.getStyle()).toEqual(newStyle);
});
it('should fire a styleChanged event', function (done) {
layer.on('styleChanged', function (l) {
expect(l).toBe(layer);
expect(l.getStyle()).toEqual(newStyle);
done();
});
layer.setStyle(newStyle);
});
it('should not fire a styleChanged event when setting the same style twice', function () {
var styleChangedSpy = jasmine.createSpy('styleChangedSpy');
layer.on('styleChanged', styleChangedSpy);
layer.setStyle(style);
expect(styleChangedSpy).not.toHaveBeenCalled();
});
});
describe('when the layer has an engine', function () {
var client;
beforeEach(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
});
describe('and the style has no engine', function () {
it('should set the engine into the style and update the internal style.', function (done) {
client.addLayer(layer)
.then(function () {
return layer.setStyle(newStyle);
})
.then(function () {
expect(layer.getStyle().$getEngine()).toBe(layer._engine);
done();
});
});
});
describe('and the style has an engine', function () {
it('should update the internal style when the engines are equal', function (done) {
newStyle.$setEngine(client._engine);
client.addLayer(layer)
.then(function () {
return layer.setStyle(newStyle);
})
.then(function () {
expect(layer.getStyle().$getEngine()).toBe(layer._engine);
done();
});
});
it('should throw an error when the engines are not equal', function (done) {
newStyle.$setEngine('fakeEngine');
client.addLayer(layer)
.then(function () {
expect(function () {
return layer.setStyle(newStyle);
}).toThrowError('A layer can\'t have a style which belongs to a different client.');
done();
});
});
});
it('should fire a cartoError when the style is invalid', function (done) {
var styleChangedSpy = jasmine.createSpy('styleChangedSpy');
layer.on('error', styleChangedSpy);
client.addLayer(layer)
.then(function () {
var malformedStyle = new carto.style.CartoCSS('#layer { invalid-rule: foo}');
return layer.setStyle(malformedStyle);
})
.catch(function () {
expect(styleChangedSpy).toHaveBeenCalled();
done();
});
});
it('should fire a styleChanged event when the layer belongs to a client', function (done) {
var styleChangedSpy = jasmine.createSpy('styleChangedSpy');
layer.on('styleChanged', styleChangedSpy);
client.addLayer(layer)
.then(function () {
return layer.setStyle(newStyle);
})
.then(function () {
expect(styleChangedSpy).toHaveBeenCalled();
done();
});
});
it('should set the internal model style', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
client.on(carto.events.ERROR, alert);
client.addLayer(layer)
.then(function () {
client.on(carto.events.SUCCESS, function () {
var expected = '#layer { marker-fill: green; }';
var actual = layer.$getInternalModel().get('cartocss');
expect(expected).toEqual(actual);
done();
});
layer.setStyle(newStyle);
});
});
});
});
describe('.$setEngine', function () {
it('probando', function () {
var layer = new carto.layer.Layer(source, style);
var engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.resolve()) };
var error = {
message: 'an error'
};
var capturedError;
layer.$setEngine(engineMock);
layer.on(carto.events.ERROR, function (error) {
capturedError = error;
});
layer._internalModel.set('error', error);
expect(capturedError).toBeDefined();
expect(capturedError.name).toEqual('CartoError');
expect(capturedError.message).toEqual('an error');
});
});
describe('.setSource', function () {
var layer;
var newSource;
beforeEach(function () {
layer = new carto.layer.Layer(source, style);
newSource = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple LIMIT 10');
});
it('should throw an error when the source is not a valid parameter', function () {
expect(function () {
layer.setSource('bad-parameter');
}).toThrowError('The given object is not a valid source. See "carto.source.Base".');
});
describe('when the layer hasn\'t been set an engine', function () {
it('should normally add the source', function () {
layer.setSource(newSource);
expect(layer.getSource()).toEqual(newSource);
});
it('should fire a sourceChanged event', function (done) {
layer.on('sourceChanged', function (l) {
expect(l).toBe(layer);
expect(l.getSource()).toEqual(newSource);
done();
});
layer.setSource(newSource);
});
});
describe('when the layer has been set an engine', function () {
var engineMock;
beforeEach(function () {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.resolve()) };
layer.$setEngine(engineMock);
});
describe('and the source has no engine', function () {
it('should normally add the source', function (done) {
layer.setSource(newSource)
.then(function () {
var actualSource = layer.$getInternalModel().get('source');
var expectedSource = newSource.$getInternalModel();
expect(actualSource).toBeDefined();
expect(actualSource).toEqual(expectedSource);
done();
});
});
it('should fire a sourceChanged event', function (done) {
var sourceChangedSpy = jasmine.createSpy('sourceChangedSpy');
layer.on('sourceChanged', sourceChangedSpy);
layer.setSource(newSource)
.then(function () {
expect(sourceChangedSpy).toHaveBeenCalled();
done();
});
});
});
describe('and the source has an engine', function () {
it('should add the source if the engines are the same', function () {
newSource.$setEngine(engineMock);
layer.setSource(newSource);
var actualSource = layer.$getInternalModel().get('source');
var expectedSource = newSource.$getInternalModel();
expect(actualSource).toEqual(expectedSource);
});
it('should throw an error if the engines are different', function () {
// This engine is different from the layer's one.
var engineMock1 = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload') };
newSource.$setEngine(engineMock1);
expect(function () {
layer.setSource(newSource);
}).toThrowError('A layer can\'t have a source which belongs to a different client.');
});
});
});
it('should not fire a sourceChanged event when setting the same source twice', function () {
var sourceChangedSpy = jasmine.createSpy('sourceChangedSpy');
layer.on('sourceChanged', sourceChangedSpy);
layer.setSource(source);
expect(sourceChangedSpy).not.toHaveBeenCalled();
});
it('should fire a cartoError when the source is invalid', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var sourceChangedSoy = jasmine.createSpy('sourceChangedSoy');
layer.on('error', sourceChangedSoy);
client.addLayer(layer)
.then(function () {
var invalidDataset = new carto.source.Dataset('invalid_dataset');
return layer.setSource(invalidDataset);
})
.catch(function () {
expect(sourceChangedSoy).toHaveBeenCalled();
done();
});
});
});
describe('.setFeatureClickColumns', function () {
var layer;
var newColums;
beforeEach(function () {
layer = new carto.layer.Layer(source, style);
newColums = ['a', 'b'];
});
it('should throw an error when the columns are not a valid parameter', function () {
expect(function () {
layer.setFeatureClickColumns([1, 2, 3]);
}).toThrowError('The given object is not a valid array of string columns.');
});
describe('when the layer hasn\'t been set an engine', function () {
it('should normally add the columns', function () {
layer.setFeatureClickColumns(newColums);
expect(layer.getFeatureClickColumns()).toEqual(newColums);
});
});
describe('when the layer has been set an engine', function () {
var engineMock;
it('should normally add the columns', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.resolve()) };
layer.$setEngine(engineMock);
layer.setFeatureClickColumns(newColums)
.then(function () {
var actualColumns = layer.getFeatureClickColumns();
var expectedColumns = newColums;
expect(actualColumns).toBeDefined();
expect(actualColumns).toEqual(expectedColumns);
done();
});
});
it('should throw an error when the columns are not valid', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.reject(new Error())) };
layer.$setEngine(engineMock);
layer.setFeatureClickColumns(['wrong'])
.catch(function (error) {
expect(error instanceof Error).toBe(true);
done();
});
});
it('should fire an error event when the columns are not valid', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.reject(new Error())) };
layer.$setEngine(engineMock);
var columnChangedError = jasmine.createSpy('columnChangedError');
layer.on('error', columnChangedError);
layer.setFeatureClickColumns(['wrong'])
.catch(function () {
expect(columnChangedError).toHaveBeenCalled();
done();
});
});
});
});
describe('.setFeatureOverColumns', function () {
var layer;
var newColums;
beforeEach(function () {
layer = new carto.layer.Layer(source, style);
newColums = ['a', 'b'];
});
it('should throw an error when the columns are not a valid parameter', function () {
expect(function () {
layer.setFeatureOverColumns([1, 2, 3]);
}).toThrowError('The given object is not a valid array of string columns.');
});
describe('when the layer hasn\'t been set an engine', function () {
it('should normally add the columns', function () {
layer.setFeatureOverColumns(newColums);
expect(layer.getFeatureOverColumns()).toEqual(newColums);
});
});
describe('when the layer has been set an engine', function () {
var engineMock;
it('should normally add the columns', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.resolve()) };
layer.$setEngine(engineMock);
layer.setFeatureOverColumns(newColums)
.then(function () {
var actualColumns = layer.getFeatureOverColumns();
var expectedColumns = newColums;
expect(actualColumns).toBeDefined();
expect(actualColumns).toEqual(expectedColumns);
done();
});
});
it('should throw an error when the columns are not valid', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.reject(new Error())) };
layer.$setEngine(engineMock);
layer.setFeatureOverColumns(['wrong'])
.catch(function (error) {
expect(error instanceof Error).toBe(true);
done();
});
});
it('should fire an error event when the columns are not valid', function (done) {
engineMock = { on: jasmine.createSpy('on'), reload: jasmine.createSpy('reload').and.returnValue(Promise.reject(new Error())) };
layer.$setEngine(engineMock);
var columnChangedError = jasmine.createSpy('columnChangedError');
layer.on('error', columnChangedError);
layer.setFeatureOverColumns(['wrong'])
.catch(function () {
expect(columnChangedError).toHaveBeenCalled();
done();
});
});
});
});
describe('.show', function () {
it('should set the layer visibility to true', function () {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.hide();
expect(layer.isVisible()).toEqual(false);
expect(layer.isHidden()).toEqual(true);
layer.show();
expect(layer.isVisible()).toEqual(true);
expect(layer.isHidden()).toEqual(false);
});
it('should trigger a visibilityChanged event', function (done) {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.hide();
layer.on('visibilityChanged', function () {
expect(layer.isVisible()).toEqual(true);
expect(layer.isHidden()).toEqual(false);
done();
});
layer.show();
});
});
describe('.hide', function () {
it('should set the layer visibility to false', function () {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.hide();
expect(layer.isVisible()).toEqual(false);
expect(layer.isHidden()).toEqual(true);
});
it('should trigger a visibilityChanged event', function (done) {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.on('visibilityChanged', function () {
expect(layer.isVisible()).toEqual(false);
expect(layer.isHidden()).toEqual(true);
done();
});
layer.hide();
});
});
describe('.toggle', function () {
it('should toggle the layer visibility', function () {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.toggle();
expect(layer.isVisible()).toEqual(false);
expect(layer.isHidden()).toEqual(true);
layer.toggle();
expect(layer.isVisible()).toEqual(true);
expect(layer.isHidden()).toEqual(false);
});
it('should trigger a visibilityChanged event', function (done) {
var layer = new carto.layer.Layer(source, style);
expect(layer.isVisible()).toEqual(true);
layer.on('visibilityChanged', function () {
expect(layer.isVisible()).toEqual(false);
expect(layer.isHidden()).toEqual(true);
done();
});
layer.toggle();
});
});
describe('.setOrder', function () {
it('should call moveLayer with the passed index', function () {
var clientMock = { moveLayer: jasmine.createSpy('moveLayer') };
var layer = new carto.layer.Layer(source, style);
layer.$setClient(clientMock);
layer.setOrder(1);
expect(clientMock.moveLayer).toHaveBeenCalledWith(layer, 1);
});
});
describe('.bringToBack', function () {
it('should call moveLayer with the passed index', function () {
var clientMock = { moveLayer: jasmine.createSpy('moveLayer') };
var layer = new carto.layer.Layer(source, style);
layer.$setClient(clientMock);
layer.bringToBack();
expect(clientMock.moveLayer).toHaveBeenCalledWith(layer, 0);
});
});
describe('.bringToFront', function () {
it('should call moveLayer with the passed index', function () {
var numberOfLayers = 3;
var clientMock = { moveLayer: jasmine.createSpy('moveLayer'), _layers: { size: function () { return numberOfLayers; } } };
var layer = new carto.layer.Layer(source, style);
layer.$setClient(clientMock);
layer.bringToFront();
expect(clientMock.moveLayer).toHaveBeenCalledWith(layer, numberOfLayers - 1);
});
});
describe('.isInteractive', function () {
it('returns true if layer has featureClickColumns', function () {
const layer = new carto.layer.Layer(source, style, {
featureClickColumns: ['cartodb_id']
});
expect(layer.isInteractive()).toBe(true);
});
it('returns true if layer has featureOverColumns', function () {
const layer = new carto.layer.Layer(source, style, {
featureOverColumns: ['cartodb_id']
});
expect(layer.isInteractive()).toBe(true);
});
it('returns false if layer doesn\'t have getFeatureClickColumns or getFeatureHoverColumns', function () {
const layer = new carto.layer.Layer(source, style);
expect(layer.isInteractive()).toBe(false);
});
});
xit('should update "internalmodel.cartocss" when the style is updated', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var layer = new carto.layer.Layer(source, style);
var newStyle = '#layer { marker-fill: #FABADA; }';
client.addLayer(layer)
.then(function () {
return style.setContent(newStyle);
})
.then(function () {
expect(layer.$getInternalModel().get('cartocss')).toEqual(newStyle);
done();
});
});
});

View File

@@ -0,0 +1,230 @@
var metadataParser = require('../../../../../../src/api/v4/layer/metadata/parser');
describe('api/v4/layer/metadata/parser', function () {
describe('.getMetadataFromRules', function () {
it('should parse correctly the range rules', function () {
// From:
// marker-width: ramp([scalerank], range(5, 20), quantiles(4));
// marker-fill-opacity: ramp([pop_max], range(0,1), jenks);
var rules = [
{
'selector': '#layer',
'prop': 'marker-width',
'column': 'scalerank',
'mapping': '>',
'buckets': [
{
'filter': {
'type': 'range',
'start': 0,
'end': 6
},
'value': 5
},
{
'filter': {
'type': 'range',
'start': 6,
'end': 7
},
'value': 10
},
{
'filter': {
'type': 'range',
'start': 7,
'end': 7
},
'value': 15
},
{
'filter': {
'type': 'range',
'start': 7,
'end': 10
},
'value': 20
}
],
'stats': {
'filter_avg': 6.642174269325321
}
},
{
'selector': '#layer',
'prop': 'marker-fill-opacity',
'column': 'pop_max',
'mapping': '>',
'buckets': [
{
'filter': {
'type': 'range',
'start': -99,
'end': 37945
},
'value': 0
},
{
'filter': {
'type': 'range',
'start': 37945,
'end': 138954
},
'value': 0.25
},
{
'filter': {
'type': 'range',
'start': 138954,
'end': 616990
},
'value': 0.5
},
{
'filter': {
'type': 'range',
'start': 616990,
'end': 2544000
},
'value': 0.75
},
{
'filter': {
'type': 'range',
'start': 2544000,
'end': 35676000
},
'value': 1
}
],
'stats': {
'filter_avg': 322717.4763725758
}
}
];
var metadataList = metadataParser.getMetadataFromRules(rules);
expect(metadataList).toBeDefined();
expect(metadataList[0].getType()).toBe('buckets');
expect(metadataList[0].getColumn()).toBe('scalerank');
expect(metadataList[0].getMapping()).toBe('>');
expect(metadataList[0].getProperty()).toBe('marker-width');
expect(metadataList[0].getAverage()).toBe(6.642174269325321);
expect(metadataList[0].getMin()).toBe(0);
expect(metadataList[0].getMax()).toBe(10);
expect(metadataList[0].getBuckets()).toEqual([
{ min: 0, max: 6, value: 5 },
{ min: 6, max: 7, value: 10 },
{ min: 7, max: 7, value: 15 },
{ min: 7, max: 10, value: 20 }
]);
expect(metadataList[0].getType()).toBe('buckets');
expect(metadataList[1].getColumn()).toBe('pop_max');
expect(metadataList[1].getMapping()).toBe('>');
expect(metadataList[1].getProperty()).toBe('marker-fill-opacity');
expect(metadataList[1].getAverage()).toBe(322717.4763725758);
expect(metadataList[1].getMin()).toBe(-99);
expect(metadataList[1].getMax()).toBe(35676000);
expect(metadataList[1].getBuckets()).toEqual([
{ min: -99, max: 37945, value: 0 },
{ min: 37945, max: 138954, value: 0.25 },
{ min: 138954, max: 616990, value: 0.5 },
{ min: 616990, max: 2544000, value: 0.75 },
{ min: 2544000, max: 35676000, value: 1 }
]);
});
it('should parse correctly the category rules', function () {
// From:
// marker-fill: ramp([scalerank], (#5F4690, #1D6996, #38A6A5, #666666), (7, 6, 10), '=', category);
// marker-file: ramp([scalerank], (url('https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/rail-light-18.svg'), url('https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/park-18.svg')), (7, 10), '=');
var rules = [
{
'selector': '#layer',
'prop': 'marker-fill',
'column': 'scalerank',
'mapping': '=',
'buckets': [
{
'filter': {
'name': 7,
'type': 'category'
},
'value': '#5F4690'
},
{
'filter': {
'name': 6,
'type': 'category'
},
'value': '#1D6996'
},
{
'filter': {
'name': 10,
'type': 'category'
},
'value': '#38A6A5'
},
{
'filter': {
'type': 'default'
},
'value': '#666666'
}
],
'stats': {}
},
{
'selector': '',
'prop': 'marker-file',
'column': 'scalerank',
'mapping': '=',
'buckets': [
{
'filter': {
'name': 7,
'type': 'category'
},
'value': 'url(\'https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/rail-light-18.svg\')'
},
{
'filter': {
'name': 10,
'type': 'category'
},
'value': 'url(\'https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/park-18.svg\')'
}
],
'stats': {}
}
];
var metadataList = metadataParser.getMetadataFromRules(rules);
expect(metadataList).toBeDefined();
expect(metadataList[0].getType()).toBe('categories');
expect(metadataList[0].getColumn()).toBe('scalerank');
expect(metadataList[0].getMapping()).toBe('=');
expect(metadataList[0].getProperty()).toBe('marker-fill');
expect(metadataList[0].getDefaultValue()).toBe('#666666');
expect(metadataList[0].getCategories()).toEqual([
{ name: 7, value: '#5F4690' },
{ name: 6, value: '#1D6996' },
{ name: 10, value: '#38A6A5' }
]);
expect(metadataList[0].getType()).toBe('categories');
expect(metadataList[1].getColumn()).toBe('scalerank');
expect(metadataList[1].getMapping()).toBe('=');
expect(metadataList[1].getProperty()).toBe('marker-file');
expect(metadataList[1].getDefaultValue()).not.toBeDefined();
expect(metadataList[1].getCategories()).toEqual([
{ name: 7, value: 'url(\'https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/rail-light-18.svg\')' },
{ name: 10, value: 'url(\'https://s3.amazonaws.com/com.cartodb.users-assets.production/maki-icons/park-18.svg\')' }
]);
});
});
});

View File

@@ -0,0 +1,41 @@
var Layers = require('../../../../src/api/v4/layers');
describe('api/v4/layers', function () {
var layers;
function createFakeLayer (id) {
return {
getId: function () {
return id;
}
};
}
function seedLayers (layers) {
var layerA = createFakeLayer('A');
var layerB = createFakeLayer('B');
var layerC = createFakeLayer('C');
layers.add(layerA);
layers.add(layerB);
layers.add(layerC);
return [layerA, layerB, layerC];
}
beforeEach(function () {
layers = new Layers();
});
describe('.remove', function () {
it('should remove the layer from the collection', function () {
var createdLayers = seedLayers(layers);
layers.remove(createdLayers[1]);
expect(layers.size()).toBe(2);
expect(layers.indexOf(createdLayers[0])).toBe(0);
expect(layers.indexOf(createdLayers[2])).toBe(1);
});
});
});

View File

@@ -0,0 +1,141 @@
/* global google */
var carto = require('../../../../../src/api/v4');
describe('src/api/v4/native/google-maps-map-type', function () {
var client;
var layer;
var mapType;
var map;
beforeEach(function () {
var element = document.createElement('div');
element.id = 'map';
document.body.appendChild(element);
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
map = new google.maps.Map(element);
mapType = client.getGoogleMapsMapType(map);
});
afterEach(function () {
document.getElementById('map').remove();
});
describe('layer events', function () {
var spy;
var internalEventMock;
beforeEach(function () {
spy = jasmine.createSpy('spy');
map.overlayMapTypes.push(mapType);
var source = new carto.source.SQL('foo');
var style = new carto.style.CartoCSS('bar');
layer = new carto.layer.Layer(source, style);
client.addLayer(layer, { reload: false });
internalEventMock = {
layer: {
id: layer.getId()
},
latlng: [ 10, 20 ],
feature: { name: 'foo' }
};
});
it('should trigger carto.layer.events.FEATURE_CLICKED event', function () {
layer.on(carto.layer.events.FEATURE_CLICKED, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
mapType._internalView.trigger('featureClick', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
it('should trigger carto.layer.events.FEATURE_OVER event', function () {
layer.on(carto.layer.events.FEATURE_OVER, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
mapType._internalView.trigger('featureOver', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
it('should trigger carto.layer.events.FEATURE_OUT featureOut events', function () {
layer.on(carto.layer.events.FEATURE_OUT, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
mapType._internalView.trigger('featureOut', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
describe('mouse pointer', function () {
describe('when mousing over a feature', function () {
it("should NOT set the mouse cursor to 'pointer' if layer doesn't have featureOverColumns or featureClickColumns", function () {
expect(map.get('draggableCursor')).not.toBeDefined();
mapType._internalView.trigger('featureOver', internalEventMock);
expect(map.get('draggableCursor')).not.toBeDefined();
});
it("should set the mouse cursor to 'pointer' if layer has featureOverColumns", function () {
layer._featureOverColumns = [ 'foo' ];
expect(map.get('draggableCursor')).not.toBeDefined();
mapType._internalView.trigger('featureOver', internalEventMock);
expect(map.get('draggableCursor')).toEqual('pointer');
});
it("should set the mouse cursor to 'pointer' if layer has featureClickColumns", function () {
layer._featureClickColumns = [ 'foo' ];
expect(map.get('draggableCursor')).not.toBeDefined();
mapType._internalView.trigger('featureOver', internalEventMock);
expect(map.get('draggableCursor')).toEqual('pointer');
});
it("should set the mouse cursor to 'pointer' if layer has overed features after a featureOut", function () {
mapType._hoveredLayers = ['L100'];
expect(map.get('draggableCursor')).not.toBeDefined();
mapType._internalView.trigger('featureOut', internalEventMock);
expect(map.get('draggableCursor')).toEqual('pointer');
});
});
describe('when mousing over NO features', function () {
it("should set the mouse cursor to 'auto'", function () {
expect(map.get('draggableCursor')).not.toBeDefined();
mapType._internalView.trigger('featureOut', internalEventMock);
expect(map.get('draggableCursor')).toEqual('auto');
});
});
});
});
});

View File

@@ -0,0 +1,198 @@
/* global L */
var carto = require('../../../../../src/api/v4');
describe('src/api/v4/native/leaflet-layer', function () {
var client;
var layer;
var leafletLayer;
var map;
beforeEach(function () {
var element = document.createElement('div');
element.id = 'map';
document.body.appendChild(element);
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
map = L.map('map').setView([42.431234, -8.643616], 5);
});
afterEach(function () {
document.getElementById('map').remove();
});
it('allows custom options', function () {
leafletLayer = client.getLeafletLayer({ maxZoom: 10 });
expect(leafletLayer.options.maxZoom).toBe(10);
});
describe('addTo', function () {
beforeEach(function () {
leafletLayer = client.getLeafletLayer();
});
it('should add a leaflet layer to the map', function () {
expect(countLeafletLayers(map)).toEqual(0);
leafletLayer.addTo(map);
expect(countLeafletLayers(map)).toEqual(1);
});
});
describe('removeFrom', function () {
beforeEach(function () {
leafletLayer = client.getLeafletLayer();
});
it('should remove the leaflet layer from the map', function () {
expect(countLeafletLayers(map)).toEqual(0);
leafletLayer.addTo(map);
expect(countLeafletLayers(map)).toEqual(1);
leafletLayer.removeFrom(map);
expect(countLeafletLayers(map)).toEqual(0);
});
});
describe('layer events', function () {
var spy;
var internalEventMock;
beforeEach(function () {
spy = jasmine.createSpy('spy');
leafletLayer = client.getLeafletLayer();
leafletLayer.addTo(map);
var source = new carto.source.SQL('foo');
var style = new carto.style.CartoCSS('bar');
layer = new carto.layer.Layer(source, style);
client.addLayer(layer);
internalEventMock = {
layer: {
id: layer.getId()
},
latlng: [ 10, 20 ],
feature: { name: 'foo' }
};
});
it('should trigger carto.layer.events.FEATURE_CLICKED event', function () {
layer.on(carto.layer.events.FEATURE_CLICKED, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
leafletLayer._internalView.trigger('featureClick', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
it('should trigger carto.layer.events.FEATURE_OVER event', function () {
layer.on(carto.layer.events.FEATURE_OVER, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
leafletLayer._internalView.trigger('featureOver', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
it('should trigger carto.layer.events.FEATURE_OUT featureOut events', function () {
layer.on(carto.layer.events.FEATURE_OUT, spy);
var expectedExternalEvent = {
data: { name: 'foo' },
latLng: { lat: 10, lng: 20 }
};
leafletLayer._internalView.trigger('featureOut', internalEventMock);
expect(spy).toHaveBeenCalledWith(expectedExternalEvent);
});
it('should trigger carto.layer.events.TILE_ERROR events', function () {
layer.on(carto.layer.events.TILE_ERROR, spy);
layer._featureClickColumns = [ 'foo' ];
var error = {
message: 'an error'
};
leafletLayer._internalView.trigger('featureError', error);
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({
name: 'CartoError',
message: 'an error'
}));
});
describe('mouse pointer', function () {
describe('when mousing over a feature', function () {
it("should NOT set the mouse cursor to 'pointer' if layer doesn't have featureOverColumns or featureClickColumns", function () {
expect(map.getContainer().style.cursor).toEqual('');
leafletLayer._internalView.trigger('featureOver', internalEventMock);
expect(map.getContainer().style.cursor).toEqual('');
});
it("should set the mouse cursor to 'pointer' if layer has featureOverColumns", function () {
layer._featureOverColumns = [ 'foo' ];
expect(map.getContainer().style.cursor).toEqual('');
leafletLayer._internalView.trigger('featureOver', internalEventMock);
expect(map.getContainer().style.cursor).toEqual('pointer');
});
it("should set the mouse cursor to 'pointer' if layer has featureClickColumns", function () {
layer._featureClickColumns = [ 'foo' ];
expect(map.getContainer().style.cursor).toEqual('');
leafletLayer._internalView.trigger('featureOver', internalEventMock);
expect(map.getContainer().style.cursor).toEqual('pointer');
});
it("should set the mouse cursor to 'pointer' if layer has overed features after a featureOut", function () {
leafletLayer._hoveredLayers = ['L100'];
expect(map.getContainer().style.cursor).toEqual('');
leafletLayer._internalView.trigger('featureOut', internalEventMock);
expect(map.getContainer().style.cursor).toEqual('pointer');
});
});
describe('when mousing over NO features', function () {
it("should set the mouse cursor to 'auto'", function () {
expect(map.getContainer().style.cursor).toEqual('');
leafletLayer._internalView.trigger('featureOut', internalEventMock);
expect(map.getContainer().style.cursor).toEqual('auto');
});
});
});
});
function countLeafletLayers (map) {
return Object.keys(map._layers).length;
}
});

View File

@@ -0,0 +1,268 @@
const Base = require('../../../../../src/api/v4/source/base');
const carto = require('../../../../../src/api/v4');
describe('api/v4/source/dataset', function () {
var originalTimeout;
beforeEach(function () {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
});
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
describe('constructor', function () {
it('should return a new Dataset object', function () {
var populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
expect(populatedPlacesDataset).toBeDefined();
});
it('should autogenerate an id when no ID is given', function () {
var populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
expect(populatedPlacesDataset.getId()).toMatch(/S\d+/);
});
it('should throw an error if tableName is not provided', function () {
expect(function () {
new carto.source.Dataset(); // eslint-disable-line
}).toThrowError('Table name is required.');
});
it('should throw an error if tableName is empty', function () {
expect(function () {
new carto.source.Dataset(''); // eslint-disable-line
}).toThrowError('Table name must be not empty.');
});
it('should throw an error if tableName is not a valid string', function () {
expect(function () {
new carto.source.Dataset(3333); // eslint-disable-line
}).toThrowError('Table name must be a string.');
});
});
describe('.setTableName', function () {
let populatedPlacesDataset;
beforeEach(function () {
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
});
it('should set the dataset', function () {
populatedPlacesDataset.setTableName('airbnb_listings');
expect(populatedPlacesDataset.getTableName()).toEqual('airbnb_listings');
});
it('should throw an error if query is empty', function () {
expect(function () {
populatedPlacesDataset.setTableName(undefined);
}).toThrowError('Table name is required.');
});
it('should throw an error if query is not a valid string', function () {
expect(function () {
populatedPlacesDataset.setTableName(333);
}).toThrowError('Table name must be a string.');
});
it('should throw an error if query is empty', function () {
expect(function () {
populatedPlacesDataset.setTableName('');
}).toThrowError('Table name must be not empty.');
});
it('should trigger an tableNameChanged event when there is no internal model', function (done) {
const expectedTable = 'airbnb_listings';
populatedPlacesDataset.on('tableNameChanged', function (newQuery) {
expect(newQuery).toEqual(expectedTable);
done();
});
populatedPlacesDataset.setTableName(expectedTable);
});
it('should trigger an tableNameChanged event when there is an internal model', function (done) {
const client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
const style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
const layer = new carto.layer.Layer(populatedPlacesDataset, style);
const tableNameChangedSpy = jasmine.createSpy('tableNameChangedSpy');
const newTableName = 'airbnb_listings';
populatedPlacesDataset.on('tableNameChanged', tableNameChangedSpy);
client.addLayer(layer)
.then(function () {
return populatedPlacesDataset.setTableName(newTableName);
})
.then(function () {
expect(tableNameChangedSpy).toHaveBeenCalledWith(newTableName);
done();
});
});
it('should return a resolved promise when there is no internal model', function (done) {
const newTableName = 'airbnb_listings';
populatedPlacesDataset.setTableName(newTableName)
.then(function () {
expect(populatedPlacesDataset.getTableName()).toEqual(newTableName);
done();
});
});
it('should return a resolved promise when there is an internal model', function (done) {
const client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
const style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
const layer = new carto.layer.Layer(populatedPlacesDataset, style);
const newTableName = 'airbnb_listings';
client.addLayer(layer)
.then(function () {
return populatedPlacesDataset.setTableName(newTableName);
})
.then(function () {
expect(populatedPlacesDataset.getTableName()).toEqual(newTableName);
done();
});
});
it('should return a rejected promise with a CartoError when there is an internal model (and a reload error)', function (done) {
const client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
const style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
const layer = new carto.layer.Layer(populatedPlacesDataset, style);
const newTableName = 'invalid_dataset';
client.addLayer(layer)
.then(function () {
return populatedPlacesDataset.setTableName(newTableName);
})
.catch(function (cartoError) {
expect(cartoError.message).toMatch(/Invalid dataset name used. Dataset "invalid_dataset" does not exist./);
done();
});
});
});
describe('.getTableName', function () {
it('should return the table name', function () {
var dataset = new carto.source.Dataset('ne_10m_populated_places_simple');
expect(dataset.getTableName()).toBe('ne_10m_populated_places_simple');
});
});
describe('$setEngine', function () {
it('should create an internal model with the dataset and the engine', function () {
var populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
populatedPlacesDataset.$setEngine('fakeEngine');
var internalModel = populatedPlacesDataset.$getInternalModel();
expect(internalModel.get('id')).toEqual(populatedPlacesDataset.getId());
expect(internalModel.get('query')).toEqual('SELECT * from ne_10m_populated_places_simple');
expect(internalModel._engine).toEqual('fakeEngine');
});
});
describe('.getQueryToApply', function () {
let populatedPlacesDataset;
beforeEach(function () {
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
spyOn(populatedPlacesDataset, '_updateInternalModelQuery');
});
it('should return original query if applied filters returns no SQL', function () {
expect(populatedPlacesDataset._getQueryToApply()).toBe('SELECT * from ne_10m_populated_places_simple');
});
it('should return wrapped query if filters are applied', function () {
populatedPlacesDataset.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
expect(populatedPlacesDataset._getQueryToApply()).toBe("SELECT * FROM (SELECT * from ne_10m_populated_places_simple) as datasetQuery WHERE fake_column IN ('category')");
});
});
describe('.addFilter', function () {
let populatedPlacesDataset;
beforeEach(function () {
spyOn(Base.prototype, 'addFilter');
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
spyOn(populatedPlacesDataset, '_updateInternalModelQuery');
});
it('should call original addFilter and _updateInternalModelQuery', function () {
populatedPlacesDataset.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
expect(Base.prototype.addFilter).toHaveBeenCalled();
expect(populatedPlacesDataset._updateInternalModelQuery).toHaveBeenCalledWith(populatedPlacesDataset._getQueryToApply());
});
});
describe('.removeFilter', function () {
let populatedPlacesDataset, filter;
beforeEach(function () {
spyOn(Base.prototype, 'addFilter');
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
spyOn(populatedPlacesDataset, '_updateInternalModelQuery');
filter = populatedPlacesDataset.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
populatedPlacesDataset.addFilter(filter);
});
it('should call original removeFilter and _updateInternalModelQuery', function () {
populatedPlacesDataset.removeFilter(filter);
expect(Base.prototype.addFilter).toHaveBeenCalled();
expect(populatedPlacesDataset._updateInternalModelQuery).toHaveBeenCalledWith(populatedPlacesDataset._getQueryToApply());
});
});
describe('.getFilters', function () {
let populatedPlacesDataset, filter;
beforeEach(function () {
filter = new carto.filter.Category('fake_column', { in: ['category'] });
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
populatedPlacesDataset.addFilter(filter);
});
it('should return added filters', function () {
expect(populatedPlacesDataset.getFilters()).toEqual([filter]);
});
});
describe('errors', function () {
it('should trigger an error when invalid', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var invalidSource = new carto.source.Dataset('invalid_dataset');
var cartoCSS = new carto.style.CartoCSS('#layer { marker-fill: red; }');
invalidSource.on('error', function (cartoError) {
expect(cartoError.message).toMatch(/Invalid dataset name used. Dataset "invalid_dataset" does not exist./);
done();
});
var layer = new carto.layer.Layer(invalidSource, cartoCSS);
client.addLayer(layer).catch(function () { }); // Prevent console "uncaught error" warning.
});
});
});

View File

@@ -0,0 +1,257 @@
const Base = require('../../../../../src/api/v4/source/base');
const carto = require('../../../../../src/api/v4');
describe('api/v4/source/sql', function () {
var sqlQuery;
beforeEach(function () {
sqlQuery = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple WHERE adm0name = \'Spain\'');
});
describe('constructor', function () {
it('should return a new Dataset object', function () {
expect(sqlQuery).toBeDefined();
});
it('should autogenerate an id', function () {
expect(sqlQuery.getId()).toMatch(/S\d+/);
});
it('should throw an error if query is not provided', function () {
expect(function () {
new carto.source.SQL(); // eslint-disable-line
}).toThrowError('SQL Source must have a SQL query.');
});
it('should throw an error if query is empty', function () {
expect(function () {
new carto.source.SQL(''); // eslint-disable-line
}).toThrowError('SQL Source must have a SQL query.');
});
it('should throw an error if query is not a valid string', function () {
expect(function () {
new carto.source.SQL(3333); // eslint-disable-line
}).toThrowError('SQL Query must be a string.');
});
});
describe('.setQuery', function () {
it('should set the query', function () {
sqlQuery.setQuery('SELECT foo FROM bar');
expect(sqlQuery.getQuery()).toEqual('SELECT foo FROM bar');
});
it('should throw an error if query is empty', function () {
expect(function () {
sqlQuery.setQuery('');
}).toThrowError('SQL Source must have a SQL query.');
});
it('should throw an error if query is not a valid string', function () {
expect(function () {
sqlQuery.setQuery(333);
}).toThrowError('SQL Query must be a string.');
});
it('should trigger an queryChanged event when there is no internal model', function (done) {
var expectedQuery = 'SELECT * FROM ne_10m_populated_places_simple LIMIT 10';
sqlQuery.on('queryChanged', function (newQuery) {
expect(newQuery).toEqual(expectedQuery);
done();
});
sqlQuery.setQuery(expectedQuery);
});
it('should trigger an queryChanged event when there is an internal model', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(sqlQuery, style);
var queryChangedSpy = jasmine.createSpy('queryChangedSpy');
var newQuery = 'SELECT * FROM ne_10m_populated_places_simple LIMIT 10';
sqlQuery.on('queryChanged', queryChangedSpy);
client.addLayer(layer)
.then(function () {
return sqlQuery.setQuery(newQuery);
})
.then(function () {
expect(queryChangedSpy).toHaveBeenCalledWith(newQuery);
done();
});
});
it('should return a resolved promise when there is no internal model', function (done) {
var newQuery = 'SELECT * FROM ne_10m_populated_places_simple';
sqlQuery.setQuery(newQuery)
.then(function () {
expect(sqlQuery.getQuery()).toEqual(newQuery);
done();
});
});
it('should return a resolved promise when there is an internal model', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(sqlQuery, style);
var newQuery = 'SELECT * FROM ne_10m_populated_places_simple LIMIT 10';
client.addLayer(layer)
.then(function () {
return sqlQuery.setQuery(newQuery);
})
.then(function () {
expect(sqlQuery.getQuery()).toEqual(newQuery);
done();
});
});
it('should return a rejected promise with a CartoError when there is an internal model (and a reload error)', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var style = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(sqlQuery, style);
var newQuery = 'SELECT * FROM invalid_dataset';
client.addLayer(layer)
.then(function () {
return sqlQuery.setQuery(newQuery);
})
.catch(function (cartoError) {
expect(cartoError.message).toMatch(/Invalid dataset name used. Dataset "invalid_dataset" does not exist./);
done();
});
});
});
describe('$setEngine', function () {
it('should create an internal model with the dataset attrs and the engine', function () {
sqlQuery.$setEngine('fakeEngine');
var internalModel = sqlQuery.$getInternalModel();
expect(internalModel.get('id')).toEqual(sqlQuery.getId());
expect(internalModel.get('query')).toEqual('SELECT * FROM ne_10m_populated_places_simple WHERE adm0name = \'Spain\'');
expect(internalModel._engine).toEqual('fakeEngine');
});
});
describe('.getQueryToApply', function () {
let populatedPlacesSQL;
beforeEach(function () {
populatedPlacesSQL = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple');
spyOn(populatedPlacesSQL, '_updateInternalModelQuery');
});
it('should return original query if applied filters returns no SQL', function () {
expect(populatedPlacesSQL._getQueryToApply()).toBe('SELECT * FROM ne_10m_populated_places_simple');
});
it('should return wrapped query if filters are applied', function () {
populatedPlacesSQL.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
expect(populatedPlacesSQL._getQueryToApply()).toBe("SELECT * FROM (SELECT * FROM ne_10m_populated_places_simple) as originalQuery WHERE fake_column IN ('category')");
});
});
describe('.addFilter', function () {
let populatedPlacesSQL;
beforeEach(function () {
spyOn(Base.prototype, 'addFilter');
populatedPlacesSQL = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple');
spyOn(populatedPlacesSQL, '_updateInternalModelQuery');
});
it('should call original addFilter and _updateInternalModelQuery', function () {
populatedPlacesSQL.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
expect(Base.prototype.addFilter).toHaveBeenCalled();
expect(populatedPlacesSQL._updateInternalModelQuery).toHaveBeenCalledWith(populatedPlacesSQL._getQueryToApply());
});
});
describe('.removeFilter', function () {
let populatedPlacesDataset, filter;
beforeEach(function () {
spyOn(Base.prototype, 'addFilter');
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
spyOn(populatedPlacesDataset, '_updateInternalModelQuery');
filter = populatedPlacesDataset.addFilter(new carto.filter.Category('fake_column', { in: ['category'] }));
populatedPlacesDataset.addFilter(filter);
});
it('should call original removeFilter and _updateInternalModelQuery', function () {
populatedPlacesDataset.removeFilter(filter);
expect(Base.prototype.addFilter).toHaveBeenCalled();
expect(populatedPlacesDataset._updateInternalModelQuery).toHaveBeenCalledWith(populatedPlacesDataset._getQueryToApply());
});
});
describe('.getFilters', function () {
let populatedPlacesDataset, filter;
beforeEach(function () {
filter = new carto.filter.Category('fake_column', { in: ['category'] });
populatedPlacesDataset = new carto.source.Dataset('ne_10m_populated_places_simple');
populatedPlacesDataset.addFilter(filter);
});
it('should return added filters', function () {
expect(populatedPlacesDataset.getFilters()).toEqual([filter]);
});
});
describe('errors', function () {
it('should trigger an error when invalid', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
// The following sql has the invalid operator: ===
var invalidSource = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple WHERE adm0name === \'Spain\'');
var cartoCss = new carto.style.CartoCSS('#layer { marker-fill: red; }');
invalidSource.on('error', function (cartoError) {
expect(cartoError.message).toMatch(/operator does not exist/);
done();
});
var layer = new carto.layer.Layer(invalidSource, cartoCss);
client.addLayer(layer).catch(function () { }); // Prevent console "uncaught error" warning.
});
it('should trigger a CartoError when there is an error in the internal model', function () {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var source = new carto.source.SQL('SELECT * FROM ne_10m_populated_places_simple');
var cartoCss = new carto.style.CartoCSS('#layer { marker-fill: red; }');
var layer = new carto.layer.Layer(source, cartoCss);
var spy = jasmine.createSpy('spy');
source.on(carto.events.ERROR, spy);
client.addLayer(layer);
source._internalModel.set('error', 'an error');
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({
name: 'CartoError',
originalError: 'an error'
}));
});
});
});

View File

@@ -0,0 +1,161 @@
var carto = require('../../../../../src/api/v4');
describe('api/v4/style/cartocss', function () {
var cartoCSS;
beforeEach(function () {
cartoCSS = new carto.style.CartoCSS('#layer { marker-width:10; }');
});
describe('constructor', function () {
it('should return an object', function () {
expect(cartoCSS).toBeDefined();
});
it('should throw an error if cartoCSS is not provided', function () {
expect(function () {
new carto.style.CartoCSS(); // eslint-disable-line
}).toThrowError('CartoCSS is required.');
});
it('should throw an error if cartoCSS is empty', function () {
expect(function () {
new carto.style.CartoCSS(''); // eslint-disable-line
}).toThrowError('CartoCSS is required.');
});
it('should throw an error if cartoCSS is not a valid string', function () {
expect(function () {
new carto.style.CartoCSS(true); // eslint-disable-line
}).toThrowError('CartoCSS must be a string.');
});
});
describe('errors', function () {
it('should trigger a CartoError when the style is not valid', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var source = new carto.source.Dataset('ne_10m_populated_places_simple');
var invalidCartoCSS = new carto.style.CartoCSS('#layer { invalid-property: 10; }');
invalidCartoCSS.on('error', function (cartoError) {
expect(cartoError.message).toMatch(/Unrecognized rule "invalid-property"/);
done();
});
var layer = new carto.layer.Layer(source, invalidCartoCSS);
client.addLayer(layer)
.catch(function () { }); // Prevent console "uncaught error" warning.
});
it('should NOT trigger a CartoError when the style is valid but there are some other errors', function (done) {
var client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
var source = new carto.source.Dataset('invalid_dataset');
var invalidCartoCSS = new carto.style.CartoCSS('#layer { marker-with: 10; }');
var errorCallbackSpy = jasmine.createSpy('errorCallbackSpy').and.callThrough();
invalidCartoCSS.on('error', errorCallbackSpy);
var layer = new carto.layer.Layer(source, invalidCartoCSS);
client.addLayer(layer)
.catch(function () {
expect(errorCallbackSpy).not.toHaveBeenCalled();
done();
});
});
});
describe('.getContent', function () {
it('should return the internal style', function () {
var expected = '#layer { marker-width:10; }';
var actual = cartoCSS.getContent();
expect(actual).toEqual(expected);
});
});
describe('.setContent', function () {
var layer;
var source;
var newContent;
beforeEach(function () {
source = new carto.source.Dataset('ne_10m_populated_places_simple');
layer = new carto.layer.Layer(source, cartoCSS);
newContent = '#layer { marker-fill: #FABADA }';
});
describe('when no engine is attached', function () {
it('should update the internal style', function (done) {
cartoCSS.setContent(newContent)
.then(function () {
expect(cartoCSS.getContent()).toEqual(newContent);
done();
});
});
it('should trigger a contentChanged event', function (done) {
var contentChangedSpy = jasmine.createSpy('contentChangedSpy');
cartoCSS.on('contentChanged', contentChangedSpy);
cartoCSS.setContent(newContent)
.then(function () {
expect(cartoCSS.getContent()).toEqual(newContent);
expect(contentChangedSpy).toHaveBeenCalled();
done();
});
});
});
describe('when an engine is attached', function () {
var client;
beforeAll(function () {
client = new carto.Client({
apiKey: '84fdbd587e4a942510270a48e843b4c1baa11e18',
username: 'cartojs-test'
});
});
it('should update the internal style', function (done) {
client.addLayer(layer)
.then(function () {
return cartoCSS.setContent(newContent);
})
.then(function () {
expect(cartoCSS.getContent()).toEqual(newContent);
done();
});
});
it('should trigger a contentChanged event?', function (done) {
var contentChangedSpy = jasmine.createSpy('contentChangedSpy');
cartoCSS.on('contentChanged', contentChangedSpy);
client.addLayer(layer)
.then(function () {
return cartoCSS.setContent(newContent);
})
.then(function () {
expect(contentChangedSpy).toHaveBeenCalled();
done();
})
.catch(console.warn);
});
it('should reject the promise with a CartoError when the reload fails', function (done) {
var malformedStyle = '#layer { invalid-property: foo }';
client.addLayer(layer)
.then(function () {
return cartoCSS.setContent(malformedStyle);
})
.catch(function (cartoError) {
expect(cartoError.message).toMatch(/Unrecognized rule "invalid-property"/);
done();
});
});
});
});
});

View File

@@ -0,0 +1,352 @@
var C = require('../../../src/constants');
var VizJSON = require('../../../src/api/vizjson');
describe('src/vis/vizjson', function () {
it('should expose the vizjson attributes', function () {
var vizjson = new VizJSON({
key1: 'value1',
key2: 'value2'
});
expect(vizjson.key1).toEqual('value1');
expect(vizjson.key2).toEqual('value2');
});
it('should have an attribution overlay by default', function () {
var vizjson = new VizJSON({});
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.ATTRIBUTION)).toEqual({
type: C.OVERLAY_TYPES.ATTRIBUTION
});
});
describe('.isNamedMap', function () {
it("should return false if datasource doesn't have a template_name", function () {
var vizjson = new VizJSON({
datasource: { }
});
expect(vizjson.isNamedMap()).toBeFalsy();
});
it('should return true if datasource has a template_name', function () {
var vizjson = new VizJSON({
datasource: {
template_name: 'tpl0123456789'
}
});
expect(vizjson.isNamedMap()).toBeTruthy();
});
});
describe('.hasZoomOverlay', function () {
it("should return true if there's a zoom overlay", function () {
var vizjson = new VizJSON({
overlays: [{
type: C.OVERLAY_TYPES.ZOOM
}]
});
expect(vizjson.hasZoomOverlay()).toBeTruthy();
});
});
describe('.hasOverlay', function () {
it("should return true if there's an overlay with the given type", function () {
var vizjson = new VizJSON({
overlays: [{
type: 'something'
}]
});
expect(vizjson.hasOverlay('something')).toBeTruthy();
});
it("should return false if there isn't an overlay with the given type", function () {
var vizjson = new VizJSON({
overlays: [{
type: 'something'
}]
});
expect(vizjson.hasOverlay('else')).toBeFalsy();
});
});
describe('.getOverlayByType', function () {
it("should return the overlay if there's an overlay with the given type", function () {
var vizjson = new VizJSON({
overlays: [{
type: 'something'
}]
});
expect(vizjson.getOverlayByType('something')).toEqual({
type: 'something'
});
});
it("should return nothing if there isn't an overlay with the given type", function () {
var vizjson = new VizJSON({
overlays: [{
type: 'something'
}]
});
expect(vizjson.getOverlayByType('else')).toBeUndefined();
});
});
describe('.addHeaderOverlay', function () {
it('should add a header Overlay', function () {
var vizjson = new VizJSON({
url: 'https://carto.com',
title: 'title',
description: 'description'
});
vizjson.addHeaderOverlay('show_title', 'show_description', 'is_shareable');
expect(vizjson.getOverlayByType('header')).toEqual({
type: 'header',
order: 1,
shareable: 'is_shareable',
url: 'https://carto.com',
options: {
extra: {
title: 'title',
description: 'description',
show_title: 'show_title',
show_description: 'show_description'
}
}
});
});
});
describe('.addSearchOverlay', function () {
it('should add a search overlay', function () {
var vizjson = new VizJSON({});
vizjson.addSearchOverlay();
expect(vizjson.getOverlayByType('search')).toEqual({
type: 'search',
order: 3
});
});
});
describe('.removeOverlay', function () {
it('should remove the overlay of the given type', function () {
var vizjson = new VizJSON({
overlays: [{
type: 'something'
}]
});
expect(vizjson.getOverlayByType('something')).toBeDefined();
vizjson.removeOverlay('something');
expect(vizjson.getOverlayByType('something')).not.toBeDefined();
});
});
describe('.removeLoaderOverlay', function () {
it('should remove the loader overlay', function () {
var vizjson = new VizJSON({
overlays: [{
type: C.OVERLAY_TYPES.LOADER
}]
});
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.LOADER)).toBeDefined();
vizjson.removeLoaderOverlay();
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.LOADER)).not.toBeDefined();
});
});
describe('.removeZoomOverlay', function () {
it('should remove the zoom overlay', function () {
var vizjson = new VizJSON({
overlays: [{
type: C.OVERLAY_TYPES.ZOOM
}]
});
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.ZOOM)).toBeDefined();
vizjson.removeZoomOverlay();
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.ZOOM)).not.toBeDefined();
});
});
describe('.removeSearchOverlay', function () {
it('should remove the search overlay', function () {
var vizjson = new VizJSON({
overlays: [{
type: C.OVERLAY_TYPES.SEARCH
}]
});
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.SEARCH)).toBeDefined();
vizjson.removeSearchOverlay();
expect(vizjson.getOverlayByType(C.OVERLAY_TYPES.SEARCH)).not.toBeDefined();
});
});
describe('.enforceGMapsBaseLayer', function () {
it('should replace the existing base layer by a GMaps one', function () {
var vizjson = new VizJSON({
map_provider: C.MAP_PROVIDER_TYPES.LEAFLET,
layers: [{
options: {
type: 'Tiled',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'Positron',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '&copy; <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors &copy; <a href= \'https://carto.com/attributions\'>CARTO</a>',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
}
}]
});
vizjson.enforceGMapsBaseLayer('roadmap', { color: 'blue' });
expect(vizjson.layers[0]).toEqual({
options: {
type: 'GMapsBase',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'roadmap',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
baseType: 'roadmap',
style: {color: 'blue'}
}
});
expect(vizjson.map_provider).toEqual(C.MAP_PROVIDER_TYPES.GMAPS);
});
it('should NOT replace the existing base layer by a GMaps one if map_provider is not leaflet', function () {
var vizjson = new VizJSON({
map_provider: 'something',
layers: [{
options: {
type: 'Tiled',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'Positron',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '&copy; <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors &copy; <a href= \'https://carto.com/attributions\'>CARTO</a>',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
}
}]
});
vizjson.enforceGMapsBaseLayer('roadmap', { color: 'blue' });
expect(vizjson.layers[0]).toEqual({
options: {
type: 'Tiled',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'Positron',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '&copy; <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors &copy; <a href= \'https://carto.com/attributions\'>CARTO</a>',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
}
});
expect(vizjson.map_provider).toEqual('something');
});
it('should NOT replace the existing base layer by a GMaps one if the given type is not valid', function () {
var vizjson = new VizJSON({
map_provider: C.MAP_PROVIDER_TYPES.LEAFLET,
layers: [{
options: {
type: 'Tiled',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'Positron',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '&copy; <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors &copy; <a href= \'https://carto.com/attributions\'>CARTO</a>',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
}
}]
});
vizjson.enforceGMapsBaseLayer('invalid type', { color: 'blue' });
expect(vizjson.layers[0]).toEqual({
options: {
type: 'Tiled',
url: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
name: 'Positron',
className: 'httpsbasemapscartocdncomlight_nolabelszxypng',
attribution: '&copy; <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors &copy; <a href= \'https://carto.com/attributions\'>CARTO</a>',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
}
});
expect(vizjson.map_provider).toEqual(C.MAP_PROVIDER_TYPES.LEAFLET);
});
});
describe('.setZoom', function () {
it('should set a new zoom and unset bounds', function () {
var vizjson = new VizJSON({
zoom: 'old_zoom',
bounds: 'bounds'
});
vizjson.setZoom('new_zoom');
expect(vizjson.zoom).toEqual('new_zoom');
expect(vizjson.bounds).toBeNull();
});
});
describe('.setCenter', function () {
it('should set a new center and unset bounds', function () {
var vizjson = new VizJSON({
center: 'old_center',
bounds: 'bounds'
});
vizjson.setCenter('new_center');
expect(vizjson.center).toEqual('new_center');
expect(vizjson.bounds).toBeNull();
});
});
describe('.setBounds', function () {
it('should set bounds', function () {
var vizjson = new VizJSON({
bounds: 'old_bounds'
});
vizjson.setBounds('new_bounds');
expect(vizjson.bounds).toEqual('new_bounds');
});
});
describe('.setVector', function () {
it('should set vector', function () {
var vizjson = new VizJSON({
vector: true
});
vizjson.setVector(false);
expect(vizjson.vector).toBeFalsy();
});
});
});

View File

@@ -0,0 +1,20 @@
var torque = require('../../src/cartodb.mod.torque');
describe('torque', function () {
it('should set a window.torque object', function () {
expect(torque).toBeDefined();
expect(window.torque).toBe(torque);
});
it('should modify the window.cartodb object', function () {
expect(window.cartodb).toEqual(jasmine.any(Object));
var cdb = window.cartodb;
expect(cdb.geo).toEqual(jasmine.any(Object));
expect(cdb.geo.GMapsTorqueLayerView).toEqual(jasmine.any(Function));
expect(cdb.geo.LeafletTorqueLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.ui).toEqual(jasmine.any(Object));
});
});

147
test/spec/cartodb.spec.js Normal file
View File

@@ -0,0 +1,147 @@
/* global cartodb */
var cdb = require('../../src/cartodb');
describe('cartodb.js bundle', function () {
it('should set cartodb object in global namespace', function () {
expect(cdb).toEqual(jasmine.any(Object));
});
it('should have leaflet set', function () {
expect(cdb.L).toEqual(jasmine.any(Object));
});
it('should have jQuery in addition to the defaults', function () {
expect(cartodb.$).toBeDefined();
expect(window.$).toBeUndefined(); // …but not in global scope though
});
describe('shared for cdb object in all bundles', function () {
it('should set cartodb object in global namespace', function () {
expect(window.cdb).toBeDefined();
expect(window.cartodb).toBeDefined();
expect(window.cartodb).toBe(window.cdb);
});
it('should have common object placeholders', function () {
expect(cdb.core).toEqual(jasmine.any(Object));
expect(cdb.vis).toEqual(jasmine.any(Object));
expect(cdb.geo).toEqual(jasmine.any(Object));
expect(cdb.geo.ui).toEqual(jasmine.any(Object));
expect(cdb.geo.geocoder).toEqual(jasmine.any(Object));
expect(cdb.ui).toEqual(jasmine.any(Object));
expect(cdb.ui.common).toEqual(jasmine.any(Object));
});
it('should have expected objects on cdb object', function () {
expect(cdb.core).toEqual(jasmine.any(Object));
expect(cdb.vis).toEqual(jasmine.any(Object));
expect(cdb.vis.Loader).toEqual(jasmine.any(Object));
expect(cdb.core.Loader).toBe(cdb.vis.Loader);
expect(cdb.core.Profiler).toEqual(jasmine.any(Function));
expect(cdb.core.util).toEqual(jasmine.any(Object));
expect(cdb.SQL).toEqual(jasmine.any(Function));
expect(cdb.Promise).toEqual(jasmine.any(Function));
expect(cdb.VERSION).toEqual(jasmine.any(String));
expect(cdb.DEBUG).toEqual(jasmine.any(Boolean));
expect(cdb.CARTOCSS_VERSIONS).toEqual(jasmine.any(Object));
expect(cdb.CARTOCSS_DEFAULT_VERSION).toEqual(jasmine.any(String));
});
});
describe('shared for cdb object in all bundles except for core', function () {
it('should have the commonly used vendor libs defined', function () {
expect(cdb.$).toEqual(jasmine.any(Function));
expect(cdb.Mustache).toEqual(jasmine.any(Object));
expect(cdb.Backbone).toEqual(jasmine.any(Object));
expect(cdb._).toEqual(jasmine.any(Function));
});
it('should have some common objects', function () {
expect(cdb.config).toEqual(jasmine.any(Object));
expect(cdb.log).toEqual(jasmine.any(Object));
expect(cdb.errors).toEqual(jasmine.any(Object));
expect(cdb.templates).toEqual(jasmine.any(Object));
expect(cdb.decorators).toEqual(jasmine.any(Object));
expect(cdb.createVis).toEqual(jasmine.any(Function));
});
it('config should contain links variables', function () {
expect(cdb.config.get('cartodb_attributions')).toEqual('© <a href="https://carto.com/attributions" target="_blank">CARTO</a>');
expect(cdb.config.get('cartodb_logo_link')).toEqual('http://www.carto.com');
});
it('should generate error when error is called', function () {
expect(cdb.config).toBeDefined();
cdb.config.ERROR_TRACK_ENABLED = true;
cdb.errors.reset([]);
cdb.log.error('this is an error');
expect(cdb.errors.size()).toEqual(1);
});
it('should create a cdb.core with expected model', function () {
expect(cdb.core.Template).toBeDefined();
expect(cdb.core.TemplateList).toBeDefined();
expect(cdb.core.Model).toBeDefined();
expect(cdb.core.View).toBeDefined();
expect(cdb.core.Loader).toEqual(jasmine.any(Object));
});
it('should create a cdb.decorators', function () {
expect(cdb.decorators).toBeDefined();
});
it('should create a log', function () {
expect(cdb.log).toBeDefined();
});
it('should add templates stuff', function () {
expect(cdb.templates instanceof cdb.core.TemplateList).toBe(true);
});
it('should have a cdb.ui.common object', function () {
expect(cdb.ui.common.FullScreen).toEqual(jasmine.any(Function));
});
it('should have a cdb.geo object', function () {
expect(cdb.geo).toEqual(jasmine.any(Object));
expect(cdb.geo.geocoder).toEqual(jasmine.any(Object));
expect(cdb.geo.geocoder.YAHOO).toEqual(jasmine.any(Object));
expect(cdb.geo.geocoder.NOKIA).toEqual(jasmine.any(Object));
expect(cdb.geo.TileLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.GMapsBaseLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.WMSLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.PlainLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.TorqueLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.CartoDBLayer).toEqual(jasmine.any(Function));
expect(cdb.geo.Map).toEqual(jasmine.any(Function));
expect(cdb.geo.MapView).toEqual(jasmine.any(Function));
});
it('should have a cdb.geo.ui object', function () {
expect(cdb.geo.ui.InfowindowModel).toEqual(jasmine.any(Function));
expect(cdb.geo.ui.Infowindow).toEqual(jasmine.any(Function));
expect(cdb.geo.ui.Search).toEqual(jasmine.any(Function));
expect(cdb.geo.ui.TilesLoader).toEqual(jasmine.any(Function));
expect(cdb.geo.ui.Tooltip).toEqual(jasmine.any(Function));
});
it('should have a cdb.common object', function () {
expect(cdb.geo.common).toEqual(jasmine.any(Object));
});
it('should have a core.vis', function () {
expect(cdb.vis).toEqual(jasmine.any(Object));
expect(cdb.vis.Loader).toBe(cdb.core.Loader);
expect(cdb.vis.Vis).toEqual(jasmine.any(Function));
expect(cdb.vis.INFOWINDOW_TEMPLATE).toEqual(jasmine.any(Object));
});
});
});

View File

@@ -0,0 +1,132 @@
var $ = require('jquery');
var Model = require('../../../src/core/model');
describe('core/model', function () {
var TestModel;
var model;
beforeEach(function () {
TestModel = Model.extend({
initialize: function () {
this.initCalled = true;
Model.prototype.initialize.call(this);
},
url: 'irrelevant.json',
test_method: function () {}
});
spyOn(Model.prototype, 'initialize').and.callThrough();
model = new TestModel();
});
it('should call initialize', function () {
expect(model.initCalled).toBe(true);
expect(Model.prototype.initialize).toHaveBeenCalled();
});
it('should attach save to the element context', function () {
spyOn(model, 'save');
model.bind('irrelevantEvent', model.save);
model.trigger('irrelevantEvent');
expect(model.save).toHaveBeenCalled();
});
it('should attach fetch to the element context', function () {
spyOn(model, 'fetch');
model.bind('irrelevantEvent', model.fetch);
model.trigger('irrelevantEvent');
expect(model.fetch).toHaveBeenCalled();
});
it('should add the correct response from server', function () {
model.sync = function (method, model, options) {
options.success({ 'response': true });
};
model.fetch();
expect(model.get('response')).toBeTruthy();
});
it("should trigger 'loadModelStarted' event when fetch", function () {
var loadModelStartedSpy = jasmine.createSpy('loadModelStarted');
model.bind('loadModelStarted', loadModelStartedSpy);
model.fetch();
expect(loadModelStartedSpy).toHaveBeenCalled();
});
it("should trigger 'loadModelCompleted' event when fetched", function () {
model.sync = function (method, model, options) {
var dfd = $.Deferred();
options.success({ 'response': true });
dfd.resolve();
return dfd.promise();
};
var loadModelCompletedSpy = jasmine.createSpy('loadModelCompleted');
model.bind('loadModelCompleted', loadModelCompletedSpy);
model.fetch();
expect(loadModelCompletedSpy).toHaveBeenCalled();
});
it("should trigger 'loadModelFailed' event when fetch fails", function () {
model.url = 'irrelevantError.json';
model.sync = function (method, model, options) {
var dfd = $.Deferred();
options.error({ 'response': true });
return dfd.reject();
};
var loadModelFailedSpy = jasmine.createSpy('loadModelFailed');
model.bind('loadModelFailed', loadModelFailedSpy);
model.fetch();
expect(loadModelFailedSpy).toHaveBeenCalled();
});
it('should retrigger an event when launched on a descendant object', function (done) {
model.child = new TestModel({});
model.retrigger('cachopo', model.child);
var spy = jasmine.createSpy('spy');
model.bind('cachopo', spy);
model.child.trigger('cachopo');
setTimeout(function () {
expect(spy).toHaveBeenCalled();
done();
}, 25);
});
it("should trigger 'saving' event when save", function () {
var savingSpy = jasmine.createSpy('saving');
model.bind('saving', savingSpy);
model.save();
expect(savingSpy).toHaveBeenCalled();
});
it("should trigger 'saved' event when saved", function () {
model.sync = function (method, model, options) {
var dfd = $.Deferred();
options.success({ 'response': true });
dfd.resolve();
return dfd.promise();
};
var savedSpy = jasmine.createSpy('saving');
model.bind('saved', savedSpy);
model.save();
expect(savedSpy).toHaveBeenCalled();
});
it("should trigger 'errorSaving' event when save fails", function () {
model.url = 'irrelevantError.json';
model.sync = function (method, model, options) {
var dfd = $.Deferred();
options.error({ 'response': true });
return dfd.reject();
};
var errorSavingSpy = jasmine.createSpy('errorSaving');
model.bind('errorSaving', errorSavingSpy);
model.save();
expect(errorSavingSpy).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,58 @@
var sanitize = require('../../../src/core/sanitize');
describe('core/sanitize', function () {
describe('.html', function () {
describe('when given a HTML', function () {
it('should allow safe HTML', function () {
expect(sanitize.html('test')).toEqual('test');
expect(sanitize.html('<div>works</div>')).toEqual('<div>works</div>');
});
it('should remove unsafe stuff', function () {
expect(sanitize.html('<img src="fail.png" onerror="document.body.appendChild(document.createElement(\'script\')).src=\'http://localhost/xss.js\'" /> nono')).toEqual('<img src="fail.png"> nono');
expect(sanitize.html('nono <scrip src="ext.js"></script>')).toEqual('nono ');
});
it('should allow target attributes for links', function () {
expect(sanitize.html('<a href="https://carto.com/" target="_blank">carto.com</a>')).toEqual('<a href="https://carto.com/" target="_blank">carto.com</a>');
});
it('should remove iframe tag', function () {
expect(sanitize.html('no <iframe src="" onload="document.body.appendChild(document.createElement(\'script\')).src=\'http://localhost/xss.js\'"/> no')).toEqual('no ');
});
});
describe('when given an 2nd param with a function', function () {
beforeEach(function () {
this.optionalSanitizer = jasmine.createSpy('optionalSanitizer').and.returnValue('optional sanitizer result');
});
it('should use that to sanitize instead', function () {
expect(sanitize.html('<p>something</p>', this.optionalSanitizer)).toEqual('optional sanitizer result');
expect(this.optionalSanitizer).toHaveBeenCalled();
expect(this.optionalSanitizer).toHaveBeenCalledWith('<p>something</p>');
});
});
describe('when given a 2nd param with a non-undefined/function value', function () {
it('should skip sanitize', function () {
expect(sanitize.html('<script src="i-know-what-im-doing.js"></script>', false)).toEqual('<script src="i-know-what-im-doing.js"></script>');
expect(sanitize.html('<script src="i-know-what-im-doing.js"></script>', null)).toEqual('<script src="i-know-what-im-doing.js"></script>');
});
});
describe('common XSS attacks', function () {
var attacks = [
'<iframe><iframe src="/>"><p <a><img/src="x"/onerror="prompt(document.cookie)">',
"<iframe srcdoc='&lt;svg/onload=alert(document.cookie)&gt;>"
];
it('should avoid `' + attacks[0] + '`', function () {
expect(sanitize.html(attacks[0])).toEqual('');
});
it('should avoid `' + attacks[1] + '`', function () {
expect(sanitize.html(attacks[1])).toEqual('');
});
});
});
});

View File

@@ -0,0 +1,21 @@
var log = require('cdb.log');
var TemplateList = require('../../../src/core/template-list');
describe('core/template-list', function () {
var tmpl;
beforeEach(function () {
spyOn(log, 'error');
tmpl = new TemplateList();
tmpl.reset([
{name: 't1', template: 'hi, my name is <%= name %>'},
{name: 't2', template: 'byee!! <%= name %>'}
]);
});
it('should get template by name', function () {
expect(tmpl.getTemplate('t1')).toBeTruthy();
expect(tmpl.getTemplate('t2')({name: 'cartojs-test'})).toEqual('byee!! cartojs-test');
expect(tmpl.getTemplate('nononon')).toBeFalsy();
});
});

View File

@@ -0,0 +1,31 @@
var Template = require('../../../src/core/template');
describe('core/template', function () {
var tmpl;
beforeEach(function () {
tmpl = new Template({
template: 'hi, my name is <%= name %>'
});
});
it('should render', function () {
expect(tmpl.render({name: 'cartojs-test'})).toEqual('hi, my name is cartojs-test');
});
it('should accept compiled templates', function () {
tmpl = new Template({
compiled: function (vars) { return 'hola ' + vars.name; }
});
expect(tmpl.render({name: 'cartojs-test'})).toEqual('hola cartojs-test');
});
it('should render using mustache', function () {
tmpl = new Template({
template: 'hi, my name is {{ name }}',
type: 'mustache'
});
expect(tmpl.render({name: 'cartojs-test'})).toEqual('hi, my name is cartojs-test');
});
});

107
test/spec/core/util.spec.js Normal file
View File

@@ -0,0 +1,107 @@
var util = require('../../../src/core/util');
describe('core/util', function () {
it('should identify user agents properly', function () {
var browser, ua;
ua =
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36';
browser = util._inferBrowser(ua);
expect(browser.chrome).toBeDefined();
ua =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
browser = util._inferBrowser(ua);
expect(browser.safari).toBeDefined();
ua =
'Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16';
browser = util._inferBrowser(ua);
expect(browser.opera).toBeDefined();
ua =
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko';
browser = util._inferBrowser(ua);
expect(browser.ie).toBeDefined();
expect(browser.ie.version).toMatch(/\d+/);
ua = 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0';
browser = util._inferBrowser(ua);
expect(browser.firefox).toBeDefined();
ua =
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10136';
browser = util._inferBrowser(ua);
expect(browser.edge).toBeDefined();
});
describe('supportsTouch', function () {
var currentOnTouchStartValue, currentTouchPointsValue;
beforeEach(function () {
currentOnTouchStartValue = window.ontouchstart;
currentTouchPointsValue = navigator.msMaxTouchPoints;
window.ontouchstart = function () {};
});
it('should support it if ontouchstart event is defined', function () {
window.ontouchstart = 'something';
expect(util.supportsTouch()).toBeTruthy();
});
it('should support it if msMaxTouchPoints has more than one', function () {
Object.defineProperty(window, 'ontouchstart', {
value: undefined,
writable: true
});
navigator.msMaxTouchPoints = 2;
expect(util.supportsTouch()).toBeTruthy();
});
afterEach(function () {
window.ontouchstart = currentOnTouchStartValue;
navigator.msMaxTouchPoints = currentTouchPointsValue;
});
});
describe('google maps checks', function () {
var existingGMaps = null;
beforeEach(function () {
existingGMaps = window.google;
window.google = undefined;
});
afterEach(function () {
window.google = existingGMaps;
});
it('gmaps is required as global and it must be greater than 3.31', function () {
var checkGoogle = function () {
util.isGoogleMapsLoaded();
};
expect(checkGoogle).toThrowError('Google Maps is required');
window.google = { something: 'something' };
expect(checkGoogle).toThrowError('Google Maps is required');
var INVALID_VERSION_MESSAGE =
'Google Maps version should be >= 3.31';
window.google.maps = {
version: '2.9.9'
};
expect(checkGoogle).toThrowError(INVALID_VERSION_MESSAGE);
window.google.maps = {
version: '3.33.0'
};
expect(checkGoogle).not.toThrowError(INVALID_VERSION_MESSAGE);
window.google.maps = {
version: '3.31.0'
};
expect(checkGoogle).not.toThrow();
});
});
});

165
test/spec/core/view.spec.js Normal file
View File

@@ -0,0 +1,165 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var View = require('../../../src/core/view');
describe('core/view', function () {
var TestView;
var view;
beforeEach(function () {
TestView = View.extend({
initialize: function () {
this.init_called = true;
},
test_method: function () {}
});
View.viewCount = 0;
view = new TestView({
el: $('<div>')
});
});
it('should call initialize', function () {
expect(view.init_called).toEqual(true);
});
it('should increment refCount', function () {
expect(View.viewCount).toEqual(1);
expect(View.views[view.cid]).toBeTruthy();
});
it('should decrement refCount', function () {
view.clean();
expect(View.viewCount).toEqual(0);
expect(View.views[view.cid]).toBeFalsy();
});
it('clean should remove view from dom', function () {
var dom = $('<div>');
dom.append(view.el);
expect(dom.children().length).toEqual(1);
view.clean();
expect(dom.children().length).toEqual(0);
});
it('clean should unbind all events', function () {
view.bind('meh', function () {});
expect(_.size(view._events)).toEqual(1);
view.clean();
expect(view._events).toEqual(undefined);
});
it('should unlink the view model', function () {
var called = false;
var newView = new TestView({ el: $('<div>'), model: new Backbone.Model() });
spyOn(newView, 'test_method');
newView.model.bind('change', newView.test_method, newView);
newView.model.bind('change', function () { called = true; });
newView.model.trigger('change');
expect(called).toEqual(true);
expect(newView.test_method).toHaveBeenCalled();
expect(newView.test_method.calls.count()).toEqual(1);
called = false;
newView.clean();
// trigger again
newView.model.trigger('change');
expect(called).toEqual(true);
expect(newView.test_method.calls.count()).toEqual(1);
});
it('should unlink linked models', function () {
var called = false;
var model = new Backbone.Model();
spyOn(view, 'test_method');
model.bind('change', view.test_method, view);
model.bind('change', function () { called = true; });
view.add_related_model(model);
model.trigger('change');
expect(called).toEqual(true);
expect(view.test_method).toHaveBeenCalled();
expect(view.test_method.calls.count()).toEqual(1);
called = false;
view.clean();
expect(_.size(view._models)).toEqual(0);
// trigger again
model.trigger('change');
expect(called).toEqual(true);
expect(view.test_method.calls.count()).toEqual(1);
});
it('should add and remove subview', function () {
var v1 = new View();
view.addView(v1);
expect(view._subviews[v1.cid]).toEqual(v1);
expect(v1._parent).toEqual(view);
view.removeView(v1);
expect(view._subviews[v1.cid]).toEqual(undefined);
});
it('should remove and clean subviews', function () {
var v1 = new View();
spyOn(v1, 'clean');
view.addView(v1);
expect(view._subviews[v1.cid]).toEqual(v1);
view.clean();
expect(view._subviews[v1.cid]).toEqual(undefined);
expect(v1.clean).toHaveBeenCalled();
});
it('subview shuould be removed from its parent', function () {
var v1 = new View();
view.addView(v1);
expect(view._subviews[v1.cid]).toEqual(v1);
v1.clean();
expect(view._subviews[v1.cid]).toEqual(undefined);
});
it('extendEvents should extend events', function () {
var V1 = View.extend({
events: View.extendEvents({
'click': 'hide'
})
});
var v1 = new V1();
expect(v1.el.style.display).not.toEqual('none');
v1.$el.trigger('click');
expect(v1.el.style.display).toEqual('none');
});
it('should retrigger an event when launched on a descendant object', function (done) {
var launched = false;
view.child = new TestView({});
view.retrigger('cachopo', view.child);
view.bind('cachopo', function () {
launched = true;
});
view.child.trigger('cachopo');
setTimeout(function () {
expect(launched).toBeTruthy();
done();
}, 25);
});
it('should kill an event', function () {
var ev = {
stopPropagation: function () {},
preventDefault: function () {}
};
var ev2 = 'thisisnotanevent';
spyOn(ev, 'stopPropagation');
spyOn(ev, 'preventDefault');
view.killEvent(ev);
view.killEvent(ev2);
view.killEvent();
expect(ev.stopPropagation).toHaveBeenCalled();
expect(ev.preventDefault).toHaveBeenCalled();
});
});

View File

@@ -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();
}

View File

@@ -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());
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

568
test/spec/engine.spec.js Normal file
View File

@@ -0,0 +1,568 @@
var $ = require('jquery');
var Engine = require('../../src/engine');
var WindshaftError = require('../../src/windshaft/error');
var MockFactory = require('../helpers/mockFactory');
var createEngine = require('../spec/fixtures/engine.fixture.js');
var FAKE_RESPONSE = require('./windshaft/response.mock');
var FAKE_ERROR_PAYLOAD = require('./windshaft/error.mock');
var CartoDBLayer = require('../../src/geo/map/cartodb-layer');
var Dataview = require('../../src/dataviews/dataview-model-base');
var Backbone = require('backbone');
describe('Engine', function () {
var engineMock;
beforeEach(function () {
engineMock = createEngine({
spyReload: false,
username: 'fake-username'
});
});
describe('Constructor', function () {
it('should throw a descriptive error when called with no parameters', function () {
expect(function () {
new Engine(); // eslint-disable-line
}).toThrowError('new Engine() called with no parameters');
});
});
describe('events', function () {
var spy;
beforeEach(function () {
spy = jasmine.createSpy('spy');
});
describe('on', function () {
it('should register a callback thats called for "fake-event"', function () {
engineMock.on('fake-event', spy);
expect(spy).not.toHaveBeenCalled(); // Ensure the spy not has been called previosuly
engineMock._eventEmmitter.trigger('fake-event');
expect(spy).toHaveBeenCalled();
});
});
describe('off', function () {
it('should unregister a callback', function () {
engineMock.on('fake-event', spy);
expect(spy).not.toHaveBeenCalled(); // Ensure the spy not has been called previosuly
engineMock._eventEmmitter.trigger('fake-event');
expect(spy).toHaveBeenCalled();
engineMock.off('fake-event', spy);
engineMock._eventEmmitter.trigger('fake-event');
expect(spy.calls.count()).toBe(1);
});
});
});
describe('.addLayer', function () {
it('should add a layer', function () {
var style = '#layer { marker-color: red; }';
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var layer = new CartoDBLayer({ source: source, style: style }, { engine: engineMock });
expect(engineMock._layersCollection.length).toEqual(0);
engineMock.addLayer(layer);
expect(engineMock._layersCollection.length).toEqual(1);
expect(engineMock._layersCollection.at(0)).toEqual(layer);
});
});
describe('.removeLayer', function () {
it('should remove a layer', function () {
var style = '#layer { marker-color: red; }';
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var layer = new CartoDBLayer({ source: source, style: style }, { engine: engineMock });
engineMock.addLayer(layer);
engineMock.removeLayer(layer);
expect(engineMock._layersCollection.length).toEqual(0);
});
});
describe('.moveLayer', function () {
it('should move a layer', function () {
var style = '#layer { marker-color: red; }';
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var layer0 = new CartoDBLayer({ source: source, style: style }, { engine: engineMock });
var layer1 = new CartoDBLayer({ source: source, style: style }, { engine: engineMock });
engineMock.addLayer(layer0);
engineMock.addLayer(layer1);
expect(engineMock._layersCollection.at(0)).toEqual(layer0);
expect(engineMock._layersCollection.at(1)).toEqual(layer1);
engineMock.moveLayer(layer0, 1);
expect(engineMock._layersCollection.at(1)).toEqual(layer0);
expect(engineMock._layersCollection.at(0)).toEqual(layer1);
});
});
describe('.addDataview', function () {
it('should add a new dataview', function () {
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var dataview = new Dataview({ id: 'dataview1', source: source }, { map: {}, engine: engineMock });
expect(engineMock._dataviewsCollection.length).toEqual(0);
engineMock.addDataview(dataview);
expect(engineMock._dataviewsCollection.length).toEqual(1);
expect(engineMock._dataviewsCollection.at(0)).toEqual(dataview);
});
});
describe('._buildParams', function () {
it('should send client tag for analytics when environment is production', function () {
var previousENVValue = __ENV__;
__ENV__ = 'production';
var params = engineMock._buildParams();
expect(params.client).toBeDefined();
__ENV__ = previousENVValue;
});
});
describe('.reload', function () {
var layer;
beforeEach(function () {
var style = '#layer { marker-color: red; }';
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
layer = new CartoDBLayer({ source: source, style: style }, { engine: engineMock });
});
it('should perform a request with the state encoded in a payload (no layers, no dataviews)', function (done) {
spyOn($, 'ajax').and.callFake(function (params) {
var actual = params.url;
var expected = 'http://example.com/api/v1/map?config=%7B%22buffersize%22%3A%7B%22mvt%22%3A0%7D%2C%22layers%22%3A%5B%5D%2C%22dataviews%22%3A%7B%7D%2C%22analyses%22%3A%5B%5D%7D&api_key=' + engineMock.getApiKey();
expect(actual).toEqual(expected);
done();
});
engineMock.reload();
});
it('should perform a request with the state encoded in a payload (single layer)', function (done) {
spyOn($, 'ajax').and.callFake(function (params) {
var actual = params.url;
var expected = 'http://example.com/api/v1/map?config=%7B%22buffersize%22%3A%7B%22mvt%22%3A0%7D%2C%22layers%22%3A%5B%7B%22type%22%3A%22mapnik%22%2C%22options%22%3A%7B%22cartocss_version%22%3A%222.1.0%22%2C%22source%22%3A%7B%22id%22%3A%22a1%22%7D%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%2C%22dataviews%22%3A%7B%7D%2C%22analyses%22%3A%5B%7B%22id%22%3A%22a1%22%2C%22type%22%3A%22source%22%2C%22params%22%3A%7B%22query%22%3A%22SELECT%20*%20FROM%20table%22%7D%7D%5D%7D&api_key=' + engineMock.getApiKey();
expect(actual).toEqual(expected);
done();
});
engineMock.addLayer(layer);
engineMock.reload();
});
describe('when using Promises', function () {
it('should resolve when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
engineMock.reload().then(function (nothing) {
expect(nothing).not.toBeDefined();
done();
});
});
it('should resolve consecutive calls when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
var counter = 0;
var NUM_CALLS = 2;
function _process (nothing) {
expect(nothing).not.toBeDefined();
counter++;
(counter === NUM_CALLS) && done();
}
engineMock.reload().then(_process);
engineMock.reload().then(_process);
});
it('should reject when the server returns an error response', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.error(FAKE_ERROR_PAYLOAD); });
engineMock.reload().catch(function (error) {
expect(error instanceof WindshaftError).toBe(true);
expect(error.message).toBe('Postgis Plugin: ERROR: transform: couldnt project point (242 611 0): latitude or longitude exceeded limits.');
done();
});
});
it('should reject consecutive calls when the server returns an error response', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.error(FAKE_ERROR_PAYLOAD); });
var counter = 0;
var NUM_CALLS = 2;
function _process (error) {
expect(error).toBeDefined();
expect(error instanceof WindshaftError).toBe(true);
expect(error.message).toBe('Postgis Plugin: ERROR: transform: couldnt project point (242 611 0): latitude or longitude exceeded limits.');
counter++;
(counter === NUM_CALLS) && done();
}
engineMock.reload().catch(_process);
engineMock.reload().catch(_process);
});
});
describe('when using Callbacks', function () {
it('should call successCallback when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Attach the success callback to a spy.
var successCallback = jasmine.createSpy('successCallback');
engineMock.reload({ success: successCallback }).then(function () {
expect(successCallback).toHaveBeenCalledWith();
done();
});
});
it('should call consecutive successCallbacks when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Attach the success callbacks to a spy.
var successCallbacks = [
jasmine.createSpy('successCallback0'),
jasmine.createSpy('successCallback1')
];
var counter = 0;
var NUM_CALLS = 2;
function _process () {
expect(successCallbacks[counter]).toHaveBeenCalledWith();
counter++;
(counter === NUM_CALLS) && done();
}
engineMock.reload({ success: successCallbacks[0] }).then(_process);
engineMock.reload({ success: successCallbacks[1] }).then(_process);
});
it('should call errorCallback when the server returns an error response', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.error(FAKE_ERROR_PAYLOAD); });
// Attach the error callback to a spy.
var errorCallback = jasmine.createSpy('errorCallback');
engineMock.reload({ error: errorCallback }).catch(function () {
var error = new WindshaftError({ message: 'Postgis Plugin: ERROR: transform: couldnt project point (242 611 0): latitude or longitude exceeded limits.' });
expect(errorCallback).toHaveBeenCalledWith(error);
done();
});
});
it('should call consecutive errorCallbacks when the server returns an error response', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.error(FAKE_ERROR_PAYLOAD); });
// Attach the error callbacks to a spy.
var errorCallbacks = [
jasmine.createSpy('errorCallback0'),
jasmine.createSpy('errorCallback1')
];
var counter = 0;
var NUM_CALLS = 2;
function _process () {
var error = new WindshaftError({ message: 'Postgis Plugin: ERROR: transform: couldnt project point (242 611 0): latitude or longitude exceeded limits.' });
expect(errorCallbacks[counter]).toHaveBeenCalledWith(error);
counter++;
(counter === NUM_CALLS) && done();
}
engineMock.reload({ error: errorCallbacks[0] }).catch(_process);
engineMock.reload({ error: errorCallbacks[1] }).catch(_process);
});
});
describe('when using Events', function () {
it('should trigger a RELOAD_STARTED event when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Attach the started event handler to a spy.
var spy = jasmine.createSpy('startedEventHandler');
engineMock.on(Engine.Events.RELOAD_STARTED, spy);
engineMock.reload().then(function () {
expect(spy).toHaveBeenCalled();
done();
});
});
it('should trigger a RELOAD_SUCCESS event when the server returns a successful response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Attach the success event handler to a spy.
var spy = jasmine.createSpy('successEventHandler');
engineMock.on(Engine.Events.RELOAD_STARTED, spy);
engineMock.reload().then(function () {
expect(spy).toHaveBeenCalled();
done();
});
});
it('should trigger a RELOAD_ERROR event when the server returns an error response', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.error(FAKE_ERROR_PAYLOAD); });
// Attach the error event handler to a spy.
var spy = jasmine.createSpy('errorEventHandler');
engineMock.on(Engine.Events.RELOAD_ERROR, spy);
engineMock.reload().catch(function () {
var error = new WindshaftError({ message: 'Postgis Plugin: ERROR: transform: couldnt project point (242 611 0): latitude or longitude exceeded limits.' });
expect(spy).toHaveBeenCalledWith(error);
done();
});
});
});
it('should use the sourceID parameter', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
sourceId: 'fakeSourceId'
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), 'fakeSourceId', undefined);
done();
});
});
it('should use the latest sourceID parameter', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
sourceId: 'fakeSourceId'
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), 'fakeSourceId2', undefined);
done();
});
engineMock.reload({
sourceId: 'fakeSourceId2'
});
});
it('should use true for the forceFetch parameter if it is true', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
forceFetch: true
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), undefined, true);
done();
});
});
it('should use true for the forceFetch parameter if any is true', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
forceFetch: false
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), undefined, true);
done();
});
engineMock.reload({
forceFetch: true
});
engineMock.reload({
forceFetch: false
});
});
it('should use false for the forceFetch parameter if it is false', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
forceFetch: false
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), undefined, false);
done();
});
});
it('should use false for the forceFetch parameter if all are false', function (done) {
// Error server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
// Spy on modelupdater to ensure thats called with fakesourceId
var updateModelsSpy = spyOn(engineMock._modelUpdater, 'updateModels');
engineMock.reload({
forceFetch: false
}).then(function () {
expect(updateModelsSpy).toHaveBeenCalledWith(jasmine.anything(), undefined, false);
done();
});
engineMock.reload({
forceFetch: false
});
engineMock.reload({
forceFetch: false
});
});
it('should include the filters when the includeFilters option is true', function (done) {
// Spy on instantiateMap to ensure thats called with fake_response
spyOn(engineMock._windshaftClient, 'instantiateMap').and.callFake(function (request) { request.options.success(FAKE_RESPONSE); });
// Add mock dataview
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var dataview = new Dataview({ id: 'dataview1', source: source }, { filter: new Backbone.Model(), map: {}, engine: engineMock });
dataview.toJSON = jasmine.createSpy('toJSON').and.returnValue('fakeJson');
engineMock.addDataview(dataview);
engineMock.reload({
includeFilters: true
}).then(function () {
expect(engineMock._windshaftClient.instantiateMap).toHaveBeenCalled();
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].options.includeFilters).toEqual(true);
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].params.filters.dataviews.dataviewId).toEqual('dataview1');
done();
});
});
it('should include the filters when the latest includeFilters option is true', function (done) {
// Spy on instantiateMap to ensure thats called with fake_response
spyOn(engineMock._windshaftClient, 'instantiateMap').and.callFake(function (request) { request.options.success(FAKE_RESPONSE); });
engineMock.reload({
includeFilters: false
}).then(function () {
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].options.includeFilters).toEqual(true);
done();
});
engineMock.reload({
includeFilters: false
});
engineMock.reload({
includeFilters: true
});
});
it('should NOT include the filters when the includeFilters option is false', function (done) {
// Spy on instantiateMap to ensure thats called with fake_response
spyOn(engineMock._windshaftClient, 'instantiateMap').and.callFake(function (request) { request.options.success(FAKE_RESPONSE); });
// Add mock dataview
var source = MockFactory.createAnalysisModel({ id: 'a1', type: 'source', query: 'SELECT * FROM table' });
var dataview = new Dataview({ id: 'dataview1', source: source }, { filter: new Backbone.Model(), map: {}, engine: engineMock });
dataview.toJSON = jasmine.createSpy('toJSON').and.returnValue('fakeJson');
engineMock.addDataview(dataview);
engineMock.reload({
includeFilters: false
}).then(function () {
expect(engineMock._windshaftClient.instantiateMap).toHaveBeenCalled();
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].options.includeFilters).toEqual(false);
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].params.filters).toBeUndefined();
done();
});
});
it('should NOT include the filters when the latest includeFilters options is false', function (done) {
// Spy on instantiateMap to ensure thats called with fake_response
spyOn(engineMock._windshaftClient, 'instantiateMap').and.callFake(function (request) { request.options.success(FAKE_RESPONSE); });
engineMock.reload({
includeFilters: true
}).then(function () {
expect(engineMock._windshaftClient.instantiateMap.calls.mostRecent().args[0].options.includeFilters).toEqual(false);
done();
});
engineMock.reload({
includeFilters: true
});
engineMock.reload({
includeFilters: false
});
});
it('should update the layer metadata according to the server response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
engineMock.addLayer(layer);
engineMock.reload().then(function () {
var expectedLayerMetadata = { cartocss: '#layer {\nmarker-color: red;\n}', stats: { estimatedFeatureCount: 10031 }, cartocss_meta: { rules: [] } };
var actualLayerMetadata = engineMock._layersCollection.at(0).attributes.meta;
expect(actualLayerMetadata).toEqual(expectedLayerMetadata);
done();
});
});
it('should update the cartolayerGroup metadata according to the server response', function (done) {
// Successfull server response
spyOn($, 'ajax').and.callFake(function (params) { params.success(FAKE_RESPONSE); });
engineMock.addLayer(layer);
engineMock.reload().then(function () {
var urls = engineMock._cartoLayerGroup.attributes.urls;
// Ensure the modelUpdater has updated the cartoLayerGroup urls
expect(urls.attributes[0]).toEqual('http://3.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/0/attributes');
expect(urls.grids[0]).toEqual(
['http://0.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/0/{z}/{x}/{y}.grid.json',
'http://1.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/0/{z}/{x}/{y}.grid.json',
'http://2.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/0/{z}/{x}/{y}.grid.json',
'http://3.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/0/{z}/{x}/{y}.grid.json']);
expect(urls.image).toEqual('http://{s}.ashbu.cartocdn.com/fake-username/api/v1/map/static/center/2edba0a73a790c4afb83222183782123:1508164637676/{z}/{lat}/{lng}/{width}/{height}.{format}');
expect(urls.tiles).toEqual('http://{s}.ashbu.cartocdn.com/fake-username/api/v1/map/2edba0a73a790c4afb83222183782123:1508164637676/{layerIndexes}/{z}/{x}/{y}.{format}');
done();
});
});
});
describe('CartoLayerGroup bindings', function () {
it('should trigger a windshaft error from CartoLayerGroup error', function () {
var spy = jasmine.createSpy('spy');
engineMock.on(Engine.Events.LAYER_ERROR, spy);
engineMock._cartoLayerGroup.trigger('error:layer', 'an error');
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({
origin: 'windshaft',
_error: 'an error'
}));
});
});
describe('.getApiKey', function () {
it('should return the internal API key', function () {
var apiKey = 'qwud2iu2';
var anotherEngine = createEngine({
apiKey: apiKey
});
var returnedKey = anotherEngine.getApiKey();
expect(returnedKey).toBe(apiKey);
});
});
describe('.getAuthToken', function () {
it('should return the internal auth token', function () {
var authToken = ['covfefe', 'location'];
var anotherEngine = createEngine({
apiKey: null,
authToken: authToken
});
var returnedAuthToken = anotherEngine.getAuthToken();
expect(returnedAuthToken).toBe(authToken);
});
});
});

31
test/spec/fixtures/engine.fixture.js vendored Normal file
View File

@@ -0,0 +1,31 @@
var Engine = require('../../../src/engine');
function createEngine (opts) {
opts = opts || {};
var apiKey = opts.hasOwnProperty('apiKey')
? opts.apiKey
: 'API_KEY';
var authToken = opts.authToken || ['fabada', 'coffee'];
var username = opts.username || 'wadus';
var serverUrl = opts.serverUrl || 'http://example.com';
var spyReload = opts.hasOwnProperty('spyReload')
? opts.spyReload
: true;
var client = opts.client || 'fake-client';
var engine = new Engine({
apiKey: apiKey,
authToken: authToken,
username: username,
serverUrl: serverUrl,
client: client
});
if (spyReload) {
spyOn(engine, 'reload');
}
return engine;
}
module.exports = createEngine;

View File

@@ -0,0 +1,414 @@
var $ = require('jquery');
var Backbone = require('backbone');
var Layers = require('../../../src/geo/map/layers');
var CartoDBLayer = require('../../../src/geo/map/cartodb-layer');
var TileLayer = require('../../../src/geo/map/tile-layer');
var TorqueLayer = require('../../../src/geo/map/torque-layer');
var GMapsBaseLayer = require('../../../src/geo/map/gmaps-base-layer');
var CartoDBLayerGroup = require('../../../src/geo/cartodb-layer-group');
var createEngine = require('../fixtures/engine.fixture.js');
describe('geo/cartodb-layer-group', function () {
var engineMock;
beforeEach(function () {
this.layersCollection = new Layers();
engineMock = createEngine();
this.cartoDBLayerGroup = new CartoDBLayerGroup({}, {
layersCollection: this.layersCollection
});
});
describe('.fetchAttributes', function () {
beforeEach(function () {
this.cartoDBLayerGroup.set('urls', {
attributes: [
'http://carto.com/1/attributes',
'http://carto.com/2/attributes'
]
});
spyOn($, 'ajax').and.callFake(function (options) {
options.success('attributes!');
});
});
it('should trigger a request to the right URL', function () {
var callback = jasmine.createSpy('callback');
this.cartoDBLayerGroup.fetchAttributes(0, 1000, callback);
expect(callback).toHaveBeenCalledWith('attributes!');
expect($.ajax.calls.mostRecent().args[0].url).toEqual('http://carto.com/1/attributes/1000');
});
it('should invoke the callback with null when the ajax request fails', function () {
var callback = jasmine.createSpy('callback');
$.ajax.and.callFake(function (options) {
options.error('error!');
});
this.cartoDBLayerGroup.fetchAttributes(0, 1000, callback);
expect(callback).toHaveBeenCalledWith(null);
});
it('should append the api_key to urls', function () {
this.cartoDBLayerGroup.set('apiKey', 'THE_API_KEY');
var callback = jasmine.createSpy('callback');
this.cartoDBLayerGroup.fetchAttributes(1, 1000, callback);
expect(callback).toHaveBeenCalledWith('attributes!');
expect($.ajax.calls.mostRecent().args[0].url).toEqual('http://carto.com/2/attributes/1000?api_key=THE_API_KEY');
});
it('should append the auth_token to urls', function () {
this.cartoDBLayerGroup.set('authToken', 'AUTH_TOKEN');
var callback = jasmine.createSpy('callback');
this.cartoDBLayerGroup.fetchAttributes(1, 1000, callback);
expect(callback).toHaveBeenCalledWith('attributes!');
expect($.ajax.calls.mostRecent().args[0].url).toEqual('http://carto.com/2/attributes/1000?auth_token=AUTH_TOKEN');
});
});
describe('.getTileURLTemplate', function () {
beforeEach(function () {
this.cartoDBLayerGroup = new CartoDBLayerGroup({
indexOfLayersInWindshaft: [1, 2]
}, {
layersCollection: this.layersCollection
});
var otherLayer = new Backbone.Model();
this.cartoDBLayer1 = new CartoDBLayer({}, { engine: engineMock });
this.cartoDBLayer2 = new CartoDBLayer({}, { engine: engineMock });
this.layersCollection.reset([
otherLayer,
this.cartoDBLayer1,
this.cartoDBLayer2
]);
});
it('should return an empty array there are NO urls yet', function () {
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('');
});
it('should return an empty array if there are NO tile URL templates', function () {
this.cartoDBLayerGroup.set('urls', {
tiles: ''
});
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('');
});
describe('png', function () {
beforeEach(function () {
this.cartoDBLayerGroup.set('urls', {
tiles: 'http://carto.com/{layerIndexes}/{z}/{x}/{y}.{format}'
});
});
it('should return an array with the tile URL templates', function () {
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('http://carto.com/1,2/{z}/{x}/{y}.png');
});
it('should not include index of layers that are hidden', function () {
this.cartoDBLayer1.set('visible', false);
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('http://carto.com/2/{z}/{x}/{y}.png');
});
it('should return an empty array if all layers are hidden', function () {
this.cartoDBLayer1.set('visible', false);
this.cartoDBLayer2.set('visible', false);
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('');
});
it('should return an empty string if there are no layer indexes', function () {
spyOn(this.cartoDBLayerGroup, '_getIndexesOfVisibleMapnikLayers').and.returnValue('');
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('');
});
it('should append the api_key to urls', function () {
this.cartoDBLayerGroup.set({
apiKey: 'THE_API_KEY'
});
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('http://carto.com/1,2/{z}/{x}/{y}.png?api_key=THE_API_KEY');
});
it('should append the auth_token to urls', function () {
this.cartoDBLayerGroup.set({
authToken: 'AUTH_TOKEN'
});
expect(this.cartoDBLayerGroup.getTileURLTemplate()).toEqual('http://carto.com/1,2/{z}/{x}/{y}.png?auth_token=AUTH_TOKEN');
});
});
describe('mvt', function () {
beforeEach(function () {
this.cartoDBLayerGroup.set('urls', {
tiles: 'http://carto.com/{layerIndexes}/{z}/{x}/{y}.{format}'
});
});
it('should return a single tile URL template', function () {
expect(this.cartoDBLayerGroup.getTileURLTemplate('mvt')).toEqual('http://carto.com/mapnik/{z}/{x}/{y}.mvt');
});
it('should return a single tile URL template if all layers are hidden', function () {
this.cartoDBLayer1.set('visible', false);
this.cartoDBLayer2.set('visible', false);
expect(this.cartoDBLayerGroup.getTileURLTemplate('mvt')).toEqual('http://carto.com/mapnik/{z}/{x}/{y}.mvt');
});
it('should append the api_key to urls', function () {
this.cartoDBLayerGroup.set({
apiKey: 'THE_API_KEY'
});
expect(this.cartoDBLayerGroup.getTileURLTemplate('mvt')).toEqual('http://carto.com/mapnik/{z}/{x}/{y}.mvt?api_key=THE_API_KEY');
});
it('should append the auth_token to urls', function () {
this.cartoDBLayerGroup.set({
authToken: 'AUTH_TOKEN'
});
expect(this.cartoDBLayerGroup.getTileURLTemplate('mvt')).toEqual('http://carto.com/mapnik/{z}/{x}/{y}.mvt?auth_token=AUTH_TOKEN');
});
});
});
describe('.getTileURLTemplatesWithSubdomains', function () {
beforeEach(function () {
this.cartoDBLayerGroup = new CartoDBLayerGroup({
indexOfLayersInWindshaft: [1, 2],
urls: {
tiles: 'http://carto.com/{layerIndexes}/{z}/{x}/{y}.{format}'
}
}, {
layersCollection: this.layersCollection
});
var otherLayer = new Backbone.Model();
this.cartoDBLayer1 = new CartoDBLayer({}, { engine: engineMock });
this.cartoDBLayer2 = new CartoDBLayer({}, { engine: engineMock });
this.layersCollection.reset([
otherLayer,
this.cartoDBLayer1,
this.cartoDBLayer2
]);
});
it('should return one URL when there are NO subdomains', function () {
expect(this.cartoDBLayerGroup.getTileURLTemplatesWithSubdomains()).toEqual([ 'http://carto.com/1,2/{z}/{x}/{y}.png' ]);
});
it('should include URLs for different subdomains', function () {
this.cartoDBLayerGroup.set('urls', {
tiles: 'http://{s}.carto.com/{layerIndexes}/{z}/{x}/{y}.{format}',
subdomains: [ '0', '1', '2', '3' ]
});
expect(this.cartoDBLayerGroup.getTileURLTemplatesWithSubdomains()).toEqual([
'http://0.carto.com/1,2/{z}/{x}/{y}.png',
'http://1.carto.com/1,2/{z}/{x}/{y}.png',
'http://2.carto.com/1,2/{z}/{x}/{y}.png',
'http://3.carto.com/1,2/{z}/{x}/{y}.png'
]);
});
});
describe('.hasTileURLTemplates', function () {
beforeEach(function () {
this.cartoDBLayer1 = new CartoDBLayer({}, { engine: engineMock });
this.cartoDBLayer2 = new CartoDBLayer({}, { engine: engineMock });
this.layersCollection.reset([
this.cartoDBLayer1,
this.cartoDBLayer2
]);
this.cartoDBLayerGroup = new CartoDBLayerGroup({
indexOfLayersInWindshaft: [1, 2]
}, {
layersCollection: this.layersCollection
});
});
it('should return false if there are NO urls yet', function () {
expect(this.cartoDBLayerGroup.hasTileURLTemplates()).toBe(false);
});
it('should return false if there are NO tile URL templates', function () {
this.cartoDBLayerGroup.set('urls', {
tiles: ''
});
expect(this.cartoDBLayerGroup.hasTileURLTemplates()).toBe(false);
});
it('should return true if there are tile URL templates', function () {
this.cartoDBLayerGroup.set('urls', {
tiles: 'url1'
});
expect(this.cartoDBLayerGroup.hasTileURLTemplates()).toBe(true);
});
});
describe('.getGridURLTemplatesWithSubdomains', function () {
beforeEach(function () {
this.cartoDBLayerGroup = new CartoDBLayerGroup({}, {
layersCollection: this.layersCollection
});
});
it('should return an empty array there are NO urls yet', function () {
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(0)).toEqual([]);
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(1)).toEqual([]);
});
describe("when there're grid URLs", function () {
beforeEach(function () {
this.cartoDBLayerGroup.set('urls', {
grids: [
[ 'url1' ],
[ 'url2' ]
]
});
});
it('should return an array with the grid URL templates', function () {
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(0)).toEqual([ 'url1' ]);
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(1)).toEqual([ 'url2' ]);
});
it('should append the api_key to urls', function () {
this.cartoDBLayerGroup.set({
apiKey: 'THE_API_KEY'
});
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(0)).toEqual([ 'url1?api_key=THE_API_KEY' ]);
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(1)).toEqual([ 'url2?api_key=THE_API_KEY' ]);
});
it('should append the auth_token to urls', function () {
this.cartoDBLayerGroup.set({
authToken: 'AUTH_TOKEN'
});
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(0)).toEqual([ 'url1?auth_token=AUTH_TOKEN' ]);
expect(this.cartoDBLayerGroup.getGridURLTemplatesWithSubdomains(1)).toEqual([ 'url2?auth_token=AUTH_TOKEN' ]);
});
});
});
describe('.getAttributesBaseURL', function () {
});
describe('.getStaticImageURLTemplate', function () {
beforeEach(function () {
this.baseLayer = new TileLayer({}, { engine: engineMock });
this.cartoDBLayer1 = new CartoDBLayer({}, { engine: engineMock });
this.cartoDBLayer2 = new CartoDBLayer({}, { engine: engineMock });
this.torqueLayer = new TorqueLayer({}, { engine: engineMock });
this.labelsLayer = new TileLayer({}, { engine: engineMock });
this.layersCollection.reset([
this.baseLayer,
this.cartoDBLayer1,
this.cartoDBLayer2,
this.torqueLayer,
this.labelsLayer
]);
this.cartoDBLayerGroup.set('urls', {
image: 'http://carto.com/image'
});
});
it('should include indexes of visible layers', function () {
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://carto.com/image?layer=0,1,2,3,4');
});
it('should not include hidden layers', function () {
this.cartoDBLayer1.hide();
this.torqueLayer.hide();
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://carto.com/image?layer=0,2,4');
});
it('should ignore Google Maps base layers (Maps API is not aware of them)', function () {
this.baseLayer = new GMapsBaseLayer(null);
this.cartoDBLayer1 = new CartoDBLayer({}, { engine: engineMock });
this.cartoDBLayer2 = new CartoDBLayer({}, { engine: engineMock });
this.layersCollection.reset([
this.baseLayer,
this.cartoDBLayer1,
this.cartoDBLayer2
]);
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://carto.com/image?layer=0,1');
});
it('should include api_key param', function () {
this.cartoDBLayerGroup.set({
apiKey: 'THE_API_KEY'
});
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://carto.com/image?layer=0,1,2,3,4&api_key=THE_API_KEY');
});
it('should include auth_token param', function () {
this.cartoDBLayerGroup.set({
authToken: 'AUTH_TOKEN'
});
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://carto.com/image?layer=0,1,2,3,4&auth_token=AUTH_TOKEN');
});
it('should include subdomains', function () {
this.cartoDBLayerGroup.set('urls', {
image: 'http://{s}.carto.com/image',
subdomains: [ '0', '1' ]
});
expect(this.cartoDBLayerGroup.getStaticImageURLTemplate()).toEqual('http://0.carto.com/image?layer=0,1,2,3,4');
});
});
describe('.addError', function () {
it('should throw an error if the error does not have a type', function () {
expect(this.cartoDBLayerGroup.addError).toThrow();
});
it('should trigger an error with the specified type', function () {
var called = '';
this.cartoDBLayerGroup.on('error:limit', function () {
called = 'limit';
});
this.cartoDBLayerGroup.on('error:tile', function () {
called = 'tile';
});
this.cartoDBLayerGroup.addError({ type: 'limit' });
expect(called).toEqual('limit');
this.cartoDBLayerGroup.addError({ type: 'tile' });
expect(called).toEqual('tile');
});
});
});

View File

@@ -0,0 +1,261 @@
{
"type": "FeatureCollection",
"query": [
"los",
"angeles"
],
"features": [
{
"id": "place.9962989141465270",
"type": "Feature",
"place_type": [
"place"
],
"relevance": 0.99,
"properties": {
"wikidata": "Q65"
},
"text": "Los Angeles",
"place_name": "Los Angeles, California, United States",
"bbox": [
-118.529221009603,
33.901599990108,
-118.121099990025,
34.1612200099034
],
"center": [
-118.2439,
34.0544
],
"geometry": {
"type": "Point",
"coordinates": [
-118.2439,
34.0544
]
},
"context": [
{
"id": "region.3591",
"short_code": "US-CA",
"wikidata": "Q99",
"text": "California"
},
{
"id": "country.3145",
"short_code": "us",
"wikidata": "Q30",
"text": "United States"
}
]
},
{
"id": "place.10952642230180310",
"type": "Feature",
"place_type": [
"place"
],
"relevance": 0.99,
"properties": {
"wikidata": "Q16910"
},
"text": "Los Ángeles",
"place_name": "Los Ángeles, Bío Bío, Chile",
"bbox": [
-72.68248,
-37.663862,
-72.041277,
-37.178368
],
"center": [
-72.35,
-37.46667
],
"geometry": {
"type": "Point",
"coordinates": [
-72.35,
-37.46667
]
},
"context": [
{
"id": "region.3552",
"short_code": "CL-BI",
"wikidata": "Q2170",
"text": "Bío Bío"
},
{
"id": "country.344",
"short_code": "cl",
"wikidata": "Q298",
"text": "Chile"
}
]
},
{
"id": "poi.1611278850983920",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.99,
"properties": {
"address": "1 World Way",
"category": "international airport, airport",
"tel": "(310) 646-5252",
"wikidata": "Q8731",
"landmark": true,
"maki": "airport"
},
"text": "Los Angeles International Airport",
"place_name": "Los Angeles International Airport, 1 World Way, Los Angeles, California 90045, United States",
"center": [
-118.408056,
33.9425
],
"geometry": {
"coordinates": [
-118.408056,
33.9425
],
"type": "Point"
},
"context": [
{
"id": "neighborhood.33720",
"text": "Westchester"
},
{
"id": "postcode.8081932850252730",
"text": "90045"
},
{
"id": "place.9962989141465270",
"wikidata": "Q65",
"text": "Los Angeles"
},
{
"id": "region.3591",
"short_code": "US-CA",
"wikidata": "Q99",
"text": "California"
},
{
"id": "country.3145",
"short_code": "us",
"wikidata": "Q30",
"text": "United States"
}
]
},
{
"id": "neighborhood.2104633",
"type": "Feature",
"place_type": [
"neighborhood"
],
"relevance": 0.99,
"properties": {},
"text": "Los Angeles Heights - Keystone",
"place_name": "Los Angeles Heights - Keystone, San Antonio, Texas 78201, United States",
"bbox": [
-98.534942,
29.453364,
-98.514652,
29.485214
],
"center": [
-98.52,
29.47
],
"geometry": {
"type": "Point",
"coordinates": [
-98.52,
29.47
]
},
"context": [
{
"id": "postcode.7069290925572850",
"text": "78201"
},
{
"id": "place.7705127234253710",
"wikidata": "Q975",
"text": "San Antonio"
},
{
"id": "region.3818",
"short_code": "US-TX",
"wikidata": "Q1439",
"text": "Texas"
},
{
"id": "country.3145",
"short_code": "us",
"wikidata": "Q30",
"text": "United States"
}
]
},
{
"id": "poi.17555628334440500",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.99,
"properties": {
"address": "3939 S Figueroa St",
"category": "stadium, arena",
"tel": "(213) 747-7111",
"wikidata": "Q849784",
"landmark": true,
"maki": "baseball"
},
"text": "Los Angeles Memorial Coliseum",
"place_name": "Los Angeles Memorial Coliseum, 3939 S Figueroa St, Los Angeles, California 90037, United States",
"center": [
-118.287778,
34.014167
],
"geometry": {
"coordinates": [
-118.287778,
34.014167
],
"type": "Point"
},
"context": [
{
"id": "neighborhood.293901",
"text": "South Los Angeles"
},
{
"id": "postcode.15612390715732530",
"text": "90037"
},
{
"id": "place.9962989141465270",
"wikidata": "Q65",
"text": "Los Angeles"
},
{
"id": "region.3591",
"short_code": "US-CA",
"wikidata": "Q99",
"text": "California"
},
{
"id": "country.3145",
"short_code": "us",
"wikidata": "Q30",
"text": "United States"
}
]
}
],
"attribution": "NOTICE: © 2017 Mapbox and its suppliers. All rights reserved. Use of this data is subject to the Mapbox Terms of Service (https://www.mapbox.com/about/maps/). This response and the information it contains may not be retained."
}

View File

@@ -0,0 +1,268 @@
{
"type": "FeatureCollection",
"query": [
"plaza",
"de",
"barcelos"
],
"features": [
{
"id": "poi.10895640694945210",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.6666666666666666,
"properties": {
"landmark": true,
"tel": "07524 914990",
"category": "cafe, coffee, restaurant, tea, tea house",
"address": "Ravensburger Straße 8"
},
"text": "Plaza",
"place_name": "Plaza, Ravensburger Straße 8, Bad Waldsee, Baden-Württemberg 88339, Germany",
"center": [
9.754478,
47.920347
],
"geometry": {
"type": "Point",
"coordinates": [
9.754478,
47.920347
]
},
"context": [
{
"id": "postcode.11130053318616830",
"text": "88339"
},
{
"id": "place.11130053318505290",
"wikidata": "Q515423",
"text": "Bad Waldsee"
},
{
"id": "region.3495",
"short_code": "DE-BW",
"wikidata": "Q985",
"text": "Baden-Württemberg"
},
{
"id": "country.3135",
"short_code": "de",
"wikidata": "Q183",
"text": "Germany"
}
]
},
{
"id": "poi.13433334923344580",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.6666666666666666,
"properties": {
"landmark": true,
"tel": "06403 74200",
"category": "rail station, train station",
"address": "Alte Heerstraße 27",
"maki": "rail"
},
"text": "Platz",
"place_name": "Platz, Alte Heerstraße 27, Bodolz, Bayern 88131, Germany",
"center": [
9.667166,
47.567462
],
"geometry": {
"type": "Point",
"coordinates": [
9.667166,
47.567462
]
},
"context": [
{
"id": "locality.13257312967514310",
"wikidata": "Q11696975",
"text": "Enzisweiler"
},
{
"id": "postcode.7704245689115710",
"text": "88131"
},
{
"id": "place.11779142489218390",
"wikidata": "Q552427",
"text": "Bodolz"
},
{
"id": "region.3186",
"short_code": "DE-BY",
"wikidata": "Q980",
"text": "Bayern"
},
{
"id": "country.3135",
"short_code": "de",
"wikidata": "Q183",
"text": "Germany"
}
]
},
{
"id": "poi.11479188654945210",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.6666666666666666,
"properties": {
"landmark": true,
"tel": "0921 5160544",
"category": "restaurant",
"address": "Kirchgasse 24",
"maki": "restaurant"
},
"text": "Plaza",
"place_name": "Plaza, Kirchgasse 24, Bayreuth, Bayern 95444, Germany",
"center": [
11.57395,
49.94327
],
"geometry": {
"type": "Point",
"coordinates": [
11.57395,
49.94327
]
},
"context": [
{
"id": "postcode.14977288459298330",
"text": "95444"
},
{
"id": "place.14796666081196300",
"wikidata": "Q3923",
"text": "Bayreuth"
},
{
"id": "region.3186",
"short_code": "DE-BY",
"wikidata": "Q980",
"text": "Bayern"
},
{
"id": "country.3135",
"short_code": "de",
"wikidata": "Q183",
"text": "Germany"
}
]
},
{
"id": "poi.9477875054945210",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.3333333333333333,
"properties": {
"landmark": true,
"tel": "0226 344 306",
"category": "restaurant",
"maki": "restaurant"
},
"text": "Plaza",
"place_name": "Plaza, Beringe, Limburg 5986, Netherlands",
"center": [
5.947389,
51.337527
],
"geometry": {
"type": "Point",
"coordinates": [
5.947389,
51.337527
]
},
"context": [
{
"id": "postcode.6633279068411020",
"text": "5986"
},
{
"id": "place.6017505065203911",
"wikidata": "Q1878867",
"text": "Beringe"
},
{
"id": "region.213188",
"short_code": "NL-LI",
"wikidata": "Q1093",
"text": "Limburg"
},
{
"id": "country.3101",
"short_code": "nl",
"wikidata": "Q55",
"text": "Netherlands"
}
]
},
{
"id": "poi.6170918672945210",
"type": "Feature",
"place_type": [
"poi"
],
"relevance": 0.3333333333333333,
"properties": {
"landmark": true,
"tel": "0382 559413",
"category": "hotel, lodging, motel",
"address": "Via Togliatti 39"
},
"text": "Plaza",
"place_name": "Plaza, Via Togliatti 39, San Martino Siccomario, Pavia 27028, Italy",
"center": [
9.130854,
45.159336
],
"geometry": {
"type": "Point",
"coordinates": [
9.130854,
45.159336
]
},
"context": [
{
"id": "postcode.12821918527764180",
"text": "27028"
},
{
"id": "place.12821918528623280",
"wikidata": "Q41026",
"text": "San Martino Siccomario"
},
{
"id": "region.3790",
"short_code": "IT-PV",
"wikidata": "Q16231",
"text": "Pavia"
},
{
"id": "country.333",
"short_code": "it",
"wikidata": "Q38",
"text": "Italy"
}
]
}
],
"attribution": "NOTICE: © 2017 Mapbox and its suppliers. All rights reserved. Use of this data is subject to the Mapbox Terms of Service (https://www.mapbox.com/about/maps/). This response and the information it contains may not be retained."
}

View File

@@ -0,0 +1,67 @@
var mapboxGeocoder = require('../../../../src/geo/geocoder/mapbox-geocoder');
var TOKEN = 'fake_token';
describe('mapbox-geocoder', function () {
describe('.geocode', function () {
it('should build the right fetch url (add address and access_token)', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./mapbox-geocoder-response-0'); } }));
mapboxGeocoder.geocode('fake_address', TOKEN).then(function (result) {
var expectedFetchUrl = 'https://api.mapbox.com/geocoding/v5/mapbox.places-permanent/fake_address.json?access_token=fake_token';
expect(window.fetch).toHaveBeenCalledWith(expectedFetchUrl);
done();
});
});
it('should geocode a city location', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./mapbox-geocoder-response-0'); } }));
mapboxGeocoder.geocode('Vigo', TOKEN)
.then(function (results) {
expect(results).toBeDefined();
done();
});
});
it('should return a well formated response [example 0]', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./mapbox-geocoder-response-0'); } }));
mapboxGeocoder.geocode('Vigo', TOKEN)
.then(function (result) {
result = result[0];
expect(result.center).toBeDefined();
expect(result.center[0]).toEqual(34.0544);
expect(result.center[1]).toEqual(-118.2439);
// Bbox
expect(result.boundingbox.south).toEqual(-118.529221009603);
expect(result.boundingbox.west).toEqual(33.901599990108);
expect(result.boundingbox.north).toEqual(-118.121099990025);
expect(result.boundingbox.east).toEqual(34.1612200099034);
// Type
expect(result.type).toEqual('venue');
done();
}).catch(console.error);
});
it('should return a well formated response when the response has no bbox [example 1]', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./mapbox-geocoder-response-1'); } }));
mapboxGeocoder.geocode('Plaza de Barcelos', TOKEN)
.then(function (result) {
result = result[0];
expect(result.center).toBeDefined();
expect(result.center[0]).toEqual(47.920347);
expect(result.center[1]).toEqual(9.754478);
// Bbox
expect(result.bbox).toBeUndefined();
// Type
expect(result.type).toEqual('venue');
done();
}).catch(console.error);
});
it('should return an empty array when the response is empty', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return { features: [] }; } }));
mapboxGeocoder.geocode('Vigo', TOKEN)
.then(function (result) {
expect(result).toEqual([]);
done();
});
});
});
});

View File

@@ -0,0 +1,445 @@
{
"summary": {
"query": "santander",
"queryType": "NON_NEAR",
"queryTime": 39,
"numResults": 10,
"offset": 0,
"totalResults": 24811,
"fuzzyLevel": 1
},
"results": [
{
"type": "Geography",
"id": "ES/GEO/p0/7661",
"score": 2.418,
"entityType": "Municipality",
"address": {
"municipality": "Santander",
"countrySecondarySubdivision": "Cantabria",
"countrySubdivision": "Cantabria",
"countryCode": "ES",
"country": "Spain",
"countryCodeISO3": "ESP",
"freeformAddress": "Santander"
},
"position": {
"lat": 43.46141,
"lon": -3.8093
},
"viewport": {
"topLeftPoint": {
"lat": 43.49482,
"lon": -3.88905
},
"btmRightPoint": {
"lat": 43.43446,
"lon": -3.76325
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 43.49482,
"lon": -3.88905
},
"btmRightPoint": {
"lat": 43.43446,
"lon": -3.76325
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-00007d2e35e3"
}
}
},
{
"type": "Geography",
"id": "PH/GEO/p0/1587",
"score": 2.382,
"entityType": "Municipality",
"address": {
"municipality": "Santander",
"countrySecondarySubdivision": "Cebu",
"countrySubdivision": "Central Visayas",
"countryCode": "PH",
"country": "Philippines",
"countryCodeISO3": "PHL",
"freeformAddress": "Santander, Central Visayas"
},
"position": {
"lat": 9.42242,
"lon": 123.30378
},
"viewport": {
"topLeftPoint": {
"lat": 9.47779,
"lon": 123.29687
},
"btmRightPoint": {
"lat": 9.41298,
"lon": 123.3632
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 9.47779,
"lon": 123.29687
},
"btmRightPoint": {
"lat": 9.41298,
"lon": 123.3632
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-000077378fa7"
}
}
},
{
"type": "Geography",
"id": "MX/GEO/p0/340",
"score": 2.362,
"entityType": "Municipality",
"address": {
"municipality": "Jiménez",
"countrySubdivision": "Tamaulipas",
"countryCode": "MX",
"country": "Mexico",
"countryCodeISO3": "MEX",
"freeformAddress": "Jiménez"
},
"position": {
"lat": 24.21378,
"lon": -98.48187
},
"viewport": {
"topLeftPoint": {
"lat": 24.47171,
"lon": -98.72639
},
"btmRightPoint": {
"lat": 23.93939,
"lon": -98.27799
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 24.47171,
"lon": -98.72639
},
"btmRightPoint": {
"lat": 23.93939,
"lon": -98.27799
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-00007737d289"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/252",
"score": 2.36,
"entityType": "Municipality",
"address": {
"municipality": "Puerto Santander",
"countrySubdivision": "Norte de Santander",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Puerto Santander, Norte de Santander"
},
"position": {
"lat": 8.36227,
"lon": -72.40896
},
"viewport": {
"topLeftPoint": {
"lat": 8.38354,
"lon": -72.4475
},
"btmRightPoint": {
"lat": 8.26839,
"lon": -72.38042
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 8.38354,
"lon": -72.4475
},
"btmRightPoint": {
"lat": 8.26839,
"lon": -72.38042
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-00007d30e01c"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/888",
"score": 2.358,
"entityType": "Municipality",
"address": {
"municipality": "Santander",
"countrySubdivision": "Amazonas",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Santander, Amazonas"
},
"position": {
"lat": -0.62217,
"lon": -72.38281
},
"viewport": {
"topLeftPoint": {
"lat": -0.26464,
"lon": -72.99754
},
"btmRightPoint": {
"lat": -1.89121,
"lon": -70.72647
}
},
"boundingBox": {
"topLeftPoint": {
"lat": -0.26464,
"lon": -72.99754
},
"btmRightPoint": {
"lat": -1.89121,
"lon": -70.72647
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-00007d30ef7b"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/1010",
"score": 2.341,
"entityType": "Municipality",
"address": {
"municipality": "Santander de Quilichao",
"countrySubdivision": "Cauca",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Santander de Quilichao, Cauca"
},
"position": {
"lat": 3.00879,
"lon": -76.4859
},
"viewport": {
"topLeftPoint": {
"lat": 3.1493,
"lon": -76.61535
},
"btmRightPoint": {
"lat": 2.8461,
"lon": -76.37358
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 3.1493,
"lon": -76.61535
},
"btmRightPoint": {
"lat": 2.8461,
"lon": -76.37358
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-00007d30eff7"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/6",
"score": 2.255,
"entityType": "CountrySubdivision",
"address": {
"countrySubdivision": "Santander",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Santander"
},
"position": {
"lat": 6.92507,
"lon": -73.50153
},
"viewport": {
"topLeftPoint": {
"lat": 8.14342,
"lon": -74.52666
},
"btmRightPoint": {
"lat": 5.70671,
"lon": -72.4764
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 8.14342,
"lon": -74.52666
},
"btmRightPoint": {
"lat": 5.70671,
"lon": -72.4764
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-000077374466"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/5",
"score": 2.211,
"entityType": "CountrySubdivision",
"address": {
"countrySubdivision": "Norte de Santander",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Norte de Santander"
},
"position": {
"lat": 8.08458,
"lon": -72.84278
},
"viewport": {
"topLeftPoint": {
"lat": 9.2961,
"lon": -73.63743
},
"btmRightPoint": {
"lat": 6.87306,
"lon": -72.04813
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 9.2961,
"lon": -73.63743
},
"btmRightPoint": {
"lat": 6.87306,
"lon": -72.04813
}
},
"dataSources": {
"geometry": {
"id": "00005858-5800-1200-0000-000077374465"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/1668",
"score": 2.177,
"entityType": "MunicipalitySubdivision",
"address": {
"municipalitySubdivision": "Santander",
"municipality": "Cali",
"countrySubdivision": "Valle del Cauca",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Cali Santander, Valle del Cauca"
},
"position": {
"lat": 3.46266,
"lon": -76.51554
},
"viewport": {
"topLeftPoint": {
"lat": 3.4665,
"lon": -76.52026
},
"btmRightPoint": {
"lat": 3.45905,
"lon": -76.51054
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 3.4665,
"lon": -76.52026
},
"btmRightPoint": {
"lat": 3.45905,
"lon": -76.51054
}
},
"dataSources": {
"geometry": {
"id": "0000434f-3300-3c00-0000-000065ec89e3"
}
}
},
{
"type": "Geography",
"id": "CO/GEO/p0/2477",
"score": 2.177,
"entityType": "MunicipalitySubdivision",
"address": {
"municipalitySubdivision": "Santander",
"municipality": "Medellín",
"countrySubdivision": "Antioquia",
"countryCode": "CO",
"country": "Colombia",
"countryCodeISO3": "COL",
"freeformAddress": "Medellín Santander, Antioquia"
},
"position": {
"lat": 6.30768,
"lon": -75.57501
},
"viewport": {
"topLeftPoint": {
"lat": 6.31099,
"lon": -75.57874
},
"btmRightPoint": {
"lat": 6.30185,
"lon": -75.57128
}
},
"boundingBox": {
"topLeftPoint": {
"lat": 6.31099,
"lon": -75.57874
},
"btmRightPoint": {
"lat": 6.30185,
"lon": -75.57128
}
},
"dataSources": {
"geometry": {
"id": "0000434f-3200-3c00-0000-000065a03d92"
}
}
}
]
}

View File

@@ -0,0 +1,51 @@
var tomtomGeocoder = require('../../../../src/geo/geocoder/tomtom-geocoder');
var API_KEY = 'fake_api_key';
describe('tomtom-geocoder', function () {
describe('.geocode', function () {
it('should build the right fetch url (add address and apiKey)', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return { results: [] }; } }));
tomtomGeocoder.geocode('fake_address', API_KEY).then(function (result) {
var expectedFetchUrl = 'https://api.tomtom.com/search/2/search/fake_address.json?key=fake_api_key';
expect(window.fetch).toHaveBeenCalledWith(expectedFetchUrl);
done();
});
});
it('should geocode a city location', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./tomtom-geocoder-response-0'); } }));
tomtomGeocoder.geocode('Santander', API_KEY)
.then(function (results) {
expect(results).toBeDefined();
done();
});
});
it('should return a well formated response [example 0]', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return require('./tomtom-geocoder-response-0'); } }));
tomtomGeocoder.geocode('Santander', API_KEY)
.then(function (results) {
let bestCandidate = results[0];
expect(bestCandidate.center).toBeDefined();
expect(bestCandidate.center[0]).toEqual(43.46141); // lat
expect(bestCandidate.center[1]).toEqual(-3.8093); // lon
// Bbox
expect(bestCandidate.boundingbox.south).toEqual(43.43446);
expect(bestCandidate.boundingbox.west).toEqual(-3.88905);
expect(bestCandidate.boundingbox.north).toEqual(43.49482);
expect(bestCandidate.boundingbox.east).toEqual(-3.76325);
// Type
expect(bestCandidate.type).toEqual('localadmin');
done();
}).catch(console.error);
});
it('should return an empty array when the response is empty', function (done) {
spyOn(window, 'fetch').and.returnValue(Promise.resolve({ json: function () { return { results: [] }; } }));
tomtomGeocoder.geocode('ABCDEFGHIJ', API_KEY)
.then(function (result) {
expect(result).toEqual([]);
done();
});
});
});
});

View File

@@ -0,0 +1,148 @@
var _ = require('underscore');
var GeometryFactory = require('../../../../src/geo/geometry-models/geometry-factory');
var Point = require('../../../../src/geo/geometry-models/point');
var Polyline = require('../../../../src/geo/geometry-models/polyline');
var Polygon = require('../../../../src/geo/geometry-models/polygon');
var MultiPoint = require('../../../../src/geo/geometry-models/multi-point');
var MultiPolygon = require('../../../../src/geo/geometry-models/multi-polygon');
var MultiPolyline = require('../../../../src/geo/geometry-models/multi-polyline');
var geometryAsFeature = function (geometry) {
return {
'type': 'Feature',
'properties': {},
'geometry': geometry
};
};
describe('src/geo/geometry-models/geometry-factory', function () {
describe('.createGeometryFromGeoJSON', function () {
var pointGeometry = {
'type': 'Point',
'coordinates': [
-3.779296875,
40.245991504199026
]
};
_.each([ pointGeometry, geometryAsFeature(pointGeometry) ], function (geoJSON) {
it('should create a point', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(geoJSON);
expect(geometry instanceof Point).toBeTruthy();
expect(geometry.getCoordinates()).toEqual([ 40.245991504199026, -3.779296875 ]);
});
});
var polylineGeometry = {
'type': 'LineString',
'coordinates': [
[
-2.021484375,
43.51668853502906
],
[
3.6035156249999996,
42.293564192170095
]
]
};
_.each([ polylineGeometry, geometryAsFeature(polylineGeometry) ], function (geoJSON) {
it('should create a polyline', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(geoJSON);
expect(geometry instanceof Polyline).toBeTruthy();
expect(geometry.getCoordinates()).toEqual([
[ 43.51668853502906, -2.021484375 ],
[ 42.293564192170095, 3.6035156249999996 ]
]);
});
});
var polygonGeometry = {
'type': 'Polygon',
'coordinates': [
[
[
-8.96484375,
41.918628865183045
],
[
-7.84423828125,
43.74728909225908
],
[
-1.69189453125,
43.34914966389313
],
[
-8.96484375,
41.918628865183045
]
]
]
};
_.each([ polygonGeometry, geometryAsFeature(polygonGeometry) ], function (geoJSON) {
it('should create a polygon', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(geoJSON);
expect(geometry instanceof Polygon).toBeTruthy();
expect(geometry.getCoordinates()).toEqual([
[ 41.918628865183045, -8.96484375 ],
[ 43.74728909225908, -7.84423828125 ],
[ 43.34914966389313, -1.69189453125 ]
]);
});
});
var multiPointGeometry = {
'type': 'MultiPoint',
'coordinates': [ [100.0, 0.0], [101.0, 1.0] ]
};
it('should create a multipoint', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(multiPointGeometry);
expect(geometry instanceof MultiPoint).toBeTruthy();
expect(geometry.geometries.length).toEqual(2);
expect(geometry.geometries.at(0).getCoordinates()).toEqual([ 0, 100 ]);
expect(geometry.geometries.at(1).getCoordinates()).toEqual([ 1, 101 ]);
});
var multiPolygonGeometry = {
'type': 'MultiPolygon',
'coordinates': [
[ [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ] ],
[ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]
]
};
it('should create a multipolygon', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(multiPolygonGeometry);
expect(geometry instanceof MultiPolygon).toBeTruthy();
expect(geometry.geometries.length).toEqual(2);
expect(geometry.geometries.at(0).getCoordinates()).toEqual([ [ 2, 102 ], [ 2, 103 ], [ 3, 103 ], [ 3, 102 ] ]);
expect(geometry.geometries.at(1).getCoordinates()).toEqual([ [ 0, 100 ], [ 0, 101 ], [ 1, 101 ], [ 1, 100 ] ]);
});
var multiPolylineGeometry = {
'type': 'MultiLineString',
'coordinates': [
[ [100.0, 0.0], [101.0, 1.0] ],
[ [102.0, 2.0], [103.0, 3.0] ]
]
};
it('should create a multipolyline', function () {
var geometry = GeometryFactory.createGeometryFromGeoJSON(multiPolylineGeometry);
expect(geometry instanceof MultiPolyline).toBeTruthy();
expect(geometry.geometries.length).toEqual(2);
expect(geometry.geometries.at(0).getCoordinates()).toEqual([ [ 0, 100 ], [ 1, 101 ] ]);
expect(geometry.geometries.at(1).getCoordinates()).toEqual([ [ 2, 102 ], [ 3, 103 ] ]);
});
});
});

View File

@@ -0,0 +1,32 @@
var MultiPoint = require('../../../../src/geo/geometry-models/multi-point');
describe('src/geo/geometry-models/multi-point', function () {
beforeEach(function () {
this.multiPoint = new MultiPoint(null, {
latlngs: [
[0, 1],
[1, 2]
]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.multiPoint.toGeoJSON()).toEqual({
type: 'MultiPoint',
coordinates: [ [ 1, 0 ], [ 2, 1 ] ]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
it('should update the coordinates', function () {
var newAndExpectedGeoJSON = {
type: 'MultiPoint',
coordinates: [ [ 0, 0 ], [ 10, 10 ] ]
};
this.multiPoint.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.multiPoint.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
});
});

View File

@@ -0,0 +1,54 @@
var MultiPolygon = require('../../../../src/geo/geometry-models/multi-polygon');
describe('src/geo/geometry-models/multi-polygon', function () {
beforeEach(function () {
this.multiPolygon = new MultiPolygon(null, {
latlngs: [
[
[0, 1],
[1, 2],
[2, 3],
[3, 4]
],
[
[0, 10],
[10, 20],
[20, 30],
[30, 40]
]
]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.multiPolygon.toGeoJSON()).toEqual({
type: 'MultiPolygon',
coordinates: [
[
[ [ 1, 0 ], [ 2, 1 ], [ 3, 2 ], [ 4, 3 ], [ 1, 0 ] ]
], [
[ [ 10, 0 ], [ 20, 10 ], [ 30, 20 ], [ 40, 30 ], [ 10, 0 ] ]
]
]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
it('should update the coordinates', function () {
var newAndExpectedGeoJSON = {
type: 'MultiPolygon',
coordinates: [
[
[ [ 0, 0 ], [ 2, 1 ], [ 3, 2 ], [ 4, 3 ], [ 0, 0 ] ]
], [
[ [ 100, 0 ], [ 20, 10 ], [ 30, 20 ], [ 40, 30 ], [ 100, 0 ] ]
]
]
};
this.multiPolygon.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.multiPolygon.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
});
});

View File

@@ -0,0 +1,54 @@
var MultiPolyline = require('../../../../src/geo/geometry-models/multi-polyline');
describe('src/geo/geometry-models/multi-polyline', function () {
beforeEach(function () {
this.multiPolyline = new MultiPolyline(null, {
latlngs: [
[
[0, 1],
[1, 2],
[2, 3],
[3, 4]
],
[
[0, 10],
[10, 20],
[20, 30],
[30, 40]
]
]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.multiPolyline.toGeoJSON()).toEqual({
type: 'MultiLineString',
coordinates: [
[
[ 1, 0 ], [ 2, 1 ], [ 3, 2 ], [ 4, 3 ]
], [
[ 10, 0 ], [ 20, 10 ], [ 30, 20 ], [ 40, 30 ]
]
]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
it('should update the coordinates', function () {
var newAndExpectedGeoJSON = {
type: 'MultiLineString',
coordinates: [
[
[ 100, 0 ], [ 2, 1 ], [ 3, 2 ], [ 40, 30 ]
], [
[ 100, 0 ], [ 20, 10 ], [ 30, 20 ], [ 40, 30 ]
]
]
};
this.multiPolyline.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.multiPolyline.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
});
});

View File

@@ -0,0 +1,58 @@
var Point = require('../../../../src/geo/geometry-models/point');
describe('src/geo/geometry-models/point', function () {
beforeEach(function () {
this.point = new Point({
latlng: [100, 200]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.point.toGeoJSON()).toEqual({
type: 'Point',
coordinates: [ 200, 100 ]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
beforeEach(function () {
this.changeCallback = jasmine.createSpy('changeCallback');
this.point.on('change', this.changeCallback);
});
describe('when given a GeoJSON with the same coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = this.point.toGeoJSON();
this.point.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
});
it('should NOT trigger a "change" event', function () {
expect(this.changeCallback).not.toHaveBeenCalled();
});
});
describe('when given a GeoJSON with different coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = {
type: 'Point',
coordinates: [ 0, 300 ]
};
this.point.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.point.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
it('should trigger a "change" event', function () {
expect(this.changeCallback).toHaveBeenCalled();
});
it('should update the coordinates', function () {
expect(this.point.toGeoJSON()).toEqual({
type: 'Point',
coordinates: [ 0, 300 ]
});
});
});
});
});

View File

@@ -0,0 +1,66 @@
var Polygon = require('../../../../src/geo/geometry-models/polygon');
describe('src/geo/geometry-models/polygon', function () {
beforeEach(function () {
this.polygon = new Polygon(null, {
latlngs: [
[-1, 1], [1, 2], [3, 4]
]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.polygon.toGeoJSON()).toEqual({
type: 'Polygon',
coordinates: [
[ [ 1, -1 ], [ 2, 1 ], [ 4, 3 ], [ 1, -1 ] ]
]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
beforeEach(function () {
this.changeCallback = jasmine.createSpy('changeCallback');
this.polygon.on('change', this.changeCallback);
});
describe('when given a GeoJSON with the same coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = this.polygon.toGeoJSON();
this.polygon.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
});
it('should NOT trigger a "change" event', function () {
expect(this.changeCallback).not.toHaveBeenCalled();
});
});
describe('when given a GeoJSON with different coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = {
type: 'Polygon',
coordinates: [
[ [ 0, 0 ], [ 10, 10 ], [ 20, 20 ], [ 0, 0 ] ]
]
};
this.polygon.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.polygon.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
it('should trigger a "change" event', function () {
expect(this.changeCallback).toHaveBeenCalled();
});
it('should update the coordinates', function () {
expect(this.polygon.toGeoJSON()).toEqual({
type: 'Polygon',
coordinates: [
[ [ 0, 0 ], [ 10, 10 ], [ 20, 20 ], [ 0, 0 ] ]
]
});
});
});
});
});

View File

@@ -0,0 +1,64 @@
var Polyline = require('../../../../src/geo/geometry-models/polyline');
describe('src/geo/geometry-models/polyline', function () {
beforeEach(function () {
this.polyline = new Polyline(null, {
latlngs: [
[-1, 1], [1, 2], [3, 4]
]
});
});
describe('.toGeoJSON', function () {
it('should generate the GeoJSON correctly', function () {
expect(this.polyline.toGeoJSON()).toEqual({
type: 'LineString',
coordinates: [
[ 1, -1 ], [ 2, 1 ], [ 4, 3 ]
]
});
});
});
describe('.setCoordinatesFromGeoJSON', function () {
beforeEach(function () {
this.changeCallback = jasmine.createSpy('changeCallback');
this.polyline.on('change', this.changeCallback);
});
describe('when given a GeoJSON with the same coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = this.polyline.toGeoJSON();
this.polyline.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
});
it('should NOT trigger a "change" event', function () {
expect(this.changeCallback).not.toHaveBeenCalled();
});
});
describe('when given a GeoJSON with different coordinates', function () {
beforeEach(function () {
var newAndExpectedGeoJSON = {
type: 'LineString',
coordinates: [
[ 0, 0 ], [ 1, 1 ], [ 2, 2 ]
]
};
this.polyline.setCoordinatesFromGeoJSON(newAndExpectedGeoJSON);
expect(this.polyline.toGeoJSON()).toEqual(newAndExpectedGeoJSON);
});
it('should trigger a "change" event', function () {
expect(this.changeCallback).toHaveBeenCalled();
});
it('should update the coordinates', function () {
expect(this.polyline.toGeoJSON()).toEqual({
type: 'LineString',
coordinates: [ [ 0, 0 ], [ 1, 1 ], [ 2, 2 ] ]
});
});
});
});
});

View File

@@ -0,0 +1,15 @@
var _ = require('underscore');
var isCoordinateSimilar = function (coordinateA, coordinateB) {
return coordinateA + 0.1 > coordinateB &&
coordinateA - 0.1 < coordinateB;
};
module.exports = {
areCoordinatesSimilar: function (coordinatesA, coordinatesB) {
return _.every(coordinatesA, function (coordinate, index) {
return isCoordinateSimilar(coordinate.lat, coordinatesB[index].lat) &&
isCoordinateSimilar(coordinate.lng, coordinatesB[index].lng);
});
}
};

View File

@@ -0,0 +1,69 @@
var _ = require('underscore');
var Backbone = require('backbone');
var Map = require('../../../../src/geo/map');
var CoordinatesComparator = require('./coordinates-comparator');
module.exports = function (MapViewBase) {
if (!MapViewBase) throw new Error('MapViewBase is required');
// We extend the specific MapView and add some methods to make
// testing easier
var MapView = MapViewBase.extend({
initialize: function () {
MapViewBase.prototype.initialize.apply(this, arguments);
this._markers = [];
this._paths = [];
},
addMarker: function (marker) {
MapViewBase.prototype.addMarker.apply(this, arguments);
this._markers.push(marker);
},
removeMarker: function (marker) {
MapViewBase.prototype.removeMarker.apply(this, arguments);
var index = this._markers.indexOf(marker);
if (index >= 0) {
this._markers.splice(index, 1);
}
},
addPath: function (path) {
MapViewBase.prototype.addPath.apply(this, arguments);
this._paths.push(path);
},
removePath: function (path) {
MapViewBase.prototype.removePath.apply(this, arguments);
var index = this._paths.indexOf(path);
if (index >= 0) {
this._paths.splice(index, 1);
}
},
getMarkers: function () {
return this._markers;
},
getPaths: function () {
return this._paths;
},
findMarkerByLatLng: function (latlng) {
var markers = this.getMarkers();
return _.find(markers, function (marker) {
return CoordinatesComparator.areCoordinatesSimilar([marker.getCoordinates()], [latlng]);
}, this);
}
});
var map = new Map(null, {
layersFactory: {}
});
return new MapView({
mapModel: map,
engine: new Backbone.Model(),
layerGroupModel: {}
});
};

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsMultiPointView = require('../../../../../src/geo/geometry-views/gmaps/multi-point-view.js');
var SharedTestsForMultiPointViews = require('../shared-tests-for-multi-point-views');
describe('src/geo/geometry-views/gmaps/multi-point-view.js', function () {
SharedTestsForMultiPointViews.call(this, GMapsMapView, GMapsMultiPointView);
});

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsMultiPolygonView = require('../../../../../src/geo/geometry-views/gmaps/multi-polygon-view.js');
var SharedTestsForMultiPolygonViews = require('../shared-tests-for-multi-polygon-views');
describe('src/geo/geometry-views/gmaps/multi-polygon-view.js', function () {
SharedTestsForMultiPolygonViews.call(this, GMapsMapView, GMapsMultiPolygonView);
});

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsMultiPolylineView = require('../../../../../src/geo/geometry-views/gmaps/multi-polyline-view.js');
var SharedTestsForMultiPolylineViews = require('../shared-tests-for-multi-polyline-views');
describe('src/geo/geometry-views/gmaps/multi-polygon-view.js', function () {
SharedTestsForMultiPolylineViews.call(this, GMapsMapView, GMapsMultiPolylineView);
});

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsPointView = require('../../../../../src/geo/geometry-views/gmaps/point-view.js');
var SharedTestsForPointViews = require('../shared-tests-for-point-views');
describe('src/geo/geometry-views/gmaps/point-view.js', function () {
SharedTestsForPointViews.call(this, GMapsMapView, GMapsPointView);
});

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsPolygonView = require('../../../../../src/geo/geometry-views/gmaps/polygon-view.js');
var SharedTestsForPolygonViews = require('../shared-tests-for-polygon-views');
describe('src/geo/geometry-views/gmaps/polygon-view.js', function () {
SharedTestsForPolygonViews.call(this, GMapsMapView, GMapsPolygonView);
});

View File

@@ -0,0 +1,7 @@
var GMapsMapView = require('../../../../../src/geo/gmaps/gmaps-map-view.js');
var GMapsPolylineView = require('../../../../../src/geo/geometry-views/gmaps/polyline-view.js');
var SharedTestsForPolylineViews = require('../shared-tests-for-polyline-views');
describe('src/geo/geometry-views/gmaps/polyline-view.js', function () {
SharedTestsForPolylineViews.call(this, GMapsMapView, GMapsPolylineView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletMultiPointView = require('../../../../../src/geo/geometry-views/leaflet/multi-point-view.js');
var SharedTestsForMultiPointViews = require('../shared-tests-for-multi-point-views');
describe('src/geo/geometry-views/leaflet/multi-point-view.js', function () {
SharedTestsForMultiPointViews.call(this, LeafletMapView, LeafletMultiPointView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletMultiPolygonView = require('../../../../../src/geo/geometry-views/leaflet/multi-polygon-view.js');
var SharedTestsForMultiPolygonViews = require('../shared-tests-for-multi-polygon-views');
describe('src/geo/geometry-views/leaflet/multi-polygon-view.js', function () {
SharedTestsForMultiPolygonViews.call(this, LeafletMapView, LeafletMultiPolygonView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletMultiPolylineView = require('../../../../../src/geo/geometry-views/leaflet/multi-polyline-view.js');
var SharedTestsForMultiPolylineViews = require('../shared-tests-for-multi-polyline-views');
describe('src/geo/geometry-views/leaflet/multi-polyline-view.js', function () {
SharedTestsForMultiPolylineViews.call(this, LeafletMapView, LeafletMultiPolylineView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletPointView = require('../../../../../src/geo/geometry-views/leaflet/point-view.js');
var SharedTestsForPointViews = require('../shared-tests-for-point-views');
describe('src/geo/geometry-views/leaflet/point-view.js', function () {
SharedTestsForPointViews.call(this, LeafletMapView, LeafletPointView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletPolygonView = require('../../../../../src/geo/geometry-views/leaflet/polygon-view.js');
var SharedTestsForPolygonViews = require('../shared-tests-for-polygon-views');
describe('src/geo/geometry-views/leaflet/polygon-view.js', function () {
SharedTestsForPolygonViews.call(this, LeafletMapView, LeafletPolygonView);
});

View File

@@ -0,0 +1,7 @@
var LeafletMapView = require('../../../../../src/geo/leaflet/leaflet-map-view.js');
var LeafletPolylineView = require('../../../../../src/geo/geometry-views/leaflet/polyline-view.js');
var SharedTestsForPolylineViews = require('../shared-tests-for-polyline-views');
describe('src/geo/geometry-views/leaflet/polyline-view.js', function () {
SharedTestsForPolylineViews.call(this, LeafletMapView, LeafletPolylineView);
});

View File

@@ -0,0 +1,31 @@
var _ = require('underscore');
module.exports = function () {
beforeEach(function () {
spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; });
this.geometryView.render();
});
describe('when the model is removed', function () {
it('should remove each geometry', function () {
this.geometry.geometries.each(function (polygon) {
spyOn(polygon, 'remove');
});
this.geometry.remove();
expect(this.geometry.geometries.all(function (geometry) {
return geometry.remove.calls.count() === 1;
})).toBe(true);
});
it('should remove the view', function () {
spyOn(this.geometryView, 'remove');
this.geometry.remove();
expect(this.geometryView.remove).toHaveBeenCalled();
});
});
};

View File

@@ -0,0 +1,27 @@
var MultiPoint = require('../../../../src/geo/geometry-models/multi-point');
var SharedTestsForMultiGeometryViews = require('./shared-tests-for-multi-geometry-views');
var createMapView = require('./create-map-view');
module.exports = function (MapView, MultiPointView) {
beforeEach(function () {
this.geometry = new MultiPoint(null, {
latlngs: [
[0, 1],
[1, 2]
]
});
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometryView = new MultiPointView({
model: this.geometry,
mapView: this.mapView
});
});
SharedTestsForMultiGeometryViews.call(this);
it('should render the geometries', function () {
expect(this.mapView.getMarkers().length).toEqual(2); // 2 points
});
};

View File

@@ -0,0 +1,38 @@
var MultiPolygon = require('../../../../src/geo/geometry-models/multi-polygon');
var SharedTestsForMultiGeometryViews = require('./shared-tests-for-multi-geometry-views');
var createMapView = require('./create-map-view');
module.exports = function (MapView, MultiPolygonView) {
beforeEach(function () {
this.geometry = new MultiPolygon(null, {
latlngs: [
[
[0, 1],
[1, 2],
[2, 3],
[3, 4]
],
[
[0, 10],
[10, 20],
[20, 30],
[30, 40]
]
]
});
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometryView = new MultiPolygonView({
model: this.geometry,
mapView: this.mapView
});
});
SharedTestsForMultiGeometryViews.call(this);
it('should render the geometries', function () {
expect(this.mapView.getPaths().length).toEqual(2); // 2 geometries
expect(this.mapView.getMarkers().length).toEqual(8); // 4 markers for each geometry
});
};

View File

@@ -0,0 +1,38 @@
var MultiPolyline = require('../../../../src/geo/geometry-models/multi-polyline');
var SharedTestsForMultiGeometryViews = require('./shared-tests-for-multi-geometry-views');
var createMapView = require('./create-map-view');
module.exports = function (MapView, MultiPolylineView) {
beforeEach(function () {
this.geometry = new MultiPolyline(null, {
latlngs: [
[
[0, 1],
[1, 2],
[2, 3],
[3, 4]
],
[
[0, 10],
[10, 20],
[20, 30],
[30, 40]
]
]
});
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometryView = new MultiPolylineView({
model: this.geometry,
mapView: this.mapView
});
});
SharedTestsForMultiGeometryViews.call(this);
it('should render the geometries', function () {
expect(this.mapView.getPaths().length).toEqual(2); // 2 geometries
expect(this.mapView.getMarkers().length).toEqual(8); // 4 markers for each geometry
});
};

View File

@@ -0,0 +1,299 @@
var _ = require('underscore');
var Point = require('../../../../src/geo/geometry-models/point.js');
var createMapView = require('./create-map-view');
var CoordinatesComparator = require('./coordinates-comparator');
module.exports = function (Path, MapView, PathView) {
beforeEach(function () {
spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; });
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometry = new Path(null, {
latlngs: [
[-1, 1], [1, 2], [3, 4]
]
});
this.geometryView = new PathView({
model: this.geometry,
mapView: this.mapView
});
this.geometryView.render();
});
it('should render some markers and the path', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(3); // 3 markers
expect(markers[0].getCoordinates()).toEqual({ lat: -1, lng: 1 });
expect(markers[0].isDraggable()).toBe(false);
expect(markers[1].getCoordinates()).toEqual({ lat: 1, lng: 2 });
expect(markers[1].isDraggable()).toBe(false);
expect(markers[2].getCoordinates()).toEqual({ lat: 3, lng: 4 });
expect(markers[2].isDraggable()).toBe(false);
expect(paths[0].getCoordinates()).toEqual([
{ lat: -1, lng: 1 }, { lat: 1, lng: 2 }, { lat: 3, lng: 4 }
]);
});
it('should not render duplicated markers', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(3); // 3 markers
this.geometry.setCoordinates([
[-1, 1], [1, 2], [3, 4], [-1, 1]
]);
paths = this.mapView.getPaths();
markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(4); // 4 markers
});
describe('when the model is updated', function () {
describe('when a point changed', function () {
beforeEach(function () {
this.geometry.points.at(0).set('latlng', [ -45, 45 ]);
});
it("should update the path's latlng", function () {
expect(this.geometry.getCoordinates()).toEqual([
[ -45, 45 ], [ 1, 2 ], [ 3, 4 ]
]);
});
});
describe('when points are added', function () {
beforeEach(function () {
this.numberOfMarkersBefore = this.mapView.getMarkers().length;
var point = new Point({
latlng: [
-40,
40
]
});
this.geometry.addPoint(point);
});
it('should update the path\'s latlng', function () {
expect(this.geometry.getCoordinates()).toEqual([
[ -40, 40 ], [ -1, 1 ], [ 1, 2 ], [ 3, 4 ]
]);
});
it('should render a new marker', function () {
var numberOfMarkersAfter = this.mapView.getMarkers().length;
expect(numberOfMarkersAfter).toEqual(this.numberOfMarkersBefore + 1);
});
});
describe('when the point triggers a dblclick event', function () {
beforeEach(function () {
this.geometry.setCoordinates([
[ -10, 10 ], [ 10, 20 ], [ 30, 40 ]
]);
});
it('should remove the geometry point', function () {
var pointToDelete = new Point({
latlng: [ -40, 40 ]
});
this.geometry.addPoint(pointToDelete);
expect(this.geometry.getCoordinates()).toEqual([
[-40, 40], [ -10, 10 ], [ 10, 20 ], [ 30, 40 ]
]);
pointToDelete.trigger('dblclick');
expect(this.geometry.getCoordinates()).toEqual([
[ -10, 10 ], [ 10, 20 ], [ 30, 40 ]
]);
});
});
describe('when points are resetted,', function () {
beforeEach(function () {
this.geometry.setCoordinates([
[ -10, 10 ], [ 10, 20 ], [ 30, 40 ]
]);
});
it("should update the path's latlng", function () {
expect(this.geometry.getCoordinates()).toEqual([
[ -10, 10 ], [ 10, 20 ], [ 30, 40 ]
]);
});
it('should render the right number of markers', function () {
var numberOfMarkersAfter = this.mapView.getMarkers().length;
expect(numberOfMarkersAfter).toEqual(3);
});
});
});
describe('when the model is removed', function () {
it('should remove the markers and path from the map', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(3); // 3 markers
this.geometry.remove();
paths = this.mapView.getPaths();
markers = this.mapView.getMarkers();
expect(paths.length).toEqual(0);
expect(markers.length).toEqual(0);
});
it('should remove the view', function () {
spyOn(this.geometryView, 'remove');
this.geometry.remove();
expect(this.geometryView.remove).toHaveBeenCalled();
});
});
describe('expandable paths', function () {
beforeEach(function (done) {
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometry = new Path({
editable: true,
expandable: true
}, {
latlngs: [
[0, 0], [10, 0], [10, 10], [0, 10]
]
});
this.geometryView = new PathView({
model: this.geometry,
mapView: this.mapView
});
// Listen for the map to be ready
this.mapView.onReady(function () {
this.geometryView.render();
// Marker that we'll interact with in the tests
this.middlePointMarker = this.mapView.findMarkerByLatLng({ lat: 5, lng: 0 });
var markers = this.mapView.getMarkers();
this.numberOfMarkersBefore = markers.length;
done();
}.bind(this));
});
describe('when the model is removed', function () {
it('should remove the markers, middle points, and path from the map', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).not.toEqual(0);
expect(markers.length).not.toEqual(0);
this.geometry.remove();
paths = this.mapView.getPaths();
markers = this.mapView.getMarkers();
expect(paths.length).toEqual(0);
expect(markers.length).toEqual(0);
});
});
describe('when user mousedowns a middle point', function () {
beforeEach(function () {
expect(this.middlePointMarker.getIconURL()).toEqual(Point.MIDDLE_POINT_ICON_URL);
this.middlePointMarker.trigger('mousedown');
});
it('should add a vertex to the geometry at [5, 0]', function () {
var paths = this.mapView.getPaths();
expect(paths.length).toEqual(1);
// Coordinates have different precissions and we just check they are similar
expect(CoordinatesComparator.areCoordinatesSimilar(paths[0].getCoordinates(), [
{ lat: 0, lng: 0 }, { lat: 5, lng: 0 }, { lat: 10, lng: 0 }, { lat: 10, lng: 10 }, { lat: 0, lng: 10 }
])).toBeTruthy();
});
it('should change the icon of the middle point', function () {
expect(this.middlePointMarker.getIconURL()).toEqual(Point.DEFAULT_ICON_URL);
});
it('should add two middle points at [2.5] and [7.5, 0]', function () {
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(this.numberOfMarkersBefore + 2);
expect(this.mapView.findMarkerByLatLng({ lat: 2.5, lng: 0 })).toBeDefined();
expect(this.mapView.findMarkerByLatLng({ lat: 7.5, lng: 0 })).toBeDefined();
});
it('should remove the markers, middle points, and path from the map when the model is removed', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).not.toEqual(0);
expect(markers.length).not.toEqual(0);
this.geometry.remove();
paths = this.mapView.getPaths();
markers = this.mapView.getMarkers();
expect(paths.length).toEqual(0);
expect(markers.length).toEqual(0);
});
describe('when user drags a middle point', function () {
beforeEach(function () {
// Simulate a drag and drop to [5, -5]
spyOn(this.middlePointMarker, 'getCoordinates').and.returnValue({ lat: 5, lng: -5 });
this.middlePointMarker.trigger('dragstart');
this.middlePointMarker.trigger('drag');
this.middlePointMarker.trigger('dragend');
});
it('should update the coordinates of the new vertex to [5, -5]', function () {
var paths = this.mapView.getPaths();
expect(paths.length).toEqual(1);
expect(paths[0].getCoordinates()).toEqual([
{ lat: 0, lng: 0 }, { lat: 5, lng: -5 }, { lat: 10, lng: 0 }, { lat: 10, lng: 10 }, { lat: 0, lng: 10 }
]);
});
it('should add two middle points at [2.5] and [7.5, 0]', function () {
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(this.numberOfMarkersBefore + 2); // Only two middle points have been added
expect(this.mapView.findMarkerByLatLng({ lat: 2.5, lng: -2.5 })).toBeDefined();
expect(this.mapView.findMarkerByLatLng({ lat: 7.5, lng: -2.5 })).toBeDefined();
});
it('should delete the new point after dblclick event', function () {
this.middlePointMarker.trigger('mousedown');
this.middlePointMarker.trigger('dblclick');
var paths = this.mapView.getPaths();
expect(paths[0].getCoordinates()).toEqual([
{ lat: 0, lng: 0 }, { lat: 10, lng: 0 }, { lat: 10, lng: 10 }, { lat: 0, lng: 10 }
]);
});
});
});
});
};

View File

@@ -0,0 +1,191 @@
var _ = require('underscore');
var Point = require('../../../../src/geo/geometry-models/point.js');
var createMapView = require('./create-map-view');
module.exports = function (MapView, PointView) {
beforeEach(function () {
spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; });
this.point = new Point({
latlng: [
-40,
40
]
});
this.mapView = createMapView(MapView);
this.mapView.render();
this.pointView = new PointView({
model: this.point,
mapView: this.mapView
});
this.pointView.render();
});
it('should add a marker to the map', function () {
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(1);
expect(markers[0].getCoordinates()).toEqual({
lat: -40,
lng: 40
});
expect(markers[0].isDraggable()).toBe(false);
});
it('should add a marker to the map when the model gets a lat and lng', function () {
this.point = new Point();
this.mapView = createMapView(MapView);
this.mapView.render();
this.pointView = new PointView({
model: this.point,
mapView: this.mapView
});
this.pointView.render();
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(0);
this.point.set('latlng', [ -45, 45 ]);
markers = this.mapView.getMarkers();
expect(markers.length).toEqual(1);
});
describe('when the model is updated', function () {
it("should update the marker's latlng", function () {
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(1);
expect(markers[0].getCoordinates()).toEqual({
lat: -40,
lng: 40
});
this.point.set('latlng', [ -45, 45 ]);
markers = this.mapView.getMarkers();
expect(markers.length).toEqual(1);
expect(markers[0].getCoordinates()).toEqual({
lat: -45,
lng: 45
});
});
});
describe('when the model is removed', function () {
it('should remove the marker if model is removed', function () {
expect(this.mapView.getMarkers().length).toEqual(1);
this.point.remove();
expect(this.mapView.getMarkers().length).toEqual(0);
});
it('should remove the view', function () {
spyOn(this.pointView, 'remove');
this.point.remove();
expect(this.pointView.remove).toHaveBeenCalled();
});
});
describe('editable points', function () {
beforeEach(function () {
this.point = new Point({
latlng: [
-40,
40
],
editable: true
});
this.mapView = createMapView(MapView);
this.mapView.render();
this.pointView = new PointView({
model: this.point,
mapView: this.mapView
});
this.pointView.render();
this.marker = this.mapView.getMarkers()[0];
});
it('should add an editable marker to the map', function () {
expect(this.marker.isDraggable()).toBe(true);
});
it("should update model's latlng when the marker is dragged & dropped", function () {
spyOn(this.marker, 'getCoordinates').and.returnValue({
lat: -90,
lng: 90
});
this.marker.trigger('dragstart');
this.marker.trigger('drag');
this.marker.trigger('dragend');
expect(this.point.getCoordinates()).toEqual([ -90, 90 ]);
});
it("shouldn't update the marker's latlng while dragging", function () {
this.marker.trigger('dragstart');
this.point.set('latlng', [
-50,
50
]);
expect(this.marker.getCoordinates().lat).toEqual(-40);
expect(this.marker.getCoordinates().lng).toEqual(40);
this.marker.trigger('drag');
this.marker.trigger('dragend');
this.point.set('latlng', [
-50,
50
]);
expect(this.marker.getCoordinates().lat).toEqual(-50);
expect(this.marker.getCoordinates().lng).toEqual(50);
});
it('should bind marker events', function () {
var callback = jasmine.createSpy('callback');
var marker = this.mapView.getMarkers()[0];
this.pointView.on('mousedown', callback);
marker.trigger('mousedown');
expect(callback).toHaveBeenCalled();
});
it('should unbind marker events when the view is cleaned', function () {
var callback = jasmine.createSpy('callback');
var marker = this.mapView.getMarkers()[0];
this.pointView.on('mousedown', callback);
this.pointView.clean();
marker.trigger('mousedown');
expect(callback).not.toHaveBeenCalled();
});
});
describe('.clean', function () {
it('should remove the marker from the map', function () {
var markers = this.mapView.getMarkers();
expect(markers.length).toEqual(1);
this.pointView.clean();
markers = this.mapView.getMarkers();
expect(markers.length).toEqual(0);
});
});
};

View File

@@ -0,0 +1,96 @@
var Polygon = require('../../../../src/geo/geometry-models/polygon');
var SharedTestsForPathViews = require('./shared-tests-for-path-views');
var createMapView = require('./create-map-view');
var CoordinatesComparator = require('./coordinates-comparator');
module.exports = function (MapView, PathView) {
SharedTestsForPathViews.call(this, Polygon, MapView, PathView);
describe('expandable polygons', function () {
beforeEach(function (done) {
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometry = new Polygon({
editable: true,
expandable: true
}, {
latlngs: [
[0, 0], [10, 0], [10, 10], [0, 10]
]
});
this.geometryView = new PathView({
model: this.geometry,
mapView: this.mapView
});
// Listen for the map to be ready
this.mapView.onReady(function () {
this.geometryView.render();
done();
}.bind(this));
});
it('should render markers for each vertex, the path, and middle points', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(8); // 4 markers + 4 middle points
// Markers
expect(markers[0].getCoordinates()).toEqual({ lat: 0, lng: 0 });
expect(markers[0].isDraggable()).toBe(true);
expect(markers[1].getCoordinates()).toEqual({ lat: 10, lng: 0 });
expect(markers[1].isDraggable()).toBe(true);
expect(markers[2].getCoordinates()).toEqual({ lat: 10, lng: 10 });
expect(markers[2].isDraggable()).toBe(true);
expect(markers[3].getCoordinates()).toEqual({ lat: 0, lng: 10 });
expect(markers[3].isDraggable()).toBe(true);
// Middle points
// Coordinates have different precissions and we just check they are similar
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[4].getCoordinates() ],
[ { lat: 5, lng: 0 } ])
).toBeTruthy();
expect(markers[4].isDraggable()).toBe(true);
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[5].getCoordinates() ],
[ { lat: 10, lng: 5 } ])
).toBeTruthy();
expect(markers[5].isDraggable()).toBe(true);
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[6].getCoordinates() ],
[ { lat: 5, lng: 10 } ])
).toBeTruthy();
expect(markers[6].isDraggable()).toBe(true);
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[7].getCoordinates() ],
[ { lat: 0, lng: 5 } ])
).toBeTruthy();
expect(markers[7].isDraggable()).toBe(true);
expect(paths[0].getCoordinates()).toEqual([
{ lat: 0, lng: 0 }, { lat: 10, lng: 0 }, { lat: 10, lng: 10 }, { lat: 0, lng: 10 }
]);
});
it('should re-render middle points when map is zoomed', function () {
spyOn(this.mapView, 'removeMarker');
spyOn(this.mapView, 'addMarker');
this.mapView.trigger('zoomend');
expect(this.mapView.removeMarker.calls.count()).toEqual(4);
expect(this.mapView.addMarker.calls.count()).toEqual(4);
});
});
};

View File

@@ -0,0 +1,90 @@
var Polyline = require('../../../../src/geo/geometry-models/polyline');
var SharedTestsForPathViews = require('./shared-tests-for-path-views');
var createMapView = require('./create-map-view');
var CoordinatesComparator = require('./coordinates-comparator');
module.exports = function (MapView, PathView) {
SharedTestsForPathViews.call(this, Polyline, MapView, PathView);
describe('expandable polylines', function () {
beforeEach(function (done) {
this.mapView = createMapView(MapView);
this.mapView.render();
this.geometry = new Polyline({
editable: true,
expandable: true
}, {
latlngs: [
[0, 0], [10, 0], [10, 10], [0, 10]
]
});
this.geometryView = new PathView({
model: this.geometry,
mapView: this.mapView
});
// Listen for the map to be ready
this.mapView.onReady(function () {
this.geometryView.render();
done();
}.bind(this));
});
it('should render markers for each vertex, the path, and middle points', function () {
var paths = this.mapView.getPaths();
var markers = this.mapView.getMarkers();
expect(paths.length).toEqual(1);
expect(markers.length).toEqual(7); // 4 markers + 3 middle points
// Markers
expect(markers[0].getCoordinates()).toEqual({ lat: 0, lng: 0 });
expect(markers[0].isDraggable()).toBe(true);
expect(markers[1].getCoordinates()).toEqual({ lat: 10, lng: 0 });
expect(markers[1].isDraggable()).toBe(true);
expect(markers[2].getCoordinates()).toEqual({ lat: 10, lng: 10 });
expect(markers[2].isDraggable()).toBe(true);
expect(markers[3].getCoordinates()).toEqual({ lat: 0, lng: 10 });
expect(markers[3].isDraggable()).toBe(true);
// Middle points
// Coordinates have different precissions and we just check they are similar
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[4].getCoordinates() ],
[ { lat: 5, lng: 0 } ])
).toBeTruthy();
expect(markers[4].isDraggable()).toBe(true);
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[5].getCoordinates() ],
[ { lat: 10, lng: 5 } ])
).toBeTruthy();
expect(markers[5].isDraggable()).toBe(true);
expect(CoordinatesComparator.areCoordinatesSimilar(
[ markers[6].getCoordinates() ],
[ { lat: 5, lng: 10 } ])
).toBeTruthy();
expect(markers[6].isDraggable()).toBe(true);
expect(paths[0].getCoordinates()).toEqual([
{ lat: 0, lng: 0 }, { lat: 10, lng: 0 }, { lat: 10, lng: 10 }, { lat: 0, lng: 10 }
]);
});
it('should re-render middle points when map is zoomed', function () {
spyOn(this.mapView, 'removeMarker');
spyOn(this.mapView, 'addMarker');
this.mapView.trigger('zoomend');
expect(this.mapView.removeMarker.calls.count()).toEqual(3);
expect(this.mapView.addMarker.calls.count()).toEqual(3);
});
});
};

View File

@@ -0,0 +1,139 @@
/* global google */
var GoogleCartoDBLayerGroupClass = require('../../../../src/geo/gmaps/gmaps-cartodb-layer-group-view');
var cartoLayerGroupViewTests = require('../shared-tests-for-carto-layer-group');
var CartoDBLayer = require('../../../../src/geo/map/cartodb-layer');
var LayersCollection = require('../../../../src/geo/map/layers');
var CartoDBLayerGroup = require('../../../../src/geo/cartodb-layer-group');
describe('gmaps-cartodb-layer-group-view', function () {
/**
* Helper function used to get a google map in the shared tests.
*/
function createNativeMap (container) {
// Create a leaflet map inside a container
container.setAttribute('id', 'map');
container.style.height = '200px';
document.body.appendChild(container);
var googleMap = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: { lat: 47.84808037632246, lng: 14.2822265625 }
});
return googleMap;
}
/**
* Helper function used to get the tiles in the shared tests
*/
function getTileUrl (layerGroupView) {
return layerGroupView.options.tiles[0].replace('{s}', '0');
}
/**
* Gmaps events and Leaflet events are different.
*/
var event = {
da: { x: 121.8125, y: 94.56249999999997 },
data: { name: 'fakeCityName', cartodb_id: 123 },
e: { type: 'mousemove' },
latLng: { lat: function () { return 42.48830197960228; }, lng: function () { return -8.701171875; } },
layer: 0,
pixel: { x: 243, y: 274 }
};
// -- Shared tests for carto layer group
cartoLayerGroupViewTests(createNativeMap, GoogleCartoDBLayerGroupClass, getTileUrl, event);
// -- Google maps specific tests
describe('GMapsCartoDBLayerGroupView', function () {
var nativeMap;
var container;
var cartoDbLayer0;
var cartoDbLayer1;
var layerGroupView0;
var layerGroupView1;
var layerGroupModelMock;
var layersCollection;
var engineMock = {
on: jasmine.createSpy('on')
};
var mapModelMock = {
on: jasmine.createSpy('on'),
isFeatureInteractivityEnabled: jasmine.createSpy('isFeatureInteractivityEnabled').and.returnValue(false)
};
beforeEach(function () {
container = document.createElement('div');
nativeMap = createNativeMap(container);
cartoDbLayer0 = new CartoDBLayer({}, { engine: engineMock });
cartoDbLayer1 = new CartoDBLayer({}, { engine: engineMock });
layersCollection = new LayersCollection([cartoDbLayer0, cartoDbLayer1]);
layerGroupModelMock = new CartoDBLayerGroup({
urls: {
'subdomains': [0, 1, 2, 3],
'tiles': 'http://{s}.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/{layerIndexes}/{z}/{x}/{y}.png',
'grids': [
[
'http://0.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/0/{z}/{x}/{y}.grid.json',
'http://1.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/0/{z}/{x}/{y}.grid.json',
'http://2.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/0/{z}/{x}/{y}.grid.json',
'http://3.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/0/{z}/{x}/{y}.grid.json'
],
[
'http://0.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/1/{z}/{x}/{y}.grid.json',
'http://1.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/1/{z}/{x}/{y}.grid.json',
'http://2.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/1/{z}/{x}/{y}.grid.json',
'http://3.ashbu.cartocdn.com/documentation/api/v1/map/0123456789/1/{z}/{x}/{y}.grid.json'
]
],
'attributes': [
'http://ashbu.cartocdn.com/documentation/api/v1/map/0123456789/0/attributes',
'http://ashbu.cartocdn.com/documentation/api/v1/map/0123456789/1/attributes'
]
},
indexOfLayersInWindshaft: [1, 2]
}, {
layersCollection: layersCollection
});
layerGroupView0 = new GoogleCartoDBLayerGroupClass(layerGroupModelMock, { nativeMap: nativeMap, mapModel: mapModelMock });
layerGroupView1 = new GoogleCartoDBLayerGroupClass(layerGroupModelMock, { nativeMap: nativeMap, mapModel: mapModelMock });
nativeMap.overlayMapTypes.push(layerGroupView0);
nativeMap.overlayMapTypes.push(layerGroupView1);
});
describe('._getOverlayIndex', function () {
it('returns the index of the given layour group', function () {
expect(layerGroupView0._getOverlayIndex()).toEqual(0);
expect(layerGroupView1._getOverlayIndex()).toEqual(1);
});
});
describe('._refreshView', function () {
it('sets the layer group in the same position', function () {
layerGroupView0._refreshView();
expect(nativeMap.overlayMapTypes.getAt(0)).toEqual(layerGroupView0);
expect(nativeMap.overlayMapTypes.getAt(1)).toEqual(layerGroupView1);
});
});
describe('.remove', function () {
it('removes the correct layer group', function () {
layerGroupView0.remove();
expect(nativeMap.overlayMapTypes.getLength()).toEqual(1);
expect(nativeMap.overlayMapTypes.getAt(0)).toEqual(layerGroupView1);
});
});
afterEach(function () {
document.body.removeChild(container);
});
});
});

View File

@@ -0,0 +1,156 @@
/* global google */
var $ = require('jquery');
var Backbone = require('backbone');
var Map = require('../../../../src/geo/map');
var GoogleMapsMapView = require('../../../../src/geo/gmaps/gmaps-map-view');
describe('geo/gmaps/gmaps-map-view', function () {
var mapView;
var map;
var spy;
var container;
beforeEach(function () {
container = $('<div>').css('height', '200px');
map = new Map(null, {
layersFactory: {}
});
spyOn(map, 'setMapViewSize').and.callThrough();
spyOn(map, 'setPixelToLatLngConverter').and.callThrough();
spyOn(map, 'setLatLngToPixelConverter').and.callThrough();
mapView = new GoogleMapsMapView({
el: container,
mapModel: map,
engine: new Backbone.Model(),
layerGroupModel: new Backbone.Model()
});
mapView.render();
spy = jasmine.createSpyObj('spy', ['zoomChanged', 'centerChanged', 'scrollWheelChanged']);
map.bind('change:zoom', spy.zoomChanged);
map.bind('change:center', spy.centerChanged);
map.bind('change:scrollwheel', spy.scrollWheelChanged);
});
it('should change zoom', function () {
mapView._setZoom(null, 10);
expect(spy.zoomChanged).toHaveBeenCalled();
});
it('should disable gmaps dragging and double click zooming when the map has drag disabled', function () {
var container = $('<div>').css({
'height': '200px',
'width': '200px'
});
var map = new Map({
drag: false
}, {
layersFactory: {}
});
var mapView = new GoogleMapsMapView({
el: container,
mapModel: map,
engine: new Backbone.Model(),
layerGroupModel: new Backbone.Model()
});
mapView.render();
expect(mapView._gmapsMap.get('draggable')).toBeFalsy();
expect(mapView._gmapsMap.get('disableDoubleClickZoom')).toBeTruthy();
});
it('should change center and zoom when bounds are changed', function (done) {
var spy = jasmine.createSpy('change:center');
mapView.getSize = function () { return {x: 200, y: 200}; };
map.bind('change:center', spy);
spyOn(mapView, '_setCenter');
mapView._bindModel();
map.set({
'view_bounds_ne': [1, 1],
'view_bounds_sw': [-0.3, -1.2]
});
setTimeout(function () {
expect(mapView._setCenter).toHaveBeenCalled();
done();
}, 1000);
});
it('should "forward" a dragend event to the map model', function () {
var container = $('<div>').css({
'height': '200px',
'width': '200px'
});
var map = new Map({
drag: false
}, {
layersFactory: {}
});
var mapView = new GoogleMapsMapView({
el: container,
mapModel: map,
engine: new Backbone.Model(),
layerGroupModel: new Backbone.Model()
});
mapView.render();
spyOn(map, 'trigger');
spyOn(mapView, 'trigger');
google.maps.event.trigger(mapView._gmapsMap, 'dragend');
expect(map.trigger).toHaveBeenCalledWith('moveend', jasmine.any(Object));
map.trigger.calls.reset();
mapView.trigger.calls.reset();
google.maps.event.trigger(mapView._gmapsMap, 'zoom_changed');
expect(map.trigger).toHaveBeenCalledWith('moveend', jasmine.any(Object));
map.trigger.calls.reset();
mapView.trigger.calls.reset();
});
it('should set mapview size when bounds changes', function () {
google.maps.event.trigger(mapView._gmapsMap, 'bounds_changed');
expect(map.setMapViewSize).toHaveBeenCalled();
});
describe('converters', function () {
it('should set converters', function () {
expect(map.setPixelToLatLngConverter).toHaveBeenCalled();
expect(map.setLatLngToPixelConverter).toHaveBeenCalled();
expect(map._pixelToLatLngConverter).toBeDefined();
expect(map._latLngToPixelConverter).toBeDefined();
});
it('should call native methods', function () {
spyOn(mapView.projector, 'latLngToPixel').and.callThrough();
spyOn(mapView.projector, 'pixelToLatLng').and.callThrough();
var pixelToLatLng = map.pixelToLatLng();
pixelToLatLng({x: 0, y: 0});
expect(mapView.projector.pixelToLatLng).toHaveBeenCalled();
var latLngToPixel = map.latLngToPixel();
latLngToPixel([0, 0]);
expect(mapView.projector.latLngToPixel).toHaveBeenCalled();
});
});
describe('listeners', function () {
beforeEach(function (done) {
// Listen for the map to be ready
mapView.onReady(done);
});
it('sets isReady to true if idle event is triggered', function () {
expect(mapView._isReady).toBe(true);
});
});
});

View File

@@ -0,0 +1,69 @@
var $ = require('jquery');
var Backbone = require('backbone');
var Map = require('../../../../src/geo/map');
var GoogleMapsMapView = require('../../../../src/geo/gmaps/gmaps-map-view');
var GMapsLayerViewFactory = require('../../../../src/geo/gmaps/gmaps-layer-view-factory');
var TorqueLayer = require('../../../../src/geo/map/torque-layer');
var MockFactory = require('../../../helpers/mockFactory');
var SharedTestsForTorqueLayer = require('../shared-tests-for-torque-layer');
var torque = require('torque.js');
var createEngine = require('../../fixtures/engine.fixture.js');
describe('geo/gmaps/gmaps-torque-layer-view', function () {
beforeEach(function () {
var container = $('<div>').css('height', '200px');
var engineMock = createEngine();
this.map = new Map(null, {
layersFactory: {}
});
spyOn(this.map, 'trigger');
this.view = new GoogleMapsMapView({
el: container,
mapModel: this.map,
engine: new Backbone.Model(),
layerViewFactory: new GMapsLayerViewFactory(),
layerGroupModel: new Backbone.Model(),
showLimitErrors: false
});
this.model = new TorqueLayer({
type: 'torque',
source: MockFactory.createAnalysisModel({ id: 'a0' }),
cartocss: '#test {}',
'torque-steps': 100
}, { engine: engineMock });
spyOn(torque, 'GMapsTorqueLayer').and.callThrough();
this.map.addLayer(this.model);
this.view = this.view._layerViews[this.model.cid];
});
SharedTestsForTorqueLayer.call(this);
it('should apply TorqueLayer initialize method on the extended view with a bunch of attrs', function () {
expect(torque.GMapsTorqueLayer).toHaveBeenCalled();
var attrs = torque.GMapsTorqueLayer.calls.argsFor(0)[0];
expect(attrs.cartocss).toEqual(jasmine.any(String));
});
describe('when GMapsTorqueLayer triggers tileError', function () {
it('should trigger error:limit in mapModel if showLimitErrors is true', function () {
this.view.showLimitErrors = true;
this.view.nativeTorqueLayer.fire('tileError');
var calls = this.map.trigger.calls.all();
var types = calls.map(function (call) {
return call.args[0];
});
expect(types).toContain('error:limit');
});
it('should not trigger error:limit in mapModel if showLimitErrors is false', function () {
this.view.nativeTorqueLayer.fire('tileError');
var calls = this.map.trigger.calls.all();
var types = calls.map(function (call) {
return call.args[0];
});
expect(types).not.toContain('error:limit');
});
});
});

View File

@@ -0,0 +1,87 @@
// var $ = require('jquery');
// var CartoDBLayerGMaps = require('../../../../src/geo/gmaps/cartodb-layer-gmaps');
// describe('CartoDBLayerGMaps: Hide funcionality', function() {
// var div, map, cdb_layer;
// beforeEach(function() {
// div = document.createElement('div');
// div.setAttribute("id","map");
// div.style.height = "100px";
// div.style.width = "100px";
// map = new google.maps.Map(div, {
// center: new google.maps.LatLng(51.505, -0.09),
// disableDefaultUI: false,
// zoom: 13,
// mapTypeId: google.maps.MapTypeId.ROADMAP,
// mapTypeControl: false
// });
// cdb_layer = new CartoDBLayerGMaps({
// map: map,
// user_name:"examples",
// tile_style: 'test',
// table_name: 'earthquakes',
// query: "SELECT * FROM {{table_name}}",
// tile_style: "#{{table_name}}{marker-fill:#E25B5B}",
// opacity:0.8,
// interactivity: "cartodb_id, magnitude",
// featureOver: function(ev,latlng,pos,data) {},
// featureOut: function() {},
// featureClick: function(ev,latlng,pos,data) {},
// debug: true
// });
// map.overlayMapTypes.setAt(0, cdb_layer);
// });
// it('if hides layers should work', function(done) {
// setTimeout(function () {
// cdb_layer.hide();
// setTimeout(function() {
// var $tile = $(div).find("img[gtilekey]").first()
// , opacity = cdb_layer.options.opacity
// , before_opacity = cdb_layer.options.previous_opacity;
// expect(cdb_layer.visible).toBeFalsy();
// expect($tile.css("opacity")).toEqual('0');
// expect(opacity).toEqual(0);
// expect(before_opacity).not.toEqual(0);
// done();
// }, 500);
// }, 500);
// });
// it('If sets opacity to 0, layer should be visible', function(done) {
// setTimeout(function () {
// cdb_layer.setOpacity(0);
// expect(cdb_layer.options.visible).toBeTruthy();
// done();
// }, 500);
// });
// it('toggle layer from a visible state should work', function(done) {
// setTimeout(function () {
// cdb_layer.hide();
// visibility = cdb_layer.toggle();
// setTimeout(function() {
// var $tile = $(div).find("img[gtilekey]").first()
// , opacity = cdb_layer.options.opacity;
// expect(visibility).toBeTruthy();
// expect(cdb_layer.visible).toBeTruthy();
// expect($tile.css("opacity")).toEqual('0.99');
// expect(opacity).toEqual(0.99);
// done();
// }, 500);
// }, 500);
// });
// });

View File

@@ -0,0 +1,48 @@
// var $ = require('jquery');
// var CartoDBLayerGMaps = require('../../../../src/geo/gmaps/cartodb-layer-gmaps');
// describe('CartoDBLayerGMaps: Interaction funcionality', function() {
// var div, map, cdb_layer;
// beforeEach(function() {
// div = document.createElement('div');
// div.setAttribute("id","map");
// div.style.height = "100px";
// div.style.width = "100px";
// map = new google.maps.Map(div, {
// center: new google.maps.LatLng(51.505, -0.09),
// disableDefaultUI: false,
// zoom: 13,
// mapTypeId: google.maps.MapTypeId.ROADMAP,
// mapTypeControl: false
// });
// cdb_layer = new CartoDBLayerGMaps({
// map: map,
// user_name:"examples",
// table_name: 'country_colors',
// tile_style: 'test',
// opacity:0.8,
// interactivity: "cartodb_id",
// debug: true,
// interaction: true
// });
// map.overlayMapTypes.setAt(0, cdb_layer);
// });
// it('If there is no interaction defined, shouldn\'t work and failed', function() {
// // Fake a mouseover
// $(div).trigger('mouseover');
// expect(cdb_layer._manageOnEvents).toThrow();
// // Fake a mouseout
// $(div).trigger('mouseout');
// expect(cdb_layer._manageOffEvents).toThrow();
// // Fake a click
// $(div).trigger('click');
// expect(cdb_layer._manageOffEvents).toThrow();
// });
// });

View File

@@ -0,0 +1,73 @@
// var $ = require('jquery');
// var CartoDBLayerGMaps = require('../../../../src/geo/gmaps/cartodb-layer-gmaps');
// describe('CartoDBLayerGMaps: Opacity interaction', function() {
// var div, map, cdb_layer;
// beforeEach(function() {
// div = document.createElement('div');
// div.setAttribute("id","map");
// div.style.height = "100px";
// div.style.width = "100px";
// map = new google.maps.Map(div, {
// center: new google.maps.LatLng(51.505, -0.09),
// disableDefaultUI: false,
// zoom: 13,
// mapTypeId: google.maps.MapTypeId.ROADMAP,
// mapTypeControl: false
// });
// cdb_layer = new CartoDBLayerGMaps({
// map: map,
// user_name:"examples",
// table_name: 'earthquakes',
// query: "SELECT * FROM {{table_name}}",
// tile_style: "#{{table_name}}{marker-fill:#E25B5B}",
// opacity: 0.8,
// interactivity: "cartodb_id, magnitude",
// featureOver: function(ev,latlng,pos,data) {},
// featureOut: function() {},
// featureClick: function(ev,latlng,pos,data) {},
// debug: true
// });
// map.overlayMapTypes.setAt(0, cdb_layer);
// });
// xit('Layer opacity should be 0.8', function() {
// waits(500);
// runs(function () {
// var $layer = $(div).find("img[gtilekey]").first()
// , opacity = cdb_layer.options.opacity;
// expect(cdb_layer.options.visible).toBeTruthy();
// expect($layer.css("opacity")).toEqual('0.8');//opacity.toString());
// });
// });
// xit('Opacity shouldn\'t change if it is not visible', function() {
// waits(500);
// runs(function() {
// cdb_layer.hide();
// cdb_layer.setOpacity(0.3);
// map.overlayMapTypes.setAt(0, cdb_layer);
// });
// waits(500);
// runs(function () {
// var $layer = $(div).find("img[gtilekey]").first()
// , opacity = cdb_layer.options.opacity
// , before_opacity = cdb_layer.options.previous_opacity;
// expect(cdb_layer.options.visible).toBeFalsy();
// expect($layer.css("opacity")).toEqual('0.3');
// expect(before_opacity).toEqual(0.8);
// });
// });
// });

Some files were not shown because too many files have changed in this diff Show More