Merged: updating two-handed tests to use arrow functions.

This commit is contained in:
Oliver Eyton-Williams 2015-11-25 12:12:22 +01:00
commit 707cd08a8b
30 changed files with 1863 additions and 1702 deletions

View file

@ -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"
}

View file

@ -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: <strong><%= utc %></strong>",
"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.<br><br> <strong>If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all.</strong> 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."
}

View file

@ -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;
};
}

View file

@ -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'),
},
};

110
common/script/cron.js Normal file
View file

@ -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
}
}

View file

@ -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--;

View file

@ -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));
}

View file

@ -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']);
});

View file

@ -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}`);

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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);
});
});
});

View file

@ -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));
});
});
});

View file

@ -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';
});
};
};

View file

@ -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);
});
});

View file

@ -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);
});

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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' } });

View file

@ -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();

View file

@ -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');
}
};

View file

@ -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;

View file

@ -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() {

View file

@ -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];

View file

@ -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;
}
});

View file

@ -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",

View file

@ -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.');
}
}

View file

@ -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')}&nbsp;
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')}&nbsp;
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

View file

@ -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 <a href='http://blog.habitrpg.com/tagged/cosplay' target='_blank'>on our blog</a>, 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 <a href='mailto:leslie@habitica.com' target='_blank'>leslie@habitica.com</a> and she will make sure that you get your badge!
p If youd 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 <a href='http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Technology_Stack' target='_blank'>our tech stack</a>! 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 <a href='mailto:jobs@habitica.com'>jobs@habitica.com</a> 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. Were looking forward to hearing from you!
tr
td
.promo_habitica_sticker.pull-right
h3 Habitica Stickers
p In addition to our <a href='https://teespring.com/stores/habitica' target='_blank'>Habitica T-shirts</a>, we are now also selling <a href='https://www.stickermule.com/uk/marketplace/9317-habitica-gryphon-sticker' target='_blank'>Habitica Stickers</a>! 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 <a href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8' target='_blank'>iOS update</a>, 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 <a href='http://blog.habitrpg.com/tagged/cosplay' target='_blank'>on our blog</a>, 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 <a href='mailto:leslie@habitica.com' target='_blank'>leslie@habitica.com</a> 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 <a href='https://teespring.com/stores/habitica' target='_blank'>Habitica T-shirts</a>, we are now also selling <a href='https://www.stickermule.com/uk/marketplace/9317-habitica-gryphon-sticker' target='_blank'>Habitica Stickers</a>! 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