mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
* Clean version of PR 8175 The original PR for this was here: https://github.com/HabitRPG/habitica/pull/8175 Unfortunately while fixing a conflict in tasks.json, I messed up the rebase and wound up pulling in too many commits and making a giant mess. Sorry. :P * Fixing test failure This test seems to occasionally start failing (another coder reported the same thing happening to them in the blacksmiths’ guild) because the order in which the tasks are created can sometimes not match the order in the array. So I have sorted the tasks array after creation by the task name to ensure a consistent ordering, and slightly reordered the expect statements to match.
This commit is contained in:
@@ -481,6 +481,67 @@ describe('cron', () => {
|
||||
|
||||
expect(tasksByType.habits[0].value).to.equal(1);
|
||||
});
|
||||
|
||||
describe('counters', () => {
|
||||
let notStartOfWeekOrMonth = new Date(2016, 9, 28).getTime(); // a Friday
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Replace system clocks so we can get predictable results
|
||||
clock = sinon.useFakeTimers(notStartOfWeekOrMonth);
|
||||
});
|
||||
afterEach(() => {
|
||||
return clock.restore();
|
||||
});
|
||||
|
||||
it('should reset a daily habit counter each day', () => {
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter each Monday', () => {
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// should reset
|
||||
daysMissed = 8;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a monthly habit counter the first day of each month', () => {
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// should reset
|
||||
daysMissed = 32;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('perfect day', () => {
|
||||
|
||||
@@ -12,6 +12,9 @@ describe('taskDefaults', () => {
|
||||
expect(task.up).to.eql(true);
|
||||
expect(task.down).to.eql(true);
|
||||
expect(task.history).to.eql([]);
|
||||
expect(task.frequency).to.equal('daily');
|
||||
expect(task.counterUp).to.equal(0);
|
||||
expect(task.counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('applies defaults to a daily', () => {
|
||||
|
||||
@@ -33,6 +33,9 @@ describe('shared.ops.addTask', () => {
|
||||
expect(habit.down).to.equal(false);
|
||||
expect(habit.history).to.eql([]);
|
||||
expect(habit.checklist).to.not.exist;
|
||||
expect(habit.frequency).to.equal('daily');
|
||||
expect(habit.counterUp).to.equal(0);
|
||||
expect(habit.counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('adds an habtit when type is invalid', () => {
|
||||
|
||||
@@ -138,6 +138,9 @@ describe('shared.ops.scoreTask', () => {
|
||||
todo = generateTodo({ userId: ref.afterUser._id, text: 'some todo' });
|
||||
|
||||
expect(habit.history.length).to.eql(0);
|
||||
expect(habit.frequency).to.equal('daily');
|
||||
expect(habit.counterUp).to.equal(0);
|
||||
expect(habit.counterDown).to.equal(0);
|
||||
|
||||
// before and after are the same user
|
||||
expect(ref.beforeUser._id).to.exist;
|
||||
@@ -202,6 +205,7 @@ describe('shared.ops.scoreTask', () => {
|
||||
|
||||
expect(habit.history.length).to.eql(1);
|
||||
expect(habit.value).to.be.greaterThan(0);
|
||||
expect(habit.counterUp).to.equal(5);
|
||||
|
||||
expect(ref.afterUser.stats.hp).to.eql(50);
|
||||
expect(ref.afterUser.stats.exp).to.be.greaterThan(ref.beforeUser.stats.exp);
|
||||
@@ -213,6 +217,7 @@ describe('shared.ops.scoreTask', () => {
|
||||
|
||||
expect(habit.history.length).to.eql(1);
|
||||
expect(habit.value).to.be.lessThan(0);
|
||||
expect(habit.counterDown).to.equal(5);
|
||||
|
||||
expect(ref.afterUser.stats.hp).to.be.lessThan(ref.beforeUser.stats.hp);
|
||||
expect(ref.afterUser.stats.exp).to.eql(0);
|
||||
|
||||
@@ -136,6 +136,13 @@
|
||||
"intelligenceExample": "Relating to academic or mentally challenging pursuits",
|
||||
"perceptionExample": "Relating to work or financial tasks",
|
||||
"constitutionExample": "Relating to health, wellness, and social interaction",
|
||||
"counterPeriod": "Counter Resets Every",
|
||||
"counterPeriodDay": "Day",
|
||||
"counterPeriodWeek": "Week",
|
||||
"counterPeriodMonth": "Month",
|
||||
"habitCounter": "Counter",
|
||||
"habitCounterUp": "Positive Counter",
|
||||
"habitCounterDown": "Negative Counter",
|
||||
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
||||
"taskApprovalHasBeenRequested": "Approval has been requested",
|
||||
"approvals": "Approvals",
|
||||
|
||||
@@ -49,6 +49,9 @@ module.exports = function taskDefaults (task = {}) {
|
||||
_.defaults(task, {
|
||||
up: true,
|
||||
down: true,
|
||||
frequency: 'daily',
|
||||
counterUp: 0,
|
||||
counterDown: 0,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,14 @@ function _changeTaskValue (user, task, direction, times, cron) {
|
||||
return addToDelta;
|
||||
}
|
||||
|
||||
function _updateCounter (task, direction, times) {
|
||||
if (direction === 'up') {
|
||||
task.counterUp += times;
|
||||
} else {
|
||||
task.counterDown += times;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function scoreTask (options = {}, req = {}) {
|
||||
let {user, task, direction, times = 1, cron = false} = options;
|
||||
let delta = 0;
|
||||
@@ -207,6 +215,8 @@ module.exports = function scoreTask (options = {}, req = {}) {
|
||||
date: Number(new Date()),
|
||||
value: task.value,
|
||||
});
|
||||
|
||||
_updateCounter(task, direction, times);
|
||||
} else if (task.type === 'daily') {
|
||||
if (cron) {
|
||||
delta += _changeTaskValue(user, task, direction, times, cron);
|
||||
|
||||
@@ -316,8 +316,41 @@ export function cron (options = {}) {
|
||||
}
|
||||
});
|
||||
|
||||
// move singleton Habits towards yellow.
|
||||
tasksByType.habits.forEach((task) => { // slowly reset 'onlies' value to 0
|
||||
// check if we've passed a day on which we should reset the habit counters, including today
|
||||
let resetWeekly = false;
|
||||
let resetMonthly = false;
|
||||
for (let i = 0; i <= daysMissed; i++) {
|
||||
if (resetWeekly === true && resetMonthly === true) {
|
||||
break;
|
||||
}
|
||||
let thatDay = moment(now).subtract({days: i}).toDate();
|
||||
if (thatDay.getDay() === 1) {
|
||||
resetWeekly = true;
|
||||
}
|
||||
if (thatDay.getDate() === 1) {
|
||||
resetMonthly = true;
|
||||
}
|
||||
}
|
||||
|
||||
tasksByType.habits.forEach((task) => {
|
||||
// reset counters if appropriate
|
||||
|
||||
// this enormously clunky thing brought to you by lint
|
||||
let reset = false;
|
||||
if (task.frequency === 'daily') {
|
||||
reset = true;
|
||||
} else if (task.frequency === 'weekly' && resetWeekly === true) {
|
||||
reset = true;
|
||||
} else if (task.frequency === 'monthly' && resetMonthly === true) {
|
||||
reset = true;
|
||||
}
|
||||
if (reset === true) {
|
||||
task.counterUp = 0;
|
||||
task.counterDown = 0;
|
||||
}
|
||||
|
||||
// slowly reset value to 0 for "onlies" (Habits with + or - but not both)
|
||||
// move singleton Habits towards yellow.
|
||||
if (task.up === false || task.down === false) {
|
||||
task.value = Math.abs(task.value) < 0.1 ? 0 : task.value = task.value / 2;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,9 @@ let dailyTodoSchema = () => {
|
||||
export let HabitSchema = new Schema(_.defaults({
|
||||
up: {type: Boolean, default: true},
|
||||
down: {type: Boolean, default: true},
|
||||
counterUp: {type: Number, default: 0},
|
||||
counterDown: {type: Number, default: 0},
|
||||
frequency: {type: String, default: 'daily', enum: ['daily', 'weekly', 'monthly']},
|
||||
}, habitDailySchema()), subDiscriminatorOptions);
|
||||
export let habit = Task.discriminator('habit', HabitSchema);
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ footer.footer(ng-controller='FooterCtrl')
|
||||
a.btn.btn-default(ng-click='setHealthLow()') Health = 1
|
||||
a.btn.btn-default(ng-click='addMissedDay(1)') +1 Missed Day
|
||||
a.btn.btn-default(ng-click='addMissedDay(2)') +2 Missed Days
|
||||
a.btn.btn-default(ng-click='addMissedDay(8)') +8 Missed Days
|
||||
a.btn.btn-default(ng-click='addMissedDay(32)') +32 Missed Days
|
||||
a.btn.btn-default(ng-click='addTenGems()') +10 Gems
|
||||
a.btn.btn-default(ng-click='addHourglass()') +1 Mystic Hourglass
|
||||
a.btn.btn-default(ng-click='addGold()') +500GP
|
||||
|
||||
@@ -9,6 +9,8 @@ div(ng-if='(task.type !== "reward") || (!obj.auth && obj.purchased && obj.purcha
|
||||
a.hint(href='http://habitica.wikia.com/wiki/Task_Alias', target='_blank', popover-trigger='mouseenter', popover="{{::env.t('taskAliasPopover')}} {{::task._edit.alias ? '\n\n\' + env.t('taskAliasPopoverWarning') : ''}}")=env.t('taskAlias')
|
||||
input.form-control(ng-model='task._edit.alias' type='text' placeholder=env.t('taskAliasPlaceholder'))
|
||||
|
||||
include ./habits/frequency
|
||||
|
||||
fieldset.option-group.advanced-option(ng-show="task._edit._advanced", ng-if="!obj.auth && obj.purchased && obj.purchased.active")
|
||||
group-tasks-actions(task='task', group='obj')
|
||||
|
||||
|
||||
8
website/views/shared/tasks/edit/habits/frequency.jade
Normal file
8
website/views/shared/tasks/edit/habits/frequency.jade
Normal file
@@ -0,0 +1,8 @@
|
||||
fieldset.option-group.counter_period(ng-if='task.type === "habit" && canEdit(task)')
|
||||
.form-group
|
||||
legend.option-title=env.t('counterPeriod')
|
||||
select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||
option(value='daily')=env.t('counterPeriodDay')
|
||||
option(value='weekly')=env.t('counterPeriodWeek')
|
||||
option(value='monthly')=env.t('counterPeriodMonth')
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
.task-meta-controls
|
||||
|
||||
// Counter
|
||||
span(ng-if='task.up && task.down')
|
||||
span(tooltip=env.t('habitCounterUp')) +{{task.counterUp}}|
|
||||
span(tooltip=env.t('habitCounterDown')) -{{task.counterDown}}
|
||||
|
||||
span(ng-if='task.type=="habit" && (!task.up || !task.down)')
|
||||
span(tooltip=env.t('habitCounter')) {{task.up ? task.counterUp : task.counterDown}}
|
||||
|
||||
// Due Date
|
||||
span(ng-if='task.type=="todo" && task.date')
|
||||
span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{task.date | date:(user.preferences.dateFormat.indexOf('yyyy') == 0 ? user.preferences.dateFormat.substr(5) : user.preferences.dateFormat.substr(0,5))}}
|
||||
|
||||
Reference in New Issue
Block a user