From b32e521c563266b0715d6c080acfbfa27d7b08dd Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 15 Sep 2015 09:32:24 -0500 Subject: [PATCH] Pull in client side changes for quest routes --- test/spec/controllers/inventoryCtrlSpec.js | 66 ++++ test/spec/controllers/partyCtrlSpec.js | 228 +++++++---- test/spec/services/groupServicesSpec.js | 63 --- test/spec/services/questServicesSpec.js | 372 +++++++++++++++--- website/public/js/services/groupServices.js | 49 +-- website/public/js/services/questServices.js | 106 +++-- .../options/social/quests/questActive.jade | 8 +- .../options/social/quests/questNotActive.jade | 12 +- website/views/shared/modals/quests.jade | 8 +- 9 files changed, 620 insertions(+), 292 deletions(-) diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index acdcc86840..f1c5bc3b73 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -157,6 +157,72 @@ describe('Inventory Controller', function() { }); }); + describe('#buyQuest', function() { + var quests, questObject; + + beforeEach(inject(function(Quests) { + quests = Quests; + questObject = { key: 'whale' }; + + sandbox.stub(quests, 'buyQuest').returns({ then: function(res) { res(questObject); } }); + })); + + it('calls Quests.buyQuest', function() { + scope.buyQuest('foo'); + + expect(quests.buyQuest).to.be.calledOnce; + expect(quests.buyQuest).to.be.calledWith('foo'); + }); + + it('sets selectedQuest to resolved quest object', function() { + scope.buyQuest('whale'); + + expect(rootScope.selectedQuest).to.eql(questObject); + }); + + it('opens buyQuest modal', function() { + sandbox.spy(rootScope, 'openModal'); + + scope.buyQuest('whale'); + + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('buyQuest', {controller: 'InventoryCtrl'}); + }); + }); + + describe('#showQuest', function() { + var quests, questObject; + + beforeEach(inject(function(Quests) { + quests = Quests; + questObject = { key: 'whale' }; + + sandbox.stub(quests, 'showQuest').returns({ then: function(res) { res(questObject); } }); + })); + + it('calls Quests.showQuest', function() { + scope.showQuest('foo'); + + expect(quests.showQuest).to.be.calledOnce; + expect(quests.showQuest).to.be.calledWith('foo'); + }); + + it('sets selectedQuest to resolved quest object', function() { + scope.showQuest('whale'); + + expect(rootScope.selectedQuest).to.eql(questObject); + }); + + it('opens showQuest modal', function() { + sandbox.spy(rootScope, 'openModal'); + + scope.showQuest('whale'); + + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('showQuest', {controller: 'InventoryCtrl'}); + }); + }); + describe('#hasAllTimeTravelerItems', function() { it('returns false if there are items left in the time traveler store', function() { expect(scope.hasAllTimeTravelerItems()).to.eql(false); diff --git a/test/spec/controllers/partyCtrlSpec.js b/test/spec/controllers/partyCtrlSpec.js index 30d7925b01..092072988d 100644 --- a/test/spec/controllers/partyCtrlSpec.js +++ b/test/spec/controllers/partyCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe("Party Controller", function() { - var scope, ctrl, user, User, groups, rootScope, $controller; + var scope, ctrl, user, User, questsService, groups, rootScope, $controller; beforeEach(function() { user = specHelper.newUser(), @@ -15,7 +15,7 @@ describe("Party Controller", function() { $provide.value('User', User); }); - inject(function(_$rootScope_, _$controller_, Groups){ + inject(function(_$rootScope_, _$controller_, Groups, Quests){ rootScope = _$rootScope_; @@ -24,6 +24,7 @@ describe("Party Controller", function() { $controller = _$controller_; groups = Groups; + questsService = Quests; // Load RootCtrl to ensure shared behaviors are loaded $controller('RootCtrl', {$scope: scope, User: User}); @@ -33,129 +34,180 @@ describe("Party Controller", function() { }); describe('questAccept', function() { - it('calls Groups.questAccept', function() { - var party = {}; - var groupSpy = sandbox.stub(groups, "questAccept", function(){return true;}); - scope.questAccept(party); - groupSpy.should.have.been.calledOnce; + beforeEach(function() { + scope.group = { + quest: { members: { 'user-id': true } } + }; + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); + }); + + it('calls Quests.sendAction', function() { + scope.questAccept(); + + expect(questsService.sendAction).to.be.calledOnce; + expect(questsService.sendAction).to.be.calledWith('questAccept'); + }); + + + it('updates quest object with new participants list', function() { + scope.group.quest = { + members: { user: true, another: true } + }; + + scope.questAccept(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); describe('questReject', function() { - it('calls Groups.questReject', function() { - var party = {}; - var groupSpy = sandbox.stub(groups, "questReject", function(){return true;}); - scope.questReject(party); - groupSpy.should.have.been.calledOnce; + beforeEach(function() { + scope.group = { + quest: { members: { 'user-id': true } } + }; + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); + }); + + it('calls Quests.sendAction', function() { + scope.questReject(); + + expect(questsService.sendAction).to.be.calledOnce; + expect(questsService.sendAction).to.be.calledWith('questReject'); + }); + + + it('updates quest object with new participants list', function() { + scope.group.quest = { + members: { user: true, another: true } + }; + + scope.questReject(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); describe('questCancel', function() { var party, cancelSpy, windowSpy; beforeEach(function() { - party = {}; - cancelSpy = sandbox.stub(groups, "questCancel", function(){return true;}); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); - afterEach(function() { - windowSpy.restore(); - cancelSpy.restore(); + it('calls Quests.sendAction when alert box is confirmed', function() { + sandbox.stub(window, "confirm").returns(true); + + scope.questCancel(); + + expect(window.confirm).to.be.calledOnce; + expect(window.confirm).to.be.calledWith(window.env.t('sureCancel')); + expect(questsService.sendAction).to.be.calledOnce; + expect(questsService.sendAction).to.be.calledWith('questCancel'); }); - it('calls Groups.questCancel when alert box is confirmed', function() { - windowSpy = sandbox.stub(window, "confirm", function(){return true}); + it('does not call Quests.sendAction when alert box is not confirmed', function() { + sandbox.stub(window, "confirm").returns(false); - scope.questCancel(party); - windowSpy.should.have.been.calledOnce; - windowSpy.should.have.been.calledWith(window.env.t('sureCancel')); - cancelSpy.should.have.been.calledOnce; - }); + scope.questCancel(); - it('does not call Groups.questCancel when alert box is not confirmed', function() { - windowSpy = sandbox.stub(window, "confirm", function(){return false}); - - scope.questCancel(party); - windowSpy.should.have.been.calledOnce; - cancelSpy.should.not.have.been.calledOnce; + expect(window.confirm).to.be.calledOnce; + expect(questsService.sendAction).to.not.be.called; }); }); describe('questAbort', function() { - var party, abortSpy, windowSpy; beforeEach(function() { - party = {}; - abortSpy = sandbox.stub(groups, "questAbort", function(){return true;}); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); - afterEach(function() { - windowSpy.restore(); - abortSpy.restore(); + it('calls Quests.sendAction when two alert boxes are confirmed', function() { + sandbox.stub(window, "confirm", function(){return true}); + + scope.questAbort(); + expect(window.confirm).to.be.calledTwice; + expect(window.confirm).to.be.calledWith(window.env.t('sureAbort')); + expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort')); + + expect(questsService.sendAction).to.be.calledOnce; + expect(questsService.sendAction).to.be.calledWith('questAbort'); }); - it('calls Groups.questAbort when two alert boxes are confirmed', function() { - windowSpy = sandbox.stub(window, "confirm", function(){return true}); + it('does not call Quests.sendAction when first alert box is not confirmed', function() { + sandbox.stub(window, "confirm", function(){return false}); - 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; + scope.questAbort(); + + expect(window.confirm).to.be.calledOnce; + expect(window.confirm).to.be.calledWith(window.env.t('sureAbort')); + expect(window.confirm).to.not.be.calledWith(window.env.t('doubleSureAbort')); + + expect(questsService.sendAction).to.not.be.called; }); - it('does not call Groups.questAbort when first alert box is not confirmed', function() { - windowSpy = sandbox.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() { + it('does not call Quests.sendAction when first alert box is confirmed but second one is not', function() { // Hack to confirm first window, but not second + // Should not be necessary when we upgrade sinon var shouldReturn = false; - windowSpy = sandbox.stub(window, "confirm", function(){ + sandbox.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; + scope.questAbort(); + + expect(window.confirm).to.be.calledTwice; + expect(window.confirm).to.be.calledWith(window.env.t('sureAbort')); + expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort')); + expect(questsService.sendAction).to.not.be.called; }); }); describe('#questLeave', function() { - var party, leaveSpy, windowSpy; - beforeEach(function() { - party = {}; scope.group = { quest: { members: { 'user-id': true } } }; - leaveSpy = sandbox.stub(groups, 'questLeave').returns({ - then: sandbox.stub().yields() + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) }); }); - it('calls Groups.questLeave when alert box is confirmed', function() { - windowSpy = sandbox.stub(window, "confirm").returns(true); + it('calls Quests.sendAction when alert box is confirmed', function() { + sandbox.stub(window, "confirm").returns(true); - scope.questLeave(party); - windowSpy.should.have.been.calledOnce; - windowSpy.should.have.been.calledWith(window.env.t('sureLeave')); - leaveSpy.should.have.been.calledOnce; + scope.questLeave(); + + expect(window.confirm).to.be.calledOnce; + expect(window.confirm).to.be.calledWith(window.env.t('sureLeave')); + expect(questsService.sendAction).to.be.calledOnce; + expect(questsService.sendAction).to.be.calledWith('questLeave'); }); - it('does not call Groups.questLeave when alert box is not confirmed', function() { - windowSpy = sandbox.stub(window, "confirm").returns(false); + it('does not call Quests.sendAction when alert box is not confirmed', function() { + sandbox.stub(window, "confirm").returns(false); - scope.questLeave(party); - windowSpy.should.have.been.calledOnce; - leaveSpy.should.not.have.been.calledOnce; + scope.questLeave(); + + expect(window.confirm).to.be.calledOnce; + questsService.sendAction.should.not.have.been.calledOnce; + }); + + it('updates quest object with new participants list', function() { + scope.group.quest = { + members: { user: true, another: true } + }; + sandbox.stub(window, "confirm").returns(true); + + scope.questLeave(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); @@ -241,4 +293,28 @@ describe("Party Controller", function() { }); }); }); + + describe('#canEditQuest', function() { + var party; + + beforeEach(function() { + party = specHelper.newGroup({ + type: 'party', + leader: {}, + quest: {} + }); + }); + + it('returns false if user is not the quest leader', function() { + party.quest.leader = 'another-user'; + + expect(scope.canEditQuest(party)).to.eql(false); + }); + + it('returns true if user is quest leader', function() { + party.quest.leader = 'unique-user-id'; + + expect(scope.canEditQuest(party)).to.eql(true); + }); + }); }); diff --git a/test/spec/services/groupServicesSpec.js b/test/spec/services/groupServicesSpec.js index ff3d12ba98..e9a9285265 100644 --- a/test/spec/services/groupServicesSpec.js +++ b/test/spec/services/groupServicesSpec.js @@ -39,67 +39,4 @@ describe('groupServices', function() { groups.myGuilds(); $httpBackend.flush(); }); - - context('quest function wrappers', function() { - var successPromise, failPromise; - - beforeEach(function() { - sandbox.spy(user, 'sync'); - sandbox.stub(console, 'log'); - - successPromise = sandbox.stub().returns({ - then: function(success, failure) { - return success(); - } - }); - - failPromise = sandbox.stub().returns({ - then: function(success, failure) { - return failure('fail'); - } - }); - }); - - var questFunctions = [ - 'questAccept', - 'questReject', - 'questCancel', - 'questAbort', - 'questLeave' - ]; - - for (var i in questFunctions) { - var questFunc = questFunctions[i]; - - describe('#' + questFunc, function() { - it('calls party.$' + questFunc, function() { - var party = { }; - party['$' + questFunc] = successPromise; - - groups[questFunc](party); - - expect(party['$' + questFunc]).to.be.calledOnce; - }); - - it('syncs user if $' + questFunc + ' succeeds', function() { - var successParty = { }; - successParty['$' + questFunc] = successPromise; - - groups[questFunc](successParty); - - user.sync.should.have.been.calledOnce; - }); - - it('does not sync user if $' + questFunc + ' fails', function() { - var failParty = { }; - failParty['$' + questFunc] = failPromise; - - groups[questFunc](failParty); - - user.sync.should.not.have.been.calledOnce; - console.log.should.have.been.calledWith('fail'); - }); - }); - } - }); }); diff --git a/test/spec/services/questServicesSpec.js b/test/spec/services/questServicesSpec.js index ef5f93e7ce..c9f3e05c21 100644 --- a/test/spec/services/questServicesSpec.js +++ b/test/spec/services/questServicesSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Quests Service', function() { - var scope, rootScope, groupsService, quest, questsService, user, content; + var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy; beforeEach(function() { user = specHelper.newUser(); @@ -16,105 +16,365 @@ describe('Quests Service', function() { $provide.value('User', {user: user}); }); - inject(function($rootScope, $controller, Quests, Groups, Content) { - scope = $rootScope.$new(); - rootScope = $rootScope; - $controller('RootCtrl', {$scope: scope, User: {user: user}}); + inject(function(Quests, Groups, Content) { questsService = Quests; groupsService = Groups; content = Content; }); sandbox.stub(groupsService, 'inviteOrStartParty'); - sandbox.stub(rootScope, 'openModal'); - sandbox.stub(window,'confirm',function(){return true}); + sandbox.stub(window,'confirm'); sandbox.stub(window,'alert'); + resolveSpy = sandbox.spy(); + rejectSpy = sandbox.spy(); }); - context('functions', function() { + describe('#lockQuest', function() { - describe('lock quest', function() { + it('locks quest when user does not meet level requirement', function() { + user.stats.lvl = 15; - it('locks quest when user does not meet level requirement', function() { - user.stats.lvl = 15; + expect(questsService.lockQuest(quest)).to.be.ok; + }); - expect(questsService.lockQuest(quest)).to.be.ok; - }); + it('does not lock quest if we ignore level requirement', function() { + user.stats.lvl = 15; - it('does not lock quest if we ignore level requirement', function() { - user.stats.lvl = 15; + expect(questsService.lockQuest(quest,true)).to.not.be.ok; + }); - expect(questsService.lockQuest(quest,true)).to.not.be.ok; - }); + it('does not lock quest if user meets level requirement', function() { + user.stats.lvl = 20; - it('does not lock quest if user meets level requirement', function() { - user.stats.lvl = 20; + expect(questsService.lockQuest(quest)).to.not.be.ok; + }); - expect(questsService.lockQuest(quest)).to.not.be.ok; - }); + it('locks quest if user has not completed previous quest in series', function() { + quest.previous = 'priorQuest'; + user.stats.lvl = 25; - it('locks quest if user has not completed previous quest in series', function() { - quest.previous = 'priorQuest'; - user.stats.lvl = 25; + expect(questsService.lockQuest(quest)).to.be.ok; + }); - expect(questsService.lockQuest(quest)).to.be.ok; - }); + it('does not lock quest if user has completed previous quest in series', function() { + quest.previous = 'priorQuest'; + user.stats.lvl = 25; + user.achievements.quests.priorQuest = 1; - it('does not lock quest if user has completed previous quest in series', function() { - quest.previous = 'priorQuest'; - user.stats.lvl = 25; - user.achievements.quests.priorQuest = 1; + expect(questsService.lockQuest(quest)).to.not.be.ok; + }); + }); - expect(questsService.lockQuest(quest)).to.not.be.ok; + describe('#buyQuest', function() { + var scope; + + beforeEach(inject(function($rootScope) { + scope = $rootScope.$new(); + })); + + it('returns a promise', function() { + var promise = questsService.buyQuest('whale'); + expect(promise).to.respondTo('then'); + }); + + context('Quest key does not exist', function() { + it('rejects with message that quest is not found', function(done) { + questsService.buyQuest('foo') + .then(resolveSpy, function(rej) { + expect(rej).to.eql('No quest with that key found'); + expect(resolveSpy).to.not.be.called; + done(); + }); + + scope.$apply(); }); }); - describe('buy quest', function() { - + context('invite friends', function() { it('prompts user to invite friends to party for invite reward quests', function() { questsService.buyQuest('basilist'); - expect(window.confirm).to.have.been.calledOnce; - expect(groupsService.inviteOrStartParty).to.have.been.calledOnce; - expect(rootScope.openModal).to.have.been.notCalled; + expect(window.confirm).to.be.calledOnce; + expect(window.confirm).to.be.calledWith(env.t('mustInviteFriend')); }); - it('does not allow user to buy quests whose previous quests are incomplete', function() { + it('rejects promise if confirm is cancelled', function(done) { + window.confirm.returns(false); + + questsService.buyQuest('basilist') + .then(resolveSpy, function(rej) { + expect(rej).to.eql('Did not want to invite friends'); + expect(window.confirm).to.be.calledOnce; + expect(groupsService.inviteOrStartParty).to.not.be.called; + done(); + }); + + scope.$apply(); + }); + + it('rejects promise if confirm is cofirmed and calls groups service', function(done) { + window.confirm.returns(true); + + questsService.buyQuest('basilist') + .then(resolveSpy, function(rej) { + expect(rej).to.eql('Invite or start party'); + expect(window.confirm).to.be.calledOnce; + expect(groupsService.inviteOrStartParty).to.be.calledOnce; + done(); + }); + + scope.$apply(); + }); + }); + + context('quests in a series', function() { + it('does not allow user to buy subsquent quests in a series if user has no quest achievements', function(done) { user.stats.lvl = 100; + user.achievements.quests = undefined; - questsService.buyQuest('goldenknight2'); + questsService.buyQuest('goldenknight2') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(res).to.eql('unlockByQuesting'); + expect(resolveSpy).to.not.be.called; + done(); + }); - expect(window.alert).to.have.been.calledOnce; - expect(rootScope.openModal).to.have.been.notCalled; + scope.$apply(); }); - it('does not allow user to buy quests beyond their level', function() { + it('does not allow user to buy quests whose previous quests are incomplete', function(done) { + user.stats.lvl = 100; + user.achievements.quests = { + 'atom1': 1 + }; + + questsService.buyQuest('goldenknight2') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(resolveSpy).to.not.be.called; + done(); + }); + + scope.$apply(); + }); + }); + + context('quests with level requirement', function() { + it('does not allow user to buy quests beyond their level', function(done) { user.stats.lvl = 1; - questsService.buyQuest('vice1'); + questsService.buyQuest('vice1') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(res).to.eql('mustLvlQuest'); + done(); + }); - expect(window.alert).to.have.been.calledOnce; - expect(rootScope.openModal).to.have.been.notCalled; + scope.$apply(); }); - it('opens purchase modal if Gem quest prerequisites are met', function() { - user.stats.lvl = 100; - user.achievements.quests.atom1 = 2; + it('allows user to buy quest if they meet level requirement', function(done) { + user.stats.lvl = 30; - questsService.buyQuest('atom2'); + questsService.buyQuest('vice1') + .then(function(res) { + expect(res).to.eql(content.quests.vice1); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); - expect(scope.selectedQuest).to.eql(content.quests.atom2); - expect(rootScope.openModal).to.have.been.calledOnce; - expect(rootScope.openModal).to.have.been.calledWith('buyQuest'); + scope.$apply(); }); + }); - it('opens purchase modal if quest is Gold-purchasable', function() { - questsService.buyQuest('dilatoryDistress1'); + context('gold purchasable quests', function() { + it('sends quest object', function(done) { + questsService.buyQuest('dilatoryDistress1') + .then(function(res) { + expect(res).to.eql(content.quests.dilatoryDistress1); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); - expect(scope.selectedQuest).to.eql(content.quests.dilatoryDistress1); - expect(rootScope.openModal).to.have.been.calledOnce; - expect(rootScope.openModal).to.have.been.calledWith('buyQuest'); + scope.$apply(); + }); + }); + + context('all other quests', function() { + it('sends quest object', function(done) { + questsService.buyQuest('whale') + .then(function(res) { + expect(res).to.eql(content.quests.whale); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); + + scope.$apply(); }); }); }); + + describe('#showQuest', function() { + var scope; + + beforeEach(inject(function($rootScope) { + scope = $rootScope.$new(); + })); + + it('returns a promise', function() { + var promise = questsService.showQuest('whale'); + expect(promise).to.respondTo('then'); + }); + + context('Quest key does not exist', function() { + it('rejects with message that quest is not found', function(done) { + questsService.showQuest('foo') + .then(resolveSpy, function(rej) { + expect(rej).to.eql('No quest with that key found'); + expect(resolveSpy).to.not.be.called; + done(); + }); + + scope.$apply(); + }); + }); + + context('quests in a series', function() { + it('does not allow user to buy subsquent quests in a series if user has no quest achievements', function(done) { + user.stats.lvl = 100; + user.achievements.quests = undefined; + + questsService.showQuest('goldenknight2') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(res).to.eql('unlockByQuesting'); + expect(resolveSpy).to.not.be.called; + done(); + }); + + scope.$apply(); + }); + + it('does not allow user to buy quests whose previous quests are incomplete', function(done) { + user.stats.lvl = 100; + user.achievements.quests = { + 'atom1': 1 + }; + + questsService.showQuest('goldenknight2') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(resolveSpy).to.not.be.called; + done(); + }); + + scope.$apply(); + }); + }); + + context('quests with level requirement', function() { + it('does not allow user to buy quests beyond their level', function(done) { + user.stats.lvl = 1; + + questsService.showQuest('vice1') + .then(resolveSpy, function(res) { + expect(window.alert).to.have.been.calledOnce; + expect(res).to.eql('mustLvlQuest'); + done(); + }); + + scope.$apply(); + }); + + it('allows user to buy quest if they meet level requirement', function(done) { + user.stats.lvl = 30; + + questsService.showQuest('vice1') + .then(function(res) { + expect(res).to.eql(content.quests.vice1); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); + + scope.$apply(); + }); + }); + + context('gold purchasable quests', function() { + it('sends quest object', function(done) { + questsService.showQuest('dilatoryDistress1') + .then(function(res) { + expect(res).to.eql(content.quests.dilatoryDistress1); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); + + scope.$apply(); + }); + }); + + context('all other quests', function() { + it('sends quest object', function(done) { + questsService.showQuest('whale') + .then(function(res) { + expect(res).to.eql(content.quests.whale); + expect(window.alert).to.not.be.called; + expect(rejectSpy).to.not.be.called; + done(); + }, rejectSpy); + + scope.$apply(); + }); + }); + }); + + describe('#initQuest', function() { + + it('returns a promise', function() { + var promise = questsService.initQuest('whale'); + expect(promise).to.respondTo('then'); + }); + + it('accepts quest'); + + it('brings user to party page'); + }); + + describe('#sendAction', function() { + var fakeBackend, scope; + + beforeEach(inject(function($httpBackend, $rootScope) { + scope = $rootScope.$new(); + fakeBackend = $httpBackend; + + fakeBackend.when('GET', 'partials/main.html').respond({}); + fakeBackend.when('GET', '/api/v2/groups/party').respond({_id: 'party-id'}); + fakeBackend.when('POST', '/api/v2/groups/party-id/questReject').respond({quest: { key: 'whale' } }); + fakeBackend.flush(); + })); + + it('returns a promise', function() { + var promise = questsService.sendAction('questReject'); + expect(promise).to.respondTo('then'); + }); + + it('calls specified endpoint endpoint', function(done) { + fakeBackend.expectPOST('/api/v2/groups/party-id/questReject'); + + questsService.sendAction('questReject') + .then(function(res) { + expect(res.key).to.eql('whale'); + done(); + }); + + fakeBackend.flush(); + scope.$apply(); + }); + }); }); diff --git a/website/public/js/services/groupServices.js b/website/public/js/services/groupServices.js index ed1c821576..001a137d63 100644 --- a/website/public/js/services/groupServices.js +++ b/website/public/js/services/groupServices.js @@ -39,21 +39,9 @@ leave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/leave'}, invite: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/invite'}, removeMember: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/removeMember'}, - questAccept: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAccept'}, - questReject: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questReject'}, - questCancel: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questCancel'}, - questAbort: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAbort'}, - questLeave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questLeave'} + startQuest: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAccept'} }); - function _syncUser() { - User.sync(); - } - - function _logError(err) { - console.log(err); - } - function party(cb) { if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); return (cb) ? cb(party) : data.party; @@ -75,36 +63,6 @@ return data.tavern; } - function questAccept(party) { - Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount}); - return party.$questAccept() - .then(_syncUser, _logError); - } - - function questReject(party) { - Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount}); - return party.$questReject() - .then(_syncUser, _logError); - } - - function questCancel(party) { - Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount}); - return party.$questCancel() - .then(_syncUser, _logError); - } - - function questAbort(party) { - Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount}); - return party.$questAbort() - .then(_syncUser, _logError); - } - - function questLeave(party) { - Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount}); - return party.$questLeave() - .then(_syncUser, _logError); - } - function inviteOrStartParty(group) { Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'}); if (group.type === "party" || $location.$$path === "/options/groups/party") { @@ -125,11 +83,6 @@ publicGuilds: publicGuilds, myGuilds: myGuilds, tavern: tavern, - questAccept: questAccept, - questReject: questReject, - questAbort: questAbort, - questLeave: questLeave, - questCancel: questCancel, inviteOrStartParty: inviteOrStartParty, data: data, diff --git a/website/public/js/services/questServices.js b/website/public/js/services/questServices.js index 18fcc393c6..3662fd1b35 100644 --- a/website/public/js/services/questServices.js +++ b/website/public/js/services/questServices.js @@ -6,14 +6,17 @@ .factory('Quests', questsFactory); questsFactory.$inject = [ - '$rootScope', + '$http', + '$state', + '$q', + 'ApiUrl', 'Content', 'Groups', 'User', 'Analytics' ]; - function questsFactory($rootScope,Content,Groups,User,Analytics) { + function questsFactory($http, $state, $q, ApiUrl, Content, Groups, User, Analytics) { var user = User.user; var party = Groups.party(); @@ -26,21 +29,39 @@ return (quest.previous); } - function buyQuest(quest) { - var item = Content.quests[quest]; + function _preventQuestModal(quest) { + if (!quest) { + return 'No quest with that key found'; + } - if (item.unlockCondition && item.unlockCondition.condition === 'party invite') { - if (!confirm(window.env.t('mustInviteFriend'))) return; - return Groups.inviteOrStartParty(party); + if (quest.previous && (!user.achievements.quests || (user.achievements.quests && !user.achievements.quests[quest.previous]))){ + alert(window.env.t('unlockByQuesting', {title: Content.quests[quest.previous].text()})); + return 'unlockByQuesting'; } - if (item.previous && (!User.user.achievements.quests || (User.user.achievements.quests && !User.user.achievements.quests[item.previous]))){ - return alert(window.env.t('unlockByQuesting', {title: Content.quests[item.previous].text()})); + + if (quest.lvl > user.stats.lvl) { + alert(window.env.t('mustLvlQuest', {level: quest.lvl})) + return 'mustLvlQuest'; } - if (item.lvl && item.lvl > user.stats.lvl) { - return alert(window.env.t('mustLvlQuest', {level: item.lvl})); - } - $rootScope.selectedQuest = item; - $rootScope.openModal('buyQuest', {controller:'InventoryCtrl'}); + } + + function buyQuest(quest) { + return $q(function(resolve, reject) { + var item = Content.quests[quest]; + + var preventQuestModal = _preventQuestModal(item); + if (preventQuestModal) { + return reject(preventQuestModal); + } + + if (item.unlockCondition && item.unlockCondition.condition === 'party invite') { + if (!confirm(window.env.t('mustInviteFriend'))) return reject('Did not want to invite friends'); + Groups.inviteOrStartParty(party) + return reject('Invite or start party'); + } + + resolve(item); + }); } function questPopover(quest) { @@ -71,37 +92,52 @@ } function showQuest(quest) { - var item = Content.quests[quest]; - var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]); - if (!completedPrevious) - return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text()})); - if (item.lvl && item.lvl > user.stats.lvl) - return alert(window.env.t('mustLevel', {level: item.lvl})); - $rootScope.selectedQuest = item; - $rootScope.openModal('showQuest', {controller:'InventoryCtrl'}); - } + return $q(function(resolve, reject) { + var item = Content.quests[quest]; - function closeQuest(){ - $rootScope.selectedQuest = undefined; - } + var preventQuestModal = _preventQuestModal(item); + if (preventQuestModal) { + return reject(preventQuestModal); + } - function questInit(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName':$rootScope.selectedQuest.key}); - Analytics.updateUser({'partyID':party._id,'partySize':party.memberCount}); - party.$questAccept({key:$rootScope.selectedQuest.key}, function(){ - party.$get(); - $rootScope.$state.go('options.social.party'); + resolve(item); + }); + } + + function initQuest(key) { + return $q(function(resolve, reject) { + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName': key}); + Analytics.updateUser({'partyID':party._id,'partySize':party.memberCount}); + party.$startQuest({key:key}, function(){ + party.$syncParty(); + $state.go('options.social.party'); + resolve(); + }); + }); + } + + function sendAction(action) { + return $q(function(resolve, reject) { + + $http.post(ApiUrl.get() + '/api/v2/groups/' + party._id + '/' + action) + .then(function(response) { + Analytics.updateUser({ + partyID: party._id, + partySize: party.memberCount + }); + var quest = response.data.quest; + resolve(quest); + });; }); - closeQuest(); } return { lockQuest: lockQuest, buyQuest: buyQuest, questPopover: questPopover, + sendAction: sendAction, showQuest: showQuest, - closeQuest: closeQuest, - questInit: questInit + initQuest: initQuest } } }()); diff --git a/website/views/options/social/quests/questActive.jade b/website/views/options/social/quests/questActive.jade index 3f920689d4..855379c7c6 100644 --- a/website/views/options/social/quests/questActive.jade +++ b/website/views/options/social/quests/questActive.jade @@ -1,4 +1,4 @@ -div(ng-if='group.quest.active==true') +div(ng-if='group.quest.active===true') unless tavern tabset tab(heading=env.t('questDetails')) @@ -20,7 +20,7 @@ div(ng-if='group.quest.active==true') include ./ianQuestInfo unless tavern - button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfRunningQuest(group.quest.leader,group))', - ng-click='questAbort(party)')=env.t('abort') + button.btn.btn-sm.btn-warning(ng-if='::canEditQuest(party)', + ng-click='questAbort()')=env.t('abort') button.btn.btn-sm.btn-warning(ng-if='!(group.quest.leader && group.quest.leader === user._id) && isMemberOfRunningQuest(user._id,group)', - ng-click='questLeave(party)')=env.t('leaveQuest') + ng-click='questLeave()')=env.t('leaveQuest') diff --git a/website/views/options/social/quests/questNotActive.jade b/website/views/options/social/quests/questNotActive.jade index 6b56e78983..b21845d59d 100644 --- a/website/views/options/social/quests/questNotActive.jade +++ b/website/views/options/social/quests/questNotActive.jade @@ -1,4 +1,4 @@ -div(ng-if='group.quest.active==false') +div(ng-if='group.quest.active===false') tabset tab(heading=env.t('invitations')) +participants(false) @@ -22,9 +22,9 @@ div(ng-if='group.quest.active==false') p=env.t('questStart') span(ng-if='user.party.quest.RSVPNeeded') - button.btn.btn-sm.btn-success(ng-click='questAccept(party)')=env.t('accept') - button.btn.btn-sm.btn-danger(ng-click='questReject(party)')=env.t('reject') + button.btn.btn-sm.btn-success(ng-click='questAccept()')=env.t('accept') + button.btn.btn-sm.btn-danger(ng-click='questReject()')=env.t('reject') - span(ng-if='::group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)') - button.btn.btn-sm.btn-warning(ng-click='party.$questAccept({"force":true})')=env.t('begin') - button.btn.btn-sm.btn-danger(ng-click='questCancel(party)')=env.t('cancel') + span(ng-if='::canEditQuest(party)') + button.btn.btn-sm.btn-warning(ng-click='party.$startQuest({"force":true})')=env.t('begin') + button.btn.btn-sm.btn-danger(ng-click='questCancel()')=env.t('cancel') diff --git a/website/views/shared/modals/quests.jade b/website/views/shared/modals/quests.jade index 9dce32c5ff..c31963742b 100644 --- a/website/views/shared/modals/quests.jade +++ b/website/views/shared/modals/quests.jade @@ -43,7 +43,7 @@ script(type='text/ng-template', id='modals/showQuest.html') p=env.t('questWarning') .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('cancel') - button.btn.btn-primary(ng-click='questInit(); $close()') + button.btn.btn-primary(ng-click='questInit()') | {{:: party.memberCount > 1 ? env.t('inviteParty') : env.t('startQuest')}} script(type='text/ng-template', id='modals/buyQuest.html') @@ -79,8 +79,8 @@ 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='questReject(party); $close()')=env.t('reject') - button.btn.btn-primary(ng-click='questAccept(party); $close()')=env.t('accept') + button.btn.btn-default(ng-click='questReject(); $close()')=env.t('reject') + button.btn.btn-primary(ng-click='questAccept(); $close()')=env.t('accept') script(type='text/ng-template', id='modals/questDrop.html') .quest-icon.pull-right(class='inventory_quest_scroll_{{::selectedQuest.key}}') @@ -95,7 +95,7 @@ script(type='text/ng-template', id='modals/questDrop.html') .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('questLater') button.btn.btn-primary(ng-click='inviteOrStartParty(group); $close()', ng-if='!party.members')=env.t('startAParty') - button.btn.btn-primary(ng-click='questInit(); $close()', ng-if='party.members')=env.t('inviteParty') + button.btn.btn-primary(ng-click='questInit()', ng-if='party.members')=env.t('inviteParty') script(type='text/ng-template', id='modals/ownedQuests.html') .modal-header