From 42b482cb7efa8b37941e9fa0a58bba7a7bf3a4c6 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 28 May 2015 08:14:50 -0500 Subject: [PATCH 1/2] Make user sync after accepting or rejecting a quest --- website/public/js/controllers/groupsCtrl.js | 18 ++++++++++++++++++ .../public/js/controllers/notificationCtrl.js | 2 +- website/views/shared/modals/quests.jade | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index 7ef422ea98..e9cb113d20 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -501,6 +501,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', function($rootScope,$scope, Groups, User, Challenges, $state, $compile) { + $scope.type = 'party'; $scope.text = window.env.t('party'); $scope.group = $rootScope.party = Groups.party(); @@ -579,6 +580,23 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $rootScope.party.$questAbort(); } + $scope.questAccept = function(party){ + party.$questAccept() + .then(function(res) { + User.sync(); + }, function(err) { + console.log(err); + }); + } + + $scope.questReject = function(party){ + party.$questReject() + .then(function(res) { + User.sync(); + }, function(err) { + console.log(err); + }); + } } ]) diff --git a/website/public/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js index f53a409e78..17ea1774ed 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -150,7 +150,7 @@ habitrpg.controller('NotificationCtrl', // Quest invitation modal $rootScope.$watch('user.party.quest.RSVPNeeded', function(after, before){ if (after != true) return; - $rootScope.openModal('questInvitation'); + $rootScope.openModal('questInvitation', {controller:'PartyCtrl'}); }); $rootScope.$on('responseError', function(ev, error){ diff --git a/website/views/shared/modals/quests.jade b/website/views/shared/modals/quests.jade index 0a6692525f..ab1f6c781f 100644 --- a/website/views/shared/modals/quests.jade +++ b/website/views/shared/modals/quests.jade @@ -87,5 +87,5 @@ script(type='text/ng-template', id='modals/questInvitation.html') quest-rewards(key='{{::user.party.quest.key}}', header=env.t('rewards')) .modal-footer button.btn.btn-default(ng-click='questHold = true; $close()')=env.t('askLater') - button.btn.btn-default(ng-click='party.$questReject(); $close()')=env.t('reject') - button.btn.btn-primary(ng-click='party.$questAccept(); $close()')=env.t('accept') + button.btn.btn-default(ng-click='questReject(party); $close()')=env.t('reject') + button.btn.btn-primary(ng-click='questAccept(party); $close()')=env.t('accept') From e3bc8805fe1163bfc4e1ce0a7ab4010960c3a08d Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Fri, 29 May 2015 09:13:36 -0500 Subject: [PATCH 2/2] Refactor quest functions and add tests --- test/spec/groupCtrlSpec.js | 129 ++++++++++++++++++++ test/spec/groupServicesSpec.js | 100 ++++++++++++++- website/public/js/controllers/groupsCtrl.js | 22 +--- website/public/js/services/groupServices.js | 27 ++++ website/views/options/social/boss.jade | 8 +- 5 files changed, 265 insertions(+), 21 deletions(-) diff --git a/test/spec/groupCtrlSpec.js b/test/spec/groupCtrlSpec.js index a85cb7b810..653bfcc6b8 100644 --- a/test/spec/groupCtrlSpec.js +++ b/test/spec/groupCtrlSpec.js @@ -310,3 +310,132 @@ describe("CopyMessageModal controller", function() { }); }); }); + +describe("Party Controller", function() { + var scope, ctrl, user, User, groups, $rootScope, $controller; + + beforeEach(function() { + user = specHelper.newUser(), + user._id = "unique-user-id"; + User = { + user: user, + sync: sinon.spy + } + + module(function($provide) { + $provide.value('User', User); + }); + + inject(function(_$rootScope_, _$controller_, Groups){ + + $rootScope = _$rootScope_; + + scope = _$rootScope_.$new(); + + $controller = _$controller_; + + groups = Groups; + + // Load RootCtrl to ensure shared behaviors are loaded + $controller('RootCtrl', {$scope: scope, User: User}); + + ctrl = $controller('PartyCtrl', {$scope: scope, User: User}); + }); + }); + + describe('questAccept', function() { + it('calls Groups.questAccept', function() { + var party = {}; + var groupSpy = sinon.stub(groups, "questAccept", function(){return true;}); + scope.questAccept(party); + groupSpy.should.have.been.calledOnce; + }); + }); + + describe('questReject', function() { + it('calls Groups.questReject', function() { + var party = {}; + var groupSpy = sinon.stub(groups, "questReject", function(){return true;}); + scope.questReject(party); + groupSpy.should.have.been.calledOnce; + }); + }); + + describe('questCancel', function() { + var party, cancelSpy, windowSpy; + beforeEach(function() { + party = {}; + cancelSpy = sinon.stub(groups, "questCancel", function(){return true;}); + }); + + afterEach(function() { + windowSpy.restore(); + cancelSpy.restore(); + }); + + it('calls Groups.questCancel when alert box is confirmed', function() { + windowSpy = sinon.stub(window, "confirm", function(){return true}); + + scope.questCancel(party); + windowSpy.should.have.been.calledOnce; + windowSpy.should.have.been.calledWith(window.env.t('sureCancel')); + cancelSpy.should.have.been.calledOnce; + }); + + it('does not call Groups.questCancel when alert box is confirmed', function() { + windowSpy = sinon.stub(window, "confirm", function(){return false}); + + scope.questCancel(party); + windowSpy.should.have.been.calledOnce; + cancelSpy.should.not.have.been.calledOnce; + }); + }); + + describe('questAbort', function() { + var party, abortSpy, windowSpy; + beforeEach(function() { + party = {}; + abortSpy = sinon.stub(groups, "questAbort", function(){return true;}); + }); + + afterEach(function() { + windowSpy.restore(); + abortSpy.restore(); + }); + + it('calls Groups.questAbort when two alert boxes are confirmed', function() { + windowSpy = sinon.stub(window, "confirm", function(){return true}); + + scope.questAbort(party); + windowSpy.should.have.been.calledTwice; + windowSpy.should.have.been.calledWith(window.env.t('sureAbort')); + windowSpy.should.have.been.calledWith(window.env.t('doubleSureAbort')); + abortSpy.should.have.been.calledOnce; + }); + + it('does not call Groups.questAbort when first alert box is not confirmed', function() { + windowSpy = sinon.stub(window, "confirm", function(){return false}); + + scope.questAbort(party); + windowSpy.should.have.been.calledOnce; + windowSpy.should.have.been.calledWith(window.env.t('sureAbort')); + windowSpy.should.not.have.been.calledWith(window.env.t('doubleSureAbort')); + abortSpy.should.not.have.been.calledOnce; + }); + + it('does not call Groups.questAbort when first alert box is confirmed but second one is not', function() { + // Hack to confirm first window, but not second + var shouldReturn = false; + windowSpy = sinon.stub(window, "confirm", function(){ + shouldReturn = !shouldReturn; + return shouldReturn; + }); + + scope.questAbort(party); + windowSpy.should.have.been.calledTwice; + windowSpy.should.have.been.calledWith(window.env.t('sureAbort')); + windowSpy.should.have.been.calledWith(window.env.t('doubleSureAbort')); + abortSpy.should.not.have.been.calledOnce; + }); + }); +}); diff --git a/test/spec/groupServicesSpec.js b/test/spec/groupServicesSpec.js index 9d38dd7372..f0bfb3a538 100644 --- a/test/spec/groupServicesSpec.js +++ b/test/spec/groupServicesSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('groupServices', function() { - var $httpBackend, $http, groups; + var $httpBackend, $http, groups, user; beforeEach(function() { module(function($provide) { @@ -11,6 +11,8 @@ describe('groupServices', function() { inject(function(_$httpBackend_, Groups, User) { $httpBackend = _$httpBackend_; groups = Groups; + user = User; + user.sync = function(){}; }); }); @@ -38,4 +40,100 @@ describe('groupServices', function() { $httpBackend.flush(); }); + context('quest function wrappers', function() { + var successPromise = function() { + return { + then: function(success, failure) { + success(); + } + } + } + + var successParty = { + $questAccept: successPromise, + $questReject: successPromise, + $questCancel: successPromise, + $questAbort: successPromise + } + + var failPromise = function() { + return { + then: function(success, failure) { + failure('fail'); + } + } + } + + var failParty = { + $questAccept: failPromise, + $questReject: failPromise, + $questCancel: failPromise, + $questAbort: failPromise + } + + beforeEach(function() { + sinon.spy(user, 'sync'); + sinon.stub(console, 'log', function(arg) { return true; }); + }); + + afterEach(function() { + user.sync.restore(); + console.log.restore(); + }); + + describe('questAccept', function() { + it('syncs user if $questAccept succeeds', function() { + groups.questAccept(successParty); + user.sync.should.have.been.calledOnce; + }); + + it('does not sync user if $questAccept fails', function() { + groups.questAccept(failParty); + user.sync.should.not.have.been.calledOnce; + console.log.should.have.been.calledWith('fail'); + }); + }); + + describe('questReject', function() { + it('syncs user if $questReject succeeds', function() { + groups.questReject(successParty); + user.sync.should.have.been.calledOnce; + console.log.should.not.have.been.called; + }); + + it('does not sync user if $questReject fails', function() { + groups.questReject(failParty); + user.sync.should.not.have.been.calledOnce; + console.log.should.have.been.calledWith('fail'); + }); + }); + + describe('questCancel', function() { + it('syncs user if $questCancel succeeds', function() { + groups.questCancel(successParty); + user.sync.should.have.been.calledOnce; + console.log.should.not.have.been.called; + }); + + it('does not sync user if $questCancel fails', function() { + groups.questCancel(failParty); + user.sync.should.not.have.been.calledOnce; + console.log.should.have.been.calledWith('fail'); + }); + }); + + describe('questAbort', function() { + it('syncs user if $questAbort succeeds', function() { + groups.questAbort(successParty); + user.sync.should.have.been.calledOnce; + console.log.should.not.have.been.called; + }); + + it('does not sync user if $questAbort fails', function() { + groups.questAbort(failParty); + user.sync.should.not.have.been.calledOnce; + console.log.should.have.been.calledWith('fail'); + }); + }); + }); }); diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index e9cb113d20..c9eb2e37e7 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -569,33 +569,23 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' User.set({'invitations.party':{}}); } - $scope.questCancel = function(){ + $scope.questCancel = function(party){ if (!confirm(window.env.t('sureCancel'))) return; - $rootScope.party.$questCancel(); + Groups.questCancel(party); } - $scope.questAbort = function(){ + $scope.questAbort = function(party){ if (!confirm(window.env.t('sureAbort'))) return; if (!confirm(window.env.t('doubleSureAbort'))) return; - $rootScope.party.$questAbort(); + Groups.questAbort(party); } $scope.questAccept = function(party){ - party.$questAccept() - .then(function(res) { - User.sync(); - }, function(err) { - console.log(err); - }); + Groups.questAccept(party); } $scope.questReject = function(party){ - party.$questReject() - .then(function(res) { - User.sync(); - }, function(err) { - console.log(err); - }); + Groups.questReject(party); } } ]) diff --git a/website/public/js/services/groupServices.js b/website/public/js/services/groupServices.js index 03fe1a708e..ddd9a69ece 100644 --- a/website/public/js/services/groupServices.js +++ b/website/public/js/services/groupServices.js @@ -40,6 +40,13 @@ function(ApiUrl, $resource, $q, $http, User, Challenges) { // Defer loading everything until they're requested var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined}; + var syncUser = function(res) { + User.sync(); + } + var logError = function(err) { + console.log(err); + } + return { party: function(cb){ if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); @@ -65,6 +72,26 @@ function(ApiUrl, $resource, $q, $http, User, Challenges) { if (User.user.newMessages) delete User.user.newMessages[gid]; }, + questAccept: function(party){ + party.$questAccept() + .then(syncUser, logError); + }, + + questReject: function(party){ + party.$questReject() + .then(syncUser, logError); + }, + + questCancel: function(party){ + party.$questCancel() + .then(syncUser, logError); + }, + + questAbort: function(party){ + party.$questAbort() + .then(syncUser, logError); + }, + // Pass reference to party, myGuilds, publicGuilds, tavern; inside data in order to // be able to modify them directly (otherwise will be stick with cached version) data: data, diff --git a/website/views/options/social/boss.jade b/website/views/options/social/boss.jade index 1574f648e8..a626edc15d 100644 --- a/website/views/options/social/boss.jade +++ b/website/views/options/social/boss.jade @@ -30,9 +30,9 @@ mixin boss(tavern, mobile) button.btn.btn-sm.btn-warning(ng-if=':: group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)', ng-click='party.$questAccept({"force":true})')=env.t('begin') // // only the quest owner sees the cancel button UNLESS the quest owner is no longer in the quest/party and then everyone sees it: // This is commented-out until we have time to work out why it fails intermittently on the website and always on the mobile app (https://github.com/HabitRPG/habitrpg/issues/4074): - // button.btn.btn-sm.btn-danger(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)) || (! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfPendingQuest(group.quest.leader,group))', ng-click='questCancel()')=env.t('cancel') + // button.btn.btn-sm.btn-danger(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)) || (! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfPendingQuest(group.quest.leader,group))', ng-click='questCancel(party)')=env.t('cancel') // only the quest owner sees the cancel button: - button.btn.btn-sm.btn-danger(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group))', ng-click='questCancel()')=env.t('cancel') + button.btn.btn-sm.btn-danger(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group))', ng-click='questCancel(party)')=env.t('cancel') div(ng-if='group.quest.active==true') div(ng-if='::Content.quests[group.quest.key].boss',ng-init='boss=Content.quests[group.quest.key].boss;progress=group.quest.progress') @@ -109,6 +109,6 @@ mixin boss(tavern, mobile) unless tavern // // only the quest owner sees the abort button UNLESS the quest owner is no longer in the quest/party and then everyone sees it: // This is commented-out until we have time to work out why it fails intermittently on the website and always on the mobile app (https://github.com/HabitRPG/habitrpg/issues/4074): - // button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)) || ! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfRunningQuest(group.quest.leader,group)', ng-click='questAbort()')=env.t('abort') + // button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)) || ! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfRunningQuest(group.quest.leader,group)', ng-click='questAbort(party)')=env.t('abort') // only the quest owner sees the abort button: - button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group))', ng-click='questAbort()')=env.t('abort') + button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group))', ng-click='questAbort(party)')=env.t('abort')