var Backbone = require('backbone'); var _ = require('underscore'); var MetricsTracker = require('builder/components/metrics/metrics-tracker'); var UserActions = require('builder/data/user-actions'); var StyleContentView = require('builder/editor/style/style-content-view'); var StyleDefinitionModel = require('builder/editor/style/style-definition-model'); var QueryGeometryModel = require('builder/data/query-geometry-model'); var QuerySchemaModel = require('builder/data/query-schema-model'); var LayerContentModel = require('builder/data/layer-content-model'); var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection'); var ConfigModel = require('builder/data/config-model'); var UserModel = require('builder/data/user-model'); var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection'); var LayerDefinitionModel = require('builder/data/layer-definition-model'); var QueryRowsCollection = require('builder/data/query-rows-collection'); var FactoryModals = require('../../factories/modals'); var styleSQLErrorTemplate = require('builder/editor/style/style-content-sql-error.tpl'); var actionErrorTemplate = require('builder/editor/layers/sql-error-action.tpl'); var fakePromise = require('fixtures/builder/fake-promise.fixture.js'); describe('editor/style/style-content-view', function () { beforeEach(function () { this.configModel = new ConfigModel({ base_url: '/u/pepe' }); this.userModel = new UserModel({}, { configModel: this.configModel }); spyOn(this.userModel, 'featureEnabled').and.returnValue(true); this.overlayModel = new Backbone.Model({ visible: false }); this.model = new StyleDefinitionModel({ autogenerated: true }); spyOn(MetricsTracker, 'track'); var attrs = { query: 'SELECT * FROM table', state: 'unfetched', ready: true }; var opts = { configModel: this.configModel }; this.querySchemaModel = new QuerySchemaModel(attrs, opts); this.queryGeometryModel = new QueryGeometryModel(attrs, opts); 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.layerDefinitionsCollection = new LayerDefinitionsCollection(null, { configModel: this.configModel, userModel: this.userModel, analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection, mapId: 'm123', stateDefinitionModel: {} }); this.layerDefinitionsCollection.isThereAnyTorqueLayer = function () {}; 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.layerDefinitionsCollection }); this.layerDefinitionModel.styleModel = this.model; this.xhrSpy = jasmine.createSpyObj('xhr', ['abort', 'always', 'fail']); spyOn(Backbone.Model.prototype, 'sync').and.returnValue(this.xhrSpy); spyOn(this.layerDefinitionModel, 'save').and.callThrough(); spyOn(this.layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(this.a0); spyOn(this.querySchemaModel, 'fetch'); spyOn(this.queryGeometryModel, 'fetch'); spyOn(StyleContentView.prototype, 'render').and.callThrough(); this.userActions = UserActions({ userModel: this.userModel, analysisDefinitionsCollection: {}, analysisDefinitionNodesCollection: {}, layerDefinitionsCollection: {}, widgetDefinitionsCollection: {} }); this.queryRowsCollection = new QueryRowsCollection([], { configModel: this.configModel, querySchemaModel: this.querySchemaModel }); spyOn(this.queryRowsCollection, 'fetch'); 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 StyleContentView({ configModel: this.configModel, userModel: this.userModel, userActions: this.userActions, layerDefinitionsCollection: this.layerDefinitionsCollection, layerDefinitionModel: this.layerDefinitionModel, queryGeometryModel: this.queryGeometryModel, querySchemaModel: this.querySchemaModel, queryRowsCollection: this.queryRowsCollection, modals: FactoryModals.createModalService(), styleModel: this.model, overlayModel: this.overlayModel, editorModel: new Backbone.Model(), freezeTorgeAggregation: jasmine.createSpy('freezeTorgeAggregation'), layerContentModel: this.layerContentModel }); this.view.render(); }); describe('initialize', function () { it('should have called to _buildAggregationCarouselCollection to create _carouselCollection and hook up events', function () { expect(this.view._carouselCollection).toBeDefined(); }); }); describe('.render', function () { it('should render properly', function () { spyOn(this.view, '_initViews'); spyOn(this.view, '_toggleOverlay'); this.view.render(); expect(this.view._initViews).toHaveBeenCalled(); expect(this.view._toggleOverlay).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('when layer content model has errors', function () { describe('.render', function () { it('should render properly', function () { spyOn(this.view, '_isErrored').and.returnValue(true); spyOn(this.view, '_toggleOverlay'); spyOn(this.view, '_renderError'); this.view.render(); expect(this.view._renderError).toHaveBeenCalled(); expect(this.view._toggleOverlay).toHaveBeenCalled(); }); }); }); describe('when data is filtered', function () { describe('.render', function () { it('should render properly', function () { spyOn(this.view, '_renderFilteredData'); this.view._viewState.set('isDataFiltered', true); this.view.render(); expect(this.view._renderFilteredData).toHaveBeenCalled(); }); }); }); describe('._initViews', function () { it('should render the form and the carousel', function () { this.view._queryGeometryModel.set({ simple_geom: 'point' }); this.view.render(); expect(this.view.$('.Carousel').length).toBe(1); expect(this.view.$('.Editor-HeaderInfo').length).toBe(2); expect(this.view.$el.children().first().hasClass('js-aggregationTypes')).toBe(true); expect(_.size(this.view._subviews)).toBe(2); // [Carousel, Form] }); it('should not render carousel view when geometry is not point', function () { expect(this.view.$('.Carousel').length).toBe(0); expect(this.view.$('.Editor-HeaderInfo').length).toBe(1); expect(_.size(this.view._subviews)).toBe(1); // [Form] }); 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('._initBinds', function () { it('should call ._onAutoStyleChanged when _layerDefinitionModel:autoStyle changes', function () { spyOn(this.view, '_onAutoStyleChanged'); this.view._initBinds(); this.view._layerDefinitionModel.set({ autoStyle: true }); expect(this.view._onAutoStyleChanged).toHaveBeenCalled(); }); it('should call .render when layerContentModel:state changes', function () { this.view._initBinds(); this.view._layerContentModel.trigger('change:state'); expect(this.view.render).toHaveBeenCalled(); }); it('should call .render when _styleModel:render undo', function () { this.view._initBinds(); this.view._styleModel.trigger('undo'); expect(this.view.render).toHaveBeenCalled(); }); it('should call .render when _styleModel:render redo', function () { this.view._initBinds(); this.view._styleModel.trigger('redo'); expect(this.view.render).toHaveBeenCalled(); }); it('should call ._onStyleChanged when _layerDefinitionModel changes', function () { spyOn(this.view, '_onStyleChanged'); this.view._initBinds(); this.view._layerDefinitionModel.set({ test: true }); expect(this.view._onStyleChanged).toHaveBeenCalled(); }); it('should call ._checkEditorModel when _layerContentModel:state changes', function () { spyOn(this.view, '_checkEditorModel'); this.view._initBinds(); this.view._layerContentModel.trigger('change:state'); expect(this.view._checkEditorModel).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(); }); it('should call _setViewValues if layerContentModel:state changes', function () { spyOn(this.view, '_setViewValues'); this.view._initBinds(); this.view._layerContentModel.trigger('change:state'); expect(this.view._setViewValues).toHaveBeenCalled(); }); it('should call _checkEditorModel if layerContentModel:state changes', function () { spyOn(this.view, '_checkEditorModel'); this.view._initBinds(); this.view._layerContentModel.trigger('change:state'); expect(this.view._checkEditorModel).toHaveBeenCalled(); }); it('should call render if _queryGeometryModel:status changes', function () { this.view._initBinds(); this.view._queryGeometryModel.trigger('change:status'); expect(this.view.render).toHaveBeenCalled(); }); }); describe('._initViewState', function () { it('should initialize _viewState model', function () { expect(this.view._viewState).toBeDefined(); expect(this.view._viewState.get('isDataFiltered')).toEqual(false); }); it('should call _setViewValues', function () { spyOn(this.view, '_setViewValues'); this.view._initViewState(); expect(this.view._setViewValues).toHaveBeenCalled(); }); }); describe('._setViewValues', function () { it('should update _viewState', function (done) { spyOn(this.view._layerDefinitionModel, 'isDataFiltered').and.returnValue(Promise.resolve(true)); this.view._viewState.set('isDataFiltered', false, { silent: true }); this.view._setViewValues(); setTimeout(function () { expect(this.view._viewState.get('isDataFiltered')).toEqual(true); done(); }.bind(this), 0); }); }); describe('._buildAggregationCarouselCollection', function () { it('should create _carouselCollection and listen to change:selected event', function () { spyOn(this.view, '_onSelectAggregation'); var carouselCollection = this.view._buildAggregationCarouselCollection(); expect(carouselCollection).toBeDefined(); this.view._carouselCollection.trigger('change:selected'); expect(this.view._onSelectAggregation).toHaveBeenCalled(); }); }); describe('._renderCarousel', function () { it('should append carousel markup', function () { this.view.$el.html(''); spyOn(this.view, '_buildAggregationCarouselCollection').and.callThrough(); this.view._renderCarousel(); expect(this.view.$('ul.Carousel-list > li.Carousel-item').length).toBe(2); expect(this.view._buildAggregationCarouselCollection).toHaveBeenCalled(); }); }); describe('._toggleOverlay', function () { it('should toggle overlay', function () { expect(this.view.$el.hasClass('is-disabled')).toBe(false); this.view._overlayModel.set('visible', true, { silent: true }); this.view._toggleOverlay(); expect(this.view.$el.hasClass('is-disabled')).toBe(true); }); }); describe('._renderError', function () { it('should render the correct template', function () { var expectedMarkup = styleSQLErrorTemplate({ body: _t('editor.error-query.body', { action: actionErrorTemplate({ label: _t('editor.error-query.label') }) }) }); this.view.$el.empty(); this.view._renderError(); expect(this.view.$el.html()).toContain(expectedMarkup); }); }); describe('._renderFilteredData', function () { it('should render the correct template', function () { this.view._renderFilteredData(); expect(this.view.$el.html()).toContain('editor.layers.warnings.no-data.message'); expect(this.view.$el.html()).toContain('editor.layers.warnings.no-data.action-message'); }); }); describe('on style changed', function () { beforeEach(function () { spyOn(this.model, 'removeStylesPreAutoStyle'); this.layerDefinitionModel.save.calls.reset(); // Fake styleModel change var fillAttrs = _.clone(this.model.get('fill')); fillAttrs.size = 34; this.model.set('fill', fillAttrs); }); it('should remove autogenerated attribute', function () { expect(this.model.get('autogenerated')).toBeFalsy(); }); it('should remove styles pre autostyle if autostyle', function () { this.layerDefinitionModel.set({autoStyle: true}, {silent: true}); this.model._stylesPreAutoStyle = true; var fillAttrs = _.clone(this.model.get('fill')); fillAttrs.size = 12; this.model.set('fill', fillAttrs); expect(this.model.get('autogenerated')).toBeFalsy(); expect(this.model.removeStylesPreAutoStyle).toHaveBeenCalled(); }); it('should not change cartocss_custom property when styleModel changes', function () { this.layerDefinitionModel.set('cartocss_custom', true); var fillAttrs = _.clone(this.model.get('fill')); fillAttrs.size = 12; this.model.set('fill', fillAttrs); expect(this.layerDefinitionModel.get('cartocss_custom')).toBe(true); }); it('should save the layer', function () { expect(this.layerDefinitionModel.save.calls.count()).toBe(1); }); }); describe('autoStyle', function () { it('render on autostyle changes', function () { this.layerDefinitionModel.set({autoStyle: false}); expect(StyleContentView.prototype.render).toHaveBeenCalled(); }); }); describe('_checkEditorModel', function () { var hasGeomPromise = null; beforeEach(function () { hasGeomPromise = fakePromise(this.view._queryGeometryModel, 'hasValueAsync'); }); afterEach(function () { hasGeomPromise = null; }); it('set _editorModel to disabled if _isErrored when the hasGeom promise resolves', function (done) { var self = this; spyOn(this.view, '_isErrored').and.returnValue(true); this.view._editorModel.set({ disabled: false }, { silent: true }); this.view._checkEditorModel(); hasGeomPromise.resolve(true); setTimeout(function () { expect(self.view._editorModel.get('disabled')).toBe(true); done(); }, 0); }); it('set _editorModel to disabled if hasGeom resolve with false', function (done) { var self = this; spyOn(this.view, '_isErrored').and.returnValue(false); this.view._editorModel.set({ disabled: false }, { silent: true }); this.view._checkEditorModel(); hasGeomPromise.resolve(false); setTimeout(function () { expect(self.view._editorModel.get('disabled')).toBe(true); done(); }, 0); }); it('set _editorModel to enabled if hasGeom resolve with true and not _isErrored', function (done) { var self = this; spyOn(this.view, '_isErrored').and.returnValue(false); this.view._editorModel.set({ disabled: true }, { silent: true }); this.view._checkEditorModel(); hasGeomPromise.resolve(true); setTimeout(function () { expect(self.view._editorModel.get('disabled')).toBe(false); done(); }, 0); }); }); it('should not have leaks', function () { expect(this.view).toHaveNoLeaks(); }); });