diff --git a/test/client/unit/specs/components/notifications.js b/test/client/unit/specs/components/notifications.js index bcad135dca..be354d38e4 100644 --- a/test/client/unit/specs/components/notifications.js +++ b/test/client/unit/specs/components/notifications.js @@ -2,12 +2,14 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import NotificationsComponent from 'client/components/notifications.vue'; import Store from 'client/libs/store'; import { hasClass } from 'client/store/getters/members'; +import { toNextLevel } from 'common/script/statHelpers'; const localVue = createLocalVue(); localVue.use(Store); describe('Notifications', () => { let store; + let wrapper; beforeEach(() => { store = new Store({ @@ -29,16 +31,22 @@ describe('Notifications', () => { actions: { 'user:fetch': () => {}, 'tasks:fetchUserTasks': () => {}, + 'snackbars:add': () => {}, }, getters: { 'members:hasClass': hasClass, }, }); + wrapper = shallowMount(NotificationsComponent, { + store, + localVue, + mocks: { + $t: (string) => string, + }, + }); }); it('set user has class computed prop', () => { - const wrapper = shallowMount(NotificationsComponent, { store, localVue }); - expect(wrapper.vm.userHasClass).to.be.false; store.state.user.data.stats.lvl = 10; @@ -47,4 +55,130 @@ describe('Notifications', () => { expect(wrapper.vm.userHasClass).to.be.true; }); + describe('user exp notifcation', () => { + it('notifies when user gets more exp', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevel = 10; + store.state.user.data.stats.lvl = userLevel; + + const userExpBefore = 10; + const userExpAfter = 12; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel); + + expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore); + expSpy.restore(); + }); + + it('when user levels with exact xp', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevelBefore = 9; + const userLevelAfter = 10; + store.state.user.data.stats.lvl = userLevelAfter; + + const expEarned = 5; + const userExpBefore = toNextLevel(userLevelBefore) - expEarned; + const userExpAfter = 0; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore); + + expect(expSpy).to.be.calledWith(expEarned); + expSpy.restore(); + }); + + it('when user levels with exact more exp than needed', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevelBefore = 9; + const userLevelAfter = 10; + store.state.user.data.stats.lvl = userLevelAfter; + + const expEarned = 10; + const expNeeded = 5; + const userExpBefore = toNextLevel(userLevelBefore) - expNeeded; + const userExpAfter = 5; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore); + + expect(expSpy).to.be.calledWith(expEarned); + expSpy.restore(); + }); + + it('when user has more exp than needed then levels', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevelBefore = 9; + const userLevelAfter = 10; + store.state.user.data.stats.lvl = userLevelAfter; + + const expEarned = 10; + const expNeeded = -5; + const userExpBefore = toNextLevel(userLevelBefore) - expNeeded; + const userExpAfter = 15; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore); + + expect(expSpy).to.be.calledWith(expEarned); + expSpy.restore(); + }); + + it('when user multilevels', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevelBefore = 8; + const userLevelAfter = 10; + store.state.user.data.stats.lvl = userLevelAfter; + + const expEarned = 10 + toNextLevel(userLevelBefore + 1); + const expNeeded = 5; + const userExpBefore = toNextLevel(userLevelBefore) - expNeeded; + const userExpAfter = 5; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore); + + expect(expSpy).to.be.calledWith(expEarned); + expSpy.restore(); + }); + + it('when user looses xp', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevel = 10; + store.state.user.data.stats.lvl = userLevel; + + const userExpBefore = 10; + const userExpAfter = 5; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel); + + expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore); + expSpy.restore(); + }); + + it('when user looses xp under 0', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevel = 10; + store.state.user.data.stats.lvl = userLevel; + + const userExpBefore = 5; + const userExpAfter = -3; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel); + + expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore); + expSpy.restore(); + }); + + it('when user dies', () => { + const expSpy = sinon.spy(wrapper.vm, 'exp'); + + const userLevelBefore = 10; + const userLevelAfter = 9; + store.state.user.data.stats.lvl = userLevelAfter; + + const expEarned = -20; + const userExpBefore = 20; + const userExpAfter = 0; + wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore); + + expect(expSpy).to.be.calledWith(expEarned); + expSpy.restore(); + }); + }); }); diff --git a/website/client/components/notifications.vue b/website/client/components/notifications.vue index 96d097ac9f..3c5679e0ad 100644 --- a/website/client/components/notifications.vue +++ b/website/client/components/notifications.vue @@ -88,6 +88,7 @@ import axios from 'axios'; import moment from 'moment'; import throttle from 'lodash/throttle'; +import { toNextLevel } from '../../common/script/statHelpers'; import { shouldDo } from '../../common/script/cron'; import { mapState } from 'client/libs/store'; import notifications from 'client/mixins/notifications'; @@ -186,10 +187,8 @@ export default { ...mapState({ user: 'user.data', userHp: 'user.data.stats.hp', - userExp: 'user.data.stats.exp', userGp: 'user.data.stats.gp', userMp: 'user.data.stats.mp', - userLvl: 'user.data.stats.lvl', userNotifications: 'user.data.notifications', userAchievements: 'user.data.achievements', // @TODO: does this watch deeply? armoireEmpty: 'user.data.flags.armoireEmpty', @@ -204,6 +203,9 @@ export default { invitedToQuest () { return this.user.party.quest.RSVPNeeded && !this.user.party.quest.completed; }, + userExpAndLvl () { + return [this.user.stats.exp, this.user.stats.lvl]; + }, }, watch: { userHp (after, before) { @@ -222,11 +224,6 @@ export default { if (after < 0) this.playSound('Minus_Habit'); }, - userExp (after, before) { - if (after === before) return; - if (this.user.stats.lvl === 0) return; - this.exp(after - before); - }, userGp (after, before) { if (after === before) return; if (this.user.stats.lvl === 0) return; @@ -252,10 +249,6 @@ export default { const mana = after - before; this.mp(mana); }, - userLvl (after, before) { - if (after <= before || this.$store.state.isRunningYesterdailies) return; - this.showLevelUpNotifications(after); - }, userClassSelect (after) { if (this.user.needsCron) return; if (!after) return; @@ -279,6 +272,9 @@ export default { if (after !== true) return; this.$root.$emit('bv::show::modal', 'quest-invitation'); }, + userExpAndLvl (after, before) { + this.displayUserExpAndLvlNotifications(after[0], before[0], after[1], before[1]); + }, }, mounted () { Promise.all([ @@ -310,6 +306,35 @@ export default { document.removeEventListener('keydown', this.checkNextCron); }, methods: { + displayUserExpAndLvlNotifications (afterExp, beforeExp, afterLvl, beforeLvl) { + if (afterExp === beforeExp && afterLvl === beforeLvl) return; + + // XP evaluation + if (afterExp !== beforeExp) { + if (this.user.stats.lvl === 0) return; + + const lvlUps = afterLvl - beforeLvl; + let exp = afterExp - beforeExp; + + if (lvlUps > 0) { + let level = Math.trunc(beforeLvl); + exp += toNextLevel(level); + + // loop if more than 1 lvl up + for (let i = 1; i < lvlUps; i += 1) { + level += 1; + exp += toNextLevel(level); + } + } + this.exp(exp); + } + + // Lvl evaluation + if (afterLvl !== beforeLvl) { + if (afterLvl <= beforeLvl || this.$store.state.isRunningYesterdailies) return; + this.showLevelUpNotifications(afterLvl); + } + }, checkUserAchievements () { if (this.user.needsCron) return; diff --git a/website/client/libs/notifications.js b/website/client/libs/notifications.js index b39c4bff21..0c1c696560 100644 --- a/website/client/libs/notifications.js +++ b/website/client/libs/notifications.js @@ -47,6 +47,5 @@ export function round (number, nDigits) { } export function getXPMessage (val) { - if (val < -50) return; // don't show when they level up (resetting their exp) return `${getSign(val)} ${round(val)}`; } \ No newline at end of file diff --git a/website/client/mixins/notifications.js b/website/client/mixins/notifications.js index 0557e308ca..40b4b0e01c 100644 --- a/website/client/mixins/notifications.js +++ b/website/client/mixins/notifications.js @@ -29,9 +29,7 @@ export default { }, exp (val) { const message = getXPMessage(val); - if (message) { - this.notify(message, 'xp', 'glyphicon glyphicon-star', this.sign(val)); - } + this.notify(message, 'xp', 'glyphicon glyphicon-star', this.sign(val)); }, error (error) { this.notify(error, 'error', 'glyphicon glyphicon-exclamation-sign');