diff --git a/common/locales/en/pets.json b/common/locales/en/pets.json index 5efc19d8bc..9f8bdaa65c 100644 --- a/common/locales/en/pets.json +++ b/common/locales/en/pets.json @@ -80,6 +80,7 @@ "petKeyPets": "Release My Pets", "petKeyMounts": "Release My Mounts", "petKeyBoth": "Release Both", + "confirmPetKey": "Are you sure?", "petKeyNeverMind": "Not Yet", "gemsEach": "gems each" } diff --git a/test/spec/controllers/settingsCtrlSpec.js b/test/spec/controllers/settingsCtrlSpec.js index 858ca14180..527600b29e 100644 --- a/test/spec/controllers/settingsCtrlSpec.js +++ b/test/spec/controllers/settingsCtrlSpec.js @@ -1,9 +1,13 @@ 'use strict'; -describe('Settings Controller', function() { +describe('Settings Controller', function () { var rootScope, scope, user, User, ctrl; - beforeEach(function() { + const actionClickEvent = { + target: document.createElement('button'), + }; + + beforeEach(function () { module(function($provide) { user = specHelper.newUser(); User = { @@ -14,6 +18,9 @@ describe('Settings Controller', function() { User.user.ops = { reroll: sandbox.stub(), rebirth: sandbox.stub(), + releasePets: sandbox.stub(), + releaseMounts: sandbox.stub(), + releaseBoth: sandbox.stub(), }; $provide.value('User', User); @@ -31,20 +38,20 @@ describe('Settings Controller', function() { }); }); - describe('#openDayStartModal', function() { - beforeEach(function() { + describe('#openDayStartModal', function () { + beforeEach(function () { sandbox.stub(rootScope, 'openModal'); sandbox.stub(window, 'alert'); }); - it('opens the day start modal', function() { + it('opens the day start modal', function () { scope.openDayStartModal(5); expect(rootScope.openModal).to.be.calledOnce; expect(rootScope.openModal).to.be.calledWith('change-day-start', {scope: scope}); }); - it('sets nextCron variable', function() { + it('sets nextCron variable', function () { expect(scope.nextCron).to.not.exist; scope.openDayStartModal(5); @@ -52,7 +59,7 @@ describe('Settings Controller', function() { expect(scope.nextCron).to.exist; }); - it('calculates the next time cron will run', function() { + it('calculates the next time cron will run', function () { var fakeCurrentTime = new Date(2013, 3, 1, 3, 12).getTime(); var expectedTime = new Date(2013, 3, 1, 5, 0, 0).getTime(); sandbox.useFakeTimers(fakeCurrentTime); @@ -62,7 +69,7 @@ describe('Settings Controller', function() { expect(scope.nextCron).to.eq(expectedTime); }); - it('calculates the next time cron will run and adds a day if cron would have already passed', function() { + it('calculates the next time cron will run and adds a day if cron would have already passed', function () { var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime(); var expectedTime = new Date(2013, 3, 2, 5, 0, 0).getTime(); sandbox.useFakeTimers(fakeCurrentTime); @@ -73,9 +80,9 @@ describe('Settings Controller', function() { }); }); - describe('#saveDayStart', function() { + describe('#saveDayStart', function () { - it('updates user\'s custom day start and last cron', function() { + it('updates user\'s custom day start and last cron', function () { var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime(); var expectedTime = fakeCurrentTime; sandbox.useFakeTimers(fakeCurrentTime); @@ -90,80 +97,190 @@ describe('Settings Controller', function() { }); }); - describe('#reroll', function() { - beforeEach(function() { - scope.clickReroll(document.createElement('div')); + context('Player Reroll', function () { + describe('#reroll', function () { + beforeEach(function () { + scope.clickReroll(actionClickEvent); + }); + + it('destroys the previous popover if it exists', function () { + sandbox.spy($.fn, 'popover'); + + scope.reroll(false); + + expect(scope.popoverEl).to.exist; + expect($.fn.popover).to.be.calledWith('destroy'); + }); + + it('doesn\'t call reroll when not confirmed', function () { + scope.reroll(false); + + expect(user.ops.reroll).to.not.be.calledOnce; + }); + + it('calls reroll on the user when confirmed', function () { + sandbox.stub(rootScope.$state, 'go'); + + scope.reroll(true); + + expect(user.ops.reroll).to.be.calledWith({}); + }); + + it('navigates to the tasks page when confirmed', function () { + sandbox.stub(rootScope.$state, 'go'); + + scope.reroll(true); + + expect(rootScope.$state.go).to.be.calledWith('tasks'); + }); }); - it('destroys the previous popover if it exists', function() { - expect(scope.popoverEl).to.exist; - sandbox.spy($.fn, 'popover'); - scope.reroll(false); + describe('#clickReroll', function () { + it('displays a confirmation popover for the user', function () { + sandbox.spy($.fn, 'popover'); - $.fn.popover.should.have.been.calledWith('destroy'); - }); + scope.clickReroll(actionClickEvent); - it('doesn\'t call reroll when not confirmed', function() { - scope.reroll(false); - user.ops.reroll.should.not.have.been.called; - }); - - it('calls reroll on the user when confirmed and navigates to tasks', function() { - sandbox.stub(rootScope.$state, 'go'); - scope.reroll(true); - - user.ops.reroll.should.have.been.calledWith({}); - rootScope.$state.go.should.have.been.calledWith('tasks'); + expect($.fn.popover).to.be.calledWith('destroy'); + expect($.fn.popover).to.be.calledWith('show'); + }); }); }); - describe('#clickReroll', function() { - it('displays a confirmation popover for the user', function() { - sandbox.spy($.fn, 'popover'); - scope.clickReroll(document.createElement('div')); + context('Player Rebirth', function () { + describe('#rebirth', function () { + beforeEach(function () { + scope.clickRebirth(actionClickEvent); + }); - $.fn.popover.should.have.been.calledWith('destroy'); - $.fn.popover.should.have.been.called; - $.fn.popover.should.have.been.calledWith('show'); + it('destroys the previous popover if it exists', function () { + sandbox.spy($.fn, 'popover'); + + scope.rebirth(false); + + expect(scope.popoverEl).to.exist; + expect($.fn.popover).to.be.calledWith('destroy'); + }); + + it('doesn\'t call rebirth when not confirmed', function () { + scope.rebirth(false); + + expect(user.ops.rebirth).to.not.be.calledOnce; + }); + + it('calls rebirth on the user when confirmed', function () { + sandbox.stub(rootScope.$state, 'go'); + + scope.rebirth(true); + + expect(user.ops.rebirth).to.be.calledWith({}); + }); + + it('navigates to tasks page when confirmed', function () { + sandbox.stub(rootScope.$state, 'go'); + + scope.rebirth(true); + + expect(rootScope.$state.go).to.be.calledWith('tasks'); + }); + }); + + describe('#clickRebirth', function () { + it('displays a confirmation popover for the user', function () { + sandbox.spy($.fn, 'popover'); + + scope.clickRebirth(actionClickEvent); + + expect($.fn.popover).to.be.calledWith('destroy'); + expect($.fn.popover).to.be.calledWith('show'); + }); + }); + }) + + context('Releasing pets and mounts', function () { + describe('#release', function () { + beforeEach(function () { + scope.clickRelease('dummy', actionClickEvent); + + sandbox.stub(rootScope.$state, 'go'); + }); + + it('destroys the previous popover if it exists', function () { + sandbox.spy($.fn, 'popover'); + + scope.releaseAnimals('', false); + + expect($.fn.popover).to.be.calledWith('destroy'); + }); + + it('doesn\'t call any release method if type is not provided', function () { + scope.releaseAnimals(); + + expect(User.user.ops.releasePets).to.not.be.called; + expect(User.user.ops.releaseMounts).to.not.be.called; + expect(User.user.ops.releaseBoth).to.not.be.called; + }); + + it('doesn\'t redirect to tasks page if type is not provided', function () { + scope.releaseAnimals(); + + expect(rootScope.$state.go).to.not.be.called; + }) + + it('calls releasePets when "pets" is provided', function () { + scope.releaseAnimals('pets'); + + expect(User.user.ops.releasePets).to.be.calledOnce; + }); + + it('navigates to the tasks page when "pets" is provided', function () { + scope.releaseAnimals('pets'); + + expect(rootScope.$state.go).to.be.calledOnce; + }); + + it('calls releaseMounts when "mounts" is provided', function () { + scope.releaseAnimals('mounts'); + + expect(User.user.ops.releaseMounts).to.be.calledOnce; + }); + + it('navigates to the tasks page when "mounts" is provided', function () { + scope.releaseAnimals('mounts'); + + expect(rootScope.$state.go).to.be.calledOnce; + }); + + it('calls releaseBoth when "both" is provided', function () { + scope.releaseAnimals('both'); + + expect(User.user.ops.releaseBoth).to.be.calledOnce; + }); + + it('navigates to the tasks page when "both" is provided', function () { + scope.releaseAnimals('both'); + + expect(rootScope.$state.go).to.be.calledOnce; + }); + + it('does not call release functions when non-applicable argument is passed in', function () { + scope.releaseAnimals('dummy'); + + expect(User.user.ops.releasePets).to.not.be.called; + expect(User.user.ops.releaseMounts).to.not.be.called; + expect(User.user.ops.releaseBoth).to.not.be.called; + }); + }); + + describe('#clickRelease', function () { + it('displays a confirmation popover for the user', function () { + sandbox.spy($.fn, 'popover'); + scope.clickRelease('dummy', actionClickEvent); + + expect($.fn.popover).to.be.calledWith('destroy'); + expect($.fn.popover).to.be.called; + expect($.fn.popover).to.be.calledWith('show'); + }); }); }); - - describe('#rebirth', function() { - beforeEach(function() { - scope.clickRebirth(document.createElement('div')); - }); - - it('destroys the previous popover if it exists', function() { - expect(scope.popoverEl).to.exist; - sandbox.spy($.fn, 'popover'); - scope.rebirth(false); - - $.fn.popover.should.have.been.calledWith('destroy'); - }); - - it('doesn\'t call rebirth when not confirmed', function() { - scope.rebirth(false); - user.ops.rebirth.should.not.have.been.called; - }); - - it('calls rebirth on the user when confirmed and navigates to tasks', function() { - sandbox.stub(rootScope.$state, 'go'); - scope.rebirth(true); - - user.ops.rebirth.should.have.been.calledWith({}); - rootScope.$state.go.should.have.been.calledWith('tasks'); - }); - }); - - describe('#clickRebirth', function() { - it('displays a confirmation popover for the user', function() { - sandbox.spy($.fn, 'popover'); - scope.clickRebirth(document.createElement('div')); - - $.fn.popover.should.have.been.calledWith('destroy'); - $.fn.popover.should.have.been.called; - $.fn.popover.should.have.been.calledWith('show'); - }); - }); - }); diff --git a/website/public/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js index 32f30290bf..3672a4d703 100644 --- a/website/public/js/controllers/settingsCtrl.js +++ b/website/public/js/controllers/settingsCtrl.js @@ -4,6 +4,11 @@ habitrpg.controller('SettingsCtrl', ['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Content', 'Notification', 'Shared', '$compile', function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Content, Notification, Shared, $compile) { + var RELEASE_ANIMAL_TYPES = { + pets: 'releasePets', + mounts: 'releaseMounts', + both: 'releaseBoth', + }; // FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest // $scope.auth = function (id, token) { @@ -91,7 +96,7 @@ habitrpg.controller('SettingsCtrl', $scope.availableFormats = ['MM/dd/yyyy','dd/MM/yyyy', 'yyyy/MM/dd']; $scope.reroll = function(confirm){ - $scope.popoverEl.popover('destroy') + $scope.popoverEl.popover('destroy'); if (confirm) { User.user.ops.reroll({}); @@ -116,7 +121,7 @@ habitrpg.controller('SettingsCtrl', } $scope.rebirth = function(confirm){ - $scope.popoverEl.popover('destroy') + $scope.popoverEl.popover('destroy'); if (confirm) { User.user.ops.rebirth({}); @@ -200,19 +205,39 @@ habitrpg.controller('SettingsCtrl', }) } - $scope.releasePets = function() { - User.user.ops.releasePets({}); - $rootScope.$state.go('tasks'); + $scope.clickRelease = function(type, $event){ + // Close other popovers if they're open + $(".release_popover").not($event.target).popover('destroy'); + + // Handle clicking on the gem icon + if ($event.target.nodeName == "SPAN") { + $scope.releasePopoverEl = $($event.target.parentNode); + } else { + $scope.releasePopoverEl = $($event.target); + } + + var html = $compile( + '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' + )($scope); + + $scope.releasePopoverEl.popover('destroy').popover({ + html: true, + placement: 'top', + trigger: 'manual', + title: window.env.t('confirmPetKey'), + content: html + }).popover('show'); } - $scope.releaseMounts = function() { - User.user.ops.releaseMounts({}); - $rootScope.$state.go('tasks'); - } + $scope.releaseAnimals = function (type) { + $scope.releasePopoverEl.popover('destroy'); - $scope.releaseBoth = function() { - User.user.ops.releaseBoth({}); - $rootScope.$state.go('tasks'); + var releaseFunction = RELEASE_ANIMAL_TYPES[type]; + + if (releaseFunction) { + User.user.ops[releaseFunction]({}); + $rootScope.$state.go('tasks'); + } } // ---- Webhooks ------ diff --git a/website/views/shared/modals/drops.jade b/website/views/shared/modals/drops.jade index b91c9ae4b0..b5e5c0cbe7 100644 --- a/website/views/shared/modals/drops.jade +++ b/website/views/shared/modals/drops.jade @@ -36,17 +36,17 @@ script(type='text/ng-template', id='modals/pet-key.html') a.btn.btn-success(ng-click='openModal("buyGems")')=env.t('buyMoreGems') span.gem-cost=env.t('notEnoughGems') span(ng-if='user.balance >= 1 && petCount >= 90', ng-controller='SettingsCtrl') - a.btn.btn-danger(ng-click='$close(); releasePets()') + a.btn.btn-danger.release_popover(ng-click='clickRelease("pets", $event)') =env.t('petKeyPets') | : 4  span.Pet_Currency_Gem1x.inline-gems span(ng-if='user.balance >= 1 && mountCount >= 90', ng-controller='SettingsCtrl') - a.btn.btn-danger(ng-click='$close(); releaseMounts()') + a.btn.btn-danger.release_popover(ng-click='clickRelease("mounts", $event)') =env.t('petKeyMounts') | : 4  span.Pet_Currency_Gem1x.inline-gems span(ng-if='(user.balance >= 1.5 || user.achievements.triadBingo) && petCount >= 90 && mountCount >= 90', ng-controller='SettingsCtrl') - a.btn.btn-danger(ng-click='$close(); releaseBoth()') + a.btn.btn-danger.release_popover(ng-click='clickRelease("both", $event)') =env.t('petKeyBoth') span(ng-if='!user.achievements.triadBingo') | : 6