Show accurate experience notifications (#10676)

* Show accurate experience notifications

Add unit tests for exp notifications

* use array to compute exp and lvl values for notification changes

* Add tests for user loosing xp cases
This commit is contained in:
Rene Cordier
2018-10-14 01:24:23 +07:00
committed by Matteo Pagliazzi
parent eee5f2f1df
commit fe39ef72ff
4 changed files with 173 additions and 17 deletions

View File

@@ -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();
});
});
});

View File

@@ -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;

View File

@@ -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)}`;
}

View File

@@ -29,9 +29,7 @@ export default {
},
exp (val) {
const message = getXPMessage(val);
if (message) {
this.notify(message, 'xp', 'glyphicon glyphicon-star', this.sign(val));
}
},
error (error) {
this.notify(error, 'error', 'glyphicon glyphicon-exclamation-sign');