diff --git a/app/js/controllers.js b/app/js/controllers.js index b579e8bb..ad7ffe58 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -4455,9 +4455,11 @@ angular.module('myApp.controllers', ['myApp.i18n']) AppUsersManager.saveApiUser(user) $modalInstance.close() }, function (error) { - if (error.type == 'USERNAME_NOT_MODIFIED') { - error.handled = true - $modalInstance.close() + switch (error.type) { + case 'USERNAME_NOT_MODIFIED': + error.handled = true + $modalInstance.close() + break } })['finally'](function () { delete $scope.profile.updating @@ -4470,9 +4472,9 @@ angular.module('myApp.controllers', ['myApp.i18n']) return } MtpApiManager.invokeApi('account.checkUsername', { - username: newVal || '' + username: newVal }).then(function (valid) { - if ($scope.profile.username != newVal) { + if ($scope.profile.username !== newVal) { return } if (valid) { @@ -4481,7 +4483,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) $scope.checked = {error: true} } }, function (error) { - if ($scope.profile.username != newVal) { + if ($scope.profile.username !== newVal) { return } switch (error.type) { diff --git a/test/unit/controllers/CountrySelectModalControllerSpec.js b/test/unit/controllers/CountrySelectModalControllerSpec.js new file mode 100644 index 00000000..8d7ea801 --- /dev/null +++ b/test/unit/controllers/CountrySelectModalControllerSpec.js @@ -0,0 +1,181 @@ +'use strict' +/* global describe, it, inject, expect, beforeEach, afterEach, spyOn, jasmine, Config, SearchIndexManager */ + +describe('CountrySelectModalController', function () { + beforeEach(module('myApp.controllers')) + + beforeEach(inject(function (_$controller_, _$rootScope_, ___) { + this.$controller = _$controller_ + this.$rootScope = _$rootScope_ + this._ = ___ + + this.$scope = _$rootScope_.$new() + this.createController = function () { + this.$controller('CountrySelectModalController', { + $scope: this.$scope, + $modalInstance: {}, + $rootScope: this.$rootScope, + _: this._ + }) + } + + spyOn(SearchIndexManager, 'indexObject').and.callThrough() + })) + + beforeEach(function () { + this.ConfigCountryCodes = Config.CountryCodes + this.testData = { + singleCode: { + countryCode: ['NL', 'country_select_modal_country_nl', '+31'], + countryCode_full: 'NL Netherlands +31', + countryPhoneSets: [{ name: 'Netherlands', code: '+31' }] + }, + multipleCode: { + countryCode: ['VA', 'country_select_modal_country_va', '+39 06 698', '+379'], + countryCode_full: 'VA Vatican City +39 06 698 +379', + countryPhoneSets: [{ name: 'Vatican City', code: '+39 06 698' }, { name: 'Vatican City', code: '+379' }] + }, + multipleCode2: { + countryCode: ['AB', 'country_select_modal_country_ab', '+7 840', '+7 940', '+995 44'], + countryCode_full: 'AB Abkhazia +7 840 +7 940 +995 44', + countryPhoneSets: [{ name: 'Abkhazia', code: '+7 840' }, { name: 'Abkhazia', code: '+7 940' }, { name: 'Abkhazia', code: '+995 44' }] + }, + allSetsSorted: function () { + return [].concat(this.multipleCode2.countryPhoneSets, this.singleCode.countryPhoneSets, this.multipleCode.countryPhoneSets) + }, + allSetsUnsorted: function () { + return [].concat(this.singleCode.countryPhoneSets, this.multipleCode2.countryPhoneSets, this.multipleCode.countryPhoneSets) + } + } + }) + + afterEach(function () { + Config.CountryCodes = this.ConfigCountryCodes + }) + + // The tests before controller initiation. + // In order to mock Config data + + it('initiates Country to select', function (done) { + Config.CountryCodes = [this.testData.singleCode.countryCode] + var expected = this.testData.singleCode.countryCode_full + + this.createController() + + expect(SearchIndexManager.indexObject).toHaveBeenCalledWith(0, expected, jasmine.any(Object)) + done() + }) + + it('initiates Countriy to select with 2 (or more) country codes', function (done) { + Config.CountryCodes = [this.testData.multipleCode.countryCode] + var expected = this.testData.multipleCode.countryCode_full + + this.createController() + + expect(SearchIndexManager.indexObject).toHaveBeenCalledWith(0, expected, jasmine.any(Object)) + done() + }) + + it('initiates Countries to select', function (done) { + Config.CountryCodes = [this.testData.singleCode.countryCode, this.testData.multipleCode.countryCode] + var expected1 = this.testData.singleCode.countryCode_full + var expected2 = this.testData.multipleCode.countryCode_full + + this.createController() + + expect(SearchIndexManager.indexObject).toHaveBeenCalledWith(0, expected1, jasmine.any(Object)) + expect(SearchIndexManager.indexObject).toHaveBeenCalledWith(1, expected2, jasmine.any(Object)) + done() + }) + + describe('(after initiation)', function () { + beforeEach(function () { + Config.CountryCodes = [this.testData.singleCode.countryCode, this.testData.multipleCode2.countryCode, this.testData.multipleCode.countryCode] + this.createController() + }) + + it('initiates the right values', function (done) { + expect(this.$scope.search).toEqual({}) + expect(this.$scope.slice).toEqual({limit: 20, limitDelta: 20}) + done() + }) + + it('creates a sorted list of all selectable countries', function (done) { + this.$rootScope.$digest() + var expected = this.testData.allSetsSorted() + + expect(this.$scope.countries).toEqual(expected) + done() + }) + + it('creates a sorted list of all selectable countries for an empty string-input', function (done) { + this.$rootScope.$digest() + this.$scope.search.query = '' + this.$rootScope.$digest() + var expected = this.testData.allSetsSorted() + + expect(this.$scope.countries).toEqual(expected) + done() + }) + + describe(', when an input is given,', function () { + beforeEach(function () { + this.$rootScope.$digest() + this.$scope.search.query = 'A' + }) + + it('creates a sorted list of all countries containing the input', function (done) { + var expected = this.testData.allSetsSorted() + + expect(this.$scope.countries).toEqual(expected) + + this.$rootScope.$digest() + expected = this.testData.multipleCode2.countryPhoneSets + + expect(this.$scope.countries).toEqual(expected) + done() + }) + + it('restore the original list when the input is deleted', function (done) { + this.$rootScope.$digest() + this.$scope.search.query = '' + this.$rootScope.$digest() + + var expected = this.testData.allSetsSorted() + + expect(this.$scope.countries).toEqual(expected) + done() + }) + + it('restore the original list when the input is changed', function (done) { + this.$rootScope.$digest() + this.$scope.search.query = 'Ne' + this.$rootScope.$digest() + + var expected = this.testData.singleCode.countryPhoneSets + + expect(this.$scope.countries).toEqual(expected) + done() + }) + }) + + describe(', when no sorting is available,', function () { + beforeEach(function () { + this.StringCompare = String.prototype.localeCompare + String.prototype.localeCompare = null + }) + + afterEach(function () { + String.prototype.localeCompare = this.StringCompare + }) + + it('creates a list of all selectable countries', function (done) { + this.$rootScope.$digest() + var expected = this.testData.allSetsUnsorted() + + expect(this.$scope.countries).toEqual(expected) + done() + }) + }) + }) +}) diff --git a/test/unit/controllers/ImportContactModalControllerSpec.js b/test/unit/controllers/ImportContactModalControllerSpec.js new file mode 100644 index 00000000..1603d348 --- /dev/null +++ b/test/unit/controllers/ImportContactModalControllerSpec.js @@ -0,0 +1,160 @@ +'use strict' +/* global describe, it, inject, expect, beforeEach, jasmine */ + +describe('ImportContactModalController', function () { + beforeEach(module('myApp.controllers')) + + beforeEach(function () { + this.modalInstance = { + close: jasmine.createSpy('close'), + dismiss: jasmine.createSpy('dismiss') + } + + this.randomID = 123456852 + + function thenFinallyFactory (input) { + return { + then: function (callback) { + callback(input) + return { + finally: function (callback) { + callback() + } + } + } + } + } + + this.AppUsersManager = { + thenValue: null, + importContact: function (phone, first, last) { + this.input = { + phone: phone, + first: first, + last: last + } + return thenFinallyFactory(this.thenValue) + } + } + + this.ErrorService = { + show: jasmine.createSpy('show') + } + + this.PhonebookContactsService = { + thenValue: false, + isAvailable: function () { + return false + }, + openPhonebookImport: function () { + var then = thenFinallyFactory(this.thenValue) + return { + result: then + } + } + } + + inject(function (_$controller_, _$rootScope_) { + this.$controller = _$controller_ + this.$rootScope = _$rootScope_ + + this.$scope = _$rootScope_.$new() + this.createController = function () { + this.$controller('ImportContactModalController', { + $scope: this.$scope, + $modalInstance: this.modalInstance, + $rootScope: this.$rootScope, + AppUsersManager: this.AppUsersManager, + ErrorService: this.ErrorService, + PhonebookContactsService: this.PhonebookContactsService + }) + } + }) + }) + + it('can create a controller when no imported contacts are defined', function (done) { + this.createController() + + expect(this.$scope.importContact).toEqual({}) + done() + }) + + it('can create a controller when imported contacts are defined', function (done) { + this.$scope.importContact = { non_empty: true } + this.createController() + var expected = { non_empty: true } + + expect(this.$scope.importContact).toEqual(expected) + done() + }) + + describe('(when the controller is created), ', function () { + beforeEach(function () { + this.createController() + }) + + it('does nothing when no phonenumber was entered', function (done) { + this.$scope.doImport() + + expect(this.$scope.progress).not.toBeDefined() + + this.$scope.importContact = { + first_name: 'bob' + } + expect(this.$scope.progress).not.toBeDefined() + done() + }) + + describe('when contact-information is added, it', function () { + beforeEach(function () { + this.$scope.importContact = { + phone: '+316132465798' + } + }) + + it('can handle phoneNumber that are not telegram users', function (done) { + this.$scope.doImport() + + expect(this.ErrorService.show).toHaveBeenCalledWith({ error: {code: 404, type: 'USER_NOT_USING_TELEGRAM'} }) + expect(this.modalInstance.close).toHaveBeenCalledWith(null) + expect(this.$scope.progress.enabled).not.toBeDefined() + done() + }) + + it('can import contacts that are telegram users', function (done) { + this.AppUsersManager.thenValue = this.randomID + this.$scope.doImport() + + expect(this.ErrorService.show).not.toHaveBeenCalled() + expect(this.modalInstance.close).toHaveBeenCalledWith(this.randomID) + expect(this.$scope.progress.enabled).not.toBeDefined() + expect(this.AppUsersManager.input).toEqual({phone: '+316132465798', first: '', last: ''}) + done() + }) + + it('can handle contacts with first and last name', function (done) { + this.$scope.importContact.first_name = 'jan' + this.$scope.importContact.last_name = 'wandelaar' + this.$scope.doImport() + + expect(this.AppUsersManager.input).toEqual({phone: '+316132465798', first: 'jan', last: 'wandelaar'}) + done() + }) + }) + + it('can not import contacts from a phonebook if none were found', function (done) { + this.$scope.importPhonebook() + + expect(this.modalInstance.dismiss).toHaveBeenCalled() + done() + }) + + it('can import contacts from a phonebook', function (done) { + this.PhonebookContactsService.thenValue = {0: 'dummy'} + this.$scope.importPhonebook() + + expect(this.modalInstance.close).toHaveBeenCalledWith('dummy') + done() + }) + }) +}) diff --git a/test/unit/controllers/PasswordRecoveryModalControllerSpec.js b/test/unit/controllers/PasswordRecoveryModalControllerSpec.js new file mode 100644 index 00000000..3c2285f1 --- /dev/null +++ b/test/unit/controllers/PasswordRecoveryModalControllerSpec.js @@ -0,0 +1,113 @@ +'use strict' +/* global describe, it, inject, expect, beforeEach, jasmine */ + +describe('PasswordRecoveryModalController', function () { + beforeEach(module('myApp.controllers')) + + beforeEach(function () { + this.PasswordManager = { + errorField: null, + recover: function () { + return this + }, + then: function (callback, error) { + if (!this.errorField) { + callback({}) + } else { + error(this.errorField) + } + return { + finally: function (final) { + final() + } + } + } + } + + this.ErrorService = { alert: jasmine.createSpy('alert') } + this.modalInstance = { + close: jasmine.createSpy('close'), + dismiss: jasmine.createSpy('dismiss') + } + + inject(function (_$controller_, _$rootScope_, ___) { + this.$controller = _$controller_ + this.$scope = _$rootScope_.$new() + this.$scope.recovery = {} + + this.$controller('PasswordRecoveryModalController', { + $scope: this.$scope, + $q: {}, + _: ___, + PasswordManager: this.PasswordManager, + MtpApiManager: {}, + ErrorService: this.ErrorService, + $modalInstance: this.modalInstance + }) + }) + }) + + it('can handle a successful password change', function (done) { + this.$scope.checkCode() + + expect(this.$scope.recovery.updating).toBe(true) + expect(this.ErrorService.alert).toHaveBeenCalledWith('Password deactivated', 'You have disabled Two-Step Verification.') + expect(this.modalInstance.close).toHaveBeenCalled() + done() + }) + + describe('when an error occurs', function () { + beforeEach(function () { + this.PasswordManager.errorField = {} + }) + + it('cancels the recovery', function (done) { + this.$scope.checkCode() + + expect(this.$scope.recovery.updating).not.toBeDefined() + expect(this.ErrorService.alert).not.toHaveBeenCalled() + expect(this.modalInstance.close).not.toHaveBeenCalled() + done() + }) + + it('can handle the error for an empty code', function (done) { + this.PasswordManager.errorField.type = 'CODE_EMPTY' + this.$scope.checkCode() + + expect(this.$scope.recovery.error_field).toEqual('code') + done() + }) + + it('can handle the error for an invalid code', function (done) { + this.PasswordManager.errorField.type = 'CODE_INVALID' + this.$scope.checkCode() + + expect(this.$scope.recovery.error_field).toEqual('code') + done() + }) + + it('can handle the error for an empty password', function (done) { + this.PasswordManager.errorField.type = 'PASSWORD_EMPTY' + this.$scope.checkCode() + + expect(this.modalInstance.dismiss).toHaveBeenCalled() + done() + }) + + it('can handle the error for the unavailability of the recovery', function (done) { + this.PasswordManager.errorField.type = 'PASSWORD_RECOVERY_NA' + this.$scope.checkCode() + + expect(this.modalInstance.dismiss).toHaveBeenCalled() + done() + }) + + it('can handle the error for an expired recovery', function (done) { + this.PasswordManager.errorField.type = 'PASSWORD_RECOVERY_EXPIRED' + this.$scope.checkCode() + + expect(this.modalInstance.dismiss).toHaveBeenCalled() + done() + }) + }) +}) diff --git a/test/unit/controllers/ProfileEditModalControllerSpec.js b/test/unit/controllers/ProfileEditModalControllerSpec.js new file mode 100644 index 00000000..5fc86031 --- /dev/null +++ b/test/unit/controllers/ProfileEditModalControllerSpec.js @@ -0,0 +1,108 @@ +'use strict' +/* global describe, it, inject, expect, beforeEach, jasmine */ + +describe('ProfileEditModalController', function () { + beforeEach(module('myApp.controllers')) + + beforeEach(function () { + var id = 42 + this.randomID = id + + this.MtpApiManager = { + errorField: null, + getUserID: function () { + return { + then: function (callback) { + callback(id) + } + } + }, + invokeApi: function (action, params) { + return this + }, + then: function (callback, error) { + if (!this.errorField) { + callback({}) + } else { + error(this.errorField) + } + return { + finally: function (final) { + final() + } + } + } + } + + this.AppUsersManager = { + getUser: function (userId) { + return { + first_name: 'John', + last_name: 'Doe' + } + }, + saveApiUser: jasmine.createSpy('saveApiUser') + } + this.$modalInstance = { close: jasmine.createSpy('close') } + + inject(function (_$controller_, _$rootScope_) { + this.$controller = _$controller_ + this.$scope = _$rootScope_.$new() + + this.$controller('ProfileEditModalController', { + $scope: this.$scope, + $modalInstance: this.$modalInstance, + AppUsersManager: this.AppUsersManager, + MtpApiManager: this.MtpApiManager + }) + }) + }) + + it('should initiate the right scope', function (done) { + expect(this.$scope.profile).toEqual({first_name: 'John', last_name: 'Doe'}) + expect(this.$scope.error).toEqual({}) + done() + }) + + it('can send a successful profile update request', function (done) { + this.$scope.updateProfile() + + expect(this.AppUsersManager.saveApiUser).toHaveBeenCalled() + expect(this.$modalInstance.close).toHaveBeenCalled() + done() + }) + + it('can handle empty name/surname', function (done) { + delete this.$scope.profile.first_name + delete this.$scope.profile.last_name + this.$scope.updateProfile() + + expect(this.AppUsersManager.saveApiUser).toHaveBeenCalled() + expect(this.$modalInstance.close).toHaveBeenCalled() + done() + }) + + it('can handle an invalid first name error', function (done) { + this.MtpApiManager.errorField = {type: 'FIRSTNAME_INVALID'} + this.$scope.updateProfile() + + expect(this.$scope.error.field).toEqual('first_name') + done() + }) + + it('can handle an invalid last name error', function (done) { + this.MtpApiManager.errorField = {type: 'LASTNAME_INVALID'} + this.$scope.updateProfile() + + expect(this.$scope.error.field).toEqual('last_name') + done() + }) + + it('can handle an unmodified name error', function (done) { + this.MtpApiManager.errorField = {type: 'NAME_NOT_MODIFIED'} + this.$scope.updateProfile() + + expect(this.$modalInstance.close).toHaveBeenCalled() + done() + }) +}) diff --git a/test/unit/controllers/UsernameEditModalControllerSpec.js b/test/unit/controllers/UsernameEditModalControllerSpec.js new file mode 100644 index 00000000..b09a26f4 --- /dev/null +++ b/test/unit/controllers/UsernameEditModalControllerSpec.js @@ -0,0 +1,146 @@ +'use strict' +/* global describe, it, inject, expect, beforeEach, jasmine */ + +describe('UsernameEditModalController', function () { + beforeEach(module('myApp.controllers')) + + beforeEach(function () { + this.MtpApiManager = { + errorField: false, + isValid: true, + invokeApi: function () { + return this + }, + getUserID: function () { + return this + }, + then: function (callback, error) { + if (!this.errorField) { + callback(this.isValid) + } else { + error(this.errorField) + } + return { + finally: function (final) { + final() + } + } + } + } + + this.AppUsersManager = { + saveApiUser: jasmine.createSpy('saveApiUser'), + getUser: function (id) { + return { username: 'bob' } + } + } + + this.modalInstance = { + close: jasmine.createSpy('close') + } + + inject(function (_$controller_, _$rootScope_) { + this.$controller = _$controller_ + this.$scope = _$rootScope_.$new() + + this.$controller('UsernameEditModalController', { + $scope: this.$scope, + $modalInstance: this.modalInstance, + AppUsersManager: this.AppUsersManager, + MtpApiManager: this.MtpApiManager + }) + }) + }) + + it('constructs the information for the modal', function (done) { + var expected = { + username: 'bob' + } + expect(this.$scope.profile).toEqual(expected) + expect(this.$scope.error).toEqual({}) + done() + }) + + it('can handle a successful update of the username', function (done) { + this.$scope.updateUsername() + + expect(this.$scope.checked).toEqual({}) + expect(this.AppUsersManager.saveApiUser).toHaveBeenCalled() + expect(this.modalInstance.close).toHaveBeenCalled() + done() + }) + + it('can handle a successful update of an empty/undefined username', function (done) { + delete this.$scope.profile.username + this.$scope.updateUsername() + + expect(this.$scope.checked).toEqual({}) + expect(this.AppUsersManager.saveApiUser).toHaveBeenCalled() + expect(this.modalInstance.close).toHaveBeenCalled() + done() + }) + + it('can handle an unsuccessful update of an unmodified username', function (done) { + this.MtpApiManager.errorField = { type: 'USERNAME_NOT_MODIFIED' } + this.$scope.updateUsername() + + expect(this.$scope.checked).not.toBeDefined() + expect(this.AppUsersManager.saveApiUser).not.toHaveBeenCalled() + expect(this.modalInstance.close).toHaveBeenCalled() + done() + }) + + it('can check an empty username on change', function (done) { + this.$scope.profile.username = {} + var expected = {} + + this.$scope.$digest() + expect(this.$scope.checked).toEqual(expected) + done() + }) + + it('can check an empty string as username', function (done) { + this.$scope.profile.username = '' + var expected = {} + + this.$scope.$digest() + expect(this.$scope.checked).toEqual(expected) + done() + }) + + it('can check the initial username', function (done) { + // Previous username is expected to be valid + this.$scope.$digest() + var expected = true + + expect(this.$scope.checked.success).toBe(expected) + done() + }) + + it('does not check anything when the name is not changed', function (done) { + this.$scope.$digest() + delete this.$scope.checked.success + this.$scope.$digest() + + expect(this.$scope.checked.success).not.toBeDefined() + done() + }) + + it('can check an invalid username submission', function (done) { + this.MtpApiManager.isValid = false + this.$scope.$digest() + var expected = true + + expect(this.$scope.checked.error).toBe(expected) + done() + }) + + it('can check an invalid username submission 2', function (done) { + this.MtpApiManager.errorField = { type: 'USERNAME_INVALID' } + this.$scope.$digest() + var expected = true + + expect(this.$scope.checked.error).toBe(expected) + done() + }) +})