const _ = require('underscore'); const Backbone = require('backbone'); const ApiKeysFormView = require('dashboard/views/api-keys/api-keys-form-view'); const ApiKeysCollection = require('dashboard/data/api-keys-collection'); const ApiKeyModel = require('dashboard/data/api-key-model'); const UserTablesModel = require('dashboard/data/user-tables-model'); describe('dashboard/views/api-keys/api-keys-form-view', function () { let view, userModel, apiKeyModel, stackLayoutModel, spyOnFormChanged, userTablesModel, apiKeysCollection; const createViewFn = function (options) { userModel = new Backbone.Model({ base_url: 'wadus.com' }); userModel.getSchema = () => 'public'; apiKeyModel = new ApiKeyModel({ id: 'fake_id', type: 'regular', name: 'fake_api_key', token: 'fake_token' }, { userModel }); apiKeysCollection = new ApiKeysCollection([], { userModel }); stackLayoutModel = new Backbone.Model(); stackLayoutModel.goToStep = jasmine.createSpy('goToStep'); userTablesModel = new UserTablesModel(null, { userModel }); const viewOptions = Object.assign({}, { apiKeysCollection, stackLayoutModel, userTablesModel, userModel, apiKeyModel }, options); spyOnFormChanged = spyOn(ApiKeysFormView.prototype, '_onFormChanged'); const view = new ApiKeysFormView(viewOptions); return view; }; afterEach(function () { view && view.clean(); view = undefined; }); it('throws an error when stackLayoutModel is missing', function () { const factoryView = function () { return createViewFn({ stackLayoutModel: undefined }); }; expect(factoryView).toThrowError('stackLayoutModel is required'); }); it('throws an error when userTablesModel is missing', function () { const factoryView = function () { return createViewFn({ userTablesModel: undefined }); }; expect(factoryView).toThrowError('userTablesModel is required'); }); it('throws an error when userModel is missing', function () { const factoryView = function () { return createViewFn({ userModel: undefined }); }; expect(factoryView).toThrowError('userModel is required'); }); it('should call onFormChanged when formView changes', function () { view = createViewFn(); view._formView.trigger('change'); expect(view._onFormChanged).toHaveBeenCalled(); }); it('should call onFormChanged when apiKeyModel tables property changes', function () { view = createViewFn(); apiKeyModel.set({ tables: ['fake_table'] }); expect(view._onFormChanged).toHaveBeenCalled(); }); it('should generate a new apiKeyModel if options does not provide one', function () { view = createViewFn({ apiKeyModel: undefined }); expect(view._apiKeyModel).toBeDefined(); expect(view._apiKeyModel.isNew()).toBe(true); }); it('should generate a new form', function () { view = createViewFn(); expect(view._formView).toBeDefined(); }); describe('when an existing apiKeyModel is provided', function () { beforeEach(function () { view = createViewFn(); }); describe('.render', function () { it('should render properly', function () { spyOn(view, '_renderTooltip').and.callThrough(); view.render(); expect(_.size(view._subviews)).toBe(1); expect(view.$('.js-submit').length).toBe(0); expect(view.$('.js-api-keys-form').html().length).not.toBe(0); expect(view.$('.js-api-keys-tables').html().length).not.toBe(0); expect(view.$('input[name=token]').val()).toBe(apiKeyModel.get('token')); }); }); describe('._generateForm', function () { it('should render all fields properly', function () { const formView = view._generateForm(); formView.render(); expect(formView.$('input[name=name]').length).toBe(1); expect(formView.$('input[name=name]').attr('disabled')).toBe('disabled'); expect(formView.$('input[name=token]').length).toBe(1); // The 'copy' button changes the disabled attribute for readonly expect(formView.$('input[name=token]').attr('readonly')).toBe('readonly'); expect(formView.$('input[name=sql]').length).toBe(1); expect(formView.$('input[name=sql]').attr('disabled')).toBe('disabled'); expect(formView.$('input[name=maps]').length).toBe(1); expect(formView.$('input[name=maps]').attr('disabled')).toBe('disabled'); expect(formView.$('input[name=create]').length).toBe(1); expect(formView.$('input[name=create]').attr('disabled')).toBe('disabled'); expect(formView.$('input[name=listing]').length).toBe(1); expect(formView.$('input[name=listing]').attr('disabled')).toBe('disabled'); }); }); }); describe('when an existing apiKeyModel is not provided', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); }); describe('.render', function () { it('should render properly', function () { spyOn(view, '_renderTooltip').and.callThrough(); view.render(); expect(_.size(view._subviews)).toBe(2); expect(view.$('.js-submit').length).toBe(1); expect(view.$('.js-api-keys-form').html().length).not.toBe(0); expect(view.$('.js-api-keys-tables').html().length).not.toBe(0); }); }); describe('._generateForm', function () { it('should render all fields properly', function () { const formView = view._generateForm(); formView.render(); expect(formView.$('input[name=name]').length).toBe(1); expect(formView.$('input[name=name]').attr('disabled')).not.toBe('disabled'); expect(formView.$('input[name=token]').length).toBe(1); expect(formView.$('input[name=token]').attr('disabled')).toBe('disabled'); expect(formView.$('input[name=sql]').length).toBe(1); expect(formView.$('input[name=sql]').attr('disabled')).not.toBe('disabled'); expect(formView.$('input[name=maps]').length).toBe(1); expect(formView.$('input[name=maps]').attr('disabled')).not.toBe('disabled'); expect(formView.$('input[name=create]').length).toBe(1); expect(formView.$('input[name=create]').attr('disabled')).not.toBe('disabled'); expect(formView.$('input[name=listing]').length).toBe(1); expect(formView.$('input[name=listing]').attr('disabled')).not.toBe('disabled'); }); }); }); describe('._onClickBack', function () { beforeEach(function () { view = createViewFn(); }); it('should go to step 0 when clicking back', function () { view._onClickBack(); expect(stackLayoutModel.goToStep).toHaveBeenCalledWith(0); }); }); describe('._renderTooltip', function () { beforeEach(function () { view = createViewFn(); }); it('should render the tooltip properly', function () { const currentViews = _.size(view._subviews); view._renderTooltip(); expect(_.size(view._subviews)).toBe(currentViews + 1); }); }); describe('._hasErrors', function () { beforeEach(function () { view = createViewFn(); }); it('should return true if name has not been filled', function () { spyOn(view._formView, 'validate').and.returnValue({ name: { type: 'required', message: 'Required' } }); expect(view._hasErrors()).toBe(true); }); it('should return true if SQL is checked and CREATE or permissions have not been selected', function () { spyOn(view._formView, 'validate').and.returnValue(null); view._formView.setValue({ apis: { maps: false, sql: true } }); spyOn(view._apiKeyModel, 'hasPermissionsSelected').and.returnValue(false); expect(view._hasErrors()).toBe(true); }); it('should return true permissions have been selected but SQL or MAPS are not checked', function () { spyOn(view._formView, 'validate').and.returnValue(null); view._formView.setValue({ apis: { maps: false, sql: false } }); spyOn(view._apiKeyModel, 'hasPermissionsSelected').and.returnValue(true); expect(view._hasErrors()).toBe(true); }); it('should return false if SQL and CREATE are checked', function () { spyOn(view._formView, 'validate').and.returnValue(null); view._formView.setValue({ apis: { maps: false, sql: true }, datasets: { create: true } }); spyOn(view._apiKeyModel, 'hasPermissionsSelected').and.returnValue(true); expect(view._hasErrors()).toBe(false); }); it('should return false when only LISTING is checked', function () { spyOn(view._formView, 'validate').and.returnValue(null); view._formView.setValue({ apis: { maps: false, sql: false }, datasets: { create: false, listing: true } }); spyOn(view._apiKeyModel, 'hasPermissionsSelected').and.returnValue(false); expect(view._hasErrors()).toBe(false); }); it('should return true when LISTING is checked and SQL or MAPS are checked without permissions', function () { spyOn(view._formView, 'validate').and.returnValue(null); view._formView.setValue({ apis: { maps: true, sql: false }, datasets: { create: false, listing: true } }); spyOn(view._apiKeyModel, 'hasPermissionsSelected').and.returnValue(false); expect(view._hasErrors()).toBe(true); }); }); describe('._addApiKeyNameError', function () { beforeEach(function () { view = createViewFn(); }); it('should show a tooltip and add has-error class', function () { view.render(); const currentSubviews = _.size(view._subviews); view._addApiKeyNameError(); expect(_.size(view._subviews)).toBe(currentSubviews + 1); expect(view.$('#js-api-key-name').hasClass('has-error')).toBe(true); }); }); describe('._onFormChanged', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); spyOnFormChanged.and.callThrough(); }); it('should call _handleFormErrors', function () { spyOn(view, '_handleFormErrors'); view._onFormChanged(); expect(view._handleFormErrors).toHaveBeenCalled(); }); it('should call _handleCheckboxState', function () { spyOn(view, '_handleCheckboxState'); view._onFormChanged(); expect(view._handleCheckboxState).toHaveBeenCalled(); }); }); describe('._handleFormErrors', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); }); it('should remove has-error class in #js-api-key-name and clean errorTooltip', function () { view.render(); view._addApiKeyNameError(); spyOn(view._errorTooltip, 'clean').and.callThrough(); view._handleFormErrors(); expect(view.$('#js-api-key-name').hasClass('has-error')).toBe(false); expect(view._errorTooltip.clean).toHaveBeenCalled(); }); }); describe('._handleCheckboxState', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); }); it('should add showOnlySelect if only maps api is selected', function () { view.render(); view._formView.setValue({ apis: { maps: true, sql: false } }); view._handleCheckboxState(); expect(view.$('.ApiKeysForm-grantsTable').hasClass('showOnlySelect')).toBe(true); }); }); describe('when the form has errors', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); }); describe('._onFormChanged', function () { beforeEach(function () { spyOnFormChanged.and.callThrough(); }); it('should add is-disabled class to submit button', function () { view.render(); view._onFormChanged(); expect(view.$('.js-submit').hasClass('is-disabled')).toBe(true); }); it('should add the error tooltip', function () { spyOn(view, '_renderTooltip').and.callThrough(); view.render(); view._onFormChanged(); view._onFormChanged(); expect(view._validationTooltip).toBeDefined(); expect(view._renderTooltip).toHaveBeenCalledTimes(1); }); }); describe('._onFormSubmit', function () { it('should return', function () { view._generateForm(); spyOn(view._formView, 'commit'); view._onFormSubmit(); expect(view._formView.commit).not.toHaveBeenCalled(); }); }); }); describe('when the form has no errors', function () { beforeEach(function () { view = createViewFn({ apiKeyModel: undefined }); spyOnFormChanged.and.callThrough(); spyOn(view, '_hasErrors').and.returnValue(false); view._formView.setValue({ apis: { maps: false, sql: true }, name: 'Wadus API key', token: null }); }); describe('._onFormChanged', function () { it('should not add is-disabled class to submit button', function () { view._onFormChanged(); expect(view.$('.js-submit').hasClass('is-disabled')).toBe(false); }); it('should set validation tooltip to null if any', function () { let cleanSpy = jasmine.createSpy('clean'); view._validationTooltip = { clean: cleanSpy }; view._onFormChanged(); expect(cleanSpy).toHaveBeenCalled(); expect(view._validationTooltip).toBe(null); }); }); describe('._onFormSubmit', function () { it('should use the form data to create the API key', function () { spyOn(view._apiKeysCollection, 'create'); view._onFormSubmit(); const attributes = view._apiKeysCollection.create.calls.mostRecent().args[0]; expect(attributes.name).toEqual('Wadus API key'); expect(attributes.apis).toEqual({ maps: false, sql: true }); expect(attributes.tables).toEqual([]); }); it('should render the form with data returned from server', function () { var newModel = new Backbone.Model(); spyOn(view, '_generateForm'); spyOn(view, 'render'); spyOn(view._apiKeysCollection, 'create').and.callFake(function (attributes, options) { options.success && options.success(newModel); }); view._onFormSubmit(); expect(view._apiKeyModel).toBe(newModel); expect(view._generateForm).toHaveBeenCalled(); expect(view.render).toHaveBeenCalled(); }); it('should add api key name error when request fails', function () { spyOn(view, '_handleServerErrors'); spyOn(view._apiKeysCollection, 'create').and.callFake(function (attributes, options) { options.success && options.error(null, { responseText: 'Name has already been taken' }); }); view._onFormSubmit(); expect(view._handleServerErrors).toHaveBeenCalled(); }); }); }); describe('._clean', function () { beforeEach(function () { view = createViewFn(); }); it('should clear userTablesModel parameters when cleaning view', function () { spyOn(userTablesModel, 'clearParams'); view.clean(); expect(userTablesModel.clearParams).toHaveBeenCalled(); }); }); });