Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -0,0 +1,210 @@
var _ = require('underscore');
var Backbone = require('backbone');
var Notifier = require('builder/components/notifier/notifier');
var AnalysisNotifications = require('builder/editor/layers/analysis-views/analysis-notifications');
describe('AnalysisNotifications.track', function () {
beforeEach(function () {
this.analysisNode = new Backbone.Model({ id: 'a1' });
this.notification = new Backbone.Model();
spyOn(Notifier, 'addNotification').and.returnValue(this.notification);
spyOn(this.notification, 'set');
AnalysisNotifications.track(this.analysisNode);
});
describe('if there is an existing notification for the node', function () {
beforeEach(function () {
spyOn(Notifier, 'getNotification').and.returnValue(this.notification);
});
describe('status changes', function () {
it('should update the notification if analysis is pending', function () {
this.analysisNode.set('status', 'pending');
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'loading',
'info': 'notifications.analysis.waiting'
});
});
it('should update the notification if analysis is running', function () {
this.analysisNode.set('status', 'running');
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'loading',
'info': 'notifications.analysis.running'
});
});
it('should update the notification if analysis is waiting', function () {
this.analysisNode.set('status', 'waiting');
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'loading',
'info': 'notifications.analysis.waiting'
});
});
it('should update the notification if analysis succeeded', function () {
this.analysisNode.set('status', 'ready');
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'success',
'info': 'notifications.analysis.completed',
'closable': true,
'delay': Notifier.DEFAULT_DELAY
});
});
it('should update the notification if analysis failed', function () {
this.analysisNode.set('status', 'failed');
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'error',
'info': 'notifications.analysis.failed',
'closable': true,
'autoclosable': false
});
});
});
describe('when an analysis node is deleted', function () {
it('should update the notification', function () {
this.analysisNode.trigger('destroy', this.analysisNode);
expect(this.notification.set).toHaveBeenCalledWith({
'status': 'success',
'info': 'notifications.analysis.removed',
'closable': true,
'delay': Notifier.DEFAULT_DELAY
});
});
it('should not update the notification if avoidNotification', function () {
this.analysisNode.set({avoidNotification: true}, {silent: true});
this.analysisNode.trigger('destroy', this.analysisNode);
expect(this.notification.set).not.toHaveBeenCalled();
});
});
});
describe('if there is NO notification for the node', function () {
_.each(['pending', 'waiting', 'failed'], function (status) {
it('should display a notification if node has a ' + status + ' status initially', function () {
this.analysisNode = new Backbone.Model({ id: 'a1' });
this.analysisNode.set({
status: status
});
AnalysisNotifications.track(this.analysisNode);
expect(Notifier.addNotification).toHaveBeenCalled();
});
});
it('should NOT display a notification if node has a ready status initially', function () {
this.analysisNode = new Backbone.Model({ id: 'a1' });
this.analysisNode.set({
status: 'ready'
});
AnalysisNotifications.track(this.analysisNode);
expect(Notifier.addNotification).not.toHaveBeenCalled();
});
describe('status changes', function () {
it('should update the notification if analysis is pending', function () {
this.analysisNode.set('status', 'pending');
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'loading',
info: 'notifications.analysis.waiting',
id: this.analysisNode.cid
});
});
it('should update the notification if analysis is waiting', function () {
this.analysisNode.set('status', 'waiting');
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'loading',
info: 'notifications.analysis.waiting',
id: this.analysisNode.cid
});
});
it('should update the notification if analysis succeeded', function () {
this.analysisNode.set('status', 'ready');
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'success',
info: 'notifications.analysis.completed',
closable: true,
delay: Notifier.DEFAULT_DELAY,
id: this.analysisNode.cid
});
});
it('should update the notification if analysis failed', function () {
this.analysisNode.set({
'status': 'failed'
});
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'error',
info: 'notifications.analysis.failed',
closable: true,
autoclosable: false,
id: this.analysisNode.cid
});
});
describe('when an analysis node has errors', function () {
it('should update the notification if analysis failed and has errors', function () {
this.analysisNode.set({
'error': { message: 'something went wrong!' },
'status': 'failed'
});
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'error',
info: 'notifications.analysis.failed: something went wrong!',
closable: true,
autoclosable: false,
id: this.analysisNode.cid
});
});
it('should update the notification if analysis failed and has errors in error_message', function () {
this.analysisNode.set({
'error_message': 'something went a little bit wrong!',
'status': 'failed'
});
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'error',
info: 'notifications.analysis.failed: something went a little bit wrong!',
closable: true,
autoclosable: false,
id: this.analysisNode.cid
});
});
});
});
describe('when an analysis node is deleted', function () {
it('should update the notification', function () {
this.analysisNode.trigger('destroy', this.analysisNode);
expect(Notifier.addNotification).toHaveBeenCalledWith({
status: 'success',
info: 'notifications.analysis.removed',
closable: true,
delay: Notifier.DEFAULT_DELAY,
id: this.analysisNode.cid
});
});
});
});
});
@@ -0,0 +1,101 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var ExportMapModel = require('builder/data/export-map-definition-model');
var ExportView = require('builder/editor/components/modals/export-map/modal-export-map-view');
var templateExportMapDownload = require('builder/editor/components/modals/export-map/export-map-download.tpl');
describe('editor/components/modals/export-map/modal-export-map-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.visDefinitionModel = new VisDefinitionModel({
name: 'Patatas Bravas',
id: 'v-123'
}, {
configModel: configModel
});
this.exportMapModel = new ExportMapModel({
visualization_id: this.visDefinitionModel.get('id')
}, {
configModel: configModel
});
this.view = new ExportView({
modalModel: new Backbone.Model(),
exportMapModel: this.exportMapModel,
renderOpts: {
name: this.visDefinitionModel.get('name')
}
});
spyOn(this.view, '_renderCompleteView').and.callFake(function () {
this.$el.html(templateExportMapDownload());
});
this.view.render();
});
describe('render', function () {
it('should render confirmation view if state is undefined', function () {
expect(this.view.$el.html()).toContain(_t('editor.maps.export.confirmation.title', {
name: this.visDefinitionModel.get('name')
}));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.confirmation.desc'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.confirmation.cancel'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.confirmation.confirm'));
});
it('should render pending view when pending state', function () {
var state = 'pending';
this.exportMapModel.set('state', state);
this.view.render();
expect(this.view.$el.html()).toContain(state.charAt(0).toUpperCase() + state.slice(1) + ' ...');
});
it('should render uploading view when uploading', function () {
var state = 'uploading';
this.exportMapModel.set('state', state);
this.view.render();
expect(this.view.$el.html()).toContain(state.charAt(0).toUpperCase() + state.slice(1) + ' ...');
});
it('should render exporting view when exporting', function () {
var state = 'exporting';
this.exportMapModel.set('state', state);
this.view.render();
expect(this.view.$el.html()).toContain(state.charAt(0).toUpperCase() + state.slice(1) + ' ...');
});
it('should render complete view when complete', function () {
this.exportMapModel.set('state', 'complete');
this.view.render();
expect(this.view.$el.html()).toContain(_t('editor.maps.export.download.title'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.download.tip'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.download.desc'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.download.confirm'));
});
it('should render error view when failure', function () {
this.exportMapModel.set('state', 'failure');
this.view.render();
expect(this.view.$el.html()).toContain(_t('editor.maps.export.error.title'));
expect(this.view.$el.html()).toContain(_t('editor.maps.export.error.desc'));
});
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,102 @@
var Backbone = require('backbone');
var _ = require('underscore');
var Toggler = require('builder/components/toggler/toggler-view.js');
describe('components/toggler/toggler', function () {
beforeEach(function () {
this.togglerModel = new Backbone.Model({
labels: ['foo', 'bar'],
active: false,
disabled: false,
isDisableable: true,
onChange: function () {}
});
this.view = new Toggler({
model: this.togglerModel
});
});
describe('.render', function () {
it('should render properly', function () {
this.view.render();
expect(this.view.$('.js-input').length).toBe(1);
expect(this.view.$('.js-input').prop('checked')).toBe(false);
expect(this.view.$('label').length).toBe(2);
});
it('should be active toggleModel.active is true', function () {
this.view.model.set('active', true, { silent: true });
this.view.render();
expect(this.view.$('.js-input').prop('checked')).toBe(true);
});
it('should be disabled if is disableable', function () {
this.view.model.set('disabled', true, { silent: true });
this.view.render();
expect(this.view.$('.js-input').prop('disabled')).toBe(true);
});
it('should not be disabled if is not disableable', function () {
this.view.model.set({
disabled: true,
isDisableable: false
}, { silent: true });
this.view.render();
expect(this.view.$('.js-input').prop('disabled')).toBe(false);
});
it('should render the correct labels', function () {
this.view.render();
expect(this.view.$('label')[0].innerHTML).toEqual('foo');
expect(this.view.$('label')[1].innerHTML).toEqual('bar');
});
});
describe('._initBinds', function () {
beforeEach(function () {
spyOn(this.view, 'render');
this.view._initBinds();
});
it('should re render on change:active', function () {
this.view.model.trigger('change:active');
expect(this.view.render).toHaveBeenCalled();
});
it('should re render on change:disabled', function () {
this.view.model.trigger('change:disabled');
expect(this.view.render).toHaveBeenCalled();
});
});
describe('._initViews', function () {
it('should add a tooltip if model has tooltip', function () {
this.togglerModel.set('tooltip', 'This is a tooltip');
this.view.render();
expect(_.size(this.view._subviews)).toBe(1);
});
});
describe('._onClick', function () {
it('should toggle the property active in the model', function () {
expect(this.view.model.get('active')).toBe(false);
this.view._onClick();
expect(this.view.model.get('active')).toBe(true);
this.view._onClick();
expect(this.view.model.get('active')).toBe(false);
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,275 @@
var Backbone = require('backbone');
var _ = require('underscore');
var UndoRedo = require('builder/components/undo-redo/undo-redo-view');
var EditorModel = require('builder/data/editor-model');
var StyleModel = require('builder/editor/style/style-definition-model');
describe('components/undo-redo/undo-redo-view', function () {
beforeEach(function () {
this.editorModel = new EditorModel({
edition: false
});
this.view = new UndoRedo({
editorModel: new EditorModel({
edition: false
}),
trackModel: new StyleModel()
});
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(this.view.$('button').length).toBe(2);
});
it('should render apply button properly', function () {
this.view._editorModel.set({ edition: true });
this.view.options.applyButton = true;
this.view.render();
expect(this.view.$('button').length).toBe(3);
});
it('should render clear button properly', function () {
var view = new UndoRedo({
applyButton: true,
clearButton: true,
clearModel: new Backbone.Model({
visible: true
}),
editorModel: new EditorModel({
edition: false
}),
trackModel: new StyleModel()
});
view._editorModel.set({ edition: true });
view.render();
expect(view.$('button').length).toBe(4);
});
it('should show the apply button as not loading by default', function () {
expect(this.view.$el.find('.js-apply.is-loading').length).toBe(0);
expect(this.view.$el.find('.js-apply.is-disabled').length).toBe(0);
});
it('should show the apply button as loading if applyStatusModel changes', function () {
var view = new UndoRedo({
applyButton: true,
applyStatusModel: new Backbone.Model({
loading: false
}),
editorModel: new EditorModel({
edition: true
}),
trackModel: new StyleModel()
});
view.render();
expect(view.$el.find('.js-apply.is-loading').length).toBe(0);
expect(view.$el.find('.js-apply.is-disabled').length).toBe(0);
view._applyStatusModel.set('loading', true);
expect(view.$el.find('.js-apply.is-loading').length).toBe(1);
expect(view.$el.find('.js-apply.is-disabled').length).toBe(1);
});
});
describe('when .canUndo and .canRedo are true', function () {
beforeEach(function () {
spyOn(this.view, '_canUndo').and.returnValue(true);
spyOn(this.view, '_canRedo').and.returnValue(true);
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(this.view.$('button').length).toBe(2);
expect(_.size(this.view._subviews)).toBe(2); // [undoTooltip, redoTooltip]
});
});
});
describe('.initBinds', function () {
it('should call ._checkButtonsStyle when _editorModel:edition changes', function () {
spyOn(this.view, '_checkButtonsStyle');
this.view._initBinds();
this.view._editorModel.trigger('change:edition');
expect(this.view._checkButtonsStyle).toHaveBeenCalled();
});
it('should call .render when _trackModel triggers unredoChanged', function () {
spyOn(this.view, 'render');
this.view._initBinds();
this.view._trackModel.trigger('unredoChanged');
expect(this.view.render).toHaveBeenCalled();
});
it('should call .render when _trackModel triggers undo', function () {
spyOn(this.view, 'render');
this.view._initBinds();
this.view._trackModel.trigger('undo');
expect(this.view.render).toHaveBeenCalled();
});
it('should call .render when _trackModel triggers redo', function () {
spyOn(this.view, 'render');
this.view._initBinds();
this.view._trackModel.trigger('redo');
expect(this.view.render).toHaveBeenCalled();
});
it('should call .render when _clearModel changes', function () {
this.view._clearModel = new Backbone.Model();
spyOn(this.view, 'render');
this.view._initBinds();
this.view._clearModel.trigger('change');
expect(this.view.render).toHaveBeenCalled();
});
it('should call ._applyStatusChanged when _applyStatusModel:loading changes', function () {
this.view._applyStatusModel = new Backbone.Model();
spyOn(this.view, '_applyStatusChanged');
this.view._initBinds();
this.view._applyStatusModel.trigger('change:loading');
expect(this.view._applyStatusChanged).toHaveBeenCalled();
});
});
describe('._isDisabled', function () {
it('should be true if overlay model is visible', function () {
this.view._overlayModel = new Backbone.Model({ visible: false });
expect(this.view._isDisabled()).toBe(false);
this.view._overlayModel.set({ visible: true });
expect(this.view._isDisabled()).toBe(true);
});
});
describe('._canRedo', function () {
it('should return true if _trackModel.canRedo and is not disabled', function () {
spyOn(this.view._trackModel, 'canRedo').and.returnValue(true);
spyOn(this.view, '_isDisabled').and.returnValue(false);
expect(this.view._canRedo()).toBe(true);
});
});
describe('._canUndo', function () {
it('should return true if _trackModel.canRedo and is not disabled', function () {
spyOn(this.view._trackModel, 'canUndo').and.returnValue(true);
spyOn(this.view, '_isDisabled').and.returnValue(false);
expect(this.view._canUndo()).toBe(true);
});
});
describe('._onUndoClick', function () {
it('should call _trackModel.undo if _trackModel.canUndo is true', function () {
spyOn(this.view._trackModel, 'undo');
var canUndoSpy = spyOn(this.view._trackModel, 'canUndo');
canUndoSpy.and.returnValue(false);
this.view._onUndoClick();
expect(this.view._trackModel.undo).not.toHaveBeenCalled();
canUndoSpy.and.returnValue(true);
this.view._onUndoClick();
expect(this.view._trackModel.undo).toHaveBeenCalled();
});
});
describe('._onRedoClick', function () {
it('should call _trackModel.undo if _trackModel.canRedo is true', function () {
spyOn(this.view._trackModel, 'redo');
var canRedoSpy = spyOn(this.view._trackModel, 'canRedo');
canRedoSpy.and.returnValue(false);
this.view._onRedoClick();
expect(this.view._trackModel.redo).not.toHaveBeenCalled();
canRedoSpy.and.returnValue(true);
this.view._onRedoClick();
expect(this.view._trackModel.redo).toHaveBeenCalled();
});
});
describe('._onApplyClick', function () {
it('should call options.onApplyClick method if applyButton is true and button is not disabled', function () {
var view = new UndoRedo({
editorModel: new EditorModel({
edition: false
}),
trackModel: new StyleModel(),
onApplyClick: jasmine.createSpy('onApplyClick'),
applyButton: true
});
var isDisabled = spyOn(view, '_isDisabled');
isDisabled.and.returnValue(true);
view._onApplyClick();
expect(view.options.onApplyClick).not.toHaveBeenCalled();
isDisabled.and.returnValue(false);
view._onApplyClick();
expect(view.options.onApplyClick).toHaveBeenCalled();
});
});
describe('._onClearClick', function () {
it('should call options._onClearClick method if clearButton is true', function () {
var view = new UndoRedo({
editorModel: new EditorModel({
edition: false
}),
trackModel: new StyleModel(),
onClearClick: jasmine.createSpy('onClearClick'),
clearButton: true
});
view._onClearClick();
expect(view.options.onClearClick).toHaveBeenCalled();
});
});
describe('._applyStatusChanged', function () {
it('should call render', function () {
spyOn(this.view, 'render');
this.view._applyStatusChanged();
expect(this.view.render).toHaveBeenCalled();
});
});
describe('._checkButtonsStyle', function () {
it('should apply u-actionTextColor class if not editing', function () {
expect(this.view._getIcons().hasClass('u-actionTextColor')).toBe(true);
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,54 @@
var CoreView = require('backbone/core-view');
var PanelWithOptions = require('builder/components/view-options/panel-with-options-view.js');
var EditorModel = require('builder/data/editor-model');
describe('components/view-options/panel-with-options-view', function () {
beforeEach(function () {
var Dummy = CoreView.extend({
render: function () {
this.$el.html(this.options.content);
return this;
}
});
this.view = new PanelWithOptions({
editorModel: new EditorModel(),
createContentView: function () {
return new Dummy({
content: 'Content'
});
},
createControlView: function () {
return new Dummy({
content: 'Toogle'
});
},
createActionView: function () {
return new Dummy({
content: 'Undo'
});
}
});
this.view.render();
});
it('should render properly and have `js-optionsBar` selector for onboarding', function () {
expect(this.view.$('.js-content').length).toBe(1);
expect(this.view.$('.Options-bar.js-optionsBar').length).toBe(1);
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should switch views properly', function () {
expect(this.view.$('.js-content').html()).toContain('Content');
expect(this.view.$('.js-actions').html()).toContain('Undo');
expect(this.view.$('.js-controls').html()).toContain('Toogle');
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,121 @@
var Backbone = require('backbone');
var _ = require('underscore');
var Header = require('builder/editor/editor-header.js');
var EditorModel = require('builder/data/editor-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var ConfigModel = require('builder/data/config-model');
var PrivacyCollection = require('builder/components/modals/publish/privacy-collection');
var UserModel = require('builder/data/user-model');
describe('editor/editor-header', function () {
var clickPrivacyAction;
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({
actions: {
private_maps: true,
private_tables: true
}
}, { configModel: configModel });
var privacyCollection = new PrivacyCollection([{
privacy: 'PUBLIC',
title: 'Public',
desc: 'Lorem ipsum',
cssClass: 'is-green',
selected: true
}, {
privacy: 'LINK',
title: 'Link',
desc: 'Yabadababa',
cssClass: 'is-orange'
}, {
privacy: 'PASSWORD',
title: 'Password',
desc: 'Wadus'
}, {
privacy: 'PRIVATE',
title: 'Private',
desc: 'Funínculo',
cssClass: 'is-red'
}]);
var visDefinitionModel = new VisDefinitionModel({
name: 'My super fun vis',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived',
permission: {}
}, {
configModel: configModel
});
var editorModel = new EditorModel();
var mapcapsCollection = new Backbone.Collection([{
created_at: '2016-06-21T15:30:06+00:00'
}]);
var modals = jasmine.createSpyObj('modals', ['create', 'isOpen']);
clickPrivacyAction = jasmine.createSpy('clickPrivacyAction');
var onRemoveMap = jasmine.createSpy('onRemoveMap');
spyOn(Header.prototype, '_duplicateMap');
this.view = new Header({
editorModel: editorModel,
mapcapsCollection: mapcapsCollection,
modals: modals,
visDefinitionModel: visDefinitionModel,
privacyCollection: privacyCollection,
clickPrivacyAction: clickPrivacyAction,
onRemoveMap: onRemoveMap,
configModel: configModel,
userModel: userModel
});
this.view.render();
});
it('should render properly', function () {
expect(this.view.$('.js-toggle-menu').length).toBe(1);
expect(this.view.$('.js-toggle').length).toBe(1);
expect(this.view.$('.Editor-HeaderInfo-title').html()).toContain('My super fun vis');
expect(this.view.$('.js-toggle').html()).toContain('PUBLIC');
expect(_.size(this.view._subviews)).toBe(4); // [InlineEditorView, ShareWith, ContextMenuFactory, TipsyTooltipView]
});
it('should call duplicate when duplicate option is clicked', function () {
this.view.$('.js-toggle-menu').click();
this.view._contextMenuFactory.getContextMenu().$('[data-val="duplicate-map"]').click();
expect(Header.prototype._duplicateMap).toHaveBeenCalled();
});
it('should open privacy dropdown', function () {
this.view.$('.js-toggle').click();
expect(this.view.$('.Privacy-dialog').length).toBe(1);
});
it('should open privacy modal if belongs to an organization', function () {
spyOn(this.view._userModel, 'isInsideOrg').and.returnValue(true);
this.view.render();
this.view.$('.Share-with').click();
expect(clickPrivacyAction).toHaveBeenCalled();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,375 @@
var _ = require('underscore');
var Backbone = require('backbone');
var UserModel = require('builder/data/user-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var ModalsService = require('builder/components/modals/modals-service-model');
var OnboardingsServiceModel = require('builder/components/onboardings/onboardings-service-model');
var EditorMapView = require('builder/editor/editor-map-view');
var EditorModel = require('builder/data/editor-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var Notifier = require('builder/components/notifier/notifier');
var AppNotifications = require('builder/app-notifications');
var ConfigModel = require('builder/data/config-model');
var PrivacyCollection = require('builder/components/modals/publish/privacy-collection');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var MapModeModel = require('builder/map-mode-model');
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var Router = require('builder/routes/router');
describe('editor/editor-map-view', function () {
beforeEach(function () {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
.andReturn({ status: 200 });
spyOn(Router, 'navigate');
var configModel = new ConfigModel({
sql_api_template: 'http://{user}.localhost.lan:8080',
user_name: 'pepito'
});
var userModel = new UserModel({
limits: {
max_layers: 8
},
actions: {
private_maps: true,
private_tables: true
}
}, {
configModel: configModel
});
var privacyCollection = new PrivacyCollection([{
privacy: 'PUBLIC',
title: 'Public',
desc: 'Lorem ipsum',
cssClass: 'is-green',
selected: true
}, {
privacy: 'LINK',
title: 'Link',
desc: 'Yabadababa',
cssClass: 'is-orange'
}, {
privacy: 'PASSWORD',
title: 'Password',
desc: 'Wadus'
}, {
privacy: 'PRIVATE',
title: 'Private',
desc: 'Funínculo',
cssClass: 'is-red'
}]);
var basemapLayerDefModel = new LayerDefinitionModel({
type: 'Tiled',
name: 'Basemap is always first',
urlTemplate: 'http://{s}.example.com/{z}/{x}/{y}.png'
}, {
configModel: configModel
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
type: 'CartoDB',
table_name: 'foobar'
}, {
configModel: configModel
});
this.layerDefinitionModel.styleModel = {
isAnimation: function () {
return false;
}
};
this.nodeDefModel = new AnalysisDefinitionNodeModel({
id: 'a2',
type: 'buffer',
params: {}
}, {
configModel: configModel,
userModel: {}
});
spyOn(this.layerDefinitionModel, 'getNumberOfAnalyses').and.returnValue(3);
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: {}
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection([
this.layerDefinitionModel,
basemapLayerDefModel
], {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'map-123',
stateDefinitionModel: {}
});
var visDefinitionModel = new VisDefinitionModel({
name: 'My super fun vis',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived',
permission: {}
}, {
configModel: configModel
});
var editorModel = new EditorModel();
this.modals = new ModalsService();
this.onboardings = new OnboardingsServiceModel();
var mapcapsCollection = new Backbone.Collection([{
created_at: '2016-06-21T15:30:06+00:00'
}]);
var widgetDefinitionsCollection = new Backbone.Collection();
widgetDefinitionsCollection.add({ id: 'widget-id' });
widgetDefinitionsCollection.widgetsOwnedByLayer = function () { return 0; };
this.mapModeModel = new MapModeModel();
this.view = new EditorMapView({
mapDefinitionModel: new Backbone.Model(),
visDefinitionModel: visDefinitionModel,
privacyCollection: privacyCollection,
mapcapsCollection: mapcapsCollection,
modals: this.modals,
onboardings: this.onboardings,
basemaps: {},
userActions: {},
analysis: {},
vis: {},
configModel: configModel,
userModel: userModel,
editorModel: editorModel,
pollingModel: new Backbone.Model(),
analysisDefinitionNodesCollection: new Backbone.Collection(),
layerDefinitionsCollection: this.layerDefinitionsCollection,
widgetDefinitionsCollection: widgetDefinitionsCollection,
legendDefinitionsCollection: new Backbone.Collection(),
mapModeModel: this.mapModeModel,
stateDefinitionModel: new Backbone.Model(),
onboardingNotification: {},
settingsCollection: new Backbone.Collection(),
routeModel: new Backbone.Model({ currentRoute: '' })
});
Notifier.init({
editorModel: editorModel
});
AppNotifications.init();
this.view.render();
});
afterEach(function () {
Notifier.off();
AppNotifications.off();
jasmine.Ajax.uninstall();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should have two subview', function () {
// Notifier is new subview
expect(_.size(this.view._subviews)).toBe(2);
});
it('should render correctly', function () {
expect(this.view.$el.text()).toContain('My super fun vis');
expect(this.view.$el.text()).toContain('editor.tab-pane.layers.title-label');
expect(this.view.$el.text()).toContain('editor.tab-pane.widgets.title-label');
});
describe('._onAddAnalysisClicked', function () {
it('should add Analysis', function () {
spyOn(AnalysesService, 'addAnalysis');
this.view._onAddAnalysisClicked();
expect(AnalysesService.addAnalysis).toHaveBeenCalled();
});
});
describe('mapModeModel changes', function () {
beforeEach(function () {
var layerDefModel = this.layerDefinitionModel;
spyOn(this.view._stackLayoutView.model, 'goBack');
spyOn(this.view._stackLayoutView.model, 'goToStep');
spyOn(this.mapModeModel, 'getFeatureDefinition').and.callFake(function () {
return {
getLayerDefinition: function () {
return layerDefModel;
},
isNew: function () {
return true;
}
};
});
});
});
describe('.handleRoute', function () {
it('should call ._handleRoute', function () {
spyOn(this.view, '_handleRoute');
this.view.handleRoute();
expect(this.view._handleRoute).toHaveBeenCalled();
});
});
describe('._handleRoute', function () {
beforeEach(function () {
spyOn(this.view._stackLayoutView.model, 'goToStep');
});
describe('should switch to the correct stacklayout step', function () {
// Jasmine breaks printing layerDefinitionModel, so extract the layer id from the call list
function extractLayerTabArgs (spy) {
var lastCall = spy.calls.mostRecent();
return lastCall.args.map(function (arg) {
if (arg && arg.id !== undefined) {
return arg.id;
}
return arg;
});
}
function generateTabSpec (tabNameRoute, tabNameStack) {
return function () {
var routeModel = new Backbone.Model({
currentRoute: [tabNameRoute, 'l-1']
});
this.view._handleRoute(routeModel);
var expected = extractLayerTabArgs(this.view._stackLayoutView.model.goToStep);
expect(expected).toEqual([1, 'l-1', 'layer-content', tabNameStack]);
};
}
it('for basemap', function () {
var routeModel = new Backbone.Model({
currentRoute: ['basemap']
});
this.view._handleRoute(routeModel);
expect(this.view._stackLayoutView.model.goToStep).toHaveBeenCalledWith(1, null, 'basemaps');
});
it('for layers', function () {
var routeModel = new Backbone.Model({
currentRoute: ['layers']
});
this.view._handleRoute(routeModel);
expect(this.view._stackLayoutView.model.goToStep).toHaveBeenCalledWith(0, 'layers');
});
it('for widget when not found', function () {
spyOn(Router, 'goToLayerList');
var routeModel = new Backbone.Model({
currentRoute: ['layer_', 'invalid-layer-id']
});
this.view._handleRoute(routeModel);
expect(Router.goToLayerList).toHaveBeenCalled();
});
it('for widgets', function () {
var routeModel = new Backbone.Model({
currentRoute: ['widgets']
});
this.view._handleRoute(routeModel);
expect(this.view._stackLayoutView.model.goToStep).toHaveBeenCalledWith(0, 'widgets');
});
it('for widget', function () {
var routeModel = new Backbone.Model({
currentRoute: ['widget', 'widget-id']
});
this.view._handleRoute(routeModel);
var expected = extractLayerTabArgs(this.view._stackLayoutView.model.goToStep);
expect(expected).toEqual([1, 'widget-id', 'widget-content']);
});
it('for widget when not found', function () {
spyOn(Router, 'goToWidgetList');
var routeModel = new Backbone.Model({
currentRoute: ['widget', 'invalid-widget-id']
});
this.view._handleRoute(routeModel);
expect(Router.goToWidgetList).toHaveBeenCalled();
});
it('for feature edition', function () {
this.mapModeModel._featureDefinition = {
isNew: function () { return true; }
};
var routeModel = new Backbone.Model({
currentRoute: ['edit_feature', 'l-1']
});
this.view._handleRoute(routeModel);
var lastCallArgs = extractLayerTabArgs(this.view._stackLayoutView.model.goToStep);
expect(lastCallArgs).toEqual([2, 'l-1', false]);
});
it('for layer data tab', generateTabSpec('layer_data', 'data'));
it('for layer style tab', generateTabSpec('layer_style', 'style'));
it('for layer legend tab', generateTabSpec('layer_legends', 'legends'));
it('for layer legend tab', generateTabSpec('layer_popups', 'popups'));
describe('for layer analyses tab', function () {
it('without analysis id', function () {
var routeModel = new Backbone.Model({
currentRoute: ['layer_analyses', 'l-1']
});
this.view._handleRoute(routeModel);
var expected = extractLayerTabArgs(this.view._stackLayoutView.model.goToStep);
expect(expected).toEqual([1, 'l-1', 'layer-content', 'analyses', undefined]);
});
it('with analysis id', function () {
var routeModel = new Backbone.Model({
currentRoute: ['layer_analyses', 'l-1', 'c1']
});
this.view._handleRoute(routeModel);
var expected = extractLayerTabArgs(this.view._stackLayoutView.model.goToStep);
expect(expected).toEqual([1, 'l-1', 'layer-content', 'analyses', 'c1']);
});
});
});
});
});
@@ -0,0 +1,347 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ModalsService = require('builder/components/modals/modals-service-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var EditorPane = require('builder/editor/editor-pane');
var EditorModel = require('builder/data/editor-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var PrivacyCollection = require('builder/components/modals/publish/privacy-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var QueryRowsCollection = require('builder/data/query-rows-collection');
var AppNotifications = require('builder/app-notifications');
describe('editor/editor-pane', function () {
var analysisDefinitionNodeModel;
var handleRouteSpy;
beforeEach(function () {
jasmine.Ajax.install();
jasmine.clock().install();
jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
.andReturn({
status: 200,
responseText: '{"rows":[{},{},{}]}'
});
this.configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe'
});
AppNotifications.init();
this.userModel = new UserModel({
limits: {
max_layers: 2
},
actions: {
private_maps: true,
private_tables: true
}
}, {
configModel: this.configModel
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
options: {
type: 'CartoDB',
table_name: 'foo',
source: 'a0'
}
}, {
parse: true,
configModel: this.configModel,
stateDefinitionModel: {}
});
var querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM foo',
status: 'fetched'
}, {
configModel: this.configModel
});
var queryGeometryModel = new QueryGeometryModel({
status: 'fetched',
simple_geom: '',
query: 'SELECT * FROM foo'
}, {
configModel: this.configModel
});
var queryRowsCollection = new QueryRowsCollection([], {
configModel: this.configModel,
querySchemaModel: querySchemaModel
});
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
analysisDefinitionNodeModel = analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
table_name: 'foo',
params: {
query: 'SELECT * FROM foo'
}
});
analysisDefinitionNodeModel.querySchemaModel = querySchemaModel;
analysisDefinitionNodeModel.querySchemaModel.hasGeometryData = function () {
return true;
};
analysisDefinitionNodeModel.queryGeometryModel = queryGeometryModel;
analysisDefinitionNodeModel.queryRowsCollection = queryRowsCollection;
spyOn(this.layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(analysisDefinitionNodeModel);
this.privacyCollection = new PrivacyCollection([{
privacy: 'PUBLIC',
title: 'Public',
desc: 'Lorem ipsum',
cssClass: 'is-green',
selected: true
}, {
privacy: 'LINK',
title: 'Link',
desc: 'Yabadababa',
cssClass: 'is-orange'
}, {
privacy: 'PASSWORD',
title: 'Password',
desc: 'Wadus'
}, {
privacy: 'PRIVATE',
title: 'Private',
desc: 'Funínculo',
cssClass: 'is-red'
}]);
this.widgetDefinitionsCollection = new Backbone.Collection();
this.visDefinitionModel = new VisDefinitionModel({
name: 'Foo Map',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived',
permission: {}
}, {
configModel: this.configModel
});
this.mapStackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['prevStep', 'nextStep', 'goToStep']);
this.modals = new ModalsService();
this.mapcapsCollection = new Backbone.Collection([{
created_at: '2016-06-21T15:30:06+00:00'
}]);
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: {}
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection([
this.layerDefinitionModel
], {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'map-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.loadAllQueryGeometryModels = function (callback) {
callback();
};
spyOn(this.layerDefinitionsCollection, 'isThereAnyGeometryData').and.returnValue(Promise.resolve(true));
this.onboardingNotification = {
getKey: function () { return false; }
};
handleRouteSpy = spyOn(EditorPane.prototype, '_handleRoute');
this.view = new EditorPane({
visDefinitionModel: this.visDefinitionModel,
modals: this.modals,
privacyCollection: this.privacyCollection,
mapcapsCollection: this.mapcapsCollection,
analysis: {},
userActions: {},
userModel: this.userModel,
editorModel: new EditorModel(),
pollingModel: new Backbone.Model(),
configModel: this.configModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection,
widgetDefinitionsCollection: this.widgetDefinitionsCollection,
mapStackLayoutModel: this.mapStackLayoutModel,
selectedTabItem: 'widgets',
stateDefinitionModel: {},
onboardingNotification: this.onboardingNotification,
routeModel: new Backbone.Model()
});
this.view.render();
});
afterEach(function () {
AppNotifications.off();
jasmine.clock().uninstall();
jasmine.Ajax.uninstall();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render correctly', function () {
expect(this.view.$el.text()).toContain('Foo Map');
expect(this.view.$el.text()).toContain('editor.tab-pane.layers.title-label');
expect(this.view.$el.text()).toContain('editor.tab-pane.widgets.title-label');
expect(this.view.$el.text()).toContain('editor.button_publish');
});
it('should create PanelWithOptionsView with selector for onboarding', function () {
var contentView = this.view._mapTabPaneView.collection.at(0).attributes.createContentView();
expect(contentView.className).toEqual('Editor-content js-editorPanelContent');
});
it('should have two subviews', function () {
expect(_.size(this.view._subviews)).toBe(2); // [Header, TabPane]
});
describe('._onLayerAdd', function () {
it('should fetch visualization privacy if a new layer is added', function () {
var layerDefModel2 = new LayerDefinitionModel({
id: 'l-101',
kind: 'carto'
}, {
parse: true,
configModel: this.configModel,
stateDefinitionModel: {}
});
layerDefModel2.getAnalysisDefinitionNodeModel = function () {
return analysisDefinitionNodeModel;
};
spyOn(this.view._visDefinitionModel, 'fetch');
this.view._layerDefinitionsCollection.add(layerDefModel2);
jasmine.clock().tick(10);
expect(this.view._visDefinitionModel.fetch).toHaveBeenCalled();
});
it('should call zoomToData if origin is table', function () {
var layerDefModel2 = new LayerDefinitionModel({
id: 'l-101',
kind: 'carto'
}, {
parse: true,
configModel: this.configModel,
stateDefinitionModel: {}
});
layerDefModel2.getAnalysisDefinitionNodeModel = function () {
return analysisDefinitionNodeModel;
};
spyOn(this.view, '_zoomToData');
this.view._layerDefinitionsCollection.add(layerDefModel2, { origin: 'table' });
jasmine.clock().tick(10);
expect(this.view._zoomToData).toHaveBeenCalled();
});
});
describe('max layers', function () {
var otherLayer = new LayerDefinitionModel({
id: 'l-2',
options: {
type: 'torque',
table_name: 'fee',
source: 'a1'
}
}, {
parse: true,
configModel: new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe'
}),
stateDefinitionModel: {}
});
otherLayer.getAnalysisDefinitionNodeModel = function () {
return analysisDefinitionNodeModel;
};
afterEach(function () {
this.view._layerDefinitionsCollection.remove(otherLayer);
});
it('should have no infobox state by default', function () {
expect(this.view._infoboxModel.get('state')).toBe(null);
});
it('should show the max-layers infobox when max layer are reached', function () {
this.view._layerDefinitionsCollection.add(otherLayer);
jasmine.clock().tick(10);
expect(this.view._infoboxModel.get('state')).not.toBe(null);
});
it('should remove the max-layers infobox when max layer is not reached', function () {
this.view._layerDefinitionsCollection.add(otherLayer);
jasmine.clock().tick(10);
this.view._layerDefinitionsCollection.remove(otherLayer);
expect(this.view._infoboxModel.get('state')).toBe(null);
});
it('should show the limit infobox if there is a limit notification', function () {
AppNotifications.addNotification({ type: 'limit' });
this.view.render();
expect(this.view._infoboxModel.get('state')).toBe('limit');
expect(this.view.$('.Infobox').length).toBe(1);
expect(this.view.$('.Infobox').html()).toContain('editor.messages.limit');
});
});
describe('._initBinds', function () {
it('should call _handleRoute when _routeModel:currentRoute changes', function () {
this.view._routeModel.set({ currentRoute: 'widgets' });
expect(this.view._handleRoute).toHaveBeenCalled();
});
});
describe('._handleRoute', function () {
it('should set the correct tab pane', function () {
handleRouteSpy.and.callThrough();
spyOn(this.view._mapTabPaneView, 'setSelectedTabPaneByName');
var routeModel = new Backbone.Model({
currentRoute: ['layers']
});
this.view._handleRoute(routeModel);
expect(this.view._mapTabPaneView.setSelectedTabPaneByName).toHaveBeenCalledWith('layers');
routeModel.set({ currentRoute: ['widgets'] }, { silent: true });
this.view._handleRoute(routeModel);
expect(this.view._mapTabPaneView.setSelectedTabPaneByName).toHaveBeenCalledWith('widgets');
});
});
});
@@ -0,0 +1,112 @@
var Backbone = require('backbone');
var EditorSettingsPane = require('builder/editor/editor-settings-pane');
var ModalsService = require('builder/components/modals/modals-service-model');
var PrivacyCollection = require('builder/components/modals/publish/privacy-collection');
var ConfigModel = require('builder/data/config-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var UserModel = require('builder/data/user-model');
describe('editor/editor-settings-pane', function () {
var view;
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var privacyCollection = new PrivacyCollection([{
privacy: 'PUBLIC',
title: 'Public',
desc: 'Lorem ipsum',
cssClass: 'is-green',
selected: true
}, {
privacy: 'LINK',
title: 'Link',
desc: 'Yabadababa',
cssClass: 'is-orange'
}, {
privacy: 'PASSWORD',
title: 'Password',
desc: 'Wadus'
}, {
privacy: 'PRIVATE',
title: 'Private',
desc: 'Funínculo',
cssClass: 'is-red'
}]);
var mapcapsCollection = new Backbone.Collection([{
created_at: '2016-06-21T15:30:06+00:00'
}]);
var settingsCollection = new Backbone.Collection([{
setting: 'title',
label: _t('editor.settings.options.title'),
enabled: true,
default: false,
enabler: false
}, {
setting: 'description',
label: _t('editor.settings.options.description'),
enabled: true,
default: false,
enabler: false
}, {
setting: 'search',
label: _t('editor.settings.options.search'),
enabled: true,
default: false,
enabler: true
}, {
setting: 'zoom',
label: _t('editor.settings.options.zoom'),
enabled: true,
default: true,
enabler: true
}]);
var visDefinitionModel = new VisDefinitionModel({
name: 'My super fun vis',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived',
permission: {}
}, {
configModel: configModel
});
var editorModel = new Backbone.Model();
editorModel.isEditing = function () {
return false;
};
var userModel = new UserModel({
actions: {
private_maps: true,
private_tables: true
}
}, { configModel: configModel });
var overlaysCollection = new Backbone.Collection();
overlaysCollection.hasFetched = function () { return true; };
view = new EditorSettingsPane({
className: 'Editor-content',
configModel: configModel,
editorModel: editorModel,
mapcapsCollection: mapcapsCollection,
mapDefinitionModel: new Backbone.Model(),
modals: new ModalsService(),
overlaysCollection: overlaysCollection,
privacyCollection: privacyCollection,
settingsCollection: settingsCollection,
userModel: userModel,
visDefinitionModel: visDefinitionModel
});
});
it('should be able to check if the user is the owner', function () {
expect(view._isOwner()).toEqual(true);
});
});
@@ -0,0 +1,150 @@
var Backbone = require('backbone');
var EditorSettingsView = require('builder/editor/editor-settings-view');
var ModalsService = require('builder/components/modals/modals-service-model');
var PrivacyCollection = require('builder/components/modals/publish/privacy-collection');
var ConfigModel = require('builder/data/config-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var UserModel = require('builder/data/user-model');
var Notifier = require('builder/components/notifier/notifier');
describe('editor/editor-settings-view', function () {
var view;
var overlaysCollection;
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var mapView = new Backbone.View();
window.mapView = mapView;
var vis = new Backbone.View();
window.vis = vis;
vis.map = new Backbone.Model();
var collection = new Backbone.Collection([{
setting: 'title',
label: _t('editor.settings.options.title'),
enabled: true,
default: false,
enabler: false
}, {
setting: 'description',
label: _t('editor.settings.options.description'),
enabled: true,
default: false,
enabler: false
}, {
setting: 'search',
label: _t('editor.settings.options.search'),
enabled: true,
default: false,
enabler: true
}, {
setting: 'zoom',
label: _t('editor.settings.options.zoom'),
enabled: true,
default: true,
enabler: true
}]);
var privacyCollection = new PrivacyCollection([{
privacy: 'PUBLIC',
title: 'Public',
desc: 'Lorem ipsum',
cssClass: 'is-green',
selected: true
}, {
privacy: 'LINK',
title: 'Link',
desc: 'Yabadababa',
cssClass: 'is-orange'
}, {
privacy: 'PASSWORD',
title: 'Password',
desc: 'Wadus'
}, {
privacy: 'PRIVATE',
title: 'Private',
desc: 'Funínculo',
cssClass: 'is-red'
}]);
var mapcapsCollection = new Backbone.Collection([{
created_at: '2016-06-21T15:30:06+00:00'
}]);
var visDefinitionModel = new VisDefinitionModel({
name: 'My super fun vis',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived',
permission: {}
}, {
configModel: configModel
});
var userModel = new UserModel({
actions: {
private_maps: true,
private_tables: true
}
}, { configModel: configModel });
var editorModel = new Backbone.Model();
editorModel.isEditing = function () {
return false;
};
Notifier.init({
editorModel: editorModel,
visDefinitionModel: visDefinitionModel
});
overlaysCollection = new Backbone.Collection();
overlaysCollection.hasFetched = function () { return true; };
view = new EditorSettingsView({
mapView: mapView,
vis: vis,
modals: new ModalsService(),
privacyCollection: privacyCollection,
mapcapsCollection: mapcapsCollection,
overlaysCollection: overlaysCollection,
editorModel: editorModel,
mapDefinitionModel: new Backbone.Model(),
visDefinitionModel: visDefinitionModel,
settingsCollection: collection,
configModel: configModel,
userModel: userModel,
stateDefinitionModel: {}
});
view.render();
});
afterEach(function () {
Notifier.off();
view.remove();
});
it('should render properly', function () {
expect(view.$('.CDB-Checkbox').length).toBe(4);
expect(view.$('.CDB-Checkbox:checked').length).toBe(2);
});
it('should render loading properly', function () {
spyOn(overlaysCollection, 'hasFetched').and.returnValue(false);
view.render();
expect(view.$('.FormPlaceholder-paragraph').length).toBe(1);
expect(view.$('.FormPlaceholder-step').length).toBe(1);
expect(view.$('.FormPlaceholder-inner').length).toBe(1);
});
it('should have no leaks', function () {
expect(view).toHaveNoLeaks();
});
});
@@ -0,0 +1,43 @@
var $ = require('jquery');
var AnalysisTooltip = require('builder/editor/layers/analysis-views/analyses-tooltip-error');
describe('editor/layers/analysis-views/analysis-tooltip-error', function () {
beforeEach(function () {
this.view = $('<div class="view"><div class="Error" style="width:10px; height: 10px; background: red; "></div></div>');
$('body').append(this.view);
this.analysisNode = {
get: function (what) {
if (what === 'status') return 'failed';
if (what === 'error') return false;
if (what === 'id') return 'a1';
}
};
this._analysisTooltip = new AnalysisTooltip({
analysisNode: this.analysisNode,
element: this.view,
triggerSelector: '.Error'
});
});
it('should not have any leaks', function () {
expect(this._analysisTooltip).toHaveNoLeaks();
});
it('should create the tooltip on mouseover', function () {
this.view.find('.Error').trigger('mouseover');
expect(this._analysisTooltip.tooltip).toBeDefined();
});
it('should destroy the tooltip on mouseout', function () {
this.view.find('.Error').trigger('mouseover');
expect(this._analysisTooltip.tooltip).toBeDefined();
this.view.find('.Error').trigger('mouseout');
expect(this._analysisTooltip.tooltip).not.toBeDefined();
});
afterEach(function () {
$('.view').remove();
});
});
@@ -0,0 +1,161 @@
var ConfigModel = require('builder/data/config-model');
var CompositeLayerAnalysisView = require('builder/editor/layers/analysis-views/composite-layer-analysis-view');
var UserModel = require('builder/data/user-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
describe('editor/layers/analysis-views/composite-layer-analysis-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.analysisDefinitionNodesCollection.add({
id: 'a1',
type: 'intersection',
primary_source_name: 'target',
status: 'ready',
params: {
source: {
id: 'b0', // ref, belongs to another layer
type: 'source',
params: {
query: 'SELECT * FROM second'
},
options: {
table_name: 'second'
}
},
target: {
id: 'a0',
type: 'source',
params: {
query: 'SELECT * FROM first'
},
options: {
table_name: 'first'
}
}
}
});
var model = this.analysisDefinitionNodesCollection.get('a1');
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'map-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add({
id: 'l1',
kind: 'carto',
options: {
table_name: 'first',
cartocss: 'asd'
}
});
this.layerDefinitionsCollection.add({
id: 'l2',
kind: 'carto',
options: {
table_name: 'second',
cartocss: 'asd'
}
});
this.layerDefinitionModel = this.layerDefinitionsCollection.at(0);
this.layerDefinitionModel.set({ source: model.id });
this.view = new CompositeLayerAnalysisView({
model: model,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionModel: this.layerDefinitionModel
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
describe('when secondary source is a source node belonging to another layer', function () {
beforeEach(function () {
this.view.render();
});
it('should render the own source', function () {
expect(this.view.$('.js-primary-source').text()).toContain('a1');
expect(this.view.$('.js-primary-source').text()).toContain('intersection');
});
it('should render the secondary source as a reference to the other layer', function () {
expect(this.view.$('.js-secondary-source').text()).toContain('second');
});
it('should render the id', function () {
expect(this.view.$('.js-secondary-source').text()).toContain('b0');
});
});
describe('when secondary source is a (non-source) node belonging to another layer ', function () {
beforeEach(function () {
this.analysisDefinitionNodesCollection.add({
id: 'b1',
type: 'buffer',
status: 'ready',
params: {
radius: 100,
source: this.analysisDefinitionNodesCollection.get('b0').toJSON()
}
});
this.analysisDefinitionNodesCollection.get('a1').set('source', 'b1');
this.view.render();
});
it('should render the own source', function () {
expect(this.view.$('.js-primary-source').text()).toContain('a1');
expect(this.view.$('.js-primary-source').text()).toContain('intersection');
});
it('should render the secondary source as a reference to the other layer', function () {
expect(this.view.$('.js-secondary-source').text()).toContain('area-of-influence');
});
it('should render the id', function () {
expect(this.view.$('.js-secondary-source').text()).toContain('b1');
});
});
describe('when secondary source is a source node belonging to own layer', function () {
beforeEach(function () {
this.analysisDefinitionNodesCollection.get('b0').set('id', 'own');
this.analysisDefinitionNodesCollection.get('a1').set('source', 'own');
this.view.render();
});
it('should render the own source', function () {
expect(this.view.$('.js-primary-source').text()).toContain('a1');
expect(this.view.$('.js-primary-source').text()).toContain('intersection');
});
it('should render the secondary source as a source but without the id', function () {
expect(this.view.$('.js-secondary-source').text()).toContain('second');
});
it('should not render the id', function () {
expect(this.view.$('.js-secondary-source').text()).not.toContain('own');
});
});
});
@@ -0,0 +1,132 @@
var Backbone = require('backbone');
var _ = require('underscore');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var DefaultLayerAnalysisView = require('builder/editor/layers/analysis-views/default-layer-analysis-view');
var $ = require('jquery');
describe('editor/layers/analysis-views/default-layers-analysis-view', function () {
var $el;
beforeEach(function () {
$el = $('<div class="js-layer"><div class="js-Editor-ListLayer-titleText"></div></div>');
this.model = new AnalysisDefinitionNodeModel({
id: 'a3',
type: 'trade-area',
kind: 'walk',
time: 300,
source: 'a2'
}, {
configModel: {},
sqlAPI: {}
});
this.analysisNode = new Backbone.Model();
this.layerDefinitionModel = new Backbone.Model({id: 'l-1'});
this.view = new DefaultLayerAnalysisView({
model: this.model,
analysisNode: this.analysisNode,
layerDefinitionModel: this.layerDefinitionModel
});
});
afterEach(function () {
$el.remove();
});
describe('.render', function () {
beforeEach(function () {
this.view.render();
});
it('should render correctly', function () {
expect(_.size(this.view._subviews)).toBe(2); // [AnalysisTooltip, TipsyTooltipView]
expect(this.view.$el.text()).toContain('analyses.area-of-influence');
expect(this.view.el.className).toContain('js-analysis-node');
expect(this.view.$el.attr('data-analysis-node-id')).toContain('a3');
});
describe('when analysis node is loading', function () {
it('should render the loading state', function () {
expect(this.view.$el.html()).toContain('Loader');
expect(this.view.$el.text()).not.toContain('a3');
});
});
describe('when analysis node is ready', function () {
it('should render the node id', function () {
this.analysisNode.set('status', 'ready');
expect(this.view.$el.text()).toContain('a3');
expect(this.view.$el.html()).not.toContain('Loader');
});
});
describe('when analysis node status is failed', function () {
it('should render the error elements', function () {
this.analysisNode.set('status', 'failed');
expect(this.view.$el.hasClass('has-error')).toBe(true);
expect(this.view.$('.Editor-ListAnalysis-itemError').length).toBe(1);
});
});
});
describe('._initBinds', function () {
it('should call _toggleHover on _stateModel change highlighted', function () {
spyOn(this.view, '_toggleHover');
this.view._initBinds();
this.view._stateModel.trigger('change:highlighted');
expect(this.view._toggleHover).toHaveBeenCalled();
});
});
describe('._onMouseEnter', function () {
it('should set _stateModel highlighted to true', function () {
expect(this.view._stateModel.get('highlighted')).toBe(false);
this.view._onMouseEnter();
expect(this.view._stateModel.get('highlighted')).toBe(true);
});
});
describe('._onMouseLeave', function () {
it('should set _stateModel highlighted to false', function () {
this.view._stateModel.set('highlighted', true, { silent: true });
expect(this.view._stateModel.get('highlighted')).toBe(true);
this.view._onMouseLeave();
expect(this.view._stateModel.get('highlighted')).toBe(false);
});
});
describe('._toggleHover', function () {
it('should toggle hover', function () {
$el.append(this.view.render().el);
this.view._stateModel.set('highlighted', true, { silent: true });
this.view._toggleHover();
expect($el.hasClass('is-hover')).toBe(true);
expect($el.find('.js-Editor-ListLayer-titleText').hasClass('is-hover')).toBe(true);
this.view._stateModel.set('highlighted', false, { silent: true });
this.view._toggleHover();
expect($el.hasClass('is-hover')).toBe(false);
expect($el.find('.js-Editor-ListLayer-titleText').hasClass('is-hover')).toBe(false);
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,69 @@
var Backbone = require('backbone');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var RefLayerAnalysisView = require('builder/editor/layers/analysis-views/ref-layer-analysis-view');
describe('editor/layers/analysis-views/ref-layer-analysis-view', function () {
beforeEach(function () {
this.model = new AnalysisDefinitionNodeModel({
id: 'a3',
type: 'trade-area',
kind: 'walk',
time: 300,
source: 'a2'
}, {
configModel: {},
sqlAPI: {}
});
this.analysisNode = new Backbone.Model();
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'layerA',
kind: 'carto',
options: {
table_name: 'alice'
}
}, {
configModel: {}
});
spyOn(this.layerDefinitionModel, 'getName').and.returnValue('layer-name');
this.view = new RefLayerAnalysisView({
model: this.model,
analysisNode: this.analysisNode,
layerDefinitionModel: this.layerDefinitionModel
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title', function () {
expect(this.view.$el.text()).toContain('analyses.area-of-influence');
expect(this.view.$el.text()).toContain('layer-name');
});
it('should render the loading until ready', function () {
expect(this.view.$el.html()).toContain('Loader');
expect(this.view.$el.text()).not.toContain('a3');
});
it('should render the data-attrs and js hook to edit analysis', function () {
expect(this.view.el.className).toContain('js-analysis-node');
expect(this.view.el.dataset.analysisNodeId).toEqual('a3');
});
describe('when analysis node is ready', function () {
beforeEach(function () {
this.analysisNode.set('status', 'ready');
});
it('should render id', function () {
expect(this.view.$el.text()).toContain('a3');
expect(this.view.$el.html()).not.toContain('Loader');
});
});
});
@@ -0,0 +1,171 @@
var Backbone = require('backbone');
var UserModel = require('builder/data/user-model');
var SourceLayerAnalysisView = require('builder/editor/layers/analysis-views/source-layer-analysis-view');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var ConfigModel = require('builder/data/config-model');
var TableModel = require('builder/data/table-model');
var _ = require('underscore');
var $ = require('jquery');
describe('editor/layers/analysis-views/source-layer-analysis-view', function () {
var $el;
beforeEach(function () {
$el = $('<div class="js-layer"><div class="js-Editor-ListLayer-titleText"></div></div>');
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({
username: 'chacho'
}, {
configModel: this.configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection([{
id: 'a0',
type: 'source',
table_name: 'foo_bar',
params: {
query: 'SELECT * FROM foo_bar'
}
}], {
configModel: this.configModel,
userModel: this.userModel
});
this.sourceAnalysisDefinitionNodeModel = this.analysisDefinitionNodesCollection.get('a0');
this.layerDefinitionModel = new Backbone.Model({user_name: 'somebody'});
this.layerDefinitionModel.getQualifiedTableName = function () {};
this.view = new SourceLayerAnalysisView({
model: this.sourceAnalysisDefinitionNodeModel,
analysisNode: this.sourceAnalysisDefinitionNodeModel,
layerDefinitionModel: this.layerDefinitionModel
});
});
afterEach(function () {
$el.remove();
});
describe('.render', function () {
it('should render correctly', function () {
this.view.render();
expect(this.view.$el.text()).toContain('a0');
expect(this.view.$el.text()).toContain('foo_bar');
expect(_.size(this.view._subviews)).toBe(1); // [tooltip]
});
describe('when layer is sync', function () {
var syncView;
beforeEach(function () {
var table = new TableModel({
id: 'harrr',
name: 'another_table',
synchronization: {
id: 'test'
}
}, {
parse: true,
configModel: this.configModel
});
spyOn(this.sourceAnalysisDefinitionNodeModel, 'getTableModel').and.returnValue(table);
syncView = new SourceLayerAnalysisView({
model: this.sourceAnalysisDefinitionNodeModel,
analysisNode: this.sourceAnalysisDefinitionNodeModel,
layerDefinitionModel: this.layerDefinitionModel
});
syncView.render();
});
it('should render correctly', function () {
expect(syncView.$el.html()).toContain('CDB-IconFont CDB-IconFont-wifi');
expect(_.size(syncView._subviews)).toBe(2); // [tooltip, sync tooltip]
});
});
describe('when custom query is applied', function () {
beforeEach(function () {
spyOn(this.view._analysisNode, 'isCustomQueryApplied').and.returnValue(true);
this.view.render();
});
it('should render correctly', function () {
expect(this.view.$el.html()).toContain('js-sql');
expect(this.view.$('.js-sql').html()).toContain('SQL');
expect(_.size(this.view._subviews)).toBe(2); // [tooltip, customQuery tooltip]
});
});
});
describe('._initBinds', function () {
it('should call _toggleHover on _stateModel change highlighted', function () {
spyOn(this.view, '_toggleHover');
this.view._initBinds();
this.view._stateModel.trigger('change:highlighted');
expect(this.view._toggleHover).toHaveBeenCalled();
});
it('should call render on _tableNodeModel change synchronization', function () {
spyOn(this.view, 'render');
this.view._initBinds();
this.view._tableNodeModel.trigger('change:synchronization');
expect(this.view.render).toHaveBeenCalled();
});
});
describe('._onMouseEnter', function () {
it('should set _stateModel highlighted to true', function () {
expect(this.view._stateModel.get('highlighted')).toBe(false);
this.view._onMouseEnter();
expect(this.view._stateModel.get('highlighted')).toBe(true);
});
});
describe('._onMouseLeave', function () {
it('should set _stateModel highlighted to false', function () {
this.view._stateModel.set('highlighted', true, { silent: true });
expect(this.view._stateModel.get('highlighted')).toBe(true);
this.view._onMouseLeave();
expect(this.view._stateModel.get('highlighted')).toBe(false);
});
});
describe('._toggleHover', function () {
it('should toggle hover', function () {
$el.append(this.view.render().el);
this.view._stateModel.set('highlighted', true, { silent: true });
this.view._toggleHover();
expect($el.hasClass('is-hover')).toBe(true);
expect($el.find('.js-Editor-ListLayer-titleText').hasClass('is-hover')).toBe(true);
this.view._stateModel.set('highlighted', false, { silent: true });
this.view._toggleHover();
expect($el.hasClass('is-hover')).toBe(false);
expect($el.find('.js-Editor-ListLayer-titleText').hasClass('is-hover')).toBe(false);
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,151 @@
var BasemapContentView = require('builder/editor/layers/basemap-content-view');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var CustomBaselayersCollection = require('builder/data/custom-baselayers-collection');
var FactoryModals = require('../../factories/modals');
describe('editor/layers/basemap-content-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.basemaps = {
CARTO: {
positron_rainbow: {
className: 'positron_rainbow',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png'
},
dark_matter_rainbow: {
className: 'dark_matter_rainbow',
urlTemplate: 'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
}
},
Stamen: {
watercolor: {
className: 'watercolor_stamen',
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg'
}
},
Here: {
nokia_day: {
className: 'nokia_day',
urlTemplate: 'https://{s}.maps.nlp.nokia.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=eng&token='
}
}
};
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo',
className: 'positron_rainbow',
category: 'CARTO'
}
});
this.customBaselayersCollection = new CustomBaselayersCollection([{
id: 'basemap-id-1',
options: {
urlTemplate: '',
category: 'Custom'
}
}], {
configModel: configModel,
currentUserId: 'current-user-id'
});
this.view = new BasemapContentView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemaps: this.basemaps,
stackLayoutModel: {},
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService(),
configModel: configModel
});
this.view.render();
});
it('should render properly', function () {
expect(Object.keys(this.view._subviews).length).toBe(2); // header, content
});
it('should update header if baseLayer changes', function () {
expect(this.view.$('.Editor-HeaderInfo-titleText').text()).toBe('foo editor.layers.basemap.by CARTO');
this.view._baseLayer.set({
name: 'bar',
category: 'Stamen'
});
expect(this.view.$('.Editor-HeaderInfo-titleText').text()).toBe('bar editor.layers.basemap.by Stamen');
});
it('should update select if category changes', function () {
expect(this.view._categoriesCollection.at(0).get('selected')).toBe(true);
expect(this.view._categoriesCollection.at(1).get('selected')).toBe(false);
expect(this.view.$('.Mosaic-item').length).toBe(2);
this.view._categoriesCollection.at(1).set('selected', true);
expect(this.view._categoriesCollection.at(0).get('selected')).toBe(false);
expect(this.view._categoriesCollection.at(1).get('selected')).toBe(true);
expect(this.view.$('.Mosaic-item').length).toBe(1);
});
it('should update disabled if model changes', function () {
expect(this.view.$('.js-basemapContent').hasClass('is-disabled')).toEqual(false);
this.view.model.set('disabled', true);
expect(this.view.$('.js-basemapContent').hasClass('is-disabled')).toEqual(true);
this.view.model.set('disabled', false);
expect(this.view.$('.js-basemapContent').hasClass('is-disabled')).toEqual(false);
});
describe('when selected basemap changes', function () {
it('should update category', function () {
expect(this.view._categoriesCollection.getSelected().getName()).toBe('CARTO');
this.view._layerDefinitionsCollection.at(0).set('category', 'Here');
expect(this.view._categoriesCollection.getSelected().getName()).toBe('Here');
this.view._layerDefinitionsCollection.at(0).set('category', 'Stamen');
expect(this.view._categoriesCollection.getSelected().getName()).toBe('Stamen');
});
});
describe('._onAddBasemap', function () {
beforeEach(function () {
spyOn(this.view._basemapsCollection, 'updateSelected');
this.view._basemapsCollection.add({
id: 'basemap-id-2',
urlTemplate: 'https://b.example.com/{z}/{x}/{y}.png',
val: 'httpsbexamplecomzxypng'
});
});
it('should update selected in basemapsCollection', function () {
expect(this.view._basemapsCollection.updateSelected).toHaveBeenCalledWith('httpsbexamplecomzxypng');
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,57 @@
var BasemapCategoriesView = require('builder/editor/layers/basemap-content-views/basemap-categories-view');
var CarouselCollection = require('builder/components/custom-carousel/custom-carousel-collection');
describe('editor/layers/basemap-content-views/basemap-categories-view', function () {
beforeEach(function () {
this.collection = new CarouselCollection([{
selected: true,
val: 'CARTO',
label: 'CARTO',
template: function () {
return 'CARTO';
}
}, {
selected: false,
val: 'Here',
label: 'Here',
template: function () {
return 'Here';
}
}, {
selected: false,
val: 'Stamen',
label: 'Stamen',
template: function () {
return 'Stamen';
}
}, {
selected: false,
val: 'Custom',
label: 'Custom',
template: function () {
return 'Custom';
}
}, {
selected: false,
val: 'Color',
label: 'Color',
template: function () {
return 'Color';
}
}]);
this.view = new BasemapCategoriesView({
categoriesCollection: this.collection
});
this.view.render();
});
it('should render properly', function () {
expect(this.view.$('.Carousel-item').length).toBe(5);
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,119 @@
var BasemapFormModel = require('builder/editor/layers/basemap-content-views/basemap-form-model');
var BasemapsCollection = require('builder/editor/layers/basemap-content-views/basemaps-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var _ = require('underscore');
describe('editor/layers/basemap-content-views/basemap-form-model', function () {
beforeEach(function () {
spyOn(_, 'debounce').and.callFake(function (func) {
return function () {
func.apply(this, arguments);
};
});
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
this.basemapsCollection = new BasemapsCollection();
this.basemapsCollection.add({
default: false,
color: '#35AAE5',
image: '',
maxZoom: 32,
className: 'plain',
category: 'Color',
type: 'Plain',
selected: true,
val: 'plain',
label: 'plain',
template: 'plain'
});
this.formModel = new BasemapFormModel({
color: {
color: {
fixed: '#35AAE5',
opacity: 1
}
},
image: ''
}, {
basemapsCollection: this.basemapsCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection
});
spyOn(this.basemapsCollection, 'updateSelected');
spyOn(this.layerDefinitionsCollection, 'setBaseLayer');
});
describe('when form data changes', function () {
beforeEach(function () {
this._setColor = function () {
this.formModel.set('color', {
color: {
fixed: '#FABADA',
opacity: 1
}
});
}.bind(this);
});
it('should save changes if it is already selected', function () {
this.basemapsCollection.at(0).set('selected', true);
this._setColor();
expect(this.basemapsCollection.updateSelected).not.toHaveBeenCalled();
expect(this.layerDefinitionsCollection.setBaseLayer).toHaveBeenCalledWith({
default: false,
color: '#FABADA',
image: '',
maxZoom: 32,
className: 'plain',
category: 'Color',
type: 'Plain',
selected: true,
val: 'plain',
label: 'plain',
template: 'plain',
minZoom: 0,
name: ''
});
});
it('should not save changes if it is not selected', function () {
this.basemapsCollection.at(0).set('selected', false);
this._setColor();
expect(this.basemapsCollection.updateSelected).toHaveBeenCalledWith('plain');
expect(this.layerDefinitionsCollection.setBaseLayer).not.toHaveBeenCalled();
});
});
});
@@ -0,0 +1,66 @@
var BasemapsCollection = require('builder/editor/layers/basemap-content-views/basemaps-collection');
var BasemapFormView = require('builder/editor/layers/basemap-content-views/basemap-form-view');
var Backbone = require('backbone');
describe('editor/layers/basemap-content-views/basemap-form-view', function () {
beforeEach(function () {
this.model = new Backbone.Model({
default: false,
color: '#35AAE5',
image: '',
maxZoom: 32,
className: 'plain',
category: 'Color',
type: 'Plain',
selected: true,
val: 'plain',
label: 'plain',
template: function () {
return 'plain';
}
});
this.view = new BasemapFormView({
model: this.model,
basemapsCollection: new BasemapsCollection(),
layerDefinitionsCollection: {}
});
this.view.render();
});
it('should render with data from form model', function () {
expect(this.view.$('form').length).toEqual(1);
expect(this.view.$el.html()).toContain('Form-InputFill');
});
describe('when schema changes', function () {
beforeEach(function () {
this.prev$form = this.view.$('form');
this.view._formModel.trigger('changeSchema');
});
afterEach(function () {
this.prev$form = null;
});
it('should re-render the form', function () {
expect(this.view.$('form').length).toEqual(1);
expect(this.view.$('form')).not.toBe(this.prev$form);
});
});
describe('when form is cleaned', function () {
beforeEach(function () {
spyOn(Backbone.Form.prototype, 'remove').and.callThrough();
this.view.clean();
});
it('should remove form when view is cleaned', function () {
expect(Backbone.Form.prototype.remove).toHaveBeenCalled();
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,40 @@
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var BasemapHeaderView = require('builder/editor/layers/basemap-content-views/basemap-header-view');
describe('editor/layers/basemap-content-view', function () {
beforeEach(function () {
this.model = new LayerDefinitionModel({
id: 'l-1',
type: 'CartoDB',
name: 'thename'
}, {
configModel: {},
stateDefinitionModel: {}
});
this.view = new BasemapHeaderView({
model: this.model,
category: 'thecategory'
});
this.view.render();
});
it('should render title and default category of layer', function () {
expect(this.view.$el.text()).toContain('thename editor.layers.basemap.by thecategory');
});
it('should update title and category', function () {
expect(this.view.$el.text()).toContain('thename editor.layers.basemap.by thecategory');
this.view.model.set('name', 'thename1');
this.view.category = 'thecategory1';
this.view.render();
expect(this.view.$el.text()).toContain('thename1 editor.layers.basemap.by thecategory1');
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,77 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var BasemapInnerView = require('builder/editor/layers/basemap-content-views/basemap-inner-view');
var BasemapsCollection = require('builder/editor/layers/basemap-content-views/basemaps-collection');
var CarouselCollection = require('builder/components/custom-carousel/custom-carousel-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var FactoryModals = require('../../../factories/modals');
describe('editor/layers/basemap-content-views/basemap-inner-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo',
className: 'positron_rainbow',
category: 'CARTO'
}
});
spyOn(BasemapInnerView.prototype, '_renderSelect').and.callThrough();
var categoriesCollection = new CarouselCollection(
[
{ selected: true, val: 'CARTO' },
{ val: 'Vizzuality' }
]
);
this.view = new BasemapInnerView({
categoriesCollection: categoriesCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: new BasemapsCollection(),
customBaselayersCollection: {},
selectedCategoryVal: 'CARTO',
model: new Backbone.Model(),
modals: FactoryModals.createModalService()
});
this.view.render();
});
it('should render properly', function () {
expect(_.size(this.view._subviews)).toBe(2); // categories, select
});
it('should render select content when a new category is selected', function () {
this.view._categoriesCollection.at(1).set('selected', true);
expect(BasemapInnerView.prototype._renderSelect).toHaveBeenCalled();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,236 @@
var _ = require('underscore');
var Backbone = require('backbone');
var BasemapModelFactory = require('builder/editor/layers/basemap-content-views/basemap-model-factory');
var customBaseLayerAttrs = {
'id': '27d7e5bf-7a64-41b1-a6e0-378d842745a0',
'type': 'Tiled',
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
'attribution': null,
'maxZoom': 21,
'minZoom': 0,
'name': 'Custom basemap 16',
'tms': false,
'className': 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
'order': 16,
'visible': true
};
var expectedCustomBasemapModelAttrs = function (category) {
return {
'id': '27d7e5bf-7a64-41b1-a6e0-378d842745a0',
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
'minZoom': 0,
'maxZoom': 21,
'name': 'Custom basemap 16',
'className': 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
'attribution': null,
'category': category,
'tms': false,
'selected': false,
'val': 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
'label': 'Custom basemap 16',
'default': false,
'type': 'Tiled',
'template': jasmine.any(Function)
};
};
var testCases = [
{
basemapCategory: 'CARTO',
basemapAttrs: {
'default': true,
'urlTemplate': 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'name': 'Positron',
'className': 'positron_rainbow_labels',
'attribution': 'attribution',
'labels': {
'urlTemplate': 'http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png'
}
},
expectedBasemapModelAttrs: {
'default': true,
'urlTemplate': 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'name': 'Positron',
'className': 'positron_rainbow_labels',
'attribution': 'attribution',
'labels': {
'urlTemplate': 'http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png'
},
'type': 'Tiled',
'category': 'CARTO',
'selected': false,
'val': 'positron_rainbow_labels',
'label': 'Positron',
'template': jasmine.any(Function)
}
},
{
basemapCategory: 'Stamen',
basemapAttrs: {
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'name': 'Toner',
'className': 'toner_stamen_labels',
'attribution': 'attribution',
'labels': {
'urlTemplate': 'http://{s}.tile.stamen.com/toner-labels/{z}/{x}/{y}.png'
}
},
expectedBasemapModelAttrs: {
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': '0',
'maxZoom': '18',
'name': 'Toner',
'className': 'toner_stamen_labels',
'attribution': 'attribution',
'labels': {
'urlTemplate': 'http://{s}.tile.stamen.com/toner-labels/{z}/{x}/{y}.png'
},
'type': 'Tiled',
'category': 'Stamen',
'selected': false,
'val': 'toner_stamen_labels',
'label': 'Toner',
'default': false,
'template': jasmine.any(Function)
}
},
{
basemapCategory: 'Here',
basemapAttrs: {
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-hybrid/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': 0,
'maxZoom': 18,
'name': 'Toner Hybrid',
'className': 'toner_hybrid_stamen',
'attribution': 'Map tiles by <a href="http://stamen.com">"'
},
expectedBasemapModelAttrs: {
'urlTemplate': 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-hybrid/{z}/{x}/{y}.png',
'subdomains': 'abcd',
'minZoom': 0,
'maxZoom': 18,
'name': 'Toner Hybrid',
'className': 'toner_hybrid_stamen',
'attribution': 'Map tiles by <a href=\"http://stamen.com\">\"',
'type': 'Tiled',
'category': 'Here',
'selected': false,
'val': 'toner_hybrid_stamen',
'label': 'Toner Hybrid',
'template': jasmine.any(Function),
'default': false
}
},
{
basemapCategory: 'GMaps',
basemapAttrs: {
'name': 'GMaps Roadmap',
'maxZoom': 40,
'minZoom': 0,
'baseType': 'roadmap',
'className': 'googlemaps',
'style': '[]',
'default': true
},
expectedBasemapModelAttrs: {
'name': 'GMaps Roadmap',
'maxZoom': 40,
'minZoom': 0,
'baseType': 'roadmap',
'className': 'googlemaps',
'style': '[]',
'default': true,
'type': 'GMapsBase',
'category': 'GMaps',
'selected': false,
'val': 'roadmap',
'label': 'Roadmap',
'template': jasmine.any(Function)
}
},
{
basemapCategory: 'Custom',
basemapAttrs: customBaseLayerAttrs,
expectedBasemapModelAttrs: expectedCustomBasemapModelAttrs('Custom')
},
{
basemapCategory: 'NASA',
basemapAttrs: customBaseLayerAttrs,
expectedBasemapModelAttrs: expectedCustomBasemapModelAttrs('NASA')
},
{
basemapCategory: 'TileJSON',
basemapAttrs: customBaseLayerAttrs,
expectedBasemapModelAttrs: expectedCustomBasemapModelAttrs('TileJSON')
},
{
basemapCategory: 'Mapbox',
basemapAttrs: customBaseLayerAttrs,
expectedBasemapModelAttrs: expectedCustomBasemapModelAttrs('Mapbox')
},
{
basemapCategory: 'WMS',
basemapAttrs: customBaseLayerAttrs,
expectedBasemapModelAttrs: expectedCustomBasemapModelAttrs('WMS')
},
{
basemapCategory: 'Color',
basemapAttrs: {
'color': '#FABADA',
'image': 'IMAGE',
'className': 'CLASS_NAME'
},
expectedBasemapModelAttrs: {
'default': false,
'color': '#FABADA',
'image': 'IMAGE',
'minZoom': 0,
'maxZoom': 32,
'className': 'plain',
'category': 'Color',
'type': 'Plain',
'selected': false,
'name': '',
'val': 'plain',
'label': 'plain',
'template': jasmine.any(Function)
}
}
];
describe('builder/editor/layers/basemap-content-views/basemap-model-factory.js', function () {
describe('.createBasemapModel', function () {
beforeEach(function () {
var layerDefinitionsCollection = new Backbone.Collection();
layerDefinitionsCollection.getBaseLayer = function () {
return new Backbone.Model();
};
var configModel = new Backbone.Model();
this.basemapModelFactory = new BasemapModelFactory(layerDefinitionsCollection, configModel);
});
_.each(testCases, function (testCase) {
var basemapCategory = testCase.basemapCategory;
var basemapAttrs = testCase.basemapAttrs;
var expectedBasemapModelAttrs = testCase.expectedBasemapModelAttrs;
it('should create a BasemapModel for ' + basemapCategory + ' basemaps', function () {
var basemapModel = this.basemapModelFactory.createBasemapModel(basemapCategory, basemapAttrs);
expect(basemapModel.toJSON()).toEqual(expectedBasemapModelAttrs);
});
}, this);
});
});
@@ -0,0 +1,40 @@
var BasemapsModel = require('builder/editor/layers/basemap-content-views/basemap-model');
describe('editor/layers/basemap-content-views/basemap-model', function () {
beforeEach(function () {
this.model1 = new BasemapsModel({
selected: true,
val: 'positron_rainbow',
label: 'Positron (labels below)',
template: function () {
return 'positron_rainbow';
}
});
this.model2 = new BasemapsModel({
selected: false,
val: 'dark_matter_rainbow',
label: '',
template: function () {
return 'dark_matter_rainbow';
}
});
});
describe('.getName', function () {
it('should return label or value if empty', function () {
expect(this.model1.get('label')).toBe('Positron (labels below)');
expect(this.model1.get('label')).toBe(this.model1.getName());
expect(this.model2.get('label')).toBe('');
expect(this.model2.get('val')).toBe('dark_matter_rainbow');
expect(this.model2.get('val')).toBe(this.model2.getName());
});
});
describe('.getValue', function () {
it('.getValue', function () {
expect(this.model1.get('val')).toBe('positron_rainbow');
expect(this.model1.getValue()).toBe(this.model1.get('val'));
});
});
});
@@ -0,0 +1,256 @@
var BasemapSelectView = require('builder/editor/layers/basemap-content-views/basemap-select-view');
var BasemapsCollection = require('builder/editor/layers/basemap-content-views/basemaps-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var CustomBaselayersCollection = require('builder/data/custom-baselayers-collection');
var FactoryModals = require('../../../factories/modals');
describe('editor/layers/basemap-content-views/basemap-select-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
this.basemapsCollection = new BasemapsCollection([{
default: true,
url: 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Positron (labels below)',
className: 'positron_rainbow',
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
category: 'CARTO',
selected: true,
val: 'positron_rainbow',
label: 'Positron (labels below)',
template: function () {
return 'positron_rainbow';
}
}, {
default: false,
url: 'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Dark matter (labels below)',
className: 'dark_matter_rainbow',
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
category: 'CARTO',
selected: false,
val: 'dark_matter_rainbow',
label: 'Dark matter (labels below)',
template: function () {
return 'dark_matter_rainbow';
}
}, {
default: false,
url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Watercolor',
className: 'watercolor_stamen',
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.',
category: 'Stamen',
selected: false,
val: 'watercolor_stamen',
label: 'Watercolor',
template: function () {
return 'watercolor_stamen';
}
}, {
default: false,
url: '://{s}.maps.nlp.nokia.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=eng&token=A7tBPacePg9Mj_zghvKt9Q&app_id=KuYppsdXZznpffJsKT24',
subdomains: '1234',
minZoom: '0',
maxZoom: '20',
name: 'Nokia Day',
className: 'nokia_day',
attribution: 'Map&copy;2016 HERE <a href="http://here.net/services/terms" target="_blank">Terms of use</a>',
category: 'Here',
selected: false,
val: 'nokia_day',
label: 'Nokia Day',
template: function () {
return 'nokia_day';
}
}, {
default: false,
color: '#ff6600',
image: '',
maxZoom: 32,
className: 'plain',
category: 'Color',
type: 'Plain',
selected: false,
val: 'plain',
label: 'plain',
template: function () {
return 'plain';
}
}, {
id: 'basemap-id-1',
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
minZoom: 0,
maxZoom: 21,
name: 'Custom basemap 1',
className: 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
attribution: null,
category: 'Custom',
selected: false,
val: 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
default: false,
subdomains: '',
labels: null,
type: 'Tiled'
}, {
id: 'basemap-id-2',
urlTemplate: 'https://a.tiles.mapbox.com/v4/username.12ab45c/{z}/{x}/{y}.png?access_token=aBcDC12323abc',
minZoom: 0,
maxZoom: 21,
name: 'MapBox Streets Outdoors Global Preview',
className: 'httpsatilesmapboxcomv4username12ab45czxypngaccess_tokenaBcDC12323abc',
attribution: '<a href=\'https://www.mapbox.com/about/maps/\' target=\'_blank\'>&copy; Mapbox &copy; OpenStreetMap</a> <a class=\'mapbox-improve-map\' href=\'https://www.mapbox.com/map-feedback/\' target=\'_blank\'>Improve this map</a>',
category: 'Mapbox',
selected: false,
val: 'httpsatilesmapboxcomv4username12ab45czxypngaccess_tokenaBcDC12323abc',
label: 'MapBox Streets Outdoors Global Preview',
default: false,
subdomains: '',
labels: null,
type: 'Tiled',
highlighted: false
}]);
this.customBaselayersCollection = new CustomBaselayersCollection([{
id: 'basemap-id-1',
options: {
urlTemplate: '',
category: 'Custom'
}
}], {
configModel: configModel,
currentUserId: 'current-user-id'
});
});
afterEach(function () {
this.view.clean();
});
it('should render properly', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'CARTO',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(2);
expect(this.view.$('.Mosaic-item').length).toBe(2);
});
describe('select', function () {
afterEach(function () {
this.view.clean();
});
it('should render mosaic if category is CARTO, or Here', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'Here',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(1);
expect(this.view.$('.Mosaic-item').length).toBe(1);
});
it('should render mosaic if category is Stamen', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'Stamen',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(1);
expect(this.view.$('.Mosaic-item').length).toBe(1);
});
it('should render mosaic if category is Custom', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'Custom',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(1);
expect(this.view.$('.Mosaic-item').length).toBe(2); // Custom basemap and "Add basemap" button
});
it('should render mosaic if category is Mapbox', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'Mapbox',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(1);
expect(this.view.$('.Mosaic-item').length).toBe(2); // Mapbox basemap and "Add basemap" button
});
it('should render form if category is Color', function () {
this.view = new BasemapSelectView({
layerDefinitionsCollection: this.layerDefinitionsCollection,
basemapsCollection: this.basemapsCollection,
selectedCategoryVal: 'Color',
customBaselayersCollection: this.customBaselayersCollection,
modals: FactoryModals.createModalService()
});
this.view.render();
expect(this.view._filteredBasemapsCollection.length).toBe(1);
expect(this.view.$('.Form-InputFill').length).toBe(1);
});
});
});
@@ -0,0 +1,193 @@
var BasemapsCollection = require('builder/editor/layers/basemap-content-views/basemaps-collection');
describe('editor/layers/basemap-content-views/basemaps-collection', function () {
beforeEach(function () {
this.collection = new BasemapsCollection([{
default: true,
url: 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Positron (labels below)',
className: 'positron_rainbow',
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
category: 'CARTO',
selected: true,
val: 'positron_rainbow',
label: 'Positron (labels below)',
template: function () {
return 'positron_rainbow';
}
}, {
default: false,
url: 'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Dark matter (labels below)',
className: 'dark_matter_rainbow',
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
category: 'CARTO',
selected: false,
val: 'dark_matter_rainbow',
label: 'Dark matter (labels below)',
template: function () {
return 'dark_matter_rainbow';
}
}, {
default: false,
url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: '0',
maxZoom: '18',
name: 'Watercolor',
className: 'watercolor_stamen',
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.',
category: 'Stamen',
selected: false,
val: 'watercolor_stamen',
label: 'Watercolor',
template: function () {
return 'watercolor_stamen';
}
}, {
default: false,
url: '://{s}.maps.nlp.nokia.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=eng&token=A7tBPacePg9Mj_zghvKt9Q&app_id=KuYppsdXZznpffJsKT24',
subdomains: '1234',
minZoom: '0',
maxZoom: '20',
name: 'Nokia Day',
className: 'nokia_day',
attribution: 'Map&copy;2016 HERE <a href="http://here.net/services/terms" target="_blank">Terms of use</a>',
category: 'Here',
selected: false,
val: 'nokia_day',
label: 'Nokia Day',
template: function () {
return 'nokia_day';
}
}, {
default: false,
color: '#ff6600',
image: '',
maxZoom: 32,
className: 'plain',
category: 'Color',
type: 'Plain',
selected: false,
val: 'plain',
label: 'plain',
template: function () {
return 'plain';
}
}]);
this.collection.add({
id: 'basemap-id-1',
urlTemplate: 'https://a.tiles.mapbox.com/v4/username.12ab45c/{z}/{x}/{y}.png?access_token=aBcDC12323abc',
maxZoom: 21,
minZoom: 0,
name: 'MapBox Streets Outdoors Global Preview',
className: 'httpsatilesmapboxcomv4username12ab45czxypngaccess_tokenaBcDC12323abc',
attribution: '<a href="https://www.mapbox.com/about/maps/" target="_blank">&copy; Mapbox &copy; OpenStreetMap</a> <a class="mapbox-improve-map" href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a>',
order: 26,
val: 'httpsatilesmapboxcomv4username12ab45czxypngaccess_tokenaBcDC12323abc'
});
this.collection.add({
id: 'basemap-id-2',
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
maxZoom: 21,
minZoom: 0,
name: 'Custom basemap 29',
className: 'httpsstamentilessasslfastlynetwatercolorzxyjpg',
attribution: null,
order: 29,
val: 'httpsstamentilessasslfastlynetwatercolorzxyjpg'
});
});
it('should have only one selected', function () {
var l0 = this.collection.at(0);
var l1 = this.collection.at(1);
expect(l0.get('selected')).toBeTruthy();
expect(this.collection.where({ selected: true }).length).toBe(1);
l1.set('selected', true);
expect(this.collection.where({ selected: true }).length).toBe(1);
expect(l0.get('selected')).toBeFalsy();
expect(l1.get('selected')).toBeTruthy();
});
describe('.getSelected', function () {
it('should return selected model', function () {
var l0 = this.collection.at(0);
var selected = this.collection.getSelected();
expect(l0.get('selected')).toBeTruthy();
expect(selected.get('selected')).toBeTruthy();
expect(l0.get('className')).toBe(selected.get('className'));
});
});
describe('.findByCategory', function () {
it('should return models for that category', function () {
expect(this.collection.findByCategory('CARTO').length).toBe(2);
});
});
describe('.getDefaultCategory', function () {
it('should return default category', function () {
expect(this.collection.getDefaultCategory()).toBe('CARTO');
});
});
describe('.getCategories', function () {
it('should return an array of categories', function () {
expect(this.collection.getCategories()).toContain('CARTO');
expect(this.collection.getCategories()).toContain('Here');
expect(this.collection.getCategories()).toContain('Stamen');
expect(this.collection.getCategories()).toContain('Color');
expect(this.collection.getCategories()).toContain('Custom');
expect(this.collection.getCategories()).toContain('Mapbox');
expect(this.collection.getCategories()).toContain('WMS');
expect(this.collection.getCategories()).toContain('TileJSON');
});
});
describe('.updateSelected', function () {
it('should update selected passing the classname', function () {
var l0 = this.collection.at(0);
var l1 = this.collection.at(1);
expect(l0.get('className')).toBe('positron_rainbow');
expect(l0.get('selected')).toBeTruthy();
expect(l1.get('selected')).toBeFalsy();
this.collection.updateSelected('dark_matter_rainbow');
expect(l1.get('className')).toBe('dark_matter_rainbow');
expect(l0.get('selected')).toBeFalsy();
expect(l1).toBeTruthy();
});
});
describe('.updateCategory', function () {
it('should update category passing the classname and a category', function () {
var l5 = this.collection.at(5);
expect(l5.get('category')).toBe('Custom');
this.collection.updateCategory('httpsatilesmapboxcomv4username12ab45czxypngaccess_tokenaBcDC12323abc', 'Mapbox');
expect(l5.get('category')).toBe('Mapbox');
});
});
describe('.getByValue', function () {
it('should return model by value', function () {
var l0 = this.collection.at(0);
var l1 = this.collection.getByValue('positron_rainbow');
expect(l0.get('className')).toBe(l1.get('className'));
});
});
});
@@ -0,0 +1,270 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditFeatureContentView = require('builder/editor/layers/edit-feature-content-view');
var MapModeModel = require('builder/map-mode-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var FeatureDefinitionModel = require('builder/data/feature-definition-model');
var EditorModel = require('builder/data/editor-model');
var ModalsService = require('builder/components/modals/modals-service-model');
var Stylehelper = require('builder/helpers/style');
var Router = require('builder/routes/router');
var QueryGeometryModelMock = function (simple_geom) {
var geom = simple_geom || '';
return {
hasValueAsync: function () {
return Promise.resolve(geom !== '');
},
resetFetch: function () {}
};
};
describe('editor/layers/edit-feature-content-view', function () {
beforeEach(function () {
spyOn(Router, 'navigate');
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(new RegExp('^http(s)?://.*tables.*'))
.andReturn({ status: 200 });
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
this.layerDefinitionModel = layerDefinitionsCollection.at(0);
this.mapModeModel = new MapModeModel();
this.editorModel = new EditorModel();
this.mapStackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['prevStep', 'nextStep', 'goToStep']);
this.modals = new ModalsService();
this.rowCollection = {
resetFetch: jasmine.createSpy('resetFetch')
};
var featureDefinition = new FeatureDefinitionModel({}, {
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
userModel: this.userModel
});
this.mapModeModel.enterDrawingFeatureMode(featureDefinition);
this.view = new EditFeatureContentView({
layerDefinitionModel: this.layerDefinitionModel,
configModel: this.configModel,
stackLayoutModel: this.mapStackLayoutModel,
mapModeModel: this.mapModeModel,
editorModel: this.editorModel,
model: new Backbone.Model({
hasChanges: false,
isValidAttributes: true,
isValidGeometry: true
}),
modals: this.modals
});
spyOn(this.view, '_addRow').and.callThrough();
spyOn(this.view, '_renderInfo').and.callThrough();
this.view._rowsCollection = this.rowCollection;
this.view.render();
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should get table details', function () {
expect(this.view._tableName).toBe('foo');
expect(this.view._url).toBe('/u/pepe/dataset/foo');
});
it('should render properly', function () {
expect(_.size(this.view._subviews)).toBe(2); // header, content
});
describe('when feature is new', function () {
it('should add row', function () {
expect(this.view._addRow).toHaveBeenCalled();
expect(this.view._renderInfo).toHaveBeenCalled();
});
});
describe('when feature already exists', function () {
var view;
beforeEach(function () {
var featureDefinition = new FeatureDefinitionModel({
cartodb_id: 1
}, {
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
userModel: this.userModel
});
featureDefinition.fetch();
this.mapModeModel.enterEditingFeatureMode(featureDefinition);
view = new EditFeatureContentView({
layerDefinitionModel: this.layerDefinitionModel,
configModel: this.configModel,
stackLayoutModel: this.mapStackLayoutModel,
mapModeModel: this.mapModeModel,
editorModel: this.editorModel,
model: new Backbone.Model({
hasChanges: false
}),
modals: this.modals
});
spyOn(view, '_addRow').and.callThrough();
spyOn(view, '_renderInfo').and.callThrough();
view.render();
});
it('should not add row', function () {
expect(view._addRow).not.toHaveBeenCalled();
expect(view._renderInfo).toHaveBeenCalled();
});
});
describe('._onDestroyFeatureSuccess', function () {
it('should call `_onUpdateFeature`', function () {
spyOn(this.view, '_onUpdateFeature').and.callThrough();
spyOn(Router, 'goToStyleTab').and.callThrough();
this.view.notification = {
set: function () {}
};
spyOn(this.view.notification, 'set');
this.view._onDestroyFeatureSuccess();
expect(this.view._onUpdateFeature).toHaveBeenCalledWith('destroy');
expect(this.view.notification.set).toHaveBeenCalled();
expect(this.rowCollection.resetFetch).toHaveBeenCalled();
expect(Router.goToStyleTab).toHaveBeenCalledWith(this.layerDefinitionModel.get('id'));
});
});
describe('._onSaveFeatureSuccess', function () {
it('should call `_onUpdateFeature`', function () {
spyOn(this.view, '_onUpdateFeature').and.callThrough();
spyOn(Router, 'editFeature');
this.view.notification = {
set: function () {}
};
spyOn(this.view.notification, 'set');
this.view._onSaveFeatureSuccess('save');
expect(this.view._onUpdateFeature).toHaveBeenCalled();
expect(this.view.notification.set).toHaveBeenCalled();
expect(this.rowCollection.resetFetch).not.toHaveBeenCalled();
expect(Router.editFeature).toHaveBeenCalled();
});
});
describe('._onUpdateFeature', function () {
it('should reset fetch if saving and current geometry model does not have value', function (done) {
var self = this;
this.view._queryGeometryModel = new QueryGeometryModelMock();
spyOn(this.view._queryGeometryModel, 'resetFetch');
this.view._onUpdateFeature('save');
setTimeout(function () {
expect(self.view._queryGeometryModel.resetFetch).toHaveBeenCalled();
expect(self.rowCollection.resetFetch).not.toHaveBeenCalled();
done();
}, 200);
});
it('should not reset fetch if saving and current geometry model has value', function (done) {
var self = this;
this.view._queryGeometryModel = new QueryGeometryModelMock('polygon');
spyOn(this.view._queryGeometryModel, 'resetFetch');
this.view._onUpdateFeature('save');
setTimeout(function () {
expect(self.view._queryGeometryModel.resetFetch).not.toHaveBeenCalled();
done();
}, 200);
});
it('should reset fetch if destroying', function (done) {
var self = this;
this.view._queryGeometryModel = new QueryGeometryModelMock();
spyOn(this.view._queryGeometryModel, 'resetFetch');
this.view._onUpdateFeature('destroy');
setTimeout(function () {
expect(self.view._queryGeometryModel.resetFetch).toHaveBeenCalled();
expect(self.rowCollection.resetFetch).toHaveBeenCalled();
done();
}, 200);
});
});
describe('when `cancelPreviousEditions` is triggered from _editorModel', function () {
it('should call clean method', function () {
spyOn(this.view, 'clean');
this.view._initBinds();
this.view._editorModel.trigger('cancelPreviousEditions');
expect(this.view.clean).toHaveBeenCalled();
});
});
describe('reload vis', function () {
beforeEach(function () {
spyOn(Stylehelper, 'getColorAttribute');
spyOn(Stylehelper, 'getSizeAttribute');
});
it('should reload vis if metadata changes and layer is styled but that attribute', function () {
Stylehelper.getColorAttribute.and.returnValue('foo');
Stylehelper.getSizeAttribute.and.returnValue(undefined);
this.view._featureModel.set('foo', 10);
expect(this.view._needReloadVis()).toBeTruthy();
});
it('should reload vis if metadata changes and layer is not styled but that attribute', function () {
Stylehelper.getColorAttribute.and.returnValue('bar');
Stylehelper.getSizeAttribute.and.returnValue(undefined);
this.view._featureModel.set('foo', 10);
expect(this.view._needReloadVis()).toBeFalsy();
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,184 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var EditFeatureActionView = require('builder/editor/layers/edit-feature-content-views/edit-feature-action-view');
var EditFeatureGeometryFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-geometry-form-model');
var EditFeatureAttributesFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-attributes-form-model');
var QueryColumnsCollection = require('builder/data/query-columns-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
describe('editor/layers/edit-feature-content-views/edit-feature-action-view', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.querySchemaModel = new QuerySchemaModel({}, {
configModel: this.configModel
});
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
var layerDefinitionModel = layerDefinitionsCollection.at(0);
this.columnsCollection = new QueryColumnsCollection([
{
name: 'cartodb_id',
type: 'number'
}, {
name: 'name',
type: 'string'
}, {
name: 'description',
type: 'string'
}, {
name: 'the_geom',
type: 'geometry'
}
], {
querySchemaModel: this.querySchemaModel,
configModel: this.configModel
});
this.featureModel = new Backbone.Model({
the_geom: '{"type":"LineString","coordinates":[[0,0],[0,1]]}',
name: '',
description: ''
});
this.geometryFormModel = new EditFeatureGeometryFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel
});
this.attributesFormModel = new EditFeatureAttributesFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel,
columnsCollection: this.columnsCollection,
configModel: this.configModel,
nodeDefModel: layerDefinitionModel.getAnalysisDefinitionNodeModel()
});
this.model = new Backbone.Model({
hasChanges: false,
isValidAttributes: true,
isValidGeometry: true
});
this.view = new EditFeatureActionView({
model: this.model,
featureModel: this.featureModel,
geometryFormModel: this.geometryFormModel,
attributesFormModel: this.attributesFormModel
});
this.view.render();
});
it('should render properly', function () {
expect(this.view.$('.js-save').length).toBe(1);
});
it('button should be disabled by default', function () {
expect(this.view.$el.html()).toContain('is-disabled');
});
it('should update model if feature changes', function () {
expect(this.model.get('hasChanges')).toBe(false);
this.featureModel.set('the_geom', '{"type":"LineString","coordinates":[[0,0],[1,1]]}');
expect(this.model.get('hasChanges')).toBe(true);
});
describe('when is new', function () {
beforeEach(function () {
this.featureModel.isNew = function () { return true; };
});
it('button should be add', function () {
expect(this.view.$el.html()).toContain('editor.edit-feature.add');
});
});
describe('when feature already exists', function () {
beforeEach(function () {
this.featureModel.isNew = function () { return false; };
this.view.render();
});
it('button should be save', function () {
expect(this.view.$el.html()).toContain('editor.edit-feature.save');
});
});
describe('save feature', function () {
beforeEach(function () {
spyOn(this.featureModel, 'save');
});
describe('when has no changes', function () {
beforeEach(function () {
this.view.$('.js-save').click();
});
it('button should be disabled', function () {
expect(this.view.$el.html()).toContain('is-disabled');
});
});
describe('when has changes', function () {
beforeEach(function () {
spyOn(this.featureModel, 'trigger');
this.model.set('hasChanges', true);
this.view.$('.js-save').click();
});
it('should save the feature model', function () {
expect(this.featureModel.save).toHaveBeenCalled();
});
describe('when save succeeds', function () {
it('should update changes', function () {
this.featureModel.save.calls.argsFor(0)[0].success();
expect(this.model.get('hasChanges')).toBe(false);
expect(this.featureModel.trigger).toHaveBeenCalledWith('saveFeatureSuccess', 'add', this.featureModel);
});
});
describe('when save fails', function () {
it('should not have changes', function () {
this.featureModel.save.calls.argsFor(0)[0].error();
expect(this.model.get('hasChanges')).toBe(true);
expect(this.featureModel.trigger).toHaveBeenCalledWith('saveFeatureFailed');
});
});
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,278 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryColumnsCollection = require('builder/data/query-columns-collection');
var EditFeatureAttributesFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-attributes-form-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var UserModel = require('builder/data/user-model');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var CDB = require('internal-carto.js');
describe('editor/layers/edit-feature-content-views/edit-feature-attributes-form-model', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.querySchemaModel = new QuerySchemaModel({}, {
configModel: this.configModel
});
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
this.layerDefinitionModel = layerDefinitionsCollection.at(0);
this.columnsCollection = new QueryColumnsCollection([
{
name: 'cartodb_id',
type: 'number'
}, {
name: 'string',
type: 'string'
}, {
name: 'boolean',
type: 'boolean'
}, {
name: 'number',
type: 'number'
}, {
name: 'date',
type: 'date'
}, {
name: 'created_at',
type: 'date'
}, {
name: 'the_geom',
type: 'geometry'
}, {
name: 'the_geom_webmercator',
type: 'geometry'
}, {
name: 'updated_at',
type: 'date'
}, {
name: 'coordonnees',
type: 'number[]'
}
], {
querySchemaModel: this.querySchemaModel,
configModel: this.configModel
});
this.featureModel = new Backbone.Model({
cartodb_id: 1,
string: 'string',
boolean: false,
number: 1,
date: '2016-10-21T11:27:39+00:00',
created_at: '2016-10-21T11:27:39+00:00',
the_geom: '{"type":"Point","coordinates":[0,0]}',
the_geom_webmercator: '{"type":"Point","coordinates":[0,0]}',
updated_at: '2016-10-21T11:27:39+00:00',
coordonnees: '48.1100259201,-1.6780371631'
});
this.formModel = new EditFeatureAttributesFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel,
columnsCollection: this.columnsCollection,
configModel: this.configModel,
nodeDefModel: this.layerDefinitionModel.getAnalysisDefinitionNodeModel()
});
});
it('should generate schema from columns collection, omit reserved columns, and assign default Text type', function () {
expect(this.formModel.schema).toEqual({
'cartodb_id': {
'type': 'Number',
'isFormatted': false,
'showSlider': false,
'editorAttrs': {
'disabled': true
}
},
'string': {
'type': 'Text'
},
'boolean': {
'type': 'Radio',
'options': [
{
'label': 'form-components.editors.radio.true',
'val': true
},
{
'label': 'form-components.editors.radio.false',
'val': false
},
{
'label': 'form-components.editors.radio.null',
'val': null
}
]
},
'number': {
'type': 'Number',
'isFormatted': true,
'showSlider': false
},
'date': {
'type': 'DateTime',
'dialogMode': DialogConstants.Mode.FLOAT
},
'coordonnees': {
'type': 'Text'
}
});
});
describe('when there is a column of type string', function () {
beforeEach(function () {
this.columnsCollection = new QueryColumnsCollection([
{
name: 'cartodb_id',
type: 'number'
}, {
name: 'names',
type: 'string'
}
], {
querySchemaModel: this.querySchemaModel,
configModel: this.configModel
});
this.featureModel = new Backbone.Model({
cartodb_id: 1,
names: 'carlos'
});
});
describe('when all values are null', function () {
var formModel;
beforeEach(function () {
spyOn(CDB.SQL.prototype, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [{
names: null
}, {
names: null
}, {
names: null
}],
fields: []
});
});
spyOn(EditFeatureAttributesFormModel.prototype, '_onChangeSchema');
formModel = new EditFeatureAttributesFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel,
columnsCollection: this.columnsCollection,
configModel: this.configModel,
nodeDefModel: this.layerDefinitionModel.getAnalysisDefinitionNodeModel()
});
});
it('should', function () {
expect(EditFeatureAttributesFormModel.prototype._onChangeSchema).not.toHaveBeenCalled();
expect(formModel.schema).toEqual({
'cartodb_id': {
'type': 'Number',
'isFormatted': false,
'showSlider': false,
'editorAttrs': {
'disabled': true
}
},
'names': {
'type': 'Text'
}
});
});
});
describe('when not all values are null', function () {
var formModel;
beforeEach(function () {
spyOn(CDB.SQL.prototype, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [{
names: 'pepe'
}, {
names: 'paco'
}, {
names: 'juan'
}],
fields: []
});
});
spyOn(EditFeatureAttributesFormModel.prototype, '_onChangeSchema');
formModel = new EditFeatureAttributesFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel,
columnsCollection: this.columnsCollection,
configModel: this.configModel,
nodeDefModel: this.layerDefinitionModel.getAnalysisDefinitionNodeModel()
});
});
it('should', function () {
expect(EditFeatureAttributesFormModel.prototype._onChangeSchema).toHaveBeenCalled();
expect(formModel.schema).toEqual({
cartodb_id: {
type: 'Number',
isFormatted: false,
showSlider: false,
editorAttrs: {
disabled: true
}
},
names: {
type: 'Suggest',
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
showSearch: true,
defaultValue: true,
allowFreeTextInput: true,
collectionData: [ 'pepe', 'paco', 'juan' ]
}
}
});
});
});
});
describe('when form data changes', function () {
it('should update feature model', function () {
expect(this.featureModel.get('boolean')).toBe(false);
this.formModel.set('boolean', true);
expect(this.featureModel.get('boolean')).toBe(true);
});
});
});
@@ -0,0 +1,54 @@
var Backbone = require('backbone');
var EditFeatureAttributesFormView = require('builder/editor/layers/edit-feature-content-views/edit-feature-attributes-form-view');
describe('editor/layers/edit-feature-content-views/edit-feature-attributes-form-view', function () {
beforeEach(function () {
this.model = new Backbone.Model({
string: 'string',
boolean: false,
number: 1,
date: '2016-10-21T11:27:39+00:00',
the_geom: '{"type":"Point","coordinates":[0,0]}'
});
this.view = new EditFeatureAttributesFormView({
model: this.model
});
this.view.render();
});
it('should render with data from form model', function () {
expect(this.view.$('form').length).toEqual(1);
});
describe('when schema changes', function () {
beforeEach(function () {
this.prev$form = this.view.$('form');
this.view.model.trigger('changeSchema');
});
afterEach(function () {
this.prev$form = null;
});
it('should re-render the form', function () {
expect(this.view.$('form').length).toEqual(1);
expect(this.view.$('form')).not.toBe(this.prev$form);
});
});
describe('when form is cleaned', function () {
beforeEach(function () {
spyOn(Backbone.Form.prototype, 'remove').and.callThrough();
this.view.clean();
});
it('should remove form when view is cleaned', function () {
expect(Backbone.Form.prototype.remove).toHaveBeenCalled();
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,16 @@
var EditFeatureControlView = require('builder/editor/layers/edit-feature-content-views/edit-feature-control-view');
describe('editor/layers/edit-feature-content-views/edit-feature-control-view', function () {
beforeEach(function () {
this.view = new EditFeatureControlView();
this.view.render();
});
it('should render properly', function () {
expect(this.view.$('.js-back').length).toBe(1);
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,218 @@
var Backbone = require('backbone');
require('backbone-forms');
var EditFeatureGeometryFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-geometry-form-model');
var EditFeatureGeometryPointFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-geometry-point-form-model');
describe('editor/layers/edit-feature-content-views/edit-feature-geometry-form-model', function () {
beforeEach(function () {
this.featureModel = new Backbone.Model({
the_geom: '{"type":"Point","coordinates":[0,0]}',
name: '',
description: ''
});
});
describe('type is point', function () {
beforeEach(function () {
this.formModel = new EditFeatureGeometryPointFormModel({
lat: 0,
lng: 0
}, {
featureModel: this.featureModel
});
});
it('should generate lat lng schema from geometry', function () {
expect(Object.keys(this.formModel.schema).length).toEqual(2);
expect(this.formModel.schema.lng.type).toEqual('Number');
expect(this.formModel.schema.lat.type).toEqual('Number');
});
describe('latitude is erroneous', function () {
it('should return regular error if lat is not a number', function () {
this.formModel.set({ lat: 'Look at me!', lng: 100 });
var form = new Backbone.Form({ model: this.formModel });
var validation = form.getEditor('lat').validate();
expect(validation.message).toBe('editor.edit-feature.valid-lat');
});
it('should return custom error if lat is out of bounds', function () {
this.formModel.set({ lng: 10, lat: -120 });
var form = new Backbone.Form({ model: this.formModel });
var validation = form.getEditor('lat').validate();
expect(validation.message).toBe('editor.edit-feature.out-of-bounds-lat');
});
});
describe('longitude is erroneous', function () {
it('should return regular error if lng is not a number', function () {
this.formModel.set({ lng: 'Look at me!', lat: 10 });
var form = new Backbone.Form({ model: this.formModel });
var validation = form.getEditor('lng').validate();
expect(validation.message).toBe('editor.edit-feature.valid-lng');
});
it('should return custom error if lng is out of bounds', function () {
this.formModel.set({ lng: -5000, lat: 10 });
var form = new Backbone.Form({ model: this.formModel });
var validation = form.getEditor('lng').validate();
expect(validation.message).toBe('editor.edit-feature.out-of-bounds-lng');
});
});
describe('form data changes', function () {
it('should update feature model', function () {
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[0,0]}');
this.formModel.set('lat', 3.141592653);
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[0,3.14159265]}');
this.formModel.set({
'lat': -90,
'lng': 180
});
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[180,-90]}');
this.formModel.set({
'lat': 0,
'lng': 0
});
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[0,0]}');
});
it('should only update the_geom once when lat and lng change so that changes can be detected', function () {
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[0,0]}');
this.formModel.set({
'lat': -89.0123456789,
'lng': 179.0123456789
});
expect(this.featureModel.get('the_geom')).toBe('{"type":"Point","coordinates":[179.01234568,-89.01234568]}');
expect(this.featureModel.hasChanged('the_geom')).toBeTruthy();
});
it('should set the_geom\'s feature without setting a previous one', function () {
this.featureModel.unset('the_geom');
this.formModel.set({ lat: 10, lng: 20 });
expect(this.featureModel.get('the_geom')).toEqual('{"type":"Point","coordinates":[20,10]}');
});
});
});
describe('type is not point', function () {
var formModel;
var featureModel;
var coords = [39.46975, -0.37739];
beforeEach(function () {
featureModel = new Backbone.Model({
the_geom: '{"type":"LineString","coordinates":[[0,0],[0,1]]}',
name: '',
description: ''
});
formModel = new EditFeatureGeometryFormModel(featureModel.toJSON(), {
featureModel: featureModel
});
});
describe('schema', function () {
it('should generate schema from geometry', function () {
expect(formModel.schema).toEqual({
the_geom: {
type: 'Text',
validators: [ 'required', jasmine.any(Function) ],
editorAttrs: { disabled: true },
hasCopyButton: true
}
});
});
it('should NOT include copy attr if feature does NOT have the_geom', function () {
featureModel.unset('the_geom');
formModel = new EditFeatureGeometryFormModel(featureModel.toJSON(), {
featureModel: featureModel
});
expect(formModel.schema.the_geom.hasCopyButton).toEqual(false);
});
});
describe('._formatCoordinate', function () {
it('should format properly the coordinate', function () {
expect(formModel._formatCoordinate(0)).toEqual(0);
expect(formModel._formatCoordinate(coords[0])).toEqual(39.46975);
expect(formModel._formatCoordinate(coords[1])).toEqual(-0.37739);
});
});
describe('._isValidCoordinate', function () {
it('should validate coordinate', function () {
expect(formModel._isValidCoordinate(null)).toBe(false);
expect(formModel._isValidCoordinate(0)).toBe(true);
expect(formModel._isValidCoordinate(coords[0])).toBe(true);
expect(formModel._isValidCoordinate(coords[1])).toBe(true);
expect(formModel._isValidCoordinate(coords[1].toString())).toBe(true);
});
});
describe('._getLatitude', function () {
it('should return latitude', function () {
expect(formModel._getLatitude(coords)).toBe(39.46975);
});
});
describe('._getLongitude', function () {
it('should return longitude', function () {
expect(formModel._getLongitude(coords)).toBe(-0.37739);
});
});
describe('._validateGeom', function () {
var the_geom_invalid_lng, the_geom_invalid_lat, the_geom_invalid_latlng, the_geom_valid;
var invalid_lng_message = '{"message":"editor.edit-feature.out-of-bounds-lng"}';
var invalid_lat_message = '{"message":"editor.edit-feature.out-of-bounds-lat"}';
var invalid_latlng_message = '{"message":"editor.edit-feature.out-of-bounds-lat editor.edit-feature.out-of-bounds-lng"}';
describe('type is line', function () {
it('should validate the_geom', function () {
the_geom_invalid_lng = '{"type":"LineString","coordinates":[[-200,0],[-190,0]]}';
the_geom_invalid_lat = '{"type":"LineString","coordinates":[[0,-110],[0,-100]]}';
the_geom_invalid_latlng = '{"type":"LineString","coordinates":[[-200,-110],[-190,-100]]}';
the_geom_valid = '{"type":"LineString","coordinates":[[0,0],[1,1]]}';
formModel._featureModel.isLine = function () { return true; };
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_lng))).toBe(invalid_lng_message);
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_lat))).toBe(invalid_lat_message);
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_latlng))).toBe(invalid_latlng_message);
expect(formModel._validateGeom(the_geom_valid)).toBe(undefined);
});
});
describe('type is not line', function () {
it('should validate the_geom', function () {
the_geom_invalid_lng = '{"type":"Polygon","coordinates":[[[-200,0],[-190,0],[-190,10],[-200,0]]]}';
the_geom_invalid_lat = '{"type":"Polygon","coordinates":[[[0,-110],[0,-100],[10,-100],[0,-110]]]}';
the_geom_invalid_latlng = '{"type":"Polygon","coordinates":[[[-200,-110],[-190,-100],[-190,-100],[-190,-110]]]}';
the_geom_valid = '{"type":"Polygon","coordinates":[[[0,0],[2,2],[1,1],[0,0]]]}';
formModel._featureModel.isLine = function () { return false; };
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_lng))).toBe(invalid_lng_message);
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_lat))).toBe(invalid_lat_message);
expect(JSON.stringify(formModel._validateGeom(the_geom_invalid_latlng))).toBe(invalid_latlng_message);
expect(formModel._validateGeom(the_geom_valid)).toBe(undefined);
});
});
});
});
});
@@ -0,0 +1,54 @@
var Backbone = require('backbone');
var EditFeatureGeometryFormView = require('builder/editor/layers/edit-feature-content-views/edit-feature-geometry-form-view');
describe('editor/layers/edit-feature-content-views/edit-feature-geometry-form-view', function () {
beforeEach(function () {
this.featureModel = new Backbone.Model({
the_geom: '{"type":"LineString","coordinates":[[0,0],[0,1]]}',
name: '',
description: ''
});
this.featureModel.getFeatureType = function () { return 'line'; };
this.featureModel.isPoint = function () { return false; };
this.view = new EditFeatureGeometryFormView({
model: this.featureModel
});
this.view.render();
});
it('should render with data from form model', function () {
expect(this.view.$('form').length).toEqual(1);
});
describe('when schema changes', function () {
beforeEach(function () {
this.prev$form = this.view.$('form');
this.view.model.trigger('changeSchema');
});
afterEach(function () {
this.prev$form = null;
});
it('should re-render the form', function () {
expect(this.view.$('form').length).toEqual(1);
expect(this.view.$('form')).not.toBe(this.prev$form);
});
});
describe('when form is cleaned', function () {
beforeEach(function () {
spyOn(Backbone.Form.prototype, 'remove').and.callThrough();
this.view.clean();
});
it('should remove form when view is cleaned', function () {
expect(Backbone.Form.prototype.remove).toHaveBeenCalled();
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,55 @@
var Backbone = require('backbone');
var EditFeatureHeaderView = require('builder/editor/layers/edit-feature-content-views/edit-feature-header-view');
var FactoryModals = require('../../../factories/modals');
describe('editor/layers/edit-feature-content-views/edit-feature-header-view', function () {
var letter = 'CustomLetter';
var layerName = 'LayerName';
var tableName = 'table_name';
beforeEach(function () {
this.model = {
getFeatureType: function () { return 'line'; }
};
var layerDefinitionModel = new Backbone.Model({
letter: letter
});
layerDefinitionModel.getColor = function () {
return '#fabada';
};
layerDefinitionModel.getName = function () {
return layerName;
};
layerDefinitionModel.tableName = function () {
return tableName;
};
this.view = new EditFeatureHeaderView({
url: 'http://',
tableName: tableName,
model: this.model,
modals: FactoryModals.createModalService(),
layerDefinitionModel: layerDefinitionModel,
isNew: false,
backAction: function () {}
});
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(this.view.$el.text()).toContain('back');
expect(this.view.$el.text()).toContain('editor.edit-feature.edit');
expect(this.view.$el.html()).toContain(layerName);
expect(this.view.$el.html()).toContain(letter);
expect(this.view.$el.html()).toContain('<a href="http://" target="_blank" title="' + tableName + '" class="Editor-headerLayerName">' + tableName + '</a>');
expect(this.view.$el.html()).toContain('js-context-menu');
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,102 @@
var _ = require('underscore');
var Backbone = require('backbone');
var EditFeatureInnerView = require('builder/editor/layers/edit-feature-content-views/edit-feature-inner-view');
var EditFeatureGeometryFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-geometry-form-model');
var EditFeatureAttributesFormModel = require('builder/editor/layers/edit-feature-content-views/edit-feature-attributes-form-model');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryColumnsCollection = require('builder/data/query-columns-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
describe('editor/layers/edit-feature-content-views/edit-feature-inner-view', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add({
id: 'l-1',
kind: 'carto',
options: {
table_name: 'foo'
}
});
var layerDefinitionModel = layerDefinitionsCollection.at(0);
this.querySchemaModel = new QuerySchemaModel({}, {
configModel: this.configModel
});
this.columnsCollection = new QueryColumnsCollection([
{
name: 'cartodb_id',
type: 'number'
}, {
name: 'name',
type: 'string'
}, {
name: 'description',
type: 'string'
}, {
name: 'the_geom',
type: 'geometry'
}
], {
querySchemaModel: this.querySchemaModel,
configModel: this.configModel
});
this.featureModel = new Backbone.Model({
the_geom: '{"type":"LineString","coordinates":[[0,0],[0,1]]}',
name: '',
description: ''
});
this.geometryFormModel = new EditFeatureGeometryFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel
});
this.attributesFormModel = new EditFeatureAttributesFormModel(this.featureModel.toJSON(), {
featureModel: this.featureModel,
columnsCollection: this.columnsCollection,
configModel: this.configModel,
nodeDefModel: layerDefinitionModel.getAnalysisDefinitionNodeModel()
});
this.featureModel.isPoint = function () { return false; };
this.featureModel.getFeatureType = function () { return 'line'; };
this.view = new EditFeatureInnerView({
featureModel: this.featureModel,
geometryFormModel: this.geometryFormModel,
attributesFormModel: this.attributesFormModel
});
this.view.render();
});
it('should render properly', function () {
expect(_.size(this.view._subviews)).toBe(2); // geometry, attributes
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,166 @@
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var LayerAnalysesView = require('builder/editor/layers/layer-analyses-view');
var LayerAnalysisViewFactory = require('builder/editor/layers/layer-analysis-view-factory');
describe('editor/layers/layer-analyses-view', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
status: 'ready',
query: 'SELECT * FROM own_source',
table_name: 'own_source'
});
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm-123',
stateDefinitionModel: {}
});
this.layerAnalysisViewFactory = new LayerAnalysisViewFactory(this.analysisDefinitionNodesCollection);
});
describe('given a layer definition with only its own source', function () {
beforeEach(function () {
this.layerDefinitionsCollection.add({
id: 'l1',
kind: 'carto',
source: 'a0',
options: {
table_name: 'own_source',
cartocss: 'asd'
}
});
this.view = new LayerAnalysesView({
model: this.layerDefinitionsCollection.get('l1'),
sortableSelector: '.js-layers',
layerAnalysisViewFactory: this.layerAnalysisViewFactory
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render the source', function () {
expect(this.view.$el.text()).toContain('a0');
expect(this.view.$el.text()).toContain('own_source');
});
});
describe('given a layer definition with an analysis within its own layer', function () {
beforeEach(function () {
this.analysisDefinitionNodesCollection.add({
id: 'b1',
type: 'trade-area',
status: 'ready',
params: {
kind: 'walk',
time: 300,
source: {
id: 'b0',
type: 'source',
table_name: 'foo',
params: {
query: 'SELECT * FROM foo'
}
}
}
});
this.layerDefinitionsCollection.add({
id: 'l1',
kind: 'carto',
options: {
table_name: 'foo',
cartocss: 'asd',
letter: 'b',
source: 'b1',
name: 'layerB'
}
});
this.view = new LayerAnalysesView({
model: this.layerDefinitionsCollection.get('l1'),
sortableSelector: '.js-layers',
layerAnalysisViewFactory: this.layerAnalysisViewFactory
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render the sources', function () {
expect(this.view.$el.text()).toContain('b1');
expect(this.view.$el.text()).toContain('analyses.area-of-influence');
expect(this.view.$el.text()).toContain('b0');
expect(this.view.$el.text()).toContain('foo');
});
describe('given a layer definition with an analysis referencing the head of other layer', function () {
beforeEach(function () {
this.analysisDefinitionNodesCollection.add({
id: 'c1',
type: 'trade-area',
status: 'ready',
params: {
kind: 'walk',
time: 200,
source: this.analysisDefinitionNodesCollection.get('b1').toJSON()
}
});
this.layerDefinitionsCollection.add({
id: 'l2',
kind: 'carto',
options: {
table_name: 'foo',
cartocss: 'asd',
letter: 'c',
source: 'c1',
name: 'layerC'
}
});
this.view = new LayerAnalysesView({
model: this.layerDefinitionsCollection.get('l2'),
sortableSelector: '.js-layers',
layerAnalysisViewFactory: this.layerAnalysisViewFactory
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render the own source node', function () {
expect(this.view.$el.text()).toContain('c1');
expect(this.view.$el.text()).toContain('analyses.area-of-influence');
});
it('should render the source of the other layer but not more', function () {
expect(this.view.$el.text()).toContain('b1');
expect(this.view.$el.text()).not.toContain('b0');
});
});
});
});
@@ -0,0 +1,74 @@
var $ = require('jquery');
var Backbone = require('backbone');
var LayerAnalysisDraggableView = require('builder/editor/layers/layer-analysis-draggable-view');
describe('editor/layers/layer-analysis-draggable-view', function () {
beforeEach(function () {
this.$nodeViewElement = $('div');
spyOn(this.$nodeViewElement, 'draggable').and.callThrough();
this.model = new Backbone.Model({
id: 'a1',
type: 'buffer'
});
this.view = new LayerAnalysisDraggableView({
layerDefinitionModel: new Backbone.Model(),
model: this.model,
getNextLetter: function () { return 'b'; },
sortableSelector: '.js-layers',
$nodeViewElement: this.$nodeViewElement
});
this.view.render();
});
afterEach(function () {
this.$nodeViewElement = null;
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
describe('should initialize draggable for given nodeView element', function () {
beforeEach(function () {
expect(this.$nodeViewElement.draggable).toHaveBeenCalled();
expect(this.$nodeViewElement.data('ui-draggable')).toBeDefined();
this.draggableArgs = this.$nodeViewElement.draggable.calls.argsFor(0)[0];
});
it('should connect draggable item to layers sortable list', function () {
expect(this.draggableArgs.connectToSortable).toEqual('.js-layers');
expect(this.draggableArgs.appendTo).toEqual('.js-layers');
});
describe('when dragged', function () {
beforeEach(function () {
// simulate dragging
this.html = this.draggableArgs.helper();
});
it('should create a helper HTML, previewing a new layer', function () {
expect(this.html).toEqual(jasmine.any(String));
expect(this.html).toContain('Layer');
expect(this.html).toContain('>b<', 'should have the next-letter representation');
expect(this.html).toContain('"a1"', 'should have the node-id the new layer will be based on');
expect(this.html).toContain('area-of-influence'); // title of a buffer type
});
});
});
describe('.clean', function () {
beforeEach(function () {
this.view.clean();
});
it('should remove draggable behavior from element', function () {
expect(this.$nodeViewElement.data('ui-draggable')).toBeUndefined();
});
it('should remove reference to DOM element', function () {
expect(this.view.options.$nodeViewElement).toBe(null);
});
});
});
@@ -0,0 +1,827 @@
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LayerContentView = require('builder/editor/layers/layer-content-view');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var EditorModel = require('builder/data/editor-model');
var UserNotifications = require('builder/data/user-notifications');
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
var FactoryModals = require('../../factories/modals');
var Router = require('builder/routes/router');
describe('editor/layers/layer-content-view/layer-content-view', function () {
var view;
var analysisDefinitionNodeModel;
var editorModel;
var layerDefinitionModel;
var dashboardCanvasEl;
var handleRouteSpy;
var analysesTabName = 'analyses';
var dataTabName = 'data';
var createViewFn = function (options) {
dashboardCanvasEl = document.createElement('div');
dashboardCanvasEl.className = 'CDB-Dashboard-canvas';
document.body.appendChild(dashboardCanvasEl);
var canvas = document.createElement('div');
canvas.classList.add('CDB-Map-canvas');
dashboardCanvasEl.appendChild(canvas);
var configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'pepe',
sql_api_protocol: 'http'
});
var userModel = new UserModel({}, {
configModel: configModel,
username: 'pepe'
});
var mapDefModel = new Backbone.Model({
legends: true
});
layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
kind: 'carto',
options: {
source: 'a0',
letter: 'a',
table_name: 'foo'
}
}, {
parse: true,
configModel: configModel
});
var onboardings = new Backbone.Model();
onboardings.create = function () {};
onboardings.destroy = function () {};
analysisDefinitionNodeModel = new AnalysisDefinitionNodeSourceModel({
id: 'a1',
type: 'source',
table_name: 'foo'
}, {
configModel: configModel,
userModel: userModel,
collection: new Backbone.Collection()
});
analysisDefinitionNodeModel.querySchemaModel.attributes.query = 'SELECT * FROM foo';
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: new Backbone.Collection(),
mapId: 'map-123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add(layerDefinitionModel);
var widgetDefinitionsCollection = new Backbone.Collection();
widgetDefinitionsCollection.isThereTimeSeries = function () {
return false;
};
widgetDefinitionsCollection.isThereOtherWidgets = function () {
return false;
};
spyOn(layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(analysisDefinitionNodeModel);
spyOn(layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.returnValue(analysisDefinitionNodeModel);
spyOn(Router, 'goToLayerList');
spyOn(Router, 'navigate');
handleRouteSpy = spyOn(LayerContentView.prototype, '_handleRoute');
var onboardingNotification = new UserNotifications({}, {
key: 'builder',
configModel: configModel
});
var visDefinitionModel = new Backbone.Model();
var stackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['prevStep', 'goToStep']);
AnalysesService.init({
onboardings: onboardings,
layerDefinitionsCollection: layerDefinitionsCollection,
modals: FactoryModals.createModalService(),
userModel: userModel,
configModel: configModel
});
editorModel = new EditorModel({ edition: false });
var viewOptions = {
mapDefinitionModel: mapDefModel,
layerDefinitionModel: layerDefinitionModel,
analysisDefinitionsCollection: new Backbone.Collection(),
analysisDefinitionNodesCollection: new Backbone.Collection(),
layerDefinitionsCollection: layerDefinitionsCollection,
legendDefinitionsCollection: new Backbone.Collection(),
widgetDefinitionsCollection: widgetDefinitionsCollection,
userModel: userModel,
modals: FactoryModals.createModalService(),
onboardings: onboardings,
analysis: {},
userActions: {},
vis: {},
stackLayoutModel: stackLayoutModel,
configModel: configModel,
editorModel: editorModel,
mapModeModel: jasmine.createSpyObj('mapModeModel', ['enterDrawingFeatureMode']),
stateDefinitionModel: {},
onboardingNotification: onboardingNotification,
visDefinitionModel: visDefinitionModel
};
return new LayerContentView(_.extend(viewOptions, options));
};
describe('.initialize', function () {
beforeEach(function () {
spyOn(LayerContentView.prototype, '_updateViewState').and.callThrough();
spyOn(LayerContentView.prototype, '_initViewState').and.callThrough();
spyOn(LayerContentView.prototype, '_initBinds').and.callThrough();
spyOn(AnalysesService, 'setLayerId').and.callThrough();
view = createViewFn();
});
it('should initialize correctly', function () {
expect(view.model._querySchemaModel).toBeDefined();
expect(view.model._queryGeometryModel).toBeDefined();
expect(view.model._queryRowsCollection).toBeDefined();
expect(LayerContentView.prototype._updateViewState).toHaveBeenCalled();
expect(LayerContentView.prototype._initViewState).toHaveBeenCalled();
expect(LayerContentView.prototype._initBinds).toHaveBeenCalled();
expect(AnalysesService.setLayerId).toHaveBeenCalled();
});
});
describe('.render', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view, '_renderHeaderView').and.callThrough();
spyOn(view, '_renderLayerTabPaneView').and.callThrough();
});
it('should render correctly', function () {
view.render();
expect(_.size(view._subviews)).toBe(3); // [Header, Geometry Toolips]
expect(view.$('.Editor-HeaderInfo-titleText').text()).toContain('foo');
});
it('should have no leaks', function () {
expect(view).toHaveNoLeaks();
});
describe('when all query objects are loaded', function () {
beforeEach(function () {
view.render();
});
it('should render correctly', function () {
expect(_.size(view._subviews)).toBe(3); // [Header, TabPane, Geometry Toolips]
expect(view.$('.Editor-HeaderInfo-titleText').text()).toContain('foo');
expect(view.$('.CDB-NavMenu .CDB-NavMenu-item').length).toBe(5);
expect(view.$('.js-editorPanelContent').length).toBe(1); // Onboarding
});
});
describe('when can be georeferenced', function () {
beforeEach(function () {
view._viewState.set('canBeGeoreferenced', true);
view.render();
});
it('should disable tabs', function () {
expect(view.$el.find('.CDB-NavMenu-item.is-disabled').length).toBe(3);
});
it('should select the analysis tab', function () {
expect(view._layerTabPaneView.getSelectedTabPaneName()).toBe(analysesTabName);
});
});
describe('when analysisDefinitionNodeModel has errors', function () {
beforeEach(function () {
spyOn(analysisDefinitionNodeModel, 'hasFailed').and.returnValue(true);
view.render();
});
it('should disable tabs properly', function () {
expect(view.$el.find('.CDB-NavMenu-item.is-disabled').length).toBe(3);
});
it('should select the analysis tab', function () {
expect(view._layerTabPaneView.getSelectedTabPaneName()).toBe(analysesTabName);
});
});
describe('when is empty', function () {
beforeEach(function () {
spyOn(analysisDefinitionNodeModel.querySchemaModel, 'isDone').and.returnValue(true);
spyOn(analysisDefinitionNodeModel.queryGeometryModel, 'isDone').and.returnValue(true);
spyOn(analysisDefinitionNodeModel.queryRowsCollection, 'isDone').and.returnValue(true);
spyOn(analysisDefinitionNodeModel, 'hasFailed').and.returnValue(false);
view._viewState.set({
isLayerEmpty: true
});
view.render();
});
it('should select the data tab', function () {
expect(view._layerTabPaneView.getSelectedTabPaneName()).toBe(dataTabName);
});
});
});
describe('._initViewState', function () {
beforeEach(function () {
view = createViewFn();
});
it('should initialize viewState', function () {
view._initViewState();
expect(view._viewState).toBeDefined();
expect(view._viewState.get('isLayerEmpty')).toEqual(false);
expect(view._viewState.get('canBeGeoreferenced')).toEqual(false);
expect(view._viewState.get('isLayerDone')).toEqual(false);
});
it('should call setViewState', function () {
spyOn(view, '_setViewState');
view._initViewState();
expect(view._setViewState).toHaveBeenCalled();
});
});
describe('._updateViewState', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view, '_fetchAllQueryObjects').and.returnValue(Promise.resolve(true));
spyOn(view, '_setViewState').and.callThrough();
});
it('should call _setViewState if isDone', function () {
spyOn(view.model, 'isDone').and.returnValue(true);
view._updateViewState();
expect(view._setViewState).toHaveBeenCalled();
});
it('should call _setViewState after all queries have been fetched', function (done) {
spyOn(view.model, 'isDone').and.returnValue(false);
view._updateViewState();
setTimeout(function () {
expect(view._setViewState).toHaveBeenCalled();
done();
}, 0);
});
});
describe('._setViewState', function () {
beforeEach(function () {
view = createViewFn();
});
describe('set isLayerEmpty', function () {
it('should be setted to true if layer is empty', function (done) {
view.render();
spyOn(view, 'render');
spyOn(view._layerDefinitionModel, 'isEmptyAsync').and.returnValue(Promise.resolve(true));
spyOn(view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
view._setViewState();
setTimeout(function () {
expect(view._viewState.get('isLayerEmpty')).toEqual(true);
expect(view._viewState.get('canBeGeoreferenced')).toEqual(true);
done();
}, 0);
});
});
describe('set canBeGeoreferenced', function () {
it('should be setted to true if layer can not be georeferenced', function (done) {
view._viewState.set({
isLayerEmpty: true,
canBeGeoreferenced: false
});
view.render();
spyOn(view._layerDefinitionModel, 'isEmptyAsync').and.returnValue(Promise.resolve(true));
spyOn(view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
view._setViewState();
setTimeout(function () {
expect(view._viewState.get('canBeGeoreferenced')).toEqual(true);
done();
}, 0);
});
it('should be setted to false if layer can be georeferenced', function (done) {
view._viewState.set({
isLayerEmpty: true,
canBeGeoreferenced: true
});
view.render();
spyOn(view._layerDefinitionModel, 'isEmptyAsync').and.returnValue(Promise.resolve(true));
spyOn(view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(false));
view._setViewState();
setTimeout(function () {
expect(view._viewState.get('canBeGeoreferenced')).toEqual(false);
done();
}, 0);
});
});
describe('set isLayerDone', function () {
// isLayerDone can be false when entering and the UI should respond to that
it('should set isLayerDone when entering the function and again when the promise resolves', function (done) {
view._viewState.set('isLayerDone', true, { silent: true });
spyOn(view._layerDefinitionModel, 'isEmptyAsync').and.returnValue(Promise.resolve(true));
spyOn(view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(false));
spyOn(view._layerDefinitionModel, 'isDone').and.returnValue(false);
view._setViewState();
expect(view._viewState.get('isLayerDone')).toBe(false);
view._layerDefinitionModel.isDone.and.returnValue(true);
setTimeout(function () {
expect(view._viewState.get('isLayerDone')).toEqual(true);
done();
}, 0);
});
});
});
describe('._renderLayerTabPaneView', function () {
it('should disable all tabs but data is layer is empty', function () {
view = createViewFn();
view._viewState.set({
isLayerEmpty: true,
isLayerDone: true
}, { silent: true });
view._renderLayerTabPaneView();
var tabs = view.$('nav > ul > li');
expect(tabs[0].classList.contains('is-disabled')).toBe(false);
expect(tabs[1].classList.contains('is-disabled')).toBe(true);
expect(tabs[2].classList.contains('is-disabled')).toBe(true);
expect(tabs[3].classList.contains('is-disabled')).toBe(true);
expect(tabs[4].classList.contains('is-disabled')).toBe(true);
});
it('should enable all tabs but data is layer is not empty', function () {
view = createViewFn();
view._viewState.set({
isLayerEmpty: false,
isLayerDone: true
}, { silent: true });
view._renderLayerTabPaneView();
var tabs = view.$('nav > ul > li');
expect(tabs[0].classList.contains('is-disabled')).toBe(false);
expect(tabs[1].classList.contains('is-disabled')).toBe(false);
expect(tabs[2].classList.contains('is-disabled')).toBe(false);
expect(tabs[3].classList.contains('is-disabled')).toBe(false);
expect(tabs[4].classList.contains('is-disabled')).toBe(false);
});
});
describe('._onClickBack', function () {
beforeEach(function () {
view = createViewFn();
view.render();
});
it('should call goToLayerList in Router', function () {
view.$('.js-back').click();
expect(Router.goToLayerList).toHaveBeenCalled();
});
it('should set editorModel edition to false', function () {
editorModel.set('edition', true, { silent: true });
view.$('.js-back').click();
expect(editorModel.get('edition')).toBe(false);
});
});
describe('.handleRoute', function () {
beforeEach(function () {
view = createViewFn();
view.render();
});
it('should create editOverlay', function () {
handleRouteSpy.and.callThrough();
var routeModel = new Backbone.Model({
currentRoute: ['add_feature_']
});
view._handleRoute(routeModel);
expect($(dashboardCanvasEl).find('.js-editOverlay').length).toBe(1);
});
});
describe('._renderContextButtons', function () {
var analysisDefNodeModel;
var layerDefinitionModel;
var queryGeometryModel;
var needsGeoSpy;
var isEmptySpy;
function hasTipsy (subviews) {
var newGeomItems = _.filter(subviews, function (elem) {
return elem.el.classList.contains('js-newGeometryView');
});
if (newGeomItems.length === 0) return false;
return newGeomItems.reduce(function (hasTipsy, btn) {
return btn.tipsy && hasTipsy;
}, true);
}
beforeEach(function () {
view = createViewFn();
view.render();
analysisDefNodeModel = view._getAnalysisDefinitionNodeModel();
layerDefinitionModel = view._layerDefinitionModel;
queryGeometryModel = view._getQueryGeometryModel();
spyOn(analysisDefNodeModel, 'isReadOnly');
needsGeoSpy = spyOn(view._layerDefinitionModel, 'canBeGeoreferenced');
isEmptySpy = spyOn(view._layerDefinitionModel, 'isEmptyAsync');
view._renderContextButtons();
});
afterEach(function () {
view._destroyContextButtons();
});
it('shouldn\'t disable tabs', function () {
needsGeoSpy.and.returnValue(Promise.resolve(false));
isEmptySpy.and.returnValue(Promise.resolve(false));
view.render();
expect(view.$el.find('.CDB-NavMenu-item.is-disabled').length).toBe(0);
});
it('should not create editOverlay tip view', function () {
expect($(dashboardCanvasEl).find('js-editOverlay').length).toBe(0);
});
it('should navigate when click on new geometry', function () {
queryGeometryModel.set('simple_geom', 'point');
analysisDefNodeModel.isReadOnly.and.returnValue(false);
layerDefinitionModel.set('visible', true);
view._renderContextButtons();
$(dashboardCanvasEl).find('.js-newGeometry').trigger('click');
expect(Router.navigate).toHaveBeenCalled();
});
it('should render context switch toggler properly', function () {
expect($(dashboardCanvasEl).find('.Editor-contextSwitcher.js-mapTableView').length).toBe(1);
});
it('should render geometry buttons properly', function () {
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem').length).toBe(3);
});
it('should disable edit geometry buttons when node is read only and visible', function () {
analysisDefNodeModel.isReadOnly.and.returnValue(true);
layerDefinitionModel.set('visible', true);
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem.is-disabled').length).toBe(3);
});
it('should disable edit geometry buttons when node is read only and not visible', function () {
analysisDefNodeModel.isReadOnly.and.returnValue(true);
layerDefinitionModel.set('visible', false);
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem.is-disabled').length).toBe(3);
});
it('should disable edit geometry buttons when node is neither visible nor read only', function () {
analysisDefNodeModel.isReadOnly.and.returnValue(false);
layerDefinitionModel.set('visible', false);
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem.is-disabled').length).toBe(3);
});
it('should not disable edit geometry buttons when node is visible and not read only', function () {
analysisDefNodeModel.isReadOnly.and.returnValue(false);
layerDefinitionModel.set('visible', true);
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem.is-disabled').length).toBe(0);
});
it('should add tipsy subview when not visible or read only', function () {
view.clearSubViews();
analysisDefNodeModel.isReadOnly.and.returnValue(true);
layerDefinitionModel.set('visible', false);
expect(hasTipsy(view._subviews)).toBe(true);
});
it('should not add tipsy subview when not visible or read only', function () {
view.clearSubViews();
analysisDefNodeModel.isReadOnly.and.returnValue(false);
layerDefinitionModel.set('visible', true);
expect(hasTipsy(view._subviews)).toBe(false);
});
it('should display only one button per geometry', function () {
queryGeometryModel.set('simple_geom', 'polygon');
view._renderContextButtons();
expect($(dashboardCanvasEl).find('.Editor-contextSwitcherItem.js-newGeometryItem').length).toBe(1);
});
it('should show the button for the simple_geom type', function () {
queryGeometryModel.set('simple_geom', 'point');
view._renderContextButtons();
expect($(dashboardCanvasEl).find('.js-newGeometry[data-feature-type="point"]').length).toBe(1);
queryGeometryModel.set('simple_geom', 'line');
view._renderContextButtons();
expect($(dashboardCanvasEl).find('.js-newGeometry[data-feature-type="line"]').length).toBe(1);
queryGeometryModel.set('simple_geom', 'polygon');
view._renderContextButtons();
expect($(dashboardCanvasEl).find('.js-newGeometry[data-feature-type="polygon"]').length).toBe(1);
});
});
describe('._initBinds', function () {
function buildResponse (geom) {
return {
status: 200,
contentType: 'application/json; charset=utf-8',
responseText: '{"rows":[{"the_geom":"' + geom + '"}],"time":0.005,"fields":{"the_geom":{"type":"string"}},"total_rows":1}'
};
}
beforeEach(function () {
view = createViewFn();
jasmine.Ajax.install();
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should call _renderContextButtons and set default style properties if there is a change in simple_geom to a valid value', function () {
var queryGeometryModel = view._getQueryGeometryModel();
queryGeometryModel.set('status', 'unfetched');
queryGeometryModel.set('simple_geom', 'point');
queryGeometryModel.set('query', 'SELECT TOP 10 FROM fake_table;');
spyOn(view, '_renderContextButtons');
jasmine.Ajax.stubRequest(/sql/)
.andReturn(buildResponse('polygon'));
view._initBinds();
queryGeometryModel.set('simple_geom', 'polygon');
expect(view._renderContextButtons).toHaveBeenCalled();
});
it('should call _renderContextButtons and not set default style properties if there is a change in simple_geom to nothing', function () {
var queryGeometryModel = view._getQueryGeometryModel();
queryGeometryModel.set('status', 'unfetched');
queryGeometryModel.set('simple_geom', 'point');
queryGeometryModel.set('query', 'SELECT TOP 10 FROM fake_table;');
spyOn(view, '_renderContextButtons');
jasmine.Ajax.stubRequest(/sql/)
.andReturn(buildResponse(''));
view._initBinds();
queryGeometryModel.set('simple_geom', '');
expect(view._renderContextButtons).toHaveBeenCalled();
});
it('should call _onTogglerChanged if togglerModel:active changes', function () {
spyOn(view, '_onTogglerChanged');
view._initBinds();
view._togglerModel.trigger('change:active');
expect(view._onTogglerChanged).toHaveBeenCalled();
});
it('should call handleRoute if routeModel:currentRoute changes', function () {
var routeModel = Router.getRouteModel();
routeModel.trigger('change:currentRoute', routeModel);
expect(view._handleRoute).toHaveBeenCalled();
});
it('should call render if analysisDefinitionNodeModel:error changes', function () {
spyOn(view, 'render');
view._initBinds();
analysisDefinitionNodeModel.trigger('change:error');
expect(view.render).toHaveBeenCalled();
});
it('should call _updateViewState on change model state', function () {
spyOn(view, 'render');
spyOn(view, '_updateViewState');
view._initBinds();
view.model.set('state', 'fake');
expect(view._updateViewState).toHaveBeenCalled();
});
it('should call _updateViewState on change layerDefinitionModel source', function () {
spyOn(view, 'render');
spyOn(view, '_updateViewState');
view._initBinds();
view._layerDefinitionModel.set('source', 'fake');
expect(view._updateViewState).toHaveBeenCalled();
});
}); // end of '._initBinds'
describe('._onTogglerChanged', function () {
beforeEach(function () {
view = createViewFn();
view.render();
});
it('should set context correctly', function () {
expect(view.model.get('context')).toEqual('map');
view._togglerModel.set('active', true, { silent: true });
view._onTogglerChanged();
expect(view.model.get('context')).toEqual('table');
view._togglerModel.set('active', false, { silent: true });
view._onTogglerChanged();
expect(view.model.get('context')).toEqual('map');
});
it('should call _initTable if context is table', function () {
spyOn(view, '_initTable');
view._togglerModel.set('active', true, { silent: true });
view._onTogglerChanged();
expect(view._initTable).toHaveBeenCalled();
});
it('should call _destroyTable if context is map', function () {
spyOn(view, '_destroyTable');
view._togglerModel.set('active', false, { silent: true });
view._onTogglerChanged();
expect(view._destroyTable).toHaveBeenCalled();
});
});
describe('._isContextTable', function () {
beforeEach(function () {
view = createViewFn();
view.render();
});
it('should return true if model context is table', function () {
expect(view._isContextTable()).toBe(false);
view.model.set('context', 'table', { silent: true });
expect(view._isContextTable()).toBe(true);
});
});
describe('when rendering analysis tab', function () {
var addBindingsAndRender = function () {
spyOn(analysisDefinitionNodeModel.querySchemaModel, 'isDone').and.returnValue(true);
spyOn(analysisDefinitionNodeModel.queryGeometryModel, 'isDone').and.returnValue(true);
spyOn(analysisDefinitionNodeModel.queryRowsCollection, 'isDone').and.returnValue(true);
view.render();
};
describe('when analysis payload is a string and its in the formsCollection', function () {
it('should use the analysis payload as selectedNodeId', function () {
var analysisPayload = 'a1';
spyOn(AnalysisFormsCollection.prototype, 'get').and.returnValue(analysisPayload);
view = createViewFn({
selectedTabItem: 'analyses',
analysisPayload: analysisPayload
});
addBindingsAndRender();
var analysesContentView = _.find(view._layerTabPaneView._subviews, function (view) {
return view.className === 'Editor-content';
});
expect(analysesContentView._selectedNodeId).toEqual(analysisPayload);
});
});
describe('when analysis payload is an object', function () {
it('should use the analysis payload id key as selectedNodeId', function () {
var analysisPayload = {
id: 'a2'
};
view = createViewFn({
selectedTabItem: 'analyses',
analysisPayload: analysisPayload
});
addBindingsAndRender();
var analysesContentView = _.find(view._layerTabPaneView._subviews, function (view) {
return view.className === 'Editor-content';
});
expect(analysesContentView._selectedNodeId).toEqual(analysisPayload.id);
});
});
describe('when analysis payload is null', function () {
describe('when last node exists', function () {
it('should redirect to the last node id', function () {
var lastAnalysisNode = new Backbone.Model({
id: 'a1'
});
spyOn(AnalysisFormsCollection.prototype, 'at').and.returnValue(lastAnalysisNode);
spyOn(Router, 'goToAnalysisNode');
view = createViewFn({
selectedTabItem: 'analyses',
analysisPayload: null
});
addBindingsAndRender();
expect(Router.goToAnalysisNode).toHaveBeenCalledWith(
layerDefinitionModel.get('id'),
lastAnalysisNode.get('id'),
{ trigger: false, replace: true }
);
});
});
describe('when there is no nodes left', function () {
it('should redirect to the analysis tab', function () {
spyOn(Router, 'goToAnalysisTab');
view = createViewFn({
selectedTabItem: 'analyses',
analysisPayload: null
});
addBindingsAndRender();
expect(Router.goToAnalysisTab).toHaveBeenCalledWith(
layerDefinitionModel.get('id'),
{ trigger: false, replace: true }
);
});
});
});
});
afterEach(function () {
dashboardCanvasEl.parentNode && dashboardCanvasEl.parentNode.removeChild(dashboardCanvasEl);
view.clean();
});
});
@@ -0,0 +1,440 @@
var _ = require('underscore');
var Backbone = require('backbone');
var UserModel = require('builder/data/user-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var AnalysesContentView = require('builder/editor/layers/layer-content-views/analyses/analyses-content-view');
var ConfigModel = require('builder/data/config-model');
var LayerContentModel = require('builder/data/layer-content-model');
var analysisPlaceholderTemplate = require('builder/editor/layers/layer-content-views/analyses/analyses-placeholder.tpl');
var analysisSQLErrorTemplate = require('builder/editor/layers/layer-content-views/analyses/analyses-sql-error.tpl');
var actionErrorTemplate = require('builder/editor/layers/sql-error-action.tpl');
var ScrollView = require('builder/components/scroll/scroll-view');
var AnalysisControlsView = require('builder/components/scroll/scroll-view');
var Router = require('builder/routes/router');
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
describe('editor/layers/layer-content-view/analyses-content-view', function () {
var view;
var userActions;
var analysisFormsCollection;
var layerDefinitionModel;
var analysisDefinitionNodesCollection;
var overlayModel;
var layerContentModel;
var deleteAnalysisModel;
var stackLayoutModel;
var renderTooltipSpy;
var renderSpy;
var onLayerDefSourceChangedSpy;
var setDefaultSelectedNodeIdSpy;
var toggleOverlaySpy;
var removeUselessModelsSpy;
var onAnalysisFormsCollectionSyncSpy;
var nodeA0;
var nodeA1;
var nodeB1;
var formA0;
var formA1;
var formB1;
var createViewFn = function (options) {
var configModel = new ConfigModel({
user_name: 'pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
spyOn(userModel, 'fetch');
userActions = jasmine.createSpyObj('userActions', ['saveLayer']);
deleteAnalysisModel = new Backbone.Model({
analysisId: null
});
overlayModel = new Backbone.Model({
visible: false
});
analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
nodeA0 = analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
table_name: 'foo',
query: 'SELECT * FROM table_name'
});
nodeA1 = analysisDefinitionNodesCollection.add({
id: 'a1',
type: 'buffer',
radio: 300,
source: 'a0'
});
nodeB1 = analysisDefinitionNodesCollection.add({
id: 'b1',
type: 'buffer',
radio: 300,
source: 'a1'
});
layerDefinitionModel = new LayerDefinitionModel({
id: 'a0',
letter: 'a',
source: 'a0',
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
}
}, {
configModel: configModel,
collection: new Backbone.Collection(),
parse: true
});
layerDefinitionModel.findAnalysisDefinitionNodeModel = function (id) {
switch (id) {
case 'a0': return nodeA0;
case 'a1': return nodeA1;
}
};
analysisFormsCollection = new AnalysisFormsCollection(null, {
configModel: configModel,
userActions: userActions,
layerDefinitionModel: layerDefinitionModel,
analysisSourceOptionsModel: new Backbone.Model()
});
analysisFormsCollection.isEmpty = function () { return true; };
var querySchemaModel = new Backbone.Model();
querySchemaModel.hasRepeatedErrors = function () { return false; };
var queryGeometryModel = new Backbone.Model();
queryGeometryModel.hasRepeatedErrors = function () { return false; };
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
layerContentModel.isErrored = function () { return false; };
stackLayoutModel = {};
var defaultOptions = {
userActions,
analysisDefinitionNodesCollection,
analysisFormsCollection,
layerDefinitionModel,
userModel,
configModel,
stackLayoutModel,
overlayModel,
layerContentModel,
deleteAnalysisModel
};
var view = new AnalysesContentView(_.extend(defaultOptions, options));
renderTooltipSpy = spyOn(view, '_renderTooltip');
return view;
};
beforeEach(function () {
renderSpy = spyOn(AnalysesContentView.prototype, 'render');
onLayerDefSourceChangedSpy = spyOn(AnalysesContentView.prototype, '_onLayerDefSourceChanged');
setDefaultSelectedNodeIdSpy = spyOn(AnalysesContentView.prototype, '_setDefaultSelectedNodeId');
toggleOverlaySpy = spyOn(AnalysesContentView.prototype, '_toggleOverlay');
onAnalysisFormsCollectionSyncSpy = spyOn(AnalysesContentView.prototype, '_onAnalysisFormsCollectionSync');
removeUselessModelsSpy = spyOn(AnalysisFormsCollection.prototype, 'removeUselessModels');
view = createViewFn();
});
describe('.render', function () {
it('should render properly', function () {
renderSpy.and.callThrough();
spyOn(view, '_initViews');
view.render();
expect(view._initViews).toHaveBeenCalled();
expect(toggleOverlaySpy).toHaveBeenCalled();
});
it('should not have any leaks', function () {
renderSpy.and.callThrough();
view.render();
expect(view).toHaveNoLeaks();
});
describe('is errored', function () {
it('should render error', function () {
renderSpy.and.callThrough();
view._isErrored = function () { return true; };
spyOn(view, '_renderError');
view.render();
expect(view._renderError).toHaveBeenCalled();
});
});
});
describe('._isErrored', function () {
it('should return if layer content model is errored', function () {
expect(view._isErrored()).toBe(false);
layerContentModel.isErrored = function () { return true; };
expect(view._isErrored()).toBe(true);
});
});
describe('._renderError', function () {
it('should render error', function () {
var template = analysisSQLErrorTemplate({
body: _t('editor.error-query.body', {
action: actionErrorTemplate({
label: _t('editor.error-query.label')
})
})
});
spyOn(view.$el, 'html');
view._renderError();
expect(view.$el.html).toHaveBeenCalledWith(template);
});
});
describe('._initViews', function () {
it('should init views', function () {
var template = analysisPlaceholderTemplate();
spyOn(view.$el, 'html');
view._initViews();
expect(view.$el.html).toHaveBeenCalledWith(template);
expect(renderTooltipSpy).toHaveBeenCalled();
});
describe('analysis forms collection is not empty', function () {
it('should init views', function () {
analysisFormsCollection.isEmpty = function () { return false; };
var formModel = 'wadus';
spyOn(view, '_getFormModel').and.returnValue(formModel);
spyOn(view, '_renderScrollableListView');
spyOn(view, '_renderControlsView');
view._initViews();
expect(view._renderScrollableListView).toHaveBeenCalledWith(formModel);
expect(view._renderControlsView).toHaveBeenCalledWith(formModel);
});
});
});
describe('._toggleOverlay', function () {
it('should toggle overlay', function () {
toggleOverlaySpy.and.callThrough();
expect(view.$el.hasClass('is-disabled')).toBe(false);
overlayModel.set('visible', true, { silent: true });
view._toggleOverlay();
expect(view.$el.hasClass('is-disabled')).toBe(true);
});
});
describe('_renderTooltip', function () {
it('should renderTooltip', function () {
renderSpy.and.callThrough();
view.render();
const currentViews = _.size(view._subviews);
renderTooltipSpy.and.callThrough();
view._renderTooltip();
expect(_.size(view._subviews)).toBe(currentViews + 1);
});
});
describe('_initBinds', function () {
it('should listen to layerDefinitionModel change source', function () {
layerDefinitionModel.set('source', 'a1');
expect(onLayerDefSourceChangedSpy).toHaveBeenCalled();
});
it('should listen to analysisFormsCollection destroyedModels', function () {
analysisFormsCollection.trigger('destroyedModels');
expect(setDefaultSelectedNodeIdSpy).toHaveBeenCalled();
});
it('should listen to analysisFormsCollection remove', function () {
analysisFormsCollection.reset([{}]);
analysisFormsCollection.remove(analysisFormsCollection.at(0));
expect(setDefaultSelectedNodeIdSpy).toHaveBeenCalled();
});
it('should listen to overlayModel change visible', function () {
overlayModel.set('visible', true);
expect(toggleOverlaySpy).toHaveBeenCalled();
});
it('should listen to analysisFormsCollection change persisted', function () {
var analysisFormModel = analysisFormsCollection.add({ persisted: false });
analysisFormModel.set('persisted', true);
expect(onAnalysisFormsCollectionSyncSpy).toHaveBeenCalled();
});
});
describe('_renderScrollableListView', function () {
it('should render scrollable list view', function () {
spyOn(ScrollView.prototype, 'render').and.returnValue(this);
view._renderScrollableListView();
expect(_.size(view._subviews)).toBe(1); // ['ScrollView']
});
});
describe('_renderControlsView', function () {
it('should render constrols view', function () {
spyOn(AnalysisControlsView.prototype, 'render').and.returnValue(this);
var formModel = new Backbone.Model({
id: 'wadus',
type: 'type'
});
view._renderControlsView(formModel);
expect(_.size(view._subviews)).toBe(1); // ['AnalysisControlsView']
});
});
describe('_getFormModel', function () {
afterEach(function () {
analysisFormsCollection.reset([]);
});
it('should get form model', function () {
formA0 = analysisFormsCollection.add(nodeA0.attributes);
view = createViewFn({
analysisFormsCollection: analysisFormsCollection
});
expect(view._getFormModel()).toBe(formA0);
});
describe('with selectedNodeId', function () {
it('should get form model', function () {
formA0 = analysisFormsCollection.add(nodeA0.attributes);
formA1 = analysisFormsCollection.add(nodeA1.attributes);
view = createViewFn({
selectedNodeId: formA1.get('id'),
analysisFormsCollection: analysisFormsCollection
});
expect(view._getFormModel()).toBe(formA1);
});
});
});
describe('_getQuerySchemaModelForEstimation', function () {
afterEach(function () {
analysisFormsCollection.reset([]);
});
it('should get QuerySchemaModel', function () {
spyOn(layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(nodeA0);
view = createViewFn({
analysisFormsCollection: analysisFormsCollection
});
expect(view._getQuerySchemaModelForEstimation()).toBe(nodeA0.querySchemaModel);
});
describe('with selectedNodeId', function () {
it('should get form model', function () {
formA0 = analysisFormsCollection.add(nodeA0.attributes);
formA1 = analysisFormsCollection.add(nodeA1.attributes);
formB1 = analysisFormsCollection.add(nodeB1.attributes);
view = createViewFn({
selectedNodeId: formB1.get('id'),
analysisFormsCollection: analysisFormsCollection
});
expect(view._getQuerySchemaModelForEstimation()).toBe(nodeA1.querySchemaModel);
});
});
});
describe('_onLayerDefSourceChanged', function () {
it('should remove models from the analysis forms collection', function () {
onLayerDefSourceChangedSpy.and.callThrough();
view._onLayerDefSourceChanged();
expect(removeUselessModelsSpy).toHaveBeenCalled();
});
});
describe('_setDefaultSelectedNodeId', function () {
afterEach(function () {
analysisFormsCollection.reset([]);
});
it('should call Route.goToAnalysisNode with selectedNodeId', function () {
setDefaultSelectedNodeIdSpy.and.callThrough();
spyOn(Router, 'goToAnalysisNode');
spyOn(AnalysesService, 'clearNotAppliedAnalysis');
formA0 = analysisFormsCollection.add(nodeA0.attributes);
view._setDefaultSelectedNodeId();
expect(AnalysesService.clearNotAppliedAnalysis).toHaveBeenCalledWith(nodeA0.get('id'));
expect(Router.goToAnalysisNode).toHaveBeenCalledWith(layerDefinitionModel.get('id'), nodeA0.get('id'));
});
});
describe('_onAnalysisFormsCollectionSync', function () {
it('should call AnalysesService.clearNotAppliedAnalysis', function () {
onAnalysisFormsCollectionSyncSpy.and.callThrough();
spyOn(AnalysesService, 'clearNotAppliedAnalysis');
view._onAnalysisFormsCollectionSync(nodeA0);
expect(AnalysesService.clearNotAppliedAnalysis).toHaveBeenCalledWith(nodeA0.get('id'));
});
});
});
@@ -0,0 +1,259 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AreaOfInfluenceFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model', function () {
beforeEach(function () {
this._configModel = new ConfigModel({base_url: '/u/pepe'});
this._configModel.dataServiceEnabled = function () {
return true;
};
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'buffer',
query: 'SELECT * FROM a_table'
}, {
configModel: this._configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'buffer',
radius: undefined,
source: 'a0'
}, {
configModel: this._configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: this._configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
analyses: analyses,
configModel: this._configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'buffer',
distance: 'meters',
radius: 100,
isolines: 1,
dissolved: false
});
});
it('should not have disabled types', function () {
spyOn(this.model._analyses, 'isAnalysisValidByType').and.returnValue(true);
this.model._setSchema();
var typeOptions = this.model.schema.type.options.map(function (d) {
return _.pick(d, 'val', 'disabled');
});
expect(typeOptions).toEqual([
{val: 'buffer', disabled: false},
{val: 'trade-area', disabled: false}
]);
expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
});
describe('when model is created with custom distance and radius', function () {
beforeEach(function () {
this.a1.set({
radius: 2000,
distance: 'kilometers'
});
this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
analyses: analyses,
configModel: this._configModel,
userActions: {},
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should convert the radius to the distance scale', function () {
expect(this.model.get('radius')).toEqual(2);
expect(this.model.get('distance')).toEqual('kilometers');
});
});
describe('when source is not valid for other type', function () {
beforeEach(function () {
this.a0.isValidAsInputForType.and.returnValue(false);
this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
analyses: analyses,
configModel: this._configModel,
userActions: {},
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should disable type since source is not valid input for the other type', function () {
var typeOptions = this.model.schema.type.options.map(function (d) {
return _.pick(d, 'val', 'disabled');
});
expect(typeOptions).toEqual([
{val: 'buffer', disabled: false},
{val: 'trade-area', disabled: true}
]);
expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
});
});
describe('when type is changed', function () {
describe('for a new model', function () {
beforeEach(function () {
this.prevSchema = this.model.schema;
this.model.set('type', 'trade-area');
});
it('should update the schema', function () {
expect(this.model.schema).not.toBe(this.prevSchema);
});
it('should replace the attributes but maintain persisted state', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'trade-area',
kind: 'walk',
isolines: 1,
time: 100,
dissolved: false
});
});
});
describe('for an persisted model', function () {
beforeEach(function () {
this.prevSchema = this.model.schema;
this.model.set('persisted', true);
this.model.set('type', 'trade-area');
});
it('should update the schema', function () {
expect(this.model.schema).not.toBe(this.prevSchema);
});
it('should replace the attributes but maintain persisted state', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'trade-area',
kind: 'walk',
isolines: 1,
time: 100,
dissolved: false,
persisted: true
});
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('type,kind,time,isolines,dissolved');
});
});
});
describe('.getTemplateData', function () {
it('should return the default data for given type', function () {
var bufferTemplateData = this.model.getTemplateData();
expect(bufferTemplateData).toEqual({
parametersDataFields: jasmine.any(String)
});
this.model.set('type', 'trade-area');
expect(this.model.getTemplateData()).toEqual({
parametersDataFields: jasmine.any(String)
});
expect(this.model.getTemplateData().parametersDataFields).not.toEqual(bufferTemplateData.parametersDataFields);
});
});
describe('.updateNodeDefinition', function () {
beforeEach(function () {
this.model.updateNodeDefinition(this.a1);
});
it('should replace the attributes on the given node model but maintain values not visible/editable in the form', function () {
expect(this.a1.attributes).toEqual({
id: 'a1',
type: 'buffer',
source: 'a0',
radius: 100,
isolines: 1,
distance: 'meters',
dissolved: false
});
});
it('should convert string values to integers before setting the attrs on model', function () {
this.model.set({
type: 'trade-area',
dissolved: 'true',
isolines: '3',
time: '60'
});
this.model.updateNodeDefinition(this.a1);
expect(this.a1.attributes).toEqual({
dissolved: true,
isolines: 3,
time: 60,
type: 'trade-area',
id: 'a1',
source: 'a0',
kind: 'walk'
});
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('type,distance,radius,isolines,dissolved');
});
it('should convert radius to meters according to the selected distance', function () {
this.model.set({
distance: 'kilometers',
radius: 3
});
this.model.updateNodeDefinition(this.a1);
expect(this.a1.get('radius')).toEqual(3000);
expect(this.a1.get('distance')).toEqual('kilometers');
});
});
});
@@ -0,0 +1,30 @@
var AREA_OF_INFLUENCE_TYPES = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-types');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-types', function () {
AREA_OF_INFLUENCE_TYPES.forEach(function (d) {
it('should have a type', function () {
expect(d.type).toEqual(jasmine.any(String));
});
describe('area-of-influence type ' + d.type, function () {
it('should have a label', function () {
expect(d.label).toEqual(jasmine.any(String));
});
it('should have parametersDataFields defined with which schema props to use', function () {
expect(d.parametersDataFields).toEqual(jasmine.any(String), 'on the format of "source,type,some_other,…"');
expect(d.parametersDataFields).not.toContain(' ', 'the parametersDataFields must not contain any spaces');
});
it('should have a parse function', function () {
expect(d.parse).toEqual(jasmine.any(Function));
expect(d.parse.length).toEqual(1, 'should take one argument, which is the attrs from the analysis-definition-node-model');
});
it('should have a formatAttrs function', function () {
expect(d.formatAttrs).toEqual(jasmine.any(Function));
expect(d.formatAttrs.length).toEqual(1, 'should take one argument of the area-of-influence-form-model');
});
});
});
});
@@ -0,0 +1,380 @@
var Backbone = require('backbone');
var camshaftReference = require('builder/data/camshaft-reference');
var UserActions = require('builder/data/user-actions');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var BaseAnalysisFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model', function () {
beforeEach(function () {
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: {}
});
this.userActions = UserActions({
userModel: {},
analysisDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
widgetDefinitionsCollection: {}
});
this.model = new BaseAnalysisFormModel(null, {
analyses: analyses,
configModel: {},
userActions: this.userActions,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: new Backbone.Model({
fetching: true
})
});
spyOn(camshaftReference, 'validate');
});
describe('.validate', function () {
it('should be valid if passing camshaft validation', function () {
camshaftReference.validate.and.returnValue({some_required_param: 'is missing'});
expect(this.model.isValid()).toBe(false);
camshaftReference.validate.and.returnValue(undefined);
expect(this.model.isValid()).toBe(true);
});
it('should return errors set through setFormValidationErrors', function () {
expect(this.model.isValid()).toBe(true);
expect(this.model.validationError).toBeNull();
this.changeSpy = jasmine.createSpy('change');
this.model.on('change', this.changeSpy);
this.model.setFormValidationErrors({some_prop: 'invalid'});
expect(this.model.isValid()).toBe(false);
expect(this.model.validationError).toEqual({some_prop: 'invalid'});
expect(this.changeSpy).toHaveBeenCalled();
this.changeSpy.calls.reset();
this.model.setFormValidationErrors(undefined);
expect(this.model.isValid()).toBe(true);
expect(this.model.validationError).toBeNull();
expect(this.changeSpy).toHaveBeenCalled();
});
});
describe('.updateNodeDefinition', function () {
beforeEach(function () {
this.nodeDefModel = new Backbone.Model({id: 'a1'});
spyOn(this.nodeDefModel, 'set');
this.model.set({
id: 'a1',
type: 'trade-area',
isolines: '1',
time: '123.75',
dissolved: 'false',
non_default_param: {}
});
this.expectedFormattedAttrs = {
id: 'a1',
type: 'trade-area',
isolines: 1,
time: 123.75,
dissolved: false,
non_default_param: {}
};
camshaftReference.validate.and.returnValue(true);
spyOn(camshaftReference, 'parse').and.returnValue(this.expectedFormattedAttrs);
this.model.updateNodeDefinition(this.nodeDefModel);
});
it('should parse attrs before', function () {
expect(camshaftReference.parse).toHaveBeenCalledWith(this.model.attributes);
});
it('should update the node with parsed attrs', function () {
expect(this.nodeDefModel.set).toHaveBeenCalled();
expect(this.nodeDefModel.set.calls.argsFor(0)[0]).toEqual(this.expectedFormattedAttrs);
});
});
describe('.createNodeDefinition', function () {
beforeEach(function () {
this.nodeDefModel = new Backbone.Model({id: 'a1'});
spyOn(this.nodeDefModel, 'set');
this.model.set({
id: 'b1',
type: 'trade-area',
isolines: '1',
time: '123.75',
dissolved: 'false',
non_default_param: {}
});
this.expectedFormattedAttrs = {
id: 'b1',
type: 'trade-area',
isolines: 1,
time: 123.75,
dissolved: false,
non_default_param: {}
};
camshaftReference.validate.and.returnValue(true);
spyOn(camshaftReference, 'parse').and.returnValue(this.expectedFormattedAttrs);
spyOn(this.userActions, 'createAnalysisNode').and.returnValue(this.nodeDefModel);
this.model.createNodeDefinition(this.userActions);
});
it('should parse attrs before', function () {
expect(camshaftReference.parse).toHaveBeenCalledWith(this.model.attributes);
});
it('should create a new node model', function () {
expect(this.userActions.createAnalysisNode).toHaveBeenCalledWith(this.expectedFormattedAttrs, this.layerDefinitionModel);
});
it('should mark the model as persisted', function () {
expect(this.model.get('persisted')).toBe(true);
});
});
describe('.getTemplateData', function () {
it('should return an empty object', function () {
expect(this.model.getTemplateData()).toEqual({});
});
});
describe('._isPrimarySource', function () {
it('should return if the attr argument is the primary source', function () {
this.model.set('primary_source_name', 'target');
expect(this.model._isPrimarySource('target')).toBe(true);
expect(this.model._isPrimarySource('left_source')).toBe(false);
});
});
describe('._isFetchingOptions', function () {
it('should return true if it is fetching options', function () {
expect(this.model._isFetchingOptions()).toBe(true);
});
});
describe('._getSourceOption', function () {
it('should call to _getSourceNodeDefModel to retrieve the sourceNodeDefModel', function () {
var nodeDefModel = new Backbone.Model({
id: 'wutang'
});
nodeDefModel.isSourceType = function () { return true; };
nodeDefModel.getColor = function () { return 'magenta'; };
spyOn(this.model, '_getSourceNodeDefModel').and.returnValue(nodeDefModel);
var sourceOption = this.model._getSourceOption();
expect(sourceOption[0].val).toEqual(nodeDefModel.id);
});
});
describe('_getSourceNodeDefModel', function () {
beforeEach(function () {
this.model.set('source', 'a0', { silent: true });
this.model.set('primary_source', 'z1', { silent: true });
});
it('should return first the `source` node definition model', function () {
spyOn(this.model._layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (name) {
return (name === 'a0' || name === 'z1')
? 'the ' + name
: null;
});
var sourceNodeDefModel = this.model._getSourceNodeDefModel();
expect(sourceNodeDefModel).toEqual('the a0');
});
it('should return the `primary_source` node definition model in case there is no `source`', function () {
spyOn(this.model._layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (name) {
return name === 'z1'
? 'the z1'
: null;
});
var sourceNodeDefModel = this.model._getSourceNodeDefModel();
expect(sourceNodeDefModel).toEqual('the z1');
});
});
describe('._getSourceOptionsForSource', function () {
it('should throw error if no sourceAttrName provided', function () {
var self = this;
function wrap () {
self.model._getSourceOptionsForSource({});
}
expect(wrap).toThrow(Error('sourceAttrName is required.'));
});
it('should return source if it is the primary source and ignorePrimarySource is false', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
spyOn(this.model, '_isPrimarySource').and.returnValue(true);
var result = this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName
});
expect(result).toEqual(['the source']);
});
it('should return object with isLoading if it is fetching options', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
spyOn(this.model, '_isPrimarySource').and.returnValue(true);
spyOn(this.model, '_isFetchingOptions').and.returnValue(true);
var result = this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName,
ignorePrimarySource: true
});
expect(result).toEqual([{
val: 'the source',
label: 'editor.layers.analysis-form.loading',
isLoading: true
}]);
});
it('should return source options if it is fetched', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
this.model._layerDefinitionModel.set('source', 808);
this.model._analysisSourceOptionsModel.getSelectOptions = function (geomType) {
return [
{
val: 606,
type: 'node'
}, {
val: 707,
type: 'other'
}, {
val: 808,
type: 'other'
}
];
};
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
var result = this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName,
ignorePrimarySource: true
});
expect(result).toEqual([
{
val: 606,
type: 'node'
}, {
val: 707,
type: 'other'
}
]);
});
it('should return only source options with type node if it is fetched and onlyNodes option provided', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
this.model._layerDefinitionModel.set('source', 808);
this.model._analysisSourceOptionsModel.getSelectOptions = function () {};
spyOn(this.model._analysisSourceOptionsModel, 'getSelectOptions').and.returnValue([
{
val: 606,
type: 'node'
}, {
val: 707,
type: 'other'
}, {
val: 808,
type: 'other'
}
]);
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
var result = this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName,
ignorePrimarySource: true,
onlyNodes: true
});
expect(result).toEqual([{
val: 606,
type: 'node'
}]);
expect(this.model._analysisSourceOptionsModel.getSelectOptions).toHaveBeenCalledWith('*');
});
it('should call to getSelectOptions with the right type of geom if option is provided', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
this.model._layerDefinitionModel.set('source', 808);
this.model._analysisSourceOptionsModel.getSelectOptions = function () {};
spyOn(this.model._analysisSourceOptionsModel, 'getSelectOptions').and.returnValue([]);
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName,
ignorePrimarySource: true,
onlyNodes: true,
requiredSimpleGeometryType: 'point'
});
expect(this.model._analysisSourceOptionsModel.getSelectOptions).toHaveBeenCalledWith('point');
});
it('should return the layer source with the options if includeSourceNode param is provided', function () {
var sourceAttrName = 'target';
this.model.set(sourceAttrName, 'the source');
this.model._layerDefinitionModel.set('source', 1);
this.model._analysisSourceOptionsModel.getSelectOptions = function () {};
spyOn(this.model._analysisSourceOptionsModel, 'getSelectOptions').and.returnValue([
{
val: 3,
type: 'node'
}, {
val: 2,
type: 'other'
}, {
val: 1,
type: 'node'
}
]);
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
var result = this.model._getSourceOptionsForSource({
sourceAttrName: sourceAttrName,
includeSourceNode: true
});
expect(result).toEqual([{
val: 3,
type: 'node'
}, {
val: 2,
type: 'other'
}, {
val: 1,
type: 'node'
}]);
expect(this.model._analysisSourceOptionsModel.getSelectOptions).toHaveBeenCalledWith('*');
});
});
});
@@ -0,0 +1,151 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var CentroidFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/centroid-form-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/centroid-form-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'centroid',
query: 'SELECT * FROM a_table'
}, {
configModel: configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'centroid',
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new CentroidFormModel(this.a1.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'centroid',
category: '',
weight: '',
aggregate: '',
aggregation_column: '',
aggregation: 'count'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
});
it('should bind properly', function () {
this.model.set('category', 'wadus');
expect(this.model.get('category_column')).toBe('wadus');
this.model.set('category', '');
expect(this.model.get('category_column')).not.toBeDefined();
this.model.set('weight', 'wadus');
expect(this.model.get('weight_column')).toBe('wadus');
this.model.set('weight', '');
expect(this.model.get('weight_column')).not.toBeDefined();
this.model.set('aggregate', {operator: 'avg', attribute: 'wadus'});
expect(this.model.get('aggregation_column')).toBe('wadus');
expect(this.model.get('aggregation')).toBe('avg');
});
describe('type centroid', function () {
beforeEach(function () {
this.model.set('category', 'wadus');
});
it('should have type centroid', function () {
expect(this.model.get('type')).toBe('centroid');
});
it('should not include weight_column', function () {
expect(this.model.get('weight_column')).not.toBeDefined();
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'centroid',
category: 'wadus',
category_column: 'wadus',
weight: '',
aggregate: '',
aggregation_column: '',
aggregation: 'count'
});
});
});
describe('type weighted-centroid', function () {
beforeEach(function () {
this.model.set('weight', 'wadus');
});
it('should change type to weighted-centroid', function () {
expect(this.model.get('type')).toBe('weighted-centroid');
});
it('should include weight_column', function () {
expect(this.model.get('weight_column')).toBeDefined();
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'weighted-centroid',
category: '',
weight: 'wadus',
weight_column: 'wadus',
aggregate: '',
aggregation_column: '',
aggregation: 'count'
});
});
});
});
@@ -0,0 +1,299 @@
var Backbone = require('backbone');
var cdb = require('internal-carto.js');
var Model = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/closest-form-model');
var BaseAnalysisFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model');
var analyses = require('builder/data/analyses');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/closest-form-model', function () {
var cdbSQLBackup;
beforeEach(function () {
this.configModel = new Backbone.Model({});
this.layerDefinitionModel = new Backbone.Model({});
this.layerDefinitionModel.getName = function () { return 'Layer Name'; };
this.layerDefinitionModel.getTableName = function () { return 'table_name'; };
this.layerDefinitionModel.findAnalysisDefinitionNodeModel = function () {
var node = {
id: 808,
letter: function () { return 'A'; },
getColor: function () { return '#fabada'; },
isSourceType: function () { return true; },
querySchemaModel: new Backbone.Model({
query: 'SELECT * FROM somewhere;'
})
};
return node;
};
this.layerDefinitionModel.getName = function () { return 'Metro Madrid'; };
this.analysisSourceOptionsModel = new Backbone.Model({
fetching: false
});
this.analysisSourceOptionsModel.getSelectOptions = function () {
return [];
};
this.analysisSourceOptionsModel.createSourceNodeUnlessExisting = function () {};
cdbSQLBackup = cdb.SQL;
cdb.SQL = function () {
return {
execute: function () {}
};
};
this.initializeOptions = {
analyses: analyses,
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel
};
this.model = new Model(null, this.initializeOptions);
});
afterEach(function () {
cdb.SQL = cdbSQLBackup;
});
describe('parse', function () {
it('should set default value to responses property', function () {
var parsed = this.model.parse({});
expect(parsed.responses).toBe(1);
});
it('should not modify responses propertyif present', function () {
var parsed = this.model.parse({responses: 32});
expect(parsed.responses).toBe(32);
});
});
describe('initialize', function () {
it('should call proper function and hook events', function () {
spyOn(BaseAnalysisFormModel.prototype, 'initialize');
spyOn(this.model, '_resetColumnOptions');
spyOn(this.model, '_initBinds');
spyOn(this.model, '_setSchema');
spyOn(this.model, '_fetchColumns');
this.model.initialize(null, this.initializeOptions);
expect(BaseAnalysisFormModel.prototype.initialize).toHaveBeenCalled();
expect(this.model._resetColumnOptions).toHaveBeenCalled();
expect(this.model._initBinds).toHaveBeenCalled();
expect(this.model._setSchema).toHaveBeenCalled();
expect(this.model._fetchColumns).toHaveBeenCalled();
});
});
describe('_initBinds', function () {
it('should hook up change:fetching and change:target events', function () {
spyOn(this.model, '_onSourceOptionsFetched');
this.model._initBinds();
this.model._analysisSourceOptionsModel.set('fetching', true);
expect(this.model._events['change:target']).toBeDefined();
expect(this.model._onSourceOptionsFetched).toHaveBeenCalled();
});
});
describe('.getTemplate', function () {
it('should return the template', function () {
var template = this.model.getTemplate();
var templateContent = template({linkContent: '', hasTarget: false});
expect(templateContent.indexOf('<form>')).toBeGreaterThan(-1);
expect(templateContent.indexOf('data-fields="source,target"')).toBeGreaterThan(-1);
expect(templateContent.indexOf('data-fields="responses,category"')).toBeGreaterThan(-1);
});
it('should disable 2nd step fields if hasTarget false', function () {
var template = this.model.getTemplate();
var templateContent = template({linkContent: '', hasTarget: false});
expect(templateContent.indexOf('Editor-HeaderInfo is-disabled')).toBeGreaterThan(-1);
});
it('should not disable 2nd step fields if hasTarget true', function () {
var template = this.model.getTemplate();
var templateContent = template({linkContent: '', hasTarget: true});
expect(templateContent.indexOf('Editor-HeaderInfo is-disabled')).toBe(-1);
});
});
describe('.getTemplateData', function () {
it('should return an object with hasTarget', function () {
var templateData = this.model.getTemplateData();
expect(templateData).toEqual({ hasTarget: false });
});
});
describe('._resetColumnOptions', function () {
it('should reset _columnOptions and hook up "columnsFetched" event', function () {
this.model._columnOptions.set('aProperty', 'some value');
spyOn(this.model, '_onColumnsFetched');
this.model._resetColumnOptions();
this.model._columnOptions.trigger('columnsFetched');
expect(this.model._columnOptions.aProperty).not.toBeDefined();
expect(this.model._onColumnsFetched).toHaveBeenCalled();
});
});
describe('._onSourceOptionsFetched', function () {
it('should call _setSchema', function () {
spyOn(this.model, '_setSchema');
this.model._onSourceOptionsFetched();
expect(this.model._setSchema).toHaveBeenCalled();
});
});
describe('._setSchema', function () {
it('should call _setSchema of BaseAnalysisFormModel with proper schema', function () {
spyOn(BaseAnalysisFormModel.prototype, '_setSchema');
spyOn(this.model, '_primarySourceSchemaItem').and.callFake(function (title) {
return title;
});
spyOn(this.model, '_getSourceOptionsForSource').and.callFake(function (sourceAttrName) {
return sourceAttrName;
});
spyOn(this.model, '_isSourceDisabled').and.callFake(function (source) {
return 'called with ' + source;
});
spyOn(this.model._columnOptions, 'filterByType').and.callFake(function (type) { return type; });
var expectedSchema = {
source: 'editor.layers.analysis-form.base-layer',
target: {
type: 'NodeDataset',
title: 'editor.layers.analysis-form.target',
options: {sourceAttrName: 'target', includeSourceNode: true},
dialogMode: DialogConstants.Mode.FLOAT,
validators: ['required'],
editorAttrs: {
disabled: 'called with target'
}
},
category: {
type: 'EnablerEditor',
title: '',
label: 'editor.layers.analysis-form.group-by',
help: 'editor.layers.analysis-form.find-nearest.categorized-help',
editor: {
type: 'Select',
options: 'string',
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
showLabel: false
}
}
},
responses: {
type: 'Number',
title: 'editor.layers.analysis-form.find-nearest.max-results',
value: 1,
validators: ['required', {
type: 'interval',
min: 1,
max: 32
}]
}
};
this.model._setSchema();
expect(BaseAnalysisFormModel.prototype._setSchema).toHaveBeenCalledWith(expectedSchema);
});
});
describe('._isSourceDisabled', function () {
it('should return if attr is primary source or it is fetching, combination 1', function () {
spyOn(this.model, '_isPrimarySource').and.returnValue(true);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
var result = this.model._isSourceDisabled('target');
expect(this.model._isPrimarySource).toHaveBeenCalledWith('target');
expect(result).toBe(true);
});
it('should return if attr is primary source or it is fetching, combination 2', function () {
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(true);
var result = this.model._isSourceDisabled('target');
expect(this.model._isFetchingOptions).toHaveBeenCalled();
expect(result).toBe(true);
});
it('should return if attr is primary source or it is fetching, combination 3', function () {
spyOn(this.model, '_isPrimarySource').and.returnValue(false);
spyOn(this.model, '_isFetchingOptions').and.returnValue(false);
var result = this.model._isSourceDisabled('target');
expect(result).toBe(false);
});
});
describe('._onChangeTarget', function () {
it('should reset category and fetch columnds', function () {
this.model.set('target', 'madrid');
spyOn(this.model, '_setSchema');
spyOn(this.model, '_fetchColumns');
spyOn(this.model._analysisSourceOptionsModel, 'createSourceNodeUnlessExisting');
this.model._onChangeTarget();
expect(this.model.get('category')).toEqual('');
expect(this.model._setSchema).toHaveBeenCalled();
expect(this.model._analysisSourceOptionsModel.createSourceNodeUnlessExisting).toHaveBeenCalledWith('madrid');
expect(this.model._fetchColumns).toHaveBeenCalled();
});
});
describe('._fetchColumns', function () {
beforeEach(function () {
this.model.set('target', 'a layer');
this.layerDefinitionModel.findAnalysisDefinitionNodeModel = function () {};
});
it('should set node in columnOptions if target is a node', function () {
spyOn(this.model._layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.returnValue('node');
spyOn(this.model._columnOptions, 'setNode');
this.model._fetchColumns();
expect(this.model._layerDefinitionModel.findAnalysisDefinitionNodeModel).toHaveBeenCalledWith('a layer');
expect(this.model._columnOptions.setNode).toHaveBeenCalledWith('node');
});
it('should set target dataset in columnOptions if target is a dataset', function () {
spyOn(this.model._layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.returnValue(null);
spyOn(this.model._columnOptions, 'setDataset');
this.model._fetchColumns();
expect(this.model._layerDefinitionModel.findAnalysisDefinitionNodeModel).toHaveBeenCalledWith('a layer');
expect(this.model._columnOptions.setDataset).toHaveBeenCalledWith('a layer');
});
});
describe('._onColumnsFetched', function () {
it('should call _setSchema', function () {
spyOn(this.model, '_setSchema');
this.model._onColumnsFetched();
expect(this.model._setSchema).toHaveBeenCalled();
});
});
});
@@ -0,0 +1,79 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var DataObservatoryMeasureModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-measure-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-measure-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'concave-hull',
query: 'SELECT * FROM a_table'
}, {
configModel: configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'data-observatory-measure',
final_column: 'my column!!!',
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new DataObservatoryMeasureModel(this.a1.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(5);
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
final_column: 'my_column',
type: 'data-observatory-measure',
segment_name: null
});
});
});
@@ -0,0 +1,141 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var DataObservatoryMeasureModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-multiple-measure-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-multiple-measure-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.querySchemaModel = new Backbone.Model({
query: 'select * from wadus'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'data-observatory-multiple-measures',
area: 'calafornia',
numerators: ['foo', 'bar'],
column_names: ['final_column', 'otra'],
denominators: [null, null],
numerator_timespans: [null, null],
geom_ids: ['wadus', null],
normalizations: ['area', 'area'],
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.a0.querySchemaModel = this.querySchemaModel;
this.a0.queryGeometryModel = new Backbone.Model();
this.a0.queryGeometryModel.isFetched = function () { return true; };
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.a0);
this.model = new DataObservatoryMeasureModel(this.a0.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('filter Global region', function () {
spyOn(this.model.regions.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [
{
num_measurements: 2,
region_id: 'section/tags.global',
region_name: '"Global"'
}, {
num_measurements: 634,
region_id: 'section/tags.united_states',
region_name: '"United States"'
}
]
});
});
this.model.regions.fetch();
expect(this.model.regions.length).toBe(2);
var options = this.model._getRegionOptions();
expect(options.length).toBe(1);
expect(options[0].get('label')).toBe('United States');
});
it('should generate schema properly', function () {
expect(Object.keys(this.model.schema).length).toBe(3);
expect(this.model.schema.measurements).toBeDefined();
});
it('should generate measurements fields properly when region is selected', function () {
this.model.set('area', null);
var fields = this.model.getTemplateData().fields;
expect(fields).toBe('');
this.model.set('area', 'wadus');
fields = this.model.getTemplateData().fields;
expect(fields).toBe('measurements');
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
type: 'data-observatory-multiple-measures',
source: 'a0',
area: 'calafornia',
numerators: ['foo', 'bar'],
column_names: ['final_column', 'otra'],
denominators: [null, null],
normalizations: ['area', 'area'],
numerator_timespans: [null, null],
geom_ids: ['wadus', null]
});
});
describe('validate', function () {
it('no values should not return error', function () {
spyOn(this.model, '_getNumerators').and.returnValue([]);
spyOn(this.model, '_getColumnNames').and.returnValue([]);
expect(this.model.validate()).toBeUndefined();
});
it('empty values should return error', function () {
spyOn(this.model, '_getNumerators').and.returnValue(['']);
spyOn(this.model, '_getColumnNames').and.returnValue(['']);
expect(this.model.validate()).not.toBeUndefined();
});
it('mismatch length should return error', function () {
spyOn(this.model, '_getNumerators').and.returnValue(['foo', 'bar']);
spyOn(this.model, '_getColumnNames').and.returnValue(['wadus']);
expect(this.model.validate()).not.toBeUndefined();
});
it('right values should not return error', function () {
spyOn(this.model, '_getNumerators').and.returnValue(['foo']);
spyOn(this.model, '_getColumnNames').and.returnValue(['wadus']);
expect(this.model.validate()).toBeUndefined();
});
});
});
@@ -0,0 +1,134 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var DataObservatoryMeasureModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-multiple-nested-measure-model');
var RegionsCollection = require('builder/data/data-observatory/regions-collection');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/data-observatory-multiple-nested-measure-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.querySchemaModel = new Backbone.Model({
query: 'select * from wadus'
});
this.nodeDefModel = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'data-observatory-multiple-measures',
numerators: ['foo', 'bar'],
column_names: ['final_column', 'otra'],
denominators: [null, null],
numerator_timespans: [null, null],
geom_ids: ['wadus', null],
normalizations: ['area', 'area'],
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.nodeDefModel.queryGeometryModel = new Backbone.Model();
this.nodeDefModel.queryGeometryModel.isFetched = function () { return true; };
this.regions = new RegionsCollection([], {
configModel: configModel,
nodeDefModel: this.nodeDefModel
});
spyOn(this.regions.SQL, 'execute').and.callFake(function (query, vars, params) {
var payload = {
rows: [
{
num_measurements: 2,
region_id: 'section/tags.global',
region_name: '"Global"'
}, {
num_measurements: 634,
region_id: 'section/tags.united_states',
region_name: '"United States"'
}
]
};
params && params.success(payload);
});
this.regions.fetch();
this.form = new Backbone.View();
this.form.commit = jasmine.createSpy('commit');
this.form.model = new Backbone.Model({
area: 'Wadus'
});
this.model = new DataObservatoryMeasureModel(this.nodeDefModel.attributes, {
configModel: configModel,
nodeDefModel: this.nodeDefModel,
regions: this.regions,
form: this.form
});
spyOn(this.model.measurement.SQL, 'execute').and.callFake(function (query, vars, params) {
var payload = {
rows: [
{
numer_id: 'us.zillow.AllHomes_Zhvi',
numer_name: 'Zillow Home Value Index for All homes',
numer_tags: '{"subsection/tags.housing": "Housing", "license/us.zillow.zillow-license": "Zillow Terms of Use for Aggregate Data", "subsection/us.census.acs.segments": "US Population Segments"}'
},
{
numer_id: 'us.census.acs.B19083001',
numer_name: 'Gini Index',
numer_tags: '{"subsection/tags.age_gender": "Age and Gender", "license/tags.norestrictions": "Unrestricted"}'
},
{
numer_id: 'us.census.acs.B01001002',
numer_name: 'Male Population',
numer_tags: '{"subsection/tags.age_gender": "Age and Gender", "license/tags.norestrictions": "Unrestricted"}'
}
]
};
params && params.success(payload);
});
});
it('should generate schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(6);
});
describe('suggested column', function () {
beforeEach(function () {
spyOn(this.model.columnName.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [
{
obs_getmeta: [
{
suggested_name: 'wadus_2010_2014'
}
]
}
]
});
});
spyOn(this.model, '_getColumnNameOptions').and.returnValue({
success: this.model._columnNameSuccess,
numer_id: 'wadus'
});
});
it('should update suggested column', function () {
this.model._fetchNewColumn();
expect(this.model.get('column_name')).toBe('wadus_2010_2014');
});
});
});
@@ -0,0 +1,396 @@
var Backbone = require('backbone');
var cdb = require('internal-carto.js');
var FormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/deprecated-sql-function-form-model');
var BaseAnalysisFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model');
var analyses = require('builder/data/analyses');
var availableFunctionsQuery = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/available-functions.tpl');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/deprecated-sql-function-form-model', function () {
var cdbSQLBackup;
beforeEach(function () {
this.configModel = new Backbone.Model({});
cdbSQLBackup = cdb.SQL;
cdb.SQL = function () {
return {
execute: function () {}
};
};
this.layerDefinitionModel = new Backbone.Model({});
this.layerDefinitionModel.getName = function () { return 'Layer Name'; };
this.layerDefinitionModel.getTableName = function () { return 'table_name'; };
this.layerDefinitionModel.findAnalysisDefinitionNodeModel = function () {
var node = {
id: 808,
letter: function () { return 'A'; },
getColor: function () { return '#fabada'; },
isSourceType: function () { return true; },
querySchemaModel: new Backbone.Model({
query: 'SELECT * FROM somewhere;'
})
};
return node;
};
this.layerDefinitionModel.getName = function () { return 'Metro Madrid'; };
this.analysisSourceOptionsModel = new Backbone.Model({
fetching: false
});
this.initializeOptions = {
analyses: analyses,
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel
};
this.formModel = new FormModel(null, this.initializeOptions);
});
afterEach(function () {
cdb.SQL = cdbSQLBackup;
});
describe('initialize', function () {
it('should call proper function and hook events', function () {
spyOn(BaseAnalysisFormModel.prototype, 'initialize');
spyOn(this.formModel, '_setSchema');
spyOn(this.formModel, '_initBinds');
spyOn(this.formModel, '_fetchAvailableFunctions');
this.formModel.initialize(null, this.initializeOptions);
expect(BaseAnalysisFormModel.prototype.initialize).toHaveBeenCalled();
expect(this.formModel._SQL).toBeDefined();
expect(this.formModel.get('fetchStatus')).toBe('unfetched');
expect(this.formModel._functionsInfo.length).toBe(0);
expect(this.formModel._initBinds).toHaveBeenCalled();
expect(this.formModel._setSchema).toHaveBeenCalled();
expect(this.formModel._fetchAvailableFunctions).toHaveBeenCalled();
});
});
describe('._initBinds', function () {
it('should hook up the proper events', function () {
expect(this.formModel._events['change:fetchStatus'].length).toBe(1);
expect(this.formModel._events['change:function_name'].length).toBe(1);
expect(this.formModel._analysisSourceOptionsModel._events['change:fetching'].length).toBe(1);
});
});
describe('.validate', function () {
it('should serialize function arguments', function () {
var attrs = 'the attributes';
var options = 'the options';
var calls = [];
spyOn(this.formModel, '_serializeFunctionArgs').and.callFake(function () {
calls.push('serialize');
});
spyOn(BaseAnalysisFormModel.prototype, 'validate').and.callFake(function () {
calls.push('validate');
});
this.formModel.validate(attrs, options);
expect(calls[0]).toEqual('serialize');
expect(calls[1]).toEqual('validate');
});
});
describe('.getTemplate', function () {
it('should return sql function template', function () {
var template = this.formModel.getTemplate()({
fields: ['oneField', 'otherField']
});
expect(template).toContain('data-fields="oneField,otherField"');
expect(template).toContain('editor.layers.analysis-form.deprecated-sql-function.title');
});
});
describe('.getTemplateData', function () {
it('should return proper fields of the selected function', function () {
var selectedFunction = 'dep_ext_fakeFunk';
this.formModel._functionsInfo = [{
functionName: selectedFunction,
hasSecondaryNode: true,
params: {
radius: 'number',
includeCities: 'boolean'
}
}];
this.formModel.attributes.function_name = selectedFunction;
var templateData = this.formModel.getTemplateData();
expect(templateData.fields).toEqual('secondary_source,radius,includeCities');
});
});
// _getSelectedFunctions gets tested with this spec as well
describe('.setSchema, ._buildSchema and _addParamToSchema', function () {
it('should return schema based of selected function params', function () {
var selectedFunction = 'dep_ext_fakeFunk';
this.formModel.attributes.function_name = selectedFunction;
this.formModel._functionsInfo = [{
functionName: selectedFunction,
hasSecondaryNode: true,
params: {
radius: 'number',
includeCities: 'boolean',
category: 'string'
}
}];
spyOn(this.formModel, '_getSourceOptionsForSource').and.returnValue('sourceOptionsForSource');
var schema = this.formModel._buildSchema();
// Primary source
expect(schema.primary_source).toBeDefined();
expect(schema.primary_source.title).toEqual('editor.layers.analysis-form.deprecated-sql-function.input');
expect(schema.primary_source.type).toEqual('NodeDataset');
// Secondary source
expect(schema.secondary_source).toBeDefined();
expect(schema.secondary_source.options).toEqual('sourceOptionsForSource');
expect(schema.secondary_source.title).toEqual('editor.layers.analysis-form.deprecated-sql-function.target');
expect(schema.secondary_source.type).toEqual('NodeDataset');
// Number param
expect(schema.radius).toBeDefined();
expect(schema.radius.showSlider).toBe(false);
expect(schema.radius.title).toBe('radius');
expect(schema.radius.type).toBe('Number');
expect(schema.radius.validators[0]).toBe('required');
expect(this.formModel.get('radius')).toBe(0);
// Boolean param
expect(schema.includeCities).toBeDefined();
expect(schema.includeCities.title).toBe('includeCities');
expect(schema.includeCities.type).toBe('Radio');
expect(schema.includeCities.options[0]).toEqual({val: 'true', label: 'true'});
expect(schema.includeCities.options[1]).toEqual({val: 'false', label: 'false'});
expect(this.formModel.get('includeCities')).toBe(true);
// String param
expect(schema.category).toBeDefined();
expect(schema.category.title).toBe('category');
expect(schema.category.type).toBe('Text');
expect(schema.category.validators[0]).toBe('required');
expect(this.formModel.get('category')).toBe('');
});
it('.setSchema should call base form', function () {
spyOn(BaseAnalysisFormModel.prototype, '_setSchema');
spyOn(this.formModel, '_buildSchema').and.returnValue('schema built');
this.formModel._setSchema();
expect(BaseAnalysisFormModel.prototype._setSchema).toHaveBeenCalledWith('schema built');
});
});
describe('_onSourceOptionsFetched', function () {
it('should call _setSchema', function () {
spyOn(this.formModel, '_setSchema');
this.formModel._onSourceOptionsFetched();
expect(this.formModel._setSchema).toHaveBeenCalled();
});
});
describe('_onFetchStatusChanged', function () {
it('should call _setSchema', function () {
spyOn(this.formModel, '_setSchema');
this.formModel._onSourceOptionsFetched();
expect(this.formModel._setSchema).toHaveBeenCalled();
});
});
describe('_onFunctionSelected', function () {
it('should call _setSchema', function () {
spyOn(this.formModel, '_setSchema');
this.formModel._onSourceOptionsFetched();
expect(this.formModel._setSchema).toHaveBeenCalled();
});
});
describe('._fetchAvailableFunctions', function () {
var SQL;
var SQLcallbacks;
var queryUsed;
var trackBackup;
beforeEach(function () {
SQL = this.formModel._SQL;
this.formModel._SQL = {
execute: function (query, whatever, callbacks) {
queryUsed = query;
SQLcallbacks = callbacks;
}
};
trackBackup = window.trackJs;
});
afterEach(function () {
this.formModel._SQL = SQL;
SQLcallbacks = null;
queryUsed = null;
window.trackJs = trackBackup;
});
// This test checks also _parseFunctionsMetadata and _capitalizeFunctionName
it('should fill _functionsInfo if successful', function () {
var metadata = {
rows: [{
fn_name: 'dep_ext_funk',
has_secondary_node: true,
params_names: ['radius', 'category'],
params_types: ['number', 'string']
}, {
fn_name: 'dep_ext_empty',
has_secondary_node: false,
params_names: [],
params_types: []
}]
};
this.formModel._fetchAvailableFunctions();
expect(this.formModel.get('fetchStatus')).toEqual('fetching');
SQLcallbacks.success(metadata);
expect(this.formModel.get('fetchStatus')).toEqual('fetched');
expect(this.formModel._functionsInfo.length).toBe(2);
var first = this.formModel._functionsInfo[0];
var second = this.formModel._functionsInfo[1];
expect(first).toEqual({
functionName: 'DEP_EXT_funk',
hasSecondaryNode: true,
params: {
category: 'string',
radius: 'number'
}
});
expect(second).toEqual({
functionName: 'DEP_EXT_empty',
hasSecondaryNode: false,
params: {}
});
expect(queryUsed).toEqual(availableFunctionsQuery());
});
it('should track error if error', function () {
var errors = {
responseJSON: '{anError: {}}'
};
var errorTracked;
window.trackJs = {
track: function (error) {
errorTracked = error;
}
};
this.formModel._fetchAvailableFunctions();
expect(this.formModel.get('fetchStatus')).toEqual('fetching');
SQLcallbacks.error(errors);
expect(this.formModel.get('fetchStatus')).toEqual('error');
expect(errorTracked).toEqual(errors.responseJSON);
});
});
describe('_getSelectFunctionAttrs', function () {
it('should return correct options for select function dropdown', function () {
// Unfetched
this.formModel.attributes.fetchStatus = 'unfetched';
var status = this.formModel._getSelectFunctionAttrs();
expect(status).toEqual({
disabled: true
});
// Fetching
this.formModel.attributes.fetchStatus = 'fetching';
status = this.formModel._getSelectFunctionAttrs();
expect(status).toEqual({
disabled: true,
loading: true
});
// Fetched
this.formModel.attributes.fetchStatus = 'fetched';
status = this.formModel._getSelectFunctionAttrs();
expect(status).toEqual({});
this.formModel._functionsInfo = ['one', 'two'];
status = this.formModel._getSelectFunctionAttrs();
expect(status).toEqual({
placeholder: 'editor.layers.analysis-form.deprecated-sql-function.choose-function-small'
});
// Error
this.formModel.attributes.fetchStatus = 'error';
status = this.formModel._getSelectFunctionAttrs();
expect(status).toEqual({
disabled: true,
placeholder: 'error'
});
});
});
describe('_parseFunctionOptions', function () {
it('should parse functions info to dropdown values', function () {
this.formModel._functionsInfo = [
{ functionName: 'dep_ext_funk' },
{ functionName: 'dep_ext_soul' }
];
var parsed = this.formModel._parseFunctionOptions();
expect(parsed.length).toBe(2);
expect(parsed).toEqual([
{ val: 'dep_ext_funk', label: 'dep_ext_funk' },
{ val: 'dep_ext_soul', label: 'dep_ext_soul' }
]);
});
});
describe('._serializeFunctionArgs', function () {
it('should set function_args to equal the arguments of the selected function', function () {
this.formModel.attributes.function_name = 'dep_ext_funk';
this.formModel.attributes.radius = 808;
this.formModel.attributes.category = 'place';
this.formModel._functionsInfo = [
{
functionName: 'dep_ext_funk',
params: {
radius: 'number',
category: 'string'
}
}
];
this.formModel._serializeFunctionArgs();
expect(this.formModel.get('function_args')).toEqual([808, 'place']);
});
});
});
@@ -0,0 +1,15 @@
var template = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/deprecated-sql-function.tpl');
describe('deprecated-sql-function-form-template', function () {
it('should have `primary_source` and `function_name` as data fields', function () {
var html = template({fields: []});
expect(html).toContain('data-fields="primary_source,function_name"');
});
it('should have the given data fields', function () {
var html = template({fields: ['secondary_source', 'radius']});
expect(html).toContain('data-fields="secondary_source,radius"');
});
});
@@ -0,0 +1,123 @@
var Backbone = require('backbone');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisSourceOptionsModel = require('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model');
var FallbackFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/fallback-form-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/fallback-form-model', function () {
beforeEach(function () {
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: {}
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel');
});
describe('when created for a type with a single source', function () {
beforeEach(function () {
this.analysisSourceOptionsModel = new AnalysisSourceOptionsModel({fetching: true}, {
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
tablesCollection: {}
});
var attrs = {
id: 'a2',
type: 'buffer',
source: 'a1'
};
this.model = new FallbackFormModel(attrs, {
analyses: analyses,
configModel: {},
userActions: {},
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {}
});
});
describe('should generate schema from camshaft reference', function () {
beforeEach(function () {
expect(this.model.schema).toBeDefined();
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBeGreaterThan(0);
});
it('should not allow to change source', function () {
expect(this.model.schema.source.editorAttrs.disabled).toBe(true);
});
});
describe('for a model representing a type which contains params of type array', function () {
beforeEach(function () {
this.model.set({
type: 'filter-category',
source: 'a1',
column: 'col'
});
});
describe('when updated', function () {
beforeEach(function () {
this.nodeDefModel = new Backbone.Model();
expect(this.model.isValid()).toBe(true, 'the model should be valid to be saved');
});
it('should split array params for values', function () {
this.model.set('accept', 'a, b | b, c, d, e, and f');
this.model.updateNodeDefinition(this.nodeDefModel);
expect(this.nodeDefModel.get('accept')).toEqual(jasmine.any(Array));
expect(this.nodeDefModel.get('accept')).toEqual(['a', 'b | b', 'c', 'd', 'e', 'and f']);
});
it('should set a null value if there is no value', function () {
this.model.set('accept', '');
this.model.updateNodeDefinition(this.nodeDefModel);
expect(this.nodeDefModel.get('accept')).toBe(null);
});
});
});
});
describe('when creatd for a type with multple sources', function () {
beforeEach(function () {
this.analysisSourceOptionsModel = new AnalysisSourceOptionsModel({fetching: true}, {
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
tablesCollection: {}
});
var attrs = {
id: 'b2',
type: 'aggregate-intersection'
};
this.model = new FallbackFormModel(attrs, {
analyses: analyses,
configModel: {},
userActions: {},
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel
});
});
describe('when source options is fetched', function () {
beforeEach(function () {
spyOn(this.analysisSourceOptionsModel, 'getSelectOptions').and.returnValue([
{val: 'b1', label: 'b1'},
{val: 'c2', label: 'c2'}
]);
this.analysisSourceOptionsModel.set('fetching', false);
});
it('should update schema', function () {
expect(this.analysisSourceOptionsModel.getSelectOptions).toHaveBeenCalled();
expect(this.model.schema).toBeDefined();
});
});
});
});
@@ -0,0 +1,227 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var FilterFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/filter-by-node-column');
var AnalysisSourceOptionsModel = require('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/filter-by-node-column', function () {
var createModelFn = function (attributes, options) {
var defaultOptions = {
analyses: analyses,
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel
};
this.model = new FilterFormModel(_.extend({}, this.a1.attributes, attributes), _.extend({}, defaultOptions, options));
};
beforeEach(function () {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
.andReturn({ status: 200 });
this.configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
var userModel = new UserModel({}, {
configModel: this.configModel
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'filter-by-node-column',
query: 'SELECT * FROM a_table'
}, {
configModel: this.configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'filter-by-node-column',
source: 'a0'
}, {
configModel: this.configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: this.configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'map-123',
stateDefinitionModel: {}
});
this.analysisSourceOptionsModel = new AnalysisSourceOptionsModel(null, {
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection,
tablesCollection: new Backbone.Collection()
});
this.analysisSourceOptionsModel.getSelectOptions = function () {
return this;
};
this.analysisSourceOptionsModel.find = function () {
return this;
};
this.analysisSourceOptionsModel.filter = function () {
return [];
};
this.createModel = createModelFn.bind(this);
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should add analysis schema', function () {
this.createModel();
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
this.createModel();
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-by-node-column'
});
});
it('should have generated form fields', function () {
this.createModel();
expect(Object.keys(this.model.schema).length).toBe(4);
});
describe('template', function () {
beforeEach(function () {
this.createModel();
});
describe('should return proper return template data', function () {
it('when no filter source', function () {
this.model.set('filter_source', undefined);
expect(this.model.getTemplateData()).toEqual({
hasFilterSource: false
});
});
it('when something as filter_source', function () {
this.model.set('filter_source', 'shiva');
expect(this.model.getTemplateData()).toEqual({
hasFilterSource: true
});
});
});
it('should disable the second step if not source column', function () {
var template = this.model.getTemplate()({
hasFilterSource: false,
linkContent: ''
});
expect(template.indexOf('Editor-HeaderInfo is-disabled')).not.toBe(-1);
});
it('should enable the second step if not source column', function () {
var template = this.model.getTemplate()({
hasFilterSource: true,
linkContent: ''
});
expect(template.indexOf('Editor-HeaderInfo is-disabled')).toBe(-1);
});
});
describe('type filter by node column', function () {
beforeEach(function () {
this.createModel({
filter_source: 'a0',
column: null,
filter_column: null
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-by-node-column',
filter_source: 'a0',
column: null,
filter_column: null
});
});
describe('filter_column', function () {
it('validator is correct', function () {
this.model.set('column', 'cartodb_id');
expect(this.model.schema.column).toBeDefined();
this.model.set('filter_column', 'numero_de_finca');
expect(this.model.schema.filter_column).toBeDefined();
expect(this.model.schema.filter_column.validators).toBeDefined();
expect(this.model.schema.filter_column.validators[0]).toEqual('required');
expect(this.model.schema.filter_column.validators[1]).toEqual(jasmine.any(Function));
});
});
describe('._validateFilterColumn', function () {
it('should validate column types match', function () {
var formValues = {
column: 'cartodb_id',
filter_column: 'numero_de_finca'
};
spyOn(this.model._columnOptions, 'findColumn').and.returnValue({
type: 'number'
});
spyOn(this.model._filterColumnOptions, 'findColumn').and.returnValue({
type: 'string'
});
expect(JSON.stringify(this.model._validateFilterColumn('numero_de_finca', formValues)))
.toBe('{"message":"editor.layers.analysis-form.should-match"}');
});
});
});
});
@@ -0,0 +1,530 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var analyses = require('builder/data/analyses');
var FilterFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/filter-form-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/filter-form-model', function () {
beforeEach(function () {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
.andReturn({ status: 200 });
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'source',
query: 'SELECT * FROM a_table'
}, {
configModel: configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'filter-range',
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new FilterFormModel(this.a1.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
kind: 'between',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
column: '',
accept_reject: 'accept'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(5);
expect(this.model.schema.min).not.toBeDefined();
expect(this.model.schema.max).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
column: null,
kind: 'between',
greater_than_or_equal: 0,
less_than_or_equal: 50
});
});
it('should have the second step disabled for no column', function () {
var templateData = { linkContent: '', column: null, histogram_stats: null, parametersDataFields: '' };
expect(this.model.getTemplate()(templateData)).toContain('Editor-HeaderInfo is-disabled');
});
it('should enable the second step if column selected', function () {
var templateData = { linkContent: '', column: 'morty', histogram_stats: null, parametersDataFields: '' };
expect(this.model.getTemplate()(templateData)).not.toContain('Editor-HeaderInfo is-disabled');
});
describe('number', function () {
beforeEach(function () {
spyOn(this.model, '_getSelectedColumnType').and.returnValue('number');
this.model.set('column', 'number');
});
describe('kind is-equal-to', function () {
beforeEach(function () {
this.model.set({
kind: 'is-equal-to',
text: 1
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
accept_reject: 'accept',
column: 'number',
kind: 'is-equal-to',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
text: 1
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.text).toBeDefined();
expect(this.model.schema.min).not.toBeDefined();
expect(this.model.schema.max).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
column: 'number',
kind: 'is-equal-to',
accept: [1]
});
});
});
describe('kind is-less-or-equal-than', function () {
beforeEach(function () {
this.model.set({
kind: 'is-less-or-equal-than',
less_than_or_equal: 4
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
accept_reject: 'accept',
column: 'number',
kind: 'is-less-or-equal-than',
greater_than_or_equal: 0,
less_than_or_equal: 4,
greater_than: 0,
less_than: 50
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.less_than_or_equal).toBeDefined();
expect(this.model.schema.less_than).not.toBeDefined();
expect(this.model.schema.greater_than).not.toBeDefined();
expect(this.model.schema.greater_than_or_equal).not.toBeDefined();
expect(this.model.schema.text).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
column: 'number',
kind: 'is-less-or-equal-than',
less_than_or_equal: 4
});
});
});
describe('kind is-less-than', function () {
beforeEach(function () {
this.model.set({
kind: 'is-less-than',
less_than: 1
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
accept_reject: 'accept',
column: 'number',
kind: 'is-less-than',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 1
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.less_than).toBeDefined();
expect(this.model.schema.greater_than).not.toBeDefined();
expect(this.model.schema.greater_than_or_equal).not.toBeDefined();
expect(this.model.schema.less_than_or_equal).not.toBeDefined();
expect(this.model.schema.text).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
column: 'number',
kind: 'is-less-than',
less_than: 1
});
});
});
describe('kind is-greater-than', function () {
beforeEach(function () {
this.model.set({
kind: 'is-greater-than',
greater_than: 1
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
accept_reject: 'accept',
column: 'number',
kind: 'is-greater-than',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 1,
less_than: 50
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.greater_than).toBeDefined();
expect(this.model.schema.greater_than_or_equal).not.toBeDefined();
expect(this.model.schema.less_than_or_equal).not.toBeDefined();
expect(this.model.schema.less_than).not.toBeDefined();
expect(this.model.schema.text).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
column: 'number',
kind: 'is-greater-than',
greater_than: 1
});
});
});
describe('kind is-greater-or-equal-than', function () {
beforeEach(function () {
this.model.set({
kind: 'is-greater-or-equal-than',
greater_than_or_equal: 4
});
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
accept_reject: 'accept',
column: 'number',
kind: 'is-greater-or-equal-than',
greater_than_or_equal: 4,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.greater_than_or_equal).toBeDefined();
expect(this.model.schema.less_than_or_equal).not.toBeDefined();
expect(this.model.schema.less_than).not.toBeDefined();
expect(this.model.schema.greater_than).not.toBeDefined();
expect(this.model.schema.text).not.toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-range',
column: 'number',
kind: 'is-greater-or-equal-than',
greater_than_or_equal: 4
});
});
});
});
describe('string', function () {
beforeEach(function () {
spyOn(this.model, '_getSelectedColumnType').and.returnValue('string');
this.model.set({
column: 'string',
text: 'wadus'
});
});
describe('accept', function () {
it('should not include default attributes', function () {
expect(this.model.get('kind')).toBe(null);
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
accept_reject: 'accept',
column: 'string',
kind: null,
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
text: 'wadus'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.text).toBeDefined();
expect(this.model.schema.accept_reject).toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
column: 'string',
accept: ['wadus']
});
});
});
describe('reject', function () {
beforeEach(function () {
this.model.set('accept_reject', 'reject');
});
it('should not include default attributes', function () {
expect(this.model.get('kind')).toBe(null);
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
accept_reject: 'reject',
column: 'string',
kind: null,
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
text: 'wadus'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.text).toBeDefined();
expect(this.model.schema.accept_reject).toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
column: 'string',
reject: ['wadus']
});
});
});
});
describe('boolean', function () {
beforeEach(function () {
spyOn(this.model, '_getSelectedColumnType').and.returnValue('boolean');
this.model.set({
column: 'boolean',
text: 'true'
});
});
it('should not include default attributes', function () {
expect(this.model.get('kind')).toBe('is-boolean');
});
describe('accept', function () {
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
accept_reject: 'accept',
column: 'boolean',
kind: 'is-boolean',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
text: 'true'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.text).toBeDefined();
expect(this.model.schema.accept_reject).toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
column: 'boolean',
kind: 'is-boolean',
accept: ['true']
});
});
});
describe('reject', function () {
beforeEach(function () {
this.model.set('accept_reject', 'reject');
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
accept_reject: 'reject',
column: 'boolean',
kind: 'is-boolean',
greater_than_or_equal: 0,
less_than_or_equal: 50,
greater_than: 0,
less_than: 50,
text: 'true'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.text).toBeDefined();
expect(this.model.schema.accept_reject).toBeDefined();
});
it('should format the attributes', function () {
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'filter-category',
column: 'boolean',
kind: 'is-boolean',
reject: ['true']
});
});
});
});
});
@@ -0,0 +1,159 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var analyses = require('builder/data/analyses');
var GeoreferenceFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/georeference-form-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/georeference-form-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'source',
query: 'SELECT * FROM a_table'
}, {
configModel: configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'georeference-long-lat',
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new GeoreferenceFormModel(this.a1.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'georeference-long-lat',
latitude: undefined,
longitude: undefined
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
});
describe('type georeference-street-address', function () {
beforeEach(function () {
this.model.set('type', 'georeference-street-address');
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
context: undefined,
id: 'a1',
source: 'a0',
type: 'georeference-street-address',
street_address_column: '',
city: '',
state: '',
country: ''
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(7);
expect(this.model.schema.street_address_column).toBeDefined();
});
describe('context advance', function () {
it('should have generated form fields', function () {
this.model.set('context', 'advance');
expect(Object.keys(this.model.schema).length).toBe(7);
expect(this.model.schema.street_address_column).not.toBeDefined();
expect(this.model.schema.street_address_template).toBeDefined();
});
it('should format street_address properly', function () {
spyOn(this.model._typeDef(), '_wrapInBraces').and.callThrough();
this.model.set('street_address_column', 'address');
this.model.set('context', 'advance');
expect(this.model._typeDef()._wrapInBraces).toHaveBeenCalledWith('address');
expect(this.model.get('street_address_template')).toBe('{{address}}');
});
});
});
describe('type georeference-postal-code-model', function () {
beforeEach(function () {
this.model.set('type', 'georeference-postal-code');
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
context: undefined,
id: 'a1',
source: 'a0',
type: 'georeference-postal-code',
postal_code_column: '',
postal_code_country: ''
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
expect(this.model.schema.postal_code_column).toBeDefined();
expect(this.model.schema.postal_code_column.validators[0]).toBe('required');
expect(this.model.schema.postal_code_country).toBeDefined();
expect(this.model.schema.postal_code_country.validators[0]).toBe('required');
});
});
it('should return only numeric columns for latitude and longitude', function () {
this.model._columnOptions._columnOptions = {
cartodb_id: { type: 'number' },
title: { type: 'string' },
created_at: { type: 'date' }
};
this.model._setSchema();
expect(this.model.schema.latitude.options.length).toBe(1);
expect(this.model.schema.longitude.options.length).toBe(1);
});
});
@@ -0,0 +1,171 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var GroupPointsFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/group-points-form-model');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/group-points-form-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'concave-hull',
query: 'SELECT * FROM a_table'
}, {
configModel: configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'bounding-box',
source: 'a0'
}, {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.model = new GroupPointsFormModel(this.a1.attributes, {
analyses: analyses,
configModel: configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: {},
parse: true
});
});
it('should add analysis schema', function () {
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'bounding-box',
category_column: undefined,
aggregate: Object({ operator: 'count', attribute: '' }),
aggregation_column: '',
aggregation: 'count'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(4);
});
it('should bind properly', function () {
this.model.set('aggregate', { operator: 'avg', attribute: 'wadus' });
expect(this.model.get('aggregation_column')).toBe('wadus');
expect(this.model.get('aggregation')).toBe('avg');
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('source,type,category_column');
});
it('should format the attributes', function () {
this.model.set('aggregate', { operator: 'avg', attribute: 'wadus' });
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'bounding-box',
aggregation: 'avg',
aggregation_column: 'wadus'
});
});
describe('type bounding-circle', function () {
beforeEach(function () {
this.model.set('type', 'bounding-circle');
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('source,type,category_column');
});
it('should format the attributes', function () {
this.model.set('category_column', 'cluster_no');
this.model.set('aggregate', { operator: 'avg', attribute: 'wadus' });
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'bounding-circle',
category_column: 'cluster_no',
aggregation: 'avg',
aggregation_column: 'wadus'
});
});
});
describe('type convex-hull', function () {
beforeEach(function () {
this.model.set('type', 'convex-hull');
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('source,type,category_column');
});
it('should format the attributes', function () {
this.model.set('aggregate', { operator: 'sum', attribute: 'wadus' });
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'convex-hull',
aggregation: 'sum',
aggregation_column: 'wadus'
});
});
});
describe('type concave-hull', function () {
beforeEach(function () {
this.model.set('type', 'concave-hull');
});
it('should have proper attributes', function () {
expect(this.model._typeDef().parametersDataFields).toBe('source,type,target_percentage,allow_holes,category_column');
});
it('should format the attributes', function () {
this.model.set('aggregate', { operator: 'sum', attribute: 'wadus' });
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
source: 'a0',
type: 'concave-hull',
target_percent: 0.7,
aggregation: 'sum',
aggregation_column: 'wadus'
});
});
});
});
@@ -0,0 +1,308 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var MergeFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/merge-form-model');
var AnalysisSourceOptionsModel = require('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-views/analyses/analysis-form-models/merge-form-model', function () {
var createModelFn = function (attributes, options) {
var defaultOptions = {
analyses: analyses,
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel,
parse: true
};
this.model = new MergeFormModel(_.extend({}, this.a1.attributes, attributes), _.extend({}, defaultOptions, options));
};
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe',
api_key: 'wadus',
user_name: 'pepe'
});
var userModel = new UserModel({}, {
configModel: this.configModel
});
this.analysisDefinitionNodesCollection = new Backbone.Collection();
this.a0 = new AnalysisDefinitionNodeSourceModel({
id: 'a0',
type: 'merge',
query: 'SELECT * FROM a_table'
}, {
configModel: this.configModel,
userModel: {},
collection: this.analysisDefinitionNodesCollection
});
spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);
this.a1 = new AnalysisDefinitionNodeModel({
id: 'a1',
type: 'merge',
source: 'a0'
}, {
configModel: this.configModel,
collection: this.analysisDefinitionNodesCollection
});
this.layerDefinitionModel = new LayerDefinitionModel(null, {
configModel: this.configModel
});
spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
switch (id) {
case 'a0': return this.a0;
case 'a1': return this.a1;
default: return null;
}
}.bind(this));
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'map-123',
stateDefinitionModel: {}
});
this.analysisSourceOptionsModel = new AnalysisSourceOptionsModel(null, {
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection,
tablesCollection: new Backbone.Collection()
});
this.analysisSourceOptionsModel.getSelectOptions = function () {
return this;
};
this.analysisSourceOptionsModel.find = function () {
return this;
};
this.analysisSourceOptionsModel.filter = function () {
return [];
};
this.createModel = createModelFn.bind(this);
});
it('should add analysis schema', function () {
this.createModel();
expect(this.model.schema).toBeDefined();
});
it('should have attributes set with defaults', function () {
this.createModel();
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'merge',
source_geometry_selector: undefined,
join_operator: 'inner',
source_geometry: 'left_source',
right_source_columns: []
});
});
it('should have generated form fields', function () {
this.createModel();
expect(Object.keys(this.model.schema).length).toBe(8);
});
it('should disable the fields appropriately', function () {
this.createModel();
var template = this.model.getTemplate()({
linkContent: '',
right_source: true,
hasLeftAndRightSourceColumns: true
});
expect((template.match(/Editor-HeaderInfo is-disabled/g) || []).length).toBe(0);
template = this.model.getTemplate()({
linkContent: '',
right_source: true,
hasLeftAndRightSourceColumns: false
});
expect((template.match(/Editor-HeaderInfo is-disabled/g) || []).length).toBe(1);
template = this.model.getTemplate()({
linkContent: '',
right_source: false,
hasLeftAndRightSourceColumns: false
});
expect((template.match(/Editor-HeaderInfo is-disabled/g) || []).length).toBe(2);
});
it('should format the attributes', function () {
this.createModel();
expect(this.model._formatAttrs(this.model.attributes)).toEqual({
id: 'a1',
type: 'merge',
join_operator: 'inner',
source_geometry_selector: undefined,
right_source_columns: [],
source_geometry: 'left_source',
left_source_columns: [],
left_source: '',
right_source: '',
left_source_column: null,
right_source_column: null
});
});
describe('parsing the model', function () {
beforeEach(function () {
this.createModel();
});
it('should parse the model with right source_geometry', function () {
var attrs = {
id: 'a1',
source: 'a0',
type: 'merge',
source_geometry: 'right_source',
right_source: 'right'
};
expect(this.model.parse(attrs)).toEqual({
id: 'a1',
source: 'a0',
type: 'merge',
right_source: 'right',
source_geometry_selector: 'right',
source_geometry: 'right_source'
});
});
it('should parse the model without source_geometry', function () {
var attrs = {
id: 'a1',
source: 'a0',
type: 'merge',
source_geometry: null,
left_source: 'left'
};
expect(this.model.parse(attrs)).toEqual({
id: 'a1',
source: 'a0',
type: 'merge',
left_source: 'left',
source_geometry: null,
source_geometry_selector: 'left'
});
});
});
describe('type join columns', function () {
beforeEach(function () {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
.andReturn({ status: 200 });
this.createModel({
left_source: 'a0',
right_source: 'a0',
left_source_column: null,
right_source_column: null
}, {
analyses: analyses,
configModel: this.configModel,
layerDefinitionModel: this.layerDefinitionModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel,
parse: true
});
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should include left_source and right_source column', function () {
expect(this.model.get('left_source_column')).toBeDefined();
expect(this.model.get('right_source_column')).toBeDefined();
});
it('should have proper attributes', function () {
expect(this.model.attributes).toEqual({
id: 'a1',
source: 'a0',
type: 'merge',
source_geometry_selector: 'a0',
join_operator: 'inner',
source_geometry: 'left_source',
right_source_columns: [],
left_source_column: null,
right_source_column: null,
left_source: 'a0',
right_source: 'a0'
});
});
it('should have generated form fields', function () {
expect(Object.keys(this.model.schema).length).toBe(8);
expect(this.model.schema.left_source_column).toBeDefined();
expect(this.model.schema.right_source_column).toBeDefined();
});
describe('right_source_column', function () {
it('validator is correct', function () {
this.model.set('left_source_column', 'cartodb_id');
expect(this.model.schema.left_source_column).toBeDefined();
expect(this.model.schema.left_source_column.validators).toBeDefined();
expect(this.model.schema.left_source_column.validators[0]).toEqual('required');
this.model.set('right_source_column', 'numero_de_finca');
expect(this.model.schema.right_source_column).toBeDefined();
expect(this.model.schema.right_source_column.validators).toBeDefined();
expect(this.model.schema.right_source_column.validators[0]).toEqual('required');
expect(this.model.schema.right_source_column.validators[1]).toEqual(jasmine.any(Function));
});
});
describe('._validateRightSourceColumn', function () {
it('should validate column types match', function () {
var self = this;
var formValues = {
join_operator: 'inner',
left_source_column: 'cartodb_id',
right_source_column: 'numero_de_finca'
};
spyOn(this.model._leftColumnOptions, 'findColumn').and.returnValue({
type: 'number'
});
spyOn(this.model._rightColumnOptions, 'findColumn').and.returnValue({
type: 'string'
});
expect(JSON.stringify(self.model._validateRightSourceColumn('numero_de_finca', formValues)))
.toBe('{"message":"editor.layers.analysis-form.should-match"}');
});
});
});
});
@@ -0,0 +1,73 @@
var Backbone = require('backbone');
var AnalysesQuotaEnough = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-enough');
describe('editor/layers/layer-content-view/analyses/analyses-quota/analyses-quota-enough', function () {
beforeEach(function () {
var configModel = new Backbone.Model({
user_name: 'foo',
sql_api_template: 'foo',
api_key: 'foo'
});
AnalysesQuotaEnough.init(configModel);
this.successCallback = jasmine.createSpy('successCallback');
this.errorCallback = jasmine.createSpy('errorCallback');
});
describe('requests', function () {
beforeEach(function () {
AnalysesQuotaEnough.deferred = {
state: function () {
return 'pending';
},
promise: function () {
return 'foo';
}
};
});
afterEach(function () {
AnalysesQuotaEnough.deferred = null;
});
it('should avoid multple request if executing', function () {
spyOn(AnalysesQuotaEnough.SQL, 'execute');
AnalysesQuotaEnough.fetch();
expect(AnalysesQuotaEnough.SQL.execute).not.toHaveBeenCalled();
});
});
it('not enough quota', function () {
spyOn(AnalysesQuotaEnough.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [{ 'cdb_enough_quota': false }]
});
});
AnalysesQuotaEnough.fetch().then(this.successCallback, this.errorCallback);
expect(this.successCallback).toHaveBeenCalledWith(false);
});
it('enough quota', function () {
spyOn(AnalysesQuotaEnough.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [{ 'cdb_enough_quota': true }]
});
});
AnalysesQuotaEnough.fetch().then(this.successCallback, this.errorCallback);
expect(this.successCallback).toHaveBeenCalledWith(true);
});
it('error', function () {
spyOn(AnalysesQuotaEnough.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.error({
responseText: '{"error": ["foo"]}'
});
});
AnalysesQuotaEnough.fetch().then(this.successCallback, this.errorCallback);
expect(this.errorCallback).toHaveBeenCalled();
expect(this.successCallback).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,62 @@
var Backbone = require('backbone');
var AnalysesQuotaEstimation = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-estimation-input');
describe('editor/layers/layer-content-view/analyses/analyses-quota/analyses-quota-estimation-input', function () {
beforeEach(function () {
var configModel = new Backbone.Model({
user_name: 'foo',
sql_api_template: 'foo',
api_key: 'foo'
});
AnalysesQuotaEstimation.init(configModel);
this.successCallback = jasmine.createSpy('successCallback');
this.errorCallback = jasmine.createSpy('errorCallback');
});
describe('requests', function () {
beforeEach(function () {
AnalysesQuotaEstimation.deferred = {
state: function () {
return 'pending';
},
promise: function () {
return 'foo';
}
};
});
afterEach(function () {
AnalysesQuotaEstimation.deferred = null;
});
it('should avoid multple request if executing', function () {
spyOn(AnalysesQuotaEstimation.SQL, 'execute');
AnalysesQuotaEstimation.fetch();
expect(AnalysesQuotaEstimation.SQL.execute).not.toHaveBeenCalled();
});
});
it('success', function () {
spyOn(AnalysesQuotaEstimation.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [{ row_count: 3242 }]
});
});
AnalysesQuotaEstimation.fetch().then(this.successCallback, this.errorCallback);
expect(this.successCallback).toHaveBeenCalledWith(3242);
});
it('error', function () {
spyOn(AnalysesQuotaEstimation.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.error({
responseText: '{"error": ["foo"]}'
});
});
AnalysesQuotaEstimation.fetch().then(this.successCallback, this.errorCallback);
expect(this.errorCallback).toHaveBeenCalled();
expect(this.successCallback).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,59 @@
var Backbone = require('backbone');
var AnalysesQuotaInfo = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-info');
describe('editor/layers/layer-content-view/analyses/analyses-quota/analyses-quota-info', function () {
beforeEach(function () {
var configModel = new Backbone.Model({
user_name: 'foo',
sql_api_template: 'foo',
api_key: 'foo'
});
this.quotaInfo = AnalysesQuotaInfo.get(configModel);
spyOn(this.quotaInfo.SQL, 'execute').and.callFake(function (query, vars, params) {
params && params.success({
rows: [
{ service: 'isolines', monthly_quota: 2000, used_quota: 23, soft_limit: false, provider: 'heremaps' },
{ service: 'hires_geocoder', monthly_quota: 1000, used_quota: 0, soft_limit: true, provider: 'mapzen' },
{ service: 'routing', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'mapzen' },
{ service: 'observatory', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'data observatory' }
]
});
});
this.successCallback = jasmine.createSpy('successCallback');
this.errorCallback = jasmine.createSpy('errorCallback');
});
it('should fetch services', function () {
this.quotaInfo.fetch({
success: this.successCallback,
error: this.errorCallback
});
expect(this.successCallback).toHaveBeenCalled();
expect(this.quotaInfo.length).toBe(4);
});
it('should get service by name', function () {
this.quotaInfo.fetch({
success: this.successCallback,
error: this.errorCallback
});
var isolines = this.quotaInfo.getService('isolines');
expect(isolines.get('provider')).toBe('heremaps');
expect(isolines.get('monthly_quota')).toBe(2000);
expect(isolines.get('used_quota')).toBe(23);
});
it('should avoid multiple request if fetching', function () {
spyOn(this.quotaInfo, 'isFetching').and.returnValue(true);
this.quotaInfo.fetch({
success: this.successCallback,
error: this.errorCallback
});
expect(this.quotaInfo.SQL.execute).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,121 @@
var Backbone = require('backbone');
var AnalysesQuotaOptions = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-options');
var AnalysesQuotaInfo = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-info');
describe('editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-options', function () {
beforeEach(function () {
var configModel = new Backbone.Model({
user_name: 'foo',
sql_api_template: 'foo',
api_key: 'foo'
});
this.quotaInfo = AnalysesQuotaInfo.get(configModel);
this.quotaInfo.reset([
{service: 'isolines', monthly_quota: 2000, used_quota: 23, soft_limit: false, provider: 'heremaps'},
{service: 'hires_geocoder', monthly_quota: 1000, used_quota: 0, soft_limit: true, provider: 'heremaps'},
{service: 'routing', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'mapzen'},
{service: 'observatory', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'data observatory'}
]);
});
describe('requiresQuota', function () {
it('analysis does not require quota', function () {
expect(AnalysesQuotaOptions.requiresQuota('foo', this.quotaInfo)).toBe(false);
});
it('routing or data observatory', function () {
expect(AnalysesQuotaOptions.requiresQuota('routing-sequential', this.quotaInfo)).toBe(true);
expect(AnalysesQuotaOptions.requiresQuota('data-observatory-measure', this.quotaInfo)).toBe(true);
});
it('data observatory multiple should NOT require quota', function () {
expect(AnalysesQuotaOptions.requiresQuota('data-observatory-multiple-measures', this.quotaInfo)).toBeFalsy();
});
describe('trade-area', function () {
it('should require quota if provider is set', function () {
var geocoder = this.quotaInfo.getService('hires_geocoder');
expect(AnalysesQuotaOptions.requiresQuota('trade-area', this.quotaInfo)).toBeTruthy();
geocoder.set('provider', 'mapzen');
expect(AnalysesQuotaOptions.requiresQuota('trade-area', this.quotaInfo)).toBeTruthy();
});
it('should not require quota when provider is empty', function () {
var isolines = this.quotaInfo.getService('isolines');
expect(AnalysesQuotaOptions.requiresQuota('trade-area', this.quotaInfo)).toBeTruthy();
isolines.set('provider', null);
expect(AnalysesQuotaOptions.requiresQuota('trade-area', this.quotaInfo)).toBeFalsy();
});
});
describe('georeference-street-address', function () {
it('should return quota if provider is not google', function () {
expect(AnalysesQuotaOptions.requiresQuota('georeference-street-address', this.quotaInfo)).toBeTruthy();
});
it('should not return quota if provider is google', function () {
var serviceModel = this.quotaInfo.at(1);
serviceModel.set('provider', 'google');
expect(AnalysesQuotaOptions.requiresQuota('georeference-street-address', this.quotaInfo)).toBeFalsy();
});
});
describe('georeference-city', function () {
it('should return quota if provider is not google', function () {
expect(AnalysesQuotaOptions.requiresQuota('georeference-city', this.quotaInfo)).toBeTruthy();
});
it('should not return quota if provider is google', function () {
var serviceModel = this.quotaInfo.at(1);
serviceModel.set('provider', 'google');
expect(AnalysesQuotaOptions.requiresQuota('georeference-city', this.quotaInfo)).toBeFalsy();
});
});
});
describe('transform input', function () {
it('should return same input for analysis without quota', function () {
expect(AnalysesQuotaOptions.transformInput('foo', 20, {})).toBe(20);
});
it('should return same input for analysis with quota', function () {
var formModel = new Backbone.Model({
measurements: [1, 2]
});
expect(AnalysesQuotaOptions.transformInput('georeference-street-address', 20, {})).toBe(20);
expect(AnalysesQuotaOptions.transformInput('data-observatory-measure', 15, formModel)).toBe(15);
});
it('should return input multiplied for tracts for isolines', function () {
var formModel = new Backbone.Model({
isolines: 1
});
expect(AnalysesQuotaOptions.transformInput('trade-area', 20, formModel)).toBe(20);
formModel.set('isolines', 2);
expect(AnalysesQuotaOptions.transformInput('trade-area', 20, formModel)).toBe(40);
formModel.set('isolines', 10);
expect(AnalysesQuotaOptions.transformInput('trade-area', 20, formModel)).toBe(200);
});
});
it('getAnalysisName', function () {
expect(AnalysesQuotaOptions.getAnalysisName('foo')).toBe(null);
expect(AnalysesQuotaOptions.getAnalysisName('trade-area')).toBe('editor.layers.analysis-form.quota.analysis-type.trade-area');
expect(AnalysesQuotaOptions.getAnalysisName('data-observatory-measure')).toBe('editor.layers.analysis-form.quota.analysis-type.data-observatory-measure');
});
it('getServiceName', function () {
expect(AnalysesQuotaOptions.getServiceName('foo')).toBe(null);
expect(AnalysesQuotaOptions.getServiceName('trade-area')).toBe('isolines');
expect(AnalysesQuotaOptions.getServiceName('data-observatory-measure')).toBe('observatory');
});
it('getUserDataName', function () {
expect(AnalysesQuotaOptions.getUserDataName('foo')).toBe(null);
expect(AnalysesQuotaOptions.getUserDataName('trade-area')).toBe('here_isolines');
expect(AnalysesQuotaOptions.getUserDataName('data-observatory-measure')).toBe('obs_general');
});
});
@@ -0,0 +1,169 @@
var Backbone = require('backbone');
var AnalysesQuotaPresenter = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-presenter');
describe('editor/layers/layer-content-view/analyses/analyses-quota/analyses-quota-presenter', function () {
var configModel = new Backbone.Model({
cartodb_com_hosted: false
});
var userModel = {
isInsideOrg: function () {
return false;
},
isOrgOwner: function () {
return false;
}
};
it('Hard limit, enough quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: true,
hardLimit: true,
totalQuota: 2000,
usedQuota: 1800
},
creditsLeft: 200
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.enough-quota');
expect(message.type).toBe('success');
});
it('Hard limit, not enough quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: false,
hardLimit: true,
totalQuota: 2000,
usedQuota: 1800
},
creditsLeft: 100
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.hard-limit-not-enough-quota');
expect(message.type).toBe('alert');
});
it('Hard limit, not quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: true,
hardLimit: true,
totalQuota: 2000,
usedQuota: 2000
},
creditsLeft: 0
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.no-credits-body');
expect(message.type).toBe('alert');
});
it('Hard limit, not quota assigned', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: false,
hardLimit: true,
totalQuota: 0,
usedQuota: 0
},
creditsLeft: 0
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.no-quota-assigned-body');
expect(message.type).toBe('alert');
});
it('Soft limit, enough quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: true,
hardLimit: false,
totalQuota: 2000,
usedQuota: 1800
},
creditsLeft: 200
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.enough-quota');
expect(message.type).toBe('success');
});
it('Soft limit, not enough quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: false,
hardLimit: false,
totalQuota: 2000,
usedQuota: 1900
},
creditsLeft: 100
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.soft-limit-enough-quota');
expect(message.type).toBe('alert');
});
it('Soft limit, not quota', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: false,
hardLimit: false,
totalQuota: 2000,
usedQuota: 2000
},
creditsLeft: 0
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.soft-limit-enough-quota');
expect(message.type).toBe('alert');
});
it('Soft limit, not quota assigned', function () {
var payload = {
estimation: 127,
quotaInfo: {
blockPrice: 0,
blockSize: 1000,
enoughQuota: false,
hardLimit: false,
totalQuota: 0,
usedQuota: 0
},
creditsLeft: 0
};
var message = AnalysesQuotaPresenter.make(payload, configModel, userModel);
expect(message.body).toBe('editor.layers.analysis-form.quota.no-quota-assigned-body');
expect(message.type).toBe('alert');
});
});
@@ -0,0 +1,8 @@
module.exports = {
mock: function (obj, type, response) {
obj.SQL.execute = function (query, vars, params) {
var callback = params && params[type] || params.success;
callback(response);
};
}
};
@@ -0,0 +1,197 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
var ModalsService = require('builder/components/modals/modals-service-model');
var AddAnalysisView = require('builder/components/modals/add-analysis/add-analysis-view');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
describe('editor/layers/layer-content-view/analyses/analyses-service', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
options: {
type: 'CartoDB',
table_name: 'foo',
letter: 'a',
source: 'a0'
}
}, {
parse: true,
configModel: this.configModel
});
this.onboardings = new Backbone.Model();
this.onboardings.create = function () {};
this.onboardings.destroy = function () {};
this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: new Backbone.Collection(),
mapId: 'map-123',
stateDefinitionModel: {}
});
this.layerDefinitionsCollection.add(this.layerDefinitionModel);
this.modals = new ModalsService();
AnalysesService.init({
onboardings: this.onboardings,
layerDefinitionsCollection: this.layerDefinitionsCollection,
modals: this.modals,
userModel: this.userModel,
configModel: this.configModel
});
var model1 = new Backbone.Model({
createStackView: function (stackLayoutModel) {
return new CoreView();
}
});
var model2 = new Backbone.Model({
createStackView: function (stackLayoutModel) {
return new CoreView();
}
});
var collection = new Backbone.Collection([model1, model2]);
this._stackLayoutView = new StackLayoutView({
collection: collection
});
});
describe('.setLayerId', function () {
it('should set LayerId', function () {
AnalysesService.setLayerId(this.layerDefinitionModel.get('id'));
expect(AnalysesService._layerId).toBe('l-1');
});
});
describe('.setStackLayoutView', function () {
it('should set StackLayoutView', function () {
AnalysesService.setStackLayoutView(this._stackLayoutView);
expect(AnalysesService._stackLayoutView).toBe(this._stackLayoutView);
});
});
describe('.generateGeoreferenceAnalysis', function () {
it('should return a georeference analysis definition', function () {
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
this.a0 = this.analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
table_name: 'foo'
});
spyOn(this.a0, 'isCustomQueryApplied').and.returnValue(false);
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm123',
stateDefinitionModel: {}
});
layerDefinitionsCollection.add(this.layerDefinitionModel);
AnalysesService.init({
onboardings: this.onboardings,
layerDefinitionsCollection: layerDefinitionsCollection,
modals: this.modals,
userModel: this.userModel,
configModel: this.configModel
});
AnalysesService.setLayerId(this.layerDefinitionModel.get('id'));
expect(AnalysesService.generateGeoreferenceAnalysis()).toEqual({
source: 'a0',
id: 'a1',
type: 'georeference-long-lat'
});
});
});
describe('.addAnalysis', function () {
beforeEach(function () {
var mockView = new CoreView();
spyOn(AddAnalysisView.prototype, 'initialize');
spyOn(AddAnalysisView.prototype, 'render').and.returnValue(mockView);
spyOn(AnalysesService._modals, 'create');
spyOn(AnalysesService._modals, 'onDestroyOnce');
AnalysesService.setLayerId(this.layerDefinitionModel.get('id'));
AnalysesService.setStackLayoutView(this._stackLayoutView);
AnalysesService.addAnalysis();
});
afterEach(function () {
AnalysesService._modals.destroy();
});
it('should open a modal add-analysis-view', function () {
expect(AnalysesService._modals.create).toHaveBeenCalled();
});
describe('when modal is closed', function () {
beforeEach(function () {
AnalysesService.setStackLayoutView(this._stackLayoutView);
spyOn(AnalysesService._stackLayoutView.model, 'goToStep');
this.analysisFormAttrs = {id: 'a1', type: 'buffer'};
var destroyOnceArgs = AnalysesService._modals.onDestroyOnce.calls.argsFor(0);
destroyOnceArgs[0].call(destroyOnceArgs[1], this.analysisFormAttrs);
});
it('should redirect stack to layer-content', function () {
expect(AnalysesService._stackLayoutView.model.goToStep).toHaveBeenCalledWith(1, this.layerDefinitionModel, 'layer-content', 'analyses', this.analysisFormAttrs);
});
});
});
describe('.saveNotAppliedAnalysis', function () {
it('should save the analysis attrs in _notAppliedAnalysis with persisted false', function () {
AnalysesService.saveNotAppliedAnalysis({ id: 'someAnalysisId' });
expect(AnalysesService._notAppliedAnalysis).toEqual({ id: 'someAnalysisId', persisted: false });
});
});
describe('.getNotAppliedAnalysis', function () {
it('should return the value in _notAppliedAnalysis', function () {
var analysisAttrs = { id: 'whatAnAwesomeAnalysis' };
AnalysesService._notAppliedAnalysis = analysisAttrs;
expect(AnalysesService.getNotAppliedAnalysis()).toEqual(analysisAttrs);
});
});
describe('.clearNotAppliedAnalysis', function () {
it('should set _notAppliedAnalysis to null', function () {
AnalysesService._notAppliedAnalysis = { id: 'thisExistsForSure' };
expect(AnalysesService._notAppliedAnalysis).not.toBeNull();
AnalysesService.clearNotAppliedAnalysis();
expect(AnalysesService._notAppliedAnalysis).toBeNull();
});
});
});
@@ -0,0 +1,303 @@
var _ = require('underscore');
var Backbone = require('backbone');
var AnalysesView = require('builder/editor/layers/layer-content-views/analyses/analyses-view');
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
var UserNotifications = require('builder/data/user-notifications');
var ConfigModel = require('builder/data/config-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var QueryRowsCollection = require('builder/data/query-rows-collection');
var OnboardingsServiceModel = require('builder/components/onboardings/onboardings-service-model');
var LayerContentModel = require('builder/data/layer-content-model');
var ONBOARDING_KEY = 'layer-analyses-onboarding';
describe('editor/layers/layer-content-views/analyses/analyses-view', function () {
var analysisFormsCollection;
var userActions;
var layerDefinitionModel;
var getQueryAndCheckStateSpy;
var infoboxStateSpy;
var initializeBackup = PanelWithOptionsView.prototype.initialize;
var renderBackup = PanelWithOptionsView.prototype.render;
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var onboardingNotification = new UserNotifications({}, {
key: 'builder',
configModel: configModel
});
this.onboardings = new OnboardingsServiceModel();
this.onboardings.create = function () {};
this.editorModel = new Backbone.Model();
this.editorModel.isEditing = function () {
return false;
};
this.querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM table',
status: 'fetched'
}, {
configModel: {}
});
spyOn(this.querySchemaModel, 'fetch');
this.queryGeometryModel = new QueryGeometryModel({
query: 'SELECT * FROM table',
status: 'fetched',
simple_geom: 'point'
}, {
configModel: {}
});
spyOn(this.queryGeometryModel, 'fetch');
this.queryRowsCollection = new QueryRowsCollection([], {
configModel: this.configModel,
querySchemaModel: this.querySchemaModel
});
spyOn(this.queryRowsCollection, 'fetch');
layerDefinitionModel = new Backbone.Model();
layerDefinitionModel.canBeGeoreferenced = function () { return Promise.resolve(false); };
layerDefinitionModel.toggleVisible = function () { };
layerDefinitionModel.getAnalysisDefinitionNodeModel = function () {
return {
querySchemaModel: this.querySchemaModel,
queryGeometryModel: this.queryGeometryModel,
queryRowsCollection: this.queryRowsCollection
};
}.bind(this);
var analysisDefinitionNodesCollection = new Backbone.Collection();
analysisDefinitionNodesCollection.isEmpty = function () { return true; };
userActions = jasmine.createSpyObj('userActions', ['saveLayer']);
analysisFormsCollection = new Backbone.Collection();
analysisFormsCollection.isEmpty = function () {
return true;
};
var querySchemaModel = new Backbone.Model();
querySchemaModel.hasRepeatedErrors = function () { return false; };
var queryGeometryModel = new Backbone.Model();
queryGeometryModel.hasRepeatedErrors = function () { return false; };
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
var layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
getQueryAndCheckStateSpy = spyOn(AnalysesView.prototype, '_getQueryAndCheckState');
infoboxStateSpy = spyOn(AnalysesView.prototype, '_infoboxState');
this.view = new AnalysesView({
userActions: userActions,
analysisFormsCollection: analysisFormsCollection,
layerDefinitionModel: layerDefinitionModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
configModel: {},
userModel: {},
editorModel: this.editorModel,
stackLayoutModel: {},
onboardings: this.onboardings,
onboardingNotification: onboardingNotification,
layerContentModel: layerContentModel
});
PanelWithOptionsView.prototype.initialize = function () { };
PanelWithOptionsView.prototype.render = function () { return this; };
});
afterEach(function () {
PanelWithOptionsView.prototype.initialize = initializeBackup;
PanelWithOptionsView.prototype.render = renderBackup;
});
describe('initialize', function () {
it('should create _onboardingNotification', function () {
expect(this.view._onboardingNotification).not.toBeUndefined();
expect(this.view._onboardingNotification.get('key')).toBe('builder');
});
});
describe('.render', function () {
it('should render properly', function () {
spyOn(this.view, '_launchOnboarding');
spyOn(this.view, '_initViews');
this.view.render();
expect(this.view._launchOnboarding).toHaveBeenCalled();
expect(this.view._initViews).toHaveBeenCalled();
expect(infoboxStateSpy).toHaveBeenCalled();
});
});
describe('._initBinds', function () {
it('should check and set infobox state when there is a remove or reset in the analyses form collection', function () {
analysisFormsCollection.reset([{ id: 'whatever' }]);
expect(getQueryAndCheckStateSpy).toHaveBeenCalled();
getQueryAndCheckStateSpy.calls.reset();
analysisFormsCollection.remove(analysisFormsCollection.at(0));
expect(getQueryAndCheckStateSpy.calls.count()).toBe(1);
});
it('should set infobox when layer visibility changes', function () {
layerDefinitionModel.set('visible', false);
expect(infoboxStateSpy).toHaveBeenCalled();
});
});
describe('._initViews', function () {
it('should init views', function () {
this.view.render();
expect(_.size(this.view._subviews)).toBe(1);
});
});
describe('_getQueryAndCheckState', function () {
describe('analysis forms collection is not empty', function () {
it('should do nothing if onboarding was already skipped', function () {
getQueryAndCheckStateSpy.and.callThrough();
analysisFormsCollection.isEmpty = function () {
return false;
};
this.view._getQueryAndCheckState();
expect(infoboxStateSpy).toHaveBeenCalled();
});
});
});
describe('_launchOnboarding', function () {
it('should do nothing if onboarding was already skipped', function () {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, true);
spyOn(this.view._onboardingLauncher, 'launch');
spyOn(this.view._layerDefinitionModel, 'canBeGeoreferenced');
this.view._launchOnboarding();
expect(this.view._layerDefinitionModel.canBeGeoreferenced).not.toHaveBeenCalled();
expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
});
it('should do nothing if there is an analysis', function (done) {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
spyOn(this.view._analysisFormsCollection, 'isEmpty').and.returnValue(false);
spyOn(this.view._onboardingLauncher, 'launch');
spyOn(this.view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
this.view._launchOnboarding();
setTimeout(function () {
expect(this.view._onboardingNotification.getKey(ONBOARDING_KEY)).toEqual(true);
expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
done();
}.bind(this), 0);
});
it('should create and launch onboarding', function (done) {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
spyOn(this.view._analysisFormsCollection, 'isEmpty').and.returnValue(true);
spyOn(this.view._onboardingLauncher, 'launch');
spyOn(this.view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(false));
this.view._launchOnboarding();
setTimeout(function () {
expect(this.view._onboardingNotification.getKey(ONBOARDING_KEY)).toEqual(false);
expect(this.view._onboardingLauncher.launch).toHaveBeenCalled();
done();
}.bind(this), 0);
});
it('should skip onboading if layer can be geocoded', function (done) {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
spyOn(this.view._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
spyOn(this.view._onboardingNotification, 'setKey');
this.view._launchOnboarding();
setTimeout(function () {
expect(this.view._onboardingNotification.setKey).toHaveBeenCalledWith(ONBOARDING_KEY, true);
done();
}.bind(this), 0);
});
});
describe('._infoboxState', function () {
beforeEach(function () {
infoboxStateSpy.and.callThrough();
spyOn(this.view, '_isLayerHidden').and.returnValue(false);
});
describe('if layer is hidden', function () {
it('should set infobox state', function () {
this.view._isLayerHidden.and.returnValue(true);
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('layer-hidden');
expect(this.view._overlayModel.get('visible')).toBe(true);
});
});
describe('if is deleting layer', function () {
it('should set infobox state', function () {
this.view._deleteAnalysisModel.set('analysisId', 'a1');
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('deleting-analysis');
expect(this.view._overlayModel.get('visible')).toBe(true);
});
});
it('should set infobox state', function () {
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('');
expect(this.view._overlayModel.get('visible')).toBe(false);
});
});
describe('._showHiddenLayer', function () {
it('should show layer if is visible', function () {
spyOn(layerDefinitionModel, 'toggleVisible');
this.view._showHiddenLayer();
expect(layerDefinitionModel.toggleVisible).toHaveBeenCalled();
expect(userActions.saveLayer).toHaveBeenCalledWith(layerDefinitionModel);
});
});
describe('._isLayerHidden', function () {
it('should return true if layer is not visible', function () {
layerDefinitionModel.set('visible', false);
expect(this.view._isLayerHidden()).toBe(true);
});
});
});
@@ -0,0 +1,86 @@
var _ = require('underscore');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var AnalysesWorkflowItemView = require('builder/editor/layers/layer-content-views/analyses/analyses-workflow-item-view');
var Router = require('builder/routes/router');
describe('editor/layers/layer-content-view/analyses/analyses-workflow-item-view', function () {
var view, analysisNode;
var nodeId = 'a2';
var createViewFn = function (options) {
spyOn(Router, 'navigate');
analysisNode = new AnalysisDefinitionNodeModel({
id: nodeId,
type: 'buffer',
params: {}
}, {
configModel: {}
});
view = new AnalysesWorkflowItemView({
selectedNodeId: nodeId,
analysisNode: analysisNode,
layerId: 'l-1'
});
return view;
};
describe('.render', function () {
it('should render propertly', function () {
view = createViewFn();
view.render();
expect(view.$el.html()).toContain(nodeId);
expect(view.$el.html()).toContain('analyses.area-of-influence.title');
expect(_.size(view._subviews)).toBe(0); // No tooltip if it's selected
});
it('should not have any leaks', function () {
view = createViewFn();
view.render();
expect(view).toHaveNoLeaks();
});
});
describe('when is not selected', function () {
beforeEach(function () {
view = createViewFn();
view._selectedNodeId = 'a0';
view.render();
});
describe('.render', function () {
it('should render propertly', function () {
expect(_.size(view._subviews)).toBe(1); // [Tooltip]
});
});
});
describe('when is new', function () {
beforeEach(function () {
view = createViewFn();
analysisNode = undefined;
view.render();
});
describe('.render', function () {
it('should render propertly', function () {
expect(_.size(view._subviews)).toBe(0); // No tooltip if it's new
});
});
});
describe('._onNodeClick', function () {
it('should call Router.goToAnalysisNode', function () {
spyOn(Router, 'goToAnalysisNode');
view = createViewFn();
view._onNodeClick();
expect(Router.goToAnalysisNode).toHaveBeenCalledWith('l-1', nodeId);
});
});
});
@@ -0,0 +1,127 @@
var Backbone = require('backbone');
var _ = require('underscore');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var AnalysesWorkflowListView = require('builder/editor/layers/layer-content-views/analyses/analyses-workflow-list-view');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var Router = require('builder/routes/router');
describe('editor/layers/layer-content-view/analyses/analyses-workflow-list-view', function () {
beforeEach(function () {
spyOn(Router, 'navigate');
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: {},
userModel: {}
});
this.analysisDefinitionNodesCollection.add({
id: 'a1',
type: 'source',
status: 'ready'
});
this.analysisDefinitionNodesCollection.add({
id: 'a2',
type: 'source',
status: 'ready'
});
this.analysisDefinitionNodesCollection.add({
id: 'a3',
type: 'source',
status: 'ready'
});
this.layerDefModel = new LayerDefinitionModel({
id: 'l-1',
options: {
type: 'CartoDB',
table_name: 'foo',
source: 'a1',
letter: 'a'
}
}, {
parse: true,
collection: new Backbone.Collection(),
configModel: {}
});
spyOn(this.layerDefModel, 'getAnalysisDefinitionNodeModel');
this.analysisFormsCollection = new AnalysisFormsCollection(null, {
configModel: {},
userActions: {},
layerDefinitionModel: this.layerDefModel,
analysisSourceOptionsModel: {}
});
this.analysisFormsCollection.add([
{id: 'a3'},
{id: 'a2'},
{id: 'a1'}
]);
spyOn(this.analysisFormsCollection, 'deleteNode');
this.view = new AnalysesWorkflowListView({
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
analysisFormsCollection: this.analysisFormsCollection,
selectedNodeId: 'a2',
layerId: this.layerDefModel.id,
hideAddButton: false
});
this.view.render();
});
describe('.render', function () {
it('should render correctly', function () {
expect(_.size(this.view._subviews)).toBe(4); // [3 x Analysis Node, Tooltip]
});
it('should display add button and the list of analysis', function () {
expect(this.view.$('.js-add-analysis').length).toBe(1);
expect(this.view.$('.js-add-analysis').data('layer-id')).toEqual('l-1');
});
it('should render an item per node', function () {
var content = this.view.$el.html();
expect(content).toContain('a3');
expect(content).toContain('a2');
expect(content).toContain('a1');
});
it('should highlight selected node', function () {
expect(this.view.$('li:eq(1) .VerticalRadioList-radio .CDB-Radio').attr('checked')).toBe('checked');
expect(this.view.$('li:eq(1)').text()).toContain('a2');
});
});
describe('when a node has failed', function () {
it('should have a has-failed class', function () {
var analysisNodeModel = this.analysisDefinitionNodesCollection.models[0];
analysisNodeModel.set('status', 'failed');
this.view.render();
// The first analysis is the last in the list
expect(this.view.$('li:eq(2) .VerticalRadioList-itemInner').hasClass('has-error')).toBe(true);
});
});
describe('when a node is loading', function () {
it('should show a loader', function () {
var analysisNodeModel = this.analysisDefinitionNodesCollection.models[0];
analysisNodeModel.set('status', 'loading');
this.view.render();
// The first analysis is the last in the list
expect(this.view.$('li:eq(2)').html()).toContain('CDB-LoaderIcon');
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
afterEach(function () {
this.view.clean();
});
});
@@ -0,0 +1,102 @@
var Backbone = require('backbone');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var AnalysesWorkflowView = require('builder/editor/layers/layer-content-views/analyses/analyses-workflow-view');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
describe('editor/layers/layer-content-view/analyses/analyses-workflow-view', function () {
beforeEach(function () {
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: {},
userModel: {}
});
this.analysisDefinitionNodesCollection.add({
id: 'a1',
type: 'source',
status: 'ready'
});
this.analysisDefinitionNodesCollection.add({
id: 'a2',
type: 'source',
status: 'ready'
});
this.analysisDefinitionNodesCollection.add({
id: 'a3',
type: 'source',
status: 'ready'
});
this.layerDefModel = new LayerDefinitionModel({
id: 'l-1',
options: {
type: 'CartoDB',
table_name: 'foo',
source: 'a1',
letter: 'a'
}
}, {
parse: true,
collection: new Backbone.Collection(),
configModel: {}
});
spyOn(this.layerDefModel, 'getAnalysisDefinitionNodeModel');
spyOn(this.layerDefModel, 'findAnalysisDefinitionNodeModel');
spyOn(this.layerDefModel, 'canBeGeoreferenced').and.returnValue(false);
this.nodeDefModel = new AnalysisDefinitionNodeModel({
id: 'a2',
type: 'buffer',
params: {}
}, {
configModel: {}
});
this.analysisFormsCollection = new AnalysisFormsCollection(null, {
configModel: {},
userActions: {},
layerDefinitionModel: this.layerDefModel,
analysisSourceOptionsModel: {}
});
this.analysisFormsCollection.add([
{id: 'a3'},
{id: 'a2'},
{id: 'a1'}
]);
this.view = new AnalysesWorkflowView({
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
analysisFormsCollection: this.analysisFormsCollection,
selectedNodeId: 'a2',
layerDefinitionModel: this.layerDefModel
});
this.view.render();
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render when analysis node is added', function () {
spyOn(this.view, 'render').and.callThrough();
this.analysisDefinitionNodesCollection.add({
id: 'a5',
type: 'source',
status: 'ready'
});
expect(this.view.render).not.toHaveBeenCalled();
// A change in the source should trigger the render
this.layerDefModel.set({ source: 'a5' });
expect(this.view.render).toHaveBeenCalled();
// But another change in the source without adding a node should not
this.layerDefModel.set({ source: 'a4' });
expect(this.view.render.calls.count()).toBe(1);
});
});
@@ -0,0 +1,668 @@
var Backbone = require('backbone');
var _ = require('underscore');
var userActions = require('builder/data/user-actions');
var UserModel = require('builder/data/user-model');
var BaseAnalysisFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model');
var AnalysisControlsView = require('builder/editor/layers/layer-content-views/analyses/analysis-controls-view');
var analyses = require('builder/data/analyses');
var AnalysesQuotaOptions = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-options');
var AnalysesQuotaInfo = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-info');
var AnalysesQuotaEnough = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-enough');
var AnalysesQuotaEstimation = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-estimation-input');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var mockSQL = require('./analyses-quota/mock-sql');
describe('editor/layers/layer-content-view/analyses/analysis-controls-view', function () {
beforeEach(function () {
spyOn(_, 'debounce').and.callFake(function (func) {
return function () {
func.apply(this, arguments);
};
});
this.configModel = new Backbone.Model({
user_name: 'foo',
sql_api_template: 'foo',
api_key: 'foo'
});
this.querySchemaModel = new Backbone.Model({
query: 'SELECT * from foo'
});
this.deleteAnalysisModel = new Backbone.Model({
analysisId: null
});
this.quotaInfo = AnalysesQuotaInfo.get(this.configModel);
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 23, soft_limit: false, provider: 'heremaps'},
{service: 'hires_geocoder', monthly_quota: 1000, used_quota: 0, soft_limit: true, provider: 'heremaps'},
{service: 'routing', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'mapzen'},
{service: 'observatory', monthly_quota: 1000, used_quota: 0, soft_limit: false, provider: 'data observatory'}
]
});
this.analysisNode = new Backbone.Model();
this.analysisNode.hasFailed = function () {
return this.analysisNode.get('status') === 'failed';
}.bind(this);
this.analysisNode.isDone = function () {
return this.analysisNode.get('status') === 'ready';
}.bind(this);
this.analysisNode.canBeDeletedByUser = function () {
return true;
};
this.userModel = new UserModel({
username: 'pepe',
isolines_provider: 'heremaps',
actions: {
private_tables: true
}
}, {
configModel: 'c'
});
this.formModel = new BaseAnalysisFormModel({
id: 'a1',
type: 'routing'
}, {
analyses: analyses,
configModel: {},
layerDefinitionModel: {},
analysisSourceOptionsModel: {}
});
spyOn(this.formModel, 'isValid').and.returnValue(true);
this.userActions = userActions({
userModel: this.userModel,
analysisDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
widgetDefinitionsCollection: {}
});
spyOn(this.userActions, 'saveAnalysis');
this.analysisFormsCollection = new AnalysisFormsCollection(null, {
userActions: this.userActions,
configModel: this.configModel,
layerDefinitionModel: {},
analysisSourceOptionsModel: {}
});
spyOn(this.analysisFormsCollection, 'deleteNode');
this.analysisFormsCollection.add([{ id: 'a1' }]);
this.stackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['prevStep']);
this._layerDefinitionModel = {
canBeGeoreferenced: function () {
},
hasAnalyses: function () {}
};
spyOn(this._layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
spyOn(this._layerDefinitionModel, 'hasAnalyses');
});
it('should not fetch quota if no analysis node', function () {
spyOn(AnalysisControlsView.prototype, '_fetchQuotaIfNeeded').and.callThrough();
var view = new AnalysisControlsView({
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
expect(view._viewModel).toBeDefined();
expect(AnalysisControlsView.prototype._fetchQuotaIfNeeded).toHaveBeenCalled();
});
describe('.initialize', function () {
beforeEach(function () {
this.view = new AnalysisControlsView({
analysisNode: this.analysisNode,
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
});
it('should generate a viewModel', function () {
expect(this.view._viewModel).toBeDefined();
expect(this.view._viewModel.get('isNewAnalysis')).toBeFalsy();
expect(this.view._viewModel.get('hasChanges')).toBeFalsy();
});
describe('._initViewState', function () {
it('should initialize viewState properly', function () {
spyOn(this.view, '_setViewState');
this.view._initViewState();
expect(this.view._viewState).toBeDefined();
expect(this.view._viewState.get('canBeGeoreferenced')).toEqual(false);
expect(this.view._setViewState).toHaveBeenCalled();
});
});
it('should render when form changes', function () {
spyOn(this.view, 'render');
this.formModel.set('type', 'foo');
expect(this.view.render).toHaveBeenCalled();
});
it('should add the track class', function () {
this.view.render();
expect(this.view.$('.js-save').hasClass('track-apply')).toBeTruthy();
expect(this.view.$('.js-save').hasClass('track-routing-analysis')).toBeTruthy();
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
describe('analysis without quota', function () {
beforeEach(function () {
this.formModel.set('type', 'centroid');
this.view = new AnalysisControlsView({
analysisNode: this.analysisNode,
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
this.view.render();
});
it('should render a save button disabled', function () {
expect(this.view.$el.html()).toContain('is-disabled');
});
it('should not allow to save', function () {
this.view.$('.js-save').click();
expect(this.userActions.saveAnalysis).not.toHaveBeenCalled();
});
it('should have apply label since it is not persisted yet', function () {
expect(this.view.$('.js-save').html()).toContain('apply');
});
it('should not render quota info in any case', function () {
spyOn(this.view, '_createQuotaView').and.callThrough();
spyOn(this.view, '_createButtonsView').and.callThrough();
spyOn(AnalysesQuotaOptions, 'requiresQuota').and.returnValue(false);
spyOn(this.view, '_isAnalysisDone').and.returnValue(false);
spyOn(this.view, '_hasChanges').and.returnValue(true);
this.view.render();
expect(this.view._createQuotaView).not.toHaveBeenCalled();
expect(this.view._createButtonsView).toHaveBeenCalled();
this.view._createButtonsView.calls.reset();
this.view._viewModel.attributes.userFetchModelState = 'error';
this.view.render();
expect(this.view._createQuotaView).not.toHaveBeenCalled();
expect(this.view._createButtonsView.calls.count()).toEqual(1);
this.view._createButtonsView.calls.reset();
this.view._viewModel.attributes.userFetchModelState = 'fetching';
this.view.render();
expect(this.view._createQuotaView).not.toHaveBeenCalled();
expect(this.view._createButtonsView.calls.count()).toEqual(1);
});
describe('when form is valid', function () {
beforeEach(function () {
this.formModel.set('foo', 'just to trigger re-render');
});
it('should enable button when analysis node\'s status is ready', function () {
expect(this.view.$el.html()).toContain('is-disabled');
this.analysisNode.set('status', 'ready');
expect(this.view.$el.html()).not.toContain('is-disabled');
});
describe('when save button is clicked', function () {
beforeEach(function () {
this.analysisNode.set('status', 'ready');
this.view.$('.js-save').click();
});
it('should save changes', function () {
expect(this.userActions.saveAnalysis).toHaveBeenCalledWith(this.formModel);
});
});
});
describe('when the analysis failed', function () {
beforeEach(function () {
this.formModel.set('foo', 'just to trigger re-render');
});
it('should enable button when analysis node\'s status is ready', function () {
expect(this.view.$el.html()).toContain('is-disabled');
this.analysisNode.set('status', 'failed');
expect(this.view.$el.html()).not.toContain('is-disabled');
});
describe('when save button is clicked', function () {
beforeEach(function () {
this.analysisNode.set('status', 'failed');
this.view.$('.js-save').click();
});
it('should save changes', function () {
expect(this.userActions.saveAnalysis).toHaveBeenCalledWith(this.formModel);
});
});
});
describe('when model is persisted', function () {
beforeEach(function () {
this.formModel.set('persisted', true);
this.view.render();
});
it('should change label to apply', function () {
expect(this.view.$('.js-save').html()).toContain('apply');
});
});
});
describe('analysis with quota', function () {
beforeEach(function () {
this.formModel.set('type', 'trade-area');
this.view = new AnalysisControlsView({
analysisNode: this.analysisNode,
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
this.quotaInfo.fetch();
mockSQL.mock(AnalysesQuotaEstimation, 'success', {
rows: [{'QUERY PLAN': 'Seq Scan on foo (cost=0.00..6.97 rows=325 width=108)'}]
});
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': true}]
});
});
it('should not fetch quota when form changes and not valid', function () {
spyOn(this.view, '_fetchQuotaIfNeeded').and.callThrough();
spyOn(this.view, '_fetchQuota');
this.formModel.isValid.and.returnValue(false);
this.view.render();
this.formModel.trigger('change');
expect(this.view._fetchQuotaIfNeeded).toHaveBeenCalled();
expect(this.view._fetchQuota).not.toHaveBeenCalled();
});
it('should render loading when fetching', function () {
this.view.render();
this.view._infoboxModel.set('state', 'fetching');
expect(this.view.$('.Infobox-wrapper').length).toBe(1);
expect(this.view.$('.Infobox .CDB-LoaderIcon').length).toBe(1); // Loading state
expect(this.view.$('.Infobox-wrapper').text()).toContain('editor.layers.analysis-form.quota.loading');
});
it('should not let user run the analysis if form is not valid', function () {
this.formModel.isValid.and.returnValue(false);
this.userActions.saveAnalysis.calls.reset();
this.view.render();
this.view.$('.js-action').click();
expect(this.userActions.saveAnalysis).not.toHaveBeenCalled();
});
it('should not let user run the analysis if user doesn\'t have credit assigned', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 0, used_quota: 0, soft_limit: true, provider: 'heremaps'}
]
});
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': true}]
});
this.userModel.set('here_isolines', {
block_price: 1
});
this.userActions.saveAnalysis.calls.reset();
this.view.render();
this.formModel.trigger('change');
expect(this.view.$el.html()).toContain('is-disabled');
this.view.$('.js-action').click();
expect(this.userActions.saveAnalysis).not.toHaveBeenCalled();
});
it('should not let user run the analysis if not enough quota', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 2000, soft_limit: false, provider: 'heremaps'}
]
});
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': false}]
});
this.userModel.set('here_isolines', {
block_price: 1
});
this.userActions.saveAnalysis.calls.reset();
this.formModel.trigger('change');
expect(this.view.$el.html()).toContain('is-disabled');
this.view.$('.js-action').click();
expect(this.userActions.saveAnalysis).not.toHaveBeenCalled();
});
it('should let user run the analysis if no credits but soft limit', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 2000, soft_limit: false, provider: 'heremaps'}
]
});
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': true}]
});
this.userModel.set('here_isolines', {
block_price: 1
});
this.userActions.saveAnalysis.calls.reset();
this.formModel.trigger('change');
expect(this.view.$el.html()).not.toContain('is-disabled');
this.view.$('.js-action').click();
expect(this.userActions.saveAnalysis).toHaveBeenCalled();
});
it('should change label to confirm', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': true}]
});
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 1900, soft_limit: false, provider: 'heremaps'}
]
});
this.formModel.trigger('change');
expect(this.view.$('.js-action').html()).toContain('confirm');
});
it('should save changes', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
this.analysisNode.set('status', 'ready');
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 1900, soft_limit: false, provider: 'heremaps'}
]
});
this.userModel.set('here_isolines', {
block_price: 1
});
this.view.render();
this.view.$('.js-save').click();
expect(this.userActions.saveAnalysis).toHaveBeenCalledWith(this.formModel);
});
it('should change analysis node\'s state to launched when saving', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
this.analysisNode.set('status', 'ready');
var nodeChange = jasmine.createSpy('nodeChange');
this.analysisNode.on('change:status', nodeChange);
mockSQL.mock(this.quotaInfo, 'success', {
rows: [
{service: 'isolines', monthly_quota: 2000, used_quota: 1900, soft_limit: false, provider: 'heremaps'}
]
});
this.view.render();
this.view.$('.js-save').click();
// silent when putting launched
expect(nodeChange.calls.count()).toBe(0);
expect(this.analysisNode.get('status')).toBe('launched');
expect(this.userActions.saveAnalysis).toHaveBeenCalledWith(this.formModel);
});
it('render a save button disabled after saved an analysis', function () {
this.view._viewModel.set('hasChanges', false);
this.view.render();
this.analysisNode.set('status', 'ready');
expect(this.view.$el.html()).toContain('is-disabled');
});
describe('dataservices api not existing', function () {
beforeEach(function () {
this.quotaInfo._state = 'error';
});
it('should avoid fetching quota', function () {
spyOn(this.view, '_fetchQuota');
expect(this.view._fetchQuota).not.toHaveBeenCalled();
});
it('should show an infowindow error if quota view should be shown for edited analysis', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
this.view.render();
expect(this.view._infoboxModel.get('state')).toBe('error');
expect(this.view.$el.html()).toContain('editor.layers.analysis-form.quota.quota-fetch-error');
});
});
});
describe('change analysis type', function () {
beforeEach(function () {
this.formModel.set('type', 'georeference-ip-address');
this.view = new AnalysisControlsView({
analysisNode: this.analysisNode,
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
this.quotaInfo.fetch();
mockSQL.mock(AnalysesQuotaEstimation, 'success', {
rows: [{'QUERY PLAN': 'Seq Scan on foo (cost=0.00..6.97 rows=325 width=108)'}]
});
mockSQL.mock(AnalysesQuotaEnough, 'success', {
rows: [{'cdb_enough_quota': true}]
});
});
it('should render properly if type changes', function () {
spyOn(this.view, '_canSave').and.returnValue(true);
this.view.render();
expect(this.view.$el.html()).not.toContain('is-disabled');
expect(this.view.$('.Infobox-wrapper').length).toBe(0);
this.formModel.set({type: 'georeference-street-address'});
expect(this.view.$('.Infobox-wrapper').length).toBe(1);
});
});
describe('when can delete', function () {
beforeEach(function () {
this.view = new AnalysisControlsView({
analysisNode: this.analysisNode,
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
this.view.render();
});
it('should render deletion if can delete', function () {
expect(this.view.$el.html()).toContain('js-delete');
});
describe('when click delete-analysis', function () {
describe('when the analysis is the last node', function () {
it('should delete selectednode', function () {
this.view._formModel = this.analysisFormsCollection.first();
this.view.$('.js-delete').click();
expect(this.analysisFormsCollection.deleteNode).toHaveBeenCalledWith('a1');
});
});
describe('when the analysis is not the last node', function () {
it('should set the nodeId in the deleteAnalysisNode model', function () {
var newAnalysis = this.analysisFormsCollection.add({ id: 'a2' });
this.view._formModel = newAnalysis;
this.view.$('.js-delete').click();
expect(this.analysisFormsCollection.deleteNode).not.toHaveBeenCalled();
expect(this.deleteAnalysisModel.get('analysisId')).toEqual('a2');
});
});
});
});
describe('delete button', function () {
beforeEach(function () {
this.view = new AnalysisControlsView({
analysisFormsCollection: this.analysisFormsCollection,
userModel: this.userModel,
userActions: this.userActions,
formModel: this.formModel,
stackLayoutModel: this.stackLayoutModel,
configModel: this.configModel,
quotaInfo: this.quotaInfo,
querySchemaModel: this.querySchemaModel,
layerDefinitionModel: this._layerDefinitionModel,
deleteAnalysisModel: this.deleteAnalysisModel
});
spyOn(this.view, '_isAnalysisDone').and.returnValue(true);
spyOn(this.view, '_hasChanges').and.returnValue(false);
});
describe('should be disabled', function () {
it('if layer needs georeferencing', function (done) {
var self = this;
this._layerDefinitionModel.canBeGeoreferenced.and.returnValue(Promise.resolve(true));
this._layerDefinitionModel.hasAnalyses.and.returnValue(false);
this.view.render();
setTimeout(function () {
expect(self.view.$('.js-delete').hasClass('is-disabled')).toBe(true);
done();
}, 0);
});
});
describe('should be enabled', function () {
beforeEach(function () {
this._layerDefinitionModel.hasAnalyses.and.returnValue(true);
});
it('if layer needs georeferencing but there are analyses already', function (done) {
var self = this;
this._layerDefinitionModel.canBeGeoreferenced.and.returnValue(Promise.resolve(true));
this.view.render();
setTimeout(function () {
expect(self.view.$('.js-delete').hasClass('is-disabled')).toBe(false);
done();
}, 0);
});
it('if layer does not need georeferencing', function (done) {
var self = this;
this._layerDefinitionModel.canBeGeoreferenced.and.returnValue(Promise.resolve(false));
this.view.render();
setTimeout(function () {
expect(self.view.$('.js-delete').hasClass('is-disabled')).toBe(false);
done();
}, 0);
});
});
});
});
@@ -0,0 +1,159 @@
var Backbone = require('backbone');
var camshaftReference = require('builder/data/camshaft-reference');
var areaOfInfluenceTemplate = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form.tpl');
var BaseAnalysisFormModel = require('builder/editor/layers/layer-content-views/analyses/analysis-form-models/base-analysis-form-model');
var AnalysisFormView = require('builder/editor/layers/layer-content-views/analyses/analysis-form-view');
var analyses = require('builder/data/analyses');
describe('editor/layers/layer-content-view/analyses/analysis-form-view', function () {
beforeEach(function () {
this.formModel = new BaseAnalysisFormModel({
id: 'a1',
type: 'buffer',
source: 'a0',
radius: '100'
}, {
analyses: analyses,
configModel: {},
layerDefinitionModel: {},
analysisSourceOptionsModel: {}
});
this.formModel.schema.source = {type: 'Text'};
this.formModel.schema.radius = {type: 'Number'};
spyOn(this.formModel, 'getTemplate').and.returnValue(areaOfInfluenceTemplate);
spyOn(this.formModel, 'getTemplateData').and.returnValue({parametersDataFields: 'radius'});
spyOn(this.formModel, 'setFormValidationErrors').and.callThrough();
spyOn(camshaftReference, 'validate');
this.view = new AnalysisFormView({
formModel: this.formModel,
configModel: {}
});
this.view.render();
});
it('should render with template and data from form model', function () {
expect(this.view.$el.html()).toContain('form');
expect(this.view.$el.html()).toContain('data-fields="radius"');
});
it('should not validate when view is rendered intially', function () {
expect(camshaftReference.validate).not.toHaveBeenCalled();
});
describe('when form changes with erroneous data', function () {
beforeEach(function () {
camshaftReference.validate.and.returnValue({radius: '42 is not the answer, you fool!'});
// simulate change
this.view._formView.setValue('radius', '42');
this.view._formView.trigger('change');
});
it('should show errors when validation fails', function (done) {
var self = this;
setTimeout(function () {
expect(camshaftReference.validate).toHaveBeenCalled();
expect(self.view.$el.html()).toContain('Error');
done();
}, 0);
});
it('should update form model anyway', function () {
expect(this.formModel.get('radius')).toEqual(42);
});
it('should set form validation errors on the model', function (done) {
var self = this;
setTimeout(function () {
expect(self.formModel.setFormValidationErrors).toHaveBeenCalled();
expect(self.formModel.setFormValidationErrors.calls.argsFor(0)[0]).toBeUndefined();
expect(self.formModel.setFormValidationErrors.calls.argsFor(1)[0]).toEqual({radius: jasmine.any(String)});
done();
}, 0);
});
describe('when validation passes', function () {
beforeEach(function () {
this.formModel.setFormValidationErrors.calls.reset();
camshaftReference.validate.and.returnValue(undefined);
// simulate change
this.view._formView.setValue('radius', '20');
this.view._formView.trigger('change');
});
it('should remove form validation errors', function () {
expect(this.formModel.setFormValidationErrors.calls.argsFor(0)[0]).toBeUndefined();
expect(this.formModel.setFormValidationErrors.calls.argsFor(1)[0]).toBeUndefined();
});
it('should update model', function () {
expect(this.formModel.get('radius')).toEqual(20);
});
});
});
describe('when schema changes', function () {
beforeEach(function () {
this.prev$form = this.view.$('form');
this.formModel.trigger('changeSchema');
});
afterEach(function () {
this.prev$form = null;
});
it('should re-render the form', function () {
expect(this.view.$('form').length).toEqual(1);
expect(this.view.$('form')).not.toBe(this.prev$form);
});
});
describe('_onChangeAnalysisFormView and _showAnalysisFormErrors', function () {
it('_onChangeAnalysisFormView calls _showAnalysisFormErrors with the form view Id that was active when the function was called', function (done) {
var self = this;
spyOn(this.view, '_showAnalysisFormErrors');
var formId = this.view._formView.cid;
this.view._onChangeAnalysisFormView();
setTimeout(function () {
expect(self.view._showAnalysisFormErrors).toHaveBeenCalledWith(formId);
done();
}, 0);
});
it('_showAnalysisFormErrors should not commit the form if the current form is different from the one who called it', function () {
var _prevShowFn = this.view._showAnalysisFormErrors;
var self = this;
spyOn(this.view, '_showAnalysisFormErrors').and.callFake(function (formId) {
self.view._formView.cid = 'another-form-808';
_prevShowFn.call(this, formId);
});
spyOn(this.view, '_formView');
this.view._onChangeAnalysisFormView();
expect(this.view._formView).not.toHaveBeenCalled();
});
});
describe('when form is cleaned', function () {
beforeEach(function () {
spyOn(Backbone.Form.prototype, 'remove').and.callThrough();
this.view.clean();
});
it('should remove form when view is cleaned', function () {
expect(Backbone.Form.prototype.remove).toHaveBeenCalled();
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,163 @@
var Backbone = require('backbone');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisSourceOptionsModel = require('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model');
var AnalysisFormsCollection = require('builder/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var UserActions = require('builder/data/user-actions');
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
describe('builder/editor/layers/layer-content-view/analyses/analysis-forms-collection', function () {
beforeEach(function () {
var configModel = new Backbone.Model();
configModel.dataServiceEnabled = function () {
return true;
};
this.analysisDefinitionNodesCollection = new Backbone.Collection();
var nodeOpts = {
configModel: configModel,
collection: this.analysisDefinitionNodesCollection
};
this.a0 = new AnalysisDefinitionNodeModel({id: 'a0', type: 'trade-area'}, nodeOpts);
spyOn(this.a0, 'hasPrimarySource').and.returnValue(false);
this.a1 = new AnalysisDefinitionNodeModel({id: 'a1', type: 'buffer'}, nodeOpts);
spyOn(this.a1, 'hasPrimarySource').and.returnValue(true);
spyOn(this.a1, 'getPrimarySource').and.returnValue(this.a0);
this.a2 = new AnalysisDefinitionNodeModel({id: 'a2', type: 'source'}, nodeOpts);
spyOn(this.a2, 'hasPrimarySource').and.returnValue(true);
spyOn(this.a2, 'getPrimarySource').and.returnValue(this.a1);
this.analysisDefinitionNodesCollection.reset([this.a0, this.a1, this.a2]);
this.layerDefModel = new LayerDefinitionModel({
id: 'l-1',
options: {
type: 'CartoDB',
table_name: 'foo',
source: 'a2',
letter: 'a'
}
}, {
parse: true,
collection: new Backbone.Collection(),
configModel: configModel
});
spyOn(this.layerDefModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.a2);
this.analysisSourceOptionsModel = new AnalysisSourceOptionsModel(null, {
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionsCollection: this.layerDefModel.collection,
tablesCollection: new Backbone.Collection()
});
var userModel = new Backbone.Model();
userModel.dataServiceEnabled = function () {
return true;
};
this.userActions = UserActions({
userModel: userModel,
analysisDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
widgetDefinitionsCollection: {}
});
this.collection = new AnalysisFormsCollection(null, {
configModel: configModel,
layerDefinitionModel: this.layerDefModel,
analysisSourceOptionsModel: this.analysisSourceOptionsModel,
userActions: this.userActions
});
});
describe('.resetByLayerDefinition', function () {
beforeEach(function () {
this.collection.resetByLayerDefinition();
});
it('should have created a form model for each node (excluding the source)', function () {
expect(this.collection.pluck('id')).toEqual(['a2', 'a1']);
});
it('should have same attributes as the node models they represent', function () {
expect(this.collection.get('a1').attributes).toEqual(jasmine.objectContaining(this.a1.attributes));
expect(this.collection.get('a2').attributes).toEqual(jasmine.objectContaining(this.a2.attributes));
});
it('should mark them as persisted', function () {
expect(this.collection.get('a1').attributes).toEqual(jasmine.objectContaining({persisted: true}));
expect(this.collection.get('a2').attributes).toEqual(jasmine.objectContaining({persisted: true}));
});
});
describe('.addHead', function () {
beforeEach(function () {
spyOn(this.collection, 'remove').and.callThrough();
spyOn(AnalysesService, 'saveNotAppliedAnalysis');
this.res = this.collection.addHead({id: 'a3'}); // unknown
});
it('should return the created formModel', function () {
expect(this.res).toBeDefined();
});
it('should replace any existing node with same id', function () {
// if have same id it's because the current head node is a temporary one that wasn't saved, so just replace it
expect(this.collection.remove).toHaveBeenCalledWith('a3');
});
it('should prepend new form model', function () {
expect(this.collection.first().id).toEqual('a3');
});
it('should call AnalysesService.saveNotAppliedAnalysis with the new form model', function () {
expect(AnalysesService.saveNotAppliedAnalysis).toHaveBeenCalledWith({id: 'a3'});
});
});
describe('.deleteNode', function () {
describe('when given id represents an persisted node', function () {
beforeEach(function () {
spyOn(this.userActions, 'deleteAnalysisNode');
this.collection.deleteNode('a2');
});
it('should destroy the node-definition for given id', function () {
expect(this.userActions.deleteAnalysisNode).toHaveBeenCalledWith('a2');
});
});
describe('when given id is for a new form model', function () {
beforeEach(function () {
this.collection.add({id: 'a3'}, {at: 0});
this.layerDefModel.findAnalysisDefinitionNodeModel.and.returnValue();
this.collection.deleteNode('a3');
});
it('should just remove the form model from the collection', function () {
expect(this.collection.pluck('id')).toEqual([]);
});
});
});
describe('for a form model', function () {
beforeEach(function () {
this.nodeDefModel = this.analysisDefinitionNodesCollection.add({
id: 'a1'
});
this.model = this.collection.add({
id: 'a1',
type: 'buffer',
radius: 100
});
});
it('should create a form model', function () {
expect(this.model).toBeDefined();
});
});
});
@@ -0,0 +1,206 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var TablesCollection = require('builder/data/tables-collection');
var TableModel = require('builder/data/table-model');
var AnalysisSourceOptionsModel = require('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model');
describe('builder/editor/layers/layer-content-views/analyses/analysis-source-options-model', function () {
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
spyOn(TableModel.prototype, 'fetch');
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: {}
});
this.analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
params: {
table_name: 'hello',
query: 'SELECT * FROM first'
}
});
var layerModel = new Backbone.Model({
name: 'Layer Name',
table_name: 'table_name',
source: 'a0',
color: '#000'
});
layerModel.getName = function () { return this.get('name'); };
layerModel.getTableName = function () { return this.get('table_name'); };
this.layerDefinitionsCollection = new Backbone.Collection(layerModel);
this.layerDefinitionsCollection.findOwnerOfAnalysisNode = function (node) {
return layerModel;
};
this.tablesCollection = new TablesCollection(null, {
configModel: configModel
});
this.model = new AnalysisSourceOptionsModel(null, {
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
layerDefinitionsCollection: this.layerDefinitionsCollection,
tablesCollection: this.tablesCollection
});
});
it('should not have any select options just yet', function () {
expect(this.model.getSelectOptions('point')).toEqual([]);
expect(this.model.getSelectOptions('polygon')).toEqual([]);
});
describe('when analysis are fetching but there are nodes', function () {
beforeEach(function () {
var nodes = [{
label: 'a0',
simpleGeometryType: 'polygon',
val: 'a0'
}];
this.model.set({
nodes_options: nodes,
fetching: true
});
});
it('should populate nodes from already fetched nodes', function () {
expect(this.model.getSelectOptions('polygon')).toEqual([
jasmine.objectContaining({
val: 'a0',
label: 'a0',
type: 'node'
})
]);
});
});
describe('when analysis are already fetched', function () {
beforeEach(function () {
spyOn(this.tablesCollection, 'fetch');
spyOn(this.analysisDefinitionNodesCollection.first().queryGeometryModel, 'fetch');
this.analysisDefinitionNodesCollection.first().queryGeometryModel.set({
simple_geom: 'polygon',
status: 'fetched'
});
this.model.fetch();
this.tablesCollection.trigger('sync');
expect(this.model.get('fetching')).toBe(false);
});
it('should populate nodes from already fetched nodes', function () {
expect(this.model.getSelectOptions('polygon')).toEqual([
jasmine.objectContaining({
val: 'a0',
label: 'a0',
type: 'node'
})
]);
});
});
describe('when analysis nodes are fetched', function () {
beforeEach(function () {
spyOn(this.tablesCollection, 'fetch');
spyOn(this.analysisDefinitionNodesCollection.first().queryGeometryModel, 'fetch');
this.model.fetch();
});
it('should fetch tables reducing the data to be retrieved', function () {
expect(this.tablesCollection.fetch.calls.first().args).toContain(
{
data: Object({
show_liked: false,
show_stats: false,
show_table_size_and_row_count: false,
show_permission: false,
show_synchronization: false,
show_uses_builder_features: false,
load_totals: false,
per_page: 1000
})
}
);
});
it('should be in fetching state', function () {
expect(this.model.get('fetching')).toBe(true);
});
describe('when all items are fetched successfully', function () {
beforeEach(function () {
this.tablesCollection.add({
name: 'table_with_points',
geometry_types: ['ST_POINT']
});
this.tablesCollection.trigger('sync');
expect(this.model.get('fetching')).toBe(true);
this.analysisDefinitionNodesCollection.first().queryGeometryModel.set({
simple_geom: 'polygon',
status: 'fetched'
});
});
it('should not be fetching anymore', function () {
expect(this.model.get('fetching')).toBe(false);
});
it('should have items matching geometry type', function () {
expect(this.model.getSelectOptions('polygon')).toEqual([
jasmine.objectContaining({
val: 'a0',
label: 'a0',
type: 'node'
})
]);
expect(this.model.getSelectOptions('point')).toEqual([
jasmine.objectContaining({
val: 'table_with_points',
label: 'table_with_points',
type: 'dataset'
})
]);
});
it('should have items matching multiple accepted geometries', function () {
expect(this.model.getSelectOptions(['polygon', 'point'])).toEqual([
jasmine.objectContaining({
val: 'a0',
label: 'a0',
type: 'node'
}),
jasmine.objectContaining({
val: 'table_with_points',
label: 'table_with_points',
type: 'dataset'
})
]);
});
it('should have items matching a wildcard geometry', function () {
expect(this.model.getSelectOptions(['*'])).toEqual([
jasmine.objectContaining({
val: 'a0',
label: 'a0',
type: 'node'
}),
jasmine.objectContaining({
val: 'table_with_points',
label: 'table_with_points',
type: 'dataset'
})
]);
});
});
});
});
@@ -0,0 +1,566 @@
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var LayerContentModel = require('builder/data/layer-content-model');
var DataContentView = require('builder/editor/layers/layer-content-views/data/data-content-view');
var DataColumnsModel = require('builder/editor/layers/layer-content-views/data/data-columns-model');
var TableStats = require('builder/components/modals/add-widgets/tablestats');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var dataNoGeometryTemplate = require('builder/editor/layers/layer-content-views/data/data-content-nogeometry.tpl');
describe('editor/layers/layers-content-view/data/data-content-view', function () {
var view;
var layerDefinitionModel;
var layerContentModel;
var querySchemaModel;
var queryGeometryModel;
var columnsModel;
var widgetDefinitionsCollection;
var createViewFn = function (options) {
var configModel = new ConfigModel({
base_url: '/u/pepe',
user_name: 'cdb',
api_key: 'foo'
});
var ts = new TableStats({
configModel: configModel
});
var overlayModel = new Backbone.Model({
visible: false
});
querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM table',
status: 'fetched'
}, {
configModel: {}
});
queryGeometryModel = new QueryGeometryModel({
query: 'SELECT * FROM table',
simple_geom: 'point',
status: 'fetched'
}, {
configModel: {}
});
var analysisDefinitionNodeModel = new Backbone.Model({});
analysisDefinitionNodeModel.querySchemaModel = querySchemaModel;
analysisDefinitionNodeModel.queryGeometryModel = queryGeometryModel;
layerDefinitionModel = new Backbone.Model({});
layerDefinitionModel.getAnalysisDefinitionNodeModel = function () {
return analysisDefinitionNodeModel;
};
layerDefinitionModel.isDataFiltered = function () {};
layerDefinitionModel.canBeGeoreferenced = function () {};
widgetDefinitionsCollection = new Backbone.Collection([{
type: 'category',
source: 'a0',
column: 'city',
analysisDefinitionNodeModel: function () {
return new Backbone.Model({id: 'a0'});
}
}]);
var columnModel = new Backbone.Model({
columns: 3
});
var stackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['goToStep']);
columnsModel = new DataColumnsModel({}, {
layerDefinitionModel: layerDefinitionModel,
widgetDefinitionsCollection: widgetDefinitionsCollection,
tableStats: ts
});
var m0 = new Backbone.Model({
cid: 1,
type: 'histogram',
selected: false
});
m0.analysisDefinitionNodeModel = function () { return {id: 'a1'}; };
var m1 = new Backbone.Model({
cid: 2,
type: 'time-series',
selected: false
});
m1.analysisDefinitionNodeModel = function () { return {id: 'a2'}; };
var m2 = new Backbone.Model({
cid: 3,
type: 'time-series',
selected: false
});
m2.analysisDefinitionNodeModel = function () { return {id: 'a3'}; };
spyOn(columnsModel, 'getCollection').and.returnValue(new Backbone.Collection());
var userActions = {
saveWidgetOption: function () {
return Promise.resolve({id: 'w1'});
},
updateWidgetsOrder: function () {
return Promise.resolve({});
}
};
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
view = new DataContentView({
widgetDefinitionsCollection: widgetDefinitionsCollection,
stackLayoutModel: stackLayoutModel,
userActions: userActions,
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
columnsNumberModel: columnModel,
columnsModel: columnsModel,
infoboxModel: new Backbone.Model(),
overlayModel: overlayModel,
layerContentModel: layerContentModel,
layerDefinitionModel: layerDefinitionModel
});
view._columnsCollection.add(m0);
view._columnsCollection.add(m1);
view._columnsCollection.add(m2);
spyOn(queryGeometryModel, 'hasValueAsync').and.returnValue(Promise.resolve(true));
spyOn(layerDefinitionModel, 'isDataFiltered').and.returnValue(Promise.resolve(true));
spyOn(layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(true));
return view;
};
describe('initialize', function () {
beforeEach(function () {
spyOn(DataContentView.prototype, '_initViewState');
view = createViewFn();
});
it('should initialize properly', function () {
expect(DataContentView.prototype._initViewState).toHaveBeenCalled();
});
});
describe('.render', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view, '_renderStats');
spyOn(view, '_toggleOverlay');
view.render();
});
it('should render properly', function () {
expect(view._renderStats).toHaveBeenCalled();
expect(view._toggleOverlay).toHaveBeenCalled();
});
it('should render the "apply" button properly in advanced mode', function () {
view.$('.Options-bar .CDB-Toggle.js-input').click();
expect(view.$('.js-apply').hasClass('.CDB-Size-small')).toBeFalsy();
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
});
describe('when layer content model has errors', function () {
describe('.render', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view, '_isErrored').and.returnValue(true);
spyOn(view, '_showError');
spyOn(view, '_toggleOverlay');
});
it('should render properly', function () {
view.render();
expect(view._showError).toHaveBeenCalled();
expect(view._toggleOverlay).toHaveBeenCalled();
});
});
});
describe('when data is filtered', function () {
describe('.render', function () {
beforeEach(function () {
view = createViewFn();
});
it('should render properly', function () {
view._viewState.set('isDataFiltered', true);
view.render();
expect(view.el.innerHTML).toContain(
dataNoGeometryTemplate({
message: 'editor.layers.warnings.no-data.message',
action: 'editor.layers.warnings.no-data.action-message'
})
);
});
});
});
describe('when query geometry model has no value', function () {
describe('.render', function () {
beforeEach(function () {
view = createViewFn();
});
it('should render properly', function () {
view._viewState.set('hasGeom', false);
view.render();
expect(view.el.innerHTML).toContain(
dataNoGeometryTemplate({
message: 'editor.data.no-geometry-data.message',
action: 'editor.data.no-geometry-data.action-message'
})
);
});
});
});
describe('._initModels', function () {
beforeEach(function () {
view = createViewFn();
});
it('should create _columnsCollection', function () {
view._columnsCollection = undefined;
expect(view._columnsCollection).not.toBeDefined();
view._initModels();
expect(view._columnsCollection).toBeDefined();
});
it('should create model', function () {
view.model = undefined;
expect(view.model).not.toBeDefined();
view._initModels();
expect(view.model).toBeDefined();
});
});
describe('._initViewState', function () {
beforeEach(function () {
view = createViewFn();
});
it('should initialize _viewState model properly', function () {
spyOn(view, '_setViewState');
view._initViewState();
expect(view._viewState.get('hasGeom')).toEqual(true);
expect(view._viewState.get('isDataFiltered')).toEqual(false);
expect(view._viewState.get('canBeGeoreferenced')).toEqual(false);
expect(view._setViewState).toHaveBeenCalled();
});
});
describe('._initBinds', function () {
it('should call _setViewState when layerContentModel:state changes', function () {
spyOn(DataContentView.prototype, '_setViewState');
view = createViewFn();
layerContentModel.set({ state: 'fetched' });
expect(DataContentView.prototype._setViewState).toHaveBeenCalled();
});
it('should call ._handleStats when _columnsModel:render changes', function () {
spyOn(DataContentView.prototype, '_handleStats');
view = createViewFn();
view._columnsModel.set({ render: true });
expect(DataContentView.prototype._handleStats).toHaveBeenCalled();
});
it('should call ._handleWidget when _columnsCollection:selected changes', function () {
spyOn(DataContentView.prototype, '_handleWidget');
view = createViewFn();
view._columnsCollection.at(0).set({ selected: true });
expect(DataContentView.prototype._handleWidget).toHaveBeenCalled();
});
it('should call .render when _overlayModel:visible changes', function () {
spyOn(DataContentView.prototype, '_toggleOverlay');
view = createViewFn();
view._overlayModel.set({ visible: true });
expect(DataContentView.prototype._toggleOverlay).toHaveBeenCalled();
});
it('should call render when _viewState changes', function () {
spyOn(DataContentView.prototype, 'render');
view = createViewFn();
view._viewState.set({ isDataFiltered: true });
expect(DataContentView.prototype.render).toHaveBeenCalled();
});
});
describe('._isErrored', function () {
it('should return layerContentModel isErrored', function () {
spyOn(view._layerContentModel, 'isErrored').and.returnValues(false, true);
expect(view._isErrored()).toBe(false);
expect(view._isErrored()).toBe(true);
});
});
describe('._columnsReady', function () {
beforeEach(function () {
view = createViewFn();
});
it('should return true if _columnsModel:render is true', function () {
view._columnsModel.set({ render: false }, { silent: true });
expect(view._columnsReady()).toBe(false);
view._columnsModel.set({ render: true }, { silent: true });
expect(view._columnsReady()).toBe(true);
});
});
describe('._toggleOverlay', function () {
beforeEach(function () {
view = createViewFn();
});
it('should toggle overlay', function () {
expect(view.$el.hasClass('is-disabled')).toBe(false);
view._overlayModel.set('visible', true, { silent: true });
view._toggleOverlay();
expect(view.$el.hasClass('is-disabled')).toBe(true);
});
});
describe('._handleWidget', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view._userActions, 'saveWidgetOption')
.and.returnValue(Promise.resolve(view._columnsCollection.at(1)));
});
it('should call add a new widget if the model is selected', function (done) {
spyOn(view, '_addWidget');
view.render();
view._columnsCollection.at(1).set({ selected: true });
view._handleWidget(view._columnsCollection.at(1));
setTimeout(function () {
expect(view._addWidget).toHaveBeenCalledWith(view._columnsCollection.at(1));
done();
}, 0);
});
it('should destroy the widget if the model is selected', function (done) {
spyOn(view, '_destroyWidget');
view.render();
view._handleWidget(view._columnsCollection.at(1));
setTimeout(function () {
expect(view._destroyWidget).toHaveBeenCalledWith(view._columnsCollection.at(1));
done();
}, 0);
});
});
describe('._addWidget', function () {
beforeEach(function () {
view = createViewFn();
spyOn(view._userActions, 'saveWidgetOption')
.and
.returnValue(Promise.resolve(view._columnsCollection.at(0)));
});
it('should set the widget if the model exists', function (done) {
var existingModel = widgetDefinitionsCollection.at(0);
var model = view._columnsCollection.at(0);
spyOn(view._columnsModel, 'findWidget').and.returnValue(existingModel);
spyOn(model, 'set').and.callThrough();
view.render();
view._addWidget(model);
setTimeout(function () {
expect(model.set).toHaveBeenCalled();
expect(model.get('widget')).toEqual(existingModel);
done();
}, 0);
});
it('should call saveWidget if the model does not exist', function (done) {
var newModel = new Backbone.Model();
spyOn(view, '_saveWidget');
view.render();
view._columnsModel.set({ render: true });
view._saveWidget(newModel);
setTimeout(function () {
expect(view._saveWidget).toHaveBeenCalledWith(newModel);
done();
}, 0);
});
});
describe('._saveWidget', function () {
var newModel;
beforeEach(function () {
view = createViewFn();
newModel = new Backbone.Model({});
});
it('should call saveWidgetOption', function (done) {
spyOn(view._userActions, 'saveWidgetOption')
.and
.returnValue(Promise.resolve(view._columnsCollection.at(0)));
spyOn(newModel, 'set');
view.render();
view._saveWidget(newModel);
setTimeout(function () {
expect(view._userActions.saveWidgetOption).toHaveBeenCalledWith(newModel);
expect(newModel.set).toHaveBeenCalled();
done();
}, 0);
});
it('should call updateWidgetsOrder if the new widget model is created', function (done) {
var newModel = new Backbone.Model({});
spyOn(view._userActions, 'saveWidgetOption')
.and
.returnValue(Promise.resolve(newModel));
spyOn(view._userActions, 'updateWidgetsOrder');
view.render();
view._saveWidget(newModel);
setTimeout(function () {
expect(view._userActions.updateWidgetsOrder).toHaveBeenCalled();
done();
}, 0);
});
it('should render again if both the creation and the update work', function (done) {
spyOn(view._userActions, 'saveWidgetOption')
.and
.returnValue(Promise.resolve(newModel));
spyOn(view._userActions, 'updateWidgetsOrder')
.and
.returnValue(Promise.resolve({}));
spyOn(view, 'render');
view.render();
view._saveWidget(newModel);
setTimeout(function () {
expect(view.render).toHaveBeenCalledTimes(2);
done();
}, 0);
});
it('should manage time-series properly', function (done) {
spyOn(view._userActions, 'saveWidgetOption')
.and
.returnValue(Promise.resolve(view._columnsCollection.at(2)));
view.render();
spyOn(view, '_renderStats').and.callThrough();
spyOn(view, '_normalizeTimeSeriesColumn').and.callThrough();
view._columnsModel.set({ render: true });
view._columnsCollection.at(2).set({ selected: true });
setTimeout(function () {
expect(view._renderStats).toHaveBeenCalled();
expect(view._normalizeTimeSeriesColumn).toHaveBeenCalled();
expect(view._columnsCollection.at(1).get('selected')).toBe(false);
expect(view._columnsCollection.at(1).get('widget')).toBeFalsy();
expect(view._columnsCollection.at(2).get('widget')).toBeTruthy();
done();
}, 0);
});
});
describe('._destroyWidget', function () {
beforeEach(function () {
view = createViewFn();
});
it('should call the destroy function if the model exists', function () {
var widgetModel = widgetDefinitionsCollection.at(0);
var model = new Backbone.Model({
widget: widgetModel
});
spyOn(widgetModel, 'destroy');
view.render();
view._destroyWidget(model);
expect(widgetModel.destroy).toHaveBeenCalled();
});
});
describe('._setViewState', function () {
beforeEach(function () {
view = createViewFn();
view._viewState.set({
hasGeom: false,
canBeGeoreferenced: false,
isDataFiltered: false
});
});
it('should be able to update the view state of hasGeom', function (done) {
setTimeout(function () {
expect(view._viewState.get('hasGeom')).toEqual(true);
done();
}, 0);
});
});
});
@@ -0,0 +1,523 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var LayerContentModel = require('builder/data/layer-content-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var SQLModel = require('builder/editor/layers/layer-content-views/data/data-sql-model');
var UserModel = require('builder/data/user-model');
var VisDefinitionModel = require('builder/data/vis-definition-model');
var Notifier = require('builder/components/notifier/notifier.js');
var DataView = require('builder/editor/layers/layer-content-views/data/data-view');
var SQLNotifications = require('builder/sql-notifications.js');
var UserNotifications = require('builder/data/user-notifications');
var ONBOARDING_KEY = 'layer-data-onboarding';
describe('editor/layers/layers-content-view/data/data-view', function () {
var canBeGeoreferencedPromise;
var createViewFn = function (options) {
options = options || {};
this.sqlModel = new SQLModel({
content: 'SELECT * FROM table'
});
this.configModel = new ConfigModel({
sql_api_template: 'http://{user}.localhost.lan:8080',
user_name: 'pepito',
api_key: 'hello-apikey'
});
this.node = new Backbone.Model({
type: 'source'
});
this.node.getDefaultQuery = function () {
return 'SELECT * FROM table';
};
this.node.querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM table',
status: 'fetched'
}, {
configModel: this.configModel
});
this.node.queryGeometryModel = new QueryGeometryModel({
query: 'SELECT * FROM table',
status: 'fetched',
simple_geom: 'point'
}, {
configModel: this.configModel
});
this.stackLayoutModel = jasmine.createSpyObj('stackLayoutModel', ['goToStep']);
this.editorModel = new Backbone.Model();
this.editorModel.isEditing = function () { return false; };
this.editorModel.isDisabled = function () { return false; };
this.userActions = jasmine.createSpyObj('userActions', ['saveAnalysisSourceQuery', 'saveLayer']);
this.visDefinitionModel = new VisDefinitionModel({
name: 'Foo Map',
privacy: 'PUBLIC',
updated_at: '2016-06-21T15:30:06+00:00',
type: 'derived'
}, {
configModel: this.configModel
});
if (!options.canBeGeoreferencedPromise) {
this.layerDefinitionModel = new Backbone.Model({
user_name: 'pepito'
});
this.layerDefinitionModel.isDataFiltered = function () {
return Promise.resolve(true);
};
this.layerDefinitionModel.getAnalysisDefinitionNodeModel = function () {
return this.node;
}.bind(this);
this.layerDefinitionModel.getTableName = function () {
return 'table';
};
this.layerDefinitionModel.canBeGeoreferenced = function () {};
this.layerDefinitionModel.toggleVisible = function () { };
this.userModel = new UserModel({
username: 'pepe'
}, {
configModel: this.configModel
});
this.layerDefinitionModel.sqlModel = this.sqlModel;
canBeGeoreferencedPromise = spyOn(this.layerDefinitionModel, 'canBeGeoreferenced');
canBeGeoreferencedPromise.and.returnValue(Promise.resolve(true));
}
Notifier.init({
editorModel: this.editorModel,
visDefinitionModel: this.visDefinitionModel
});
this.onboardingNotification = new UserNotifications({}, {
key: 'builder',
configModel: this.configModel
});
var querySchemaModel = new Backbone.Model();
querySchemaModel.hasRepeatedErrors = function () { return false; };
var queryGeometryModel = new Backbone.Model();
queryGeometryModel.hasRepeatedErrors = function () { return false; };
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
this.layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
return new DataView({
layerDefinitionModel: this.layerDefinitionModel,
stackLayoutModel: this.stackLayoutModel,
widgetDefinitionsCollection: new Backbone.Collection(),
editorModel: this.editorModel,
userModel: this.userModel,
userActions: this.userActions,
configModel: this.configModel,
onboardings: {},
onboardingNotification: this.onboardingNotification,
layerContentModel: this.layerContentModel
});
};
beforeEach(function () {
jasmine.Ajax.install();
spyOn(SQLNotifications, 'showErrorNotification');
spyOn(SQLNotifications, 'removeNotification');
spyOn(SQLNotifications, '_addOrUpdateNotification');
this.view = createViewFn.call(this);
spyOn(this.view._SQL, 'execute').and.callFake(function (query, vars, params) {
params.success();
});
spyOn(this.view._onboardingLauncher, 'launch');
this.view._widgetDefinitionsCollection.isThereTimeSeries = function () {
return false;
};
spyOn(this.view, 'clean');
this.view.render();
});
afterEach(function () {
Notifier.off();
jasmine.Ajax.uninstall();
});
describe('.render', function () {
it('should render properly', function () {
expect(_.size(this.view._subviews)).toBe(1);
});
it('render should call _launchOnboarding', function () {
spyOn(this.view, '_launchOnboarding');
this.view.render();
expect(this.view._launchOnboarding).toHaveBeenCalled();
});
it('render should call _initViews', function () {
spyOn(this.view, '_initViews');
this.view.render();
expect(this.view._initViews).toHaveBeenCalled();
});
it('should not have leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
it('should create internal codemirror model', function () {
expect(this.view._codemirrorModel).toBeDefined();
});
it('should create _onboardingNotification', function () {
expect(this.view._onboardingNotification).not.toBe(null);
});
describe('._initBinds', function () {
it('should set codemirror value when query schema model changes', function () {
spyOn(this.view._codemirrorModel, 'set');
this.node.querySchemaModel.set('query_errors', ['Syntax error']);
expect(this.view._codemirrorModel.set).toHaveBeenCalled();
});
it('should update codemirror if query schema model changes', function () {
spyOn(this.view._codemirrorModel, 'set');
this.view._querySchemaModel.set({query: 'SELECT * FROM table LIMIT 10'});
expect(this.view._codemirrorModel.set).toHaveBeenCalled();
});
it('should set codemirror value when undo or redo is applied', function () {
this.sqlModel.set('content', 'SELECT foo FROM table');
spyOn(this.view._codemirrorModel, 'set');
this.sqlModel.undo();
expect(this.view._codemirrorModel.set).toHaveBeenCalled();
this.view._codemirrorModel.set.calls.reset();
this.sqlModel.redo();
expect(this.view._codemirrorModel.set).toHaveBeenCalled();
});
it('should sync the query status with the apply button status', function () {
this.view._querySchemaModel.set('status', 'fetching');
expect(this.view._applyButtonStatusModel.get('loading')).toBe(true);
this.view._querySchemaModel.set('status', 'fetched');
expect(this.view._applyButtonStatusModel.get('loading')).toBe(false);
this.view._querySchemaModel.set('status', 'unavailable');
expect(this.view._applyButtonStatusModel.get('loading')).toBe(false);
this.view._querySchemaModel.set('status', 'unfetched');
expect(this.view._applyButtonStatusModel.get('loading')).toBe(false);
});
it('should call ._onChangeEdition when _editorModel:edition changes', function () {
spyOn(this.view, '_onChangeEdition');
this.view._initBinds();
this.view._editorModel.trigger('change:edition');
expect(this.view._onChangeEdition).toHaveBeenCalled();
});
it('should call ._onChangeDisabled when _editorModel:disabled changes', function () {
spyOn(this.view, '_onChangeDisabled');
this.view._initBinds();
this.view._editorModel.trigger('change:disabled');
expect(this.view._onChangeDisabled).toHaveBeenCalled();
});
it('should call ._onTogglerChanged when _togglerModel:active changes', function () {
spyOn(this.view, '_onTogglerChanged');
this.view._initBinds();
this.view._togglerModel.trigger('change:active');
expect(this.view._onTogglerChanged).toHaveBeenCalled();
});
});
describe('._initModels', function () {
it('should create _infoboxModel', function () {
this.view._infoboxModel = undefined;
expect(this.view._infoboxModel).not.toBeDefined();
this.view._initModels();
expect(this.view._infoboxModel).toBeDefined();
});
it('should change the _infoboxModel state to georeference if can be georeferenced', function (done) {
spyOn(this.view, '_isLayerHidden').and.returnValue(false);
this.view = createViewFn.call(this);
setTimeout(function () {
expect(this.view._infoboxModel.get('state')).toEqual('georeference');
done();
}.bind(this), 0);
});
it('should change the _infoboxModel state to empty if it can not be georeferenced', function (done) {
spyOn(this.view, '_isLayerHidden').and.returnValue(false);
canBeGeoreferencedPromise.and.returnValue(Promise.resolve(false));
this.view = createViewFn.call(this, { canBeGeoreferencedPromise: true });
setTimeout(function () {
expect(this.view._infoboxModel.get('state')).not.toEqual('georeference');
done();
}.bind(this), 0);
});
it('should change the _infoboxModel to georeference if the layer is not hidden', function (done) {
spyOn(this.view, '_isLayerHidden').and.returnValue(false);
this.view = createViewFn.call(this);
setTimeout(function () {
expect(this.view._infoboxModel.get('state')).toEqual('georeference');
done();
}.bind(this), 0);
});
it('should not the _infoboxModel state to georeference if the layer is hidden', function (done) {
spyOn(this.view, '_isLayerHidden').and.returnValue(true);
canBeGeoreferencedPromise.and.returnValue(Promise.resolve(false));
this.view = createViewFn.call(this, { canBeGeoreferencedPromise: true });
setTimeout(function () {
expect(this.view._infoboxModel.get('state')).not.toEqual('georeference');
done();
}.bind(this), 0);
});
it('should create _overlayModel', function () {
this.view._overlayModel = undefined;
expect(this.view._overlayModel).not.toBeDefined();
this.view._initModels();
expect(this.view._overlayModel).toBeDefined();
});
it('should create _applyButtonStatusModel', function () {
this.view._applyButtonStatusModel = undefined;
expect(this.view._applyButtonStatusModel).not.toBeDefined();
this.view._initModels();
expect(this.view._applyButtonStatusModel).toBeDefined();
});
it('should create _togglerModel', function () {
this.view._togglerModel = undefined;
expect(this.view._togglerModel).not.toBeDefined();
this.view._initModels();
expect(this.view._togglerModel).toBeDefined();
});
});
it('should apply default query and refresh map and dataset view when query alters data', function () {
jasmine.Ajax.stubRequest(new RegExp('^http(s)?.*'))
.andReturn({ status: 200 });
var originalQuery = 'SELECT * FROM table';
spyOn(this.editorModel, 'isEditing').and.returnValue(true);
spyOn(this.node.querySchemaModel, 'resetDueToAlteredData');
this.view._codemirrorModel.set('content', 'DELETE FROM table WHERE cartodb_id=2');
this.view._parseSQL();
expect(this.view._codemirrorModel.get('content')).toBe(originalQuery);
expect(this.userActions.saveAnalysisSourceQuery).toHaveBeenCalled();
expect(this.node.querySchemaModel.get('query_errors').length).toBe(0);
expect(this.node.querySchemaModel.get('query')).toBe(originalQuery);
expect(this.node.querySchemaModel.resetDueToAlteredData).toHaveBeenCalled();
});
it('should apply error from the SQL api', function () {
jasmine.Ajax.stubRequest(new RegExp('^http(s)?.*'))
.andReturn({status: 429, responseText: '{"error":["You are over platform\'s limits. Please contact us to know more details"]}'});
spyOn(this.editorModel, 'isEditing').and.returnValue(true);
spyOn(this.node.querySchemaModel, 'resetDueToAlteredData');
spyOn(this.node.querySchemaModel, 'canFetch').and.returnValue(true);
spyOn(this.view, '_forceErrors');
this.view._codemirrorModel.set('content', 'SELECT *, pg_sleep(1) FROM table');
this.view._parseSQL();
expect(this.userActions.saveAnalysisSourceQuery).not.toHaveBeenCalled();
expect(this.node.querySchemaModel.get('query_errors').length).toBe(1);
expect(this.view._forceErrors).toHaveBeenCalled();
});
it('should fetch geometry when a new query is applied', function () {
this.view._codemirrorModel.set('content', 'SELECT * FROM table LIMIT 10');
spyOn(this.node.queryGeometryModel, 'fetch');
spyOn(this.node.querySchemaModel, 'fetch');
this.view._parseSQL();
expect(this.node.queryGeometryModel.hasChanged('simple_geom')).toBeFalsy();
expect(this.node.queryGeometryModel.get('query')).toBe('SELECT * FROM table LIMIT 10');
expect(this.node.queryGeometryModel.fetch).toHaveBeenCalled();
});
it('should not let run the same query that is applied', function () {
var originalQuery = 'SELECT * FROM table';
spyOn(this.node.querySchemaModel, 'fetch');
this.node.querySchemaModel.set('query', originalQuery);
this.view._codemirrorModel.set('content', originalQuery);
this.view._parseSQL();
expect(this.node.querySchemaModel.fetch).not.toHaveBeenCalled();
this.view._codemirrorModel.set('content', originalQuery.toUpperCase());
this.view._parseSQL();
expect(this.node.querySchemaModel.fetch).not.toHaveBeenCalled();
});
it('should not apply an empty query', function () {
spyOn(this.node.querySchemaModel, 'fetch');
this.view._codemirrorModel.set('content', '');
this.view._parseSQL();
expect(this.node.querySchemaModel.fetch).not.toHaveBeenCalled();
});
it('should not set a query with a ; character at the end', function () {
spyOn(this.node.querySchemaModel, 'fetch');
this.view._codemirrorModel.set('content', 'select * from whatever;');
this.view._parseSQL();
expect(this.view._codemirrorModel.get('content')).toBe('select * from whatever');
expect(this.node.querySchemaModel.fetch).toHaveBeenCalled();
this.view._codemirrorModel.set('content', 'select * from whatever; hello');
this.view._parseSQL();
expect(this.view._codemirrorModel.get('content')).toBe('select * from whatever; hello');
});
it('should not throw notification if same query that is applied', function () {
SQLNotifications._addOrUpdateNotification.calls.reset();
spyOn(this.node.querySchemaModel, 'fetch');
this.view._sqlModel.set('content', 'SELECT * FROM table');
this.view._codemirrorModel.set('content', 'SELECT * + FROM table');
this.view._parseSQL();
expect(this.node.querySchemaModel.fetch).toHaveBeenCalled();
this.node.querySchemaModel.set('query', 'SELECT * FROM table');
this.node.querySchemaModel.set('query_errors', ['Syntax error']);
this.view._codemirrorModel.set('content', 'SELECT * FROM table');
this.node.querySchemaModel.set('query_errors', []);
this.view._parseSQL();
expect(SQLNotifications.showErrorNotification).toHaveBeenCalledTimes(1);
expect(SQLNotifications.removeNotification).toHaveBeenCalledTimes(1);
expect(SQLNotifications._addOrUpdateNotification).toHaveBeenCalledTimes(1);
});
describe('_launchOnboarding', function () {
it('should do nothing if onboarding was skipped', function () {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, true);
this.view._onboardingLauncher.launch.calls.reset();
this.view._launchOnboarding();
expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
});
it('should do nothing if we are on editing mode', function () {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
this.view._editorModel.isEditing = function () { return true; };
this.view._onboardingLauncher.launch.calls.reset();
this.view._launchOnboarding();
expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
});
it('should launch the onboarding', function () {
this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
this.view._editorModel.isEditing = function () { return false; };
this.view._onboardingLauncher.launch.calls.reset();
this.view._launchOnboarding();
expect(this.view._onboardingLauncher.launch).toHaveBeenCalled();
});
});
describe('._infoboxState', function () {
beforeEach(function () {
this.view._infoboxModel.set('state', '');
this.view._overlayModel.set('visible', false);
this.view._editorModel.set('edition', false);
this.view._codemirrorModel.set('readonly', false);
spyOn(this.view, '_hasAnalysisApplied').and.returnValue(false);
spyOn(this.view, '_isLayerHidden').and.returnValue(false);
});
describe('if layer is editing and has analysis', function () {
it('should set infobox state', function () {
this.view._editorModel.set('edition', true);
this.view._hasAnalysisApplied.and.returnValue(true);
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('readonly');
expect(this.view._codemirrorModel.get('readonly')).toBe(true);
});
});
describe('if layer is hidden', function () {
it('should set infobox state', function () {
this.view._isLayerHidden.and.returnValue(true);
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('layer-hidden');
expect(this.view._overlayModel.get('visible')).toBe(true);
});
});
it('should unset infobox state', function () {
this.view._infoboxState();
expect(this.view._infoboxModel.get('state')).toBe('');
expect(this.view._overlayModel.get('visible')).toBe(false);
});
});
describe('._showHiddenLayer', function () {
it('should show layer if is visible', function () {
spyOn(this.view._layerDefinitionModel, 'toggleVisible');
this.view._showHiddenLayer();
expect(this.view._layerDefinitionModel.toggleVisible).toHaveBeenCalled();
expect(this.view._userActions.saveLayer).toHaveBeenCalledWith(this.view._layerDefinitionModel, { shouldPreserveAutoStyle: true });
});
});
describe('._isLayerHidden', function () {
it('should return true if layer is not visible', function () {
this.view._layerDefinitionModel.set('visible', false);
expect(this.view._isLayerHidden()).toBe(true);
});
});
});
@@ -0,0 +1,181 @@
var Backbone = require('backbone');
var _ = require('underscore');
var WidgetOptionModel = require('builder/components/modals/add-widgets/widget-option-model');
var StatView = require('builder/editor/layers/layer-content-views/data/stat-view');
var TableStats = require('builder/components/modals/add-widgets/tablestats');
var Router = require('builder/routes/router');
describe('editor/layers/layers-content-view/data/stat-view', function () {
var nullValue = 0.0059;
var topCatValue = 0.8534;
var view;
var graph;
function createView (withBooleanColumn, withProperBoolean) {
var layerDefinitionModel = new Backbone.Model();
var analysisDefinitionNodeModel = new Backbone.Model({
id: 'a0'
});
var tuples = [{
columnModel: new Backbone.Model(),
analysisDefinitionNodeModel: analysisDefinitionNodeModel,
layerDefinitionModel: layerDefinitionModel
}];
var statModel = new WidgetOptionModel({
type: 'category',
column: withBooleanColumn ? 'boolean' : 'number',
title: 'location of wadus',
name: 'location',
layer_index: 0,
table: 'marias',
selected: false,
tuples: tuples,
widget: new Backbone.Model({ id: 'widget-1' })
});
view = new StatView({
statModel: statModel,
isVisible: true
});
spyOn(view, 'clean');
graph = {
stats: {
freqs: true
},
getNullsPercentage: function () {
return nullValue;
},
getPercentageInTopCategories: function () {
return topCatValue;
},
getCategory: function () {
return 'foo';
},
getTrues: function () {
return withProperBoolean ? 0.45635 : undefined;
}
};
spyOn(Router, 'goToWidget');
spyOn(TableStats.prototype, 'graphFor').and.callFake(function () {
view.model.set({graph: graph});
});
view.render();
view.$el.appendTo(document.body);
}
describe('.render', function () {
beforeEach(function () {
createView();
});
it('should render properly', function () {
expect(view.$('.js-checkbox').length).toBe(1);
expect(view.$('.js-stat').length).toBe(1);
expect(view.$('.js-title').text()).toBe('location of wadus');
expect(view.$('.StatsList-tag').text()).toBe('number');
expect(_.size(view._subviews)).toBe(0);
});
describe('when _statModel is selected', function () {
beforeEach(function () {
view._statModel.set('selected', true);
});
it('should render correctly', function () {
expect(view.$('.js-help').length).toBe(1);
expect(_.size(view._subviews)).toBe(1); // [tooltip]
});
});
});
it('should select/unselect the widget properly', function () {
createView();
view.$('.js-checkbox').trigger('click');
expect(view._statModel.get('selected')).toBe(true);
expect(view.$('.js-style').hasClass('is-hidden')).toBe(false);
view.$('.js-checkbox').trigger('click');
expect(view._statModel.get('selected')).toBe(false);
expect(view.$('.js-style').length).toBe(0);
});
it('should be selected when widget is added', function () {
createView();
view._statModel.set({selected: true});
expect(view.$('.js-checkbox').prop('checked')).toBe(true);
});
it('should go to style widget when clicked on style link', function () {
createView();
view.$('.js-checkbox').trigger('click');
expect(view.$('.js-style').hasClass('is-hidden')).toBe(false);
view.$('.js-style').trigger('click');
expect(Router.goToWidget).toHaveBeenCalledWith('widget-1');
});
it('should not have any leaks', function () {
createView();
expect(view).toHaveNoLeaks();
});
describe('_showCategory', function () {
it('non-zero null data should be rounded up to two decimal positions.', function () {
// Arrange
createView();
// Act
view._showCategory(graph);
// Assert
expect(view.$('.js-null')[0].textContent.trim()).toEqual('0.59editor.data.stats.null');
expect(view.$('.js-percent')[0].textContent.trim()).toEqual('85.3editor.data.stats.top-cat');
});
it('boolean column proper data should be rounded up to two decimal positions', function () {
// Arrange
createView(true, true);
// Act
view._showCategory(graph);
// Arrange
expect(view.$('.js-percent')[0].textContent.trim()).toEqual('45.6editor.data.stats.trues');
});
it('boolean column with no proper data should not be rendered', function () {
// Arrange
createView(true, false);
// Act
view._showCategory(graph);
// Arrange
expect(view.$('.js-percent')[0].textContent.trim()).toBeFalsy();
});
});
describe('._isSelected', function () {
it('returns the value of _statModel selected', function () {
expect(view._isSelected()).toBe(false);
view._statModel.set('selected', true);
expect(view._isSelected()).toBe(true);
});
});
it('should have no leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
var parent = view.el.parentNode;
parent && parent.removeChild(view.el);
view.clean();
});
});
@@ -0,0 +1,159 @@
var _ = require('underscore');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var InfowindowBaseView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-base-view');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var EditorModel = require('builder/data/editor-model');
var ScrollView = require('builder/components/scroll/scroll-view');
var TEMPLATES = [
{
value: '',
infowindowIcon: require('builder/editor/layers/layer-content-views/infowindow/infowindow-icons/infowindow-none.tpl'),
label: _t('editor.layers.infowindow.style.none'),
tooltip: _t('editor.layers.infowindow.tooltips.none')
}, {
value: 'infowindow_light',
infowindowIcon: require('builder/editor/layers/layer-content-views/infowindow/infowindow-icons/infowindow-light.tpl'),
label: _t('editor.layers.infowindow.style.infowindow_light'),
tooltip: _t('editor.layers.infowindow.tooltips.infowindow_light')
}
];
describe('editor/layers/layer-content-view/infowindow/infowindow-base-view', function () {
var view;
var layerDefinitionModel;
var editorModel;
var model;
var renderSpy;
var onChangeSpy;
var infoboxStateSpy;
var onChangeEditionSpy;
var onChangeDisabledSpy;
var onTogglerChangedSpy;
var toggleOverlaySpy;
var createViewFn = function (options) {
layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
},
infowindow: {},
tooltip: {}
}, {
parse: true,
configModel: {}
});
editorModel = new EditorModel({
disabled: false
});
model = new InfowindowDefinitionModel({
template: '',
template_name: ''
}, {
configModel: {}
});
model.hasTemplate = function () { return true; };
InfowindowBaseView.prototype._initTemplates = function () { return TEMPLATES; };
spyOn(ScrollView.prototype, 'render').and.returnValue(this);
view = new InfowindowBaseView({
model: model,
editorModel: editorModel,
layerDefinitionModel: layerDefinitionModel,
userActions: {},
querySchemaModel: {}
});
view._templates = TEMPLATES;
return view;
};
beforeEach(function () {
renderSpy = spyOn(InfowindowBaseView.prototype, 'render');
onChangeSpy = spyOn(InfowindowBaseView.prototype, '_onChange');
infoboxStateSpy = spyOn(InfowindowBaseView.prototype, '_infoboxState');
onChangeEditionSpy = spyOn(InfowindowBaseView.prototype, '_onChangeEdition');
onChangeDisabledSpy = spyOn(InfowindowBaseView.prototype, '_onChangeDisabled');
onTogglerChangedSpy = spyOn(InfowindowBaseView.prototype, '_onTogglerChanged');
toggleOverlaySpy = spyOn(InfowindowBaseView.prototype, '_toggleOverlay');
view = createViewFn();
});
describe('.render', function () {
it('should render properly', function () {
renderSpy.and.callThrough();
spyOn(view, '_initViews');
view.render();
expect(view._initViews).toHaveBeenCalled();
expect(infoboxStateSpy).toHaveBeenCalled();
expect(toggleOverlaySpy).toHaveBeenCalled();
});
it('should have no leaks', function () {
renderSpy.and.callThrough();
view.render();
expect(view).toHaveNoLeaks();
});
});
describe('._initBinds', function () {
it('should listen to model change', function () {
view.model.set('wadus', true);
expect(onChangeSpy).toHaveBeenCalled();
});
it('should listen to layerDefinitionModel change visible', function () {
layerDefinitionModel.set('visible', false);
expect(infoboxStateSpy).toHaveBeenCalled();
});
it('should listen to editorModel change edition', function () {
view._editorModel.set('edition', true);
expect(onChangeEditionSpy).toHaveBeenCalled();
});
it('should listen to editorModel change disabled', function () {
view._editorModel.set('disabled', true);
expect(onChangeDisabledSpy).toHaveBeenCalled();
});
it('should listen to togglerModel change active', function () {
view._togglerModel.set('active', true);
expect(onTogglerChangedSpy).toHaveBeenCalled();
});
it('should listen to overlayModel change visible', function () {
view._overlayModel.set('visible', true);
expect(toggleOverlaySpy).toHaveBeenCalled();
});
});
describe('._initViews', function () {
it('should init views', function () {
view._initViews();
expect(_.size(view._subviews)).toBe(1); // ['PanelWithOptionsView']
});
});
});
@@ -0,0 +1,54 @@
var ConfigModel = require('builder/data/config-model');
var InfowindowModel = require('builder/data/infowindow-click-model');
describe('editor/layers/layer-content-view/infowindow/infowindow-click-model', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.model = new InfowindowModel({
fields: [{name: 'test', title: true, order: 0}]
}, {
configModel: this.configModel
});
});
it('should set template_name and template', function () {
expect(this.model.get('template_name')).toEqual('');
expect(this.model.get('template')).toEqual('');
this.model.setTemplate('infowindow_dark');
expect(this.model.get('template_name')).toEqual('infowindow_dark');
expect(this.model.get('template')).toContain('<div class="CDB-infowindow CDB-infowindow--dark js-infowindow" style="background:#2E3C43">');
});
it('should set default template', function () {
this.model.setTemplate('infowindow_dark');
expect(this.model.get('template_name')).toEqual('infowindow_dark');
this.model.setDefault();
expect(this.model.get('template_name')).toEqual('');
});
it('should empty fields if template is empty', function () {
expect(this.model.get('fields')).toEqual([{name: 'test', title: true, order: 0}]);
this.model.setTemplate('');
expect(this.model.get('fields')).toEqual([]);
});
it('should transform template with headerColor if template is infowindow_color', function () {
expect(this.model.get('template_name')).toEqual('');
expect(this.model.get('template')).toEqual('');
this.model.setTemplate('infowindow_color');
expect(this.model.get('template_name')).toEqual('infowindow_color');
expect(this.model.get('template')).toContain('div class="CDB-infowindow-header CDB-infowindow-headerBg CDB-infowindow-headerBg--light js-header" style="background: #35AAE5;"');
this.model.set('headerColor', {
color: {
fixed: '#FABADA',
opacity: 1
}
});
this.model.setTemplate('infowindow_color');
expect(this.model.get('template_name')).toEqual('infowindow_color');
expect(this.model.get('template')).toContain('div class="CDB-infowindow-header CDB-infowindow-headerBg CDB-infowindow-headerBg--light js-header" style="background: #FABADA;"');
});
});
@@ -0,0 +1,176 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var InfowindowContentView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-content-view');
var QuerySchemaModel = require('builder/data/query-schema-model');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var EditorModel = require('builder/data/editor-model');
var ConfigModel = require('builder/data/config-model');
describe('editor/layers/layer-content-view/infowindow/infowindow-content-view', function () {
var view;
var model;
var querySchemaModel;
var layerDefinitionModel;
var overlayModel;
var editorModel;
var renderItemsSpy;
var updateEditorSpy;
beforeEach(function () {
var configModel = new ConfigModel();
querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM table'
}, {
configModel: configModel
});
spyOn(querySchemaModel, 'fetch');
layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
},
infowindow: {},
tooltip: {}
}, {
parse: true,
configModel: configModel
});
model = new InfowindowDefinitionModel({
template: '',
template_name: ''
}, {
configModel: configModel
});
model.hasTemplate = function () { return true; };
overlayModel = new Backbone.Model({
visible: false
});
editorModel = new EditorModel({
disabled: false
});
renderItemsSpy = spyOn(InfowindowContentView.prototype, '_renderItems');
updateEditorSpy = spyOn(InfowindowContentView.prototype, '_updateEditor');
view = new InfowindowContentView({
querySchemaModel: querySchemaModel,
model: model,
layerDefinitionModel: layerDefinitionModel,
overlayModel: overlayModel,
editorModel: editorModel,
templates: [{
value: ''
}, {
value: 'infowindow_light'
}]
});
});
describe('render', function () {
it('should render properly', function () {
spyOn(view, '_initViews');
view.render();
expect(view._initViews).toHaveBeenCalled();
});
it('should have no leaks', function () {
view.render();
expect(view).toHaveNoLeaks();
});
});
describe('_initBinds', function () {
it('should listen to model change template_name', function () {
model.set('template_name', 'wadus');
expect(renderItemsSpy).toHaveBeenCalled();
});
it('should listen to model change template', function () {
model.set('template', 'wadus');
expect(updateEditorSpy).toHaveBeenCalled();
});
});
describe('_initViews', function () {
it('should init views', function () {
view._initViews();
expect(_.size(view._subviews)).toBe(1); // ['InfowindowSelectView']
expect(renderItemsSpy).toHaveBeenCalled();
});
});
describe('_renderItems', function () {
it('should render items', function () {
renderItemsSpy.and.callThrough();
view._renderItems();
expect(_.size(view._subviews)).toBe(1); // ['InfowindowItemsView']
});
});
describe('_initCollection', function () {
it('should init collection', function () {
view._initCollection();
expect(view._templatesCollection).toBeDefined();
expect(view._templatesCollection.size()).toBe(2);
});
});
describe('_updateEditor', function () {
beforeEach(function () {
updateEditorSpy.and.callThrough();
});
it('shouln\'t update editor', function () {
expect(editorModel.get('disabled')).toBe(false);
view._updateEditor();
expect(editorModel.get('disabled')).toBe(false);
});
describe('model hasn\'t template', function () {
it('should update editor', function () {
model.hasTemplate = function () { return false; };
expect(editorModel.get('disabled')).toBe(false);
view._updateEditor();
expect(editorModel.get('disabled')).toBe(true);
});
});
});
describe('_checkValidTemplate', function () {
it('should check valid template', function () {
expect(view._checkValidTemplate()).toBe(false);
});
describe('with valid template', function () {
it('should check valid template', function () {
view.model.set('template_name', 'infowindow_light');
expect(view._checkValidTemplate()).toBe(true);
});
});
});
});
@@ -0,0 +1,60 @@
var ConfigModel = require('builder/data/config-model');
var InfowindowDescriptionView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-description-view.js');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
describe('editor/layers/layer-content-view/infowindows/infowindow-description-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
model = new InfowindowDefinitionModel({
fields: [
{
name: 'description',
title: true,
position: 0
},
{
name: 'name',
title: true,
position: 1
}
]
}, {
configModel: this.configModel
});
view = new InfowindowDescriptionView({
model: model,
namesCount: 2
});
});
it('should render all selected', function () {
view.render();
expect(view.$('.js-textInfo').html().trim()).toEqual('All selected');
});
it('should render count selected', function () {
view.model.set('fields', [{ name: 'description', title: true, position: 0 }]);
view.render();
expect(view.$('.js-textInfo').html().trim()).toEqual('1 selected');
});
it('should render none selected', function () {
view.model.set('fields', []);
view.render();
expect(view.$('.js-textInfo').html().trim()).toEqual('None selected');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,68 @@
var $ = require('jquery');
var _ = require('underscore');
var ConfigModel = require('builder/data/config-model');
var InfowindowFieldView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-field-view');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
describe('editor/layers/layer-content-view/infowindows/infowindow-field-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
model = new InfowindowDefinitionModel({}, {
configModel: this.configModel
});
view = new InfowindowFieldView({
layerInfowindowModel: model,
field: { name: 'name1', title: true },
position: 0
});
});
describe('.render', function () {
beforeEach(function () {
view.render();
});
it('should render correctly', function () {
expect(_.size(view._subviews)).toBe(1); // [tooltip]
});
});
it('should toggle check', function () {
view.render();
model.addField('name1');
expect(!!$(view.$el.find('.js-checkbox')).attr('checked')).toEqual(true);
model.removeField('name1');
expect(!!$(view.$el.find('.js-checkbox')).attr('checked')).toEqual(false);
});
it('should toggle field on click', function () {
view.render();
model.addField('name1');
expect(model.containsField('name1')).toEqual(true);
view.toggle();
expect(model.containsField('name1')).toEqual(false);
});
it('should set empty alternative name after uncheck', function () {
view.render();
model.addField('name1');
model.setAlternativeName('name1', 'nombre');
expect(model.getAlternativeName('name1')).toEqual('nombre');
view.toggle();
expect(model.getAlternativeName('name1')).toEqual('');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,114 @@
var ConfigModel = require('builder/data/config-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var InfowindowFieldsView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-fields-view');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
require('jquery-ui');
describe('editor/layers/layer-content-view/infowindows/infowindow-fields-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM foobar',
status: 'fetched'
}, {
configModel: this.configModel
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
},
infowindow: {}
}, {
parse: true,
configModel: this.configModel
});
model = new InfowindowDefinitionModel({}, {
configModel: this.configModel
});
view = new InfowindowFieldsView({
querySchemaModel: this.querySchemaModel,
model: model,
layerDefinitionModel: this.layerDefinitionModel
});
});
it('should render fields', function () {
view._querySchemaModel.columnsCollection.reset([
{ name: 'name1', type: 'string', position: 0 },
{ name: 'name2', type: 'number', position: 1 },
{ name: 'name3', type: 'number', position: 2 }
]);
view.render();
expect(view.$el.find('li').length).toEqual(3);
});
it('should render description', function () {
view.render();
expect(view.$el.find('.js-textInfo').length).toEqual(1);
});
describe('sortable', function () {
beforeEach(function () {
view._querySchemaModel.columnsCollection.reset([
{ name: 'name1', type: 'string', position: 0 },
{ name: 'name2', type: 'number', position: 1 }
]);
view.model.addField('name1', 0);
view.model.addField('name2', 1);
});
it('should unselect/select all', function () {
view.render();
expect(view.model.attributes.fields.length).toEqual(2);
view._fieldsDescriptionView.trigger('toggle');
expect(view.model.attributes.fields.length).toEqual(0);
view._fieldsDescriptionView.trigger('toggle');
expect(view.model.attributes.fields.length).toEqual(2);
});
it('should be initialized when view is rendered', function () {
spyOn(view, '_initSortable').and.callThrough();
view.render();
expect(view._initSortable).toHaveBeenCalled();
expect(view.$('.js-fields').data('ui-sortable')).not.toBeUndefined();
});
it('should update the order of the models when sort has finished', function () {
view.render();
// Impossible to fake sortable behaviour so...
view.$('.js-field:eq(1)').insertBefore(view.$('.js-field:eq(0)'));
view._onSortableFinish();
// End of fake sortable
expect(view.model.getFieldPos('name2')).toBe(0);
expect(view.model.getFieldPos('name1')).toBe(1);
});
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
describe('._onSortableFinish', function () {
it('should call _storeSortedFields function', function () {
spyOn(view, '_storeSortedFields');
view._onSortableFinish();
expect(view._storeSortedFields).toHaveBeenCalled();
});
});
});
@@ -0,0 +1,57 @@
var InfowindowFormView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-form-view.js');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var ConfigModel = require('builder/data/config-model');
describe('editor/layers/layer-content-view/infowindows/infowindow-form-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
model = new InfowindowDefinitionModel({
template_name: '',
width: 400
}, {
configModel: this.configModel
});
view = new InfowindowFormView({
model: model
});
});
describe('render', function () {
it('should render properly', function () {
view.render();
expect(view.$('form').length).toBe(1);
});
});
it('should update fields depending on template_name', function () {
view.model.set('template_name', 'test1');
view.render();
expect(view.$('.Editor-formInner').length).toBe(1);
expect(view.$('.Editor-formInner label').text().trim()).toEqual('editor.layers.infowindow.style.size.label');
view.model.set('template_name', 'infowindow_color');
view.render();
expect(view.$('.Editor-formInner').length).toBe(2);
expect(view.$('.Editor-formInner:eq(1) label').text().trim()).toEqual('editor.layers.infowindow.style.header-color');
});
it('should update width', function () {
view.model.set('template_name', 'test1');
view.render();
expect(view.$('.Editor-formInner .js-input').val()).toBe('400');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,73 @@
var ConfigModel = require('builder/data/config-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var InfowindowItemsView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-items-view');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var _ = require('underscore');
describe('editor/layers/layer-content-view/infowindows/infowindow-items-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM foobar',
status: 'fetched'
}, {
configModel: this.configModel
});
spyOn(this.querySchemaModel, 'fetch').and.callThrough();
this.querySchemaModel.columnsCollection.reset([
{ name: 'name1', type: 'string', position: 0 },
{ name: 'name2', type: 'number', position: 1 },
{ name: 'name3', type: 'number', position: 2 }
]);
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
},
infowindow: {}
}, {
parse: true,
configModel: this.configModel
});
model = new InfowindowDefinitionModel({}, {
configModel: this.configModel
});
view = new InfowindowItemsView({
querySchemaModel: this.querySchemaModel,
model: model,
layerDefinitionModel: this.layerDefinitionModel,
hasValidTemplate: true
});
});
it('should not render items if has not valid template', function () {
view.render();
expect(_.size(view._subviews)).toBe(1);
view._hasValidTemplate = false;
view.render();
expect(_.size(view._subviews)).toBe(0);
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,72 @@
var InfowindowSelectView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-select-view.js');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var ConfigModel = require('builder/data/config-model');
var CarouselCollection = require('builder/components/custom-carousel/custom-carousel-collection');
var _ = require('underscore');
describe('editor/layers/layer-content-view/infowindows/infowindow-select-view', function () {
var view, model;
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
model = new InfowindowDefinitionModel({
template_name: 'test1'
}, {
configModel: this.configModel
});
var templates = [
{
value: '',
label: 'None'
}, {
value: 'test1',
label: 'Test1'
}
];
var collection = new CarouselCollection(
_.map(templates, function (template) {
return {
selected: model.get('template_name') === template.value,
val: template.value,
label: template.label,
template: function () {
return template.label;
}
};
}, this)
);
view = new InfowindowSelectView({
model: model,
templatesCollection: collection
});
});
it('should select template if there is match', function () {
view.render();
var s = view._templatesCollection.find(function (mdl) { return mdl.get('selected'); });
expect(s.get('val')).toEqual('test1');
expect(s.get('label')).toEqual('Test1');
});
it("should select 'None' if there is no match", function () {
view.model.set('template_name', 'none');
view.render();
var s = view._templatesCollection.find(function (mdl) { return mdl.get('selected'); });
expect(s.get('val')).toEqual('');
expect(s.get('label')).toEqual('None');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,354 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var InfowindowsView = require('builder/editor/layers/layer-content-views/infowindow/infowindows-view');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var InfowindowView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-click-view');
var TooltipView = require('builder/editor/layers/layer-content-views/infowindow/infowindow-hover-view');
var InfowindowDefinitionModel = require('builder/data/infowindow-definition-model');
var EditorModel = require('builder/data/editor-model');
var UserActions = require('builder/data/user-actions');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var AnalysisDefinitionsCollection = require('builder/data/analysis-definitions-collection');
var QueryRowsCollection = require('builder/data/query-rows-collection');
describe('editor/layers/layer-content-view/infowindow/infowindows-view', function () {
var view, configModel, layerDefinitionModel, querySchemaModel, layerContentModel;
var createViewFn = function (options) {
configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
spyOn(userModel, 'featureEnabled').and.returnValue(true);
var analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
var a0 = analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
table_name: 'foo'
});
spyOn(a0, 'isCustomQueryApplied').and.returnValue(false);
var layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
configModel: configModel,
userModel: userModel,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
mapId: 'm123',
stateDefinitionModel: {}
});
layerContentModel = new Backbone.Model({
state: 'initial'
});
var analysisDefinitionsCollection = new AnalysisDefinitionsCollection(null, {
configModel: configModel,
vizId: 'viz-123',
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
layerDefinitionsCollection: layerDefinitionsCollection
});
querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM foobar',
status: 'fetched'
}, {
configModel: configModel
});
querySchemaModel.columnsCollection.reset([
{
type: 'number',
name: 'cartodb_id'
}, {
type: 'number',
name: 'a_number'
}
]);
spyOn(querySchemaModel, 'fetch');
var queryGeometryModel = new QueryGeometryModel({
query: 'SELECT * FROM foobar',
status: 'fetched',
simple_geom: 'point'
}, {
configModel: configModel
});
spyOn(queryGeometryModel, 'fetch');
var queryRowsCollection = new QueryRowsCollection([], {
configModel: configModel,
querySchemaModel: querySchemaModel
});
spyOn(queryRowsCollection, 'fetch');
layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
},
infowindow: {
template_name: '',
template: ''
},
tooltip: {
template_name: '',
template: ''
}
}, {
parse: true,
configModel: configModel
});
spyOn(layerDefinitionModel, 'save');
spyOn(layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(a0);
var defaultOptions = {
configModel: configModel,
editorModel: new EditorModel(),
userActions: new UserActions({
userModel: userModel,
analysisDefinitionsCollection: analysisDefinitionsCollection,
analysisDefinitionNodesCollection: analysisDefinitionNodesCollection,
layerDefinitionsCollection: layerDefinitionsCollection,
widgetDefinitionsCollection: {}
}),
layerDefinitionModel: layerDefinitionModel,
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection,
layerContentModel: layerContentModel
};
view = new InfowindowsView(_.extend(defaultOptions, options));
return view;
};
describe('when data is filtered', function () {
describe('.render', function () {
it('should call _renderFilteredData', function () {
view = createViewFn();
spyOn(view, '_renderFilteredData');
view._viewState.set('isDataFiltered', true, { silent: true });
view.render();
expect(view._renderFilteredData).toHaveBeenCalled();
});
});
});
describe('when layer has infowindowModel', function () {
beforeEach(function () {
view = createViewFn();
view.render();
});
it('should render two tabs', function () {
expect(view._layerTabPaneView).toBeDefined();
expect(_.size(view._layerTabPaneView._subviews)).toBe(3); // 2 tabs, 1 pane
expect(view.$('.CDB-NavSubmenu .CDB-NavSubmenu-item').length).toBe(2);
expect(view.$('.CDB-NavSubmenu-item:eq(0)').text()).toContain('editor.layers.infowindow-menu-tab-pane-labels.click');
expect(view.$('.CDB-NavSubmenu-item:eq(0) .CDB-NavSubmenu-status').text()).toBe('editor.layers.infowindow.style.none');
expect(view.$('.CDB-NavSubmenu-item:eq(1)').text()).toContain('editor.layers.infowindow-menu-tab-pane-labels.hover');
expect(view.$('.CDB-NavSubmenu-item:eq(1) .CDB-NavSubmenu-status').text()).toContain('editor.layers.tooltip.style.none');
});
it('should change tab if infowindow (and tooltip) changes', function () {
expect(view.$('.CDB-NavSubmenu-item:eq(0) .CDB-NavSubmenu-status').text()).toBe('editor.layers.infowindow.style.none');
expect(view.$('.CDB-NavSubmenu-item:eq(1) .CDB-NavSubmenu-status').text()).toBe('editor.layers.tooltip.style.none');
view._layerInfowindowModel.set('template_name', 'infowindow_dark');
view.render();
expect(view.$('.CDB-NavSubmenu-item:eq(0) .CDB-NavSubmenu-status').text()).toBe('editor.layers.infowindow.style.infowindow_dark');
expect(view.$('.CDB-NavSubmenu-item:eq(1) .CDB-NavSubmenu-status').text()).toBe('editor.layers.tooltip.style.none');
// change tab and change template_name, '.js-menu .CDB-NavSubmenu-item.is-selected .js-NavSubmenu-status' is needed
view._layerTabPaneView.collection.at(1).set('selected', true);
view._layerTooltipModel.set('template_name', 'tooltip_dark');
view.render();
expect(view.$('.CDB-NavSubmenu-item:eq(0) .CDB-NavSubmenu-status').text()).toBe('editor.layers.infowindow.style.infowindow_dark');
expect(view.$('.CDB-NavSubmenu-item:eq(1) .CDB-NavSubmenu-status').text()).toBe('editor.layers.tooltip.style.tooltip_dark');
});
describe('infowindows tabs', function () {
var model;
beforeEach(function () {
model = new InfowindowDefinitionModel({}, {
configModel: configModel
});
});
it('infowindow should render properly', function () {
view = new InfowindowView({
layerDefinitionModel: layerDefinitionModel,
querySchemaModel: querySchemaModel,
userActions: {},
model: model,
editorModel: new EditorModel()
});
view.render();
expect(_.size(view._subviews)).toBe(1); // fields / codemirror view
});
it('tooltip should render properly', function () {
view = new TooltipView({
layerDefinitionModel: layerDefinitionModel,
querySchemaModel: querySchemaModel,
userActions: {},
model: model,
editorModel: new EditorModel()
});
view.render();
expect(_.size(view._subviews)).toBe(1); // fields / codemirror view
});
});
it('should render placeholder if style model type is aggregated', function () {
expect(view.$('.CDB-Size-huge').length).toBe(0);
spyOn(layerDefinitionModel.styleModel, 'isAggregatedType').and.returnValue(true);
view.render();
expect(view.$('.CDB-Size-huge').length).toBe(1);
expect(view.$('.CDB-Size-huge').text()).toBe('editor.layers.infowindow.placeholder-interactivity-text');
});
it('should render placeholder if style model has animated enabled', function () {
expect(view.$('.CDB-Size-huge').length).toBe(0);
spyOn(layerDefinitionModel.styleModel, 'isAnimation').and.returnValue(true);
view.render();
expect(view.$('.CDB-Size-huge').length).toBe(1);
expect(view.$('.CDB-Size-huge').text()).toBe('editor.layers.infowindow.placeholder-interactivity-text');
});
it('should render placeholder when has no columns', function () {
expect(view.$('.CDB-Size-huge').length).toBe(0);
querySchemaModel.columnsCollection.reset([
{
type: 'number',
name: 'cartodb_id'
}
]);
view.render();
expect(view.$('.CDB-Size-huge').length).toBe(1);
expect(view.$('.CDB-Size-huge').text()).toBe('editor.layers.infowindow.placeholder-columns-text');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
});
describe('when layer doesn\'t have infowindowModel (basemap, torque, ...)', function () {
beforeEach(function () {
view = createViewFn();
view._layerInfowindowModel = undefined;
view.render();
});
it('should render placeholder', function () {
expect(view._layerTabPaneView).not.toBeDefined();
expect(view.$('.CDB-Size-huge').length).toBe(1);
expect(view.$('.CDB-Size-huge').text()).toBe('editor.layers.infowindow.placeholder-interactivity-text');
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
});
describe('.initBinds', function () {
beforeEach(function () {
spyOn(InfowindowsView.prototype, '_changeStyle');
spyOn(InfowindowsView.prototype, '_updateSelectedChild');
spyOn(InfowindowsView.prototype, '_setViewValues');
spyOn(InfowindowsView.prototype, 'render').and.callThrough();
view = createViewFn();
view.render();
});
it('should call _changeStyle if _editorModel:edition changes', function () {
view._initBinds();
view._editorModel.trigger('change:edition');
expect(view._changeStyle).toHaveBeenCalled();
});
it('should call _updateSelectedChild if _layerInfowindowModel:template changes', function () {
view._initBinds();
view._layerInfowindowModel.trigger('change:template');
expect(view._updateSelectedChild).toHaveBeenCalled();
});
it('should call _updateSelectedChild if _layerTooltipModel:template changes', function () {
view._initBinds();
view._layerTooltipModel.trigger('change:template');
expect(view._updateSelectedChild).toHaveBeenCalled();
});
it('should call _setViewValues if layerContentModel:state changes', function () {
view._initBinds();
view._layerContentModel.trigger('change:state');
expect(view._setViewValues).toHaveBeenCalled();
});
it('should render if _querySchemaModel:status changes', function () {
view._initBinds();
view._querySchemaModel.trigger('change:status');
expect(view.render).toHaveBeenCalled();
});
});
describe('._renderFilteredData', function () {
it('should render the correct template', function () {
view._renderFilteredData();
expect(view.$el.html()).toContain('editor.layers.warnings.no-data.message');
expect(view.$el.html()).toContain('editor.layers.warnings.no-data.action-message');
});
});
describe('._setViewValues', function () {
it('should update _viewState', function (done) {
spyOn(view._layerDefinitionModel, 'isDataFiltered').and.returnValue(Promise.resolve(true));
view._viewState.set('isDataFiltered', false, { silent: true });
view._setViewValues();
setTimeout(function () {
expect(view._viewState.get('isDataFiltered')).toEqual(true);
done();
}, 0);
});
});
});
@@ -0,0 +1,52 @@
var _ = require('underscore');
var LegendColorTypes = require('builder/editor/layers/layer-content-views/legend/color/legend-color-types');
var LegendTypes = require('builder/editor/layers/layer-content-views/legend/legend-types');
var StyleConstants = require('builder/components/form-components/_constants/_style');
var StyleModel = require('builder/editor/style/style-definition-model');
describe('editor/layers/layer-content-view/legend/color/legend-color-types', function () {
function getLegendType (type) {
return _.find(LegendColorTypes, function (legendColorType) {
return legendColorType.value === type;
}, this);
}
var anStyleFillByBooleanAttribute = new StyleModel({
type: StyleConstants.Type.SIMPLE,
fill: {
color: {
attribute: 'a_boolean_field',
attribute_type: 'boolean',
range: ['#2bd900', '#d96b6b', '#e7e7e7'],
quantification: undefined
}
},
stroke: {
color: {
fixed: 'white'
}
}
});
describe('.category legend', function () {
beforeEach(function () {
this.legendType = getLegendType(LegendTypes.CATEGORY);
});
it('should be compatible with a boolean attribute in a color by value viz', function () {
expect(this.legendType.isStyleCompatible(anStyleFillByBooleanAttribute)).toBe(true);
});
});
describe('.choropleth legend', function () {
beforeEach(function () {
this.legendType = getLegendType(LegendTypes.CHOROPLETH);
});
it('should be incompatible with a boolean attribute in a color by value viz', function () {
expect(this.legendType.isStyleCompatible(anStyleFillByBooleanAttribute)).toBe(false);
});
});
});
@@ -0,0 +1,134 @@
var LegendColorHelper = require('builder/editor/layers/layer-content-views/legend/form/legend-color-helper');
describe('editor/layers/layer-content-views/legends/form/legend-color-helper', function () {
it('should return collection category items properly', function () {
var style = {
'attribute': 'line',
'attribute_type': 'string',
'range': [
'#555',
'#5B3F95',
'#1D6996',
'#129C63',
'#73AF48',
'#EDAD08',
'#E17C05',
'#C94034',
'#BA0040',
'#8E1966',
'#6F3072'
],
'domain': [
'Others',
'\'L1\'',
'\'L10\'',
'\'L12\'',
'\'L2\'',
'\'L3\'',
'\'L4\'',
'\'L5\'',
'\'L6\'',
'\'L7\'',
'\'L9\''
]
};
var categories = LegendColorHelper.getCategories(style);
expect(categories.length).toBe(11);
expect(categories[0].fill.color.fixed).toBe('#555');
expect(categories[0].title).toBe('Others');
expect(categories[3].fill.color.fixed).toBe('#129C63');
expect(categories[3].title).toBe('\'L12\'');
expect(categories[10].fill.color.fixed).toBe('#6F3072');
expect(categories[10].title).toBe('\'L9\'');
});
it('should return collection category items properly for choropleth style', function () {
var style = {
'attribute': 'line',
'attribute_type': 'number',
'range': [
'#555',
'#5B3F95',
'#1D6996',
'#129C63',
'#73AF48',
'#EDAD08',
'#E17C05',
'#C94034',
'#BA0040',
'#8E1966',
'#6F3072'
]
};
var categories = LegendColorHelper.getCategories(style);
expect(categories.length).toBe(11);
expect(categories[0].fill.color.fixed).toBe('#555');
expect(categories[0].title).toBe('');
expect(categories[3].fill.color.fixed).toBe('#129C63');
expect(categories[3].title).toBe('');
expect(categories[10].fill.color.fixed).toBe('#6F3072');
expect(categories[10].title).toBe('');
});
it('should return single category item properly', function () {
var style = {
'fixed': '#d4d438',
'opacity': 1
};
var categories = LegendColorHelper.getCategories(style);
expect(categories.length).toBe(1);
expect(categories[0].fill.color.fixed).toBe('#d4d438');
expect(categories[0].title).toBe('');
});
it('should return bubble properly', function () {
var style = {
'fixed': '#d4d438',
'opacity': 1
};
var bubble = LegendColorHelper.getBubbles(style);
expect(bubble.color.fixed).toBe('#d4d438');
});
describe('simpleColor', function () {
it('should return same output as input in color object provided', function () {
var color = {
fixed: '#fabada',
image: 'icon.svg'
};
var categories = LegendColorHelper.getCategories(color);
expect(categories.length).toBe(1);
expect(categories[0].fill.color.fixed).toEqual('#fabada');
expect(categories[0].fill.color.image).toEqual('icon.svg');
});
});
describe('collectionColor', function () {
it('', function () {
var color = {
attribute: 'name',
range: ['#fabada', '#c0ffee'],
domain: ['Pedro', 'Pablo'],
images: ['pedro.png', '']
};
var categories = LegendColorHelper.getCategories(color);
expect(categories.length).toBe(2);
expect(categories[0].fill.color.fixed).toEqual('#fabada');
expect(categories[0].fill.color.opacity).toBe(1);
expect(categories[0].fill.color.image).toEqual('pedro.png');
expect(categories[0].title).toEqual('Pedro');
expect(categories[1].fill.color.fixed).toEqual('#c0ffee');
expect(categories[1].fill.color.opacity).toBe(1);
expect(categories[1].fill.color.image).toEqual('');
expect(categories[1].title).toEqual('Pablo');
});
});
});
@@ -0,0 +1,30 @@
var LegendColors = require('builder/editor/layers/layer-content-views/legend/form/legend-color-range');
describe('editor/layers/layer-content-views/legends/form/legend-color-range', function () {
var COLORS = [
'#66B79E',
'#E65176',
'#528995',
'#FF710F',
'#305482',
'#CCD859',
'#565175',
'#FFB927',
'#3EBCAE',
'#E54C1F'
];
it('should rotate properly', function () {
expect(LegendColors.getNextColor()).toBe(COLORS[1]);
expect(LegendColors.getNextColor()).toBe(COLORS[2]);
expect(LegendColors.getNextColor()).toBe(COLORS[3]);
expect(LegendColors.getNextColor()).toBe(COLORS[4]);
expect(LegendColors.getNextColor()).toBe(COLORS[5]);
expect(LegendColors.getNextColor()).toBe(COLORS[6]);
expect(LegendColors.getNextColor()).toBe(COLORS[7]);
expect(LegendColors.getNextColor()).toBe(COLORS[8]);
expect(LegendColors.getNextColor()).toBe(COLORS[9]);
expect(LegendColors.getNextColor()).toBe(COLORS[0]);
expect(LegendColors.getNextColor()).toBe(COLORS[1]);
});
});
@@ -0,0 +1,74 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var LegendCustomDefinitionModel = require('builder/data/legends/legend-custom-definition-model');
var LegendCustomFormDefinitionModel = require('builder/editor/layers/layer-content-views/legend/form/legend-custom-definition-form-model');
var FactoryModals = require('../../../../../factories/modals');
describe('editor/layers/layer-content-views/legends/form/legend-custom-definition-model', function () {
var legendDefinitionModel;
var legendFormModel;
beforeEach(function () {
var configModel = new ConfigModel({
base_url: '/u/pepe'
});
var userModel = new UserModel({}, {
configModel: configModel
});
legendDefinitionModel = new LegendCustomDefinitionModel({
title: 'Foo',
items: [{
title: 'foo',
color: '#fabada',
icon: 'http://image.io/foo.svg'
}, {
title: 'bar',
color: '#abadaf',
icon: 'http://image.io/bar.svg'
}, {
title: 'baz',
color: '#ababab',
icon: 'http://image.io/baz.svg'
}]
}, {
layerDefinitionModel: new Backbone.Model(),
configModel: configModel,
vizId: 1
});
legendFormModel = new LegendCustomFormDefinitionModel(
_.pick(legendDefinitionModel.attributes, 'title', 'items'), {
legendDefinitionModel: legendDefinitionModel,
userModel: userModel,
configModel: configModel,
modals: FactoryModals.createModalService()
}
);
});
it('should initialize items correctly', function () {
var items = legendFormModel.get('items');
expect(items.length).toBe(3);
expect(items[0].fill.color.fixed).toBe('#fabada');
expect(items[0].fill.color.image).toBe('http://image.io/foo.svg');
expect(items[1].fill.color.fixed).toBe('#abadaf');
expect(items[1].fill.color.image).toBe('http://image.io/bar.svg');
expect(items[2].fill.color.fixed).toBe('#ababab');
expect(items[2].fill.color.image).toBe('http://image.io/baz.svg');
});
it('should parse items correctly', function () {
var items = legendFormModel.toJSON().items;
expect(items.length).toBe(3);
expect(items[0].color).toBe('#fabada');
expect(items[0].image).toBe('http://image.io/foo.svg');
expect(items[1].color).toBe('#abadaf');
expect(items[1].image).toBe('http://image.io/bar.svg');
expect(items[2].color).toBe('#ababab');
expect(items[2].image).toBe('http://image.io/baz.svg');
});
});
@@ -0,0 +1,117 @@
var Backbone = require('backbone');
var LegendBaseTypeView = require('builder/editor/layers/layer-content-views/legend/legend-base-type-view');
var MapDefinitionModel = require('builder/data/map-definition-model');
var LegendDefinitionsCollection = require('builder/data/legends/legend-definitions-collection');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LayerContentModel = require('builder/data/layer-content-model');
var FactoryModals = require('../../../../factories/modals');
describe('editor/layers/layer-content-view/legend/legend-base-type-view', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/wadus'
});
this.userModel = new UserModel({
username: 'foo',
google_maps_api_key: 123456
}, {
configModel: this.configModel
});
this.mapDefinitionModel = new MapDefinitionModel({
scrollwheel: false
}, {
parse: true,
configModel: this.configModel,
userModel: this.userModel,
layerDefinitionsCollection: new Backbone.Collection()
});
this.layerDefModel = new LayerDefinitionModel({
id: 'layerA',
type: 'cartoDB',
letter: 'a',
source: 'a0'
}, {
configModel: this.configModel
});
this.editorModel = new Backbone.Model();
this.editorModel.isEditing = function () {
return false;
};
this.editorModel.isDisabled = function () {
return false;
};
var querySchemaModel = new Backbone.Model();
querySchemaModel.hasRepeatedErrors = function () { return false; };
var queryGeometryModel = new Backbone.Model();
queryGeometryModel.hasRepeatedErrors = function () { return false; };
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
var layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
this.legendDefinitionsCollection = new LegendDefinitionsCollection(null, {
configModel: this.configModel,
layerDefinitionsCollection: new Backbone.Collection(),
vizId: 'v-123'
});
this.view = new LegendBaseTypeView({
mapDefinitionModel: this.mapDefinitionModel,
userModel: this.userModel,
configModel: this.configModel,
layerDefinitionModel: this.layerDefModel,
editorModel: this.editorModel,
layerContentModel: layerContentModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: 'I dunno lol',
userActions: {},
modals: FactoryModals.createModalService(),
overlayModel: new Backbone.Model(),
infoboxModel: new Backbone.Model()
});
spyOn(this.view, '_infoboxState');
spyOn(this.view, '_onChangeEdition');
spyOn(this.view, '_onChangeDisabled');
spyOn(this.view, '_onTogglerChanged');
spyOn(this.view, '_toggleOverlay');
});
it('_initBinds', function () {
this.view._initBinds();
this.view._layerContentModel.set('state', 'wadus');
expect(this.view._infoboxState).toHaveBeenCalled();
this.view._layerDefinitionModel.set('visible', false);
expect(this.view._infoboxState).toHaveBeenCalled();
this.view._mapDefinitionModel.set('legends', {});
expect(this.view._infoboxState).toHaveBeenCalled();
this.view._editorModel.set('edition', true);
expect(this.view._onChangeEdition).toHaveBeenCalled();
this.view._editorModel.set('disabled', false);
expect(this.view._onChangeDisabled).toHaveBeenCalled();
this.view._togglerModel.set('active', true);
expect(this.view._onTogglerChanged).toHaveBeenCalled();
this.view._overlayModel.set('visible', true);
expect(this.view._toggleOverlay).toHaveBeenCalled();
});
});
@@ -0,0 +1,438 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var LegendContentView = require('builder/editor/layers/layer-content-views/legend/legend-content-view');
var LegendColorTypes = require('builder/editor/layers/layer-content-views/legend/color/legend-color-types');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LegendDefinitionCollection = require('builder/data/legends/legend-definitions-collection');
var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory');
var FactoryModals = require('../../../../factories/modals');
var CategoryFormModel = require('builder/editor/layers/layer-content-views/legend/form/legend-category-definition-form-model');
var StyleModel = require('builder/editor/style/style-definition-model');
var StyleConstants = require('builder/components/form-components/_constants/_style');
describe('editor/layers/layer-content-view/legend/legend-content-view', function () {
var legendType = {
color: 'color',
none: 'none',
category: 'category'
};
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
}
}, {
parse: true,
configModel: this.configModel
});
this.layerDefinitionModel.styleModel = new StyleModel({
type: StyleConstants.Type.SIMPLE,
fill: {
color: {
fixed: '#892b27'
}
}
});
var layerDefinitionsCollection = new Backbone.Collection();
layerDefinitionsCollection.add(this.layerDefinitionModel);
this.legendDefinitionsCollection = new LegendDefinitionCollection(null, {
configModel: this.configModel,
layerDefinitionsCollection: layerDefinitionsCollection,
vizId: 'v-123'
});
LegendFactory.init(this.legendDefinitionsCollection);
this.legendDefinitionModel = new Backbone.Model({
type: legendType.none
}, {
layerDefinitionModel: this.layerDefinitionModel,
configModel: this.configModel,
vizId: 'v-123'
});
this.legendDefinitionModel.setAttributes = jasmine.createSpy();
this.editorModel = new Backbone.Model({
edition: false
});
this.editorModel.isEditing = function () { return false; };
function updateLegend () { }
this.view = new LegendContentView({
overlayModel: new Backbone.Model(),
legendTypes: LegendColorTypes,
updateLegend: updateLegend,
editorModel: this.editorModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionModel: this.legendDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: legendType.color,
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService()
});
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(this.view._carouselCollection.length).toBe(2); // none and custom
expect(_.size(this.view._subviews)).toBe(1); // [Carousel]
expect(this.view.$('.js-carousel').children().length).toBe(1);
expect(this.view.$('.js-form').children().length).toBe(0);
this.view._carouselCollection.at(1).set({ selected: true });
expect(_.size(this.view._subviews)).toBe(2); // [Carousel, FormView]
expect(this.view.$('.js-form').children().length).toBe(1);
// Clean the form view when type is none
this.view._carouselCollection.at(0).set({ selected: true });
expect(_.size(this.view._subviews)).toBe(1);
expect(this.view.$('.js-form').children().length).toBe(0);
});
});
describe('.initBinds', function () {
it('should call _onCarouselChange when _carouselCollection:selected changes', function () {
spyOn(this.view, '_onCarouselChange');
this.view._initBinds();
this.view._carouselCollection.at(1).set({ selected: true });
expect(this.view._onCarouselChange).toHaveBeenCalled();
});
});
describe('._initViews', function () {
it('should call ._renderCarousel', function () {
spyOn(this.view, '_renderCarousel');
this.view._initViews();
expect(this.view._renderCarousel).toHaveBeenCalled();
});
it('should call ._renderForm', function () {
spyOn(this.view, '_renderForm');
this.view._initViews();
expect(this.view._renderForm).toHaveBeenCalledWith(legendType.none);
});
it('should call ._updateToggle', function () {
spyOn(this.view, '_updateToggle');
this.view._initViews();
expect(this.view._updateToggle).toHaveBeenCalledWith(legendType.none);
});
});
describe('._onCarouselChange', function () {
it('should call the proper functions', function () {
spyOn(this.view, '_updateLegendModel');
spyOn(this.view, '_createFormModel');
spyOn(this.view, '_renderForm');
spyOn(this.view, '_updateToggle');
var model = this.view._carouselCollection.at(1);
this.view._onCarouselChange(model);
expect(this.view._updateLegendModel).not.toHaveBeenCalled();
expect(this.view._createFormModel).not.toHaveBeenCalled();
expect(this.view._renderForm).not.toHaveBeenCalled();
expect(this.view._updateToggle).not.toHaveBeenCalled();
model.set({ selected: true }, { silent: true });
this.view._onCarouselChange(model);
expect(this.view._updateLegendModel).toHaveBeenCalled();
expect(this.view._createFormModel).toHaveBeenCalled();
expect(this.view._renderForm).toHaveBeenCalled();
expect(this.view._updateToggle).toHaveBeenCalled();
});
});
describe('._createFormModel', function () {
it('should unbind FormModel if exists', function () {
var formModel = {};
this.view._formModel = formModel;
spyOn(this.view, '_unBindFormModel');
this.view._createFormModel();
expect(this.view._unBindFormModel).toHaveBeenCalledWith(formModel);
});
it('should not unbind FormModel if doesn\'t exist', function () {
spyOn(this.view, '_unBindFormModel');
this.view._createFormModel();
expect(this.view._unBindFormModel).not.toHaveBeenCalled();
});
it('should create a new FormModel when type exists and not equal to "none"', function () {
spyOn(this.view, '_bindFormModel');
this.view._createFormModel(legendType.category);
expect(this.view._formModel instanceof CategoryFormModel).toBe(true);
expect(this.view._bindFormModel).toHaveBeenCalledWith(this.view._formModel);
});
it('should not create a new FormModel when type is not defined or equal to "none"', function () {
spyOn(this.view, '_bindFormModel');
this.view._createFormModel(legendType.none);
expect(this.view._formModel).not.toBeDefined();
});
});
describe('._updateLegendModel', function () {
var defaultAttrs = { default: 'attributes' };
var newLegendModel = new Backbone.Model();
beforeEach(function () {
spyOn(LegendFactory, 'enableLegend');
spyOn(LegendFactory, 'createLegend').and.returnValue(newLegendModel);
spyOn(LegendFactory, 'disableLegend');
spyOn(LegendFactory, 'removeLegend');
spyOn(this.view, '_updateLegend');
spyOn(this.view, '_getDefaultAttributes').and.returnValue(defaultAttrs);
});
it('should assign type value to this.lastType', function () {
this.view._lastType = null;
this.view._updateLegendModel('bubble');
expect(this.view._lastType).toBe('bubble');
});
describe('when type is not none', function () {
beforeEach(function () {
this.view._updateLegendModel('bubble');
});
it('should call LegendFactory.enableLegend', function () {
expect(LegendFactory.enableLegend).toHaveBeenCalledWith(this.view._type);
});
it('should call LegendFactory.createLegend', function () {
expect(LegendFactory.createLegend).toHaveBeenCalledWith(this.layerDefinitionModel, 'bubble', defaultAttrs);
});
it('should call ._updateLegend', function () {
expect(this.view._updateLegend).toHaveBeenCalledWith(newLegendModel);
});
});
describe('when there is no type or is none', function () {
it('should call LegendFactory.disableLegend', function () {
this.view._updateLegendModel('none');
expect(LegendFactory.disableLegend).toHaveBeenCalledWith('none');
this.view._updateLegendModel(null);
expect(LegendFactory.disableLegend).toHaveBeenCalledWith(null);
});
it('should call LegendFactory.removeLegend', function () {
this.view._updateLegendModel('none');
expect(LegendFactory.removeLegend).toHaveBeenCalled();
this.view._updateLegendModel(null);
expect(LegendFactory.removeLegend).toHaveBeenCalled();
});
it('should call ._updateLeged', function () {
this.view._updateLegendModel('none');
expect(this.view._updateLegend).toHaveBeenCalledWith(null);
this.view._updateLegendModel(null);
expect(this.view._updateLegend).toHaveBeenCalledWith(null);
});
it('should delete this._legendDefinitionModel', function () {
this.view._legendDefinitionModel = 'legendDefinitionModel';
this.view._updateLegendModel(null);
expect(this.view._legendDefinitionModel).not.toBeDefined();
});
});
});
describe('._renderForm', function () {
it('should call ._removeForm', function () {
spyOn(this.view, '_removeForm');
this.view._renderForm();
expect(this.view._removeForm).toHaveBeenCalled();
});
it('should call ._addForm if type is defined and not "none"', function () {
spyOn(this.view, '_addForm');
this.view._renderForm(legendType.color);
expect(this.view._addForm).toHaveBeenCalled();
});
it('should not call ._addForm if there is no type or type is none', function () {
spyOn(this.view, '_addForm');
this.view._renderForm();
expect(this.view._addForm).not.toHaveBeenCalled();
});
});
describe('._updateToggle', function () {
it('should set editorModel:disabled true if type is "none"', function () {
this.view._updateToggle(legendType.none);
expect(this.view._editorModel.get('disabled')).toBe(true);
});
it('should set editorModel:disabled false if type is not "none"', function () {
this.view._updateToggle(legendType.color);
expect(this.view._editorModel.get('disabled')).toBe(false);
});
});
describe('._updateChanges', function () {
beforeEach(function () {
spyOn(LegendFactory, 'createLegend');
this.view._formModel = new Backbone.Model();
});
it('should call _legendDefinitionModel.setAttributes if _legendDefinitionModel is defined', function () {
this.view._updateChanges();
expect(this.legendDefinitionModel.setAttributes).toHaveBeenCalled();
});
it('should call LegendFactory.createLegend if _legendDefinitionModel is defined', function () {
this.view._updateChanges();
expect(LegendFactory.createLegend).toHaveBeenCalledWith(this.layerDefinitionModel, this.legendDefinitionModel.get('type'));
LegendFactory.createLegend.calls.reset();
this.view._legendDefinitionModel = undefined;
this.view._updateChanges();
expect(LegendFactory.createLegend).not.toHaveBeenCalled();
});
});
it('should render the "apply" button properly in advanced mode', function () {
this.view.$('.Options-bar .CDB-Toggle.js-input').click();
expect(this.view.$('.js-apply').hasClass('.CDB-Size-small')).toBeFalsy();
});
describe('carousel', function () {
it('should render carousel styleModel for points based', function () {
this.layerDefinitionModel.styleModel = new StyleModel({
type: StyleConstants.Type.SIMPLE,
fill: {
color: {
attribute: 'n_anclajes',
attribute_type: 'number'
}
}
});
var view = new LegendContentView({
overlayModel: new Backbone.Model(),
legendTypes: LegendColorTypes,
updateLegend: function () { },
editorModel: this.editorModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionModel: this.legendDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: 'color',
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService()
});
view.render();
expect(view._carouselCollection.pluck('val')).toEqual(['none', 'choropleth', 'custom']);
});
it('should render carousel styleModel for lines based', function () {
this.layerDefinitionModel.styleModel = new StyleModel({
type: StyleConstants.Type.SIMPLE,
stroke: {
color: {
attribute: 'n_anclajes',
attribute_type: 'number'
}
}
});
var view = new LegendContentView({
overlayModel: new Backbone.Model(),
legendTypes: LegendColorTypes,
updateLegend: function () { },
editorModel: this.editorModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionModel: this.legendDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: 'color',
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService()
});
view.render();
expect(view._carouselCollection.pluck('val')).toEqual(['none', 'choropleth', 'custom']);
});
it('should render category legend option in the carousel if styleModel has quantification category', function () {
this.layerDefinitionModel.styleModel = new StyleModel({
type: StyleConstants.Type.SIMPLE,
fill: {
color: {
attribute: 'n_anclajes',
attribute_type: 'number',
quantification: 'category'
}
}
});
var view = new LegendContentView({
overlayModel: new Backbone.Model(),
legendTypes: LegendColorTypes,
updateLegend: function () { },
editorModel: this.editorModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionModel: this.legendDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: 'color',
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService()
});
view.render();
expect(view._carouselCollection.pluck('val')).toEqual(['none', 'category', 'custom']);
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,170 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LegendDefinitionsCollection = require('builder/data/legends/legend-definitions-collection');
var LegendsState = require('builder/data/legends/legends-state');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory');
var ConfigModel = require('builder/data/config-model');
var Validations = require('builder/editor/layers/layer-content-views/legend/legend-validations');
function getSuccessResponse (id, type, title) {
var success = _.clone(TestResponses.save.success);
success.responseText = _.template(success.responseText)({
id: id,
type: type,
title: title || ''
});
return success;
}
var TestResponses = {
save: {
success: {
'status': '200',
'content-type': 'application/json',
responseText: '{"created_at":"2016-09-30T07:57:15+00:00","id":"<%= id %>","layer_id":"l-1","title":"<%= title %>","type":"<%= type %>","definition": {"color":"#fabada", "prefix":"", "suffix": "" }}'
}
},
destroy: {
success: {
status: 204
}
}
};
describe('editor/layers/layer-content-view/legend/legend-factory', function () {
beforeEach(function () {
jasmine.Ajax.install();
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
}
}, {
parse: true,
configModel: this.configModel
});
var styleModel = new Backbone.Model({
fill: {
color: {
fixed: '#fabada'
}
}
});
this.layerDefinitionModel.styleModel = styleModel;
this.layerDefinitionsCollection = new Backbone.Collection();
this.layerDefinitionsCollection.add(this.layerDefinitionModel);
this.legendDefinitionsCollection = new LegendDefinitionsCollection([], {
layerDefinitionsCollection: this.layerDefinitionsCollection,
configModel: this.configModel,
vizId: 'v-123'
});
LegendsState.init(this.layerDefinitionsCollection, this.legendDefinitionsCollection);
spyOn(Validations, 'bubble').and.returnValue(true);
spyOn(Validations, 'choropleth').and.returnValue(true);
spyOn(Validations, 'category').and.returnValue(true);
LegendFactory.init(this.legendDefinitionsCollection);
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('create legend', function () {
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(1, 'bubble'));
LegendFactory.createLegend(this.layerDefinitionModel, 'choropleth');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'choropleth'));
expect(this.legendDefinitionsCollection.length).toBe(2);
expect(this.legendDefinitionsCollection.at(0).get('type')).toBe('bubble');
expect(this.legendDefinitionsCollection.at(1).get('type')).toBe('choropleth');
});
it('update doesn\'t send two requests for the same attribute types', function () {
LegendFactory.createLegend(this.layerDefinitionModel, 'category');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'category'));
LegendFactory.createLegend(this.layerDefinitionModel, 'choropleth');
jasmine.Ajax.requests.mostRecent().respondWith(TestResponses.destroy.success);
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'choropleth'));
LegendFactory.createLegend(this.layerDefinitionModel, 'custom');
jasmine.Ajax.requests.mostRecent().respondWith(TestResponses.destroy.success);
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'custom'));
expect(this.legendDefinitionsCollection.length).toBe(1);
expect(this.legendDefinitionsCollection.at(0).get('type')).toBe('custom');
});
it('update an existent legend', function () {
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble', {title: 'foo'});
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(1, 'bubble'));
var model = this.legendDefinitionsCollection.findByLayerDefModelAndType(this.layerDefinitionModel, 'bubble');
spyOn(model, 'set').and.callThrough();
model.setAttributes({title: 'fiu'});
model.set.calls.reset();
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble', {title: 'bar'});
expect(model.set).toHaveBeenCalledWith({title: 'bar'});
expect(model.get('title')).toEqual('fiu');
expect(model.get('customState')).toEqual(['title']);
// Mimic the remove the title action by the UI form
model.setAttributes({title: 'foo'});
model.set.calls.reset();
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble', {title: 'bar'});
expect(model.set).toHaveBeenCalledWith({title: 'bar'});
expect(model.get('title')).toEqual('foo');
expect(model.get('customState')).toEqual(['title']);
});
it('remove legend', function () {
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(1, 'bubble'));
LegendFactory.createLegend(this.layerDefinitionModel, 'choropleth');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'choropleth'));
LegendFactory.removeLegend(this.layerDefinitionModel, 'bubble');
jasmine.Ajax.requests.mostRecent().respondWith(TestResponses.destroy.success);
expect(this.legendDefinitionsCollection.length).toBe(1);
expect(this.legendDefinitionsCollection.at(0).get('type')).toBe('choropleth');
});
it('remove all legends', function () {
LegendFactory.createLegend(this.layerDefinitionModel, 'bubble');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(1, 'bubble'));
LegendFactory.createLegend(this.layerDefinitionModel, 'choropleth');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(2, 'choropleth'));
LegendFactory.createLegend(this.layerDefinitionModel, 'category');
jasmine.Ajax.requests.mostRecent().respondWith(getSuccessResponse(3, 'category'));
LegendFactory.removeAllLegend(this.layerDefinitionModel);
expect(this.legendDefinitionsCollection.length).toBe(0);
});
});
@@ -0,0 +1,53 @@
var Backbone = require('backbone');
var LegendPropertiesFormView = require('builder/editor/layers/layer-content-views/legend/legend-properties-form');
describe('editor/layers/layer-content-views/legend/legend-properties-form', function () {
var view;
beforeEach(function () {
view = new LegendPropertiesFormView({
formModel: new Backbone.Model()
});
});
describe('.render', function () {
it('should render properly', function () {
spyOn(view, '_removeFormView');
spyOn(view, '_initViews');
view.render();
expect(view.$el.html()).toContain('js-propertiesForm');
expect(view.$el.html()).toContain('js-selector');
expect(view._removeFormView).toHaveBeenCalled();
expect(view._initViews).toHaveBeenCalled();
});
});
describe('_updateChanges', function () {
it('should call .commit on _formView', function () {
view.render();
spyOn(view._formView, 'commit');
view._updateChanges();
expect(view._formView.commit).toHaveBeenCalled();
});
});
describe('_removeFormView', function () {
it('should call .remove on _formView', function () {
view.render();
spyOn(view._formView, 'remove');
view._removeFormView();
expect(view._formView.remove).toHaveBeenCalled();
});
});
it('should not have any leaks', function () {
expect(view).toHaveNoLeaks();
});
afterEach(function () {
view.clean();
});
});
@@ -0,0 +1,283 @@
var Backbone = require('backbone');
var _ = require('underscore');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var LegendsView = require('builder/editor/layers/layer-content-views/legend/legends-view');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var LayerContentModel = require('builder/data/layer-content-model');
var LegendDefinitionsCollection = require('builder/data/legends/legend-definitions-collection');
var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory');
var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var QueryRowsCollection = require('builder/data/query-rows-collection');
var FactoryModals = require('../../../../factories/modals');
var sqlErrorTemplate = require('builder/editor/layers/layer-content-views/legend/legend-content-sql-error.tpl');
var actionErrorTemplate = require('builder/editor/layers/sql-error-action.tpl');
var StyleModel = require('builder/editor/style/style-definition-model');
var StyleConstants = require('builder/components/form-components/_constants/_style');
describe('editor/layers/layer-content-view/legend/legends-view', function () {
beforeEach(function () {
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.querySchemaModel = new QuerySchemaModel({
query: 'SELECT * FROM table',
status: 'fetched'
}, {
configModel: {}
});
spyOn(this.querySchemaModel, 'fetch');
this.queryGeometryModel = new QueryGeometryModel({
query: 'SELECT * FROM table',
status: 'fetched',
simple_geom: 'point'
}, {
configModel: {}
});
spyOn(this.queryGeometryModel, 'fetch');
this.mapDefModel = new Backbone.Model({
legends: true
});
this.editorModel = new Backbone.Model({
edition: false
});
this.editorModel.isEditing = function () { return false; };
this.editorModel.isDisabled = function () { return false; };
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: this.configModel,
userModel: this.userModel
});
this.a0 = this.analysisDefinitionNodesCollection.add({
id: 'a0',
type: 'source',
table_name: 'foo'
});
spyOn(this.a0, 'isCustomQueryApplied').and.returnValue(false);
this.layerDefinitionCollection = new LayerDefinitionsCollection(null, {
configModel: this.configModel,
userModel: this.userModel,
analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
mapId: 'm123',
stateDefinitionModel: {}
});
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'abc-123',
kind: 'carto',
options: {
type: 'CartoDB',
color: '#FABADA',
table_name: 'foo',
query: 'SELECT * FROM foo',
tile_style: 'asdasd',
visible: true
}
}, {
parse: true,
configModel: this.configModel,
collection: this.layerDefinitionCollection
});
spyOn(this.layerDefinitionModel, 'save');
spyOn(this.layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(this.a0);
this.layerDefinitionModel.styleModel = new StyleModel({
type: StyleConstants.Type.SIMPLE,
fill: {
color: {
fixed: '#892b27'
},
size: {
attribute: 10
}
}
});
this.legendDefinitionsCollection = new LegendDefinitionsCollection(null, {
configModel: {},
layerDefinitionsCollection: new Backbone.Collection(),
vizId: 'v-123'
});
LegendFactory.init(this.legendDefinitionsCollection);
spyOn(LegendsView.prototype, '_changeStyle');
this.queryRowsCollection = new QueryRowsCollection([], {
configModel: this.configModel,
querySchemaModel: this.querySchemaModel
});
spyOn(this.queryRowsCollection, 'fetch');
this.layerContentModel = new LayerContentModel({}, {
querySchemaModel: this.querySchemaModel,
queryGeometryModel: this.queryGeometryModel,
queryRowsCollection: this.queryRowsCollection
});
this.view = new LegendsView({
mapDefinitionModel: this.mapDefModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
editorModel: this.editorModel,
queryGeometryModel: this.queryGeometryModel,
layerContentModel: this.layerContentModel,
userActions: {
saveLayer: jasmine.createSpy('saveLayer')
},
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService()
});
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(_.size(this.view._subviews)).toBe(1); // [TabPaneView]
expect(this.view.$el.text()).toContain('editor.legend.menu-tab-pane-labels.color');
expect(this.view.$el.text()).toContain('editor.legend.menu-tab-pane-labels.size');
});
it('should render error template if layer content model is errored', function () {
spyOn(this.layerContentModel, 'isErrored').and.returnValue(true);
var errorTemplate = _t('editor.error-query.body', {
action: actionErrorTemplate({
label: _t('editor.error-query.label')
})
});
this.view.render();
expect(this.view.el.innerHTML).toBe(sqlErrorTemplate({ body: errorTemplate }));
});
});
it('should show/hide overlay properly', function () {
this.layerDefinitionModel.set('visible', false);
expect(this.view._overlayModel.get('visible')).toBe(true);
this.layerDefinitionModel.set('visible', true);
expect(this.view._overlayModel.get('visible')).toBe(false);
});
describe('._initModels', function () {
it('should create _infoboxModel', function () {
this.view._infoboxModel = undefined;
expect(this.view._infoboxModel).not.toBeDefined();
this.view._initModels();
expect(this.view._infoboxModel).toBeDefined();
});
it('should create _overlayModel', function () {
this.view._overlayModel = undefined;
expect(this.view._overlayModel).not.toBeDefined();
this.view._initModels();
expect(this.view._overlayModel).toBeDefined();
});
});
describe('._initBinds', function () {
it('should call _changeStyle when editorModel:edition changes', function () {
this.editorModel.set({ edition: true });
expect(LegendsView.prototype._changeStyle).toHaveBeenCalled();
});
});
describe('._isErrored', function () {
it('should return layerContentModel isErrored', function () {
var isErroredSpy = spyOn(this.view._layerContentModel, 'isErrored');
isErroredSpy.and.returnValue(false);
expect(this.view._isErrored()).toBe(false);
isErroredSpy.and.returnValue(true);
expect(this.view._isErrored()).toBe(true);
});
});
describe('._isLayerHidden', function () {
it('should return true if layerDefinitionModel.visible is false', function () {
this.layerDefinitionModel.set('visible', false, { silent: true });
expect(this.view._isLayerHidden()).toBe(true);
this.layerDefinitionModel.set('visible', true, { silent: true });
expect(this.view._isLayerHidden()).toBe(false);
});
});
describe('._showHiddenLayer', function () {
it('should call layerDefinitionModel.toggleVisible', function () {
spyOn(this.view._layerDefinitionModel, 'toggleVisible');
this.view._showHiddenLayer();
expect(this.view._layerDefinitionModel.toggleVisible).toHaveBeenCalled();
});
it('should call userActions.saveLayer', function () {
this.view._showHiddenLayer();
expect(this.view._userActions.saveLayer).toHaveBeenCalled();
});
});
describe('.isStyleCompatible', function () {
it('should return true if legend type is not size', function () {
expect(this.view._isStyleCompatible('color')).toBe(true);
});
it('should return true if any of the legends size is compatible', function () {
expect(this.view._isStyleCompatible('size')).toBe(true);
});
it('should return false if no legend size is compatible', function () {
this.view._layerDefinitionModel.styleModel = new StyleModel({});
expect(this.view._isStyleCompatible('size')).toBe(false);
});
});
describe('when layer has errors', function () {
beforeEach(function () {
spyOn(this.view, '_isErrored').and.returnValue(true);
this.view.render();
});
describe('.render', function () {
it('should render properly', function () {
expect(this.view.$el.text()).toContain('editor.error-query.body');
});
});
describe('._onQueryChanged', function () {
it('should call render', function () {
spyOn(this.view, 'render');
this.view._onQueryChanged();
expect(this.view.render).toHaveBeenCalled();
});
});
});
it('should not have any leaks', function () {
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,274 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LegendSizeView = require('builder/editor/layers/layer-content-views/legend/size/legend-size-view');
var LayerContentModel = require('builder/data/layer-content-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LegendDefinitionsCollection = require('builder/data/legends/legend-definitions-collection');
var LegendBaseDefinitionModel = require('builder/data/legends/legend-base-definition-model');
var LegendCustomDefinitionModel = require('builder/data/legends/legend-custom-definition-model');
var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var FactoryModals = require('../../../../../factories/modals');
var MetricsTracker = require('builder/components/metrics/metrics-tracker');
describe('editor/layers/layer-content-view/legend/size/legend-size-view', function () {
beforeEach(function () {
spyOn(MetricsTracker, 'track');
this.configModel = new ConfigModel({
base_url: '/u/pepe'
});
this.userModel = new UserModel({}, {
configModel: this.configModel
});
this.mapDefModel = new Backbone.Model({
legends: true
});
spyOn(this.mapDefModel, 'save');
this.userActions = {
saveLayer: jasmine.createSpy('saveLayer')
};
this.layerDefinitionModel = new LayerDefinitionModel({
id: 'l-1',
fetched: true,
options: {
type: 'CartoDB',
table_name: 'foo',
cartocss: 'asd',
source: 'a0'
}
}, {
parse: true,
configModel: this.configModel
});
this.layerDefinitionsCollection = new Backbone.Collection();
this.layerDefinitionsCollection.add(this.layerDefinitionModel);
this.legendDefinitionsCollection = new LegendDefinitionsCollection(null, {
configModel: this.configModel,
layerDefinitionsCollection: this.layerDefinitionsCollection,
vizId: 'v-123'
});
LegendFactory.init(this.legendDefinitionsCollection);
this.editorModel = new Backbone.Model({
edition: false
});
this.editorModel.isEditing = function () { return false; };
this.editorModel.isDisabled = function () { return false; };
this.htmlModel = new Backbone.Model();
var querySchemaModel = new Backbone.Model();
querySchemaModel.hasRepeatedErrors = function () { return false; };
var queryGeometryModel = new Backbone.Model();
queryGeometryModel.hasRepeatedErrors = function () { return false; };
var queryRowsCollection = new Backbone.Collection();
queryRowsCollection.hasRepeatedErrors = function () { return false; };
this.layerContentModel = new LayerContentModel({}, {
querySchemaModel: querySchemaModel,
queryGeometryModel: queryGeometryModel,
queryRowsCollection: queryRowsCollection
});
this.view = new LegendSizeView({
userActions: this.userActions,
editorModel: this.editorModel,
mapDefinitionModel: this.mapDefModel,
layerContentModel: this.layerContentModel,
layerDefinitionModel: this.layerDefinitionModel,
legendDefinitionsCollection: this.legendDefinitionsCollection,
type: 'size',
userModel: this.userModel,
configModel: this.configModel,
modals: FactoryModals.createModalService(),
infoboxModel: new Backbone.Model(),
overlayModel: new Backbone.Model()
});
});
describe('.render', function () {
it('should render properly', function () {
this.view.render();
expect(_.size(this.view._subviews)).toBe(1); // [PanelWithOptionsView]
expect(this.view.$('.Infobox').length).toBe(0);
});
it('should render custom html legend infobox properly', function () {
this.legendDefinitionsCollection.add(new LegendCustomDefinitionModel({
html: '<ul><li>Foo</li></ul>'
}, {
layerDefinitionModel: this.layerDefinitionModel,
configModel: this.configModel,
vizId: 'v-123'
}));
this.view._legendDefinitionModel = this.legendDefinitionsCollection.at(0);
this.view._infoboxState();
this.view.render();
expect(this.view.$('.Infobox').length).toBe(1);
expect(this.view.$('.Infobox').html()).toContain('editor.legend.messages.custom-legend.body');
});
it('should render hidden layer infobox properly', function () {
this.layerDefinitionModel.set({visible: false});
this.view.render();
expect(this.view.$('.Infobox').length).toBe(1);
expect(this.view.$('.Infobox').html()).toContain('editor.messages.layer-hidden.body');
});
it('should render legends-disabled infobox properly', function () {
this.mapDefModel.set({legends: false});
this.view.render();
expect(this.view.$('.Infobox').length).toBe(1);
expect(this.view.$('.Infobox').html()).toContain('editor.legend.messages.legends-disabled.body');
this.view.$('.Infobox .js-action').click();
expect(this.mapDefModel.save).toHaveBeenCalled();
});
});
describe('._initBinds', function () {
it('should call ._infoboxState when layerContentModel:state changes', function () {
spyOn(this.view, '_infoboxState');
this.view._initBinds();
this.view._layerContentModel.trigger('change:state');
expect(this.view._infoboxState).toHaveBeenCalled();
});
it('should call ._infoboxState when _layerDefinitionModel:visible changes', function () {
spyOn(this.view, '_infoboxState');
this.view._initBinds();
this.view._layerDefinitionModel.trigger('change:visible');
expect(this.view._infoboxState).toHaveBeenCalled();
});
it('should call ._infoboxState when _mapDefinitionModel:legends changes', function () {
spyOn(this.view, '_infoboxState');
this.view._initBinds();
this.view._mapDefinitionModel.trigger('change:legends');
expect(this.view._infoboxState).toHaveBeenCalled();
});
it('should call ._onChangeEdition when _editorModel:edition changes', function () {
spyOn(this.view, '_onChangeEdition');
this.view._initBinds();
this.view._editorModel.trigger('change:edition');
expect(this.view._onChangeEdition).toHaveBeenCalled();
});
it('should call ._onChangeDisabled when _editorModel:disabled changes', function () {
spyOn(this.view, '_onChangeDisabled');
this.view._initBinds();
this.view._editorModel.trigger('change:disabled');
expect(this.view._onChangeDisabled).toHaveBeenCalled();
});
it('should call ._onTogglerChanged when _togglerModel:active changes', function () {
spyOn(this.view, '_onTogglerChanged');
this.view._initBinds();
this.view._togglerModel.trigger('change:active');
expect(this.view._onTogglerChanged).toHaveBeenCalled();
});
it('should call ._toggleOverlay when _overlayModel:visible changes', function () {
spyOn(this.view, '_toggleOverlay');
this.view._initBinds();
this.view._overlayModel.trigger('change:visible');
expect(this.view._toggleOverlay).toHaveBeenCalled();
});
});
describe('._isLayerHidden', function () {
it('should return true if layerDefinitionModel.visible is false', function () {
this.layerDefinitionModel.set('visible', false, { silent: true });
expect(this.view._isLayerHidden()).toBe(true);
this.layerDefinitionModel.set('visible', true, { silent: true });
expect(this.view._isLayerHidden()).toBe(false);
});
});
describe('._isErrored', function () {
it('should return layerContentModel isErrored', function () {
var isErroredSpy = spyOn(this.view._layerContentModel, 'isErrored');
isErroredSpy.and.returnValue(false);
expect(this.view._isErrored()).toBe(false);
isErroredSpy.and.returnValue(true);
expect(this.view._isErrored()).toBe(true);
});
});
describe('._showHiddenLayer', function () {
it('should call layerDefinitionModel.toggleVisible', function () {
spyOn(this.view._layerDefinitionModel, 'toggleVisible');
this.view._showHiddenLayer();
expect(this.view._layerDefinitionModel.toggleVisible).toHaveBeenCalled();
});
it('should call userActions.saveLayer', function () {
this.view._showHiddenLayer();
expect(this.view._userActions.saveLayer).toHaveBeenCalled();
});
});
it('should transmit html snippets changes properly', function () {
this.legendDefinitionsCollection.add(new LegendBaseDefinitionModel({
type: 'category'
}, {
layerDefinitionModel: this.layerDefinitionModel,
configModel: this.configModel,
vizId: 'v-123'
}));
this.view._legendDefinitionModel = this.legendDefinitionsCollection.at(0);
this.view._codemirrorModel.set('content', 'Wadus[[Legend]]');
this.view._saveLegendHTML();
this.view.render();
expect(this.view._legendDefinitionModel.get('preHTMLSnippet')).toBe('Wadus');
this.view._codemirrorModel.set('content', 'Foo[[Legend]]Bar');
this.view._saveLegendHTML();
expect(this.view._legendDefinitionModel.get('preHTMLSnippet')).toBe('Foo');
expect(this.view._legendDefinitionModel.get('postHTMLSnippet')).toBe('Bar');
});
it('should not have any leaks', function () {
this.view.render();
expect(this.view).toHaveNoLeaks();
});
});
@@ -0,0 +1,401 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LayerHeaderView = require('builder/editor/layers/layer-header-view');
var SyncModel = require('builder/data/synchronization-model');
var FactoryModals = require('../../factories/modals');
var getLayerDefinitionModelFixture = require('fixtures/builder/layer-definition-model.fixture.js');
var getConfigModelFixture = require('fixtures/builder/config-model.fixture.js');
var getQueryGeometryModelFixture = require('fixtures/builder/query-geometry-model.fixture.js');
var getQuerySchemaModelFixture = require('fixtures/builder/query-schema-model.fixture.js');
var getQueryRowsCollectionModelFixture = require('fixtures/builder/query-rows-collection.fixture.js');
var getNodeModelFixture = require('fixtures/builder/node-model.fixture.js');
describe('editor/layers/layer-header-view', function () {
var view;
var configModel;
var layerDefinitionModel;
var stateDefinitionModel;
var tableNodeModel;
var queryGeometryModel;
var querySchemaModel;
var queryRowsCollection;
var visDefinitionModel;
var widgetDefinitionsCollection;
var userModel;
var editorModel;
var sourceNodeModel;
var tableName = 'foo';
var NodeModel = getNodeModelFixture;
var createViewFn = function (options) {
options = options || {};
var hasValueAsync = options.hasOwnProperty('hasValueAsync') ? options.hasValueAsync : false;
var isEmptyAsync = options.hasOwnProperty('isEmptyAsync') ? options.isEmptyAsync : true;
var canBeGeoreferenced = options.hasOwnProperty('canBeGeoreferenced') ? options.canBeGeoreferenced : false;
configModel = getConfigModelFixture();
editorModel = new Backbone.Model();
editorModel.isEditing = function () {
return options && options.hasOwnProperty('editing') ? options.editing : false;
};
userModel = new Backbone.Model();
layerDefinitionModel = getLayerDefinitionModelFixture({
id: 'l-1',
name: tableName,
configModel: configModel,
parse: true
});
stateDefinitionModel = {
key: 'value'
};
tableNodeModel = null;
queryGeometryModel = getQueryGeometryModelFixture();
querySchemaModel = getQuerySchemaModelFixture({
query: options.query
});
queryRowsCollection = getQueryRowsCollectionModelFixture({
querySchemaModel: querySchemaModel
});
sourceNodeModel = new Backbone.Model({
type: 'source',
table_name: tableName
});
sourceNodeModel.queryGeometryModel = queryGeometryModel;
sourceNodeModel.querySchemaModel = querySchemaModel;
sourceNodeModel.queryRowsCollection = queryRowsCollection;
sourceNodeModel.isCustomQueryApplied = function () { return true; };
layerDefinitionModel.getAnalysisDefinitionNodeModel = function () {};
spyOn(sourceNodeModel.queryGeometryModel, 'hasValueAsync').and.returnValue(Promise.resolve(hasValueAsync));
spyOn(layerDefinitionModel, 'isEmptyAsync').and.returnValue(Promise.resolve(isEmptyAsync));
spyOn(layerDefinitionModel, 'canBeGeoreferenced').and.returnValue(Promise.resolve(canBeGeoreferenced));
spyOn(layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(sourceNodeModel);
visDefinitionModel = new Backbone.Model();
widgetDefinitionsCollection = new Backbone.Collection();
return new LayerHeaderView({
configModel: configModel,
editorModel: editorModel,
layerDefinitionModel: layerDefinitionModel,
layerDefinitionsCollection: {},
modals: FactoryModals.createModalService(),
stateDefinitionModel: stateDefinitionModel,
tableNodeModel: tableNodeModel,
userActions: {},
userModel: userModel,
visDefinitionModel: visDefinitionModel,
widgetDefinitionsCollection: widgetDefinitionsCollection
});
};
describe('.initialize', function () {
describe('._initVisTableModel', function () {
it('should initialize a VisTableModel if _sourceNodeModel is defined', function () {
view = createViewFn();
expect(view._visTableModel).toBeDefined();
});
});
describe('._initViewState', function () {
it('should set default state values', function () {
spyOn(LayerHeaderView.prototype, '_setViewStateValues');
view = createViewFn();
expect(view._viewState.get('isLayerEmpty')).toEqual(false);
expect(view._viewState.get('hasGeom')).toEqual(true);
expect(view._viewState.get('canBeGeoreferenced')).toEqual(false);
expect(LayerHeaderView.prototype._setViewStateValues).toHaveBeenCalled();
});
});
});
describe('._setViewStateValues', function () {
it('should be able to update _viewState properties', function (done) {
view = createViewFn({
hasValueAsync: true,
isEmptyAsync: true,
canBeGeoreferenced: true
});
view._viewState.set({
isLayerEmpty: false,
hasGeom: false,
canBeGeoreferenced: false
});
view._setViewStateValues();
setTimeout(function () {
expect(view._viewState.get('isLayerEmpty')).toEqual(true);
expect(view._viewState.get('hasGeom')).toEqual(true);
expect(view._viewState.get('canBeGeoreferenced')).toEqual(true);
done();
}, 0);
});
});
describe('.render', function () {
it('should render properly', function () {
view = createViewFn();
view.render();
expect(_.size(view._subviews)).toBe(3); // [inlineEditor, centerTooltip, toggleMenuTooltip]
});
it('should have class for onboarding', function () {
view = createViewFn();
view.render();
expect(view.$el.hasClass('js-editorPanelHeader')).toBe(true);
});
it('should show zoom icon when there is geometry', function (done) {
view = createViewFn({
hasValueAsync: true,
isEmptyAsync: true,
canBeGeoreferenced: true
});
view.render();
setTimeout(function () {
expect(view.$('.js-zoom')[0].style.display).not.toBe('none');
done();
}, 0);
});
it('should hide zoom icon when there is no geometry', function (done) {
view = createViewFn({
hasValueAsync: false,
isEmptyAsync: true,
canBeGeoreferenced: true
});
view.render();
setTimeout(function () {
expect(view.$('.js-zoom')[0].style.display).toBe('none');
done();
}, 0);
});
it('should show warning icon when the layer is empty', function (done) {
view = createViewFn({
hasValueAsync: true,
isEmptyAsync: true,
canBeGeoreferenced: false
});
view.render();
setTimeout(function () {
expect(view.$el.find('.js-warningIcon').length).toBe(1);
done();
}, 0);
});
it('should show warning icon when the layer can be georeferenced', function (done) {
view = createViewFn({
hasValueAsync: true,
isEmptyAsync: false,
canBeGeoreferenced: true
});
view.render();
setTimeout(function () {
expect(view.$el.find('.js-warningIcon').length).toBe(1);
done();
}, 0);
});
it('should not show warning icon when the layer is not empty and can not be georeferenced', function (done) {
view = createViewFn({
hasValueAsync: true,
isEmptyAsync: false,
canBeGeoreferenced: false
});
view.render();
setTimeout(function () {
expect(view.$el.find('.js-warningIcon').length).toBe(0);
done();
}, 0);
});
});
describe('._initViews', function () {
it('should call _addSyncInfo', function () {
view = createViewFn();
spyOn(view, '_addSyncInfo');
view._initViews();
expect(view._addSyncInfo).toHaveBeenCalled();
});
});
describe('._addSyncInfo', function () {
beforeEach(function () {
view = createViewFn();
});
it('should call _createSyncInfo with the proper argument if the node has a synced tableModel', function () {
layerDefinitionModel.getAnalysisDefinitionNodeModel.and.returnValue(new NodeModel({ id: 765 }, true));
spyOn(view, '_createSyncInfo');
view._addSyncInfo();
expect(view._createSyncInfo.calls.mostRecent().args[0].id).toEqual(765);
});
it('should not call _createSyncInfo if the node has not a synced tableModel', function () {
layerDefinitionModel.getAnalysisDefinitionNodeModel.and.returnValue(new NodeModel({}));
spyOn(view, '_createSyncInfo');
view._addSyncInfo();
expect(view._createSyncInfo).not.toHaveBeenCalled();
});
it('should not call _createSyncInfo if the node does not have a tableModel', function () {
layerDefinitionModel.getAnalysisDefinitionNodeModel.and.returnValue(new NodeModel());
spyOn(view, '_createSyncInfo');
view._addSyncInfo();
expect(view._createSyncInfo).not.toHaveBeenCalled();
});
});
describe('._createSyncInfo', function () {
it('should create SyncInfoView object with proper table and sync models', function () {
view = createViewFn();
var syncModel = new SyncModel(null, { configModel: configModel });
var nodeModel = new NodeModel({ id: 765 }, true, syncModel);
view._createSyncInfo(nodeModel.tableModel);
expect(view._syncInfoView).not.toBeUndefined();
expect(view._syncInfoView._syncModel.cid).toEqual(syncModel.cid);
expect(view._syncInfoView._tableModel.id).toEqual(765);
});
});
describe('._bindEvents', function () {
it('should call _onSourceChanged when layer definition model changes source', function () {
view = createViewFn();
spyOn(view, '_onSourceChanged');
view._bindEvents();
layerDefinitionModel.set('source', {});
expect(view._onSourceChanged).toHaveBeenCalled();
});
});
describe('._changeStyle', function () {
it('should have proper classes if editing in expert mode', function () {
view = createViewFn({ editing: true });
view.render();
view._changeStyle();
expect(view._getTitle().hasClass('u-whiteTextColor')).toBe(true);
expect(view._getText().hasClass('u-altTextColor')).toBe(true);
expect(view._getInlineEditor().hasClass('u-mainTextColor')).toBe(true);
expect(view._getLink().hasClass('u-whiteTextColor')).toBe(true);
expect(view._getBack().hasClass('u-whiteTextColor')).toBe(true);
expect(view._getToggleMenu().hasClass('is-white')).toBe(true);
expect(view._getZoom().hasClass('is-white')).toBe(true);
});
it('should have proper classes if not in expert mode', function () {
view = createViewFn();
view.render();
view._changeStyle();
expect(view._getTitle().hasClass('u-whiteTextColor')).toBe(false);
expect(view._getText().hasClass('u-altTextColor')).toBe(false);
expect(view._getInlineEditor().hasClass('u-mainTextColor')).toBe(false);
expect(view._getLink().hasClass('u-altTextColor')).toBe(false);
expect(view._getBack().hasClass('u-whiteTextColor')).toBe(false);
expect(view._getToggleMenu().hasClass('is-white')).toBe(false);
expect(view._getZoom().hasClass('is-white')).toBe(false);
});
});
describe('._onZoomClicked', function () {
it('should call to zoomToData with the right query', function () {
view = createViewFn({query: ''});
// The only way we have to know if zoomToData has been called is to provoke an
// error. This way we know that the query has been extracted from our querySchemaModel
// and that zoomToData has been called.
expect(function () {
view._onZoomClicked();
}).toThrowError('query is required');
});
});
describe('._getNodeModel', function () {
it('should return node model', function () {
view = createViewFn();
view._getNodeModel();
expect(layerDefinitionModel.getAnalysisDefinitionNodeModel).toHaveBeenCalled();
});
});
describe('layer name edition', function () {
it('should update name properly', function () {
view = createViewFn();
view.render();
view._onSaveSuccess('wadus');
expect(view.$('.js-title-editor').attr('title')).toBe('wadus');
expect(view.$('.js-title').text()).toBe('wadus');
});
});
describe('_showOrHideZoomVisibility', function () {
it('should show zoom if layer has geometries', function () {
view = createViewFn({
hasValueAsync: true
});
view._viewState.set('hasGeom', true);
view.render();
var zoom = view._getZoom();
expect(zoom[0].style.display).toBeDefined();
expect(zoom[0].style.display).not.toEqual('none');
});
it('should not show zoom if layer has not geometries', function () {
view = createViewFn({
hasValueAsync: false
});
view._viewState.set('hasGeom', false);
view.render();
var zoom = view._getZoom();
expect(zoom[0].style.display).toEqual('none');
});
});
});
@@ -0,0 +1,128 @@
var Backbone = require('backbone');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var BaseTiledLayerView = require('builder/editor/layers/layer-views/base-tiled-layer-view');
var PlainColorLayerView = require('builder/editor/layers/layer-views/plain-color-layer-view');
var BackgroundImageLayerView = require('builder/editor/layers/layer-views/background-image-layer-view');
var DataLayerView = require('builder/editor/layers/layer-views/data-layer-view');
var ErroredDataLayerView = require('builder/editor/layers/layer-views/errored-data-layer-view');
var LabelsLayerView = require('builder/editor/layers/layer-views/labels-layer-view');
var LayerViewFactory = require('builder/editor/layers/layer-view-factory');
var ModalsService = require('builder/components/modals/modals-service-model');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
describe('builder/editor/layers/layer-view-factory', function () {
beforeEach(function () {
this.nodeDefModel = new AnalysisDefinitionNodeModel({
id: 'a2',
type: 'buffer',
params: {}
}, {
configModel: {}
});
spyOn(this.nodeDefModel.querySchemaModel, 'fetch');
spyOn(this.nodeDefModel.queryGeometryModel, 'fetch');
spyOn(this.nodeDefModel.queryRowsCollection, 'fetch');
this.layerViewFactory = new LayerViewFactory({
modals: new ModalsService(),
userActions: {},
stackLayoutModel: {},
layerDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
analysis: {},
configModel: {},
sortableSelector: '.selector',
stateDefinitionModel: {},
visDefinitionModel: {},
widgetDefinitionsCollection: new Backbone.Collection()
});
});
it('should return an instance of BaseTiledLayerView', function () {
var layerModel = new LayerDefinitionModel({
type: 'Tiled',
order: 0
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof BaseTiledLayerView).toBe(true);
});
it('should return an instance of PlainColorLayerView', function () {
var layerModel = new LayerDefinitionModel({
type: 'Plain',
order: 0,
color: '#FABADA'
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof PlainColorLayerView).toBe(true);
});
it('should return an instance of BackgroundImageLayerView', function () {
var layerModel = new LayerDefinitionModel({
type: 'Plain',
order: 0,
image: 'http://example.com/image.png'
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof BackgroundImageLayerView).toBe(true);
});
describe('data-layer-view', function () {
var layerModel;
beforeEach(function () {
layerModel = new LayerDefinitionModel({
type: 'CartoDB',
order: 1
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel');
});
it('should return an instance of DataLayerView', function () {
layerModel.findAnalysisDefinitionNodeModel.and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof DataLayerView).toBe(true);
});
it('should return an instance of ErrorDataLayerView', function () {
layerModel.findAnalysisDefinitionNodeModel.and.returnValue(null);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof ErroredDataLayerView).toBe(true);
});
});
it('should return an instance of DataLayerView', function () {
var layerModel = new LayerDefinitionModel({
type: 'torque',
order: 1
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof DataLayerView).toBe(true);
});
it('should return an instance of LabelsLayerView', function () {
var layerModel = new LayerDefinitionModel({
type: 'Tiled',
order: 3
}, {
configModel: {}
});
spyOn(layerModel, 'findAnalysisDefinitionNodeModel').and.returnValue(this.nodeDefModel);
var layerView = this.layerViewFactory.createLayerView(layerModel);
expect(layerView instanceof LabelsLayerView).toBe(true);
});
});
@@ -0,0 +1,33 @@
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var BackgroundImageLayerView = require('builder/editor/layers/layer-views/background-image-layer-view');
describe('editor/layers/layer-views/background-image-layer-view', function () {
beforeEach(function () {
this.model = new LayerDefinitionModel({
name: 'thename',
image: 'http://example.com/image.png?size=big'
}, {
configModel: {}
});
this.view = new BackgroundImageLayerView({
model: this.model,
stackLayoutModel: {}
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title and description of layer', function () {
expect(this.view.$el.text()).toContain('image.png');
expect(this.view.$el.text()).toContain('editor.layers.image.title-label');
});
it('should render thumbnail image', function () {
expect(this.view.$('img').attr('src')).toEqual('http://example.com/image.png?size=big');
});
});
@@ -0,0 +1,44 @@
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var BaseTiledLayerView = require('builder/editor/layers/layer-views/base-tiled-layer-view');
describe('editor/layers/layer-views/base-tiled-layer-view', function () {
beforeEach(function () {
this.model = new LayerDefinitionModel({
name: 'thename',
urlTemplate: 'http://{s}.example.com/basemap/{z}/{x}/{y}.png',
subdomains: 'abcd'
}, {
configModel: {}
});
this.view = new BaseTiledLayerView({
model: this.model,
stackLayoutModel: {}
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title and description of layer', function () {
expect(this.view.$el.text()).toContain('thename');
expect(this.view.$el.text()).toContain('editor.layers.basemap.title-label');
});
it('should render thumbnail image', function () {
expect(this.view.$('img').attr('src')).toEqual('http://a.example.com/basemap/6/30/24.png');
});
describe('when layer has no name', function () {
beforeEach(function () {
this.model.set('name', '');
});
it('should render title and description of layer', function () {
expect(this.view.$el.text()).toContain('editor.layers.basemap.custom-basemap');
});
});
});
@@ -0,0 +1,740 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var UserActions = require('builder/data/user-actions');
var UserModel = require('builder/data/user-model');
var StackLayoutModel = require('builder/components/stack-layout/stack-layout-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var DataLayerView = require('builder/editor/layers/layer-views/data-layer-view');
var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model');
var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
var StyleDefinitionModel = require('builder/editor/style/style-definition-model');
var FactoryModals = require('../../../factories/modals');
var Router = require('builder/routes/router');
var getLayerDefinitionModelFixture = require('fixtures/builder/layer-definition-model.fixture.js');
var getConfigModelFixture = require('fixtures/builder/config-model.fixture.js');
var fakePromise = require('fixtures/builder/fake-promise.fixture.js');
var ajaxFixtures = require('fixtures/builder/ajax-fixtures.js');
describe('editor/layers/data-layer-view', function () {
var geometryModelFakePromise = null;
var canBeGeoreferencedPromise = null;
var isLayerEmptyPromise = null;
beforeEach(function () {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(ajaxFixtures.querySchemaModel.url.http)
.andReturn(ajaxFixtures.querySchemaModel.response.ok);
jasmine.Ajax.stubRequest(ajaxFixtures.queryGeometryModel.url.http)
.andReturn(ajaxFixtures.queryGeometryModel.response.ok);
jasmine.Ajax.stubRequest(ajaxFixtures.queryRowsCollection.url.http)
.andReturn(ajaxFixtures.queryRowsCollection.response.ok);
this.modals = FactoryModals.createModalService();
spyOn(this.modals, 'create');
var configModel = getConfigModelFixture();
var userModel = new UserModel({
username: 'pepe',
quota: {}
}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
configModel: configModel,
userModel: userModel
});
this.a0 = new AnalysisDefinitionNodeModel({
id: 'a0',
type: 'buffer',
params: {}
}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection.add(this.a0);
this.model = getLayerDefinitionModelFixture({
configModel: configModel
});
this.model.findAnalysisDefinitionNodeModel = function () {};
canBeGeoreferencedPromise = fakePromise(this.model, 'canBeGeoreferenced');
isLayerEmptyPromise = fakePromise(this.model, 'isEmptyAsync');
this.layerDefinitionsCollection = this.model.collection;
this.layerDefinitionsCollection.findAnalysisDefinitionNodeModel = function () {};
this.model.styleModel = new StyleDefinitionModel({}, {
configModel: configModel
});
spyOn(this.model, 'canBeDeletedByUser');
spyOn(this.model, 'getNumberOfAnalyses').and.returnValue(3);
spyOn(this.model, 'getAnalysisDefinitionNodeModel').and.returnValue(this.a0);
this.a0.queryGeometryModel.set('simple_geom', 'polygon');
this.queryGeometryModel = this.a0.queryGeometryModel;
geometryModelFakePromise = fakePromise(this.queryGeometryModel, 'hasValueAsync');
this.c2 = new AnalysisDefinitionNodeModel({
id: 'c2',
type: 'buffer',
params: {}
}, {
configModel: configModel
});
this.analysisDefinitionNodesCollection.add(this.c2);
spyOn(this.model, 'findAnalysisDefinitionNodeModel').and.returnValue(this.c2);
this.stackLayoutModel = new StackLayoutModel(null, {stackLayoutItems: {}});
spyOn(this.stackLayoutModel, 'nextStep');
this.newAnalysesViewSpy = jasmine.createSpy('newAnalysesView').and.callFake(function (el) {
var view = new CoreView({
el: el
});
view.render = function () {
this.$el.html('ANALYSES');
return this;
};
return view;
});
this.userActions = UserActions({
userModel: {},
analysisDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
widgetDefinitionsCollection: {}
});
this.promise = $.Deferred();
spyOn(this.userActions, 'saveLayer').and.returnValue(this.promise);
this.widgetDefinitionsCollection = new Backbone.Collection();
this.widgetDefinitionsCollection.widgetsOwnedByLayer = function () { return 0; };
this.visDefinitionModel = new Backbone.Model();
this.view = new DataLayerView({
modals: this.modals,
model: this.model,
userActions: this.userActions,
stackLayoutModel: this.stackLayoutModel,
newAnalysesView: this.newAnalysesViewSpy,
layerDefinitionsCollection: this.layerDefinitionsCollection,
configModel: configModel,
stateDefinitionModel: {},
visDefinitionModel: this.visDefinitionModel,
widgetDefinitionsCollection: this.widgetDefinitionsCollection,
analysisDefinitionNodesCollection: {}
});
spyOn(this.view, '_renameLayer').and.callThrough();
this.view.$el.appendTo(document.body);
this.view.render();
});
afterEach(function () {
var parent = this.view.el.parentNode;
parent && parent.removeChild(this.view.el);
this.view.clean();
jasmine.Ajax.uninstall();
});
describe('_initialize', function () {
it('initial values used in .render have the proper value', function () {
expect(this.view._viewState.get('needsGeocoding')).toBe(false);
expect(this.view._viewState.get('isLayerEmpty')).toBe(false);
expect(this.view._viewState.get('queryGeometryHasGeom')).toBe(true);
});
it('should call _setViewState', function () {
expect(geometryModelFakePromise.hasBeenCalled()).toBe(true);
expect(canBeGeoreferencedPromise.hasBeenCalled()).toBe(true);
expect(isLayerEmptyPromise.hasBeenCalled()).toBe(true);
});
it('should subscribe to fetch all query objects and then call to fetchQueryRowsIfRequired', function (done) {
spyOn(this.view.model, 'fetchQueryRowsIfRequired');
// spyOn(this.view, '_setViewState');
var nodeDefModel = this.view.model.getAnalysisDefinitionNodeModel();
var querySchemaModel = nodeDefModel.querySchemaModel;
var queryRowsCollection = nodeDefModel.queryRowsCollection;
var queryGeometryModel = this.view._queryGeometryModel;
querySchemaModel.set({ ready: true, query: 'SELECT * FROM wadus' }, { silent: true });
queryGeometryModel.set({ ready: true, query: 'SELECT * FROM wadus' }, { silent: true });
queryGeometryModel.fetch();
querySchemaModel.fetch();
setTimeout(function () {
queryRowsCollection.trigger('inFinalStatus');
}, 0);
setTimeout(function () {
expect(this.view.model.fetchQueryRowsIfRequired).toHaveBeenCalled();
done();
}.bind(this), 0);
});
});
describe('_bindEvents', function () {
it('should hook up proper event handlers', function () {
spyOn(this.view, 'render');
spyOn(this.view, '_setViewState');
spyOn(this.view, '_onQueryRowsStatusChanged');
this.view._bindEvents();
this.view.model.trigger('change');
expect(this.view.render).toHaveBeenCalled();
this.view._queryGeometryModel.trigger('change:simple_geom');
expect(this.view._setViewState).toHaveBeenCalled();
this.view._queryRowsStatus.trigger('change:status');
expect(this.view._onQueryRowsStatusChanged).toHaveBeenCalled();
this.view.render.calls.reset();
this.view._viewState.trigger('change');
expect(this.view.render).toHaveBeenCalled();
});
it('should hook up model destroy event with _onDestroy only once', function () {
spyOn(this.view, '_onDestroy');
this.view._bindEvents();
this.view.model.trigger('destroy');
this.view.model.trigger('destroy');
expect(this.view._onDestroy.calls.count()).toBe(1);
});
});
describe('.render', function () {
it('should render properly depending on default values', function () {
this.view.render();
expect(_.size(this.view._subviews)).toBe(3); // [inlineEditor, toggleTooltip, toggleMenuTooltip]
});
describe('depending on _needsGeocoding value', function () {
beforeEach(function () {
this.view._queryGeometryHasGeom = false; // To avoid rendering `js-analyses`. We want to depend only on _needsGeocoding
});
it('should render proper markup if no geocoding needed', function () {
this.view._needsGeocoding = false;
this.view.render();
// We're checking against some markup that renders if needsGeocoding is false
expect(this.view.$('ul.js-analyses').length).toBe(1);
expect(this.view.$el.hasClass('is-empty')).toBe(false);
expect(this.view.$('.js-geocode').length).toBe(0);
});
it('should render proper markup if geocoding needed', function () {
this.view._viewState.set('needsGeocoding', true);
this.view._viewState.set('queryGeometryHasGeom', true);
this.view._viewState.set('brokenLayer', true);
this.view.render();
// We're checking against some markup that renders if needsGeocoding is true
// expect(this.view.$('ul.js-analyses').length).toBe(0);
expect(this.view.$el.hasClass('is-empty')).toBe(true);
expect(this.view.$('.js-geocode').length).toBe(1);
expect(this.view.$('.js-geocode').data('tipsy')).not.toBeUndefined();
});
});
describe('depending on _isLayerEmpty value', function () {
it('should render proper markup if the layer is empty', function () {
this.view._viewState.set('isLayerEmpty', true);
this.view.render();
expect(this.view.$('.js-emptylayer').length).toBe(1);
expect(this.view.$('.js-emptylayer').length).toBeGreaterThan(0); // Render warning icon
});
it('should render proper markup if the layer is not empty', function () {
this.view._viewState.set('isLayerEmpty', false);
this.view.render();
expect(this.view.$('.js-emptylayer').length).toBe(0);
});
});
describe('depending on _queryGeometryHasGeom value', function () {
beforeEach(function () {
this.view._viewState.set('needsGeocoding', true); // To avoid rendering `js-analyses`. We want to depend only on _queryGeometryHasGeom
});
it('should render proper markup if the layer has geometry', function () {
this.view._viewState.set('queryGeometryHasGeom', true);
this.view.render();
expect(this.view.$('ul.js-analyses').length).toBe(1);
expect(this.view.$('button.js-toggle').length).toBe(1);
});
it('should render proper markup if the layer has no geometry', function () {
this.view._viewState.set('queryGeometryHasGeom', false);
this.view.render();
expect(this.view.$('ul.js-analyses').length).toBe(0);
expect(this.view.$('button.js-toggle').length).toBe(0);
});
});
describe('depending on _isFetchingRows value', function () {
it('should render proper markup (a placeholder) if query rows is fetching', function () {
this.view._viewState.set('isFetchingRows', true);
this.view.render();
expect(this.view.$('.Editor-ListLayer-itemHeader > .Editor-ListLayer-media--placeholder').length).toBe(1);
expect(this.view.$('.js-thumbnail').length).toBe(0);
expect(this.view.$el.hasClass('Editor-ListLayer-item--fetching')).toBe(true);
});
it('should not render a placeholder if query rows is not fetching', function () {
this.view._viewState.set('isFetchingRows', false);
this.view.render();
expect(this.view.$('.Editor-ListLayer-itemHeader > .Editor-ListLayer-media--placeholder').length).toBe(0);
expect(this.view.$('.js-thumbnail').length).toBe(1);
expect(this.view.$el.hasClass('Editor-ListLayer-item--fetching')).toBe(false);
});
});
});
describe('when layer is source', function () {
var view;
beforeEach(function () {
this.model = new LayerDefinitionModel({
id: 'layer-A',
name: 'table_name',
letter: 'a',
source: 'a0',
visible: true
}, {
configModel: {},
collection: this.layerDefinitionsCollection
});
this.model.styleModel = new StyleDefinitionModel({}, {
configModel: {}
});
spyOn(this.model, 'canBeDeletedByUser');
spyOn(this.model, 'getNumberOfAnalyses').and.returnValue(3);
spyOn(this.model, 'getAnalysisDefinitionNodeModel').and.returnValue(this.a0);
spyOn(this.model, 'findAnalysisDefinitionNodeModel').and.returnValue(this.c2);
view = new DataLayerView({
modals: this.modals,
model: this.model,
userActions: this.userActions,
stackLayoutModel: this.stackLayoutModel,
newAnalysesView: this.newAnalysesViewSpy,
layerDefinitionsCollection: this.layerDefinitionsCollection,
configModel: {},
stateDefinitionModel: {},
visDefinitionModel: this.visDefinitionModel,
widgetDefinitionsCollection: this.widgetDefinitionsCollection,
analysisDefinitionNodesCollection: {}
});
view._queryGeometryHasGeom = function () {
return true;
};
view.$el.appendTo(document.body);
view.render();
});
afterEach(function () {
var parent = view.el.parentNode;
parent && parent.removeChild(view.el);
view.clean();
});
describe('.render', function () {
it('should render properly', function () {
expect(_.size(view._subviews)).toBe(4); // [inlineEditor, analysesViewTooltip, toggleTooltip, toggleMenuTooltip]
});
});
describe('when layer has an analysis', function () {
it('should render analysis views', function () {
expect(view.$el.text()).toContain('ANALYSES');
// Assert args being what's expected too
expect(this.newAnalysesViewSpy).toHaveBeenCalled();
expect(this.newAnalysesViewSpy.calls.argsFor(0)[0]).toEqual(view.$('.js-analyses'));
expect(this.newAnalysesViewSpy.calls.argsFor(0)[1]).toEqual(this.model);
});
});
});
describe('when layer is Torque', function () {
describe('.render', function () {
it('should render properly', function () {
this.view._isTorque = function () {
return true;
};
this.view.render();
expect(_.size(this.view._subviews)).toBe(4); // [inlineEditor, torqueTooltip, toggleTooltip, toggleMenuTooltip]
});
});
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title of layer', function () {
expect(this.view.$el.text()).toContain('table_name');
});
it('should add sortable class', function () {
expect(this.view.$el.hasClass('js-sortable-item')).toBeTruthy();
});
it('should be displayed as hidden when layer is hidden', function () {
expect(this.view.$('.js-thumbnail').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-title').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses-widgets-info').hasClass('is-hidden')).toBe(false);
this.model.set('visible', false);
expect(this.view.$('.js-thumbnail').hasClass('is-hidden')).toBe(true);
expect(this.view.$('.js-title').hasClass('is-hidden')).toBe(true);
expect(this.view.$('.js-analyses').hasClass('is-hidden')).toBe(true);
expect(this.view.$('.js-analyses-widgets-info').hasClass('is-hidden')).toBe(true);
});
it('should be with errors when layer has errors', function () {
expect(this.view.$('.js-error').length).toBe(0);
this.model.set('error', 'an error');
expect(this.view.$('.js-error').length).toBe(1);
});
it('should not add js-sortable-item class if layer is torque', function () {
this.model.set('type', 'torque');
this.view.render();
expect(this.view.$el.hasClass('js-sortable-item')).toBeFalsy();
expect(this.view.$el.hasClass('is-animated')).toBeTruthy();
});
describe('context menu', function () {
it('should toggle the context-menu box when option is clicked', function () {
expect(this.view.$el.find('.CDB-Box-modal').length).toBe(0);
this.view.$('.js-toggle-menu').click();
expect($('body').find('.CDB-Box-modal').css('display')).toEqual('block');
this.view.$('.js-toggle-menu').click();
expect($('body').find('.CDB-Box-modal').length).toBe(0);
});
describe('Delete layer…', function () {
it('should not be posible to delete a layer if canBeDeletedByUser return false', function () {
this.model.canBeDeletedByUser.and.returnValue(false);
this.view.$('.js-toggle-menu').click();
expect($('body').find(".CDB-Box-modal button[title='editor.layers.options.delete']").length).toEqual(0);
});
it('should be posible to delete a layer if canBeDeletedByUser', function () {
this.model.canBeDeletedByUser.and.returnValue(true);
this.view.$('.js-toggle-menu').click();
expect($('body').find(".CDB-Box-modal button[title='editor.layers.options.delete']").length).toEqual(1);
});
});
});
describe('when title is clicked', function () {
var originalSetTimeout = null;
beforeEach(function () {
spyOn(Router, 'goToStyleTab');
originalSetTimeout = setTimeout;
jasmine.clock().install();
this.view.$('.js-title').click();
jasmine.clock().tick(300);
});
it('should edit layer', function (done) {
var self = this;
canBeGeoreferencedPromise.resolve(false);
originalSetTimeout(function () {
expect(Router.goToStyleTab).toHaveBeenCalledWith(self.model.get('id'));
done();
}, 0);
});
afterEach(function () {
jasmine.clock().uninstall();
});
});
describe('when title is doubleclicked', function () {
beforeEach(function () {
jasmine.clock().install();
this.view.$('.js-title').click();
this.view.$('.js-title').click();
jasmine.clock().tick(300);
});
it('should show inline editor', function () {
expect(this.view.$('.js-input').is(':visible')).toBe(true);
});
afterEach(function () {
jasmine.clock().uninstall();
});
});
describe('when thumbnail is clicked', function () {
var originalSetTimeout = null;
beforeEach(function () {
spyOn(Router, 'goToStyleTab');
originalSetTimeout = setTimeout;
jasmine.clock().install();
this.view.$('.js-thumbnail').click();
jasmine.clock().tick(300);
});
afterEach(function () {
jasmine.clock().uninstall();
});
it('should edit layer', function (done) {
var self = this;
canBeGeoreferencedPromise.resolve(false);
originalSetTimeout(function () {
expect(Router.goToStyleTab).toHaveBeenCalledWith(self.model.get('id'));
done();
}, 0);
});
});
describe('when the toggle icon is clicked', function () {
beforeEach(function () {
this.model.set({autoStyle: 'foo'});
});
it('should toggle the layer', function () {
this.view.$('.js-toggle').click();
expect(this.model.get('visible')).toEqual(false);
expect(this.userActions.saveLayer).toHaveBeenCalled();
expect(this.userActions.saveLayer.calls.argsFor(0)[0]).toEqual(this.view.model);
expect(this.userActions.saveLayer.calls.argsFor(0)[1]).toEqual({
shouldPreserveAutoStyle: true
});
expect(this.model.get('autoStyle')).toEqual('foo');
this.userActions.saveLayer.calls.reset();
this.view.$('.js-toggle').click();
expect(this.model.get('visible')).toEqual(true);
expect(this.userActions.saveLayer).toHaveBeenCalled();
expect(this.userActions.saveLayer.calls.argsFor(0)[0]).toEqual(this.view.model);
expect(this.userActions.saveLayer.calls.argsFor(0)[1]).toEqual({
shouldPreserveAutoStyle: true
});
expect(this.model.get('autoStyle')).toEqual('foo');
});
});
describe('expanded/collapsed states', function () {
it('should expand and collapse the layer', function () {
expect(this.view.$('.js-analyses').hasClass('is-collapsed')).toBe(false);
// Show the context menu and collapse the layer
this.view.$('.js-toggle-menu').click();
$('body').find('.CDB-Box-modal button[title="editor.layers.options.collapse"]').click();
expect(this.view.$('.js-analyses').hasClass('is-collapsed')).toBe(true);
// Show the context menu and expand the layer
this.view.$('.js-toggle-menu').click();
$('body').find('.CDB-Box-modal button[title="editor.layers.options.expand"]').click();
// Show the context menu and expand the layer again
expect(this.view.$('.js-analyses').hasClass('is-collapsed')).toBe(false);
});
});
describe('rename layer', function () {
it('should rename the layer', function () {
// Show the context menu and collapse the layer
this.view.$('.js-toggle-menu').click();
$('body').find('.CDB-Box-modal button[title="editor.layers.options.rename"]').click();
expect(this.view.$('.js-input').is(':visible')).toBe(true);
this.view.$('.js-input').val('Foo');
var e = $.Event('keyup');
e.which = 13;
this.view.$('.js-input').trigger(e);
expect(this.model.get('table_name_alias')).toBe('Foo');
expect(this.userActions.saveLayer).toHaveBeenCalledWith(this.model);
});
});
describe('geometry type', function () {
it('should show polygon svg', function () {
expect(this.view.$('svg').length).toBe(1);
expect(this.view.$('svg circle').length).toBe(4);
});
});
describe('._showContextMenu', function () {
it('should show center map option if geom and clicking on it should call _centerMap', function () {
this.view._viewState.set('needsGeocoding', false);
this.view.render();
spyOn(this.view, '_centerMap');
this.view._showContextMenu();
expect(this.view._menuView.$('button[title^="editor.layers.options.center-map"]').length).toBe(1);
this.view._menuView.$('button[title^="editor.layers.options.center-map"]').click();
expect(this.view._centerMap).toHaveBeenCalled();
});
it('should not show center map option if no geom', function () {
this.view._viewState.set('needsGeocoding', true);
this.view.render();
this.view._showContextMenu();
expect(this.view._menuView.$('button[title^="editor.layers.options.center-map"]').length).toBe(0);
});
});
describe('._centerMap', function () {
it('should call to zoomToData with query from queryGeometryModel', function () {
var theView = this.view;
this.queryGeometryModel.set('simple_geom', '');
// The only way we have to know if zoomToData has been called is to provoke an
// error. This way we know that the query has been extracted from our querySchemaModel
// and that zoomToData has been called.
expect(function () {
theView._centerMap();
}).toThrowError('query is required');
this.queryGeometryModel.set('simple_geom', 'polygon');
});
});
describe('._isTorque', function () {
it('should return if model is Torque Layer', function () {
spyOn(this.view.model, 'isTorqueLayer');
this.view._isTorque();
expect(this.view.model.isTorqueLayer).toHaveBeenCalled();
});
});
describe('._setViewState', function () {
var initialHasGeomValue;
var initialGeocodingValue;
var initialLayerEmptyValue;
beforeEach(function () {
initialGeocodingValue = this.view._viewState.get('needsGeocoding');
initialHasGeomValue = this.view._viewState.get('queryGeometryHasGeom');
initialLayerEmptyValue = this.view._viewState.get('isLayerEmpty');
});
function resolvePromises (hasGeomValue, geocodingValue, layerEmptyValue) {
geometryModelFakePromise.resolve(hasGeomValue);
canBeGeoreferencedPromise.resolve(geocodingValue);
isLayerEmptyPromise.resolve(layerEmptyValue);
}
it('shoud update needsGeocoding', function (done) {
resolvePromises(initialHasGeomValue, !initialGeocodingValue, initialLayerEmptyValue);
setTimeout(function () {
expect(this.view._viewState.get('needsGeocoding')).toEqual(!initialGeocodingValue);
done();
}.bind(this), 0);
});
it('shoud update isLayerEmpty', function (done) {
resolvePromises(initialHasGeomValue, initialGeocodingValue, !initialLayerEmptyValue);
setTimeout(function () {
expect(this.view._viewState.get('isLayerEmpty')).toEqual(!initialLayerEmptyValue);
done();
}.bind(this), 0);
});
it('should update queryGeometryHasGeom', function (done) {
resolvePromises(!initialHasGeomValue, !initialGeocodingValue, initialLayerEmptyValue);
setTimeout(function () {
expect(this.view._viewState.get('queryGeometryHasGeom')).toEqual(!initialHasGeomValue);
done();
}.bind(this), 0);
});
it('should update isFetchingRows', function (done) {
this.view._viewState.set('isFetchingRows', false, { silent: true });
spyOn(this.view, '_isFetchingRows').and.returnValue(true);
resolvePromises(initialHasGeomValue, initialGeocodingValue, initialLayerEmptyValue);
setTimeout(function () {
expect(this.view._viewState.get('isFetchingRows')).toBe(true);
done();
}.bind(this), 0);
});
});
describe('_isFetchingRows', function () {
it('should return query rows status of the current analysisDefinitionModel', function () {
var node = this.view.model.getAnalysisDefinitionNodeModel();
var rowsStatus = node.queryRowsCollection.statusModel;
rowsStatus.set('status', 'fetching');
var isFetching = this.view._isFetchingRows();
expect(isFetching).toBe(true);
rowsStatus.set('status', 'fetched');
isFetching = this.view._isFetchingRows();
expect(isFetching).toBe(false);
});
});
describe('_onQueryRowsStatusChanged', function () {
it('shoudl set viewState to isFetchingRows and call setViewState', function () {
this.view._viewState.set('isFetchingRows', false, { silent: true });
spyOn(this.view, '_isFetchingRows').and.returnValue(true);
spyOn(this.view, '_setViewState');
this.view._onQueryRowsStatusChanged();
expect(this.view._viewState.get('isFetchingRows')).toBe(true);
expect(this.view._setViewState).toHaveBeenCalled();
});
});
});
@@ -0,0 +1,160 @@
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var UserActions = require('builder/data/user-actions');
var StackLayoutModel = require('builder/components/stack-layout/stack-layout-model');
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var ErroredDataLayerView = require('builder/editor/layers/layer-views/errored-data-layer-view');
var StyleDefinitionModel = require('builder/editor/style/style-definition-model');
var QueryRowsCollection = require('builder/data/query-rows-collection');
var QuerySchemaModel = require('builder/data/query-schema-model');
var QueryGeometryModel = require('builder/data/query-geometry-model');
var FactoryModals = require('../../../factories/modals');
describe('editor/layers/errored-data-layer-view', function () {
beforeEach(function () {
spyOn(QueryGeometryModel.prototype, 'fetch');
spyOn(QuerySchemaModel.prototype, 'fetch');
spyOn(QueryRowsCollection.prototype, 'fetch');
this.layerDefinitionsCollection = new Backbone.Collection([]);
this.layerDefinitionsCollection.save = jasmine.createSpy('save');
this.modals = FactoryModals.createModalService();
spyOn(this.modals, 'create');
this.model = new LayerDefinitionModel({
id: 'layer-A',
name: 'table_name',
letter: 'a',
source: 'a0',
visible: true
}, {
configModel: {},
collection: this.layerDefinitionsCollection
});
this.model.styleModel = new StyleDefinitionModel({}, {
configModel: {}
});
spyOn(this.model, 'canBeDeletedByUser');
spyOn(this.model, 'getNumberOfAnalyses').and.returnValue(3);
spyOn(this.model, 'getAnalysisDefinitionNodeModel').and.returnValue(null);
spyOn(this.model, 'findAnalysisDefinitionNodeModel').and.returnValue(null);
this.stackLayoutModel = new StackLayoutModel(null, {stackLayoutItems: {}});
spyOn(this.stackLayoutModel, 'nextStep');
this.newAnalysesViewSpy = jasmine.createSpy('newAnalysesView').and.callFake(function (el) {
var view = new CoreView({
el: el
});
view.render = function () {
this.$el.html('ANALYSES');
return this;
};
return view;
});
this.userActions = UserActions({
userModel: {},
analysisDefinitionsCollection: {},
analysisDefinitionNodesCollection: {},
layerDefinitionsCollection: {},
widgetDefinitionsCollection: {}
});
this.promise = $.Deferred();
spyOn(this.userActions, 'saveLayer').and.returnValue(this.promise);
var widgetDefinitionsCollection = new Backbone.Collection();
widgetDefinitionsCollection.widgetsOwnedByLayer = function () { return 0; };
var visDefinitionModel = new Backbone.Model();
this.view = new ErroredDataLayerView({
modals: this.modals,
model: this.model,
userActions: this.userActions,
stackLayoutModel: this.stackLayoutModel,
newAnalysesView: this.newAnalysesViewSpy,
layerDefinitionsCollection: this.layerDefinitionsCollection,
configModel: {},
stateDefinitionModel: {},
visDefinitionModel: visDefinitionModel,
widgetDefinitionsCollection: widgetDefinitionsCollection,
analysisDefinitionNodesCollection: {}
});
spyOn(this.view, '_renameLayer').and.callThrough();
this.view.$el.appendTo(document.body);
this.view.render();
});
afterEach(function () {
var parent = this.view.el.parentNode;
parent && parent.removeChild(this.view.el);
this.view.clean();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title of layer', function () {
expect(this.view.$el.text()).toContain('table_name');
});
it('should not add sortable class', function () {
expect(this.view.$el.hasClass('js-sortable-item')).toBeFalsy();
});
it('should be displayed always', function () {
expect(this.view.$('.js-thumbnail').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-title').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses-widgets-info').hasClass('is-hidden')).toBe(false);
this.model.set('visible', false);
expect(this.view.$('.js-thumbnail').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-title').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses').hasClass('is-hidden')).toBe(false);
expect(this.view.$('.js-analyses-widgets-info').hasClass('is-hidden')).toBe(false);
});
it('should contain is-errored class', function () {
expect(this.view.$el.hasClass('is-errored')).toBeTruthy();
});
it('should render the broken node', function () {
expect(this.view.$('.js-analyses > li').size()).toBe(1);
expect(this.view.$('.Editor-ListAnalysis-itemInfo .CDB-Text').text()).toContain('a0');
expect(this.view.$('.Editor-ListAnalysis-title').text()).toContain('editor.layers.errors.broken-node');
});
describe('context menu', function () {
it('should toggle the context-menu box when option is clicked', function () {
expect(this.view.$el.find('.CDB-Box-modal').length).toBe(0);
this.view.$('.js-toggle-menu').click();
expect($('body').find('.CDB-Box-modal').css('display')).toEqual('block');
this.view.$('.js-toggle-menu').click();
expect($('body').find('.CDB-Box-modal').length).toBe(0);
});
describe('Delete layer…', function () {
it('should be posible to delete the layer', function () {
this.view.$('.js-toggle-menu').click();
expect($('body').find(".CDB-Box-modal button[title='editor.layers.options.delete-and-reload']").length).toEqual(1);
});
it('should reload the map when layer is removed', function () {
this.view.$('.js-toggle-menu').click();
spyOn(this.view, '_reloadApplication');
$('body').find(".CDB-Box-modal button[title='editor.layers.options.delete-and-reload']").click();
this.model.trigger('destroy', this.model);
expect(this.view._reloadApplication).toHaveBeenCalled();
});
});
});
});
@@ -0,0 +1,33 @@
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var LabelsLayerView = require('builder/editor/layers/layer-views/labels-layer-view');
describe('editor/layers/layer-views/labels-layer-view', function () {
beforeEach(function () {
this.model = new LayerDefinitionModel({
name: 'thename',
urlTemplate: 'http://{s}.example.com/labels/{z}/{x}/{y}.png'
}, {
configModel: {}
});
this.view = new LabelsLayerView({
model: this.model,
stackLayoutModel: {}
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title and description of layer', function () {
expect(this.view.$el.text()).toContain('thename');
expect(this.view.$el.text()).toContain('editor.layers.labels.title-label');
});
it('should render thumbnail image', function () {
expect(this.view.$('img').attr('src')).toEqual('http://a.example.com/labels/6/30/24.png');
});
});
@@ -0,0 +1,33 @@
var LayerDefinitionModel = require('builder/data/layer-definition-model');
var PlainColorLayerView = require('builder/editor/layers/layer-views/plain-color-layer-view');
describe('editor/layers/layer-views/plain-color-layer-view', function () {
beforeEach(function () {
this.model = new LayerDefinitionModel({
name: 'thename',
color: '#FFFFFF'
}, {
configModel: {}
});
this.view = new PlainColorLayerView({
model: this.model,
stackLayoutModel: {}
});
this.view.render();
});
it('should have no leaks', function () {
expect(this.view).toHaveNoLeaks();
});
it('should render title and description of layer', function () {
expect(this.view.$el.text()).toContain('#FFFFFF');
expect(this.view.$el.text()).toContain('editor.layers.color.title-label');
});
it('should render thumbnail image', function () {
expect(this.view.$('.js-thumbnail').css('background-color')).toEqual('rgb(255, 255, 255)');
});
});

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