diff --git a/.eslintrc b/.eslintrc index 82c291e8ce..585f456870 100644 --- a/.eslintrc +++ b/.eslintrc @@ -81,9 +81,6 @@ "block-spacing": [2, "always"], "key-spacing": [2, {"beforeColon": false, "afterColon": true}], "max-nested-callbacks": [2, 3], - "mocha/no-exclusive-tests": 2, - "mocha/no-global-tests": 2, - "mocha/handle-done-callback": 2, "new-cap": 2, "new-parens": 2, "newline-after-var": 2, @@ -119,11 +116,5 @@ ecmaFeatures : { modules: true }, - "extends": "eslint:recommended", - "globals": { - "expect": true - }, - "plugins": [ - "mocha" - ] + "extends": "eslint:recommended" } diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json index 714ccb0c30..c1d467aefd 100644 --- a/common/locales/en/settings.json +++ b/common/locales/en/settings.json @@ -144,5 +144,8 @@ "gemCapExtra": "Gem Cap Extra:", "mysticHourglasses": "Mystic Hourglasses:", "paypal": "PayPal", - "amazonPayments": "Amazon Payments" + "amazonPayments": "Amazon Payments", + "timezone": "Time Zone", + "timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>", + "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.

If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices." } diff --git a/common/script/content/gear/index.js b/common/script/content/gear/index.js index 1ef3088c14..a9fe254e7b 100644 --- a/common/script/content/gear/index.js +++ b/common/script/content/gear/index.js @@ -61,11 +61,11 @@ each(GEAR_TYPES, (type) => { let _canOwn = item.canOwn || canOwnFuncTrue; item.canOwn = (user) => { - let userOwnsItem = Boolean(user.items.gear.owned[key]); + let userHasOwnedItem = ownsItem(key)(user); let eventIsCurrent = moment().isAfter(item.event.start) && moment().isBefore(item.event.end); let compatibleWithUserClass = item.specialClass ? user.stats.class === item.specialClass : true; - return _canOwn(user) && (userOwnsItem || eventIsCurrent) && compatibleWithUserClass; + return _canOwn(user) && (userHasOwnedItem || eventIsCurrent) && compatibleWithUserClass; }; } diff --git a/common/script/content/gear/sets/armoire.js b/common/script/content/gear/sets/armoire.js index 888b2586c4..77c9ff065c 100644 --- a/common/script/content/gear/sets/armoire.js +++ b/common/script/content/gear/sets/armoire.js @@ -18,7 +18,7 @@ let armor = { str: 7, per: 7, set: 'gladiator', - canOwn: ownsItem('armor_armoire_gladiatorArmor '), + canOwn: ownsItem('armor_armoire_gladiatorArmor'), }, rancherRobes: { text: t('armorArmoireRancherRobesText'), @@ -28,7 +28,7 @@ let armor = { per: 5, int: 5, set: 'rancher', - canOwn: ownsItem('armor_armoire_rancherRobes '), + canOwn: ownsItem('armor_armoire_rancherRobes'), }, goldenToga: { text: t('armorArmoireGoldenTogaText'), @@ -37,7 +37,7 @@ let armor = { str: 8, con: 8, set: 'goldenToga', - canOwn: ownsItem('armor_armoire_goldenToga '), + canOwn: ownsItem('armor_armoire_goldenToga'), }, hornedIronArmor: { text: t('armorArmoireHornedIronArmorText'), @@ -46,7 +46,7 @@ let armor = { con: 9, per: 7, set: 'hornedIron', - canOwn: ownsItem('armor_armoire_hornedIronArmor '), + canOwn: ownsItem('armor_armoire_hornedIronArmor'), }, plagueDoctorOvercoat: { text: t('armorArmoirePlagueDoctorOvercoatText'), @@ -56,7 +56,7 @@ let armor = { str: 5, con: 6, set: 'plagueDoctor', - canOwn: ownsItem('armor_armoire_plagueDoctorOvercoat '), + canOwn: ownsItem('armor_armoire_plagueDoctorOvercoat'), }, shepherdRobes: { text: t('armorArmoireShepherdRobesText'), @@ -65,7 +65,7 @@ let armor = { str: 9, per: 9, set: 'shepherd', - canOwn: ownsItem('armor_armoire_shepherdRobes '), + canOwn: ownsItem('armor_armoire_shepherdRobes'), }, royalRobes: { text: t('armorArmoireRoyalRobesText'), @@ -75,7 +75,7 @@ let armor = { per: 5, int: 5, set: 'royal', - canOwn: ownsItem('armor_armoire_royalRobes '), + canOwn: ownsItem('armor_armoire_royalRobes'), }, }; @@ -85,7 +85,7 @@ let eyewear = { notes: t('eyewearArmoirePlagueDoctorMaskNotes'), value: 100, set: 'plagueDoctor', - canOwn: ownsItem('eyewear_armoire_plagueDoctorMask '), + canOwn: ownsItem('eyewear_armoire_plagueDoctorMask'), }, }; @@ -97,7 +97,7 @@ let head = { con: 7, per: 7, set: 'soothing', - canOwn: ownsItem('head_armoire_lunarCrown '), + canOwn: ownsItem('head_armoire_lunarCrown'), }, redHairbow: { text: t('headArmoireRedHairbowText'), @@ -106,7 +106,7 @@ let head = { str: 5, int: 5, con: 5, - canOwn: ownsItem('head_armoire_redHairbow '), + canOwn: ownsItem('head_armoire_redHairbow'), }, violetFloppyHat: { text: t('headArmoireVioletFloppyHatText'), @@ -115,7 +115,7 @@ let head = { per: 5, int: 5, con: 5, - canOwn: ownsItem('head_armoire_violetFloppyHat '), + canOwn: ownsItem('head_armoire_violetFloppyHat'), }, gladiatorHelm: { text: t('headArmoireGladiatorHelmText'), @@ -124,7 +124,7 @@ let head = { per: 7, int: 7, set: 'gladiator', - canOwn: ownsItem('head_armoire_gladiatorHelm '), + canOwn: ownsItem('head_armoire_gladiatorHelm'), }, rancherHat: { text: t('headArmoireRancherHatText'), @@ -134,7 +134,7 @@ let head = { per: 5, int: 5, set: 'rancher', - canOwn: ownsItem('head_armoire_rancherHat '), + canOwn: ownsItem('head_armoire_rancherHat'), }, royalCrown: { text: t('headArmoireRoyalCrownText'), @@ -142,7 +142,7 @@ let head = { value: 100, str: 10, set: 'royal', - canOwn: ownsItem('head_armoire_royalCrown '), + canOwn: ownsItem('head_armoire_royalCrown'), }, blueHairbow: { text: t('headArmoireBlueHairbowText'), @@ -151,7 +151,7 @@ let head = { per: 5, int: 5, con: 5, - canOwn: ownsItem('head_armoire_blueHairbow '), + canOwn: ownsItem('head_armoire_blueHairbow'), }, goldenLaurels: { text: t('headArmoireGoldenLaurelsText'), @@ -160,7 +160,7 @@ let head = { per: 8, con: 8, set: 'goldenToga', - canOwn: ownsItem('head_armoire_goldenLaurels '), + canOwn: ownsItem('head_armoire_goldenLaurels'), }, hornedIronHelm: { text: t('headArmoireHornedIronHelmText'), @@ -169,7 +169,7 @@ let head = { con: 9, str: 7, set: 'hornedIron', - canOwn: ownsItem('head_armoire_hornedIronHelm '), + canOwn: ownsItem('head_armoire_hornedIronHelm'), }, yellowHairbow: { text: t('headArmoireYellowHairbowText'), @@ -178,7 +178,7 @@ let head = { int: 5, per: 5, str: 5, - canOwn: ownsItem('head_armoire_yellowHairbow '), + canOwn: ownsItem('head_armoire_yellowHairbow'), }, redFloppyHat: { text: t('headArmoireRedFloppyHatText'), @@ -187,7 +187,7 @@ let head = { con: 6, int: 6, per: 6, - canOwn: ownsItem('head_armoire_redFloppyHat '), + canOwn: ownsItem('head_armoire_redFloppyHat'), }, plagueDoctorHat: { text: t('headArmoirePlagueDoctorHatText'), @@ -197,7 +197,7 @@ let head = { str: 6, con: 5, set: 'plagueDoctor', - canOwn: ownsItem('head_armoire_plagueDoctorHat '), + canOwn: ownsItem('head_armoire_plagueDoctorHat'), }, blackCat: { text: t('headArmoireBlackCatText'), @@ -205,7 +205,7 @@ let head = { value: 100, int: 9, per: 9, - canOwn: ownsItem('head_armoire_blackCat '), + canOwn: ownsItem('head_armoire_blackCat'), }, orangeCat: { text: t('headArmoireOrangeCatText'), @@ -213,7 +213,7 @@ let head = { value: 100, con: 9, str: 9, - canOwn: ownsItem('head_armoire_orangeCat '), + canOwn: ownsItem('head_armoire_orangeCat'), }, blueFloppyHat: { text: t('headArmoireBlueFloppyHatText'), @@ -222,7 +222,7 @@ let head = { per: 7, int: 7, con: 7, - canOwn: ownsItem('head_armoire_blueFloppyHat '), + canOwn: ownsItem('head_armoire_blueFloppyHat'), }, shepherdHeaddress: { text: t('headArmoireShepherdHeaddressText'), @@ -230,7 +230,7 @@ let head = { value: 100, int: 9, set: 'shepherd', - canOwn: ownsItem('head_armoire_shepherdHeaddress '), + canOwn: ownsItem('head_armoire_shepherdHeaddress'), }, }; @@ -242,7 +242,7 @@ let shield = { con: 5, str: 5, set: 'gladiator', - canOwn: ownsItem('shield_armoire_gladiatorShield '), + canOwn: ownsItem('shield_armoire_gladiatorShield'), }, midnightShield: { text: t('shieldArmoireMidnightShieldText'), @@ -250,7 +250,7 @@ let shield = { value: 100, con: 10, str: 2, - canOwn: ownsItem('shield_armoire_midnightShield '), + canOwn: ownsItem('shield_armoire_midnightShield'), }, royalCane: { text: t('shieldArmoireRoyalCaneText'), @@ -260,7 +260,7 @@ let shield = { int: 5, per: 5, set: 'royal', - canOwn: ownsItem('shield_armoire_royalCane '), + canOwn: ownsItem('shield_armoire_royalCane'), }, }; @@ -272,7 +272,7 @@ let weapon = { str: 5, per: 5, con: 5, - canOwn: ownsItem('weapon_armoire_basicCrossbow '), + canOwn: ownsItem('weapon_armoire_basicCrossbow'), }, lunarSceptre: { text: t('weaponArmoireLunarSceptreText'), @@ -281,7 +281,7 @@ let weapon = { con: 7, int: 7, set: 'soothing', - canOwn: ownsItem('weapon_armoire_lunarSceptre '), + canOwn: ownsItem('weapon_armoire_lunarSceptre'), }, rancherLasso: { twoHanded: true, @@ -292,7 +292,7 @@ let weapon = { per: 5, int: 5, set: 'rancher', - canOwn: ownsItem('weapon_armoire_rancherLasso '), + canOwn: ownsItem('weapon_armoire_rancherLasso'), }, mythmakerSword: { text: t('weaponArmoireMythmakerSwordText'), @@ -301,7 +301,7 @@ let weapon = { str: 6, per: 6, set: 'goldenToga', - canOwn: ownsItem('weapon_armoire_mythmakerSword '), + canOwn: ownsItem('weapon_armoire_mythmakerSword'), }, ironCrook: { text: t('weaponArmoireIronCrookText'), @@ -310,7 +310,7 @@ let weapon = { str: 7, per: 7, set: 'hornedIron', - canOwn: ownsItem('weapon_armoire_ironCrook '), + canOwn: ownsItem('weapon_armoire_ironCrook'), }, goldWingStaff: { text: t('weaponArmoireGoldWingStaffText'), @@ -320,7 +320,7 @@ let weapon = { int: 4, per: 4, str: 4, - canOwn: ownsItem('weapon_armoire_goldWingStaff '), + canOwn: ownsItem('weapon_armoire_goldWingStaff'), }, batWand: { text: t('weaponArmoireBatWandText'), @@ -328,7 +328,7 @@ let weapon = { value: 100, int: 10, per: 2, - canOwn: ownsItem('weapon_armoire_batWand '), + canOwn: ownsItem('weapon_armoire_batWand'), }, shepherdsCrook: { text: t('weaponArmoireShepherdsCrookText'), @@ -336,7 +336,7 @@ let weapon = { value: 100, con: 9, set: 'shepherd', - canOwn: ownsItem('weapon_armoire_shepherdsCrook '), + canOwn: ownsItem('weapon_armoire_shepherdsCrook'), }, }; diff --git a/common/script/cron.js b/common/script/cron.js new file mode 100644 index 0000000000..639bbf6b65 --- /dev/null +++ b/common/script/cron.js @@ -0,0 +1,110 @@ +/* + ------------------------------------------------------ + Cron and time / day functions + ------------------------------------------------------ + */ +import _ from 'lodash'; +import moment from 'moment'; + +export const DAY_MAPPING = { + 0: 'su', + 1: 'm', + 2: 't', + 3: 'w', + 4: 'th', + 5: 'f', + 6: 's', +}; + +/* + Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences. + Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values. + {now} is also passed in for various purposes, one example being the test scripts scripts testing different "now" times. + */ + +function sanitizeOptions (o) { + let ref = Number(o.dayStart || 0); + let dayStart = !_.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0; + let timezoneOffset = o.timezoneOffset ? Number(o.timezoneOffset) : Number(moment().zone()); + // TODO: check and clean timezoneOffset as for dayStart (e.g., might not be a number) + let now = o.now ? moment(o.now).zone(timezoneOffset) : moment().zone(timezoneOffset); + + // return a new object, we don't want to add "now" to user object + return { + dayStart, + timezoneOffset, + now, + }; +} + +export function startOfWeek (options = {}) { + let o = sanitizeOptions(options); + + return moment(o.now).startOf('week'); +} + +/* + This is designed for use with any date that has an important time portion (e.g., when comparing the current date-time with the previous cron's date-time for determing if cron should run now). + It changes the time portion of the date-time to be the Custom Day Start hour, so that the date-time is now the user's correct start of day. + It SUBTRACTS a day if the date-time's original hour is before CDS (e.g., if your CDS is 5am and it's currently 4am, it's still the previous day). + This is NOT suitable for manipulating any dates that are displayed to the user as a date with no time portion, such as a Daily's Start Dates (e.g., a Start Date of today shows only the date, so it should be considered to be today even if the hidden time portion is before CDS). + */ + +export function startOfDay (options = {}) { + let o = sanitizeOptions(options); + let dayStart = moment(o.now).startOf('day').add({ hours: o.dayStart }); + + if (moment(o.now).hour() < o.dayStart) { + dayStart.subtract({ days: 1 }); + } + return dayStart; +} + +/* + Absolute diff from "yesterday" till now + */ + +export function daysSince (yesterday, options = {}) { + let o = sanitizeOptions(options); + + return startOfDay(_.defaults({ now: o.now }, o)).diff(startOfDay(_.defaults({ now: yesterday }, o)), 'days'); +} + +/* + Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart? + */ + +export function shouldDo (day, dailyTask, options = {}) { + if (dailyTask.type !== 'daily') { + return false; + } + let o = sanitizeOptions(options); + let startOfDayWithCDSTime = startOfDay(_.defaults({ now: day }, o)); + + // The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it. + // Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times. + // NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS. + let taskStartDate = moment(dailyTask.startDate).zone(o.timezoneOffset); + + taskStartDate = moment(taskStartDate).startOf('day'); + if (taskStartDate > startOfDayWithCDSTime.startOf('day')) { + return false; // Daily starts in the future + } + if (dailyTask.frequency === 'daily') { // "Every X Days" + if (!dailyTask.everyX) { + return false; // error condition + } + let daysSinceTaskStart = startOfDayWithCDSTime.startOf('day').diff(taskStartDate, 'days'); + + return daysSinceTaskStart % dailyTask.everyX === 0; + } else if (dailyTask.frequency === 'weekly') { // "On Certain Days of the Week" + if (!dailyTask.repeat) { + return false; // error condition + } + let dayOfWeekNum = startOfDayWithCDSTime.day(); // e.g., 0 for Sunday + + return dailyTask.repeat[DAY_MAPPING[dayOfWeekNum]]; + } else { + return false; // error condition - unexpected frequency string + } +} diff --git a/common/script/index.js b/common/script/index.js index 5bc5044492..ee3790af6f 100644 --- a/common/script/index.js +++ b/common/script/index.js @@ -1,4 +1,10 @@ -var $w, _, api, content, i18n, moment, preenHistory, sanitizeOptions, sortOrder, +import { + daysSince, + shouldDo, +} from '../../common/script/cron'; +import * as statHelpers from './statHelpers'; + +var $w, _, api, content, i18n, moment, preenHistory, sortOrder, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; moment = require('moment'); @@ -12,6 +18,13 @@ i18n = require('./i18n'); api = module.exports = {}; api.i18n = i18n; +api.shouldDo = shouldDo; + +api.maxLevel = statHelpers.MAX_LEVEL; +api.capByLevel = statHelpers.capByLevel; +api.maxHealth = statHelpers.MAX_HEALTH; +api.tnl = statHelpers.toNextLevel; +api.diminishingReturns = statHelpers.diminishingReturns; $w = api.$w = function(s) { return s.split(' '); @@ -61,184 +74,6 @@ api.planGemLimits = { convCap: 25 }; - -/* - ------------------------------------------------------ - Time / Day - ------------------------------------------------------ - */ - - -/* - Each time we're performing date math (cron, task-due-days, etc), we need to take user preferences into consideration. - Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values. - {now} is also passed in for various purposes, one example being the test scripts scripts testing different "now" times - */ - -sanitizeOptions = function(o) { - var dayStart, now, ref, timezoneOffset; - dayStart = !_.isNaN(+o.dayStart) && (0 <= (ref = +o.dayStart) && ref <= 24) ? +o.dayStart : 0; - timezoneOffset = o.timezoneOffset ? +o.timezoneOffset : +moment().zone(); - now = o.now ? moment(o.now).zone(timezoneOffset) : moment(+(new Date)).zone(timezoneOffset); - return { - dayStart: dayStart, - timezoneOffset: timezoneOffset, - now: now - }; -}; - -api.startOfWeek = api.startOfWeek = function(options) { - var o; - if (options == null) { - options = {}; - } - o = sanitizeOptions(options); - return moment(o.now).startOf('week'); -}; - -api.startOfDay = function(options) { - var dayStart, o; - if (options == null) { - options = {}; - } - o = sanitizeOptions(options); - dayStart = moment(o.now).startOf('day').add({ - hours: o.dayStart - }); - if (moment(o.now).hour() < o.dayStart) { - dayStart.subtract({ - days: 1 - }); - } - return dayStart; -}; - -api.dayMapping = { - 0: 'su', - 1: 'm', - 2: 't', - 3: 'w', - 4: 'th', - 5: 'f', - 6: 's' -}; - - -/* - Absolute diff from "yesterday" till now - */ - -api.daysSince = function(yesterday, options) { - var o; - if (options == null) { - options = {}; - } - o = sanitizeOptions(options); - return api.startOfDay(_.defaults({ - now: o.now - }, o)).diff(api.startOfDay(_.defaults({ - now: yesterday - }, o)), 'days'); -}; - - -/* - Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart? - */ - -api.shouldDo = function(day, dailyTask, options) { - var dayOfWeekCheck, dayOfWeekNum, daysSinceTaskStart, everyXCheck, o, startOfDayWithCDSTime, taskStartDate; - if (options == null) { - options = {}; - } - if (dailyTask.type !== 'daily') { - return false; - } - o = sanitizeOptions(options); - startOfDayWithCDSTime = api.startOfDay(_.defaults({ - now: day - }, o)); - taskStartDate = moment(dailyTask.startDate).zone(o.timezoneOffset); - taskStartDate = moment(taskStartDate).startOf('day'); - if (taskStartDate > startOfDayWithCDSTime.startOf('day')) { - return false; - } - if (dailyTask.frequency === 'daily') { - if (!dailyTask.everyX) { - return false; - } - daysSinceTaskStart = startOfDayWithCDSTime.startOf('day').diff(taskStartDate, 'days'); - everyXCheck = daysSinceTaskStart % dailyTask.everyX === 0; - return everyXCheck; - } else if (dailyTask.frequency === 'weekly') { - if (!dailyTask.repeat) { - return false; - } - dayOfWeekNum = startOfDayWithCDSTime.day(); - dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeekNum]]; - return dayOfWeekCheck; - } else { - return false; - } -}; - - -/* - ------------------------------------------------------ - Level cap - ------------------------------------------------------ - */ - -api.maxLevel = 100; - -api.capByLevel = function(lvl) { - if (lvl > api.maxLevel) { - return api.maxLevel; - } else { - return lvl; - } -}; - - -/* - ------------------------------------------------------ - Health cap - ------------------------------------------------------ - */ - -api.maxHealth = 50; - - -/* - ------------------------------------------------------ - Scoring - ------------------------------------------------------ - */ - -api.tnl = function(lvl) { - return Math.round(((Math.pow(lvl, 2) * 0.25) + (10 * lvl) + 139.75) / 10) * 10; -}; - - -/* - A hyperbola function that creates diminishing returns, so you can't go to infinite (eg, with Exp gain). - {max} The asymptote - {bonus} All the numbers combined for your point bonus (eg, task.value * user.stats.int * critChance, etc) - {halfway} (optional) the point at which the graph starts bending - */ - -api.diminishingReturns = function(bonus, max, halfway) { - if (halfway == null) { - halfway = max / 2; - } - return max * (bonus / (bonus + halfway)); -}; - -api.monod = function(bonus, rateOfIncrease, max) { - return rateOfIncrease * max * bonus / (rateOfIncrease * bonus + max); -}; - - /* Preen history for users with > 7 history entries This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array @@ -293,7 +128,6 @@ api.preenTodos = function(tasks) { }); }; - /* Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there */ @@ -535,7 +369,7 @@ api.taskClasses = function(task, filters, dayStart, lastCron, showCompleted, mai classes += " beingEdited"; } if (type === 'todo' || type === 'daily') { - if (completed || (type === 'daily' && !api.shouldDo(+(new Date), task, { + if (completed || (type === 'daily' && !shouldDo(+(new Date), task, { dayStart: dayStart }))) { classes += " completed"; @@ -2326,7 +2160,7 @@ api.wrap = function(user, main) { } } dropMultiplier = ((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0) ? 2 : 1; - if ((api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) { + if ((daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) { return; } if (((ref3 = user.flags) != null ? ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) { @@ -2533,7 +2367,7 @@ api.wrap = function(user, main) { options = {}; } now = +options.now || +(new Date); - daysMissed = api.daysSince(user.lastCron, _.defaults({ + daysMissed = daysSince(user.lastCron, _.defaults({ now: now }, user.preferences)); if (!(daysMissed > 0)) { @@ -2599,7 +2433,7 @@ api.wrap = function(user, main) { thatDay = moment(now).subtract({ days: 1 }); - if (api.shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { + if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { _.each(daily.checklist, (function(box) { box.completed = false; return true; @@ -2653,7 +2487,7 @@ api.wrap = function(user, main) { thatDay = moment(now).subtract({ days: n + 1 }); - if (api.shouldDo(thatDay.toDate(), task, user.preferences)) { + if (shouldDo(thatDay.toDate(), task, user.preferences)) { scheduleMisses++; if (user.stats.buffs.stealth) { user.stats.buffs.stealth--; diff --git a/common/script/statHelpers.js b/common/script/statHelpers.js new file mode 100644 index 0000000000..9f92d506ba --- /dev/null +++ b/common/script/statHelpers.js @@ -0,0 +1,44 @@ +/* + ------------------------------------------------------ + Level cap + ------------------------------------------------------ + */ + +export const MAX_LEVEL = 100; + +export function capByLevel (lvl) { + if (lvl > MAX_LEVEL) { + return MAX_LEVEL; + } else { + return lvl; + } +} + +/* + ------------------------------------------------------ + Health cap + ------------------------------------------------------ + */ + +export const MAX_HEALTH = 50; + +/* + ------------------------------------------------------ + Scoring + ------------------------------------------------------ + */ + +export function toNextLevel (lvl) { + return Math.round((Math.pow(lvl, 2) * 0.25 + 10 * lvl + 139.75) / 10) * 10; +} + +/* + A hyperbola function that creates diminishing returns, so you can't go to infinite (eg, with Exp gain). + {max} The asymptote + {bonus} All the numbers combined for your point bonus (eg, task.value * user.stats.int * critChance, etc) + {halfway} (optional) the point at which the graph starts bending + */ + +export function diminishingReturns (bonus, max, halfway = max / 2) { + return max * (bonus / (bonus + halfway)); +} diff --git a/tasks/gulp-eslint.js b/tasks/gulp-eslint.js index f67e0a47c1..83930556c7 100644 --- a/tasks/gulp-eslint.js +++ b/tasks/gulp-eslint.js @@ -1,54 +1,79 @@ import gulp from 'gulp'; import eslint from 'gulp-eslint'; +const SERVER_FILES = [ + './website/src/**/api-v3/**/*.js', + // Comment these out in develop, uncomment them in api-v3 + // './website/src/models/user.js', + // './website/src/server.js' +]; +const COMMON_FILES = [ + './common/script/**/*.js', + // @TODO remove these negations as the files are converted over. + '!./common/script/index.js', + '!./common/script/content/index.js', + '!./common/script/src/**/*.js', + '!./common/script/public/**/*.js', +]; +const TEST_FILES = [ + './test/**/*.js', + // @TODO remove these negations as the test files are cleaned up. + '!./test/api-legacy/**/*', + '!./test/api/**/*', + '!./test/common/simulations/**/*', + '!./test/content/**/*', + '!./test/e2e/**/*', + '!./test/server_side/**/*', + '!./test/spec/**/*', +]; + +let linter = (src, options) => { + return gulp + .src(src) + .pipe(eslint(options)) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +} + // TODO lint client // TDOO separate linting cong between // TODO lint gulp tasks, tests, ...? // TODO what about prefer-const rule? // TODO remove estraverse dependency once https://github.com/adametry/gulp-eslint/issues/117 sorted out gulp.task('lint:server', () => { - return gulp - .src([ - './website/src/**/api-v3/**/*.js', - // Comment these out in develop, uncomment them in api-v3 - // './website/src/models/user.js', - // './website/src/server.js' - ]) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failAfterError()); + return linter(SERVER_FILES); }); gulp.task('lint:common', () => { - return gulp - .src([ - './common/script/**/*.js', - // @TODO remove these negations as the files are converted over. - '!./common/script/index.js', - '!./common/script/content/index.js', - '!./common/script/src/**/*.js', - '!./common/script/public/**/*.js', - ]) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failAfterError()); + return linter(COMMON_FILES); }); gulp.task('lint:tests', () => { - return gulp - .src([ - './test/**/*.js', - // @TODO remove these negations as the test files are cleaned up. - '!./test/api-legacy/**/*', - '!./test/common/**/*', - '!./test/content/**/*', - '!./test/e2e/**/*', - '!./test/server_side/**/*', - '!./test/spec/**/*', - ]) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failAfterError()); + let options = { + rules: { + 'max-nested-callbacks': 0, + 'no-unused-expressions': 0, + 'mocha/no-exclusive-tests': 2, + 'mocha/no-global-tests': 2, + 'mocha/handle-done-callback': 2, + }, + globals: { + 'expect': true, + '_': true, + 'sinon': true, + }, + plugins: [ 'mocha' ], + }; + + return linter(TEST_FILES, options); }); -gulp.task('lint', ['lint:server', 'lint:common']); +gulp.task('lint', ['lint:server', 'lint:common', 'lint:tests']); + +gulp.task('lint:watch', () => { + gulp.watch([ + SERVER_FILES, + COMMON_FILES, + TEST_FILES, + ], ['lint']); +}); diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index bbaf401bfe..c553fdcd3f 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -39,6 +39,13 @@ let testBin = (string) => { return `NODE_ENV=testing ./node_modules/.bin/${string}`; }; +gulp.task('test:nodemon', (done) => { + process.env.PORT = TEST_SERVER_PORT; + process.env.NODE_DB_URI=TEST_DB_URI; + + runSequence('nodemon') +}); + gulp.task('test:prepare:mongo', (cb) => { mongoose.connect(TEST_DB_URI, (err) => { if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`); diff --git a/test/common/algos.mocha.js b/test/common/algos.mocha.js index 06ebd3044d..b398a5bb72 100644 --- a/test/common/algos.mocha.js +++ b/test/common/algos.mocha.js @@ -1,44 +1,37 @@ -var $w, _, beforeAfter, cycle, expect, expectClosePoints, expectDayResetNoDamage, expectGainedPoints, expectLostPoints, expectNoChange, expectStrings, moment, newUser, repeatWithoutLastWeekday, rewrapUser, shared, sinon, test_helper; +/* eslint-disable camelcase, func-names, no-shadow */ +import { + DAY_MAPPING, + startOfWeek, + startOfDay, + daysSince, +} from '../../common/script/cron'; -_ = require('lodash'); - -expect = require('expect.js'); - -sinon = require('sinon'); - -moment = require('moment'); - -shared = require('../../common/script/index.js'); - -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; - -test_helper = require('./test_helper'); - -test_helper.addCustomMatchers(); - -$w = function(s) { +let expect = require('expect.js'); +let sinon = require('sinon'); +let moment = require('moment'); +let test_helper = require('./test_helper'); +let shared = require('../../common/script/index.js'); +let $w = (s) => { return s.split(' '); }; +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; +test_helper.addCustomMatchers(); /* Helper Functions */ -newUser = function(addTasks) { - var buffs, user; - if (addTasks == null) { - addTasks = true; - } - buffs = { +let newUser = (addTasks = true) => { + let buffs = { per: 0, int: 0, con: 0, str: 0, stealth: 0, - streaks: false + streaks: false, }; - user = { + let user = { auth: { - timestamps: {} + timestamps: {}, }, stats: { str: 1, @@ -46,12 +39,12 @@ newUser = function(addTasks) { per: 1, int: 1, mp: 32, - "class": 'warrior', - buffs: buffs + class: 'warrior', + buffs, }, items: { lastDrop: { - count: 0 + count: 0, }, hatchingPotions: {}, eggs: {}, @@ -59,209 +52,194 @@ newUser = function(addTasks) { gear: { equipped: {}, costume: {}, - owned: {} + owned: {}, }, - quests: {} + quests: {}, }, party: { quest: { progress: { - down: 0 - } - } + down: 0, + }, + }, }, preferences: { - autoEquip: true + autoEquip: true, }, dailys: [], todos: [], rewards: [], flags: {}, achievements: { - ultimateGearSets: {} + ultimateGearSets: {}, }, contributor: { - level: 2 + level: 2, }, - _tmp: {} + _tmp: {}, }; + shared.wrap(user); - user.ops.reset(null, function() {}); + user.ops.reset(null, () => {}); if (addTasks) { - _.each(['habit', 'todo', 'daily'], function(task) { - return user.ops.addTask({ + _.each(['habit', 'todo', 'daily'], (task) => { + user.ops.addTask({ body: { type: task, - id: shared.uuid() - } + id: shared.uuid(), + }, }); }); } return user; }; -rewrapUser = function(user) { +let rewrapUser = (user) => { user._wrapped = false; shared.wrap(user); return user; }; -expectStrings = function(obj, paths) { - return _.each(paths, function(path) { - return expect(obj[path]).to.be.ok(); - }); -}; +let beforeAfter = (options = {}) => { + let lastCron; + let user = newUser(); + let ref = [user, _.cloneDeep(user)]; + let before = ref[0]; + let after = ref[1]; -beforeAfter = function(options) { - var after, before, lastCron, ref, user; - if (options == null) { - options = {}; - } - user = newUser(); - ref = [user, _.cloneDeep(user)], before = ref[0], after = ref[1]; rewrapUser(after); if (options.dayStart) { before.preferences.dayStart = after.preferences.dayStart = options.dayStart; } before.preferences.timezoneOffset = after.preferences.timezoneOffset = options.timezoneOffset || moment().zone(); if (options.limitOne) { - before[options.limitOne + "s"] = [before[options.limitOne + "s"][0]]; - after[options.limitOne + "s"] = [after[options.limitOne + "s"][0]]; + before[`${options.limitOne}s`] = [before[`${options.limitOne}s`][0]]; + after[`${options.limitOne}s`] = [after[`${options.limitOne}s`][0]]; } if (options.daysAgo) { - lastCron = moment(options.now || +(new Date)).subtract({ - days: options.daysAgo + lastCron = moment(options.now || Number(new Date())).subtract({ + days: options.daysAgo, }); } if (options.daysAgo && options.cronAfterStart) { lastCron.add({ hours: options.dayStart, - minutes: 1 + minutes: 1, }); } if (options.daysAgo) { - lastCron = +lastCron; + lastCron = Number(lastCron); } - _.each([before, after], function(obj) { + _.each([before, after], (obj) => { if (options.daysAgo) { - return obj.lastCron = lastCron; + obj.lastCron = lastCron; } }); return { - before: before, - after: after + before, + after, }; }; -expectLostPoints = function(before, after, taskType) { +let expectLostPoints = (before, after, taskType) => { if (taskType === 'daily' || taskType === 'habit') { expect(after.stats.hp).to.be.lessThan(before.stats.hp); - expect(after[taskType + "s"][0].history).to.have.length(1); + expect(after[`${taskType}s`][0].history).to.have.length(1); } else { expect(after.history.todos).to.have.length(1); } expect(after).toHaveExp(0); expect(after).toHaveGP(0); - return expect(after[taskType + "s"][0].value).to.be.lessThan(before[taskType + "s"][0].value); + expect(after[`${taskType}s`][0].value).to.be.lessThan(before[`${taskType}s`][0].value); }; -expectGainedPoints = function(before, after, taskType) { +let expectGainedPoints = (before, after, taskType) => { expect(after.stats.hp).to.be(50); expect(after.stats.exp).to.be.greaterThan(before.stats.exp); expect(after.stats.gp).to.be.greaterThan(before.stats.gp); - expect(after[taskType + "s"][0].value).to.be.greaterThan(before[taskType + "s"][0].value); + expect(after[`${taskType}s`][0].value).to.be.greaterThan(before[`${taskType}s`][0].value); if (taskType === 'habit') { - return expect(after[taskType + "s"][0].history).to.have.length(1); + expect(after[`${taskType}s`][0].history).to.have.length(1); } }; -expectNoChange = function(before, after) { - return _.each($w('stats items gear dailys todos rewards preferences'), function(attr) { - return expect(after[attr]).to.eql(before[attr]); +let expectNoChange = (before, after) => { + _.each($w('stats items gear dailys todos rewards preferences'), (attr) => { + expect(after[attr]).to.eql(before[attr]); }); }; -expectClosePoints = function(before, after, taskType) { +let expectClosePoints = (before, after, taskType) => { expect(Math.abs(after.stats.exp - before.stats.exp)).to.be.lessThan(0.0001); expect(Math.abs(after.stats.gp - before.stats.gp)).to.be.lessThan(0.0001); - return expect(Math.abs(after[taskType + "s"][0].value - before[taskType + "s"][0].value)).to.be.lessThan(0.0001); + expect(Math.abs(after[taskType + 's'][0].value - before[taskType + 's'][0].value)).to.be.lessThan(0.0001); // eslint-disable-line prefer-template }; -expectDayResetNoDamage = function(b, a) { - var after, before, ref; - ref = [_.cloneDeep(b), _.cloneDeep(a)], before = ref[0], after = ref[1]; - _.each(after.dailys, function(task, i) { +let expectDayResetNoDamage = (b, a) => { + let ref = [_.cloneDeep(b), _.cloneDeep(a)]; + let before = ref[0]; + let after = ref[1]; + + _.each(after.dailys, (task, i) => { expect(task.completed).to.be(false); expect(before.dailys[i].value).to.be(task.value); expect(before.dailys[i].streak).to.be(task.streak); - return expect(task.history).to.have.length(1); + expect(task.history).to.have.length(1); }); - _.each(after.todos, function(task, i) { + _.each(after.todos, (task, i) => { expect(task.completed).to.be(false); - return expect(before.todos[i].value).to.be.greaterThan(task.value); + expect(before.todos[i].value).to.be.greaterThan(task.value); }); expect(after.history.todos).to.have.length(1); - _.each([before, after], function(obj) { + _.each([before, after], (obj) => { delete obj.stats.buffs; - return _.each($w('dailys todos history lastCron'), function(path) { + _.each($w('dailys todos history lastCron'), (path) => { return delete obj[path]; }); }); delete after._tmp; - return expectNoChange(before, after); + expectNoChange(before, after); }; -cycle = function(array) { - var n; - n = -1; - return function(seed) { - if (seed == null) { - seed = 0; - } - n++; - return array[n % array.length]; - }; -}; - -repeatWithoutLastWeekday = function() { - var repeat; - repeat = { +let repeatWithoutLastWeekday = () => { + let repeat = { su: true, m: true, t: true, w: true, th: true, f: true, - s: true + s: true, }; - if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { + + if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { repeat.su = false; } else { repeat.s = false; } return { - repeat: repeat + repeat, }; }; -describe('User', function() { - it('sets correct user defaults', function() { - var base_gear, buffs, user; - user = newUser(); - base_gear = { +describe('User', () => { + it('sets correct user defaults', () => { + let user = newUser(); + let base_gear = { armor: 'armor_base_0', weapon: 'weapon_base_0', head: 'head_base_0', - shield: 'shield_base_0' + shield: 'shield_base_0', }; - buffs = { + let buffs = { per: 0, int: 0, con: 0, str: 0, stealth: 0, - streaks: false + streaks: false, }; + expect(user.stats).to.eql({ str: 1, con: 1, @@ -272,47 +250,48 @@ describe('User', function() { lvl: 1, exp: 0, gp: 0, - "class": 'warrior', - buffs: buffs + class: 'warrior', + buffs, }); expect(user.items.gear).to.eql({ equipped: base_gear, costume: base_gear, owned: { - weapon_warrior_0: true - } + weapon_warrior_0: true, + }, }); - return expect(user.preferences).to.eql({ + expect(user.preferences).to.eql({ autoEquip: true, - costume: false + costume: false, }); }); - it('calculates max MP', function() { - var user; - user = newUser(); + it('calculates max MP', () => { + let user = newUser(); + expect(user).toHaveMaxMP(32); user.stats.int = 10; expect(user).toHaveMaxMP(50); user.stats.lvl = 5; expect(user).toHaveMaxMP(54); - user.stats["class"] = 'wizard'; + user.stats.class = 'wizard'; user.items.gear.equipped.weapon = 'weapon_wizard_1'; - return expect(user).toHaveMaxMP(63); + expect(user).toHaveMaxMP(63); }); - it('handles perfect days', function() { - var cron, user, yesterday; - user = newUser(); + it('handles perfect days', () => { + let user = newUser(); + user.dailys = []; - _.times(3, function() { + _.times(3, () => { return user.dailys.push(shared.taskDefaults({ type: 'daily', - startDate: moment().subtract(7, 'days') + startDate: moment().subtract(7, 'days'), })); }); - cron = function() { + let cron = () => { user.lastCron = moment().subtract(1, 'days'); return user.fns.cron(); }; + cron(); expect(user.stats.buffs.str).to.be(0); expect(user.achievements.perfect).to.not.be.ok(); @@ -320,191 +299,197 @@ describe('User', function() { cron(); expect(user.stats.buffs.str).to.be(0); expect(user.achievements.perfect).to.not.be.ok(); - _.each(user.dailys, function(d) { - return d.completed = true; + _.each(user.dailys, (d) => { + d.completed = true; }); cron(); expect(user.stats.buffs.str).to.be(1); expect(user.achievements.perfect).to.be(1); - yesterday = moment().subtract(1, 'days'); - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; - _.each(user.dailys.slice(1), function(d) { - return d.completed = true; + + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; + _.each(user.dailys.slice(1), (d) => { + d.completed = true; }); cron(); expect(user.stats.buffs.str).to.be(1); - return expect(user.achievements.perfect).to.be(2); + expect(user.achievements.perfect).to.be(2); }); - describe('Resting in the Inn', function() { - var cron, user; - user = null; - cron = null; - beforeEach(function() { + describe('Resting in the Inn', () => { + let user = null; + let cron = null; + + beforeEach(() => { user = newUser(); user.preferences.sleep = true; - cron = function() { + cron = () => { user.lastCron = moment().subtract(1, 'days'); return user.fns.cron(); }; user.dailys = []; - return _.times(2, function() { + _.times(2, () => { return user.dailys.push(shared.taskDefaults({ type: 'daily', - startDate: moment().subtract(7, 'days') + startDate: moment().subtract(7, 'days'), })); }); }); - it('remains in the inn on cron', function() { + it('remains in the inn on cron', () => { cron(); - return expect(user.preferences.sleep).to.be(true); + expect(user.preferences.sleep).to.be(true); }); - it('resets dailies', function() { + it('resets dailies', () => { user.dailys[0].completed = true; cron(); - return expect(user.dailys[0].completed).to.be(false); + expect(user.dailys[0].completed).to.be(false); }); - it('resets checklist on incomplete dailies', function() { + it('resets checklist on incomplete dailies', () => { user.dailys[0].checklist = [ { - "text": "1", - "id": "checklist-one", - "completed": true + text: '1', + id: 'checklist-one', + completed: true, }, { - "text": "2", - "id": "checklist-two", - "completed": true + text: '2', + id: 'checklist-two', + completed: true, }, { - "text": "3", - "id": "checklist-three", - "completed": false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; cron(); - return _.each(user.dailys[0].checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); }); }); - it('resets checklist on complete dailies', function() { + it('resets checklist on complete dailies', () => { user.dailys[0].checklist = [ { - "text": "1", - "id": "checklist-one", - "completed": true + text: '1', + id: 'checklist-one', + completed: true, }, { - "text": "2", - "id": "checklist-two", - "completed": true + text: '2', + id: 'checklist-two', + completed: true, }, { - "text": "3", - "id": "checklist-three", - "completed": false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; user.dailys[0].completed = true; cron(); - return _.each(user.dailys[0].checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); }); }); - it('does not reset checklist on grey incomplete dailies', function() { - var yesterday; - yesterday = moment().subtract(1, 'days'); - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; + it('does not reset checklist on grey incomplete dailies', () => { + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; user.dailys[0].checklist = [ { - "text": "1", - "id": "checklist-one", - "completed": true + text: '1', + id: 'checklist-one', + completed: true, }, { - "text": "2", - "id": "checklist-two", - "completed": true + text: '2', + id: 'checklist-two', + completed: true, }, { - "text": "3", - "id": "checklist-three", - "completed": true - } + text: '3', + id: 'checklist-three', + completed: true, + }, ]; cron(); - return _.each(user.dailys[0].checklist, function(box) { - return expect(box.completed).to.be(true); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(true); }); }); - it('resets checklist on complete grey complete dailies', function() { - var yesterday; - yesterday = moment().subtract(1, 'days'); - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; + it('resets checklist on complete grey complete dailies', () => { + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; user.dailys[0].checklist = [ { - "text": "1", - "id": "checklist-one", - "completed": true + text: '1', + id: 'checklist-one', + completed: true, }, { - "text": "2", - "id": "checklist-two", - "completed": true + text: '2', + id: 'checklist-two', + completed: true, }, { - "text": "3", - "id": "checklist-three", - "completed": true - } + text: '3', + id: 'checklist-three', + completed: true, + }, ]; user.dailys[0].completed = true; cron(); - return _.each(user.dailys[0].checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); }); }); - it('does not damage user for incomplete dailies', function() { + it('does not damage user for incomplete dailies', () => { expect(user).toHaveHP(50); user.dailys[0].completed = true; user.dailys[1].completed = false; cron(); - return expect(user).toHaveHP(50); + expect(user).toHaveHP(50); }); - it('gives credit for complete dailies', function() { + it('gives credit for complete dailies', () => { user.dailys[0].completed = true; expect(user.dailys[0].history).to.be.empty; cron(); - return expect(user.dailys[0].history).to.not.be.empty; + expect(user.dailys[0].history).to.not.be.empty; }); - return it('damages user for incomplete dailies after checkout', function() { + it('damages user for incomplete dailies after checkout', () => { expect(user).toHaveHP(50); user.dailys[0].completed = true; user.dailys[1].completed = false; user.preferences.sleep = false; cron(); - return expect(user.stats.hp).to.be.lessThan(50); + expect(user.stats.hp).to.be.lessThan(50); }); }); - describe('Death', function() { - var user; - user = void 0; - it('revives correctly', function() { + describe('Death', () => { + let user; + + beforeEach(() => { user = newUser(); + }); + + it('revives correctly', () => { user.stats = { gp: 10, exp: 100, lvl: 2, hp: 0, - "class": 'warrior' + class: 'warrior', }; user.ops.revive(); expect(user).toHaveGP(0); expect(user).toHaveExp(0); expect(user).toHaveLevel(1); expect(user).toHaveHP(50); - return expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: false + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: false, }); }); - it("doesn't break unbreakables", function() { - var ce; + + it('doesn\'t break unbreakables', () => { + let ce; + ce = shared.countExists; - user = newUser(); - user.items.gear.owned['shield_warrior_1'] = true; - user.items.gear.owned['shield_rogue_1'] = true; - user.items.gear.owned['head_special_nye'] = true; + user.items.gear.owned.shield_warrior_1 = true; + user.items.gear.owned.shield_rogue_1 = true; + user.items.gear.owned.head_special_nye = true; expect(ce(user.items.gear.owned)).to.be(4); user.stats.hp = 0; user.ops.revive(); @@ -515,168 +500,170 @@ describe('User', function() { user.stats.hp = 0; user.ops.revive(); expect(ce(user.items.gear.owned)).to.be(2); - return expect(user.items.gear.owned).to.eql({ + expect(user.items.gear.owned).to.eql({ weapon_warrior_0: false, shield_warrior_1: false, shield_rogue_1: true, - head_special_nye: true + head_special_nye: true, }); }); - return it("handles event items", function() { + + it('handles event items', () => { + user.items.gear.owned.head_special_nye = true; + shared.content.gear.flat.head_special_nye.event.start = '2012-01-01'; shared.content.gear.flat.head_special_nye.event.end = '2012-02-01'; expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); - delete user.items.gear.owned['head_special_nye']; + delete user.items.gear.owned.head_special_nye; expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(false); shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5, 'days'); shared.content.gear.flat.head_special_nye.event.end = moment().add(5, 'days'); - return expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); }); }); - describe('Rebirth', function() { - var user; - user = void 0; - return it('removes correct gear', function() { - user = newUser(); + describe('Rebirth', () => { + it('removes correct gear', () => { + let user = newUser(); + user.stats.lvl = 100; user.items.gear.owned = { - "weapon_warrior_0": true, - "weapon_warrior_1": true, - "armor_warrior_1": false, - "armor_mystery_201402": true, - "back_mystery_201402": false, - "head_mystery_201402": true, - "weapon_armoire_basicCrossbow": true + weapon_warrior_0: true, + weapon_warrior_1: true, + armor_warrior_1: false, + armor_mystery_201402: true, + back_mystery_201402: false, + head_mystery_201402: true, + weapon_armoire_basicCrossbow: true, }; user.ops.rebirth(); - return expect(user.items.gear.owned).to.eql({ - "weapon_warrior_0": true, - "weapon_warrior_1": false, - "armor_warrior_1": false, - "armor_mystery_201402": true, - "back_mystery_201402": false, - "head_mystery_201402": true, - "weapon_armoire_basicCrossbow": false + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + weapon_warrior_1: false, + armor_warrior_1: false, + armor_mystery_201402: true, + back_mystery_201402: false, + head_mystery_201402: true, + weapon_armoire_basicCrossbow: false, }); }); }); - describe('store', function() { - it('buys a Quest scroll', function() { - var user; - user = newUser(); + describe('store', () => { + it('buys a Quest scroll', () => { + let user = newUser(); + user.stats.gp = 205; user.ops.buyQuest({ params: { - key: 'dilatoryDistress1' - } + key: 'dilatoryDistress1', + }, }); expect(user.items.quests).to.eql({ - dilatoryDistress1: 1 + dilatoryDistress1: 1, }); - return expect(user).toHaveGP(5); + expect(user).toHaveGP(5); }); - it('does not buy Quests without enough Gold', function() { - var user; - user = newUser(); + it('does not buy Quests without enough Gold', () => { + let user = newUser(); + user.stats.gp = 1; user.ops.buyQuest({ params: { - key: 'dilatoryDistress1' - } + key: 'dilatoryDistress1', + }, }); expect(user.items.quests).to.eql({}); - return expect(user).toHaveGP(1); + expect(user).toHaveGP(1); }); - it('does not buy nonexistent Quests', function() { - var user; - user = newUser(); + it('does not buy nonexistent Quests', () => { + let user = newUser(); + user.stats.gp = 9999; user.ops.buyQuest({ params: { - key: 'snarfblatter' - } + key: 'snarfblatter', + }, }); expect(user.items.quests).to.eql({}); - return expect(user).toHaveGP(9999); + expect(user).toHaveGP(9999); }); - return it('does not buy Gem-premium Quests', function() { - var user; - user = newUser(); + it('does not buy Gem-premium Quests', () => { + let user = newUser(); + user.stats.gp = 9999; user.ops.buyQuest({ params: { - key: 'kraken' - } + key: 'kraken', + }, }); expect(user.items.quests).to.eql({}); - return expect(user).toHaveGP(9999); + expect(user).toHaveGP(9999); }); }); - describe('Gem purchases', function() { - it('does not purchase items without enough Gems', function() { - var user; - user = newUser(); + describe('Gem purchases', () => { + it('does not purchase items without enough Gems', () => { + let user = newUser(); + user.ops.purchase({ params: { type: 'eggs', - key: 'Cactus' - } + key: 'Cactus', + }, }); user.ops.purchase({ params: { type: 'gear', - key: 'headAccessory_special_foxEars' - } + key: 'headAccessory_special_foxEars', + }, }); user.ops.unlock({ query: { - path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars' - } + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', + }, }); expect(user.items.eggs).to.eql({}); - return expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, }); }); - it('purchases an egg', function() { - var user; - user = newUser(); + it('purchases an egg', () => { + let user = newUser(); + user.balance = 1; user.ops.purchase({ params: { type: 'eggs', - key: 'Cactus' - } + key: 'Cactus', + }, }); expect(user.items.eggs).to.eql({ - Cactus: 1 + Cactus: 1, }); - return expect(user.balance).to.eql(0.25); + expect(user.balance).to.eql(0.25); }); - it('purchases fox ears', function() { - var user; - user = newUser(); + it('purchases fox ears', () => { + let user = newUser(); + user.balance = 1; user.ops.purchase({ params: { type: 'gear', - key: 'headAccessory_special_foxEars' - } + key: 'headAccessory_special_foxEars', + }, }); expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, - headAccessory_special_foxEars: true + headAccessory_special_foxEars: true, }); - return expect(user.balance).to.eql(0.5); + expect(user.balance).to.eql(0.5); }); - return it('unlocks all the animal ears at once', function() { - var user; - user = newUser(); + it('unlocks all the animal ears at once', () => { + let user = newUser(); + user.balance = 2; user.ops.unlock({ query: { - path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars' - } + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', + }, }); expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, @@ -687,56 +674,57 @@ describe('User', function() { headAccessory_special_pandaEars: true, headAccessory_special_pigEars: true, headAccessory_special_tigerEars: true, - headAccessory_special_wolfEars: true + headAccessory_special_wolfEars: true, }); - return expect(user.balance).to.eql(0.75); + expect(user.balance).to.eql(0.75); }); }); - describe('spells', function() { - return _.each(shared.content.spells, function(spellClass) { - return _.each(spellClass, function(spell) { - return it(spell.text + " has valid values", function() { + describe('spells', () => { + _.each(shared.content.spells, (spellClass) => { + _.each(spellClass, (spell) => { + it(`${spell.text} has valid values`, () => { expect(spell.target).to.match(/^(task|self|party|user)$/); expect(spell.mana).to.be.an('number'); if (spell.lvl) { expect(spell.lvl).to.be.an('number'); expect(spell.lvl).to.be.above(0); } - return expect(spell.cast).to.be.a('function'); + expect(spell.cast).to.be.a('function'); }); }); }); }); - describe('drop system', function() { - var MAX_RANGE_FOR_EGG, MAX_RANGE_FOR_FOOD, MAX_RANGE_FOR_POTION, MIN_RANGE_FOR_EGG, MIN_RANGE_FOR_FOOD, MIN_RANGE_FOR_POTION, user; - user = null; - MIN_RANGE_FOR_POTION = 0; - MAX_RANGE_FOR_POTION = .3; - MIN_RANGE_FOR_EGG = .4; - MAX_RANGE_FOR_EGG = .6; - MIN_RANGE_FOR_FOOD = .7; - MAX_RANGE_FOR_FOOD = 1; - beforeEach(function() { + describe('drop system', () => { + let user = null; + const MIN_RANGE_FOR_POTION = 0; + const MAX_RANGE_FOR_POTION = 0.3; + const MIN_RANGE_FOR_EGG = 0.4; + const MAX_RANGE_FOR_EGG = 0.6; + const MIN_RANGE_FOR_FOOD = 0.7; + const MAX_RANGE_FOR_FOOD = 1; + + beforeEach(function () { user = newUser(); user.flags.dropsEnabled = true; this.task_id = shared.uuid(); return user.ops.addTask({ body: { type: 'daily', - id: this.task_id - } + id: this.task_id, + }, }); }); - it('drops a hatching potion', function() { - var j, random, ref, ref1, results; - results = []; - for (random = j = ref = MIN_RANGE_FOR_POTION, ref1 = MAX_RANGE_FOR_POTION; j <= ref1; random = j += .1) { + + it('drops a hatching potion', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_POTION; random <= MAX_RANGE_FOR_POTION; random += 0.1) { sinon.stub(user.fns, 'predictableRandom').returns(random); user.ops.score({ params: { id: this.task_id, - direction: 'up' - } + direction: 'up', + }, }); expect(user.items.eggs).to.be.empty; expect(user.items.hatchingPotions).to.not.be.empty; @@ -745,16 +733,16 @@ describe('User', function() { } return results; }); - it('drops a pet egg', function() { - var j, random, ref, ref1, results; - results = []; - for (random = j = ref = MIN_RANGE_FOR_EGG, ref1 = MAX_RANGE_FOR_EGG; j <= ref1; random = j += .1) { + it('drops a pet egg', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_EGG; random <= MAX_RANGE_FOR_EGG; random += 0.1) { sinon.stub(user.fns, 'predictableRandom').returns(random); user.ops.score({ params: { id: this.task_id, - direction: 'up' - } + direction: 'up', + }, }); expect(user.items.eggs).to.not.be.empty; expect(user.items.hatchingPotions).to.be.empty; @@ -763,16 +751,17 @@ describe('User', function() { } return results; }); - it('drops food', function() { - var j, random, ref, ref1, results; - results = []; - for (random = j = ref = MIN_RANGE_FOR_FOOD, ref1 = MAX_RANGE_FOR_FOOD; j <= ref1; random = j += .1) { + + it('drops food', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_FOOD; random <= MAX_RANGE_FOR_FOOD; random += 0.1) { sinon.stub(user.fns, 'predictableRandom').returns(random); user.ops.score({ params: { id: this.task_id, - direction: 'up' - } + direction: 'up', + }, }); expect(user.items.eggs).to.be.empty; expect(user.items.hatchingPotions).to.be.empty; @@ -781,23 +770,25 @@ describe('User', function() { } return results; }); - return it('does not get a drop', function() { + + it('does not get a drop', function () { sinon.stub(user.fns, 'predictableRandom').returns(0.5); user.ops.score({ params: { id: this.task_id, - direction: 'up' - } + direction: 'up', + }, }); expect(user.items.eggs).to.eql({}); expect(user.items.hatchingPotions).to.eql({}); expect(user.items.food).to.eql({}); - return user.fns.predictableRandom.restore(); + + user.fns.predictableRandom.restore(); }); }); - describe('Quests', function() { - return _.each(shared.content.quests, function(quest) { - return it((quest.text()) + " has valid values", function() { + describe('Quests', () => { + _.each(shared.content.quests, (quest) => { + it(`${ quest.text() } has valid values`, () => { expect(quest.notes()).to.be.an('string'); if (quest.completion) { expect(quest.completion()).to.be.an('string'); @@ -817,214 +808,225 @@ describe('User', function() { if (quest.boss) { expect(quest.boss.name()).to.be.an('string'); expect(quest.boss.hp).to.be.greaterThan(0); - return expect(quest.boss.str).to.be.greaterThan(0); + expect(quest.boss.str).to.be.greaterThan(0); } else if (quest.collect) { - return _.each(quest.collect, function(collect) { + _.each(quest.collect, (collect) => { expect(collect.text()).to.be.an('string'); - return expect(collect.count).to.be.greaterThan(0); + expect(collect.count).to.be.greaterThan(0); }); } }); }); }); - describe('Achievements', function() { - _.each(shared.content.classes, function(klass) { - var user; - user = newUser(); + describe('Achievements', () => { + _.each(shared.content.classes, (klass) => { + let user = newUser(); + user.stats.gp = 10000; - _.each(shared.content.gearTypes, function(type) { - return _.each([1, 2, 3, 4, 5], function(i) { + _.each(shared.content.gearTypes, (type) => { + _.each([1, 2, 3, 4, 5], (i) => { return user.ops.buy({ - params: '#{type}_#{klass}_#{i}' + params: `${type}_${klass}_${i}`, }); }); }); - it('does not get ultimateGear ' + klass, function() { - return expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok(); + + it(`does not get ultimateGear ${klass}`, () => { + expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok(); }); - _.each(shared.content.gearTypes, function(type) { + _.each(shared.content.gearTypes, (type) => { return user.ops.buy({ - params: '#{type}_#{klass}_6' + params: `${type}_${klass}_6`, }); }); - return xit('gets ultimateGear ' + klass, function() { - return expect(user.achievements.ultimateGearSets[klass]).to.be.ok(); + + xit(`gets ultimateGear ${klass}`, () => { + expect(user.achievements.ultimateGearSets[klass]).to.be.ok(); }); }); - return it('does not remove existing Ultimate Gear achievements', function() { - var user; - user = newUser(); + it('does not remove existing Ultimate Gear achievements', () => { + let user = newUser(); + user.achievements.ultimateGearSets = { - 'healer': true, - 'wizard': true, - 'rogue': true, - 'warrior': true + healer: true, + wizard: true, + rogue: true, + warrior: true, }; user.items.gear.owned.shield_warrior_5 = false; user.items.gear.owned.weapon_rogue_6 = false; user.ops.buy({ - params: 'shield_warrior_5' + params: 'shield_warrior_5', }); - return expect(user.achievements.ultimateGearSets).to.eql({ - 'healer': true, - 'wizard': true, - 'rogue': true, - 'warrior': true + expect(user.achievements.ultimateGearSets).to.eql({ + healer: true, + wizard: true, + rogue: true, + warrior: true, }); }); }); - return describe('unlocking features', function() { - it('unlocks drops at level 3', function() { - var user; - user = newUser(); + describe('unlocking features', () => { + it('unlocks drops at level 3', () => { + let user = newUser(); + user.stats.lvl = 3; user.fns.updateStats(user.stats); - return expect(user.flags.dropsEnabled).to.be.ok(); + expect(user.flags.dropsEnabled).to.be.ok(); }); - it('unlocks Rebirth at level 50', function() { - var user; - user = newUser(); + it('unlocks Rebirth at level 50', () => { + let user = newUser(); + user.stats.lvl = 50; user.fns.updateStats(user.stats); - return expect(user.flags.rebirthEnabled).to.be.ok(); + expect(user.flags.rebirthEnabled).to.be.ok(); }); - return describe('level-awarded Quests', function() { - it('gets Attack of the Mundane at level 15', function() { - var user; - user = newUser(); + describe('level-awarded Quests', () => { + it('gets Attack of the Mundane at level 15', () => { + let user = newUser(); + user.stats.lvl = 15; user.fns.updateStats(user.stats); expect(user.flags.levelDrops.atom1).to.be.ok(); - return expect(user.items.quests.atom1).to.eql(1); + expect(user.items.quests.atom1).to.eql(1); }); - it('gets Vice at level 30', function() { - var user; - user = newUser(); + + it('gets Vice at level 30', () => { + let user = newUser(); + user.stats.lvl = 30; user.fns.updateStats(user.stats); expect(user.flags.levelDrops.vice1).to.be.ok(); - return expect(user.items.quests.vice1).to.eql(1); + expect(user.items.quests.vice1).to.eql(1); }); - it('gets Golden Knight at level 40', function() { - var user; - user = newUser(); + + it('gets Golden Knight at level 40', () => { + let user = newUser(); + user.stats.lvl = 40; user.fns.updateStats(user.stats); expect(user.flags.levelDrops.goldenknight1).to.be.ok(); - return expect(user.items.quests.goldenknight1).to.eql(1); + expect(user.items.quests.goldenknight1).to.eql(1); }); - return it('gets Moonstone Chain at level 60', function() { - var user; - user = newUser(); + + it('gets Moonstone Chain at level 60', () => { + let user = newUser(); + user.stats.lvl = 60; user.fns.updateStats(user.stats); expect(user.flags.levelDrops.moonstone1).to.be.ok(); - return expect(user.items.quests.moonstone1).to.eql(1); + expect(user.items.quests.moonstone1).to.eql(1); }); }); }); }); -describe('Simple Scoring', function() { - beforeEach(function() { - var ref; - return ref = beforeAfter(), this.before = ref.before, this.after = ref.after, ref; +describe('Simple Scoring', () => { + beforeEach(function () { + let ref = beforeAfter(); + + this.before = ref.before; + this.after = ref.after; }); - it('Habits : Up', function() { + + it('Habits : Up', function () { this.after.ops.score({ params: { id: this.after.habits[0].id, - direction: 'down' + direction: 'down', }, query: { - times: 5 - } + times: 5, + }, }); - return expectLostPoints(this.before, this.after, 'habit'); + expectLostPoints(this.before, this.after, 'habit'); }); - it('Habits : Down', function() { + it('Habits : Down', function () { this.after.ops.score({ params: { id: this.after.habits[0].id, - direction: 'up' + direction: 'up', }, query: { - times: 5 - } + times: 5, + }, }); - return expectGainedPoints(this.before, this.after, 'habit'); + expectGainedPoints(this.before, this.after, 'habit'); }); - it('Dailys : Up', function() { + it('Dailys : Up', function () { this.after.ops.score({ params: { id: this.after.dailys[0].id, - direction: 'up' - } + direction: 'up', + }, }); - return expectGainedPoints(this.before, this.after, 'daily'); + expectGainedPoints(this.before, this.after, 'daily'); }); - it('Dailys : Up, Down', function() { + it('Dailys : Up, Down', function () { this.after.ops.score({ params: { id: this.after.dailys[0].id, - direction: 'up' - } + direction: 'up', + }, }); this.after.ops.score({ params: { id: this.after.dailys[0].id, - direction: 'down' - } + direction: 'down', + }, }); - return expectClosePoints(this.before, this.after, 'daily'); + expectClosePoints(this.before, this.after, 'daily'); }); - it('Todos : Up', function() { + it('Todos : Up', function () { this.after.ops.score({ params: { id: this.after.todos[0].id, - direction: 'up' - } + direction: 'up', + }, }); - return expectGainedPoints(this.before, this.after, 'todo'); + expectGainedPoints(this.before, this.after, 'todo'); }); - return it('Todos : Up, Down', function() { + + it('Todos : Up, Down', function () { this.after.ops.score({ params: { id: this.after.todos[0].id, - direction: 'up' - } + direction: 'up', + }, }); this.after.ops.score({ params: { id: this.after.todos[0].id, - direction: 'down' - } + direction: 'down', + }, }); - return expectClosePoints(this.before, this.after, 'todo'); + expectClosePoints(this.before, this.after, 'todo'); }); }); -describe('Cron', function() { - it('computes shouldCron', function() { - var paths, user; - user = newUser(); - paths = {}; +describe('Cron', () => { + it('computes shouldCron', () => { + let user = newUser(); + let paths = {}; + user.fns.cron({ - paths: paths + paths, }); expect(user.lastCron).to.not.be.ok; - user.lastCron = +moment().subtract(1, 'days'); + user.lastCron = Number(moment().subtract(1, 'days')); paths = {}; user.fns.cron({ - paths: paths + paths, }); - return expect(user.lastCron).to.be.greaterThan(0); + expect(user.lastCron).to.be.greaterThan(0); }); - it('only dailies & todos are affected', function() { - var after, afterTasks, before, beforeTasks, ref; - ref = beforeAfter({ - daysAgo: 1 - }), before = ref.before, after = ref.after; + it('only dailies & todos are affected', () => { + let ref = beforeAfter({ + daysAgo: 1, + }); + let before = ref.before; + let after = ref.after; + before.dailys = before.todos = after.dailys = after.todos = []; after.fns.cron(); before.stats.mp = after.stats.mp; @@ -1032,130 +1034,137 @@ describe('Cron', function() { delete after.stats.buffs; delete before.stats.buffs; expect(before.stats).to.eql(after.stats); - beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards); - afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards); - return expect(beforeTasks).to.eql(afterTasks); + + let beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards); + let afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards); + + expect(beforeTasks).to.eql(afterTasks); }); - describe('preening', function() { - beforeEach(function() { - return this.clock = sinon.useFakeTimers(Date.parse("2013-11-20"), "Date"); + describe('preening', () => { + beforeEach(function () { + this.clock = sinon.useFakeTimers(Date.parse('2013-11-20'), 'Date'); }); - afterEach(function() { + afterEach(function () { return this.clock.restore(); }); - return it('should preen user history', function() { - var after, before, history, ref; - ref = beforeAfter({ - daysAgo: 1 - }), before = ref.before, after = ref.after; - history = [ + + it('should preen user history', function () { + let ref = beforeAfter({ + daysAgo: 1, + }); + let after = ref.after; + + let history = [ { date: '09/01/2012', - value: 0 + value: 0, }, { date: '10/01/2012', - value: 0 + value: 0, }, { date: '11/01/2012', - value: 2 + value: 2, }, { date: '12/01/2012', - value: 2 + value: 2, }, { date: '01/01/2013', - value: 1 + value: 1, }, { date: '01/15/2013', - value: 3 + value: 3, }, { date: '02/01/2013', - value: 2 + value: 2, }, { date: '02/15/2013', - value: 4 + value: 4, }, { date: '03/01/2013', - value: 3 + value: 3, }, { date: '03/15/2013', - value: 5 + value: 5, }, { date: '04/01/2013', - value: 4 + value: 4, }, { date: '04/15/2013', - value: 6 + value: 6, }, { date: '05/01/2013', - value: 5 + value: 5, }, { date: '05/15/2013', - value: 7 + value: 7, }, { date: '06/01/2013', - value: 6 + value: 6, }, { date: '06/15/2013', - value: 8 + value: 8, }, { date: '07/01/2013', - value: 7 + value: 7, }, { date: '07/15/2013', - value: 9 + value: 9, }, { date: '08/01/2013', - value: 8 + value: 8, }, { date: '08/15/2013', - value: 10 + value: 10, }, { date: '09/01/2013', - value: 9 + value: 9, }, { date: '09/15/2013', - value: 11 + value: 11, }, { date: '010/01/2013', - value: 10 + value: 10, }, { date: '010/15/2013', - value: 12 + value: 12, }, { date: '011/01/2013', - value: 12 + value: 12, }, { date: '011/02/2013', - value: 13 + value: 13, }, { date: '011/03/2013', - value: 14 + value: 14, }, { date: '011/04/2013', - value: 15 - } + value: 15, + }, ]; + after.history = { exp: _.cloneDeep(history), - todos: _.cloneDeep(history) + todos: _.cloneDeep(history), }; after.habits[0].history = _.cloneDeep(history); after.fns.cron(); after.history.exp.pop(); after.history.todos.pop(); - return _.each([after.history.exp, after.history.todos, after.habits[0].history], function(arr) { - return expect(_.map(arr, function(x) { + _.each([after.history.exp, after.history.todos, after.habits[0].history], function (arr) { + expect(_.map(arr, (x) => { return x.value; })).to.eql([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); }); }); }); - describe('Todos', function() { - it('1 day missed', function() { - var after, before, ref; - ref = beforeAfter({ - daysAgo: 1 - }), before = ref.before, after = ref.after; + describe('Todos', () => { + it('1 day missed', () => { + let ref = beforeAfter({ + daysAgo: 1, + }); + let before = ref.before; + let after = ref.after; + before.dailys = after.dailys = []; after.fns.cron(); expect(after).toHaveHP(50); @@ -1163,117 +1172,123 @@ describe('Cron', function() { expect(after).toHaveGP(0); expect(before.todos[0].value).to.be(0); expect(after.todos[0].value).to.be(-1); - return expect(after.history.todos).to.have.length(1); + expect(after.history.todos).to.have.length(1); }); - return it('2 days missed', function() { - var after, before, ref; - ref = beforeAfter({ - daysAgo: 2 - }), before = ref.before, after = ref.after; + it('2 days missed', () => { + let ref = beforeAfter({ + daysAgo: 2, + }); + let before = ref.before; + let after = ref.after; + before.dailys = after.dailys = []; after.fns.cron(); expect(before.todos[0].value).to.be(0); - return expect(after.todos[0].value).to.be(-1); + expect(after.todos[0].value).to.be(-1); }); }); - describe('cron day calculations', function() { - var dayStart, fstr; - dayStart = 4; - fstr = "YYYY-MM-DD HH:mm:ss"; - it('startOfDay before dayStart', function() { - var start; - start = shared.startOfDay({ - now: moment('2014-10-09 02:30:00'), - dayStart: dayStart - }); - return expect(start.format(fstr)).to.eql('2014-10-08 04:00:00'); - }); - it('startOfDay after dayStart', function() { - var start; - start = shared.startOfDay({ - now: moment('2014-10-09 05:30:00'), - dayStart: dayStart - }); - return expect(start.format(fstr)).to.eql('2014-10-09 04:00:00'); - }); - it('daysSince cron before, now after', function() { - var days, lastCron; - lastCron = moment('2014-10-09 02:30:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-09 11:30:00'), - dayStart: dayStart - }); - return expect(days).to.eql(1); - }); - it('daysSince cron before, now before', function() { - var days, lastCron; - lastCron = moment('2014-10-09 02:30:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-09 03:30:00'), - dayStart: dayStart - }); - return expect(days).to.eql(0); - }); - it('daysSince cron after, now after', function() { - var days, lastCron; - lastCron = moment('2014-10-09 05:30:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-09 06:30:00'), - dayStart: dayStart - }); - return expect(days).to.eql(0); - }); - it('daysSince cron after, now tomorrow before', function() { - var days, lastCron; - lastCron = moment('2014-10-09 12:30:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-10 01:30:00'), - dayStart: dayStart - }); - return expect(days).to.eql(0); - }); - it('daysSince cron after, now tomorrow after', function() { - var days, lastCron; - lastCron = moment('2014-10-09 12:30:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-10 10:30:00'), - dayStart: dayStart - }); - return expect(days).to.eql(1); - }); - return xit('daysSince, last cron before new dayStart', function() { - var days, lastCron; - lastCron = moment('2014-10-09 01:00:00'); - days = shared.daysSince(lastCron, { - now: moment('2014-10-09 05:00:00'), - dayStart: dayStart - }); - return expect(days).to.eql(0); - }); - }); - return describe('dailies', function() { - return describe('new day', function() { + describe('cron day calculations', () => { + let dayStart = 4; + let fstr = 'YYYY-MM-DD HH: mm: ss'; + it('startOfDay before dayStart', () => { + let start = startOfDay({ + now: moment('2014-10-09 02: 30: 00'), + dayStart, + }); + + expect(start.format(fstr)).to.eql('2014-10-08 04: 00: 00'); + }); + it('startOfDay after dayStart', () => { + let start = startOfDay({ + now: moment('2014-10-09 05: 30: 00'), + dayStart, + }); + + expect(start.format(fstr)).to.eql('2014-10-09 04: 00: 00'); + }); + it('daysSince cron before, now after', () => { + let lastCron = moment('2014-10-09 02: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 11: 30: 00'), + dayStart, + }); + + expect(days).to.eql(1); + }); + it('daysSince cron before, now before', () => { + let lastCron = moment('2014-10-09 02: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 03: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + it('daysSince cron after, now after', () => { + let lastCron = moment('2014-10-09 05: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 06: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + it('daysSince cron after, now tomorrow before', () => { + let lastCron = moment('2014-10-09 12: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-10 01: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + it('daysSince cron after, now tomorrow after', () => { + let lastCron = moment('2014-10-09 12: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-10 10: 30: 00'), + dayStart, + }); + + expect(days).to.eql(1); + }); + xit('daysSince, last cron before new dayStart', () => { + let lastCron = moment('2014-10-09 01: 00: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 05: 00: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + }); + + describe('dailies', () => { + describe('new day', () => { /* - This section runs through a "cron matrix" of all permutations (that I can easily account for). It sets + This section runs through a 'cron matrix' of all permutations (that I can easily account for). It sets task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron, and so on - testing each possible outcome along the way */ - var cronMatrix, recurseCronMatrix, runCron; - runCron = function(options) { - return _.each([480, 240, 0, -120], function(timezoneOffset) { - var after, before, now, ref; - now = shared.startOfWeek({ - timezoneOffset: timezoneOffset + + function runCron (options) { + _.each([480, 240, 0, -120], function (timezoneOffset) { + let now = startOfWeek({ + timezoneOffset, }).add(options.currentHour || 0, 'hours'); - ref = beforeAfter({ - now: now, - timezoneOffset: timezoneOffset, + + let ref = beforeAfter({ + now, + timezoneOffset, daysAgo: 1, cronAfterStart: options.cronAfterStart || true, dayStart: options.dayStart || 0, - limitOne: 'daily' - }), before = ref.before, after = ref.after; + limitOne: 'daily', + }); + + let before = ref.before; + let after = ref.after; + if (options.repeat) { before.dailys[0].repeat = after.dailys[0].repeat = options.repeat; } @@ -1284,42 +1299,42 @@ describe('Cron', function() { before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days'); if (options.shouldDo) { expect(shared.shouldDo(now.toDate(), after.dailys[0], { - timezoneOffset: timezoneOffset, + timezoneOffset, dayStart: options.dayStart, - now: now + now, })).to.be.ok(); } after.fns.cron({ - now: now + now, }); before.stats.mp = after.stats.mp; - switch (options.expect) { - case 'losePoints': - expectLostPoints(before, after, 'daily'); - break; - case 'noChange': - expectNoChange(before, after); - break; - case 'noDamage': - expectDayResetNoDamage(before, after); + + if (options.expect === 'losePoints') { + expectLostPoints(before, after, 'daily'); + } else if (options.expect === 'noChange') { + expectNoChange(before, after); + } else if (options.expect === 'noDamage') { + expectDayResetNoDamage(before, after); } + return { - before: before, - after: after + before, + after, }; }); - }; - cronMatrix = { + } + + let cronMatrix = { steps: { 'due yesterday': { defaults: { daysAgo: 1, cronAfterStart: true, - limitOne: 'daily' + limitOne: 'daily', }, steps: { '(simple)': { - expect: 'losePoints' + expect: 'losePoints', }, 'due today': { defaults: { @@ -1330,45 +1345,45 @@ describe('Cron', function() { w: true, th: true, f: true, - s: true - } + s: true, + }, }, steps: { 'pre-dayStart': { defaults: { currentHour: 3, dayStart: 4, - shouldDo: true + shouldDo: true, }, steps: { - 'checked': { + checked: { checked: true, - expect: 'noChange' + expect: 'noChange', }, 'un-checked': { checked: false, - expect: 'noChange' - } - } + expect: 'noChange', + }, + }, }, 'post-dayStart': { defaults: { currentHour: 5, dayStart: 4, - shouldDo: true + shouldDo: true, }, steps: { - 'checked': { + checked: { checked: true, - expect: 'noDamage' + expect: 'noDamage', }, - 'unchecked': { + unchecked: { checked: false, - expect: 'losePoints' - } - } - } - } + expect: 'losePoints', + }, + }, + }, + }, }, 'NOT due today': { defaults: { @@ -1379,129 +1394,128 @@ describe('Cron', function() { w: true, th: true, f: true, - s: true - } + s: true, + }, }, steps: { 'pre-dayStart': { defaults: { currentHour: 3, dayStart: 4, - shouldDo: true + shouldDo: true, }, steps: { - 'checked': { + checked: { checked: true, - expect: 'noChange' + expect: 'noChange', }, 'un-checked': { checked: false, - expect: 'noChange' - } - } + expect: 'noChange', + }, + }, }, 'post-dayStart': { defaults: { currentHour: 5, dayStart: 4, - shouldDo: false + shouldDo: false, }, steps: { - 'checked': { + checked: { checked: true, - expect: 'noDamage' + expect: 'noDamage', }, - 'unchecked': { + unchecked: { checked: false, - expect: 'losePoints' - } - } - } - } - } - } + expect: 'losePoints', + }, + }, + }, + }, + }, + }, }, 'not due yesterday': { defaults: repeatWithoutLastWeekday(), steps: { '(simple)': { - expect: 'noDamage' + expect: 'noDamage', }, 'post-dayStart': { currentHour: 5, dayStart: 4, - expect: 'noDamage' + expect: 'noDamage', }, 'pre-dayStart': { currentHour: 3, dayStart: 4, - expect: 'noChange' - } - } - } - } + expect: 'noChange', + }, + }, + }, + }, }; - recurseCronMatrix = function(obj, options) { - if (options == null) { - options = {}; - } + + let recurseCronMatrix = (obj, options = {}) => { if (obj.steps) { - return _.each(obj.steps, function(step, text) { - var o; - o = _.cloneDeep(options); - if (o.text == null) { + _.each(obj.steps, (step, text) => { + let o = _.cloneDeep(options); + + if (!o.text) { o.text = ''; } - o.text += " " + text + " "; + o.text += `${text}`; return recurseCronMatrix(step, _.defaults(o, obj.defaults)); }); } else { - return it("" + options.text, function() { + it(`${options.text}`, () => { return runCron(_.defaults(obj, options)); }); } }; + return recurseCronMatrix(cronMatrix); }); }); }); -describe('Helper', function() { - it('calculates gold coins', function() { +describe('Helper', () => { + it('calculates gold coins', () => { expect(shared.gold(10)).to.eql(10); expect(shared.gold(1.957)).to.eql(1); - return expect(shared.gold()).to.eql(0); + expect(shared.gold()).to.eql(0); }); - it('calculates silver coins', function() { + it('calculates silver coins', () => { expect(shared.silver(10)).to.eql(0); expect(shared.silver(1.957)).to.eql(95); - expect(shared.silver(0.01)).to.eql("01"); - return expect(shared.silver()).to.eql("00"); + expect(shared.silver(0.01)).to.eql('01'); + expect(shared.silver()).to.eql('00'); }); - it('calculates experience to next level', function() { + it('calculates experience to next level', () => { expect(shared.tnl(1)).to.eql(150); expect(shared.tnl(2)).to.eql(160); expect(shared.tnl(10)).to.eql(260); - return expect(shared.tnl(99)).to.eql(3580); + expect(shared.tnl(99)).to.eql(3580); }); - return it('calculates the start of the day', function() { - var fstr, today, zone; - fstr = 'YYYY-MM-DD HH:mm:ss'; - today = '2013-01-01 00:00:00'; - zone = moment(today).zone(); - expect(shared.startOfDay({ - now: new Date(2013, 0, 1, 0) + it('calculates the start of the day', () => { + let fstr = 'YYYY-MM-DD HH: mm: ss'; + let today = '2013-01-01 00: 00: 00'; + let zone = moment(today).zone(); + + expect(startOfDay({ + now: new Date(2013, 0, 1, 0), }, { - timezoneOffset: zone + timezoneOffset: zone, }).format(fstr)).to.eql(today); - expect(shared.startOfDay({ - now: new Date(2013, 0, 1, 5) + expect(startOfDay({ + now: new Date(2013, 0, 1, 5), }, { - timezoneOffset: zone + timezoneOffset: zone, }).format(fstr)).to.eql(today); - return expect(shared.startOfDay({ + expect(startOfDay({ now: new Date(2013, 0, 1, 23, 59, 59), - timezoneOffset: zone + timezoneOffset: zone, }).format(fstr)).to.eql(today); }); }); diff --git a/test/common/count.js b/test/common/count.js index 86d746d3ff..a19d648267 100644 --- a/test/common/count.js +++ b/test/common/count.js @@ -1,191 +1,206 @@ -var sinon = require('sinon'); -var chai = require("chai") -chai.use(require("sinon-chai")) -var expect = chai.expect +/* eslint-disable camelcase */ +let count = require('../../common/script/count'); -var count = require('../../common/script/count'); +describe('count', () => { + describe('beastMasterProgress', () => { + it('returns 0 if no pets', () => { + let pets = {}; + let beastMasterTotal = count.beastMasterProgress(pets); -describe('count', function() { - describe('beastMasterProgress', function() { - it('returns 0 if no pets', function() { - var pets = {}; - var beastMasterTotal = count.beastMasterProgress(pets); expect(beastMasterTotal).to.eql(0); }); - it('counts drop pets', function() { - var pets = { "Dragon-Red": 1, "Wolf-Base": 2 }; - var beastMasterTotal = count.beastMasterProgress(pets); + it('counts drop pets', () => { + let pets = { 'Dragon-Red': 1, 'Wolf-Base': 2 }; + let beastMasterTotal = count.beastMasterProgress(pets); + expect(beastMasterTotal).to.eql(2); }); - it('does not count quest pets', function() { - var pets = { "Dragon-Red": 1, "Gryphon-Base": 1 }; - var beastMasterTotal = count.beastMasterProgress(pets); + it('does not count quest pets', () => { + let pets = { 'Dragon-Red': 1, 'Gryphon-Base': 1 }; + let beastMasterTotal = count.beastMasterProgress(pets); + expect(beastMasterTotal).to.eql(1); }); - it('does not count pets hatched with premium potions', function() { - var pets = { - "Wolf-Spooky": 5, - "Dragon-Spooky": 5, - "FlyingPig-Base": 5 - } - var beastMasterTotal = count.beastMasterProgress(pets); - expect(beastMasterTotal).to.eql(1); - }); - - it('does not count special pets', function() { - var pets = { - "Wolf-Base": 2, - "Wolf-Veteran": 1, - "Wolf-Cerberus": 1, - "Dragon-Hydra": 1 + it('does not count pets hatched with premium potions', () => { + let pets = { + 'Wolf-Spooky': 5, + 'Dragon-Spooky': 5, + 'FlyingPig-Base': 5, }; - var beastMasterTotal = count.beastMasterProgress(pets); + let beastMasterTotal = count.beastMasterProgress(pets); + expect(beastMasterTotal).to.eql(1); }); - it('counts drop pets that have been raised to a mount', function() { - var raisedToMount = -1; - var pets = { "Dragon-Red": 1, "Wolf-Base": raisedToMount }; - var beastMasterTotal = count.beastMasterProgress(pets); + it('does not count special pets', () => { + let pets = { + 'Wolf-Base': 2, + 'Wolf-Veteran': 1, + 'Wolf-Cerberus': 1, + 'Dragon-Hydra': 1, + }; + let beastMasterTotal = count.beastMasterProgress(pets); + + expect(beastMasterTotal).to.eql(1); + }); + + it('counts drop pets that have been raised to a mount', () => { + let raisedToMount = -1; + let pets = { 'Dragon-Red': 1, 'Wolf-Base': raisedToMount }; + let beastMasterTotal = count.beastMasterProgress(pets); + expect(beastMasterTotal).to.eql(2); }); - it('does not counts drop pets that have been released', function() { - var releasedPet = 0; - var pets = { "Dragon-Red": 1, "Wolf-Base": releasedPet }; - var beastMasterTotal = count.beastMasterProgress(pets); + it('does not counts drop pets that have been released', () => { + let releasedPet = 0; + let pets = { 'Dragon-Red': 1, 'Wolf-Base': releasedPet }; + let beastMasterTotal = count.beastMasterProgress(pets); + expect(beastMasterTotal).to.eql(1); }); }); - describe('mountMasterProgress', function() { - it('returns 0 if no mounts', function() { - var mounts = {}; - var mountMasterTotal = count.mountMasterProgress(mounts); + describe('mountMasterProgress', () => { + it('returns 0 if no mounts', () => { + let mounts = {}; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(0); }); - it('counts drop mounts', function() { - var mounts = { "Dragon-Red": true, "Wolf-Base": true }; - var mountMasterTotal = count.mountMasterProgress(mounts); + it('counts drop mounts', () => { + let mounts = { 'Dragon-Red': true, 'Wolf-Base': true }; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(2); }); - it('does not count premium mounts', function() { - var mounts = { - "Dragon-Red": true, - "FlyingPig-Spooky": true - } - var mountMasterTotal = count.mountMasterProgress(mounts); + it('does not count premium mounts', () => { + let mounts = { + 'Dragon-Red': true, + 'FlyingPig-Spooky': true, + }; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(1); }); - it('does not count quest mounts', function() { - var mounts = { "Dragon-Red": true, "Gryphon-Base": true }; - var mountMasterTotal = count.mountMasterProgress(mounts); + it('does not count quest mounts', () => { + let mounts = { 'Dragon-Red': true, 'Gryphon-Base': true }; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(1); }); - it('does not count special mounts', function() { - var mounts = { "Wolf-Base": true, "BearCub-Polar": true}; - var mountMasterTotal = count.mountMasterProgress(mounts); + it('does not count special mounts', () => { + let mounts = { 'Wolf-Base': true, 'BearCub-Polar': true}; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(1); }); - it('only counts drop mounts that are currently owned', function() { - var notCurrentlyOwned = false; - var mounts = { "Dragon-Red": true, "Wolf-Base": notCurrentlyOwned }; - var mountMasterTotal = count.mountMasterProgress(mounts); + it('only counts drop mounts that are currently owned', () => { + let notCurrentlyOwned = false; + let mounts = { 'Dragon-Red': true, 'Wolf-Base': notCurrentlyOwned }; + let mountMasterTotal = count.mountMasterProgress(mounts); + expect(mountMasterTotal).to.eql(1); }); }); - describe('remainingGearInSet', function() { - it('counts remaining gear based on set', function() { - var gear = { - 'weapon_wizard_0':true, - 'weapon_wizard_1':true, - 'weapon_warrior_0':true, - 'weapon_warrior_1':true, - 'weapon_armor_0':true, - 'weapon_armor_1':true + describe('remainingGearInSet', () => { + it('counts remaining gear based on set', () => { + let gear = { + weapon_wizard_0: true, + weapon_wizard_1: true, + weapon_warrior_0: true, + weapon_warrior_1: true, + weapon_armor_0: true, + weapon_armor_1: true, }; - var armoireCount = count.remainingGearInSet(gear, 'warrior'); + let armoireCount = count.remainingGearInSet(gear, 'warrior'); + expect(armoireCount).to.eql(20); }); - it.skip('includes previously owned items in count (https://github.com/HabitRPG/habitrpg/issues/5624#issuecomment-124018717)', function() { - var gear = { - 'weapon_warrior_0':false, - 'weapon_warrior_1':false, - 'weapon_armor_0':true, - 'weapon_armor_1':true + it.skip('includes previously owned items in count (https: //github.com/HabitRPG/habitrpg/issues/5624#issuecomment-124018717)', () => { + let gear = { + weapon_warrior_0: false, + weapon_warrior_1: false, + weapon_armor_0: true, + weapon_armor_1: true, }; - var armoireCount = count.remainingGearInSet(gear, 'warrior'); + let armoireCount = count.remainingGearInSet(gear, 'warrior'); + expect(armoireCount).to.eql(20); }); }); - describe('dropPetsCurrentlyOwned', function() { - it('counts drop pets owned', function() { - var pets = { - "Wolf-Base": 2, - "Wolf-Red": 4 + describe('dropPetsCurrentlyOwned', () => { + it('counts drop pets owned', () => { + let pets = { + 'Wolf-Base': 2, + 'Wolf-Red': 4, }; - var dropPets = count.dropPetsCurrentlyOwned(pets); + let dropPets = count.dropPetsCurrentlyOwned(pets); + expect(dropPets).to.eql(2); }); - it('does not count pets that have been raised to mounts', function() { - var pets = { - "Wolf-Base": -1, - "Wolf-Red": 4, - "Wolf-Veteran": 1, - "Gryphon-Base": 1 + it('does not count pets that have been raised to mounts', () => { + let pets = { + 'Wolf-Base': -1, + 'Wolf-Red': 4, + 'Wolf-Veteran': 1, + 'Gryphon-Base': 1, }; - var dropPets = count.dropPetsCurrentlyOwned(pets); + let dropPets = count.dropPetsCurrentlyOwned(pets); + expect(dropPets).to.eql(1); }); - it('does not count quest pets', function() { - var pets = { - "Wolf-Base": 2, - "Wolf-Red": 4, - "Gryphon-Base": 1 + it('does not count quest pets', () => { + let pets = { + 'Wolf-Base': 2, + 'Wolf-Red': 4, + 'Gryphon-Base': 1, }; - var dropPets = count.dropPetsCurrentlyOwned(pets); + let dropPets = count.dropPetsCurrentlyOwned(pets); + expect(dropPets).to.eql(2); }); - it('does not count special pets', function() { - var pets = { - "Wolf-Base": 2, - "Wolf-Red": 4, - "Wolf-Veteran": 1 + it('does not count special pets', () => { + let pets = { + 'Wolf-Base': 2, + 'Wolf-Red': 4, + 'Wolf-Veteran': 1, }; - var dropPets = count.dropPetsCurrentlyOwned(pets); + let dropPets = count.dropPetsCurrentlyOwned(pets); + expect(dropPets).to.eql(2); }); }); - describe('questsOfCategory', function() { - - it('counts user quest scrolls of a particular category', function() { - var quests = { - "atom1": 2, - "whale": 4, - "kraken": 2, - "sheep": 1, - "goldenknight2": 1 + describe('questsOfCategory', () => { + it('counts user quest scrolls of a particular category', () => { + let quests = { + atom1: 2, + whale: 4, + kraken: 2, + sheep: 1, + goldenknight2: 1, }; - var petQuestCount = count.questsOfCategory(quests, 'pet'); - var unlockableQuestCount = count.questsOfCategory(quests, 'unlockable'); - var goldQuestCount = count.questsOfCategory(quests, 'gold'); + let petQuestCount = count.questsOfCategory(quests, 'pet'); + let unlockableQuestCount = count.questsOfCategory(quests, 'unlockable'); + let goldQuestCount = count.questsOfCategory(quests, 'gold'); + expect(petQuestCount).to.eql(3); expect(unlockableQuestCount).to.eql(2); expect(goldQuestCount).to.eql(0); diff --git a/test/common/dailies.js b/test/common/dailies.js index 92d80ee5ad..079ecb59e9 100644 --- a/test/common/dailies.js +++ b/test/common/dailies.js @@ -1,57 +1,53 @@ -var _, cron, expect, moment, newUser, repeatWithoutLastWeekday, shared, sinon; +/* eslint-disable camelcase */ +import { + startOfWeek, +} from '../../common/script/cron'; -_ = require('lodash'); - -expect = require('expect.js'); - -sinon = require('sinon'); - -moment = require('moment'); - -shared = require('../../common/script/index.js'); +let expect = require('expect.js'); // eslint-disable-line no-shadow +let moment = require('moment'); +let shared = require('../../common/script/index.js'); shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; -repeatWithoutLastWeekday = function() { - var repeat; - repeat = { +let repeatWithoutLastWeekday = () => { // eslint-disable-line no-unused-vars + let repeat = { su: true, m: true, t: true, w: true, th: true, f: true, - s: true + s: true, }; - if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { + + if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { repeat.su = false; } else { repeat.s = false; } return { - repeat: repeat + repeat, }; }; /* Helper Functions */ -newUser = function(addTasks) { - var buffs, user; - if (addTasks == null) { - addTasks = true; - } +let newUser = (addTasks = true) => { + let buffs; + let user; + buffs = { per: 0, int: 0, con: 0, str: 0, stealth: 0, - streaks: false + streaks: false, }; user = { auth: { - timestamps: {} + timestamps: {}, }, stats: { str: 1, @@ -59,27 +55,27 @@ newUser = function(addTasks) { per: 1, int: 1, mp: 32, - "class": 'warrior', - buffs: buffs + class: 'warrior', + buffs, }, items: { lastDrop: { - count: 0 + count: 0, }, hatchingPotions: {}, eggs: {}, food: {}, gear: { equipped: {}, - costume: {} - } + costume: {}, + }, }, party: { quest: { progress: { - down: 0 - } - } + down: 0, + }, + }, }, preferences: {}, dailys: [], @@ -88,45 +84,42 @@ newUser = function(addTasks) { flags: {}, achievements: {}, contributor: { - level: 2 - } + level: 2, + }, }; shared.wrap(user); - user.ops.reset(null, function() {}); + user.ops.reset(null, () => {}); if (addTasks) { - _.each(['habit', 'todo', 'daily'], function(task) { - return user.ops.addTask({ + _.each(['habit', 'todo', 'daily'], (task) => { + user.ops.addTask({ body: { type: task, - id: shared.uuid() - } + id: shared.uuid(), + }, }); }); } return user; }; -cron = function(usr, missedDays) { - if (missedDays == null) { - missedDays = 1; - } +let cron = (usr, missedDays = 1) => { usr.lastCron = moment().subtract(missedDays, 'days'); - return usr.fns.cron(); + usr.fns.cron(); }; -describe('daily/weekly that repeats everyday (default)', function() { - var daily, user, weekly; - user = null; - daily = null; - weekly = null; - describe('when startDate is in the future', function() { - beforeEach(function() { +describe('daily/weekly that repeats everyday (default)', () => { + let user = null; + let daily = null; + let 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' + frequency: 'daily', }), shared.taskDefaults({ type: 'daily', startDate: moment().add(7, 'days'), @@ -138,39 +131,39 @@ describe('daily/weekly that repeats everyday (default)', function() { w: true, th: true, f: true, - s: true - } - }) + s: true, + }, + }), ]; daily = user.dailys[0]; - return weekly = user.dailys[1]; + weekly = user.dailys[1]; }); - it('does not damage user for not completing it', function() { + it('does not damage user for not completing it', () => { cron(user); - return expect(user.stats.hp).to.be(50); + expect(user.stats.hp).to.be(50); }); - it('does not change value on cron if daily is incomplete', function() { + it('does not change value on cron if daily is incomplete', () => { cron(user); expect(daily.value).to.be(0); - return expect(weekly.value).to.be(0); + expect(weekly.value).to.be(0); }); - it('does not reset checklists if daily is not marked as complete', function() { - var checklist; - checklist = [ + it('does not reset checklists if daily is not marked as complete', () => { + let checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; + daily.checklist = checklist; weekly.checklist = checklist; cron(user); @@ -179,359 +172,369 @@ describe('daily/weekly that repeats everyday (default)', function() { expect(daily.checklist[2].completed).to.be(false); expect(weekly.checklist[0].completed).to.be(true); expect(weekly.checklist[1].completed).to.be(true); - return expect(weekly.checklist[2].completed).to.be(false); + expect(weekly.checklist[2].completed).to.be(false); }); - it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ + it('resets checklists if daily is marked as complete', () => { + let checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; + daily.checklist = checklist; weekly.checklist = checklist; daily.completed = true; weekly.completed = true; cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); }); }); - return it('is due on startDate', function() { - var daily_due_on_start_date, daily_due_today, weekly_due_on_start_date, weekly_due_today; - daily_due_today = shared.shouldDo(moment(), daily); - daily_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), daily); + it('is due on startDate', () => { + let daily_due_today = shared.shouldDo(moment(), daily); + let 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); + + let weekly_due_today = shared.shouldDo(moment(), weekly); + let weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); + expect(weekly_due_today).to.be(false); - return expect(weekly_due_on_start_date).to.be(true); + expect(weekly_due_on_start_date).to.be(true); }); }); - describe('when startDate is in the past', function() { - beforeEach(function() { + describe('when startDate is in the past', () => { + beforeEach(() => { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', startDate: moment().subtract(7, 'days'), - frequency: 'daily' + frequency: 'daily', }), shared.taskDefaults({ type: 'daily', startDate: moment().subtract(7, 'days'), - frequency: 'weekly' - }) + frequency: 'weekly', + }), ]; daily = user.dailys[0]; - return weekly = user.dailys[1]; + weekly = user.dailys[1]; }); - it('does damage user for not completing it', function() { + it('does damage user for not completing it', () => { cron(user); - return expect(user.stats.hp).to.be.lessThan(50); + expect(user.stats.hp).to.be.lessThan(50); }); - it('decreases value on cron if daily is incomplete', function() { + it('decreases value on cron if daily is incomplete', () => { cron(user, 1); expect(daily.value).to.be(-1); - return expect(weekly.value).to.be(-1); + expect(weekly.value).to.be(-1); }); - it('decreases value on cron once only if daily is incomplete and multiple days are missed', function() { + it('decreases value on cron once only if daily is incomplete and multiple days are missed', () => { cron(user, 7); expect(daily.value).to.be(-1); - return expect(weekly.value).to.be(-1); + expect(weekly.value).to.be(-1); }); - it('resets checklists if daily is not marked as complete', function() { - var checklist; + it('resets checklists if daily is not marked as complete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; daily.checklist = checklist; weekly.checklist = checklist; cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); }); }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ + it('resets checklists if daily is marked as complete', () => { + let checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; + daily.checklist = checklist; daily.completed = true; weekly.checklist = checklist; weekly.completed = true; cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); }); }); }); - return describe('when startDate is today', function() { - beforeEach(function() { + describe('when startDate is today', () => { + beforeEach(() => { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', startDate: moment().subtract(1, 'days'), - frequency: 'daily' + frequency: 'daily', }), shared.taskDefaults({ type: 'daily', startDate: moment().subtract(1, 'days'), - frequency: 'weekly' - }) + frequency: 'weekly', + }), ]; daily = user.dailys[0]; - return weekly = user.dailys[1]; + weekly = user.dailys[1]; }); - it('does damage user for not completing it', function() { + it('does damage user for not completing it', () => { cron(user); - return expect(user.stats.hp).to.be.lessThan(50); + expect(user.stats.hp).to.be.lessThan(50); }); - it('decreases value on cron if daily is incomplete', function() { + it('decreases value on cron if daily is incomplete', () => { cron(user); expect(daily.value).to.be.lessThan(0); - return expect(weekly.value).to.be.lessThan(0); + expect(weekly.value).to.be.lessThan(0); }); - it('resets checklists if daily is not marked as complete', function() { - var checklist; + it('resets checklists if daily is not marked as complete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; daily.checklist = checklist; weekly.checklist = checklist; cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); }); }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; + it('resets checklists if daily is marked as complete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true + text: '1', + id: 'checklist-one', + completed: true, }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true + text: '2', + id: 'checklist-two', + completed: true, }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } + text: '3', + id: 'checklist-three', + completed: false, + }, ]; daily.checklist = checklist; daily.completed = true; weekly.checklist = checklist; weekly.completed = true; cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); }); }); }); }); -describe('daily that repeats every x days', function() { - var daily, user; - user = null; - daily = null; - beforeEach(function() { +describe('daily that repeats every x days', () => { + let user = null; + let daily = null; + + beforeEach(() => { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', startDate: moment(), - frequency: 'daily' - }) + frequency: 'daily', + }), ]; - return daily = user.dailys[0]; + daily = user.dailys[0]; }); - return _.times(11, function(due) { - return it('where x equals ' + due, function() { + _.times(11, (due) => { + it(`where x equals ${due}`, () => { daily.everyX = due; - return _.times(30, function(day) { - var isDue; + _.times(30, (day) => { + let isDue; + isDue = shared.shouldDo(moment().add(day, 'days'), daily); if (day % due === 0) { expect(isDue).to.be(true); } if (day % due !== 0) { - return expect(isDue).to.be(false); + expect(isDue).to.be(false); } }); }); }); }); -describe('daily that repeats every X days when multiple days are missed', function() { - var daily, everyX, startDateDaysAgo, user; - everyX = 3; - startDateDaysAgo = everyX * 3; - user = null; - daily = null; - describe('including missing a due date', function() { - var missedDays; - missedDays = everyX * 2 + 1; - beforeEach(function() { +describe('daily that repeats every X days when multiple days are missed', () => { + let everyX = 3; + let startDateDaysAgo = everyX * 3; + let user = null; + let daily = null; + + describe('including missing a due date', () => { + let missedDays = everyX * 2 + 1; + + beforeEach(() => { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', - everyX: everyX - }) + everyX, + }), ]; - return daily = user.dailys[0]; + daily = user.dailys[0]; }); - it('decreases value on cron once only if daily is incomplete', function() { + it('decreases value on cron once only if daily is incomplete', () => { cron(user, missedDays); - return expect(daily.value).to.be(-1); + expect(daily.value).to.be(-1); }); - it('resets checklists if daily is incomplete', function() { - var checklist; - checklist = [ + it('resets checklists if daily is incomplete', () => { + let checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - } + text: '1', + id: 'checklist-one', + completed: true, + }, ]; + daily.checklist = checklist; cron(user, missedDays); - return _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; + it('resets checklists if daily is marked as complete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - } + text: '1', + id: 'checklist-one', + completed: true, + }, ]; daily.checklist = checklist; daily.completed = true; cron(user, missedDays); - return _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); }); }); - return describe('but not missing a due date', function() { - var missedDays; + describe('but not missing a due date', () => { + let missedDays; + missedDays = everyX - 1; - beforeEach(function() { + beforeEach(() => { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', - everyX: everyX - }) + everyX, + }), ]; - return daily = user.dailys[0]; + daily = user.dailys[0]; }); - it('does not decrease value on cron', function() { + it('does not decrease value on cron', () => { cron(user, missedDays); - return expect(daily.value).to.be(0); + expect(daily.value).to.be(0); }); - it('does not reset checklists if daily is incomplete', function() { - var checklist; + it('does not reset checklists if daily is incomplete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - } + text: '1', + id: 'checklist-one', + completed: true, + }, ]; daily.checklist = checklist; cron(user, missedDays); - return _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(true); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(true); }); }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; + it('resets checklists if daily is marked as complete', () => { + let checklist; + checklist = [ { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - } + text: 1, + id: 'checklist-one', + completed: true, + }, ]; daily.checklist = checklist; daily.completed = true; cron(user, missedDays); - return _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); }); }); }); diff --git a/test/common/statHelpers.test.js b/test/common/statHelpers.test.js new file mode 100644 index 0000000000..a10a1ad714 --- /dev/null +++ b/test/common/statHelpers.test.js @@ -0,0 +1,66 @@ +import { + maxHealth, + maxLevel, + capByLevel, + tnl, + diminishingReturns, +} from '../../common/script/index'; + +describe('helper functions used in stat calculations', () => { + describe('maxHealth', () => { + it('provides a maximum Health value', () => { + const HEALTH_CAP = 50; + + expect(maxHealth).to.eql(HEALTH_CAP); + }); + }); + + const LEVEL_CAP = 100; + const LEVEL = 57; + + describe('maxLevel', () => { + it('returns a maximum level for attribute gain', () => { + expect(maxLevel).to.eql(LEVEL_CAP); + }); + }); + + describe('capByLevel', () => { + it('returns level given if below cap', () => { + expect(capByLevel(LEVEL)).to.eql(LEVEL); + }); + + it('returns level given if equal to cap', () => { + expect(capByLevel(LEVEL_CAP)).to.eql(LEVEL_CAP); + }); + + it('returns level cap if above cap', () => { + expect(capByLevel(LEVEL_CAP + LEVEL)).to.eql(LEVEL_CAP); + }); + }); + + describe('toNextLevel', () => { + it('increases Experience target from one level to the next', () => { + _.times(110, (level) => { + expect(tnl(level + 1)).to.be.greaterThan(tnl(level)); + }); + }); + }); + + describe('diminishingReturns', () => { + const BONUS = 600; + const MAXIMUM = 200; + const HALFWAY = 75; + + it('provides a value under the maximum, given a bonus and maximum', () => { + expect(diminishingReturns(BONUS, MAXIMUM)).to.be.lessThan(MAXIMUM); + }); + + it('provides a value under the maximum, given a bonus, maximum, and halfway point', () => { + expect(diminishingReturns(BONUS, MAXIMUM, HALFWAY)).to.be.lessThan(MAXIMUM); + }); + + it('provides a different curve if a halfway point is defined', () => { + expect(diminishingReturns(BONUS, MAXIMUM, HALFWAY)).to.not.eql(diminishingReturns(BONUS, MAXIMUM)); + }); + }); +}); diff --git a/test/common/test_helper.js b/test/common/test_helper.js index 3c482b44d3..7b9ba2beff 100644 --- a/test/common/test_helper.js +++ b/test/common/test_helper.js @@ -1,53 +1,59 @@ -var expect; +/* eslint-disable prefer-template, no-shadow, func-names */ -expect = require('expect.js'); +let expect = require('expect.js'); + +module.exports.addCustomMatchers = function () { + let Assertion; -module.exports.addCustomMatchers = function() { - var Assertion; Assertion = expect.Assertion; - Assertion.prototype.toHaveGP = function(gp) { - var actual; + Assertion.prototype.toHaveGP = function (gp) { + let actual; + actual = this.obj.stats.gp; - return this.assert(actual === gp, function() { - return "expected user to have " + gp + " gp, but got " + actual; - }, function() { - return "expected user to not have " + gp + " gp"; + return this.assert(actual === gp, () => { + return 'expected user to have ' + gp + ' gp, but got ' + actual; + }, () => { + return 'expected user to not have ' + gp + ' gp'; }); }; - Assertion.prototype.toHaveHP = function(hp) { - var actual; + Assertion.prototype.toHaveHP = function (hp) { + let actual; + actual = this.obj.stats.hp; - return this.assert(actual === hp, function() { - return "expected user to have " + hp + " hp, but got " + actual; - }, function() { - return "expected user to not have " + hp + " hp"; + return this.assert(actual === hp, () => { + return 'expected user to have ' + hp + ' hp, but got ' + actual; + }, () => { + return 'expected user to not have ' + hp + ' hp'; }); }; - Assertion.prototype.toHaveExp = function(exp) { - var actual; + Assertion.prototype.toHaveExp = function (exp) { + let actual; + actual = this.obj.stats.exp; - return this.assert(actual === exp, function() { - return "expected user to have " + exp + " experience points, but got " + actual; - }, function() { - return "expected user to not have " + exp + " experience points"; + return this.assert(actual === exp, () => { + return 'expected user to have ' + exp + ' experience points, but got ' + actual; + }, () => { + return 'expected user to not have ' + exp + ' experience points'; }); }; - Assertion.prototype.toHaveLevel = function(lvl) { - var actual; + Assertion.prototype.toHaveLevel = function (lvl) { + let actual; + actual = this.obj.stats.lvl; - return this.assert(actual === lvl, function() { - return "expected user to be level " + lvl + ", but got " + actual; - }, function() { - return "expected user to not be level " + lvl; + return this.assert(actual === lvl, () => { + return 'expected user to be level ' + lvl + ', but got ' + actual; + }, () => { + return 'expected user to not be level ' + lvl; }); }; - return Assertion.prototype.toHaveMaxMP = function(mp) { - var actual; + Assertion.prototype.toHaveMaxMP = function (mp) { + let actual; + actual = this.obj._statsComputed.maxMP; - return this.assert(actual === mp, function() { - return "expected user to have " + mp + " max mp, but got " + actual; - }, function() { - return "expected user to not have " + mp + " max mp"; + return this.assert(actual === mp, () => { + return 'expected user to have ' + mp + ' max mp, but got ' + actual; + }, () => { + return 'expected user to not have ' + mp + ' max mp'; }); }; }; diff --git a/test/common/user.fns.buy.test.js b/test/common/user.fns.buy.test.js index f348a61cdb..94ace92a0a 100644 --- a/test/common/user.fns.buy.test.js +++ b/test/common/user.fns.buy.test.js @@ -1,30 +1,28 @@ -var sinon = require('sinon'); -var chai = require("chai") -chai.use(require("sinon-chai")) -var expect = chai.expect -var _ = require('lodash'); +/* eslint-disable camelcase */ -var shared = require('../../common/script/index.js'); +import sinon from 'sinon'; // eslint-disable-line no-shadow -describe('user.fns.buy', function() { - var user; +let shared = require('../../common/script/index.js'); - beforeEach(function() { +describe('user.fns.buy', () => { + let user; + + beforeEach(() => { user = { items: { gear: { owned: { - weapon_warrior_0: true + weapon_warrior_0: true, }, equipped: { - weapon_warrior_0: true - } - } + weapon_warrior_0: true, + }, + }, }, preferences: {}, stats: { gp: 200 }, achievements: { }, - flags: { } + flags: { }, }; shared.wrap(user); @@ -33,32 +31,32 @@ describe('user.fns.buy', function() { sinon.stub(user.fns, 'predictableRandom'); }); - afterEach(function() { + afterEach(() => { user.fns.randomVal.restore(); user.fns.predictableRandom.restore(); }); - context('Potion', function() { - it('recovers 15 hp', function() { + context('Potion', () => { + it('recovers 15 hp', () => { user.stats.hp = 30; user.ops.buy({params: {key: 'potion'}}); expect(user.stats.hp).to.eql(45); }); - it('does not increase hp above 50', function() { + it('does not increase hp above 50', () => { user.stats.hp = 45; user.ops.buy({params: {key: 'potion'}}); expect(user.stats.hp).to.eql(50); }); - it('deducts 25 gp', function() { + it('deducts 25 gp', () => { user.stats.hp = 45; user.ops.buy({params: {key: 'potion'}}); expect(user.stats.gp).to.eql(175); }); - it('does not purchase if not enough gp', function() { + it('does not purchase if not enough gp', () => { user.stats.hp = 45; user.stats.gp = 5; user.ops.buy({params: {key: 'potion'}}); @@ -68,8 +66,8 @@ describe('user.fns.buy', function() { }); }); - context('Gear', function() { - it('adds equipment to inventory', function() { + context('Gear', () => { + it('adds equipment to inventory', () => { user.stats.gp = 31; user.ops.buy({params: {key: 'armor_warrior_1'}}); @@ -77,7 +75,7 @@ describe('user.fns.buy', function() { expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true }); }); - it('deducts gold from user', function() { + it('deducts gold from user', () => { user.stats.gp = 31; user.ops.buy({params: {key: 'armor_warrior_1'}}); @@ -85,7 +83,7 @@ describe('user.fns.buy', function() { expect(user.stats.gp).to.eql(1); }); - it('auto equips equipment if user has auto-equip preference turned on', function() { + it('auto equips equipment if user has auto-equip preference turned on', () => { user.stats.gp = 31; user.preferences.autoEquip = true; @@ -94,7 +92,7 @@ describe('user.fns.buy', function() { expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1'); }); - it('buys equipment but does not auto-equip', function() { + it('buys equipment but does not auto-equip', () => { user.stats.gp = 31; user.preferences.autoEquip = false; @@ -103,7 +101,7 @@ describe('user.fns.buy', function() { expect(user.items.gear.equipped).to.not.have.property('armor'); }); - it('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', function() { + it('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => { user.stats.gp = 100; user.preferences.autoEquip = true; user.ops.buy({params: {key: 'shield_warrior_1'}}); @@ -117,7 +115,7 @@ describe('user.fns.buy', function() { expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1'); }); - it('buys two-handed equipment but does not automatically remove sword or shield', function() { + it('buys two-handed equipment but does not automatically remove sword or shield', () => { user.stats.gp = 100; user.preferences.autoEquip = false; user.ops.buy({params: {key: 'shield_warrior_1'}}); @@ -131,7 +129,7 @@ describe('user.fns.buy', function() { expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1'); }); - it('does not buy equipment without enough Gold', function() { + it('does not buy equipment without enough Gold', () => { user.stats.gp = 20; user.ops.buy({params: {key: 'armor_warrior_1'}}); @@ -140,7 +138,7 @@ describe('user.fns.buy', function() { }); }); - context('Quests', function() { + context('Quests', () => { it('buys a Quest scroll'); it('does not buy Quests without enough Gold'); @@ -150,48 +148,49 @@ describe('user.fns.buy', function() { it('does not buy Gem-premium Quests'); }); - context('Enchanted Armoire', function() { - var YIELD_EQUIPMENT = .5; - var YIELD_FOOD = .7; - var YIELD_EXP = .9; + context('Enchanted Armoire', () => { + let YIELD_EQUIPMENT = 0.5; + let YIELD_FOOD = 0.7; + let YIELD_EXP = 0.9; - var fullArmoire = {} + let fullArmoire = {}; - _(shared.content.gearTypes).each(function(type) { - _(shared.content.gear.tree[type].armoire).each(function(gearObject, gearName) { + _(shared.content.gearTypes).each((type) => { + _(shared.content.gear.tree[type].armoire).each((gearObject) => { let armoireKey = gearObject.key; + fullArmoire[armoireKey] = true; }); }); - beforeEach(function() { + beforeEach(() => { user.achievements.ultimateGearSets = { rogue: true }; user.flags.armoireOpened = true; user.stats.exp = 0; user.items.food = {}; }); - context('failure conditions', function() { - it('does not open if user does not have enough gold', function(done) { + context('failure conditions', () => { + it('does not open if user does not have enough gold', (done) => { user.fns.predictableRandom.returns(YIELD_EQUIPMENT); user.stats.gp = 50; - user.ops.buy({params: {key: 'armoire'}}, function(response) { + user.ops.buy({params: {key: 'armoire'}}, (response) => { expect(response.message).to.eql('Not Enough Gold'); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); expect(user.items.food).to.be.empty; expect(user.stats.exp).to.eql(0); done(); }); }); - it('does not open without Ultimate Gear achievement',function(done) { + it('does not open without Ultimate Gear achievement', (done) => { user.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.achievements.ultimateGearSets = {'healer':false,'wizard':false,'rogue':false,'warrior':false}; + user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false}; - user.ops.buy({params: {key: 'armoire'}}, function(response) { - expect(response.message).to.eql("You can't buy this item"); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); + user.ops.buy({params: {key: 'armoire'}}, (response) => { + expect(response.message).to.eql('You can\'t buy this item'); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); expect(user.items.food).to.be.empty; expect(user.stats.exp).to.eql(0); done(); @@ -199,32 +198,33 @@ describe('user.fns.buy', function() { }); }); - context('non-gear awards', function() { - it('gives Experience', function() { + context('non-gear awards', () => { + it('gives Experience', () => { user.fns.predictableRandom.returns(YIELD_EXP); - user.ops.buy({params: {key: 'armoire'}}) + user.ops.buy({params: {key: 'armoire'}}); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); expect(user.items.food).to.be.empty; expect(user.stats.exp).to.eql(46); expect(user.stats.gp).to.eql(100); }); - it('gives food', function() { - var honey = shared.content.food.Honey; + it('gives food', () => { + let honey = shared.content.food.Honey; + user.fns.randomVal.returns(honey); user.fns.predictableRandom.returns(YIELD_FOOD); - user.ops.buy({params: {key: 'armoire'}}) + user.ops.buy({params: {key: 'armoire'}}); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); - expect(user.items.food).to.eql({'Honey': 1}); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.eql({Honey: 1}); expect(user.stats.exp).to.eql(0); expect(user.stats.gp).to.eql(100); }); - it('does not give equipment if all equipment has been found', function() { + it('does not give equipment if all equipment has been found', () => { user.fns.predictableRandom.returns(YIELD_EQUIPMENT); user.items.gear.owned = fullArmoire; user.stats.gp = 150; @@ -232,7 +232,8 @@ describe('user.fns.buy', function() { user.ops.buy({params: {key: 'armoire'}}); expect(user.items.gear.owned).to.eql(fullArmoire); - var armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + expect(armoireCount).to.eql(0); expect(user.stats.exp).to.eql(30); @@ -240,43 +241,46 @@ describe('user.fns.buy', function() { }); }); - context('gear awards', function() { - beforeEach(function() { - var shield = shared.content.gear.tree.shield.armoire.gladiatorShield; + context('gear awards', () => { + beforeEach(() => { + let shield = shared.content.gear.tree.shield.armoire.gladiatorShield; + user.fns.randomVal.returns(shield); }); - it('always drops equipment the first time', function() { + it('always drops equipment the first time', () => { delete user.flags.armoireOpened; user.fns.predictableRandom.returns(YIELD_EXP); user.ops.buy({params: {key: 'armoire'}}); expect(user.items.gear.owned).to.eql({ - 'weapon_warrior_0': true, - 'shield_armoire_gladiatorShield': true + weapon_warrior_0: true, + shield_armoire_gladiatorShield: true, }); - var armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); - expect(armoireCount).to.eql (_.size(fullArmoire) - 1) + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 1); expect(user.items.food).to.be.empty; expect(user.stats.exp).to.eql(0); expect(user.stats.gp).to.eql(100); }); - it('gives more equipment', function() { + it('gives more equipment', () => { user.fns.predictableRandom.returns(YIELD_EQUIPMENT); user.items.gear.owned = { weapon_warrior_0: true, - head_armoire_hornedIronHelm: true + head_armoire_hornedIronHelm: true, }; user.stats.gp = 200; user.ops.buy({params: {key: 'armoire'}}); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true, 'shield_armoire_gladiatorShield':true, 'head_armoire_hornedIronHelm':true}); - var armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); - expect(armoireCount).to.eql((_.size(fullArmoire) - 2)); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true}); + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 2); expect(user.stats.gp).to.eql(100); }); }); diff --git a/test/common/user.fns.ultimateGear.test.js b/test/common/user.fns.ultimateGear.test.js index 9dc80eaea5..a95cb403d9 100644 --- a/test/common/user.fns.ultimateGear.test.js +++ b/test/common/user.fns.ultimateGear.test.js @@ -1,25 +1,36 @@ -var shared = require('../../common/script/index.js'); -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations +/* eslint-disable camelcase */ + +let shared = require('../../common/script/index.js'); + +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; require('./test_helper'); -describe('User.fns.ultimateGear', function() { - - it('sets armoirEnabled when partial achievement already achieved', function() { - var user = shared.wrap({ - items: { gear: { owned: { - toObject: function() { return { - armor_warrior_5: true, - shield_warrior_5: true, - head_warrior_5: true, - weapon_warrior_6: true - }} - }}}, - achievements: { - ultimateGearSets: {} +describe('User.fns.ultimateGear', () => { + it('sets armoirEnabled when partial achievement already achieved', () => { + let items = { + gear: { + owned: { + toObject: () => { + return { + armor_warrior_5: true, + shield_warrior_5: true, + head_warrior_5: true, + weapon_warrior_6: true, + }; + }, + }, }, - flags: {} + }; + + let user = shared.wrap({ + items, + achievements: { + ultimateGearSets: {}, + }, + flags: {}, }); + user.fns.ultimateGear(); expect(user.flags.armoireEnabled).to.equal(true); }); diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js index 1f88b45802..8c7899cf50 100644 --- a/test/common/user.ops.buyMysterySet.test.js +++ b/test/common/user.ops.buyMysterySet.test.js @@ -1,79 +1,73 @@ -var sinon = require('sinon'); -var chai = require("chai") -chai.use(require("sinon-chai")) -var expect = chai.expect +/* eslint-disable camelcase */ -var shared = require('../../common/script/index.js'); -var Content = require('../../common/script/content/index.js'); +let shared = require('../../common/script/index.js'); -describe('user.ops.buyMysterySet', function() { - var user; +describe('user.ops.buyMysterySet', () => { + let user; - beforeEach(function() { + beforeEach(() => { user = { items: { gear: { owned: { - weapon_warrior_0: true - } - } + weapon_warrior_0: true, + }, + }, }, purchased: { plan: { consecutive: { - trinkets: 0 - } - } - } + trinkets: 0, + }, + }, + }, }; shared.wrap(user); }); - context('Mystery Sets', function() { - - context('failure conditions', function() { - - it('does not grant mystery sets without Mystic Hourglasses', function(done) { - user.ops.buyMysterySet({params:{key:'201501'}}, function(response) { - expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); - expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); + context('Mystery Sets', () => { + context('failure conditions', () => { + it('does not grant mystery sets without Mystic Hourglasses', (done) => { + user.ops.buyMysterySet({params: {key: '201501'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); done(); }); }); - it('does not grant mystery set that has already been purchased', function(done) { + it('does not grant mystery set that has already been purchased', (done) => { user.purchased.plan.consecutive.trinkets = 1; user.items.gear.owned = { weapon_warrior_0: true, weapon_mystery_301404: true, armor_mystery_301404: true, head_mystery_301404: true, - eyewear_mystery_301404: true + eyewear_mystery_301404: true, }; - user.ops.buyMysterySet({params:{key:'301404'}}, function(response) { - expect(response.message).to.eql("Mystery set not found, or set already owned"); + user.ops.buyMysterySet({params: {key: '301404'}}, (response) => { + expect(response.message).to.eql('Mystery set not found, or set already owned'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); }); }); }); - context('successful purchases', function() { - - it('buys Steampunk Accessories Set', function(done) { + context('successful purchases', () => { + it('buys Steampunk Accessories Set', (done) => { user.purchased.plan.consecutive.trinkets = 1; - user.ops.buyMysterySet({params:{key:'301404'}}, function() { + user.ops.buyMysterySet({params: {key: '301404'}}, () => { expect(user.purchased.plan.consecutive.trinkets).to.eql(0); expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, weapon_mystery_301404: true, armor_mystery_301404: true, head_mystery_301404: true, - eyewear_mystery_301404: true + eyewear_mystery_301404: true, }); + done(); }); }); diff --git a/test/common/user.ops.hatch.js b/test/common/user.ops.hatch.js index b5cf174ff9..573103a360 100644 --- a/test/common/user.ops.hatch.js +++ b/test/common/user.ops.hatch.js @@ -1,135 +1,126 @@ -var sinon = require('sinon'); -var chai = require('chai'); -chai.use(require('sinon-chai')) -var expect = chai.expect +let shared = require('../../common/script/index.js'); -var shared = require('../../common/script/index.js'); -var content = require('../../common/script/content/index.js'); +describe('user.ops.hatch', () => { + let user; -describe('user.ops.hatch', function() { - var user; - - beforeEach(function() { + beforeEach(() => { user = { items: { eggs: {}, hatchingPotions: {}, - pets: {} - } + pets: {}, + }, }; shared.wrap(user); }); - context('Pet Hatching', function() { - - context('failure conditions', function() { - - it('does not allow hatching without specifying egg and potion', function(done) { - user.ops.hatch({params:{}},function(response) { + context('Pet Hatching', () => { + context('failure conditions', () => { + it('does not allow hatching without specifying egg and potion', (done) => { + user.ops.hatch({params: {}}, (response) => { expect(response.message).to.eql('Please specify query.egg & query.hatchingPotion'); expect(user.items.pets).to.be.empty; done(); }); }); - it('does not allow hatching if user lacks specified egg', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Base':1}; - user.ops.hatch({params:{egg:'Dragon',hatchingPotion:'Base'}}, function(response) { + it('does not allow hatching if user lacks specified egg', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Dragon', hatchingPotion: 'Base'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({'Wolf':1}); - expect(user.items.hatchingPotions).to.eql({'Base':1}); + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); done(); }); }); - it('does not allow hatching if user lacks specified hatching potion', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Base':1}; - user.ops.hatch({params:{egg:'Wolf',hatchingPotion:'Golden'}}, function(response) { + it('does not allow hatching if user lacks specified hatching potion', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Golden'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({'Wolf':1}); - expect(user.items.hatchingPotions).to.eql({'Base':1}); + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); done(); }); }); - it('does not allow hatching if user already owns target pet', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Base':1}; - user.items.pets = {'Wolf-Base':10}; - user.ops.hatch({params:{egg:'Wolf',hatchingPotion:'Base'}}, function(response) { + it('does not allow hatching if user already owns target pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': 10}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageAlreadyPet')); - expect(user.items.pets).to.eql({'Wolf-Base':10}); - expect(user.items.eggs).to.eql({'Wolf':1}); - expect(user.items.hatchingPotions).to.eql({'Base':1}); + expect(user.items.pets).to.eql({'Wolf-Base': 10}); + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); done(); }); }); - it('does not allow hatching quest pet egg using premium potion', function(done) { - user.items.eggs = {'Cheetah':1}; - user.items.hatchingPotions = {'Spooky':1}; - user.ops.hatch({params:{egg:'Cheetah',hatchingPotion:'Spooky'}}, function(response) { + it('does not allow hatching quest pet egg using premium potion', (done) => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Spooky'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageInvalidEggPotionCombo')); expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({'Cheetah':1}); - expect(user.items.hatchingPotions).to.eql({'Spooky':1}); + expect(user.items.eggs).to.eql({Cheetah: 1}); + expect(user.items.hatchingPotions).to.eql({Spooky: 1}); done(); }); }); }); - context('successful hatching', function() { - - it('hatches a basic pet', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Base':1}; - user.ops.hatch({params:{egg:'Wolf',hatchingPotion:'Base'}}, function(response) { + context('successful hatching', () => { + it('hatches a basic pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Base':5}); - expect(user.items.eggs).to.eql({'Wolf':0}); - expect(user.items.hatchingPotions).to.eql({'Base':0}); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); done(); }); }); - it('hatches a quest pet', function(done) { - user.items.eggs = {'Cheetah':1}; - user.items.hatchingPotions = {'Base':1}; - user.ops.hatch({params:{egg:'Cheetah',hatchingPotion:'Base'}}, function(response) { + it('hatches a quest pet', (done) => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Base'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Cheetah-Base':5}); - expect(user.items.eggs).to.eql({'Cheetah':0}); - expect(user.items.hatchingPotions).to.eql({'Base':0}); + expect(user.items.pets).to.eql({'Cheetah-Base': 5}); + expect(user.items.eggs).to.eql({Cheetah: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); done(); }); }); - it('hatches a premium pet', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Spooky':1}; - user.ops.hatch({params:{egg:'Wolf',hatchingPotion:'Spooky'}}, function(response) { + it('hatches a premium pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Spooky'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Spooky':5}); - expect(user.items.eggs).to.eql({'Wolf':0}); - expect(user.items.hatchingPotions).to.eql({'Spooky':0}); + expect(user.items.pets).to.eql({'Wolf-Spooky': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Spooky: 0}); done(); }); }); - it('hatches a pet previously raised to a mount', function(done) { - user.items.eggs = {'Wolf':1}; - user.items.hatchingPotions = {'Base':1}; - user.items.pets = {'Wolf-Base':-1}; - user.ops.hatch({params:{egg:'Wolf',hatchingPotion:'Base'}}, function(response) { + it('hatches a pet previously raised to a mount', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': -1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Base':5}); - expect(user.items.eggs).to.eql({'Wolf':0}); - expect(user.items.hatchingPotions).to.eql({'Base':0}); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); done(); }); }); diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js index 76e3f98c9c..b9c7369d21 100644 --- a/test/common/user.ops.hourglassPurchase.test.js +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -1,101 +1,93 @@ -var sinon = require('sinon'); -var chai = require("chai") -chai.use(require("sinon-chai")) -var expect = chai.expect +let shared = require('../../common/script/index.js'); -var shared = require('../../common/script/index.js'); -var Content = require('../../common/script/content/index.js'); +describe('user.ops.hourglassPurchase', () => { + let user; -describe('user.ops.hourglassPurchase', function() { - var user; - - beforeEach(function() { + beforeEach(() => { user = { items: { pets: {}, mounts: {}, - hatchingPotions: {} + hatchingPotions: {}, }, purchased: { plan: { consecutive: { - trinkets: 0 - } - } - } + trinkets: 0, + }, + }, + }, }; shared.wrap(user); }); - context('Time Travel Stable', function() { - - context('failure conditions', function() { - - it('does not allow purchase of unsupported item types', function(done) { - user.ops.hourglassPurchase({params:{type: 'hatchingPotions', key: 'Base'}}, function(response) { + context('Time Travel Stable', () => { + context('failure conditions', () => { + it('does not allow purchase of unsupported item types', (done) => { + user.ops.hourglassPurchase({params: {type: 'hatchingPotions', key: 'Base'}}, (response) => { expect(response.message).to.eql('Item type not supported for purchase with Mystic Hourglass. Allowed types: ["pets","mounts"]'); expect(user.items.hatchingPotions).to.eql({}); done(); }); }); - it('does not grant pets without Mystic Hourglasses', function(done) { - user.ops.hourglassPurchase({params:{type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { - expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); + it('does not grant pets without Mystic Hourglasses', (done) => { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); expect(user.items.pets).to.eql({}); done(); }); }); - it('does not grant mounts without Mystic Hourglasses', function(done) { - user.ops.hourglassPurchase({params:{type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { - expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); + it('does not grant mounts without Mystic Hourglasses', (done) => { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); expect(user.items.mounts).to.eql({}); done(); }); }); - it('does not grant pet that has already been purchased', function(done) { + it('does not grant pet that has already been purchased', (done) => { user.purchased.plan.consecutive.trinkets = 1; user.items.pets = { - 'MantisShrimp-Base': true + 'MantisShrimp-Base': true, }; - user.ops.hourglassPurchase({params:{type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { - expect(response.message).to.eql("Pet already owned."); + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Pet already owned.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); }); }); - it('does not grant mount that has already been purchased', function(done) { + it('does not grant mount that has already been purchased', (done) => { user.purchased.plan.consecutive.trinkets = 1; user.items.mounts = { - 'MantisShrimp-Base': true + 'MantisShrimp-Base': true, }; - user.ops.hourglassPurchase({params:{type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { - expect(response.message).to.eql("Mount already owned."); + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Mount already owned.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); }); }); - it('does not grant pet that is not part of the Time Travel Stable', function(done) { + it('does not grant pet that is not part of the Time Travel Stable', (done) => { user.purchased.plan.consecutive.trinkets = 1; - user.ops.hourglassPurchase({params: {type: 'pets', key: 'Wolf-Veteran'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'Wolf-Veteran'}}, (response) => { expect(response.message).to.eql('Pet not available for purchase with Mystic Hourglass.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); }); }); - it('does not grant mount that is not part of the Time Travel Stable', function(done) { + it('does not grant mount that is not part of the Time Travel Stable', (done) => { user.purchased.plan.consecutive.trinkets = 1; - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'Orca-Base'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'Orca-Base'}}, (response) => { expect(response.message).to.eql('Mount not available for purchase with Mystic Hourglass.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); @@ -103,26 +95,25 @@ describe('user.ops.hourglassPurchase', function() { }); }); - context('successful purchases', function() { - - it('buys a pet', function(done) { + context('successful purchases', () => { + it('buys a pet', (done) => { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.pets).to.eql({'MantisShrimp-Base':5}); + expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); done(); }); }); - it('buys a mount', function(done) { + it('buys a mount', (done) => { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.mounts).to.eql({'MantisShrimp-Base':true}); + expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); done(); }); }); diff --git a/test/common/user.ops.test.js b/test/common/user.ops.test.js index aee5b795f7..f4e4bb82c9 100644 --- a/test/common/user.ops.test.js +++ b/test/common/user.ops.test.js @@ -1,36 +1,30 @@ -var sinon = require('sinon'); -var chai = require("chai") -chai.use(require("sinon-chai")) -var expect = chai.expect -var _ = require('lodash'); +let shared = require('../../common/script/index.js'); -var shared = require('../../common/script/index.js'); +describe('user.ops', () => { + let user; -describe('user.ops', function() { - var user; - - beforeEach(function() { + beforeEach(() => { user = { items: { gear: { }, - special: { } + special: { }, }, achievements: { }, - flags: { } + flags: { }, }; shared.wrap(user); }); - describe('readCard', function() { - it('removes card from invitation array', function() { + describe('readCard', () => { + it('removes card from invitation array', () => { user.items.special.valentineReceived = ['Leslie']; user.ops.readCard({ params: { cardType: 'valentine' } }); expect(user.items.special.valentineReceived).to.be.empty; }); - it('removes the first card from invitation array', function() { + it('removes the first card from invitation array', () => { user.items.special.valentineReceived = ['Leslie', 'Vicky']; user.ops.readCard({ params: { cardType: 'valentine' } }); diff --git a/test/helpers/api-integration.helper.js b/test/helpers/api-integration.helper.js index b6b3464c9b..c8a452f7c3 100644 --- a/test/helpers/api-integration.helper.js +++ b/test/helpers/api-integration.helper.js @@ -1,11 +1,13 @@ +/* eslint-disable no-use-before-define */ + import { assign, each, isEmpty, times, } from 'lodash'; -import {MongoClient as mongo} from 'mongodb'; -import {v4 as generateUUID} from 'uuid'; +import { MongoClient as mongo } from 'mongodb'; +import { v4 as generateUUID } from 'uuid'; import superagent from 'superagent'; import i18n from '../../common/script/src/i18n'; i18n.translations = require('../../website/src/libs/i18n.js').translations; @@ -15,19 +17,19 @@ const API_TEST_SERVER_PORT = 3003; // Sets up an abject that can make all REST requests // If a user is passed in, the uuid and api token of // the user are used to make the requests -export function requester(user={}, additionalSets) { +export function requester (user = {}, additionalSets) { return { get: _requestMaker(user, 'get', additionalSets), post: _requestMaker(user, 'post', additionalSets), put: _requestMaker(user, 'put', additionalSets), del: _requestMaker(user, 'del', additionalSets), - } -}; + }; +} // Use this to verify error messages returned by the server // That way, if the translated string changes, the test // will not break. NOTE: it checks agains errors with string as well. -export function translate(key, variables) { +export function translate (key, variables) { const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.'; const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; @@ -38,18 +40,22 @@ export function translate(key, variables) { expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG); return translatedString; -}; +} // Useful for checking things that have been deleted, // but you no longer have access to, // like private parties or users -export function checkExistence(collectionName, id) { +export function checkExistence (collectionName, id) { return new Promise((resolve, reject) => { - mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => { - if (err) return reject(err); + mongo.connect('mongodb://localhost/habitrpg_test', (connectionError, db) => { + if (connectionError) return reject(connectionError); let collection = db.collection(collectionName); - collection.find({_id: id}, {_id: 1}).limit(1).toArray((err, docs) => { + + collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { + if (findError) return reject(findError); + let exists = docs.length > 0; + db.close(); resolve(exists); }); @@ -64,41 +70,41 @@ export function checkExistence(collectionName, id) { // paramter, such as the number of wolf eggs the user has, // , you can do so by passing in the full path as a string: // { 'items.eggs.Wolf': 10 } -export function generateUser(update={}) { +export function generateUser (update = {}) { let username = generateUUID(); - let password = 'password' - let email = username + '@example.com'; + let password = 'password'; + let email = `${username}@example.com`; let request = _requestMaker({}, 'post'); return new Promise((resolve, reject) => { request('/register', { - username: username, - email: email, - password: password, + username, + email, + password, confirmPassword: password, }).then((user) => { _updateDocument('users', user, update, () => { resolve(user); }); - }); + }).catch(reject); }); -}; +} // Generates a new group. Requires a user object, which // will will become the groups leader. Takes an update // argument which will update group -export function generateGroup(leader, update={}) { +export function generateGroup (leader, update = {}) { let request = _requestMaker(leader, 'post'); return new Promise((resolve, reject) => { request('/groups').then((group) => { _updateDocument('groups', group, update, () => { resolve(group); - }); + }).catch(reject); }); }); -}; +} // This is generate group + the ability to create // real users to populate it. The settings object @@ -114,8 +120,12 @@ export function generateGroup(leader, update={}) { // invitees: an array of user objects that correspond to the invitees of the group // leader: the leader user object // group: the group object -export function createAndPopulateGroup(settings={}) { - let request, leader, members, invitees, group; +export function createAndPopulateGroup (settings = {}) { + let request; + let leader; + let members; + let invitees; + let group; let numberOfMembers = settings.members || 0; let numberOfInvites = settings.invites || 0; @@ -156,37 +166,39 @@ export function createAndPopulateGroup(settings={}) { }).then((users) => { invitees = users; - let invitePromises = []; + let invitationPromises = []; each(invitees, (invitee) => { let invitePromise = request(`/groups/${group._id}/invite`, { - uuids: [invitee._id] + uuids: [invitee._id], }); - invitePromises.push(invitePromise); + + invitationPromises.push(invitePromise); }); - return Promise.all(invitePromises); - }).then((inviteResults) => { + return Promise.all(invitationPromises); + }).then(() => { resolve({ - leader: leader, - group: group, - members: members, - invitees: invitees, + leader, + group, + members, + invitees, }); }).catch(reject); }); -}; +} // Specifically helpful for the GET /groups tests, // resets the db to an empty state and creates a tavern document -export function resetHabiticaDB() { +export function resetHabiticaDB () { return new Promise((resolve, reject) => { mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => { if (err) return reject(err); - db.dropDatabase((err) => { - if (err) return reject(err); + db.dropDatabase((dbErr) => { + if (dbErr) return reject(dbErr); let groups = db.collection('groups'); + groups.insertOne({ _id: 'habitrpg', chat: [], @@ -195,8 +207,8 @@ export function resetHabiticaDB() { type: 'guild', privacy: 'public', members: [], - }, (err) => { - if (err) return reject(err); + }, (insertErr) => { + if (insertErr) return reject(insertErr); db.close(); resolve(); @@ -206,7 +218,7 @@ export function resetHabiticaDB() { }); } -function _requestMaker(user, method, additionalSets) { +function _requestMaker (user, method, additionalSets) { return (route, send, query) => { return new Promise((resolve, reject) => { let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/v2${route}`) @@ -228,29 +240,31 @@ function _requestMaker(user, method, additionalSets) { .end((err, response) => { if (err) { if (!err.response) return reject(err); - let errorString = JSON.parse(err.response.text).err; + return reject({ - code: err.response.statusCode, - text: errorString, + code: err.response.status, + text: err.response.body.err, }); } resolve(response.body); }); }); - } + }; } -function _updateDocument(collectionName, doc, update, cb) { - if (isEmpty(update)) { return cb(); } +function _updateDocument (collectionName, doc, update, cb) { + if (isEmpty(update)) { + return cb(); + } - mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => { - if (err) throw `Error connecting to database when updating ${collectionName} collection: ${err}`; + mongo.connect('mongodb://localhost/habitrpg_test', (connectErr, db) => { + if (connectErr) throw new Error(`Error connecting to database when updating ${collectionName} collection: ${connectErr}`); let collection = db.collection(collectionName); - collection.update({ _id: doc._id }, { $set: update }, (err, result) => { - if (err) throw `Error updating ${collectionName}: ${err}`; + collection.update({ _id: doc._id }, { $set: update }, (updateErr) => { + if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`); assign(doc, update); db.close(); cb(); diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js index db95a1f0b3..92acd80310 100644 --- a/test/helpers/content.helper.js +++ b/test/helpers/content.helper.js @@ -15,7 +15,7 @@ export function expectValidTranslationString (attribute) { expect(translatedString).to.not.be.empty; expect(translatedString).to.not.eql(STRING_ERROR_MSG); expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG); -}; +} export function describeEachItem (testDescription, set, cb, describeFunction) { // describeFunction allows you to pass in 'only' or 'skip' @@ -34,8 +34,8 @@ export function describeEachItem (testDescription, set, cb, describeFunction) { describeEachItem.only = (des, set, cb) => { describeEachItem(des, set, cb, 'only'); -} +}; describeEachItem.skip = (des, set, cb) => { describeEachItem(des, set, cb, 'skip'); -} +}; diff --git a/test/helpers/globals.helper.js b/test/helpers/globals.helper.js index a731f1e8c2..21f60b7f46 100644 --- a/test/helpers/globals.helper.js +++ b/test/helpers/globals.helper.js @@ -1,9 +1,10 @@ +/* eslint-disable no-undef */ //------------------------------ // Global modules //------------------------------ -global._ = require("lodash") -global.chai = require("chai") -chai.use(require("sinon-chai")) -chai.use(require("chai-as-promised")); -global.expect = chai.expect +global._ = require('lodash'); +global.chai = require('chai'); +chai.use(require('sinon-chai')); +chai.use(require('chai-as-promised')); +global.expect = chai.expect; diff --git a/test/server_side/controllers/groups.test.js b/test/server_side/controllers/groups.test.js index 9e970c4afc..6a75c75c78 100644 --- a/test/server_side/controllers/groups.test.js +++ b/test/server_side/controllers/groups.test.js @@ -194,7 +194,7 @@ describe('Groups Controller', function() { expect(group.leave).to.not.be.called; expect(res.json).to.be.calledOnce; - expect(res.json).to.be.calledWith(403, 'You cannot leave party during an active quest. Please leave the quest first'); + expect(res.json).to.be.calledWith(403, 'You cannot leave party during an active quest. Please leave the quest first.'); }); it('prevents quest leader from leaving a party if they have started a quest', function() { diff --git a/website/public/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js index ed9ac730f1..e785c7c02e 100644 --- a/website/public/js/controllers/settingsCtrl.js +++ b/website/public/js/controllers/settingsCtrl.js @@ -204,12 +204,13 @@ habitrpg.controller('SettingsCtrl', $scope.gemGoldCap = function(subscription) { var baseCap = 25; + var gemCapIncrement = 5; + var capIncrementThreshold = 3; var gemCapExtra = User.user.purchased.plan.consecutive.gemCapExtra; - // @TODO: What are these magic numbers? 3? 5? - var blocks = Content.subscriptionBlocks[subscription.key].months / 3 * 5; + var blocks = Content.subscriptionBlocks[subscription.key].months / capIncrementThreshold; var flooredBlocks = Math.floor(blocks); - var userTotalDropCap = baseCap + gemCapExtra + flooredBlocks; + var userTotalDropCap = baseCap + gemCapExtra + flooredBlocks * gemCapIncrement; var maxDropCap = 50; return [userTotalDropCap, maxDropCap]; diff --git a/website/public/js/filters/timezoneOffsetToUtc.js b/website/public/js/filters/timezoneOffsetToUtc.js new file mode 100644 index 0000000000..1999efd0c3 --- /dev/null +++ b/website/public/js/filters/timezoneOffsetToUtc.js @@ -0,0 +1,15 @@ +angular.module('habitrpg') + .filter('timezoneOffsetToUtc', function () { + return function (offset) { + var sign = offset > 0 ? '-' : '+'; + + offset = Math.abs(offset) / 60; + + var hour = Math.floor(offset); + + var minutes_int = (offset - hour) * 60; + var minutes = minutes_int < 10 ? '0'+minutes_int : minutes_int; + + return 'UTC' + sign + hour + ':' + minutes; + } + }); diff --git a/website/public/manifest.json b/website/public/manifest.json index 5a96670531..accb695df3 100644 --- a/website/public/manifest.json +++ b/website/public/manifest.json @@ -58,6 +58,7 @@ "js/filters/money.js", "js/filters/roundLargeNumbers.js", "js/filters/taskOrdering.js", + "js/filters/timezoneOffsetToUtc.js", "js/directives/close-menu.directive.js", "js/directives/expand-menu.directive.js", diff --git a/website/src/controllers/api-v2/groups.js b/website/src/controllers/api-v2/groups.js index 0fc969c3af..cd01d1b27e 100644 --- a/website/src/controllers/api-v2/groups.js +++ b/website/src/controllers/api-v2/groups.js @@ -524,7 +524,7 @@ api.leave = function(req, res, next) { } if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { - return res.json(403, 'You cannot leave party during an active quest. Please leave the quest first'); + return res.json(403, 'You cannot leave party during an active quest. Please leave the quest first.'); } } diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade index 9a0808b2eb..2f27a2b719 100644 --- a/website/views/options/settings.jade +++ b/website/views/options/settings.jade @@ -116,6 +116,16 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') ng-disabled='dayStart == user.preferences.dayStart') =env.t('saveCustomDayStart') + hr + + h5=env.t('timezone') + .form-horizontal + .form-group + .col-sm-12 + p!=env.t('timezoneUTC', {utc: "{{ user.preferences.timezoneOffset | timezoneOffsetToUtc }}"}) + br + p!=env.t('timezoneInfo') + .personal-options.col-md-6 .panel.panel-default .panel-heading @@ -269,7 +279,7 @@ mixin subPerks() tr td span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}  - span.badge.badge-success(ng-show='_subscription.key!="basic_earned"')=env.t('buyGemsGoldCap', {amount: '{{:: gemGoldCap(_subscription) | min }}'}) + span.badge.badge-success(ng-show='_subscription.key!="basic_earned"')=env.t('buyGemsGoldCap', {amount: '{{gemGoldCap(_subscription) | min }}'}) tr td span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory') @@ -280,7 +290,7 @@ mixin subPerks() td span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}  div(ng-show='_subscription.key!="basic_earned"') - .badge.badge-success=env.t('mysticHourglass', {amount: '+{{:: numberOfMysticHourglasses(_subscription)}}'}) + .badge.badge-success=env.t('mysticHourglass', {amount: '+{{numberOfMysticHourglasses(_subscription)}}'}) .small.muted=env.t('mysticHourglassText') tr td diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index a07d982eca..53cad67402 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,26 +1,42 @@ -h2 11/16/2015 - HABITICA STICKERS AND COSTUME CONTEST BADGES! +h2 11/19/2015 - SMALL iOS UPDATE AND HABITICA HIRING NEWS! hr tr td - .achievement-costumeContest2x.pull-right - h3 Costume Contest Badges - p We've awarded all the costume contest badges! Over the coming months, we'll be posting the costumes on our blog, so be sure to follow along. + h3 Habitica Hiring News + p Exciting news! Right now, Habitica is looking to add a senior full stack developer to our team, and what better place to look than our awesome community? br - p Important: if you submitted a photo but did NOT receive your badge, it probably means that we were unable to view your entry due to privacy restrictions or other issues. Email your photo to leslie@habitica.com and she will make sure that you get your badge! + p If you’d like to apply, you should have experience as a lead developer, and be a JavaScript whiz who is familiar with MongoDB and Angular. Bonus points for familiarity with our tech stack! Passion for open source is, naturally, a must ;) br - p Thanks again to all the participants. We were very impressed by your creativity! + p Send your resume to jobs@habitica.com with your GitHub handle, Habitica username, and list of favorite online hangouts! Please also let us know whether or not you would be able to move to Los Angeles. We’re looking forward to hearing from you! tr td - .promo_habitica_sticker.pull-right - h3 Habitica Stickers - p In addition to our Habitica T-shirts, we are now also selling Habitica Stickers! Display Melior anywhere for extra motivation. - p.small.muted by Redphoenix and Sara Olson + h3 Small iOS App Update + p We've released a small iOS update, just to fix some bothersome bugs (including crashes), add a nice intro slide for new users, and make it more obvious how to invite your friends to your party. + br + p If you already reviewed the last version of the app, Apple has hidden it for this version, but you can automatically post the same review again by tapping “Write a review” and then just hitting "Send." Thank you very much for taking the time to share your thoughts with us! Posting and reposting reviews really helps us out. + p.small.muted by Viirus and Lemoness if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 11/16/2015 - HABITICA STICKERS AND COSTUME CONTEST BADGES! + tr + td + .achievement-costumeContest2x.pull-right + h3 Costume Contest Badges + p We've awarded all the costume contest badges! Over the coming months, we'll be posting the costumes on our blog, so be sure to follow along. + br + p Important: if you submitted a photo but did NOT receive your badge, it probably means that we were unable to view your entry due to privacy restrictions or other issues. Email your photo to leslie@habitica.com and she will make sure that you get your badge! + br + p Thanks again to all the participants. We were very impressed by your creativity! + tr + td + .promo_habitica_sticker.pull-right + h3 Habitica Stickers + p In addition to our Habitica T-shirts, we are now also selling Habitica Stickers! Display Melior anywhere for extra motivation. + p.small.muted by Redphoenix and Sara Olson h2 11/11/2015 - NOVEMBER PET QUEST, SHARE SUCCESS, AND HABITICA T-SHIRTS tr td