mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
* Changed recurring logic to not use moment-recur plugin for performance reasons * change only nextDue calculations add tests to make sure proper nextDue values are calculated revert schedule.matches logic to original revert shouldDo.test.js to original * fix monthly nextDue logic move tests to shouldDo.test.js * typos * revert to original logic. change not needed * add failure cases
This commit is contained in:
committed by
Keith Holliday
parent
5360f9e587
commit
0be681b7a2
@@ -5,6 +5,7 @@ import 'moment-recur';
|
||||
describe('shouldDo', () => {
|
||||
let day, dailyTask;
|
||||
let options = {};
|
||||
let nextDue = [];
|
||||
|
||||
beforeEach(() => {
|
||||
day = new Date();
|
||||
@@ -223,6 +224,25 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute daily nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'daily';
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-03').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-07').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-09').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-11').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-13').toDate());
|
||||
});
|
||||
|
||||
context('On multiples of x', () => {
|
||||
it('returns true when Custom Day Start is midnight', () => {
|
||||
dailyTask.startDate = moment().subtract(7, 'days').toDate();
|
||||
@@ -367,6 +387,73 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute weekly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'weekly';
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: true,
|
||||
m: true,
|
||||
t: true,
|
||||
w: true,
|
||||
th: true,
|
||||
f: true,
|
||||
s: true,
|
||||
};
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-02').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-03').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-04').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-06').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-07').toDate());
|
||||
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.repeat = {
|
||||
su: true,
|
||||
m: false,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: true,
|
||||
s: false,
|
||||
};
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-14').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-19').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-28').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-06-02').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-06-11').toDate());
|
||||
});
|
||||
|
||||
it('should not go into an infinite loop with invalid values', () => {
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'weekly';
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.startDate = null;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue).to.eql(false);
|
||||
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 0;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue).to.eql(false);
|
||||
});
|
||||
|
||||
context('Day of the week matches', () => {
|
||||
const weekdayMap = {
|
||||
1: 'm',
|
||||
@@ -667,6 +754,96 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute monthly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'monthly';
|
||||
dailyTask.everyX = 3;
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.daysOfMonth = [1];
|
||||
dailyTask.weeksOfMonth = [];
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-08-01').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-11-01').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-02-01').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-05-01').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-08-01').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-11-01').toDate());
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [0];
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-05').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-03').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-07').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-04').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-02').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-06').toDate());
|
||||
|
||||
day = moment('2017-05-08').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [1];
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-12').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-10').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-14').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-11').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-09').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-13').toDate());
|
||||
|
||||
day = moment('2017-05-29').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [4];
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-07-31').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-10-30').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-01-29').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-04-30').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-07-30').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-10-29').toDate());
|
||||
});
|
||||
|
||||
context('Custom Day Start is 0 <= n < 24', () => {
|
||||
beforeEach(() => {
|
||||
options.dayStart = 7;
|
||||
@@ -918,6 +1095,25 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute yearly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'yearly';
|
||||
dailyTask.everyX = 5;
|
||||
dailyTask.startDate = day;
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2022-05-01').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2027-05-01').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2032-05-01').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2037-05-01').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2042-05-01').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2047-05-01').toDate());
|
||||
});
|
||||
|
||||
context('Custom Day Start is 0 <= n < 24', () => {
|
||||
beforeEach(() => {
|
||||
options.dayStart = 7;
|
||||
|
||||
@@ -94,7 +94,7 @@ export function daysSince (yesterday, options = {}) {
|
||||
*/
|
||||
|
||||
export function shouldDo (day, dailyTask, options = {}) {
|
||||
if (dailyTask.type !== 'daily') {
|
||||
if (dailyTask.type !== 'daily' || dailyTask.startDate === null || dailyTask.everyX < 1 || dailyTask.everyX > 9999) {
|
||||
return false;
|
||||
}
|
||||
let o = sanitizeOptions(options);
|
||||
@@ -110,7 +110,6 @@ export function shouldDo (day, dailyTask, options = {}) {
|
||||
}
|
||||
|
||||
let daysOfTheWeek = [];
|
||||
|
||||
if (dailyTask.repeat) {
|
||||
for (let [repeatDay, active] of Object.entries(dailyTask.repeat)) {
|
||||
if (!isFinite(DAY_MAPPING_STRING_TO_NUMBER[repeatDay])) continue; // eslint-disable-line no-continue
|
||||
@@ -123,7 +122,14 @@ export function shouldDo (day, dailyTask, options = {}) {
|
||||
let schedule = moment(startDate).recur()
|
||||
.every(dailyTask.everyX).days();
|
||||
|
||||
if (options.nextDue) return schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||
if (options.nextDue) {
|
||||
let filteredDates = [];
|
||||
for (let i = 1; filteredDates.length < 6; i++) {
|
||||
let calcDate = moment(startDate).add(dailyTask.everyX * i, 'days');
|
||||
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
|
||||
}
|
||||
return filteredDates;
|
||||
}
|
||||
|
||||
return schedule.matches(startOfDayWithCDSTime);
|
||||
} else if (dailyTask.frequency === 'weekly') {
|
||||
@@ -134,15 +140,20 @@ export function shouldDo (day, dailyTask, options = {}) {
|
||||
|
||||
if (daysOfTheWeek.length === 0) return false;
|
||||
schedule = schedule.every(daysOfTheWeek).daysOfWeek();
|
||||
|
||||
if (options.nextDue) {
|
||||
let dates = schedule.fromDate(startOfDayWithCDSTime.subtract('1', 'days')).next(6);
|
||||
let filterDates = dates.filter((momentDate) => {
|
||||
let weekDiff = momentDate.week() - moment(startDate).week();
|
||||
let matchX = weekDiff % dailyTask.everyX === 0;
|
||||
return matchX;
|
||||
let filteredDates = [];
|
||||
for (let i = 0; filteredDates.length < 6; i++) {
|
||||
for (let j = 0; j < daysOfTheWeek.length && filteredDates.length < 6; j++) {
|
||||
let calcDate = moment(startDate).day(daysOfTheWeek[j]).add(dailyTask.everyX * i, 'weeks');
|
||||
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
|
||||
}
|
||||
}
|
||||
let sortedDates = filteredDates.sort((date1, date2) => {
|
||||
if (date1.toDate() > date2.toDate()) return 1;
|
||||
if (date2.toDate() > date1.toDate()) return -1;
|
||||
return 0;
|
||||
});
|
||||
return filterDates;
|
||||
return sortedDates;
|
||||
}
|
||||
|
||||
return schedule.matches(startOfDayWithCDSTime) && matchEveryX;
|
||||
@@ -154,19 +165,42 @@ export function shouldDo (day, dailyTask, options = {}) {
|
||||
|
||||
if (dailyTask.weeksOfMonth && dailyTask.weeksOfMonth.length > 0) {
|
||||
schedule = schedule.every(daysOfTheWeek).daysOfWeek()
|
||||
.every(dailyTask.weeksOfMonth).weeksOfMonthByDay();
|
||||
.every(dailyTask.weeksOfMonth).weeksOfMonthByDay();
|
||||
|
||||
if (options.nextDue) {
|
||||
let filteredDates = [];
|
||||
for (let i = 1; filteredDates.length < 6; i++) {
|
||||
let recurDate = moment(startDate).add(dailyTask.everyX * i, 'months');
|
||||
let calcDate = recurDate.clone();
|
||||
calcDate.day(daysOfTheWeek[0]);
|
||||
|
||||
let startDateWeek = Math.ceil(moment(startDate).date() / 7);
|
||||
let calcDateWeek = Math.ceil(calcDate.date() / 7);
|
||||
|
||||
// adjust week since weeks will rollover to other months
|
||||
if (calcDate.month() < recurDate.month()) calcDate.add(1, 'weeks');
|
||||
else if (calcDate.month() > recurDate.month()) calcDate.subtract(1, 'weeks');
|
||||
else if (calcDateWeek > startDateWeek) calcDate.subtract(1, 'weeks');
|
||||
else if (calcDateWeek < startDateWeek) calcDate.add(1, 'weeks');
|
||||
|
||||
calcDateWeek = Math.ceil(calcDate.date() / 7);
|
||||
|
||||
if (calcDate >= startOfDayWithCDSTime &&
|
||||
calcDateWeek === startDateWeek && calcDate.month() === recurDate.month()) filteredDates.push(calcDate);
|
||||
}
|
||||
return filteredDates;
|
||||
}
|
||||
return schedule.matches(startOfDayWithCDSTime) && matchEveryX;
|
||||
} else if (dailyTask.daysOfMonth && dailyTask.daysOfMonth.length > 0) {
|
||||
schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth();
|
||||
}
|
||||
|
||||
if (options.nextDue) {
|
||||
let dates = schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||
let filterDates = dates.filter((momentDate) => {
|
||||
let monthDiff = momentDate.month() - moment(startDate).month();
|
||||
let matchX = monthDiff % dailyTask.everyX === 0;
|
||||
return matchX;
|
||||
});
|
||||
return filterDates;
|
||||
if (options.nextDue) {
|
||||
let filteredDates = [];
|
||||
for (let i = 1; filteredDates.length < 6; i++) {
|
||||
let calcDate = moment(startDate).add(dailyTask.everyX * i, 'months');
|
||||
if (calcDate >= startOfDayWithCDSTime) filteredDates.push(calcDate);
|
||||
}
|
||||
return filteredDates;
|
||||
}
|
||||
}
|
||||
|
||||
return schedule.matches(startOfDayWithCDSTime) && matchEveryX;
|
||||
@@ -176,17 +210,15 @@ export function shouldDo (day, dailyTask, options = {}) {
|
||||
schedule = schedule.every(dailyTask.everyX).years();
|
||||
|
||||
if (options.nextDue) {
|
||||
let dates = schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||
let filterDates = dates.filter((momentDate) => {
|
||||
let monthDiff = momentDate.years() - moment(startDate).years();
|
||||
let matchX = monthDiff % dailyTask.everyX === 0;
|
||||
return matchX;
|
||||
});
|
||||
return filterDates;
|
||||
let filteredDates = [];
|
||||
for (let i = 1; filteredDates.length < 6; i++) {
|
||||
let calcDate = moment(startDate).add(dailyTask.everyX * i, 'years');
|
||||
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
|
||||
}
|
||||
return filteredDates;
|
||||
}
|
||||
|
||||
return schedule.matches(startOfDayWithCDSTime);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user