carto.js/test/spec/engine.spec.js
2020-06-13 18:34:34 +08:00

569 lines
24 KiB
JavaScript

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