text: var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var StyleModel = require('builder/editor/style/style-definition-model');
  var QueryGeometryModel = require('builder/data/query-geometry-model');
  var QuerySchemaModel = require('builder/data/query-schema-model');
  var CartoCSSModel = require('builder/editor/style/style-cartocss-model');
  var LayerDefinitionsCollection = require('builder/data/layer-definitions-collection');
  var LayerContentModel = require('builder/data/layer-content-model');
  var StyleView = require('builder/editor/style/style-view');
  var UserActions = require('builder/data/user-actions');
  var MetricsTracker = require('builder/components/metrics/metrics-tracker');
  var UserNotifications = require('builder/data/user-notifications');
  var ConfigModel = require('builder/data/config-model');
  var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection');
  var LayerDefinitionModel = require('builder/data/layer-definition-model');
  var UserModel = require('builder/data/user-model');
  var QueryRowsCollection = require('builder/data/query-rows-collection');
  var FactoryModals = require('../../factories/modals');
  var fakePromise = require('fixtures/builder/fake-promise.fixture.js');
  
  var ONBOARDING_KEY = 'layer-style-onboarding';
  
  describe('editor/style/style-view', function () {
    beforeEach(function () {
      jasmine.Ajax.install();
      jasmine.Ajax.stubRequest(new RegExp('.*api/v2/sql.*'))
        .andReturn({ status: 200 });
  
      this.configModel = new ConfigModel({
        sql_api_template: 'http://{user}.localhost.lan:8080',
        user_name: 'pepito'
      });
  
      this.userModel = new UserModel({}, {
        configModel: this.configModel
      });
      spyOn(this.userModel, 'featureEnabled').and.returnValue(true);
  
      this.styleModel = new StyleModel({}, { parse: true });
      this.cartocssModel = new CartoCSSModel();
  
      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.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.styleModel;
      this.layerDefinitionModel.cartocssModel = this.cartocssModel;
  
      spyOn(this.layerDefinitionModel, 'getAnalysisDefinitionNodeModel').and.returnValue(this.a0);
      spyOn(this.layerDefinitionModel, 'getTableName').and.returnValue('table');
  
      spyOn(MetricsTracker, 'track');
  
      this.queryGeometryModel = new QueryGeometryModel({
        query: 'SELECT * FROM table',
        status: 'fetched'
      }, {
        configModel: this.configModel
      });
      this.queryGeometryModel.set('simple_geom', 'point');
  
      this.querySchemaModel = new QuerySchemaModel({
        query: 'SELECT * FROM table',
        status: 'fetched'
      }, {
        configModel: this.configModel
      });
  
      this.editorModel = new Backbone.Model();
      this.editorModel.isEditing = function () { return false; };
      this.editorModel.isDisabled = function () { return false; };
  
      this.modals = FactoryModals.createModalService();
  
      this.userActions = UserActions({
        userModel: this.userModel,
        analysisDefinitionsCollection: {},
        analysisDefinitionNodesCollection: {},
        layerDefinitionsCollection: {},
        widgetDefinitionsCollection: {}
      });
  
      this.promise = $.Deferred();
      spyOn(this.userActions, 'saveLayer').and.returnValue(this.promise);
      spyOn(StyleView.prototype, '_infoboxState').and.callThrough();
  
      var onboardingNotification = new UserNotifications({}, {
        key: 'builder',
        configModel: this.configModel
      });
  
      this.queryRowsCollection = new QueryRowsCollection([], {
        configModel: this.configModel,
        querySchemaModel: this.querySchemaModel
      });
  
      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 StyleView({
        layerDefinitionsCollection: this.layerDefinitionsCollection,
        layerDefinitionModel: this.layerDefinitionModel,
        queryGeometryModel: this.queryGeometryModel,
        querySchemaModel: this.querySchemaModel,
        queryRowsCollection: this.a0.queryRowsCollection,
        modals: this.modals,
        editorModel: this.editorModel,
        userActions: this.userActions,
        configModel: this.configModel,
        userModel: this.userModel,
        onboardings: {},
        onboardingNotification: onboardingNotification,
        layerContentModel: this.layerContentModel
      });
  
      this.view.render();
    });
  
    afterEach(function () {
      jasmine.Ajax.uninstall();
  
      var dialogs = document.querySelectorAll('body > [data-dialog]');
      [].slice.call(dialogs).forEach(function (childNode) {
        document.body.removeChild(childNode);
      });
    });
  
    describe('.render', function () {
      it('should render properly', function () {
        expect(_.size(this.view._subviews)).toBe(1);
      });
    });
  
    it('should create internal codemirror model', function () {
      expect(this.view._codemirrorModel).toBeDefined();
    });
  
    it('should create _onboardingNotification', function () {
      expect(this.view._onboardingNotification).not.toBe(null);
    });
  
    it('render should call _launchOnboarding', function () {
      spyOn(this.view, '_launchOnboarding');
  
      this.view.render();
  
      expect(this.view._launchOnboarding).toHaveBeenCalled();
    });
  
    it('should show infobox if layer is hidden', function () {
      this.layerDefinitionModel.set('visible', false);
  
      expect(this.view.$('.Infobox').length).toBe(1);
      expect(this.view.$('.Infobox').html()).toContain('editor.messages.layer-hidden');
  
      this.view._codemirrorModel.set('content', 'foo');
      this.editorModel.set('edition', true);
  
      // Infobox is also visible in forms
      expect(this.view.$('.Infobox').length).toBe(1);
  
      this.editorModel.set('edition', false);
      expect(this.view.$('.Infobox').length).toBe(1);
      expect(this.view.$('.Infobox').html()).toContain('editor.messages.layer-hidden');
  
      this.layerDefinitionModel.set('visible', true);
      expect(this.view.$('.Infobox').length).toBe(0);
      expect(this.view.$('.Infobox').html()).not.toContain('editor.messages.layer-hidden');
    });
  
    it('should append existing Maps API errors to codemirror model when view is initialized', function () {
      this.layerDefinitionModel.set('error', {
        type: 'layer',
        subtype: 'turbo-carto',
        line: 99,
        message: 'something went wrong'
      }); // setting this silently
  
      this.view = new StyleView({
        layerDefinitionsCollection: this.layerDefinitionsCollection,
        layerDefinitionModel: this.layerDefinitionModel,
        queryGeometryModel: this.queryGeometryModel,
        querySchemaModel: this.querySchemaModel,
        queryRowsCollection: this.queryRowsCollection,
        modals: this.modals,
        editorModel: this.editorModel,
        userActions: this.userActions,
        configModel: {},
        userModel: {
          featureEnabled: function () { return true; }
        },
        onboardings: {},
        onboardingNotification: {},
        layerContentModel: this.layerContentModel
      });
  
      expect(this.view._codemirrorModel.get('errors')).toEqual([{
        line: 99,
        message: 'something went wrong'
      }]);
    });
  
    describe('.initBinds', function () {
      it('should call ._appendMapsAPIError when layerDefinitionModel:error changes', function () {
        spyOn(this.view, '_appendMapsAPIError');
  
        this.view._initBinds();
        this.view._layerDefinitionModel.trigger('change:error');
  
        expect(this.view._appendMapsAPIError).toHaveBeenCalled();
      });
  
      it('should call ._onCartocssChanged when layerDefinitionModel:cartocss changes', function () {
        spyOn(this.view, '_onCartocssChanged');
  
        this.view._initBinds();
        this.view._layerDefinitionModel.trigger('change:cartocss');
  
        expect(this.view._onCartocssChanged).toHaveBeenCalled();
      });
  
      it('should call ._infoboxState when layerDefinitionModel:visible changes', function () {
        this.view._initBinds();
        this.view._layerDefinitionModel.trigger('change:visible');
  
        expect(this.view._infoboxState).toHaveBeenCalled();
      });
  
      it('should call ._infoboxState when layerDefinitionModel:autoStyle changes', function () {
        this.view._initBinds();
        this.view._layerDefinitionModel.set({ autoStyle: 'test' });
  
        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 ._onUndoRedo when _cartocssModel:undo', function () {
        spyOn(this.view, '_onUndoRedo');
  
        this.view._initBinds();
        this.view._cartocssModel.trigger('undo');
  
        expect(this.view._onUndoRedo).toHaveBeenCalled();
      });
  
      it('should call ._onUndoRedo when _cartocssModel:redo', function () {
        spyOn(this.view, '_onUndoRedo');
  
        this.view._initBinds();
        this.view._cartocssModel.trigger('redo');
  
        expect(this.view._onUndoRedo).toHaveBeenCalled();
      });
  
      it('should call ._updateEditor when _querySchemaModel:redo', function () {
        spyOn(this.view, '_updateEditor');
  
        this.view._initBinds();
        this.view._querySchemaModel.set({ query_errors: 'Some errors' });
  
        expect(this.view._updateEditor).toHaveBeenCalled();
      });
    });
  
    describe('._onUndoRedo', function () {
      it('should set codemirror value when style model changes', function () {
        var newContent = 'some content';
        this.view._cartocssModel.set({ content: newContent }, { silent: true });
  
        expect(this.view._codemirrorModel.get('content')).not.toEqual(newContent);
  
        this.view._onUndoRedo();
  
        expect(this.view._codemirrorModel.get('content')).toEqual(newContent);
      });
    });
  
    describe('._onCartocssChanged', function () {
      it('should set codemirror value when style model changes', function () {
        var newCartocss = 'some cartocss';
        this.layerDefinitionModel.set({ cartocss: newCartocss }, { silent: true });
  
        expect(this.view._codemirrorModel.get('content')).not.toEqual(newCartocss);
  
        this.view._onCartocssChanged();
  
        expect(this.view._codemirrorModel.get('content')).toEqual(newCartocss);
      });
    });
  
    describe('._appendMapsAPIError', function () {
      it('should append Maps API errors to existing codemirror errors', function () {
        this.view._codemirrorModel.set('errors', [
          { line: 0, message: 'error 1' }
        ]);
  
        this.layerDefinitionModel.set('error', {
          type: 'layer',
          subtype: 'turbo-carto',
          line: 1,
          message: 'error 2'
        });
  
        expect(this.view._codemirrorModel.get('errors')).toEqual([
          { line: 0, message: 'error 1' },
          { line: 1, message: 'error 2' }
        ]);
      });
    });
  
    it('should save cartocss', function () {
      var content = '#layer {marker-fill:red}';
      spyOn(this.layerDefinitionModel, 'save');
      this.view._codemirrorModel.set('content', content);
  
      this.view._saveCartoCSS(this.view._onSaveComplete);
      expect(this.view._cartocssModel.get('content')).toBe(content);
      expect(this.layerDefinitionModel.get('autoStyle')).toBeFalsy();
      expect(this.layerDefinitionModel.save).toHaveBeenCalledWith({
        cartocss_custom: true,
        cartocss: content
      }, { complete: this.view._onSaveComplete });
    });
  
    it('should set loading to false when save is complete', function () {
      this.view._applyButtonStatusModel.set('loading', true);
  
      this.view._onSaveComplete();
  
      expect(this.view._applyButtonStatusModel.get('loading')).toBe(false);
    });
  
    it('should not save cartocss if content is empty', function () {
      var content = '';
      spyOn(this.layerDefinitionModel, 'save');
      this.view._codemirrorModel.set('content', '');
      this.view._saveCartoCSS();
      expect(this.layerDefinitionModel.get('cartocss_custom')).toBeFalsy();
      expect(this.layerDefinitionModel.get('cartocss')).not.toBe(content);
      expect(this.layerDefinitionModel.save).not.toHaveBeenCalled();
    });
  
    it('should set loading to false when cartoCSS validation fails', function () {
      spyOn(this.layerDefinitionModel, 'save');
      this.view._codemirrorModel.set('content', '#layer {');
      this.view._saveCartoCSS();
      expect(this.layerDefinitionModel.save).not.toHaveBeenCalled();
      expect(this.view._applyButtonStatusModel.get('loading')).toBe(false);
    });
  
    describe('._launchOnboarding', function () {
      var georeferencePromise = null;
      var hasGeomPromise = null;
  
      beforeEach(function () {
        georeferencePromise = fakePromise(this.view._layerDefinitionModel, 'canBeGeoreferenced');
        hasGeomPromise = fakePromise(this.view._queryGeometryModel, 'hasValueAsync');
        spyOn(this.view._onboardingLauncher, 'launch');
      });
  
      describe('when queryRowsCollection is fetched', function () {
        beforeEach(function () {
          spyOn(this.view._queryRowsCollection, 'isFetched').and.returnValue(true);
        });
  
        it('should do nothing if onboarding was already 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 it is editing', function (done) {
          this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
          this.view._editorModel.isEditing = function () { return true; };
          this.view._onboardingLauncher.launch.calls.reset();
          this.view._launchOnboarding();
  
          georeferencePromise.resolve(false);
          hasGeomPromise.resolve(true);
  
          setTimeout(function () {
            expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
            done();
          }.bind(this), 0);
        });
  
        it('should do nothing if it can be georeferenced', function (done) {
          this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
          this.view._onboardingLauncher.launch.calls.reset();
          this.view._launchOnboarding();
  
          georeferencePromise.resolve(true);
          hasGeomPromise.resolve(true);
  
          setTimeout(function () {
            expect(this.view._onboardingLauncher.launch).not.toHaveBeenCalled();
            done();
          }.bind(this), 0);
        });
  
        it('should do nothing if it doesn\'t have geometry', function (done) {
          this.view._onboardingNotification.setKey(ONBOARDING_KEY, false);
          this.view._editorModel.isEditing = function () { return false; };
          this.view._onboardingLauncher.launch.calls.reset();
          this.view._launchOnboarding();
  
          georeferencePromise.resolve(false);
          hasGeomPromise.resolve(false);
  
          setTimeout(function () {
            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);
          this.view._editorModel.isEditing = function () { return false; };
          this.view._onboardingLauncher.launch.calls.reset();
          this.view._launchOnboarding();
  
          georeferencePromise.resolve(false);
          hasGeomPromise.resolve(true);
  
          setTimeout(function () {
            expect(this.view._onboardingLauncher.launch).toHaveBeenCalled();
            done();
          }.bind(this), 0);
        });
      });
    });
  
    it('should not have leaks', function () {
      expect(this.view).toHaveNoLeaks();
    });
  });
  