Initial commit
This commit is contained in:
247
test/spec/api/createVis/create-vis.spec.js
Normal file
247
test/spec/api/createVis/create-vis.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
314
test/spec/api/createVis/scenarios/basic_vis.json.js
Normal file
314
test/spec/api/createVis/scenarios/basic_vis.json.js
Normal 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': "© <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': "© <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
|
||||
};
|
||||
19
test/spec/api/createVis/scenarios/index.js
Normal file
19
test/spec/api/createVis/scenarios/index.js
Normal 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
499
test/spec/api/sql.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
459
test/spec/api/v4/client.spec.js
Normal file
459
test/spec/api/v4/client.spec.js
Normal 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(
|
||||
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <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');
|
||||
});
|
||||
});
|
||||
});
|
||||
243
test/spec/api/v4/dataview/base.spec.js
Normal file
243
test/spec/api/v4/dataview/base.spec.js
Normal 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.');
|
||||
});
|
||||
});
|
||||
});
|
||||
438
test/spec/api/v4/dataview/category.spec.js
Normal file
438
test/spec/api/v4/dataview/category.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
265
test/spec/api/v4/dataview/formula.spec.js
Normal file
265
test/spec/api/v4/dataview/formula.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
452
test/spec/api/v4/dataview/histogram.spec.js
Normal file
452
test/spec/api/v4/dataview/histogram.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
427
test/spec/api/v4/dataview/time-series.spec.js
Normal file
427
test/spec/api/v4/dataview/time-series.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
49
test/spec/api/v4/error-handling/carto-error-extender.spec.js
Normal file
49
test/spec/api/v4/error-handling/carto-error-extender.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
107
test/spec/api/v4/error-handling/carto-error.spec.js
Normal file
107
test/spec/api/v4/error-handling/carto-error.spec.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
250
test/spec/api/v4/filter/base-sql.spec.js
Normal file
250
test/spec/api/v4/filter/base-sql.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
11
test/spec/api/v4/filter/bounding-box-gmaps.spec.js
Normal file
11
test/spec/api/v4/filter/bounding-box-gmaps.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
11
test/spec/api/v4/filter/bounding-box-leaflet.spec.js
Normal file
11
test/spec/api/v4/filter/bounding-box-leaflet.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
50
test/spec/api/v4/filter/bounding-box.spec.js
Normal file
50
test/spec/api/v4/filter/bounding-box.spec.js
Normal 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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
63
test/spec/api/v4/filter/category.spec.js
Normal file
63
test/spec/api/v4/filter/category.spec.js
Normal 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%'");
|
||||
});
|
||||
});
|
||||
});
|
||||
56
test/spec/api/v4/filter/circle.spec.js
Normal file
56
test/spec/api/v4/filter/circle.spec.js
Normal 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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
174
test/spec/api/v4/filter/filters-collection.spec.js
Normal file
174
test/spec/api/v4/filter/filters-collection.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
75
test/spec/api/v4/filter/polygon.spec.js
Normal file
75
test/spec/api/v4/filter/polygon.spec.js
Normal 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: []
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
73
test/spec/api/v4/filter/range.spec.js
Normal file
73
test/spec/api/v4/filter/range.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
171
test/spec/api/v4/layer/aggregation.spec.js
Normal file
171
test/spec/api/v4/layer/aggregation.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
677
test/spec/api/v4/layer/layer.spec.js
Normal file
677
test/spec/api/v4/layer/layer.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
230
test/spec/api/v4/layer/metadata/parser.spec.js
Normal file
230
test/spec/api/v4/layer/metadata/parser.spec.js
Normal 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\')' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
41
test/spec/api/v4/layers.spec.js
Normal file
41
test/spec/api/v4/layers.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
141
test/spec/api/v4/native/google-maps-map-type.spec.js
Normal file
141
test/spec/api/v4/native/google-maps-map-type.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
198
test/spec/api/v4/native/leaflet-layer.spec.js
Normal file
198
test/spec/api/v4/native/leaflet-layer.spec.js
Normal 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;
|
||||
}
|
||||
});
|
||||
268
test/spec/api/v4/source/dataset.spec.js
Normal file
268
test/spec/api/v4/source/dataset.spec.js
Normal 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.
|
||||
});
|
||||
});
|
||||
});
|
||||
257
test/spec/api/v4/source/sql.spec.js
Normal file
257
test/spec/api/v4/source/sql.spec.js
Normal 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'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
161
test/spec/api/v4/style/cartocss.spec.js
Normal file
161
test/spec/api/v4/style/cartocss.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
352
test/spec/api/vizjson.spec.js
Normal file
352
test/spec/api/vizjson.spec.js
Normal 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: '© <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors © <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: '© <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors © <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: '© <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors © <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: '© <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors © <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: '© <a href=\'http://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors © <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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user