mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 05:07:22 +01:00
* remove commented-out code for purging PMs - no longer needed https://github.com/HabitRPG/habitica/issues/7940#issuecomment-406489506 * adjust comments * move cron code when sleeping / resting back into main body of cron code * rename tests to use consistent terminology for sleeping * add tests for cron when user is sleeping * move sleeping tests to same place as non-sleeping test This matches how the code has sleeping and non-sleeping code mingled. * replace a broken test with new tests The deleted test wasn't working correctly. The check that the user's health hadn't decreased would have worked even if the user wasn't sleeping because the Daily had been marked completed. The new tests test both no damage from incomplete Dailies and that Dailies are reset. * add tests for Perfect Day buff and rename existing tests for consistent terminology * remove old test code
This commit is contained in:
@@ -65,6 +65,12 @@ describe('cron', () => {
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls analytics when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', () => {
|
||||
beforeEach(() => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -655,76 +661,6 @@ describe('cron', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('user is sleeping', () => {
|
||||
beforeEach(() => {
|
||||
user.preferences.sleep = true;
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('clears user buffs', () => {
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 1,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('resets all dailies without damaging user', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
let healthBefore = user.stats.hp;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
expect(user.stats.hp).to.equal(healthBefore);
|
||||
});
|
||||
|
||||
it('sets isDue for daily', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].isDue).to.be.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('todos', () => {
|
||||
beforeEach(() => {
|
||||
let todo = {
|
||||
@@ -846,6 +782,15 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
it('computes isDue when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().toDate();
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||
});
|
||||
|
||||
it('computes nextDue', () => {
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
@@ -865,6 +810,13 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should set tasks completed to false when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys', () => {
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
@@ -872,6 +824,14 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for dailys with scheduled misses', () => {
|
||||
daysMissed = 10;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
@@ -884,12 +844,19 @@ describe('cron', () => {
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
@@ -930,7 +897,7 @@ describe('cron', () => {
|
||||
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
|
||||
});
|
||||
|
||||
it('should decrement quest progress down for missing a daily', () => {
|
||||
it('should decrement quest.progress.down for missing a daily', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
@@ -939,6 +906,16 @@ describe('cron', () => {
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
|
||||
it('should not decrement quest.progress.down for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let progress = cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(progress.down).to.equal(0);
|
||||
});
|
||||
|
||||
it('should do damage for only yesterday\'s dailies', () => {
|
||||
daysMissed = 3;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1017,7 +994,7 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset habit counters even if user is resting in the Inn', () => {
|
||||
it('should reset habit counters even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
@@ -1278,7 +1255,23 @@ describe('cron', () => {
|
||||
expect(user.achievements.perfect).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments user buffs if all (at least 1) due dailies were completed', () => {
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1317,6 +1310,31 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (no due dailys) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).add({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
@@ -1341,7 +1359,50 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cronOverride({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
@@ -1373,6 +1434,20 @@ describe('cron', () => {
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('should not add mp to user when user is sleeping', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
|
||||
user.preferences.sleep = true;
|
||||
let mpBefore = user.stats.mp;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, {maxMP: 100}));
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.mp).to.equal(mpBefore);
|
||||
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
@@ -1568,7 +1643,7 @@ describe('cron', () => {
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
||||
it('increments loginIncentives by 1 even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
|
||||
@@ -172,27 +172,6 @@ function resetHabitCounters (user, tasksByType, now, daysMissed) {
|
||||
});
|
||||
}
|
||||
|
||||
function performSleepTasks (user, tasksByType, now, daysMissed) {
|
||||
user.stats.buffs = _.cloneDeep(CLEAR_BUFFS);
|
||||
|
||||
tasksByType.dailys.forEach((daily) => {
|
||||
let completed = daily.completed;
|
||||
let thatDay = moment(now).subtract({days: 1});
|
||||
|
||||
if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) {
|
||||
// TODO also untick checklists if the Daily was due on previous missed days, if two or more days were missed at once -- https://github.com/HabitRPG/habitica/pull/7218#issuecomment-219256016
|
||||
if (daily.checklist) {
|
||||
daily.checklist.forEach(box => box.completed = false);
|
||||
}
|
||||
}
|
||||
|
||||
daily.completed = false;
|
||||
setIsDueNextDue(daily, user, now);
|
||||
});
|
||||
|
||||
resetHabitCounters(user, tasksByType, now, daysMissed);
|
||||
}
|
||||
|
||||
function trackCronAnalytics (analytics, user, _progress, options) {
|
||||
analytics.track('Cron', {
|
||||
category: 'behavior',
|
||||
@@ -294,14 +273,6 @@ export function cron (options = {}) {
|
||||
user.loginIncentives++;
|
||||
awardLoginIncentives(user);
|
||||
|
||||
// User is resting at the inn.
|
||||
// On cron, buffs are cleared and all dailies are reset without performing damage
|
||||
if (user.preferences.sleep === true) {
|
||||
performSleepTasks(user, tasksByType, now, daysMissed);
|
||||
trackCronAnalytics(analytics, user, _progress, options);
|
||||
return;
|
||||
}
|
||||
|
||||
let multiDaysCountAsOneDay = true;
|
||||
// If the user does not log in for two or more days, cron (mostly) acts as if it were only one day.
|
||||
// When site-wide difficulty settings are introduced, this can be a user preference option.
|
||||
@@ -321,7 +292,8 @@ export function cron (options = {}) {
|
||||
todoTally += task.value;
|
||||
});
|
||||
|
||||
// For incomplete Dailys, add value (further incentive), deduct health, keep records for later decreasing the nightly mana gain
|
||||
// For incomplete Dailys, add value (further incentive), deduct health, keep records for later decreasing the nightly mana gain.
|
||||
// The negative effects are not done when resting in the inn.
|
||||
let dailyChecked = 0; // how many dailies were checked?
|
||||
let dailyDueUnchecked = 0; // how many dailies were un-checked?
|
||||
let atLeastOneDailyDue = false; // were any dailies due?
|
||||
@@ -372,6 +344,7 @@ export function cron (options = {}) {
|
||||
dailyDueUnchecked += 1;
|
||||
}
|
||||
|
||||
if (!user.preferences.sleep) {
|
||||
let delta = scoreTask({
|
||||
user,
|
||||
task,
|
||||
@@ -391,6 +364,7 @@ export function cron (options = {}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add history entry when task was not completed
|
||||
task.history.push({
|
||||
@@ -438,10 +412,10 @@ export function cron (options = {}) {
|
||||
return taskType._id === taskOrderId && taskType.completed === false;
|
||||
});
|
||||
});
|
||||
// TODO also adjust tasksOrder arrays to remove deleted tasks of any kind (including rewards), ensure that all existing tasks are in the arrays, no tasks IDs are duplicated -- https://github.com/HabitRPG/habitica/issues/7645
|
||||
|
||||
// preen user history so that it doesn't become a performance problem
|
||||
// also for subscribed users but differently
|
||||
// TODO also do while resting in the inn. Note that later we'll be allowing the value/color of tasks to change while sleeping (https://github.com/HabitRPG/habitica/issues/5232), so the code in performSleepTasks() might be best merged back into here for that. Perhaps wait until then to do preen history for sleeping users.
|
||||
preenUserHistory(user, tasksByType);
|
||||
|
||||
if (perfect && atLeastOneDailyDue) {
|
||||
@@ -461,20 +435,22 @@ export function cron (options = {}) {
|
||||
|
||||
// Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit
|
||||
// Adjust for fraction of dailies completed
|
||||
if (!user.preferences.sleep) {
|
||||
if (dailyDueUnchecked === 0 && dailyChecked === 0) dailyChecked = 1;
|
||||
user.stats.mp += _.max([10, 0.1 * common.statsComputed(user).maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked);
|
||||
if (user.stats.mp > common.statsComputed(user).maxMP) user.stats.mp = common.statsComputed(user).maxMP;
|
||||
}
|
||||
|
||||
// After all is said and done, progress up user's effect on quest, return those values & reset the user's
|
||||
if (!user.preferences.sleep) {
|
||||
let progress = user.party.quest.progress;
|
||||
_progress = progress.toObject(); // clone the old progress object
|
||||
progress.down = -1300;
|
||||
_.merge(progress, {down: 0, up: 0, collectedItems: 0});
|
||||
}
|
||||
|
||||
// Send notification for changes in HP and MP
|
||||
|
||||
// First remove a possible previous cron notification
|
||||
// we don't want to flood the users with many cron notifications at once
|
||||
// Send notification for changes in HP and MP.
|
||||
// First remove a possible previous cron notification because
|
||||
// we don't want to flood the users with many cron notifications at once.
|
||||
let oldCronNotif = user.notifications.find((notif, index) => {
|
||||
if (notif && notif.type === 'CRON') {
|
||||
user.notifications.splice(index, 1);
|
||||
@@ -489,19 +465,6 @@ export function cron (options = {}) {
|
||||
mp: user.stats.mp - beforeCronStats.mp - (oldCronNotif ? oldCronNotif.data.mp : 0),
|
||||
});
|
||||
|
||||
// TODO: Clean PMs - keep 200 for subscribers and 50 for free users. Should also be done while resting in the inn
|
||||
// let numberOfPMs = Object.keys(user.inbox.messages).length;
|
||||
// if (numberOfPMs > maxPMs) {
|
||||
// _(user.inbox.messages)
|
||||
// .sortBy('timestamp')
|
||||
// .takeRight(numberOfPMs - maxPMs)
|
||||
// .forEach(pm => {
|
||||
// delete user.inbox.messages[pm.id];
|
||||
// })
|
||||
//
|
||||
// user.markModified('inbox.messages');
|
||||
// }
|
||||
|
||||
// Analytics
|
||||
user.flags.cronCount++;
|
||||
trackCronAnalytics(analytics, user, _progress, options);
|
||||
|
||||
Reference in New Issue
Block a user