From 664bf491d2dbc369e0dc91be97e1496c9e4a37cd Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 17 May 2015 21:10:40 -0500 Subject: [PATCH] Adjust fallenPanda's PR * Use enum for frequency in task model * Adjust ui of task selection * Correct check for hasStarted * Remove semicolons * Set default start day to today * Correct perfect day and resting in inn tests to have dailies with start dates of a week ago * Add tests for dailies/weeklies functionality --- common/script/index.coffee | 15 +- test/common/algos.mocha.coffee | 4 +- test/common/dailies.coffee | 359 +++++++++++++++++++++++++++ website/src/models/task.js | 3 +- website/views/shared/tasks/task.jade | 8 +- 5 files changed, 374 insertions(+), 15 deletions(-) create mode 100644 test/common/dailies.coffee diff --git a/common/script/index.coffee b/common/script/index.coffee index 6d293bd831..48ca7afcbe 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -76,30 +76,31 @@ api.daysSince = (yesterday, options = {}) -> ### api.shouldDo = (day, dailyTask, options = {}) -> return false unless dailyTask.type == 'daily' && dailyTask.repeat + day = moment(day).startOf('day') if !dailyTask.startDate # TODO: Unexpected code path reached. Log a warning. - dailyTask.startDate = moment().toDate(); + dailyTask.startDate = moment().toDate() if dailyTask.startDate instanceof String #TODO: Unexpected code path reached. Log a warning. - dailyTask.startDate = moment(dailyTask.startDate).toDate(); + dailyTask.startDate = moment(dailyTask.startDate).toDate() o = sanitizeOptions options dayOfWeek = api.startOfDay(_.defaults {now:day}, o).day() - # check if event is in the future - hasStartedCheck = day >= dailyTask.startDate + # check if event is today or in the future + hasStartedCheck = day >= moment(dailyTask.startDate).startOf('day') if dailyTask.frequency == 'daily' daysSinceTaskStart = api.numDaysApart(day, dailyTask.startDate, o) everyXCheck = (daysSinceTaskStart % dailyTask.everyX == 0) return everyXCheck && hasStartedCheck else if dailyTask.frequency == 'weekly' - dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeek]]; + dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeek]] weeksSinceTaskStartWeek = api.numWeeksApart(day, dailyTask.startDate, o) everyXCheck = (weeksSinceTaskStartWeek % dailyTask.everyX == 0) return dayOfWeekCheck && everyXCheck && hasStartedCheck else # unexpected frequency string - return false; + return false api.numDaysApart = (day1, day2, o) -> startOfDay1 = api.startOfDay(_.defaults {now:day1}, o) @@ -247,7 +248,7 @@ api.taskDefaults = (task={}) -> _.defaults(task, {up:true,down:true}) if task.type is 'habit' _.defaults(task, {history: []}) if task.type in ['habit', 'daily'] _.defaults(task, {completed:false}) if task.type in ['daily', 'todo'] - _.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}, startDate: new Date(0), everyX: 1, frequency: 'weekly') if task.type is 'daily' + _.defaults(task, {streak:0, repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}, startDate: new Date(), everyX: 1, frequency: 'weekly') if task.type is 'daily' task._id = task.id # may need this for TaskSchema if we go back to using it, see http://goo.gl/a5irq4 task.value ?= if task.type is 'reward' then 10 else 0 task.priority = 1 unless _.isNumber(task.priority) # hotfix for apiv1. once we're off apiv1, we can remove this diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee index 4fc9431d86..d6c1512e42 100644 --- a/test/common/algos.mocha.coffee +++ b/test/common/algos.mocha.coffee @@ -157,7 +157,7 @@ describe 'User', -> it 'handles perfect days', -> user = newUser() user.dailys = [] - _.times 3, ->user.dailys.push shared.taskDefaults({type:'daily'}) + _.times 3, ->user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')}) cron = -> user.lastCron = moment().subtract(1,'days');user.fns.cron() cron() @@ -191,7 +191,7 @@ describe 'User', -> user.preferences.sleep = true cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron() user.dailys = [] - _.times 2, -> user.dailys.push shared.taskDefaults({type:'daily'}) + _.times 2, -> user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')}) it 'remains in the inn on cron', -> cron() diff --git a/test/common/dailies.coffee b/test/common/dailies.coffee new file mode 100644 index 0000000000..35d9d1833e --- /dev/null +++ b/test/common/dailies.coffee @@ -0,0 +1,359 @@ +_ = require 'lodash' +expect = require 'expect.js' +sinon = require 'sinon' +moment = require 'moment' +shared = require '../../common/script/index.coffee' +shared.i18n.translations = require('../../website/src/i18n.js').translations + +repeatWithoutLastWeekday = ()-> + repeat = {su:1,m:1,t:1,w:1,th:1,f:1,s:1} + if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday + repeat.su = false + else + repeat.s = false + {repeat: repeat} + +### Helper Functions #### +# @TODO: Refactor into helper file +newUser = (addTasks=true)-> + buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false} + user = + auth: + timestamps: {} + stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs} + items: + lastDrop: + count: 0 + hatchingPotions: {} + eggs: {} + food: {} + gear: + equipped: {} + costume: {} + party: + quest: + progress: + down: 0 + preferences: {} + dailys: [] + todos: [] + rewards: [] + flags: {} + achievements: {} + contributor: + level: 2 + shared.wrap(user) + user.ops.reset(null, ->) + if addTasks + _.each ['habit', 'todo', 'daily'], (task)-> + user.ops.addTask {body: {type: task, id: shared.uuid()}} + user + +cron = (usr) -> + usr.lastCron = moment().subtract(1,'days') + usr.fns.cron() + +describe 'daily/weekly that repeats everyday (default)', -> + user = null + daily = null + weekly = null + + describe 'when startDate is in the future', -> + + beforeEach -> + user = newUser() + user.dailys = [ + shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'daily'}) + shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'weekly', repeat: {su:1,m:1,t:1,w:1,th:1,f:1,s:1}}) + ] + daily = user.dailys[0] + weekly = user.dailys[1] + + it 'does not damage user for not completing it', -> + cron(user) + expect(user.stats.hp).to.be 50 + + it 'does not change value on cron if daily is incomplete', -> + cron(user) + expect(daily.value).to.be 0 + expect(weekly.value).to.be 0 + + it 'does not reset checklists if daily is not marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + weekly.checklist = checklist + cron(user) + + expect(daily.checklist[0].completed).to.be true + expect(daily.checklist[1].completed).to.be true + expect(daily.checklist[2].completed).to.be false + + expect(weekly.checklist[0].completed).to.be true + expect(weekly.checklist[1].completed).to.be true + expect(weekly.checklist[2].completed).to.be false + + it 'resets checklists if daily is marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + weekly.checklist = checklist + daily.completed = true + weekly.completed = true + cron(user) + + _.each daily.checklist, (box)-> + expect(box.completed).to.be false + + _.each weekly.checklist, (box)-> + expect(box.completed).to.be false + + it 'is due on startDate', -> + daily_due_today = shared.shouldDo moment(), daily + daily_due_on_start_date = shared.shouldDo moment().add(7, 'days'), daily + + expect(daily_due_today).to.be false + expect(daily_due_on_start_date).to.be true + + weekly_due_today = shared.shouldDo moment(), weekly + weekly_due_on_start_date = shared.shouldDo moment().add(7, 'days'), weekly + + expect(weekly_due_today).to.be false + expect(weekly_due_on_start_date).to.be true + + describe 'when startDate is in the past', -> + completeDaily = null + + beforeEach -> + user = newUser() + user.dailys = [ + shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'daily'}) + shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'weekly'}) + ] + daily = user.dailys[0] + weekly = user.dailys[1] + + it 'does damage user for not completing it', -> + cron(user) + expect(user.stats.hp).to.be.lessThan 50 + + it 'decreases value on cron if daily is incomplete', -> + cron(user) + expect(daily.value).to.be.lessThan 0 + expect(weekly.value).to.be.lessThan 0 + + it 'resets checklists if daily is not marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + weekly.checklist = checklist + cron(user) + + _.each daily.checklist, (box)-> + expect(box.completed).to.be false + + _.each weekly.checklist, (box)-> + expect(box.completed).to.be false + + it 'resets checklists if daily is marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + daily.completed = true + weekly.checklist = checklist + weekly.completed = true + cron(user) + + _.each daily.checklist, (box)-> + expect(box.completed).to.be false + + _.each weekly.checklist, (box)-> + expect(box.completed).to.be false + + describe 'when startDate is today', -> + completeDaily = null + + beforeEach -> + user = newUser() + user.dailys = [ + # Must set start date to yesterday, because cron mock sets last cron to yesterday + shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'daily'}) + shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'weekly'}) + ] + daily = user.dailys[0] + weekly = user.dailys[1] + + it 'does damage user for not completing it', -> + cron(user) + expect(user.stats.hp).to.be.lessThan 50 + + it 'decreases value on cron if daily is incomplete', -> + cron(user) + expect(daily.value).to.be.lessThan 0 + expect(weekly.value).to.be.lessThan 0 + + it 'resets checklists if daily is not marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + weekly.checklist = checklist + cron(user) + + _.each daily.checklist, (box)-> + expect(box.completed).to.be false + + _.each weekly.checklist, (box)-> + expect(box.completed).to.be false + + it 'resets checklists if daily is marked as complete', -> + checklist = [ + { + 'text' : '1', + 'id' : 'checklist-one', + 'completed' : true + }, + { + 'text' : '2', + 'id' : 'checklist-two', + 'completed' : true + }, + { + 'text' : '3', + 'id' : 'checklist-three', + 'completed' : false + } + ] + daily.checklist = checklist + daily.completed = true + weekly.checklist = checklist + weekly.completed = true + cron(user) + + _.each daily.checklist, (box)-> + expect(box.completed).to.be false + + _.each weekly.checklist, (box)-> + expect(box.completed).to.be false + +describe 'daily that repeats every x days', -> + user = null + daily = null + + beforeEach -> + user = newUser() + user.dailys = [ shared.taskDefaults({type:'daily', startDate: moment(), frequency: 'daily'}) ] + daily = user.dailys[0] + + _.times 10, (n) -> + due = n + 1 + + it 'where x equals ' + due, -> + daily.everyX = due + + _.times 100, (day) -> + isDue = shared.shouldDo moment().add(day, 'days'), daily + expect(isDue).to.be true if day % due == 0 + expect(isDue).to.be false if day % due != 0 + +describe 'weekly that repeats on M,W,F every x days', -> + user = null + weekly = null + start_date = moment() + + beforeEach -> + user = newUser() + user.dailys = [ shared.taskDefaults({type:'daily', startDate: start_date, frequency: 'weekly', repeat: {su:false,m:1,t:false,w:1,th:false,f:1,s:false}}) ] + weekly = user.dailys[0] + + _.times 10, (n) -> + due = n + 1 + + it 'where x equals ' + due, -> + weekly.everyX = due + _.times 100, (day) -> + valid_days = { Mon: true, Wed: true, Fri: true } + day_to_test = start_date.add(day, 'days') + day_of_week = moment(day_to_test).format('ddd') + + isDue = shared.shouldDo day_to_test, weekly + is_correct_week = shared.numWeeksApart(day_to_test, start_date) % due == 0 + + if valid_days[day_of_week] && is_correct_week + expect(isDue).to.be true + else + expect(isDue).to.be false diff --git a/website/src/models/task.js b/website/src/models/task.js index de6a6dfe48..ac4ef56337 100644 --- a/website/src/models/task.js +++ b/website/src/models/task.js @@ -51,8 +51,7 @@ var checklist = [{ var DailySchema = new Schema( _.defaults({ type: {type: String, 'default': 'daily'}, - //TODO: Cleaner to store interval as enum instead of str? - frequency: {type: String, 'default': 'weekly'}, // 'daily', 'weekly' + frequency: {type: String, 'default': 'weekly', enum: ['daily', 'weekly']}, everyX: {type: Number, 'default': 1}, // e.g. once every X weeks startDate: {type: Date}, history: Array, diff --git a/website/views/shared/tasks/task.jade b/website/views/shared/tasks/task.jade index f15e090d10..7cb4ae73a2 100644 --- a/website/views/shared/tasks/task.jade +++ b/website/views/shared/tasks/task.jade @@ -209,11 +209,11 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}} label.option-title=env.t('startDate') - p(class='input-group') + p.input-group input(type='text' class='form-control' datepicker-popup ng-model='task._tempDateForPicker' ng-change='updateTaskStartDate(task)' is-open='task._isDatePickerOpen') - span(class='input-group-btn') - button(type="button" ng-click="openDatePicker($event, task)") - span.input-group-addon.glyphicon.glyphicon-calendar + span.input-group-btn + button.btn.btn-default(type='button' ng-click='openDatePicker($event, task)') + .glyphicon.glyphicon-calendar // if Reward, pricing