From 7ec96877745a8f78bcd736fdeb483efe48db2242 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 07:40:05 -0600 Subject: [PATCH 01/20] Add linting from api-v3 --- .eslintrc | 120 +++++++++++++++++++++++++++++++++++++++++++ tasks/gulp-eslint.js | 48 +++++------------ 2 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..69c945431e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,120 @@ +{ + "rules": { + "indent": [2, 2], + "quotes": [2, "single"], + "linebreak-style": [2, "unix"], + "semi": [2, "always"], + "no-extra-parens": 2, + "no-unexpected-multiline": 2, + "block-scoped-var": 2, + "dot-location": [2, "property"], + "dot-notation": 2, + "eqeqeq": 2, + "no-caller": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-empty-pattern": 2, + "no-empty-label": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-implicit-coercion": 2, + "no-implied-eval": 2, + "no-native-reassign": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-new": 2, + "no-octal-escape": 2, + "no-octal": 2, + "no-param-reassign": 2, + "no-process-env": 2, + "no-proto": 2, + "no-implied-eval": 2, + "yoda": 2, + "wrap-iife": 2, + "radix": 2, + "no-with": 2, + "no-void": 2, + "no-useless-concat": 2, + "no-unused-expressions": 2, + "no-throw-literal": 2, + "no-sequences": 2, + "no-self-compare": 2, + "no-return-assign": 2, + "no-redeclare": 2, + "strict": [2, "global"], + "no-delete-var": 2, + "no-label-var": 2, + "no-shadow-restricted-names": 2, + "no-shadow": [2, { "builtinGlobals": true }], + "no-undef-init": 2, + "no-undef": [2, { typeof: true }], + "no-unused-vars": 2, + "no-use-before-define": 2, + "global-require": 2, + "handle-callback-err": [2, "^.*(e|E)rr"], + "no-path-concat": 2, + "arrow-spacing": 2, + "constructor-super": 2, + "generator-star-spacing": 2, + "no-arrow-condition": 2, + "no-class-assign": 2, + "no-const-assign": 2, + "no-dupe-class-members": 2, + "no-this-before-super": 2, + "no-var": 2, + "object-shorthand": 2, + "prefer-const": 0, + "prefer-spread": 2, + "prefer-template": 2, + "array-bracket-spacing": [2, "never"], + "brace-style": [2, "1tbs", { "allowSingleLine": false }], + "camelcase": 2, + "comma-spacing": 2, + "comma-style": [2, "last"], + "comma-dangle": [2, "always-multiline"], + "computed-property-spacing": [2, "never"], + "consistent-this": [2, "self"], + "func-names": 2, + "func-style": [2, "declaration", { "allowArrowFunctions": true }], + "block-spacing": [2, "always"], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "max-nested-callbacks": [2, 3], + "new-cap": 2, + "new-parens": 2, + "newline-after-var": 2, + "no-array-constructor": 2, + "no-continue": 2, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-trailing-spaces": 2, + "no-spaced-func": 2, + "no-new-object": 2, + "no-nested-ternary": 2, + "one-var": [2, "never"], + "operator-linebreak": [2, "after"], + "quote-props": [2, "as-needed"], + "semi-spacing": [2, {"before": false, "after": true}], + "space-after-keywords": 2, + "space-before-blocks": 2, + "space-before-function-paren": 2, + "space-before-keywords": 2, + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": 2, + "spaced-comment": [2, "always", { exceptions: ["-"]}], + "padded-blocks": [2, "never"], + "no-multiple-empty-lines": [2, {max: 2}] + }, + "env": { + "es6": true, + "node": true + }, + ecmaFeatures : { + modules: true + }, + "extends": "eslint:recommended" +} diff --git a/tasks/gulp-eslint.js b/tasks/gulp-eslint.js index 92dd25947c..403dc517f3 100644 --- a/tasks/gulp-eslint.js +++ b/tasks/gulp-eslint.js @@ -1,43 +1,21 @@ import gulp from 'gulp'; import eslint from 'gulp-eslint'; -import _ from 'lodash'; - -// TODO remove once we upgrade to lodash 3 -const defaultsDeep = _.partialRight(_.merge, _.defaults); - -const shared = { - rules: { - indent: [2, 2], - quotes: [2, 'single'], - 'linebreak-style': [2, 'unix'], - semi: [2, 'always'] - }, - extends: 'eslint:recommended', - env: { - es6: true - } -}; - -gulp.task('lint:client', () => { - return gulp.src(['./website/public/js/**/*.js']) - .pipe(eslint(defaultsDeep({ - env: { - node: true - } - }, shared))) - .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/**/*.js']) - .pipe(eslint(defaultsDeep({ - env: { - browser: true - } - }, shared))) + return gulp + .src([ + './website/src/**/api-v3/**/*.js', + './website/src/models/user.js', + './website/src/server.js' + ]) + .pipe(eslint()) .pipe(eslint.format()) .pipe(eslint.failAfterError()); }); -gulp.task('lint', ['lint:server', 'lint:client']); +gulp.task('lint', ['lint:server']); From c3b4f03c357a2ddc9e65159307ebe313868da0ee Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 07:49:48 -0600 Subject: [PATCH 02/20] Add linting for common files --- tasks/gulp-eslint.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tasks/gulp-eslint.js b/tasks/gulp-eslint.js index 403dc517f3..42462ce75d 100644 --- a/tasks/gulp-eslint.js +++ b/tasks/gulp-eslint.js @@ -10,12 +10,28 @@ gulp.task('lint:server', () => { return gulp .src([ './website/src/**/api-v3/**/*.js', - './website/src/models/user.js', - './website/src/server.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()); }); -gulp.task('lint', ['lint:server']); +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()); +}); + +gulp.task('lint', ['lint:server', 'lint:common']); From ff929b669d40f64ec7615dfbb244252c97788f82 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 21:44:10 -0600 Subject: [PATCH 03/20] Convert common tests to js --- test/common/algos.mocha.coffee | 925 ---------- test/common/algos.mocha.js | 1507 +++++++++++++++++ test/common/dailies.coffee | 418 ----- test/common/dailies.js | 541 ++++++ test/common/mocha.opts | 1 + test/common/simulations/autoAllocate.coffee | 82 - test/common/simulations/autoAllocate.js | 164 ++ .../simulations/passive_active_attrs.coffee | 180 -- .../simulations/passive_active_attrs.js | 294 ++++ test/common/test_helper.coffee | 44 - test/common/test_helper.js | 53 + 11 files changed, 2560 insertions(+), 1649 deletions(-) delete mode 100644 test/common/algos.mocha.coffee create mode 100644 test/common/algos.mocha.js delete mode 100644 test/common/dailies.coffee create mode 100644 test/common/dailies.js delete mode 100644 test/common/simulations/autoAllocate.coffee create mode 100644 test/common/simulations/autoAllocate.js delete mode 100644 test/common/simulations/passive_active_attrs.coffee create mode 100644 test/common/simulations/passive_active_attrs.js delete mode 100644 test/common/test_helper.coffee create mode 100644 test/common/test_helper.js diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee deleted file mode 100644 index 4e911ca06d..0000000000 --- a/test/common/algos.mocha.coffee +++ /dev/null @@ -1,925 +0,0 @@ -_ = require 'lodash' -expect = require 'expect.js' -sinon = require 'sinon' -moment = require 'moment' -shared = require '../../common/script/index.js' -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations -test_helper = require './test_helper' -test_helper.addCustomMatchers() -$w = (s)->s.split(' ') - -### Helper Functions #### -newUser = (addTasks=true)-> - buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false} - user = - auth: - timestamps: {} - stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs} - items: - lastDrop: - count: 0 - hatchingPotions: {} - eggs: {} - food: {} - gear: - equipped: {} - costume: {} - owned: {} - quests: {} - party: - quest: - progress: - down: 0 - preferences: { - autoEquip: true - } - dailys: [] - todos: [] - rewards: [] - flags: {} - achievements: - ultimateGearSets: {} - contributor: - level: 2 - _tmp: {} - - shared.wrap(user) - user.ops.reset(null, ->) - if addTasks - _.each ['habit', 'todo', 'daily'], (task)-> - user.ops.addTask {body: {type: task, id: shared.uuid()}} - user - -rewrapUser = (user)-> - user._wrapped = false - shared.wrap(user) - user - -expectStrings = (obj, paths) -> - _.each paths, (path) -> expect(obj[path]).to.be.ok() - -# options.daysAgo: days ago when the last cron was executed -# cronAfterStart: moves the lastCron to be after the dayStart. -# This way the daysAgo works as expected if the test case -# makes the assumption that the lastCron was after dayStart. -beforeAfter = (options={}) -> - user = newUser() - [before, after] = [user, _.cloneDeep(user)] - # avoid closure on the original user - rewrapUser(after) - before.preferences.dayStart = after.preferences.dayStart = options.dayStart if options.dayStart - before.preferences.timezoneOffset = after.preferences.timezoneOffset = (options.timezoneOffset or moment().zone()) - if options.limitOne - before["#{options.limitOne}s"] = [before["#{options.limitOne}s"][0]] - after["#{options.limitOne}s"] = [after["#{options.limitOne}s"][0]] - lastCron = moment(options.now || +new Date).subtract( {days:options.daysAgo} ) if options.daysAgo - lastCron.add( {hours:options.dayStart, minutes:1} ) if options.daysAgo and options.cronAfterStart - lastCron = +lastCron if options.daysAgo - _.each [before,after], (obj) -> - obj.lastCron = lastCron if options.daysAgo - {before:before, after:after} -#TODO calculate actual points - -expectLostPoints = (before, after, taskType) -> - if taskType in ['daily','habit'] - expect(after.stats.hp).to.be.lessThan before.stats.hp - expect(after["#{taskType}s"][0].history).to.have.length(1) - else expect(after.history.todos).to.have.length(1) - expect(after).toHaveExp 0 - expect(after).toHaveGP 0 - expect(after["#{taskType}s"][0].value).to.be.lessThan before["#{taskType}s"][0].value - -expectGainedPoints = (before, after, taskType) -> - expect(after.stats.hp).to.be 50 - expect(after.stats.exp).to.be.greaterThan before.stats.exp - expect(after.stats.gp).to.be.greaterThan before.stats.gp - expect(after["#{taskType}s"][0].value).to.be.greaterThan before["#{taskType}s"][0].value - expect(after["#{taskType}s"][0].history).to.have.length(1) if taskType is 'habit' - # daily & todo histories handled on cron - -expectNoChange = (before,after) -> - _.each $w('stats items gear dailys todos rewards preferences'), (attr)-> - expect(after[attr]).to.eql before[attr] - -expectClosePoints = (before, after, taskType) -> - expect( Math.abs(after.stats.exp - before.stats.exp) ).to.be.lessThan 0.0001 - expect( Math.abs(after.stats.gp - before.stats.gp) ).to.be.lessThan 0.0001 - expect( Math.abs(after["#{taskType}s"][0].value - before["#{taskType}s"][0].value) ).to.be.lessThan 0.0001 - -expectDayResetNoDamage = (b,a) -> - [before,after] = [_.cloneDeep(b), _.cloneDeep(a)] - _.each after.dailys, (task,i) -> - expect(task.completed).to.be false - expect(before.dailys[i].value).to.be task.value - expect(before.dailys[i].streak).to.be task.streak - expect(task.history).to.have.length(1) - _.each after.todos, (task,i) -> - expect(task.completed).to.be false - expect(before.todos[i].value).to.be.greaterThan task.value - expect(after.history.todos).to.have.length(1) - # hack so we can compare user before/after obj equality sans effected paths - _.each [before,after], (obj) -> - delete obj.stats.buffs - _.each $w('dailys todos history lastCron'), (path) -> delete obj[path] - delete after._tmp - expectNoChange(before, after) - -cycle = (array)-> - n = -1 - (seed=0)-> - n++ - return array[n % array.length] - -repeatWithoutLastWeekday = ()-> - repeat = {su:true,m:true,t:true,w:true,th:true,f:true,s:true} - if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday - repeat.su = false - else - repeat.s = false - {repeat: repeat} - -###### Specs ###### - -describe 'User', -> - it 'sets correct user defaults', -> - user = newUser() - base_gear = { armor: 'armor_base_0', weapon: 'weapon_base_0', head: 'head_base_0', shield: 'shield_base_0' } - buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false} - expect(user.stats).to.eql { str: 1, con: 1, per: 1, int: 1, hp: 50, mp: 32, lvl: 1, exp: 0, gp: 0, class: 'warrior', buffs: buffs } - expect(user.items.gear).to.eql { equipped: base_gear, costume: base_gear, owned: {weapon_warrior_0: true} } - expect(user.preferences).to.eql { autoEquip: true, costume: false } - - it 'calculates max MP', -> - user = newUser() - expect(user).toHaveMaxMP 32 - user.stats.int = 10 - expect(user).toHaveMaxMP 50 - user.stats.lvl = 5 - expect(user).toHaveMaxMP 54 - user.stats.class = 'wizard' - user.items.gear.equipped.weapon = 'weapon_wizard_1' - expect(user).toHaveMaxMP 63 - - it 'handles perfect days', -> - user = newUser() - user.dailys = [] - _.times 3, ->user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')}) - cron = -> user.lastCron = moment().subtract(1,'days');user.fns.cron() - - cron() - expect(user.stats.buffs.str).to.be 0 - expect(user.achievements.perfect).to.not.be.ok() - - user.dailys[0].completed = true - cron() - expect(user.stats.buffs.str).to.be 0 - expect(user.achievements.perfect).to.not.be.ok() - - _.each user.dailys, (d)->d.completed = true - cron() - expect(user.stats.buffs.str).to.be 1 - expect(user.achievements.perfect).to.be 1 - - # Handle greyed-out dailys - yesterday = moment().subtract(1,'days') - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false - _.each user.dailys[1..], (d)->d.completed = true - cron() - expect(user.stats.buffs.str).to.be 1 - expect(user.achievements.perfect).to.be 2 - - describe 'Resting in the Inn', -> - user = null - cron = null - - beforeEach -> - user = newUser() - user.preferences.sleep = true - cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron() - user.dailys = [] - _.times 2, -> user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')}) - - it 'remains in the inn on cron', -> - cron() - expect(user.preferences.sleep).to.be true - - it 'resets dailies', -> - user.dailys[0].completed = true - cron() - expect(user.dailys[0].completed).to.be false - - it 'resets checklist on incomplete dailies', -> - user.dailys[0].checklist = [ - { - "text" : "1", - "id" : "checklist-one", - "completed" : true - }, - { - "text" : "2", - "id" : "checklist-two", - "completed" : true - }, - { - "text" : "3", - "id" : "checklist-three", - "completed" : false - } - ] - cron() - _.each user.dailys[0].checklist, (box)-> - expect(box.completed).to.be false - - it 'resets checklist on complete dailies', -> - user.dailys[0].checklist = [ - { - "text" : "1", - "id" : "checklist-one", - "completed" : true - }, - { - "text" : "2", - "id" : "checklist-two", - "completed" : true - }, - { - "text" : "3", - "id" : "checklist-three", - "completed" : false - } - ] - user.dailys[0].completed = true - cron() - _.each user.dailys[0].checklist, (box)-> - expect(box.completed).to.be false - - it 'does not reset checklist on grey incomplete dailies', -> - yesterday = moment().subtract(1,'days') - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false - user.dailys[0].checklist = [ - { - "text" : "1", - "id" : "checklist-one", - "completed" : true - }, - { - "text" : "2", - "id" : "checklist-two", - "completed" : true - }, - { - "text" : "3", - "id" : "checklist-three", - "completed" : true - } - ] - - cron() - _.each user.dailys[0].checklist, (box)-> - expect(box.completed).to.be true - - it 'resets checklist on complete grey complete dailies', -> - yesterday = moment().subtract(1,'days') - user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false - user.dailys[0].checklist = [ - { - "text" : "1", - "id" : "checklist-one", - "completed" : true - }, - { - "text" : "2", - "id" : "checklist-two", - "completed" : true - }, - { - "text" : "3", - "id" : "checklist-three", - "completed" : true - } - ] - user.dailys[0].completed = true - - cron() - _.each user.dailys[0].checklist, (box)-> - expect(box.completed).to.be false - - it 'does not damage user for incomplete dailies', -> - expect(user).toHaveHP 50 - user.dailys[0].completed = true - user.dailys[1].completed = false - cron() - expect(user).toHaveHP 50 - - it 'gives credit for complete dailies', -> - user.dailys[0].completed = true - expect(user.dailys[0].history).to.be.empty - cron() - expect(user.dailys[0].history).to.not.be.empty - - it 'damages user for incomplete dailies after checkout', -> - expect(user).toHaveHP 50 - user.dailys[0].completed = true - user.dailys[1].completed = false - user.preferences.sleep = false - cron() - expect(user.stats.hp).to.be.lessThan 50 - - describe 'Death', -> - user = undefined - it 'revives correctly', -> - user = newUser() - user.stats = { gp: 10, exp: 100, lvl: 2, hp: 0, class: 'warrior' } - user.ops.revive() - expect(user).toHaveGP 0 - expect(user).toHaveExp 0 - expect(user).toHaveLevel 1 - expect(user).toHaveHP 50 - expect(user.items.gear.owned).to.eql { weapon_warrior_0: false } - - it "doesn't break unbreakables", -> - ce = shared.countExists - user = newUser() - # breakables (includes default weapon_warrior_0): - user.items.gear.owned['shield_warrior_1'] = true - # unbreakables because off-class or 0 value: - user.items.gear.owned['shield_rogue_1'] = true - user.items.gear.owned['head_special_nye'] = true - expect(ce user.items.gear.owned).to.be 4 - user.stats.hp = 0 - user.ops.revive() - expect(ce(user.items.gear.owned)).to.be 3 - user.stats.hp = 0 - user.ops.revive() - expect(ce(user.items.gear.owned)).to.be 2 - user.stats.hp = 0 - user.ops.revive() - expect(ce(user.items.gear.owned)).to.be 2 - expect(user.items.gear.owned).to.eql { weapon_warrior_0: false, shield_warrior_1: false, shield_rogue_1: true, head_special_nye: true } - - it "handles event items", -> - shared.content.gear.flat.head_special_nye.event.start = '2012-01-01' - shared.content.gear.flat.head_special_nye.event.end = '2012-02-01' - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be true - delete user.items.gear.owned['head_special_nye'] - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be false - - shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5,'days') - shared.content.gear.flat.head_special_nye.event.end = moment().add(5,'days') - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be true - - describe 'Rebirth', -> - user = undefined - it 'removes correct gear', -> - user = newUser() - user.stats.lvl = 100 - user.items.gear.owned = { - "weapon_warrior_0": true, - "weapon_warrior_1": true, - "armor_warrior_1": false, - "armor_mystery_201402": true, - "back_mystery_201402": false, - "head_mystery_201402": true, - "weapon_armoire_basicCrossbow": true, - } - user.ops.rebirth() - expect(user.items.gear.owned).to.eql { - "weapon_warrior_0": true, - "weapon_warrior_1": false, - "armor_warrior_1": false, - "armor_mystery_201402": true, - "back_mystery_201402": false, - "head_mystery_201402": true, - "weapon_armoire_basicCrossbow": false, - } - - describe 'store', -> - it 'buys a Quest scroll', -> - user = newUser() - user.stats.gp = 205 - user.ops.buyQuest {params: {key: 'dilatoryDistress1'}} - expect(user.items.quests).to.eql {dilatoryDistress1: 1} - expect(user).toHaveGP 5 - - it 'does not buy Quests without enough Gold', -> - user = newUser() - user.stats.gp = 1 - user.ops.buyQuest {params: {key: 'dilatoryDistress1'}} - expect(user.items.quests).to.eql {} - expect(user).toHaveGP 1 - - it 'does not buy nonexistent Quests', -> - user = newUser() - user.stats.gp = 9999 - user.ops.buyQuest {params: {key: 'snarfblatter'}} - expect(user.items.quests).to.eql {} - expect(user).toHaveGP 9999 - - it 'does not buy Gem-premium Quests', -> - user = newUser() - user.stats.gp = 9999 - user.ops.buyQuest {params: {key: 'kraken'}} - expect(user.items.quests).to.eql {} - expect(user).toHaveGP 9999 - - describe 'Gem purchases', -> - it 'does not purchase items without enough Gems', -> - user = newUser() - user.ops.purchase {params: {type: 'eggs', key: 'Cactus'}} - user.ops.purchase {params: {type: 'gear', key: 'headAccessory_special_foxEars'}} - user.ops.unlock {query: {path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'}} - expect(user.items.eggs).to.eql {} - expect(user.items.gear.owned).to.eql { weapon_warrior_0: true } - - it 'purchases an egg', -> - user = newUser() - user.balance = 1 - user.ops.purchase {params: {type: 'eggs', key: 'Cactus'}} - expect(user.items.eggs).to.eql { Cactus: 1} - expect(user.balance).to.eql 0.25 - - it 'purchases fox ears', -> - user = newUser() - user.balance = 1 - user.ops.purchase {params: {type: 'gear', key: 'headAccessory_special_foxEars'}} - expect(user.items.gear.owned).to.eql { weapon_warrior_0: true, headAccessory_special_foxEars: true } - expect(user.balance).to.eql 0.5 - - it 'unlocks all the animal ears at once', -> - user = newUser() - user.balance = 2 - user.ops.unlock {query: {path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'}} - expect(user.items.gear.owned).to.eql { weapon_warrior_0: true, headAccessory_special_bearEars: true, headAccessory_special_cactusEars: true, headAccessory_special_foxEars: true, headAccessory_special_lionEars: true, headAccessory_special_pandaEars: true, headAccessory_special_pigEars: true, headAccessory_special_tigerEars: true, headAccessory_special_wolfEars: true} - expect(user.balance).to.eql 0.75 - - describe 'spells', -> - _.each shared.content.spells, (spellClass)-> - _.each spellClass, (spell)-> - it "#{spell.text} has valid values", -> - expect(spell.target).to.match(/^(task|self|party|user)$/) - expect(spell.mana).to.be.an('number') - if spell.lvl - expect(spell.lvl).to.be.an('number') - expect(spell.lvl).to.be.above(0) - expect(spell.cast).to.be.a('function') - - describe 'drop system', -> - user = null - MIN_RANGE_FOR_POTION = 0 - MAX_RANGE_FOR_POTION = .3 - MIN_RANGE_FOR_EGG = .4 - MAX_RANGE_FOR_EGG = .6 - MIN_RANGE_FOR_FOOD = .7 - MAX_RANGE_FOR_FOOD = 1 - - beforeEach -> - user = newUser() - user.flags.dropsEnabled = true - @task_id = shared.uuid() - user.ops.addTask({body: {type: 'daily', id: @task_id}}) - - it 'drops a hatching potion', -> - for random in [MIN_RANGE_FOR_POTION..MAX_RANGE_FOR_POTION] by .1 - sinon.stub(user.fns, 'predictableRandom').returns random - user.ops.score {params: { id: @task_id, direction: 'up'}} - expect(user.items.eggs).to.be.empty - expect(user.items.hatchingPotions).to.not.be.empty - expect(user.items.food).to.be.empty - user.fns.predictableRandom.restore() - - it 'drops a pet egg', -> - for random in [MIN_RANGE_FOR_EGG..MAX_RANGE_FOR_EGG] by .1 - sinon.stub(user.fns, 'predictableRandom').returns random - user.ops.score {params: { id: @task_id, direction: 'up'}} - expect(user.items.eggs).to.not.be.empty - expect(user.items.hatchingPotions).to.be.empty - expect(user.items.food).to.be.empty - user.fns.predictableRandom.restore() - - it 'drops food', -> - for random in [MIN_RANGE_FOR_FOOD..MAX_RANGE_FOR_FOOD] by .1 - sinon.stub(user.fns, 'predictableRandom').returns random - user.ops.score {params: { id: @task_id, direction: 'up'}} - expect(user.items.eggs).to.be.empty - expect(user.items.hatchingPotions).to.be.empty - expect(user.items.food).to.not.be.empty - user.fns.predictableRandom.restore() - - it 'does not get a drop', -> - sinon.stub(user.fns, 'predictableRandom').returns 0.5 - user.ops.score {params: { id: @task_id, direction: 'up'}} - expect(user.items.eggs).to.eql {} - expect(user.items.hatchingPotions).to.eql {} - expect(user.items.food).to.eql {} - user.fns.predictableRandom.restore() - - describe 'Quests', -> - _.each shared.content.quests, (quest)-> - it "#{quest.text()} has valid values", -> - expect(quest.notes()).to.be.an('string') - expect(quest.completion()).to.be.an('string') if quest.completion - expect(quest.previous).to.be.an('string') if quest.previous - expect(quest.value).to.be.greaterThan 0 if quest.canBuy() - expect(quest.drop.gp).to.not.be.lessThan 0 - expect(quest.drop.exp).to.not.be.lessThan 0 - expect(quest.category).to.match(/pet|unlockable|gold|world/) - if quest.drop.items - expect(quest.drop.items).to.be.an(Array) - if quest.boss - expect(quest.boss.name()).to.be.an('string') - expect(quest.boss.hp).to.be.greaterThan 0 - expect(quest.boss.str).to.be.greaterThan 0 - else if quest.collect - _.each quest.collect, (collect)-> - expect(collect.text()).to.be.an('string') - expect(collect.count).to.be.greaterThan 0 - - describe 'Achievements', -> - _.each shared.content.classes, (klass) -> - user = newUser() - user.stats.gp = 10000 - _.each shared.content.gearTypes, (type) -> - _.each [1..5], (i) -> - user.ops.buy {params:'#{type}_#{klass}_#{i}'} - it 'does not get ultimateGear ' + klass, -> - expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok() - _.each shared.content.gearTypes, (type) -> - user.ops.buy {params:'#{type}_#{klass}_6'} - xit 'gets ultimateGear ' + klass, -> - expect(user.achievements.ultimateGearSets[klass]).to.be.ok() - - it 'does not remove existing Ultimate Gear achievements', -> - user = newUser() - user.achievements.ultimateGearSets = {'healer':true,'wizard':true,'rogue':true,'warrior':true} - user.items.gear.owned.shield_warrior_5 = false - user.items.gear.owned.weapon_rogue_6 = false - user.ops.buy {params:'shield_warrior_5'} - expect(user.achievements.ultimateGearSets).to.eql {'healer':true,'wizard':true,'rogue':true,'warrior':true} - - describe 'unlocking features', -> - it 'unlocks drops at level 3', -> - user = newUser() - user.stats.lvl = 3 - user.fns.updateStats(user.stats) - expect(user.flags.dropsEnabled).to.be.ok() - - it 'unlocks Rebirth at level 50', -> - user = newUser() - user.stats.lvl = 50 - user.fns.updateStats(user.stats) - expect(user.flags.rebirthEnabled).to.be.ok() - - describe 'level-awarded Quests', -> - it 'gets Attack of the Mundane at level 15', -> - user = newUser() - user.stats.lvl = 15 - user.fns.updateStats(user.stats) - expect(user.flags.levelDrops.atom1).to.be.ok() - expect(user.items.quests.atom1).to.eql 1 - - it 'gets Vice at level 30', -> - user = newUser() - user.stats.lvl = 30 - user.fns.updateStats(user.stats) - expect(user.flags.levelDrops.vice1).to.be.ok() - expect(user.items.quests.vice1).to.eql 1 - - it 'gets Golden Knight at level 40', -> - user = newUser() - user.stats.lvl = 40 - user.fns.updateStats(user.stats) - expect(user.flags.levelDrops.goldenknight1).to.be.ok() - expect(user.items.quests.goldenknight1).to.eql 1 - - it 'gets Moonstone Chain at level 60', -> - user = newUser() - user.stats.lvl = 60 - user.fns.updateStats(user.stats) - expect(user.flags.levelDrops.moonstone1).to.be.ok() - expect(user.items.quests.moonstone1).to.eql 1 - -describe 'Simple Scoring', -> - beforeEach -> - {@before, @after} = beforeAfter() - - it 'Habits : Up', -> - @after.ops.score {params: {id: @after.habits[0].id, direction: 'down'}, query: {times: 5}} - expectLostPoints(@before, @after,'habit') - - it 'Habits : Down', -> - @after.ops.score {params: {id: @after.habits[0].id, direction: 'up'}, query: {times: 5}} - expectGainedPoints(@before, @after,'habit') - - it 'Dailys : Up', -> - @after.ops.score {params: {id: @after.dailys[0].id, direction: 'up'}} - expectGainedPoints(@before, @after,'daily') - - it 'Dailys : Up, Down', -> - @after.ops.score {params: {id: @after.dailys[0].id, direction: 'up'}} - @after.ops.score {params: {id: @after.dailys[0].id, direction: 'down'}} - expectClosePoints(@before, @after, 'daily') - - it 'Todos : Up', -> - @after.ops.score {params: {id: @after.todos[0].id, direction: 'up'}} - expectGainedPoints(@before, @after,'todo') - - it 'Todos : Up, Down', -> - @after.ops.score {params: {id: @after.todos[0].id, direction: 'up'}} - @after.ops.score {params: {id: @after.todos[0].id, direction: 'down'}} - expectClosePoints(@before, @after, 'todo') - -describe 'Cron', -> - - it 'computes shouldCron', -> - user = newUser() - - paths = {};user.fns.cron {paths} - expect(user.lastCron).to.not.be.ok # it setup the cron property now - - user.lastCron = +moment().subtract(1,'days') - - paths = {};user.fns.cron {paths} - expect(user.lastCron).to.be.greaterThan 0 - -# user.lastCron = +moment().add(1,'days') -# paths = {};algos.cron user, {paths} -# expect(paths.lastCron).to.be true # busted cron (was set to after today's date) - - it 'only dailies & todos are affected', -> - {before,after} = beforeAfter({daysAgo:1}) - before.dailys = before.todos = after.dailys = after.todos = [] - after.fns.cron() - before.stats.mp=after.stats.mp #FIXME - expect(after.lastCron).to.not.be before.lastCron # make sure cron was run - delete after.stats.buffs;delete before.stats.buffs - expect(before.stats).to.eql after.stats - beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards) - afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards) - expect(beforeTasks).to.eql afterTasks - - describe 'preening', -> - beforeEach -> - @clock = sinon.useFakeTimers(Date.parse("2013-11-20"), "Date") - - afterEach -> - @clock.restore() - - it 'should preen user history', -> - {before,after} = beforeAfter({daysAgo:1}) - history = [ - # Last year should be condensed to one entry, avg: 1 - {date:'09/01/2012', value: 0} - {date:'10/01/2012', value: 0} - {date:'11/01/2012', value: 2} - {date:'12/01/2012', value: 2} - - # Each month of this year should be condensed to 1/mo, averages follow - {date:'01/01/2013', value: 1} #2 - {date:'01/15/2013', value: 3} - - {date:'02/01/2013', value: 2} #3 - {date:'02/15/2013', value: 4} - - {date:'03/01/2013', value: 3} #4 - {date:'03/15/2013', value: 5} - - {date:'04/01/2013', value: 4} #5 - {date:'04/15/2013', value: 6} - - {date:'05/01/2013', value: 5} #6 - {date:'05/15/2013', value: 7} - - {date:'06/01/2013', value: 6} #7 - {date:'06/15/2013', value: 8} - - {date:'07/01/2013', value: 7} #8 - {date:'07/15/2013', value: 9} - - {date:'08/01/2013', value: 8} #9 - {date:'08/15/2013', value: 10} - - {date:'09/01/2013', value: 9} #10 - {date:'09/15/2013', value: 11} - - {date:'010/01/2013', value: 10} #11 - {date:'010/15/2013', value: 12} - - # This month should condense each week - {date:'011/01/2013', value: 12} - {date:'011/02/2013', value: 13} - {date:'011/03/2013', value: 14} - {date:'011/04/2013', value: 15} - ] - after.history = {exp: _.cloneDeep(history), todos: _.cloneDeep(history)} - after.habits[0].history = _.cloneDeep(history) - after.fns.cron() - - # remove history entries created by cron - after.history.exp.pop() - after.history.todos.pop() - - _.each [after.history.exp, after.history.todos, after.habits[0].history], (arr) -> - expect(_.map(arr, (x)->x.value)).to.eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - - describe 'Todos', -> - it '1 day missed', -> - {before,after} = beforeAfter({daysAgo:1}) - before.dailys = after.dailys = [] - after.fns.cron() - - # todos don't effect stats - expect(after).toHaveHP 50 - expect(after).toHaveExp 0 - expect(after).toHaveGP 0 - - # but they devalue - expect(before.todos[0].value).to.be 0 # sanity check for task setup - expect(after.todos[0].value).to.be -1 # the actual test - expect(after.history.todos).to.have.length 1 - - it '2 days missed', -> - {before,after} = beforeAfter({daysAgo:2}) - before.dailys = after.dailys = [] - after.fns.cron() - - # todos devalue by only one day's worth of devaluation - expect(before.todos[0].value).to.be 0 # sanity check for task setup - expect(after.todos[0].value).to.be -1 # the actual test - - # I used hard-coded dates here instead of 'now' so the tests don't fail - # when you run them between midnight and dayStart. Nothing worse than - # intermittent failures. - describe 'cron day calculations', -> - dayStart = 4 - fstr = "YYYY-MM-DD HH:mm:ss" - - it 'startOfDay before dayStart', -> - # If the time is before dayStart, then we expect the start of the day to be yesterday at dayStart - start = shared.startOfDay {now: moment('2014-10-09 02:30:00'), dayStart} - expect(start.format(fstr)).to.eql '2014-10-08 04:00:00' - - it 'startOfDay after dayStart', -> - # If the time is after dayStart, then we expect the start of the day to be today at dayStart - start = shared.startOfDay {now: moment('2014-10-09 05:30:00'), dayStart} - expect(start.format(fstr)).to.eql '2014-10-09 04:00:00' - - it 'daysSince cron before, now after', -> - # If the lastCron was before dayStart, then a time on the same day after dayStart - # should be 1 day later than lastCron - lastCron = moment('2014-10-09 02:30:00') - days = shared.daysSince(lastCron, {now: moment('2014-10-09 11:30:00'), dayStart}) - expect(days).to.eql 1 - - it 'daysSince cron before, now before', -> - # If the lastCron was before dayStart, then a time on the same day also before dayStart - # should be 0 days later than lastCron - lastCron = moment('2014-10-09 02:30:00') - days = shared.daysSince(lastCron, {now: moment('2014-10-09 03:30:00'), dayStart}) - expect(days).to.eql 0 - - it 'daysSince cron after, now after', -> - # If the lastCron was after dayStart, then a time on the same day also after dayStart - # should be 0 days later than lastCron - lastCron = moment('2014-10-09 05:30:00') - days = shared.daysSince(lastCron, {now: moment('2014-10-09 06:30:00'), dayStart}) - expect(days).to.eql 0 - - it 'daysSince cron after, now tomorrow before', -> - # If the lastCron was after dayStart, then a time on the following day but before dayStart - # should be 0 days later than lastCron - lastCron = moment('2014-10-09 12:30:00') - days = shared.daysSince(lastCron, {now: moment('2014-10-10 01:30:00'), dayStart}) - expect(days).to.eql 0 - - it 'daysSince cron after, now tomorrow after', -> - # If the lastCron was after dayStart, then a time on the following day and after dayStart - # should be 1 day later than lastCron - lastCron = moment('2014-10-09 12:30:00') - days = shared.daysSince(lastCron, {now: moment('2014-10-10 10:30:00'), dayStart}) - expect(days).to.eql 1 - - xit 'daysSince, last cron before new dayStart', -> - # If lastCron was after dayStart (at 1am) with dayStart set at 0, changing dayStart to 4am - # should not trigger another cron the same day - - # dayStart is 0 - lastCron = moment('2014-10-09 01:00:00') - # dayStart is 4 - days = shared.daysSince(lastCron, {now: moment('2014-10-09 05:00:00'), dayStart}) - expect(days).to.eql 0 - - describe 'dailies', -> - - describe 'new day', -> - - ### - This section runs through a "cron matrix" of all permutations (that I can easily account for). It sets - task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron, - and so on - testing each possible outcome along the way - ### - - runCron = (options) -> - _.each [480, 240, 0, -120], (timezoneOffset) -> # test different timezones - now = shared.startOfWeek({timezoneOffset}).add(options.currentHour||0, 'hours') - {before,after} = beforeAfter({now, timezoneOffset, daysAgo:1, cronAfterStart:options.cronAfterStart||true, dayStart:options.dayStart||0, limitOne:'daily'}) - before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat - before.dailys[0].streak = after.dailys[0].streak = 10 - before.dailys[0].completed = after.dailys[0].completed = true if options.checked - before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days') - if options.shouldDo - expect(shared.shouldDo(now.toDate(), after.dailys[0], {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok() - after.fns.cron {now} - before.stats.mp=after.stats.mp #FIXME - switch options.expect - when 'losePoints' then expectLostPoints(before,after,'daily') - when 'noChange' then expectNoChange(before,after) - when 'noDamage' then expectDayResetNoDamage(before,after) - {before,after} - - # These test cases were written assuming that lastCron was run after dayStart - # even if currentHour < dayStart and lastCron = yesterday at currentHour. - # cronAfterStart makes sure that lastCron is moved to be after dayStart. - cronMatrix = - steps: - - 'due yesterday': - defaults: {daysAgo:1, cronAfterStart:true, limitOne: 'daily'} - steps: - - '(simple)': {expect:'losePoints'} - - 'due today': - # NOTE: a strange thing here, moment().startOf('week') is Sunday, but moment.zone(myTimeZone).startOf('week') is Monday. - defaults: {repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}} - steps: - 'pre-dayStart': - defaults: {currentHour:3, dayStart:4, shouldDo:true} - steps: - 'checked': {checked: true, expect:'noChange'} - 'un-checked': {checked: false, expect:'noChange'} - 'post-dayStart': - defaults: {currentHour:5, dayStart:4, shouldDo:true} - steps: - 'checked': {checked:true, expect:'noDamage'} - 'unchecked': {checked:false, expect: 'losePoints'} - - 'NOT due today': - defaults: {repeat:{su:true,m:false,t:true,w:true,th:true,f:true,s:true}} - steps: - 'pre-dayStart': - defaults: {currentHour:3, dayStart:4, shouldDo:true} - steps: - 'checked': {checked: true, expect:'noChange'} - 'un-checked': {checked: false, expect:'noChange'} - 'post-dayStart': - defaults: {currentHour:5, dayStart:4, shouldDo:false} - steps: - 'checked': {checked:true, expect:'noDamage'} - 'unchecked': {checked:false, expect: 'losePoints'} - - 'not due yesterday': - defaults: repeatWithoutLastWeekday() - steps: - '(simple)': {expect:'noDamage'} - 'post-dayStart': {currentHour:5,dayStart:4, expect:'noDamage'} - 'pre-dayStart': {currentHour:3, dayStart:4, expect:'noChange'} - - recurseCronMatrix = (obj, options={}) -> - if obj.steps - _.each obj.steps, (step, text) -> - o = _.cloneDeep options - o.text ?= ''; o.text += " #{text} " - recurseCronMatrix step, _.defaults(o,obj.defaults) - else - it "#{options.text}", -> runCron(_.defaults(obj,options)) - recurseCronMatrix(cronMatrix) - -describe 'Helper', -> - - it 'calculates gold coins', -> - expect(shared.gold(10)).to.eql 10 - expect(shared.gold(1.957)).to.eql 1 - expect(shared.gold()).to.eql 0 - - it 'calculates silver coins', -> - expect(shared.silver(10)).to.eql 0 - expect(shared.silver(1.957)).to.eql 95 - expect(shared.silver(0.01)).to.eql "01" - expect(shared.silver()).to.eql "00" - - it 'calculates experience to next level', -> - expect(shared.tnl 1).to.eql 150 - expect(shared.tnl 2).to.eql 160 - expect(shared.tnl 10).to.eql 260 - expect(shared.tnl 99).to.eql 3580 - - it 'calculates the start of the day', -> - fstr = 'YYYY-MM-DD HH:mm:ss' - today = '2013-01-01 00:00:00' - # get the timezone for the day, so the test case doesn't fail - # if you run it during daylight savings time because by default - # it uses moment().zone() which is the current minute offset - zone = moment(today).zone() - expect(shared.startOfDay({now: new Date(2013, 0, 1, 0)}, timezoneOffset:zone).format(fstr)).to.eql today - expect(shared.startOfDay({now: new Date(2013, 0, 1, 5)}, timezoneOffset:zone).format(fstr)).to.eql today - expect(shared.startOfDay({now: new Date(2013, 0, 1, 23, 59, 59), timezoneOffset:zone}).format(fstr)).to.eql today diff --git a/test/common/algos.mocha.js b/test/common/algos.mocha.js new file mode 100644 index 0000000000..06ebd3044d --- /dev/null +++ b/test/common/algos.mocha.js @@ -0,0 +1,1507 @@ +var $w, _, beforeAfter, cycle, expect, expectClosePoints, expectDayResetNoDamage, expectGainedPoints, expectLostPoints, expectNoChange, expectStrings, moment, newUser, repeatWithoutLastWeekday, rewrapUser, shared, sinon, test_helper; + +_ = require('lodash'); + +expect = require('expect.js'); + +sinon = require('sinon'); + +moment = require('moment'); + +shared = require('../../common/script/index.js'); + +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; + +test_helper = require('./test_helper'); + +test_helper.addCustomMatchers(); + +$w = function(s) { + return s.split(' '); +}; + + +/* Helper Functions */ + +newUser = function(addTasks) { + var buffs, user; + if (addTasks == null) { + addTasks = true; + } + buffs = { + per: 0, + int: 0, + con: 0, + str: 0, + stealth: 0, + streaks: false + }; + user = { + auth: { + timestamps: {} + }, + stats: { + str: 1, + con: 1, + per: 1, + int: 1, + mp: 32, + "class": 'warrior', + buffs: buffs + }, + items: { + lastDrop: { + count: 0 + }, + hatchingPotions: {}, + eggs: {}, + food: {}, + gear: { + equipped: {}, + costume: {}, + owned: {} + }, + quests: {} + }, + party: { + quest: { + progress: { + down: 0 + } + } + }, + preferences: { + autoEquip: true + }, + dailys: [], + todos: [], + rewards: [], + flags: {}, + achievements: { + ultimateGearSets: {} + }, + contributor: { + level: 2 + }, + _tmp: {} + }; + shared.wrap(user); + user.ops.reset(null, function() {}); + if (addTasks) { + _.each(['habit', 'todo', 'daily'], function(task) { + return user.ops.addTask({ + body: { + type: task, + id: shared.uuid() + } + }); + }); + } + return user; +}; + +rewrapUser = function(user) { + user._wrapped = false; + shared.wrap(user); + return user; +}; + +expectStrings = function(obj, paths) { + return _.each(paths, function(path) { + return expect(obj[path]).to.be.ok(); + }); +}; + +beforeAfter = function(options) { + var after, before, lastCron, ref, user; + if (options == null) { + options = {}; + } + user = newUser(); + ref = [user, _.cloneDeep(user)], before = ref[0], after = ref[1]; + rewrapUser(after); + if (options.dayStart) { + before.preferences.dayStart = after.preferences.dayStart = options.dayStart; + } + before.preferences.timezoneOffset = after.preferences.timezoneOffset = options.timezoneOffset || moment().zone(); + if (options.limitOne) { + before[options.limitOne + "s"] = [before[options.limitOne + "s"][0]]; + after[options.limitOne + "s"] = [after[options.limitOne + "s"][0]]; + } + if (options.daysAgo) { + lastCron = moment(options.now || +(new Date)).subtract({ + days: options.daysAgo + }); + } + if (options.daysAgo && options.cronAfterStart) { + lastCron.add({ + hours: options.dayStart, + minutes: 1 + }); + } + if (options.daysAgo) { + lastCron = +lastCron; + } + _.each([before, after], function(obj) { + if (options.daysAgo) { + return obj.lastCron = lastCron; + } + }); + return { + before: before, + after: after + }; +}; + +expectLostPoints = function(before, after, taskType) { + if (taskType === 'daily' || taskType === 'habit') { + expect(after.stats.hp).to.be.lessThan(before.stats.hp); + expect(after[taskType + "s"][0].history).to.have.length(1); + } else { + expect(after.history.todos).to.have.length(1); + } + expect(after).toHaveExp(0); + expect(after).toHaveGP(0); + return expect(after[taskType + "s"][0].value).to.be.lessThan(before[taskType + "s"][0].value); +}; + +expectGainedPoints = function(before, after, taskType) { + expect(after.stats.hp).to.be(50); + expect(after.stats.exp).to.be.greaterThan(before.stats.exp); + expect(after.stats.gp).to.be.greaterThan(before.stats.gp); + expect(after[taskType + "s"][0].value).to.be.greaterThan(before[taskType + "s"][0].value); + if (taskType === 'habit') { + return expect(after[taskType + "s"][0].history).to.have.length(1); + } +}; + +expectNoChange = function(before, after) { + return _.each($w('stats items gear dailys todos rewards preferences'), function(attr) { + return expect(after[attr]).to.eql(before[attr]); + }); +}; + +expectClosePoints = function(before, after, taskType) { + expect(Math.abs(after.stats.exp - before.stats.exp)).to.be.lessThan(0.0001); + expect(Math.abs(after.stats.gp - before.stats.gp)).to.be.lessThan(0.0001); + return expect(Math.abs(after[taskType + "s"][0].value - before[taskType + "s"][0].value)).to.be.lessThan(0.0001); +}; + +expectDayResetNoDamage = function(b, a) { + var after, before, ref; + ref = [_.cloneDeep(b), _.cloneDeep(a)], before = ref[0], after = ref[1]; + _.each(after.dailys, function(task, i) { + expect(task.completed).to.be(false); + expect(before.dailys[i].value).to.be(task.value); + expect(before.dailys[i].streak).to.be(task.streak); + return expect(task.history).to.have.length(1); + }); + _.each(after.todos, function(task, i) { + expect(task.completed).to.be(false); + return expect(before.todos[i].value).to.be.greaterThan(task.value); + }); + expect(after.history.todos).to.have.length(1); + _.each([before, after], function(obj) { + delete obj.stats.buffs; + return _.each($w('dailys todos history lastCron'), function(path) { + return delete obj[path]; + }); + }); + delete after._tmp; + return expectNoChange(before, after); +}; + +cycle = function(array) { + var n; + n = -1; + return function(seed) { + if (seed == null) { + seed = 0; + } + n++; + return array[n % array.length]; + }; +}; + +repeatWithoutLastWeekday = function() { + var repeat; + repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + }; + if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { + repeat.su = false; + } else { + repeat.s = false; + } + return { + repeat: repeat + }; +}; + +describe('User', function() { + it('sets correct user defaults', function() { + var base_gear, buffs, user; + user = newUser(); + base_gear = { + armor: 'armor_base_0', + weapon: 'weapon_base_0', + head: 'head_base_0', + shield: 'shield_base_0' + }; + buffs = { + per: 0, + int: 0, + con: 0, + str: 0, + stealth: 0, + streaks: false + }; + expect(user.stats).to.eql({ + str: 1, + con: 1, + per: 1, + int: 1, + hp: 50, + mp: 32, + lvl: 1, + exp: 0, + gp: 0, + "class": 'warrior', + buffs: buffs + }); + expect(user.items.gear).to.eql({ + equipped: base_gear, + costume: base_gear, + owned: { + weapon_warrior_0: true + } + }); + return expect(user.preferences).to.eql({ + autoEquip: true, + costume: false + }); + }); + it('calculates max MP', function() { + var user; + user = newUser(); + expect(user).toHaveMaxMP(32); + user.stats.int = 10; + expect(user).toHaveMaxMP(50); + user.stats.lvl = 5; + expect(user).toHaveMaxMP(54); + user.stats["class"] = 'wizard'; + user.items.gear.equipped.weapon = 'weapon_wizard_1'; + return expect(user).toHaveMaxMP(63); + }); + it('handles perfect days', function() { + var cron, user, yesterday; + user = newUser(); + user.dailys = []; + _.times(3, function() { + return user.dailys.push(shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days') + })); + }); + cron = function() { + user.lastCron = moment().subtract(1, 'days'); + return user.fns.cron(); + }; + cron(); + expect(user.stats.buffs.str).to.be(0); + expect(user.achievements.perfect).to.not.be.ok(); + user.dailys[0].completed = true; + cron(); + expect(user.stats.buffs.str).to.be(0); + expect(user.achievements.perfect).to.not.be.ok(); + _.each(user.dailys, function(d) { + return d.completed = true; + }); + cron(); + expect(user.stats.buffs.str).to.be(1); + expect(user.achievements.perfect).to.be(1); + yesterday = moment().subtract(1, 'days'); + user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; + _.each(user.dailys.slice(1), function(d) { + return d.completed = true; + }); + cron(); + expect(user.stats.buffs.str).to.be(1); + return expect(user.achievements.perfect).to.be(2); + }); + describe('Resting in the Inn', function() { + var cron, user; + user = null; + cron = null; + beforeEach(function() { + user = newUser(); + user.preferences.sleep = true; + cron = function() { + user.lastCron = moment().subtract(1, 'days'); + return user.fns.cron(); + }; + user.dailys = []; + return _.times(2, function() { + return user.dailys.push(shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days') + })); + }); + }); + it('remains in the inn on cron', function() { + cron(); + return expect(user.preferences.sleep).to.be(true); + }); + it('resets dailies', function() { + user.dailys[0].completed = true; + cron(); + return expect(user.dailys[0].completed).to.be(false); + }); + it('resets checklist on incomplete dailies', function() { + user.dailys[0].checklist = [ + { + "text": "1", + "id": "checklist-one", + "completed": true + }, { + "text": "2", + "id": "checklist-two", + "completed": true + }, { + "text": "3", + "id": "checklist-three", + "completed": false + } + ]; + cron(); + return _.each(user.dailys[0].checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + it('resets checklist on complete dailies', function() { + user.dailys[0].checklist = [ + { + "text": "1", + "id": "checklist-one", + "completed": true + }, { + "text": "2", + "id": "checklist-two", + "completed": true + }, { + "text": "3", + "id": "checklist-three", + "completed": false + } + ]; + user.dailys[0].completed = true; + cron(); + return _.each(user.dailys[0].checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + it('does not reset checklist on grey incomplete dailies', function() { + var yesterday; + yesterday = moment().subtract(1, 'days'); + user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; + user.dailys[0].checklist = [ + { + "text": "1", + "id": "checklist-one", + "completed": true + }, { + "text": "2", + "id": "checklist-two", + "completed": true + }, { + "text": "3", + "id": "checklist-three", + "completed": true + } + ]; + cron(); + return _.each(user.dailys[0].checklist, function(box) { + return expect(box.completed).to.be(true); + }); + }); + it('resets checklist on complete grey complete dailies', function() { + var yesterday; + yesterday = moment().subtract(1, 'days'); + user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false; + user.dailys[0].checklist = [ + { + "text": "1", + "id": "checklist-one", + "completed": true + }, { + "text": "2", + "id": "checklist-two", + "completed": true + }, { + "text": "3", + "id": "checklist-three", + "completed": true + } + ]; + user.dailys[0].completed = true; + cron(); + return _.each(user.dailys[0].checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + it('does not damage user for incomplete dailies', function() { + expect(user).toHaveHP(50); + user.dailys[0].completed = true; + user.dailys[1].completed = false; + cron(); + return expect(user).toHaveHP(50); + }); + it('gives credit for complete dailies', function() { + user.dailys[0].completed = true; + expect(user.dailys[0].history).to.be.empty; + cron(); + return expect(user.dailys[0].history).to.not.be.empty; + }); + return it('damages user for incomplete dailies after checkout', function() { + expect(user).toHaveHP(50); + user.dailys[0].completed = true; + user.dailys[1].completed = false; + user.preferences.sleep = false; + cron(); + return expect(user.stats.hp).to.be.lessThan(50); + }); + }); + describe('Death', function() { + var user; + user = void 0; + it('revives correctly', function() { + user = newUser(); + user.stats = { + gp: 10, + exp: 100, + lvl: 2, + hp: 0, + "class": 'warrior' + }; + user.ops.revive(); + expect(user).toHaveGP(0); + expect(user).toHaveExp(0); + expect(user).toHaveLevel(1); + expect(user).toHaveHP(50); + return expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: false + }); + }); + it("doesn't break unbreakables", function() { + var ce; + ce = shared.countExists; + user = newUser(); + user.items.gear.owned['shield_warrior_1'] = true; + user.items.gear.owned['shield_rogue_1'] = true; + user.items.gear.owned['head_special_nye'] = true; + expect(ce(user.items.gear.owned)).to.be(4); + user.stats.hp = 0; + user.ops.revive(); + expect(ce(user.items.gear.owned)).to.be(3); + user.stats.hp = 0; + user.ops.revive(); + expect(ce(user.items.gear.owned)).to.be(2); + user.stats.hp = 0; + user.ops.revive(); + expect(ce(user.items.gear.owned)).to.be(2); + return expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: false, + shield_warrior_1: false, + shield_rogue_1: true, + head_special_nye: true + }); + }); + return it("handles event items", function() { + shared.content.gear.flat.head_special_nye.event.start = '2012-01-01'; + shared.content.gear.flat.head_special_nye.event.end = '2012-02-01'; + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); + delete user.items.gear.owned['head_special_nye']; + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(false); + shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5, 'days'); + shared.content.gear.flat.head_special_nye.event.end = moment().add(5, 'days'); + return expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); + }); + }); + describe('Rebirth', function() { + var user; + user = void 0; + return it('removes correct gear', function() { + user = newUser(); + user.stats.lvl = 100; + user.items.gear.owned = { + "weapon_warrior_0": true, + "weapon_warrior_1": true, + "armor_warrior_1": false, + "armor_mystery_201402": true, + "back_mystery_201402": false, + "head_mystery_201402": true, + "weapon_armoire_basicCrossbow": true + }; + user.ops.rebirth(); + return expect(user.items.gear.owned).to.eql({ + "weapon_warrior_0": true, + "weapon_warrior_1": false, + "armor_warrior_1": false, + "armor_mystery_201402": true, + "back_mystery_201402": false, + "head_mystery_201402": true, + "weapon_armoire_basicCrossbow": false + }); + }); + }); + describe('store', function() { + it('buys a Quest scroll', function() { + var user; + user = newUser(); + user.stats.gp = 205; + user.ops.buyQuest({ + params: { + key: 'dilatoryDistress1' + } + }); + expect(user.items.quests).to.eql({ + dilatoryDistress1: 1 + }); + return expect(user).toHaveGP(5); + }); + it('does not buy Quests without enough Gold', function() { + var user; + user = newUser(); + user.stats.gp = 1; + user.ops.buyQuest({ + params: { + key: 'dilatoryDistress1' + } + }); + expect(user.items.quests).to.eql({}); + return expect(user).toHaveGP(1); + }); + it('does not buy nonexistent Quests', function() { + var user; + user = newUser(); + user.stats.gp = 9999; + user.ops.buyQuest({ + params: { + key: 'snarfblatter' + } + }); + expect(user.items.quests).to.eql({}); + return expect(user).toHaveGP(9999); + }); + return it('does not buy Gem-premium Quests', function() { + var user; + user = newUser(); + user.stats.gp = 9999; + user.ops.buyQuest({ + params: { + key: 'kraken' + } + }); + expect(user.items.quests).to.eql({}); + return expect(user).toHaveGP(9999); + }); + }); + describe('Gem purchases', function() { + it('does not purchase items without enough Gems', function() { + var user; + user = newUser(); + user.ops.purchase({ + params: { + type: 'eggs', + key: 'Cactus' + } + }); + user.ops.purchase({ + params: { + type: 'gear', + key: 'headAccessory_special_foxEars' + } + }); + user.ops.unlock({ + query: { + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars' + } + }); + expect(user.items.eggs).to.eql({}); + return expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true + }); + }); + it('purchases an egg', function() { + var user; + user = newUser(); + user.balance = 1; + user.ops.purchase({ + params: { + type: 'eggs', + key: 'Cactus' + } + }); + expect(user.items.eggs).to.eql({ + Cactus: 1 + }); + return expect(user.balance).to.eql(0.25); + }); + it('purchases fox ears', function() { + var user; + user = newUser(); + user.balance = 1; + user.ops.purchase({ + params: { + type: 'gear', + key: 'headAccessory_special_foxEars' + } + }); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + headAccessory_special_foxEars: true + }); + return expect(user.balance).to.eql(0.5); + }); + return it('unlocks all the animal ears at once', function() { + var user; + user = newUser(); + user.balance = 2; + user.ops.unlock({ + query: { + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars' + } + }); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + headAccessory_special_bearEars: true, + headAccessory_special_cactusEars: true, + headAccessory_special_foxEars: true, + headAccessory_special_lionEars: true, + headAccessory_special_pandaEars: true, + headAccessory_special_pigEars: true, + headAccessory_special_tigerEars: true, + headAccessory_special_wolfEars: true + }); + return expect(user.balance).to.eql(0.75); + }); + }); + describe('spells', function() { + return _.each(shared.content.spells, function(spellClass) { + return _.each(spellClass, function(spell) { + return it(spell.text + " has valid values", function() { + expect(spell.target).to.match(/^(task|self|party|user)$/); + expect(spell.mana).to.be.an('number'); + if (spell.lvl) { + expect(spell.lvl).to.be.an('number'); + expect(spell.lvl).to.be.above(0); + } + return expect(spell.cast).to.be.a('function'); + }); + }); + }); + }); + describe('drop system', function() { + var MAX_RANGE_FOR_EGG, MAX_RANGE_FOR_FOOD, MAX_RANGE_FOR_POTION, MIN_RANGE_FOR_EGG, MIN_RANGE_FOR_FOOD, MIN_RANGE_FOR_POTION, user; + user = null; + MIN_RANGE_FOR_POTION = 0; + MAX_RANGE_FOR_POTION = .3; + MIN_RANGE_FOR_EGG = .4; + MAX_RANGE_FOR_EGG = .6; + MIN_RANGE_FOR_FOOD = .7; + MAX_RANGE_FOR_FOOD = 1; + beforeEach(function() { + user = newUser(); + user.flags.dropsEnabled = true; + this.task_id = shared.uuid(); + return user.ops.addTask({ + body: { + type: 'daily', + id: this.task_id + } + }); + }); + it('drops a hatching potion', function() { + var j, random, ref, ref1, results; + results = []; + for (random = j = ref = MIN_RANGE_FOR_POTION, ref1 = MAX_RANGE_FOR_POTION; j <= ref1; random = j += .1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up' + } + }); + expect(user.items.eggs).to.be.empty; + expect(user.items.hatchingPotions).to.not.be.empty; + expect(user.items.food).to.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + it('drops a pet egg', function() { + var j, random, ref, ref1, results; + results = []; + for (random = j = ref = MIN_RANGE_FOR_EGG, ref1 = MAX_RANGE_FOR_EGG; j <= ref1; random = j += .1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up' + } + }); + expect(user.items.eggs).to.not.be.empty; + expect(user.items.hatchingPotions).to.be.empty; + expect(user.items.food).to.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + it('drops food', function() { + var j, random, ref, ref1, results; + results = []; + for (random = j = ref = MIN_RANGE_FOR_FOOD, ref1 = MAX_RANGE_FOR_FOOD; j <= ref1; random = j += .1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up' + } + }); + expect(user.items.eggs).to.be.empty; + expect(user.items.hatchingPotions).to.be.empty; + expect(user.items.food).to.not.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + return it('does not get a drop', function() { + sinon.stub(user.fns, 'predictableRandom').returns(0.5); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up' + } + }); + expect(user.items.eggs).to.eql({}); + expect(user.items.hatchingPotions).to.eql({}); + expect(user.items.food).to.eql({}); + return user.fns.predictableRandom.restore(); + }); + }); + describe('Quests', function() { + return _.each(shared.content.quests, function(quest) { + return it((quest.text()) + " has valid values", function() { + expect(quest.notes()).to.be.an('string'); + if (quest.completion) { + expect(quest.completion()).to.be.an('string'); + } + if (quest.previous) { + expect(quest.previous).to.be.an('string'); + } + if (quest.canBuy()) { + expect(quest.value).to.be.greaterThan(0); + } + expect(quest.drop.gp).to.not.be.lessThan(0); + expect(quest.drop.exp).to.not.be.lessThan(0); + expect(quest.category).to.match(/pet|unlockable|gold|world/); + if (quest.drop.items) { + expect(quest.drop.items).to.be.an(Array); + } + if (quest.boss) { + expect(quest.boss.name()).to.be.an('string'); + expect(quest.boss.hp).to.be.greaterThan(0); + return expect(quest.boss.str).to.be.greaterThan(0); + } else if (quest.collect) { + return _.each(quest.collect, function(collect) { + expect(collect.text()).to.be.an('string'); + return expect(collect.count).to.be.greaterThan(0); + }); + } + }); + }); + }); + describe('Achievements', function() { + _.each(shared.content.classes, function(klass) { + var user; + user = newUser(); + user.stats.gp = 10000; + _.each(shared.content.gearTypes, function(type) { + return _.each([1, 2, 3, 4, 5], function(i) { + return user.ops.buy({ + params: '#{type}_#{klass}_#{i}' + }); + }); + }); + it('does not get ultimateGear ' + klass, function() { + return expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok(); + }); + _.each(shared.content.gearTypes, function(type) { + return user.ops.buy({ + params: '#{type}_#{klass}_6' + }); + }); + return xit('gets ultimateGear ' + klass, function() { + return expect(user.achievements.ultimateGearSets[klass]).to.be.ok(); + }); + }); + return it('does not remove existing Ultimate Gear achievements', function() { + var user; + user = newUser(); + user.achievements.ultimateGearSets = { + 'healer': true, + 'wizard': true, + 'rogue': true, + 'warrior': true + }; + user.items.gear.owned.shield_warrior_5 = false; + user.items.gear.owned.weapon_rogue_6 = false; + user.ops.buy({ + params: 'shield_warrior_5' + }); + return expect(user.achievements.ultimateGearSets).to.eql({ + 'healer': true, + 'wizard': true, + 'rogue': true, + 'warrior': true + }); + }); + }); + return describe('unlocking features', function() { + it('unlocks drops at level 3', function() { + var user; + user = newUser(); + user.stats.lvl = 3; + user.fns.updateStats(user.stats); + return expect(user.flags.dropsEnabled).to.be.ok(); + }); + it('unlocks Rebirth at level 50', function() { + var user; + user = newUser(); + user.stats.lvl = 50; + user.fns.updateStats(user.stats); + return expect(user.flags.rebirthEnabled).to.be.ok(); + }); + return describe('level-awarded Quests', function() { + it('gets Attack of the Mundane at level 15', function() { + var user; + user = newUser(); + user.stats.lvl = 15; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.atom1).to.be.ok(); + return expect(user.items.quests.atom1).to.eql(1); + }); + it('gets Vice at level 30', function() { + var user; + user = newUser(); + user.stats.lvl = 30; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.vice1).to.be.ok(); + return expect(user.items.quests.vice1).to.eql(1); + }); + it('gets Golden Knight at level 40', function() { + var user; + user = newUser(); + user.stats.lvl = 40; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.goldenknight1).to.be.ok(); + return expect(user.items.quests.goldenknight1).to.eql(1); + }); + return it('gets Moonstone Chain at level 60', function() { + var user; + user = newUser(); + user.stats.lvl = 60; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.moonstone1).to.be.ok(); + return expect(user.items.quests.moonstone1).to.eql(1); + }); + }); + }); +}); + +describe('Simple Scoring', function() { + beforeEach(function() { + var ref; + return ref = beforeAfter(), this.before = ref.before, this.after = ref.after, ref; + }); + it('Habits : Up', function() { + this.after.ops.score({ + params: { + id: this.after.habits[0].id, + direction: 'down' + }, + query: { + times: 5 + } + }); + return expectLostPoints(this.before, this.after, 'habit'); + }); + it('Habits : Down', function() { + this.after.ops.score({ + params: { + id: this.after.habits[0].id, + direction: 'up' + }, + query: { + times: 5 + } + }); + return expectGainedPoints(this.before, this.after, 'habit'); + }); + it('Dailys : Up', function() { + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'up' + } + }); + return expectGainedPoints(this.before, this.after, 'daily'); + }); + it('Dailys : Up, Down', function() { + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'up' + } + }); + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'down' + } + }); + return expectClosePoints(this.before, this.after, 'daily'); + }); + it('Todos : Up', function() { + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'up' + } + }); + return expectGainedPoints(this.before, this.after, 'todo'); + }); + return it('Todos : Up, Down', function() { + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'up' + } + }); + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'down' + } + }); + return expectClosePoints(this.before, this.after, 'todo'); + }); +}); + +describe('Cron', function() { + it('computes shouldCron', function() { + var paths, user; + user = newUser(); + paths = {}; + user.fns.cron({ + paths: paths + }); + expect(user.lastCron).to.not.be.ok; + user.lastCron = +moment().subtract(1, 'days'); + paths = {}; + user.fns.cron({ + paths: paths + }); + return expect(user.lastCron).to.be.greaterThan(0); + }); + it('only dailies & todos are affected', function() { + var after, afterTasks, before, beforeTasks, ref; + ref = beforeAfter({ + daysAgo: 1 + }), before = ref.before, after = ref.after; + before.dailys = before.todos = after.dailys = after.todos = []; + after.fns.cron(); + before.stats.mp = after.stats.mp; + expect(after.lastCron).to.not.be(before.lastCron); + delete after.stats.buffs; + delete before.stats.buffs; + expect(before.stats).to.eql(after.stats); + beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards); + afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards); + return expect(beforeTasks).to.eql(afterTasks); + }); + describe('preening', function() { + beforeEach(function() { + return this.clock = sinon.useFakeTimers(Date.parse("2013-11-20"), "Date"); + }); + afterEach(function() { + return this.clock.restore(); + }); + return it('should preen user history', function() { + var after, before, history, ref; + ref = beforeAfter({ + daysAgo: 1 + }), before = ref.before, after = ref.after; + history = [ + { + date: '09/01/2012', + value: 0 + }, { + date: '10/01/2012', + value: 0 + }, { + date: '11/01/2012', + value: 2 + }, { + date: '12/01/2012', + value: 2 + }, { + date: '01/01/2013', + value: 1 + }, { + date: '01/15/2013', + value: 3 + }, { + date: '02/01/2013', + value: 2 + }, { + date: '02/15/2013', + value: 4 + }, { + date: '03/01/2013', + value: 3 + }, { + date: '03/15/2013', + value: 5 + }, { + date: '04/01/2013', + value: 4 + }, { + date: '04/15/2013', + value: 6 + }, { + date: '05/01/2013', + value: 5 + }, { + date: '05/15/2013', + value: 7 + }, { + date: '06/01/2013', + value: 6 + }, { + date: '06/15/2013', + value: 8 + }, { + date: '07/01/2013', + value: 7 + }, { + date: '07/15/2013', + value: 9 + }, { + date: '08/01/2013', + value: 8 + }, { + date: '08/15/2013', + value: 10 + }, { + date: '09/01/2013', + value: 9 + }, { + date: '09/15/2013', + value: 11 + }, { + date: '010/01/2013', + value: 10 + }, { + date: '010/15/2013', + value: 12 + }, { + date: '011/01/2013', + value: 12 + }, { + date: '011/02/2013', + value: 13 + }, { + date: '011/03/2013', + value: 14 + }, { + date: '011/04/2013', + value: 15 + } + ]; + after.history = { + exp: _.cloneDeep(history), + todos: _.cloneDeep(history) + }; + after.habits[0].history = _.cloneDeep(history); + after.fns.cron(); + after.history.exp.pop(); + after.history.todos.pop(); + return _.each([after.history.exp, after.history.todos, after.habits[0].history], function(arr) { + return expect(_.map(arr, function(x) { + return x.value; + })).to.eql([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + }); + }); + }); + describe('Todos', function() { + it('1 day missed', function() { + var after, before, ref; + ref = beforeAfter({ + daysAgo: 1 + }), before = ref.before, after = ref.after; + before.dailys = after.dailys = []; + after.fns.cron(); + expect(after).toHaveHP(50); + expect(after).toHaveExp(0); + expect(after).toHaveGP(0); + expect(before.todos[0].value).to.be(0); + expect(after.todos[0].value).to.be(-1); + return expect(after.history.todos).to.have.length(1); + }); + return it('2 days missed', function() { + var after, before, ref; + ref = beforeAfter({ + daysAgo: 2 + }), before = ref.before, after = ref.after; + before.dailys = after.dailys = []; + after.fns.cron(); + expect(before.todos[0].value).to.be(0); + return expect(after.todos[0].value).to.be(-1); + }); + }); + describe('cron day calculations', function() { + var dayStart, fstr; + dayStart = 4; + fstr = "YYYY-MM-DD HH:mm:ss"; + it('startOfDay before dayStart', function() { + var start; + start = shared.startOfDay({ + now: moment('2014-10-09 02:30:00'), + dayStart: dayStart + }); + return expect(start.format(fstr)).to.eql('2014-10-08 04:00:00'); + }); + it('startOfDay after dayStart', function() { + var start; + start = shared.startOfDay({ + now: moment('2014-10-09 05:30:00'), + dayStart: dayStart + }); + return expect(start.format(fstr)).to.eql('2014-10-09 04:00:00'); + }); + it('daysSince cron before, now after', function() { + var days, lastCron; + lastCron = moment('2014-10-09 02:30:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-09 11:30:00'), + dayStart: dayStart + }); + return expect(days).to.eql(1); + }); + it('daysSince cron before, now before', function() { + var days, lastCron; + lastCron = moment('2014-10-09 02:30:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-09 03:30:00'), + dayStart: dayStart + }); + return expect(days).to.eql(0); + }); + it('daysSince cron after, now after', function() { + var days, lastCron; + lastCron = moment('2014-10-09 05:30:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-09 06:30:00'), + dayStart: dayStart + }); + return expect(days).to.eql(0); + }); + it('daysSince cron after, now tomorrow before', function() { + var days, lastCron; + lastCron = moment('2014-10-09 12:30:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-10 01:30:00'), + dayStart: dayStart + }); + return expect(days).to.eql(0); + }); + it('daysSince cron after, now tomorrow after', function() { + var days, lastCron; + lastCron = moment('2014-10-09 12:30:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-10 10:30:00'), + dayStart: dayStart + }); + return expect(days).to.eql(1); + }); + return xit('daysSince, last cron before new dayStart', function() { + var days, lastCron; + lastCron = moment('2014-10-09 01:00:00'); + days = shared.daysSince(lastCron, { + now: moment('2014-10-09 05:00:00'), + dayStart: dayStart + }); + return expect(days).to.eql(0); + }); + }); + return describe('dailies', function() { + return describe('new day', function() { + + /* + This section runs through a "cron matrix" of all permutations (that I can easily account for). It sets + task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron, + and so on - testing each possible outcome along the way + */ + var cronMatrix, recurseCronMatrix, runCron; + runCron = function(options) { + return _.each([480, 240, 0, -120], function(timezoneOffset) { + var after, before, now, ref; + now = shared.startOfWeek({ + timezoneOffset: timezoneOffset + }).add(options.currentHour || 0, 'hours'); + ref = beforeAfter({ + now: now, + timezoneOffset: timezoneOffset, + daysAgo: 1, + cronAfterStart: options.cronAfterStart || true, + dayStart: options.dayStart || 0, + limitOne: 'daily' + }), before = ref.before, after = ref.after; + if (options.repeat) { + before.dailys[0].repeat = after.dailys[0].repeat = options.repeat; + } + before.dailys[0].streak = after.dailys[0].streak = 10; + if (options.checked) { + before.dailys[0].completed = after.dailys[0].completed = true; + } + before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days'); + if (options.shouldDo) { + expect(shared.shouldDo(now.toDate(), after.dailys[0], { + timezoneOffset: timezoneOffset, + dayStart: options.dayStart, + now: now + })).to.be.ok(); + } + after.fns.cron({ + now: now + }); + before.stats.mp = after.stats.mp; + switch (options.expect) { + case 'losePoints': + expectLostPoints(before, after, 'daily'); + break; + case 'noChange': + expectNoChange(before, after); + break; + case 'noDamage': + expectDayResetNoDamage(before, after); + } + return { + before: before, + after: after + }; + }); + }; + cronMatrix = { + steps: { + 'due yesterday': { + defaults: { + daysAgo: 1, + cronAfterStart: true, + limitOne: 'daily' + }, + steps: { + '(simple)': { + expect: 'losePoints' + }, + 'due today': { + defaults: { + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + } + }, + steps: { + 'pre-dayStart': { + defaults: { + currentHour: 3, + dayStart: 4, + shouldDo: true + }, + steps: { + 'checked': { + checked: true, + expect: 'noChange' + }, + 'un-checked': { + checked: false, + expect: 'noChange' + } + } + }, + 'post-dayStart': { + defaults: { + currentHour: 5, + dayStart: 4, + shouldDo: true + }, + steps: { + 'checked': { + checked: true, + expect: 'noDamage' + }, + 'unchecked': { + checked: false, + expect: 'losePoints' + } + } + } + } + }, + 'NOT due today': { + defaults: { + repeat: { + su: true, + m: false, + t: true, + w: true, + th: true, + f: true, + s: true + } + }, + steps: { + 'pre-dayStart': { + defaults: { + currentHour: 3, + dayStart: 4, + shouldDo: true + }, + steps: { + 'checked': { + checked: true, + expect: 'noChange' + }, + 'un-checked': { + checked: false, + expect: 'noChange' + } + } + }, + 'post-dayStart': { + defaults: { + currentHour: 5, + dayStart: 4, + shouldDo: false + }, + steps: { + 'checked': { + checked: true, + expect: 'noDamage' + }, + 'unchecked': { + checked: false, + expect: 'losePoints' + } + } + } + } + } + } + }, + 'not due yesterday': { + defaults: repeatWithoutLastWeekday(), + steps: { + '(simple)': { + expect: 'noDamage' + }, + 'post-dayStart': { + currentHour: 5, + dayStart: 4, + expect: 'noDamage' + }, + 'pre-dayStart': { + currentHour: 3, + dayStart: 4, + expect: 'noChange' + } + } + } + } + }; + recurseCronMatrix = function(obj, options) { + if (options == null) { + options = {}; + } + if (obj.steps) { + return _.each(obj.steps, function(step, text) { + var o; + o = _.cloneDeep(options); + if (o.text == null) { + o.text = ''; + } + o.text += " " + text + " "; + return recurseCronMatrix(step, _.defaults(o, obj.defaults)); + }); + } else { + return it("" + options.text, function() { + return runCron(_.defaults(obj, options)); + }); + } + }; + return recurseCronMatrix(cronMatrix); + }); + }); +}); + +describe('Helper', function() { + it('calculates gold coins', function() { + expect(shared.gold(10)).to.eql(10); + expect(shared.gold(1.957)).to.eql(1); + return expect(shared.gold()).to.eql(0); + }); + it('calculates silver coins', function() { + expect(shared.silver(10)).to.eql(0); + expect(shared.silver(1.957)).to.eql(95); + expect(shared.silver(0.01)).to.eql("01"); + return expect(shared.silver()).to.eql("00"); + }); + it('calculates experience to next level', function() { + expect(shared.tnl(1)).to.eql(150); + expect(shared.tnl(2)).to.eql(160); + expect(shared.tnl(10)).to.eql(260); + return expect(shared.tnl(99)).to.eql(3580); + }); + return it('calculates the start of the day', function() { + var fstr, today, zone; + fstr = 'YYYY-MM-DD HH:mm:ss'; + today = '2013-01-01 00:00:00'; + zone = moment(today).zone(); + expect(shared.startOfDay({ + now: new Date(2013, 0, 1, 0) + }, { + timezoneOffset: zone + }).format(fstr)).to.eql(today); + expect(shared.startOfDay({ + now: new Date(2013, 0, 1, 5) + }, { + timezoneOffset: zone + }).format(fstr)).to.eql(today); + return expect(shared.startOfDay({ + now: new Date(2013, 0, 1, 23, 59, 59), + timezoneOffset: zone + }).format(fstr)).to.eql(today); + }); +}); diff --git a/test/common/dailies.coffee b/test/common/dailies.coffee deleted file mode 100644 index 71729ff77c..0000000000 --- a/test/common/dailies.coffee +++ /dev/null @@ -1,418 +0,0 @@ -_ = require 'lodash' -expect = require 'expect.js' -sinon = require 'sinon' -moment = require 'moment' -shared = require '../../common/script/index.js' -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations - -repeatWithoutLastWeekday = ()-> - repeat = {su:true,m:true,t:true,w:true,th:true,f:true,s:true} - if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday - repeat.su = false - else - repeat.s = false - {repeat: repeat} - -### Helper Functions #### -# @TODO: Refactor into helper file -newUser = (addTasks=true)-> - buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false} - user = - auth: - timestamps: {} - stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs} - items: - lastDrop: - count: 0 - hatchingPotions: {} - eggs: {} - food: {} - gear: - equipped: {} - costume: {} - party: - quest: - progress: - down: 0 - preferences: {} - dailys: [] - todos: [] - rewards: [] - flags: {} - achievements: {} - contributor: - level: 2 - shared.wrap(user) - user.ops.reset(null, ->) - if addTasks - _.each ['habit', 'todo', 'daily'], (task)-> - user.ops.addTask {body: {type: task, id: shared.uuid()}} - user - -cron = (usr, missedDays=1) -> - usr.lastCron = moment().subtract(missedDays,'days') - usr.fns.cron() - -describe 'daily/weekly that repeats everyday (default)', -> - user = null - daily = null - weekly = null - - describe 'when startDate is in the future', -> - beforeEach -> - user = newUser() - user.dailys = [ - shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'daily'}) - shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'weekly', repeat: {su:true,m:true,t:true,w:true,th:true,f:true,s:true}}) - ] - daily = user.dailys[0] - weekly = user.dailys[1] - - it 'does not damage user for not completing it', -> - cron(user) - expect(user.stats.hp).to.be 50 - - it 'does not change value on cron if daily is incomplete', -> - cron(user) - expect(daily.value).to.be 0 - expect(weekly.value).to.be 0 - - it 'does not reset checklists if daily is not marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - weekly.checklist = checklist - cron(user) - - expect(daily.checklist[0].completed).to.be true - expect(daily.checklist[1].completed).to.be true - expect(daily.checklist[2].completed).to.be false - - expect(weekly.checklist[0].completed).to.be true - expect(weekly.checklist[1].completed).to.be true - expect(weekly.checklist[2].completed).to.be false - - it 'resets checklists if daily is marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - weekly.checklist = checklist - daily.completed = true - weekly.completed = true - cron(user) - - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - _.each weekly.checklist, (box)-> - expect(box.completed).to.be false - - it 'is due on startDate', -> - daily_due_today = shared.shouldDo moment(), daily - daily_due_on_start_date = shared.shouldDo moment().add(7, 'days'), daily - - expect(daily_due_today).to.be false - expect(daily_due_on_start_date).to.be true - - weekly_due_today = shared.shouldDo moment(), weekly - weekly_due_on_start_date = shared.shouldDo moment().add(7, 'days'), weekly - - expect(weekly_due_today).to.be false - expect(weekly_due_on_start_date).to.be true - - describe 'when startDate is in the past', -> - beforeEach -> - user = newUser() - user.dailys = [ - shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'daily'}) - shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'weekly'}) - ] - daily = user.dailys[0] - weekly = user.dailys[1] - - it 'does damage user for not completing it', -> - cron(user) - expect(user.stats.hp).to.be.lessThan 50 - - it 'decreases value on cron if daily is incomplete', -> - cron(user, 1) - expect(daily.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', -> - cron(user, 7) - expect(daily.value).to.be -1 - expect(weekly.value).to.be -1 - - it 'resets checklists if daily is not marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - weekly.checklist = checklist - cron(user) - - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - _.each weekly.checklist, (box)-> - expect(box.completed).to.be false - - it 'resets checklists if daily is marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - daily.completed = true - weekly.checklist = checklist - weekly.completed = true - cron(user) - - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - _.each weekly.checklist, (box)-> - expect(box.completed).to.be false - - describe 'when startDate is today', -> - beforeEach -> - user = newUser() - user.dailys = [ - # Must set start date to yesterday, because cron mock sets last cron to yesterday - shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'daily'}) - shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'weekly'}) - ] - daily = user.dailys[0] - weekly = user.dailys[1] - - it 'does damage user for not completing it', -> - cron(user) - expect(user.stats.hp).to.be.lessThan 50 - - it 'decreases value on cron if daily is incomplete', -> - cron(user) - expect(daily.value).to.be.lessThan 0 - expect(weekly.value).to.be.lessThan 0 - - it 'resets checklists if daily is not marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - weekly.checklist = checklist - cron(user) - - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - _.each weekly.checklist, (box)-> - expect(box.completed).to.be false - - it 'resets checklists if daily is marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - }, - { - 'text' : '2', - 'id' : 'checklist-two', - 'completed' : true - }, - { - 'text' : '3', - 'id' : 'checklist-three', - 'completed' : false - } - ] - daily.checklist = checklist - daily.completed = true - weekly.checklist = checklist - weekly.completed = true - cron(user) - - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - _.each weekly.checklist, (box)-> - expect(box.completed).to.be false - -describe 'daily that repeats every x days', -> - user = null - daily = null - - beforeEach -> - user = newUser() - user.dailys = [ shared.taskDefaults({type:'daily', startDate: moment(), frequency: 'daily'}) ] - daily = user.dailys[0] - - _.times 11, (due) -> - - it 'where x equals ' + due, -> - daily.everyX = due - - _.times 30, (day) -> - isDue = shared.shouldDo moment().add(day, 'days'), daily - expect(isDue).to.be true if day % due == 0 - expect(isDue).to.be false if day % due != 0 - -describe 'daily that repeats every X days when multiple days are missed', -> - everyX = 3 - startDateDaysAgo = everyX * 3 - user = null - daily = null - - describe 'including missing a due date', -> - missedDays = everyX * 2 + 1 - - beforeEach -> - user = newUser() - user.dailys = [ - shared.taskDefaults({type:'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', everyX: everyX}) - ] - daily = user.dailys[0] - - it 'decreases value on cron once only if daily is incomplete', -> - cron(user, missedDays) - expect(daily.value).to.be -1 - - it 'resets checklists if daily is incomplete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - } - ] - daily.checklist = checklist - cron(user, missedDays) - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - it 'resets checklists if daily is marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - } - ] - daily.checklist = checklist - daily.completed = true - cron(user, missedDays) - _.each daily.checklist, (box)-> - expect(box.completed).to.be false - - describe 'but not missing a due date', -> - missedDays = everyX - 1 - - beforeEach -> - user = newUser() - user.dailys = [ - shared.taskDefaults({type:'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', everyX: everyX}) - ] - daily = user.dailys[0] - - it 'does not decrease value on cron', -> - cron(user, missedDays) - expect(daily.value).to.be 0 - - it 'does not reset checklists if daily is incomplete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - } - ] - daily.checklist = checklist - cron(user, missedDays) - _.each daily.checklist, (box)-> - expect(box.completed).to.be true - - it 'resets checklists if daily is marked as complete', -> - checklist = [ - { - 'text' : '1', - 'id' : 'checklist-one', - 'completed' : true - } - ] - daily.checklist = checklist - daily.completed = true - cron(user, missedDays) - _.each daily.checklist, (box)-> - expect(box.completed).to.be false diff --git a/test/common/dailies.js b/test/common/dailies.js new file mode 100644 index 0000000000..dcf9f9db32 --- /dev/null +++ b/test/common/dailies.js @@ -0,0 +1,541 @@ +(function() { + var _, cron, expect, moment, newUser, repeatWithoutLastWeekday, shared, sinon; + + _ = require('lodash'); + + expect = require('expect.js'); + + sinon = require('sinon'); + + moment = require('moment'); + + shared = require('../../common/script/index.js'); + + shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; + + repeatWithoutLastWeekday = function() { + var repeat; + repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + }; + if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { + repeat.su = false; + } else { + repeat.s = false; + } + return { + repeat: repeat + }; + }; + + + /* Helper Functions */ + + newUser = function(addTasks) { + var buffs, user; + if (addTasks == null) { + addTasks = true; + } + buffs = { + per: 0, + int: 0, + con: 0, + str: 0, + stealth: 0, + streaks: false + }; + user = { + auth: { + timestamps: {} + }, + stats: { + str: 1, + con: 1, + per: 1, + int: 1, + mp: 32, + "class": 'warrior', + buffs: buffs + }, + items: { + lastDrop: { + count: 0 + }, + hatchingPotions: {}, + eggs: {}, + food: {}, + gear: { + equipped: {}, + costume: {} + } + }, + party: { + quest: { + progress: { + down: 0 + } + } + }, + preferences: {}, + dailys: [], + todos: [], + rewards: [], + flags: {}, + achievements: {}, + contributor: { + level: 2 + } + }; + shared.wrap(user); + user.ops.reset(null, function() {}); + if (addTasks) { + _.each(['habit', 'todo', 'daily'], function(task) { + return user.ops.addTask({ + body: { + type: task, + id: shared.uuid() + } + }); + }); + } + return user; + }; + + cron = function(usr, missedDays) { + if (missedDays == null) { + missedDays = 1; + } + usr.lastCron = moment().subtract(missedDays, 'days'); + return 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() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().add(7, 'days'), + frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().add(7, 'days'), + frequency: 'weekly', + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + } + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does not damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be(50); + }); + it('does not change value on cron if daily is incomplete', function() { + cron(user); + expect(daily.value).to.be(0); + return expect(weekly.value).to.be(0); + }); + it('does not reset checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + expect(daily.checklist[0].completed).to.be(true); + expect(daily.checklist[1].completed).to.be(true); + expect(daily.checklist[2].completed).to.be(false); + expect(weekly.checklist[0].completed).to.be(true); + expect(weekly.checklist[1].completed).to.be(true); + return expect(weekly.checklist[2].completed).to.be(false); + }); + it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + daily.completed = true; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return 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); + expect(daily_due_today).to.be(false); + expect(daily_due_on_start_date).to.be(true); + weekly_due_today = shared.shouldDo(moment(), weekly); + weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); + expect(weekly_due_today).to.be(false); + return expect(weekly_due_on_start_date).to.be(true); + }); + }); + describe('when startDate is in the past', function() { + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'weekly' + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be.lessThan(50); + }); + it('decreases value on cron if daily is incomplete', function() { + cron(user, 1); + expect(daily.value).to.be(-1); + return expect(weekly.value).to.be(-1); + }); + it('decreases value on cron once only if daily is incomplete and multiple days are missed', function() { + cron(user, 7); + expect(daily.value).to.be(-1); + return expect(weekly.value).to.be(-1); + }); + it('resets checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + }); + return describe('when startDate is today', function() { + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'weekly' + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be.lessThan(50); + }); + it('decreases value on cron if daily is incomplete', function() { + cron(user); + expect(daily.value).to.be.lessThan(0); + return expect(weekly.value).to.be.lessThan(0); + }); + it('resets checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + }); + }); + + describe('daily that repeats every x days', function() { + var daily, user; + user = null; + daily = null; + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment(), + frequency: 'daily' + }) + ]; + return daily = user.dailys[0]; + }); + return _.times(11, function(due) { + return it('where x equals ' + due, function() { + daily.everyX = due; + return _.times(30, function(day) { + var 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); + } + }); + }); + }); + }); + + 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() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX: everyX + }) + ]; + return daily = user.dailys[0]; + }); + it('decreases value on cron once only if daily is incomplete', function() { + cron(user, missedDays); + return expect(daily.value).to.be(-1); + }); + it('resets checklists if daily is incomplete', function() { + var checklist; + checklist = [ + { + '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); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + '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); + }); + }); + }); + return describe('but not missing a due date', function() { + var missedDays; + missedDays = everyX - 1; + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX: everyX + }) + ]; + return daily = user.dailys[0]; + }); + it('does not decrease value on cron', function() { + cron(user, missedDays); + return expect(daily.value).to.be(0); + }); + it('does not reset checklists if daily is incomplete', function() { + var checklist; + checklist = [ + { + '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); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + '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); + }); + }); + }); + }); + +}).call(this); diff --git a/test/common/mocha.opts b/test/common/mocha.opts index 84bfdce89a..a3c3e0c6dc 100644 --- a/test/common/mocha.opts +++ b/test/common/mocha.opts @@ -5,4 +5,5 @@ --growl --debug --compilers coffee:coffee-script +--compilers js:babel/register --globals io diff --git a/test/common/simulations/autoAllocate.coffee b/test/common/simulations/autoAllocate.coffee deleted file mode 100644 index fec7ccbb31..0000000000 --- a/test/common/simulations/autoAllocate.coffee +++ /dev/null @@ -1,82 +0,0 @@ -### -1) clone the repo -2) npm install -3) coffee ./tests/math_samples.coffee - -Current results at https://gist.github.com/lefnire/8049676 -### - -shared = require '../../../common/script/index.js' -_ = require 'lodash' -$w = (s)->s.split(' ') - -id = shared.uuid() -user = - stats: - class: 'warrior' - lvl:1, hp:50, gp:0, exp:10 - per:0, int:0, con:0, str:0 - buffs: {per:0, int:0, con:0, str:0} - training: {int:0,con:0,per:0,str:0} - preferences: automaticAllocation: false - party: quest: key:'evilsanta', progress: {up:0,down:0} - achievements: {} - items: - eggs: {} - hatchingPotions: {} - food: {} - gear: - equipped: - weapon: 'weapon_warrior_4' - armor: 'armor_warrior_4' - shield: 'shield_warrior_4' - head: 'head_warrior_4' - habits: [ - # we're gonna change this habit's attribute to mess with taskbased allo. Add the others to make sure our _.reduce is legit - {id:'a',value:1,type:'habit',attribute:'str'} - ] - dailys: [ - {id:'b',value:1,type:'daily',attribute:'str'} - ] - todos: [ - {id:'c',value:1,type:'todo',attribute:'con'} - {id:'d',value:1,type:'todo',attribute:'per'} - {id:'e',value:1,type:'todo',attribute:'int'} - ] - rewards: [] - -modes = - flat: _.cloneDeep user - classbased_warrior: _.cloneDeep user - classbased_rogue: _.cloneDeep user - classbased_wizard: _.cloneDeep user - classbased_healer: _.cloneDeep user - taskbased: _.cloneDeep user - -modes.classbased_warrior.stats.class = 'warrior' -modes.classbased_rogue.stats.class = 'rogue' -modes.classbased_wizard.stats.class = 'wizard' -modes.classbased_healer.stats.class = 'healer' - -_.each $w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), (mode) -> - _.merge modes[mode].preferences, - automaticAllocation: true - allocationMode: if mode.indexOf('classbased') is 0 then 'classbased' else mode - shared.wrap(modes[mode]) - -console.log "\n\n================================================" -console.log "New Simulation" -console.log "================================================\n\n" - - -_.times [20], (lvl) -> - console.log ("[lvl #{lvl}]\n--------------\n") - _.each $w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), (mode) -> - u = modes[mode] #local var - u.stats.exp = shared.tnl(lvl)+1 # level up - _.merge u.stats, {per:0,con:0,int:0,str:0} if mode is 'taskbased' # if task-based, clear stat so we can see clearly which stat got +1 - u.habits[0].attribute = u.fns.randomVal({str:'str',int:'int',per:'per',con:'con'}) - u.ops.score {params:{id:u.habits[0].id},direction:'up'} - u.fns.updateStats(u.stats) # trigger stats update - str = mode + (if mode is 'taskbased' then " (#{u.habits[0].attribute})" else "") - console.log str, _.pick(u.stats, $w 'per int con str') diff --git a/test/common/simulations/autoAllocate.js b/test/common/simulations/autoAllocate.js new file mode 100644 index 0000000000..3621dbec3b --- /dev/null +++ b/test/common/simulations/autoAllocate.js @@ -0,0 +1,164 @@ +(function() { + var $w, _, id, modes, shared, user; + + shared = require('../../../common/script/index.js'); + + _ = require('lodash'); + + $w = function(s) { + return s.split(' '); + }; + + id = shared.uuid(); + + user = { + stats: { + "class": 'warrior', + lvl: 1, + hp: 50, + gp: 0, + exp: 10, + per: 0, + int: 0, + con: 0, + str: 0, + buffs: { + per: 0, + int: 0, + con: 0, + str: 0 + }, + training: { + int: 0, + con: 0, + per: 0, + str: 0 + } + }, + preferences: { + automaticAllocation: false + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + achievements: {}, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + { + id: 'a', + value: 1, + type: 'habit', + attribute: 'str' + } + ], + dailys: [ + { + id: 'b', + value: 1, + type: 'daily', + attribute: 'str' + } + ], + todos: [ + { + id: 'c', + value: 1, + type: 'todo', + attribute: 'con' + }, { + id: 'd', + value: 1, + type: 'todo', + attribute: 'per' + }, { + id: 'e', + value: 1, + type: 'todo', + attribute: 'int' + } + ], + rewards: [] + }; + + modes = { + flat: _.cloneDeep(user), + classbased_warrior: _.cloneDeep(user), + classbased_rogue: _.cloneDeep(user), + classbased_wizard: _.cloneDeep(user), + classbased_healer: _.cloneDeep(user), + taskbased: _.cloneDeep(user) + }; + + modes.classbased_warrior.stats["class"] = 'warrior'; + + modes.classbased_rogue.stats["class"] = 'rogue'; + + modes.classbased_wizard.stats["class"] = 'wizard'; + + modes.classbased_healer.stats["class"] = 'healer'; + + _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + _.merge(modes[mode].preferences, { + automaticAllocation: true, + allocationMode: mode.indexOf('classbased') === 0 ? 'classbased' : mode + }); + return shared.wrap(modes[mode]); + }); + + console.log("\n\n================================================"); + + console.log("New Simulation"); + + console.log("================================================\n\n"); + + _.times([20], function(lvl) { + console.log("[lvl " + lvl + "]\n--------------\n"); + return _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + var str, u; + u = modes[mode]; + u.stats.exp = shared.tnl(lvl) + 1; + if (mode === 'taskbased') { + _.merge(u.stats, { + per: 0, + con: 0, + int: 0, + str: 0 + }); + } + u.habits[0].attribute = u.fns.randomVal({ + str: 'str', + int: 'int', + per: 'per', + con: 'con' + }); + u.ops.score({ + params: { + id: u.habits[0].id + }, + direction: 'up' + }); + u.fns.updateStats(u.stats); + str = mode + (mode === 'taskbased' ? " (" + u.habits[0].attribute + ")" : ""); + return console.log(str, _.pick(u.stats, $w('per int con str'))); + }); + }); + +}).call(this); diff --git a/test/common/simulations/passive_active_attrs.coffee b/test/common/simulations/passive_active_attrs.coffee deleted file mode 100644 index 0788488ec2..0000000000 --- a/test/common/simulations/passive_active_attrs.coffee +++ /dev/null @@ -1,180 +0,0 @@ -### -1) clone the repo -2) npm install -3) coffee ./tests/math_samples.coffee - -Current results at https://gist.github.com/lefnire/8049676 -### - -shared = require '../../../common/script/index.js' -_ = require 'lodash' - -id = shared.uuid() -user = - stats: {class: 'warrior', buffs: {per:0,int:0,con:0,str:0}} - party: quest: key:'evilsanta', progress: {up:0,down:0} - preferences: automaticAllocation:false - achievements:{} - flags: levelDrops: {} - items: - eggs: {} - hatchingPotions: {} - food: {} - quests:{} - gear: - equipped: - weapon: 'weapon_warrior_4' - armor: 'armor_warrior_4' - shield: 'shield_warrior_4' - head: 'head_warrior_4' - habits: [ - shared.taskDefaults({id, value: 0}) - ] - dailys: [{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",},{"text" : "1",}], - todos: [] - rewards: [] - -shared.wrap(user) -s = user.stats -task = user.tasks[id] -party = [user] - -console.log "\n\n================================================" -console.log "New Simulation" -console.log "================================================\n\n" - -clearUser = (lvl=1) -> - _.merge user.stats, {exp:0, gp:0, hp:50, lvl:lvl, str:lvl*1.5, con:lvl*1.5, per:lvl*1.5, int:lvl*1.5, mp: 100} - _.merge s.buffs, {str:0,con:0,int:0,per:0} - _.merge user.party.quest.progress, {up:0,down:0} - user.items.lastDrop = {count:0} - -_.each [1,25,50,75,100], (lvl) -> - console.log "[LEVEL #{lvl}] (#{lvl*2} points total in every attr)\n\n" - _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> - console.log "[task.value = #{taskVal} (#{color})]" - console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" - _.each ['up','down'], (direction) -> - clearUser(lvl) - b4 = {hp:s.hp, taskVal} - task.value = taskVal - task.type = 'daily' if direction is 'up' - delta = user.ops.score params:{id, direction} - console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" - - str = '- [Wizard]' - - task.value = taskVal;clearUser(lvl) - b4 = {taskVal} - shared.content.spells.wizard.fireball.cast(user,task) - str += "\tfireball(task.valΔ:#{(task.value-taskVal).toFixed(1)} exp:#{s.exp.toFixed(1)} bossHit:#{user.party.quest.progress.up.toFixed(2)})" - - task.value = taskVal;clearUser(lvl) - _party = [user, {stats:{mp:0}}] - shared.content.spells.wizard.mpheal.cast(user,_party) - str += "\t| mpheal(mp:#{_party[1].stats.mp})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.wizard.earth.cast(user,party) - str += "\t\t\t\t| earth(buffs.int:#{s.buffs.int})" - s.buffs.int = 0 - - task.value = taskVal;clearUser(lvl) - shared.content.spells.wizard.frost.cast(user,{}) - str += "\t\t\t| frost(N/A)" - - console.log str - str = '- [Warrior]' - - task.value = taskVal;clearUser(lvl) - shared.content.spells.warrior.smash.cast(user,task) - b4 = {taskVal} - str += "\tsmash(task.valΔ:#{(task.value-taskVal).toFixed(1)})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.warrior.defensiveStance.cast(user,{}) - str += "\t\t| defensiveStance(buffs.con:#{s.buffs.con})" - s.buffs.con = 0 - - task.value = taskVal;clearUser(lvl) - shared.content.spells.warrior.valorousPresence.cast(user,party) - str += "\t\t\t| valorousPresence(buffs.str:#{s.buffs.str})" - s.buffs.str = 0 - - task.value = taskVal;clearUser(lvl) - shared.content.spells.warrior.intimidate.cast(user,party) - str += "\t\t| intimidate(buffs.con:#{s.buffs.con})" - s.buffs.con = 0 - - console.log str - str = '- [Rogue]' - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.pickPocket.cast(user,task) - str += "\tpickPocket(gp:#{s.gp.toFixed(1)})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.backStab.cast(user,task) - b4 = {taskVal} - str += "\t\t| backStab(task.valΔ:#{(task.value-b4.taskVal).toFixed(1)} exp:#{s.exp.toFixed(1)} gp:#{s.gp.toFixed(1)})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.toolsOfTrade.cast(user,party) - str += "\t| toolsOfTrade(buffs.per:#{s.buffs.per})" - s.buffs.per = 0 - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.stealth.cast(user,{}) - str += "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" - user.stats.buffs.stealth = 0 - - console.log str - str = '- [Healer]' - - task.value = taskVal;clearUser(lvl) - s.hp=0 - shared.content.spells.healer.heal.cast(user,{}) - str += "\theal(hp:#{s.hp.toFixed(1)})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.healer.brightness.cast(user,{}) - b4 = {taskVal} - str += "\t\t\t| brightness(task.valΔ:#{(task.value-b4.taskVal).toFixed(1)})" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.healer.protectAura.cast(user,party) - str += "\t\t\t| protectAura(buffs.con:#{s.buffs.con})" - s.buffs.con = 0 - - task.value = taskVal;clearUser(lvl) - s.hp=0 - shared.content.spells.healer.heallAll.cast(user,party) - str += "\t\t| heallAll(hp:#{s.hp.toFixed(1)})" - - console.log str - console.log '\n' - - - console.log '------------------------------------------------------------' - -### -_.each [1,25,50,75,100,125], (lvl) -> - console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" - _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> - console.log "[task.value = #{taskVal} (#{color})]" - console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" - _.each ['up','down'], (direction) -> - clearUser(lvl) - b4 = {hp:s.hp, taskVal} - task.value = taskVal - task.type = 'daily' if direction is 'up' - delta = user.ops.score params:{id, direction} - console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.stealth.cast(user,{}) - console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" - user.stats.buffs.stealth = 0 - - console.log user.dailys.length -### diff --git a/test/common/simulations/passive_active_attrs.js b/test/common/simulations/passive_active_attrs.js new file mode 100644 index 0000000000..d22524cec3 --- /dev/null +++ b/test/common/simulations/passive_active_attrs.js @@ -0,0 +1,294 @@ +(function() { + var _, clearUser, id, party, s, shared, task, user; + + shared = require('../../../common/script/index.js'); + + _ = require('lodash'); + + id = shared.uuid(); + + user = { + stats: { + "class": 'warrior', + buffs: { + per: 0, + int: 0, + con: 0, + str: 0 + } + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + preferences: { + automaticAllocation: false + }, + achievements: {}, + flags: { + levelDrops: {} + }, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + quests: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + shared.taskDefaults({ + id: id, + value: 0 + }) + ], + dailys: [ + { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + } + ], + todos: [], + rewards: [] + }; + + shared.wrap(user); + + s = user.stats; + + task = user.tasks[id]; + + party = [user]; + + console.log("\n\n================================================"); + + console.log("New Simulation"); + + console.log("================================================\n\n"); + + clearUser = function(lvl) { + if (lvl == null) { + lvl = 1; + } + _.merge(user.stats, { + exp: 0, + gp: 0, + hp: 50, + lvl: lvl, + str: lvl * 1.5, + con: lvl * 1.5, + per: lvl * 1.5, + int: lvl * 1.5, + mp: 100 + }); + _.merge(s.buffs, { + str: 0, + con: 0, + int: 0, + per: 0 + }); + _.merge(user.party.quest.progress, { + up: 0, + down: 0 + }); + return user.items.lastDrop = { + count: 0 + }; + }; + + _.each([1, 25, 50, 75, 100], function(lvl) { + console.log("[LEVEL " + lvl + "] (" + (lvl * 2) + " points total in every attr)\n\n"); + _.each({ + red: -25, + yellow: 0, + green: 35 + }, function(taskVal, color) { + var _party, b4, str; + console.log("[task.value = " + taskVal + " (" + color + ")]"); + console.log("direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"); + _.each(['up', 'down'], function(direction) { + var b4, delta; + clearUser(lvl); + b4 = { + hp: s.hp, + taskVal: taskVal + }; + task.value = taskVal; + if (direction === 'up') { + task.type = 'daily'; + } + delta = user.ops.score({ + params: { + id: id, + direction: direction + } + }); + return console.log((direction === 'up' ? '↑' : '↓') + "\t\t" + s.exp + "/" + (shared.tnl(s.lvl)) + "\t\t" + ((b4.hp - s.hp).toFixed(1)) + "\t" + (s.gp.toFixed(1)) + "\t" + (delta.toFixed(1)) + "\t\t" + ((task.value - b4.taskVal - delta).toFixed(1)) + "\t\t\t" + (user.party.quest.progress.up.toFixed(1))); + }); + str = '- [Wizard]'; + task.value = taskVal; + clearUser(lvl); + b4 = { + taskVal: taskVal + }; + shared.content.spells.wizard.fireball.cast(user, task); + str += "\tfireball(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " bossHit:" + (user.party.quest.progress.up.toFixed(2)) + ")"; + task.value = taskVal; + clearUser(lvl); + _party = [ + user, { + stats: { + mp: 0 + } + } + ]; + shared.content.spells.wizard.mpheal.cast(user, _party); + str += "\t| mpheal(mp:" + _party[1].stats.mp + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.earth.cast(user, party); + str += "\t\t\t\t| earth(buffs.int:" + s.buffs.int + ")"; + s.buffs.int = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.frost.cast(user, {}); + str += "\t\t\t| frost(N/A)"; + console.log(str); + str = '- [Warrior]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.smash.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\tsmash(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.defensiveStance.cast(user, {}); + str += "\t\t| defensiveStance(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.valorousPresence.cast(user, party); + str += "\t\t\t| valorousPresence(buffs.str:" + s.buffs.str + ")"; + s.buffs.str = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.intimidate.cast(user, party); + str += "\t\t| intimidate(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + console.log(str); + str = '- [Rogue]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.pickPocket.cast(user, task); + str += "\tpickPocket(gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.backStab.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\t\t| backStab(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.toolsOfTrade.cast(user, party); + str += "\t| toolsOfTrade(buffs.per:" + s.buffs.per + ")"; + s.buffs.per = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.stealth.cast(user, {}); + str += "\t\t| stealth(avoiding " + user.stats.buffs.stealth + " tasks)"; + user.stats.buffs.stealth = 0; + console.log(str); + str = '- [Healer]'; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heal.cast(user, {}); + str += "\theal(hp:" + (s.hp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.brightness.cast(user, {}); + b4 = { + taskVal: taskVal + }; + str += "\t\t\t| brightness(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.protectAura.cast(user, party); + str += "\t\t\t| protectAura(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heallAll.cast(user, party); + str += "\t\t| heallAll(hp:" + (s.hp.toFixed(1)) + ")"; + console.log(str); + return console.log('\n'); + }); + return console.log('------------------------------------------------------------'); + }); + + + /* + _.each [1,25,50,75,100,125], (lvl) -> + console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" + _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> + console.log "[task.value = #{taskVal} (#{color})]" + console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" + _.each ['up','down'], (direction) -> + clearUser(lvl) + b4 = {hp:s.hp, taskVal} + task.value = taskVal + task.type = 'daily' if direction is 'up' + delta = user.ops.score params:{id, direction} + console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" + + task.value = taskVal;clearUser(lvl) + shared.content.spells.rogue.stealth.cast(user,{}) + console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" + user.stats.buffs.stealth = 0 + + console.log user.dailys.length + */ + +}).call(this); diff --git a/test/common/test_helper.coffee b/test/common/test_helper.coffee deleted file mode 100644 index 5636e7f0ce..0000000000 --- a/test/common/test_helper.coffee +++ /dev/null @@ -1,44 +0,0 @@ -expect = require 'expect.js' - -module.exports.addCustomMatchers = -> - Assertion = expect.Assertion - - Assertion.prototype.toHaveGP = (gp)-> - actual = @obj.stats.gp - @assert( - actual == gp, - -> "expected user to have #{gp} gp, but got #{actual}", - -> "expected user to not have #{gp} gp" - ) - - Assertion.prototype.toHaveHP = (hp)-> - actual = @obj.stats.hp - @assert( - actual == hp, - -> "expected user to have #{hp} hp, but got #{actual}", - -> "expected user to not have #{hp} hp" - ) - - Assertion.prototype.toHaveExp = (exp)-> - actual = @obj.stats.exp - @assert( - actual == exp, - -> "expected user to have #{exp} experience points, but got #{actual}", - -> "expected user to not have #{exp} experience points" - ) - - Assertion.prototype.toHaveLevel = (lvl)-> - actual = @obj.stats.lvl - @assert( - actual == lvl, - -> "expected user to be level #{lvl}, but got #{actual}", - -> "expected user to not be level #{lvl}" - ) - - Assertion.prototype.toHaveMaxMP = (mp)-> - actual = @obj._statsComputed.maxMP - @assert( - actual == mp, - -> "expected user to have #{mp} max mp, but got #{actual}", - -> "expected user to not have #{mp} max mp" - ) diff --git a/test/common/test_helper.js b/test/common/test_helper.js new file mode 100644 index 0000000000..3c482b44d3 --- /dev/null +++ b/test/common/test_helper.js @@ -0,0 +1,53 @@ +var expect; + +expect = require('expect.js'); + +module.exports.addCustomMatchers = function() { + var Assertion; + Assertion = expect.Assertion; + Assertion.prototype.toHaveGP = function(gp) { + var 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"; + }); + }; + Assertion.prototype.toHaveHP = function(hp) { + var 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"; + }); + }; + Assertion.prototype.toHaveExp = function(exp) { + var 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"; + }); + }; + Assertion.prototype.toHaveLevel = function(lvl) { + var 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 Assertion.prototype.toHaveMaxMP = function(mp) { + var 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"; + }); + }; +}; From bc9089328406e52bbc7cfa1009722699cbd58211 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 21:45:20 -0600 Subject: [PATCH 04/20] Correct test to declare variable --- test/common/user.fns.buy.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/user.fns.buy.test.js b/test/common/user.fns.buy.test.js index df29497dc7..f756ce0c83 100644 --- a/test/common/user.fns.buy.test.js +++ b/test/common/user.fns.buy.test.js @@ -131,7 +131,7 @@ describe('user.fns.buy', function() { _(shared.content.gearTypes).each(function(type) { _(shared.content.gear.tree[type].armoire).each(function(gearObject, gearName) { - armoireKey = gearObject.key; + let armoireKey = gearObject.key; fullArmoire[armoireKey] = true; }); }); From c829eb21ed6ddc22b6939eb156ae99e803bd4297 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 21:50:47 -0600 Subject: [PATCH 05/20] Removed unused mocha.opts --- test/common/mocha.opts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 test/common/mocha.opts diff --git a/test/common/mocha.opts b/test/common/mocha.opts deleted file mode 100644 index a3c3e0c6dc..0000000000 --- a/test/common/mocha.opts +++ /dev/null @@ -1,9 +0,0 @@ ---colors ---reporter spec ---timeout 8000 ---check-leaks ---growl ---debug ---compilers coffee:coffee-script ---compilers js:babel/register ---globals io From 4a38c36cceacf57928628cc8acf2e8429cc8d61d Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 21:51:02 -0600 Subject: [PATCH 06/20] Register babel in gruntfile --- Gruntfile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Gruntfile.js b/Gruntfile.js index f669f21901..76ba62c18d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,4 +1,5 @@ /*global module:false*/ +require('babel/register'); var _ = require('lodash'); module.exports = function(grunt) { From 351172d178fea7d9fbffe6d510ae1debcad301a5 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 14 Nov 2015 21:52:59 -0600 Subject: [PATCH 07/20] Correct common test command to compile with babel. --- tasks/gulp-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index 5b0536069c..b2b528187d 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -20,7 +20,7 @@ const TEST_DB_URI = `mongodb://localhost/${TEST_DB}` const API_V2_TEST_COMMAND = 'mocha test/api/v2 --recursive --compilers js:babel/register'; const LEGACY_API_TEST_COMMAND = 'mocha test/api-legacy'; -const COMMON_TEST_COMMAND = 'mocha test/common --compilers coffee:coffee-script'; +const COMMON_TEST_COMMAND = 'mocha test/common --compilers js:babel/register'; const CONTENT_TEST_COMMAND = 'mocha test/content --compilers js:babel/register'; const CONTENT_OPTIONS = {maxBuffer: 1024 * 500}; const KARMA_TEST_COMMAND = 'karma start'; From 6c5afb9369c8f7593b189a75ca4f0c5e22003514 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:10:07 -0600 Subject: [PATCH 08/20] Downgrade to babelify v6. (v7 uses babel v6) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56b2faa3a9..660c305606 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "async": "~0.9.0", "aws-sdk": "^2.0.25", "babel": "^5.5.4", - "babelify": "^7.2.0", + "babelify": "^6.x.x", "bower": "~1.3.12", "browserify": "~12.0.1", "coffee-script": "1.6.x", From 6f11054994894ad7164f11b4c43d1ac13ee827a0 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:10:54 -0600 Subject: [PATCH 09/20] Add trailing comma --- tasks/gulp-babelify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/gulp-babelify.js b/tasks/gulp-babelify.js index a5bd7af203..7516594fee 100644 --- a/tasks/gulp-babelify.js +++ b/tasks/gulp-babelify.js @@ -10,7 +10,7 @@ gulp.task('browserify', function () { let bundler = browserify({ entries: './common/index.js', debug: true, - transform: [[babel, { compact: false }]] + transform: [[babel, { compact: false }]], }); return bundler.bundle() From ddeb0e7b23102fb0253ee792d527e07689e0e07b Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:16:11 -0600 Subject: [PATCH 10/20] Add babel register in the server file --- website/src/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/server.js b/website/src/server.js index f166cfb190..0a8983f6a1 100644 --- a/website/src/server.js +++ b/website/src/server.js @@ -1,3 +1,4 @@ +require('babel/register'); // Only do the minimal amount of work before forking just in case of a dyno restart var cluster = require("cluster"); var _ = require('lodash'); From e5d99ce271d09905e2118bd64839611f771879aa Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:16:55 -0600 Subject: [PATCH 11/20] Convert common files to es2015. --- common/script/content/faq.js | 20 +++--- common/script/content/translation.js | 19 ++--- common/script/count.js | 68 +++++++++--------- common/script/i18n.js | 100 +++++++++++++++------------ 4 files changed, 104 insertions(+), 103 deletions(-) diff --git a/common/script/content/faq.js b/common/script/content/faq.js index e65f8969e6..d55f461b89 100644 --- a/common/script/content/faq.js +++ b/common/script/content/faq.js @@ -1,18 +1,16 @@ -'use strict'; +let t = require('./translation.js'); -var t = require('./translation.js'); +let NUMBER_OF_QUESTIONS = 12; -var NUMBER_OF_QUESTIONS = 12; - -var faq = {}; +let faq = {}; faq.questions = []; -for (var i = 0; i <= NUMBER_OF_QUESTIONS; i++) { - var question = { - question: t('faqQuestion' + i), - ios: t('iosFaqAnswer' + i), - web: t('webFaqAnswer' + i) +for (let i = 0; i <= NUMBER_OF_QUESTIONS; i++) { + let question = { + question: t(`faqQuestion${i}`), + ios: t(`iosFaqAnswer${i}`), + web: t(`webFaqAnswer${i}`), }; faq.questions.push(question); @@ -20,7 +18,7 @@ for (var i = 0; i <= NUMBER_OF_QUESTIONS; i++) { faq.stillNeedHelp = { ios: t('iosFaqStillNeedHelp'), - web: t('webFaqStillNeedHelp') + web: t('webFaqStillNeedHelp'), }; module.exports = faq; diff --git a/common/script/content/translation.js b/common/script/content/translation.js index da478c2546..eb6a39f8f0 100644 --- a/common/script/content/translation.js +++ b/common/script/content/translation.js @@ -1,20 +1,11 @@ -'use strict'; +import i18n from '../i18n'; -var i18n = require('../i18n'); - -var t = function(string, vars) { - var func = function(lang) { - if (vars == null) { - vars = { - a: 'a' - }; - } +export default function translator (string, vars = { a: 'a' }) { + function func (lang) { return i18n.t(string, vars, lang); - }; + } func.i18nLangFunc = true; // Trick to recognize this type of function return func; -}; - -module.exports = t; +} diff --git a/common/script/count.js b/common/script/count.js index 77a2974c8a..6305646836 100644 --- a/common/script/count.js +++ b/common/script/count.js @@ -1,71 +1,71 @@ -'use strict'; +import _ from 'lodash'; +import content from './content/index'; -var _ = require('lodash'); -var content = require('./content/index'); +const DROP_ANIMALS = _.keys(content.pets); -var DROP_ANIMALS = _.keys(content.pets); +function beastMasterProgress (pets) { + let count = 0; -function beastMasterProgress(pets) { - var count = 0; - _(DROP_ANIMALS).each(function(animal) { - if(pets[animal] > 0 || pets[animal] == -1) - count++ + _(DROP_ANIMALS).each((animal) => { + if (pets[animal] > 0 || pets[animal] === -1) + count++; }); return count; } -function dropPetsCurrentlyOwned(pets) { - var count = 0; +function dropPetsCurrentlyOwned (pets) { + let count = 0; - _(DROP_ANIMALS).each(function(animal) { - if(pets[animal] > 0) - count++ + _(DROP_ANIMALS).each((animal) => { + if (pets[animal] > 0) + count++; }); return count; } -function mountMasterProgress(mounts) { - var count = 0; - _(DROP_ANIMALS).each(function(animal) { +function mountMasterProgress (mounts) { + let count = 0; + + _(DROP_ANIMALS).each((animal) => { if (mounts[animal]) - count++ + count++; }); return count; } -function remainingGearInSet(userGear, set) { - var gear = _.filter(content.gear.flat, function(item) { - var setMatches = item.klass === set; - var hasItem = userGear[item.key]; +function remainingGearInSet (userGear, set) { + let gear = _.filter(content.gear.flat, (item) => { + let setMatches = item.klass === set; + let hasItem = userGear[item.key]; return setMatches && !hasItem; }); - var count = _.size(gear); + let count = _.size(gear); return count; } -function questsOfCategory(userQuests, category) { - var quests = _.filter(content.quests, function(quest) { - var categoryMatches = quest.category === category; - var hasQuest = userQuests[quest.key]; +function questsOfCategory (userQuests, category) { + let quests = _.filter(content.quests, (quest) => { + let categoryMatches = quest.category === category; + let hasQuest = userQuests[quest.key]; return categoryMatches && hasQuest; }); - var count = _.size(quests); + let count = _.size(quests); return count; } -module.exports = { - beastMasterProgress: beastMasterProgress, - dropPetsCurrentlyOwned: dropPetsCurrentlyOwned, - mountMasterProgress: mountMasterProgress, - remainingGearInSet: remainingGearInSet, - questsOfCategory: questsOfCategory +export default { + beastMasterProgress, + dropPetsCurrentlyOwned, + mountMasterProgress, + remainingGearInSet, + questsOfCategory, }; diff --git a/common/script/i18n.js b/common/script/i18n.js index 59e5e358da..908925e9e5 100644 --- a/common/script/i18n.js +++ b/common/script/i18n.js @@ -1,51 +1,63 @@ -var _; +import _ from 'lodash'; -_ = require('lodash'); - -module.exports = { +let i18n = { strings: null, translations: {}, - t: function(stringName) { - var clonedVars, e, locale, string, stringNotFound, vars; - vars = arguments[1]; - if (_.isString(arguments[1])) { - vars = null; - locale = arguments[1]; - } else if (arguments[2] != null) { - vars = arguments[1]; - locale = arguments[2]; + t, // eslint-disable-line no-use-before-define +}; + +function t (stringName) { + let vars = arguments[1]; + let locale; + + if (_.isString(arguments[1])) { + vars = null; + locale = arguments[1]; + } else if (arguments[2]) { + locale = arguments[2]; + } + + let i18nNotSetup = !i18n.strings && !i18n.translations[locale]; + + if (!locale || i18nNotSetup) { + locale = 'en'; + } + + let string; + + if (i18n.strings) { + string = i18n.strings[stringName]; + } else { + string = i18n.translations[locale] && i18n.translations[locale][stringName]; + } + + let clonedVars = _.clone(vars) || {}; + + clonedVars.locale = locale; + + if (string) { + try { + return _.template(string, clonedVars); + } catch (_error) { + return 'Error processing the string. Please see Help > Report a Bug.'; } - if ((locale == null) || (!module.exports.strings && !module.exports.translations[locale])) { - locale = 'en'; + } else { + let stringNotFound; + + if (i18n.strings) { + stringNotFound = i18n.strings.stringNotFound; + } else if (i18n.translations[locale]) { + stringNotFound = i18n.translations[locale] && i18n.translations[locale].stringNotFound; } - if (module.exports.strings) { - string = module.exports.strings[stringName]; - } else { - string = module.exports.translations[locale] && module.exports.translations[locale][stringName]; - } - clonedVars = _.clone(vars) || {}; - clonedVars.locale = locale; - if (string) { - try { - return _.template(string, clonedVars); - } catch (_error) { - e = _error; - return 'Error processing the string. Please see Help > Report a Bug.'; - } - } else { - if (module.exports.strings) { - stringNotFound = module.exports.strings.stringNotFound; - } else if (module.exports.translations[locale]) { - stringNotFound = module.exports.translations[locale] && module.exports.translations[locale].stringNotFound; - } - try { - return _.template(stringNotFound, { - string: stringName - }); - } catch (_error) { - e = _error; - return 'Error processing the string. Please see Help > Report a Bug.'; - } + + try { + return _.template(stringNotFound, { + string: stringName, + }); + } catch (_error) { + return 'Error processing the string. Please see Help > Report a Bug.'; } } -}; +} + +export default i18n; From da881153b9dbe348810448c64383882a582ba25f Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:19:29 -0600 Subject: [PATCH 12/20] Convert coffee tests to js --- test/api-legacy/challenges.coffee | 346 ------------------ test/api-legacy/challenges.js | 416 ++++++++++++++++++++++ test/api-legacy/chat.coffee | 73 ---- test/api-legacy/chat.js | 75 ++++ test/api-legacy/coupons.coffee | 191 ---------- test/api-legacy/coupons.js | 222 ++++++++++++ test/api-legacy/inAppPurchases.coffee | 281 --------------- test/api-legacy/inAppPurchases.js | 370 +++++++++++++++++++ test/api-legacy/party.coffee | 363 ------------------- test/api-legacy/party.js | 376 ++++++++++++++++++++ test/api-legacy/pushNotifications.coffee | 346 ------------------ test/api-legacy/pushNotifications.js | 434 +++++++++++++++++++++++ test/api-legacy/score.coffee | 114 ------ test/api-legacy/score.js | 146 ++++++++ test/api-legacy/subscriptions.coffee | 41 --- test/api-legacy/subscriptions.js | 54 +++ test/api-legacy/todos.coffee | 82 ----- test/api-legacy/todos.js | 91 +++++ 18 files changed, 2184 insertions(+), 1837 deletions(-) delete mode 100644 test/api-legacy/challenges.coffee create mode 100644 test/api-legacy/challenges.js delete mode 100644 test/api-legacy/chat.coffee create mode 100644 test/api-legacy/chat.js delete mode 100644 test/api-legacy/coupons.coffee create mode 100644 test/api-legacy/coupons.js delete mode 100644 test/api-legacy/inAppPurchases.coffee create mode 100644 test/api-legacy/inAppPurchases.js delete mode 100644 test/api-legacy/party.coffee create mode 100644 test/api-legacy/party.js delete mode 100644 test/api-legacy/pushNotifications.coffee create mode 100644 test/api-legacy/pushNotifications.js delete mode 100644 test/api-legacy/score.coffee create mode 100644 test/api-legacy/score.js delete mode 100644 test/api-legacy/subscriptions.coffee create mode 100644 test/api-legacy/subscriptions.js delete mode 100644 test/api-legacy/todos.coffee create mode 100644 test/api-legacy/todos.js diff --git a/test/api-legacy/challenges.coffee b/test/api-legacy/challenges.coffee deleted file mode 100644 index 30e5ccad8d..0000000000 --- a/test/api-legacy/challenges.coffee +++ /dev/null @@ -1,346 +0,0 @@ -'use strict' - -app = require("../../website/src/server") -Group = require("../../website/src/models/group").model -Challenge = require("../../website/src/models/challenge").model - -describe "Challenges", -> - - challenge = undefined - updateTodo = undefined - group = undefined - - beforeEach (done) -> - async.waterfall [ - (cb) -> - registerNewUser(cb, true) - , (user, cb) -> - request.post(baseURL + "/groups").send( - name: "TestGroup" - type: "party" - ).end (err, res) -> - expectCode res, 200 - group = res.body - expect(group.members.length).to.equal 1 - expect(group.leader).to.equal user._id - cb() - , (cb) -> - request.post(baseURL + "/challenges").send( - group: group._id - dailys: [ - type: "daily" - text: "Challenge Daily" - ] - todos: [{ - type: "todo" - text: "Challenge Todo 1" - notes: "Challenge Notes" - }] - rewards: [] - habits: [] - ).end (err, res) -> - challenge = res.body - done() - ] - - describe 'POST /challenge', -> - - it "Creates a challenge", (done) -> - request.post(baseURL + "/challenges").send( - group: group._id - dailys: [ - type: "daily" - text: "Challenge Daily" - ] - todos: [{ - type: "todo" - text: "Challenge Todo 1" - notes: "Challenge Notes" - }, { - type: "todo" - text: "Challenge Todo 2" - notes: "Challenge Notes" - }] - rewards: [] - habits: [] - official: true - ).end (err, res) -> - expectCode res, 200 - async.parallel [ - (cb) -> - User.findById user._id, cb - (cb) -> - Challenge.findById res.body._id, cb - ], (err, results) -> - user = results[0] - challenge = results[1] - expect(user.dailys[user.dailys.length - 1].text).to.equal "Challenge Daily" - expect(challenge.official).to.equal false - done() - - describe 'POST /challenge/:cid', -> - it "updates the notes on user's version of a challenge task's note without updating the challenge", (done) -> - updateTodo = challenge.todos[0] - updateTodo.notes = "User overriden notes" - async.waterfall [ - (cb) -> - request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (err, res) -> - cb() - , (cb) -> - Challenge.findById challenge._id, cb - , (chal, cb) -> - expect(chal.todos[0].notes).to.eql("Challenge Notes") - cb() - , (cb) -> - request.get(baseURL + "/user/tasks/" + updateTodo.id) - .end (err, res) -> - expect(res.body.notes).to.eql("User overriden notes") - done() - ] - - it "changes user's copy of challenge tasks when the challenge is updated", (done) -> - challenge.dailys[0].text = "Updated Daily" - request.post(baseURL + "/challenges/" + challenge._id) - .send(challenge) - .end (err, res) -> - challenge = res.body - expect(challenge.dailys[0].text).to.equal "Updated Daily" - User.findById user._id, (err, _user) -> - expectCode res, 200 - expect(_user.dailys[_user.dailys.length - 1].text).to.equal "Updated Daily" - done() - - it "does not changes user's notes on tasks when challenge task notes are updated", (done) -> - challenge.todos[0].notes = "Challenge Updated Todo Notes" - request.post(baseURL + "/challenges/" + challenge._id) - .send(challenge) - .end (err, res) -> - challenge = res.body - expect(challenge.todos[0].notes).to.equal "Challenge Updated Todo Notes" - User.findById user._id, (err, _user) -> - expectCode res, 200 - expect(_user.todos[_user.todos.length - 1].notes).to.equal "Challenge Notes" - done() - - - it "shows user notes on challenge page", (done) -> - updateTodo = challenge.todos[0] - updateTodo.notes = "User overriden notes" - async.waterfall [ - (cb) -> - request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end (err, res) -> - cb() - , (cb) -> - Challenge.findById challenge._id, cb - , (chal, cb) -> - expect(chal.todos[0].notes).to.eql("Challenge Notes") - cb() - , (cb) -> - request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end (err, res) -> - expect(res.body.todos[res.body.todos.length - 1].notes).to.equal "User overriden notes" - done() - ] - - it "Complete To-Dos", (done) -> - User.findById user._id, (err, _user) -> - u = _user - numTasks = (_.size(u.todos)) - request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end (err, res) -> - request.post(baseURL + "/user/tasks/clear-completed").end (err, res) -> - expect(_.size(res.body)).to.equal numTasks - 1 - done() - - it "Challenge deleted, breaks task link", (done) -> - itThis = this - request.del(baseURL + "/challenges/" + challenge._id).end (err, res) -> - User.findById user._id, (err, user) -> - len = user.dailys.length - 1 - daily = user.dailys[user.dailys.length - 1] - expect(daily.challenge.broken).to.equal "CHALLENGE_DELETED" - - # Now let's handle if challenge was deleted, but didn't get to update all the users (an error) - unset = $unset: {} - unset["$unset"]["dailys." + len + ".challenge.broken"] = 1 - User.findByIdAndUpdate user._id, unset, {new: true}, (err, user) -> - expect(err).to.not.exist - expect(user.dailys[len].challenge.broken).to.not.exist - request.post(baseURL + "/user/tasks/" + daily.id + "/up").end (err, res) -> - setTimeout (-> - User.findById user._id, (err, user) -> - expect(user.dailys[len].challenge.broken).to.equal "CHALLENGE_DELETED" - done() - ), 100 # we need to wait for challenge to update user, it's a background job for perf reasons - - it "admin creates a challenge", (done) -> - User.findByIdAndUpdate user._id, - $set: - "contributor.admin": true - , {new: true} - , (err, _user) -> - expect(err).to.not.exist - async.parallel [ - (cb) -> - request.post(baseURL + "/challenges").send( - group: group._id - dailys: [] - todos: [] - rewards: [] - habits: [] - official: false - ).end (err, res) -> - expect(res.body.official).to.equal false - cb() - (cb) -> - request.post(baseURL + "/challenges").send( - group: group._id - dailys: [] - todos: [] - rewards: [] - habits: [] - official: true - ).end (err, res) -> - expect(res.body.official).to.equal true - cb() - ], done - - it "User creates a non-tavern challenge with prize, deletes it, gets refund", (done) -> - User.findByIdAndUpdate user._id, - $set: - "balance": 8, - , {new: true} - , (err, user) -> - expect(err).to.not.be.ok - request.post(baseURL + "/challenges").send( - group: group._id - dailys: [] - todos: [] - rewards: [] - habits: [] - prize: 10 - ).end (err, res) -> - expect(res.body.prize).to.equal 10 - async.parallel [ - (cb) -> - User.findById user._id, cb - (cb) -> - Challenge.findById res.body._id, cb - ], (err, results) -> - user = results[0] - challenge = results[1] - expect(user.balance).to.equal 5.5 - request.del(baseURL + "/challenges/" + challenge._id).end (err, res) -> - User.findById user._id, (err, _user) -> - expect(_user.balance).to.equal 8 - done() - - it "User creates a tavern challenge with prize, deletes it, and does not get refund", (done) -> - User.findByIdAndUpdate user._id, - $set: - "balance": 8, - , {new: true} - , (err, user) -> - expect(err).to.not.be.ok - request.post(baseURL + "/challenges").send( - group: 'habitrpg' - dailys: [] - todos: [] - rewards: [] - habits: [] - prize: 10 - ).end (err, res) -> - expect(res.body.prize).to.equal 10 - async.parallel [ - (cb) -> - User.findById user._id, cb - (cb) -> - Challenge.findById res.body._id, cb - ], (err, results) -> - user = results[0] - challenge = results[1] - expect(user.balance).to.equal 5.5 - request.del(baseURL + "/challenges/" + challenge._id).end (err, res) -> - User.findById user._id, (err, _user) -> - expect(_user.balance).to.equal 5.5 - done() - - describe "non-owner permissions", () -> - challenge = undefined - - beforeEach (done) -> - async.waterfall [ - (cb) -> - request.post(baseURL + "/challenges").send( - group: group._id - name: 'challenge name' - dailys: [ - type: "daily" - text: "Challenge Daily" - ] - ).end (err, res) -> - challenge = res.body - cb() - - (cb) -> - registerNewUser(done, true) - ] - - context "non-owner", () -> - - it 'can not edit challenge', (done) -> - challenge.name = 'foobar' - request.post(baseURL + "/challenges/" + challenge._id) - .send(challenge) - .end (err, res) -> - error = res.body.err - - expect(error).to.eql("You don't have permissions to edit this challenge") - done() - - it 'can not close challenge', (done) -> - request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id) - .end (err, res) -> - error = res.body.err - - expect(error).to.eql("You don't have permissions to close this challenge") - done() - - it 'can not delete challenge', (done) -> - request.del(baseURL + "/challenges/" + challenge._id) - .end (err, res) -> - error = res.body.err - - expect(error).to.eql("You don't have permissions to delete this challenge") - done() - - context "non-owner that is an admin", () -> - - beforeEach (done) -> - User.findByIdAndUpdate(user._id, { 'contributor.admin': true }, {new: true}, done) - - it 'can edit challenge', (done) -> - challenge.name = 'foobar' - request.post(baseURL + "/challenges/" + challenge._id) - .send(challenge) - .end (err, res) -> - expect(res.body.err).to.not.exist - Challenge.findById challenge._id, (err, chal) -> - expect(chal.name).to.eql('foobar') - done() - - it 'can close challenge', (done) -> - request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id) - .end (err, res) -> - expect(res.body.err).to.not.exist - User.findById user._id, (err, usr) -> - expect(usr.achievements.challenges[0]).to.eql(challenge.name) - done() - - it 'can delete challenge', (done) -> - request.del(baseURL + "/challenges/" + challenge._id) - .end (err, res) -> - expect(res.body.err).to.not.exist - request.get(baseURL + "/challenges/" + challenge._id) - .end (err, res) -> - error = res.body.err - expect(error).to.eql("Challenge #{challenge._id} not found") - done() diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js new file mode 100644 index 0000000000..5f2ddc7749 --- /dev/null +++ b/test/api-legacy/challenges.js @@ -0,0 +1,416 @@ +(function() { + 'use strict'; + var Challenge, Group, app; + + app = require("../../website/src/server"); + + Group = require("../../website/src/models/group").model; + + Challenge = require("../../website/src/models/challenge").model; + + describe("Challenges", function() { + var challenge, group, updateTodo; + challenge = void 0; + updateTodo = void 0; + group = void 0; + beforeEach(function(done) { + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [ + { + type: "daily", + text: "Challenge Daily" + } + ], + todos: [ + { + type: "todo", + text: "Challenge Todo 1", + notes: "Challenge Notes" + } + ], + rewards: [], + habits: [] + }).end(function(err, res) { + challenge = res.body; + return done(); + }); + } + ]); + }); + describe('POST /challenge', function() { + return it("Creates a challenge", function(done) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [ + { + type: "daily", + text: "Challenge Daily" + } + ], + todos: [ + { + type: "todo", + text: "Challenge Todo 1", + notes: "Challenge Notes" + }, { + type: "todo", + text: "Challenge Todo 2", + notes: "Challenge Notes" + } + ], + rewards: [], + habits: [], + official: true + }).end(function(err, res) { + expectCode(res, 200); + return async.parallel([ + function(cb) { + return User.findById(user._id, cb); + }, function(cb) { + return Challenge.findById(res.body._id, cb); + } + ], function(err, results) { + var user; + user = results[0]; + challenge = results[1]; + expect(user.dailys[user.dailys.length - 1].text).to.equal("Challenge Daily"); + expect(challenge.official).to.equal(false); + return done(); + }); + }); + }); + }); + describe('POST /challenge/:cid', function() { + it("updates the notes on user's version of a challenge task's note without updating the challenge", function(done) { + updateTodo = challenge.todos[0]; + updateTodo.notes = "User overriden notes"; + return async.waterfall([ + function(cb) { + return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return Challenge.findById(challenge._id, cb); + }, function(chal, cb) { + expect(chal.todos[0].notes).to.eql("Challenge Notes"); + return cb(); + }, function(cb) { + return request.get(baseURL + "/user/tasks/" + updateTodo.id).end(function(err, res) { + expect(res.body.notes).to.eql("User overriden notes"); + return done(); + }); + } + ]); + }); + it("changes user's copy of challenge tasks when the challenge is updated", function(done) { + challenge.dailys[0].text = "Updated Daily"; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + challenge = res.body; + expect(challenge.dailys[0].text).to.equal("Updated Daily"); + return User.findById(user._id, function(err, _user) { + expectCode(res, 200); + expect(_user.dailys[_user.dailys.length - 1].text).to.equal("Updated Daily"); + return done(); + }); + }); + }); + it("does not changes user's notes on tasks when challenge task notes are updated", function(done) { + challenge.todos[0].notes = "Challenge Updated Todo Notes"; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + challenge = res.body; + expect(challenge.todos[0].notes).to.equal("Challenge Updated Todo Notes"); + return User.findById(user._id, function(err, _user) { + expectCode(res, 200); + expect(_user.todos[_user.todos.length - 1].notes).to.equal("Challenge Notes"); + return done(); + }); + }); + }); + return it("shows user notes on challenge page", function(done) { + updateTodo = challenge.todos[0]; + updateTodo.notes = "User overriden notes"; + return async.waterfall([ + function(cb) { + return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return Challenge.findById(challenge._id, cb); + }, function(chal, cb) { + expect(chal.todos[0].notes).to.eql("Challenge Notes"); + return cb(); + }, function(cb) { + return request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end(function(err, res) { + expect(res.body.todos[res.body.todos.length - 1].notes).to.equal("User overriden notes"); + return done(); + }); + } + ]); + }); + }); + it("Complete To-Dos", function(done) { + return User.findById(user._id, function(err, _user) { + var numTasks, u; + u = _user; + numTasks = _.size(u.todos); + return request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function(err, res) { + return request.post(baseURL + "/user/tasks/clear-completed").end(function(err, res) { + expect(_.size(res.body)).to.equal(numTasks - 1); + return done(); + }); + }); + }); + }); + it("Challenge deleted, breaks task link", function(done) { + var itThis; + itThis = this; + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + return User.findById(user._id, function(err, user) { + var daily, len, unset; + len = user.dailys.length - 1; + daily = user.dailys[user.dailys.length - 1]; + expect(daily.challenge.broken).to.equal("CHALLENGE_DELETED"); + unset = { + $unset: {} + }; + unset["$unset"]["dailys." + len + ".challenge.broken"] = 1; + return User.findByIdAndUpdate(user._id, unset, { + "new": true + }, function(err, user) { + expect(err).to.not.exist; + expect(user.dailys[len].challenge.broken).to.not.exist; + return request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function(err, res) { + return setTimeout((function() { + return User.findById(user._id, function(err, user) { + expect(user.dailys[len].challenge.broken).to.equal("CHALLENGE_DELETED"); + return done(); + }); + }), 100); + }); + }); + }); + }); + }); + it("admin creates a challenge", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "contributor.admin": true + } + }, { + "new": true + }, function(err, _user) { + expect(err).to.not.exist; + return async.parallel([ + function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + official: false + }).end(function(err, res) { + expect(res.body.official).to.equal(false); + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + official: true + }).end(function(err, res) { + expect(res.body.official).to.equal(true); + return cb(); + }); + } + ], done); + }); + }); + it("User creates a non-tavern challenge with prize, deletes it, gets refund", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "balance": 8 + } + }, { + "new": true + }, function(err, user) { + expect(err).to.not.be.ok; + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + prize: 10 + }).end(function(err, res) { + expect(res.body.prize).to.equal(10); + return async.parallel([ + function(cb) { + return User.findById(user._id, cb); + }, function(cb) { + return Challenge.findById(res.body._id, cb); + } + ], function(err, results) { + user = results[0]; + challenge = results[1]; + expect(user.balance).to.equal(5.5); + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + return User.findById(user._id, function(err, _user) { + expect(_user.balance).to.equal(8); + return done(); + }); + }); + }); + }); + }); + }); + it("User creates a tavern challenge with prize, deletes it, and does not get refund", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "balance": 8 + } + }, { + "new": true + }, function(err, user) { + expect(err).to.not.be.ok; + return request.post(baseURL + "/challenges").send({ + group: 'habitrpg', + dailys: [], + todos: [], + rewards: [], + habits: [], + prize: 10 + }).end(function(err, res) { + expect(res.body.prize).to.equal(10); + return async.parallel([ + function(cb) { + return User.findById(user._id, cb); + }, function(cb) { + return Challenge.findById(res.body._id, cb); + } + ], function(err, results) { + user = results[0]; + challenge = results[1]; + expect(user.balance).to.equal(5.5); + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + return User.findById(user._id, function(err, _user) { + expect(_user.balance).to.equal(5.5); + return done(); + }); + }); + }); + }); + }); + }); + return describe("non-owner permissions", function() { + challenge = void 0; + beforeEach(function(done) { + return async.waterfall([ + function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + name: 'challenge name', + dailys: [ + { + type: "daily", + text: "Challenge Daily" + } + ] + }).end(function(err, res) { + challenge = res.body; + return cb(); + }); + }, function(cb) { + return registerNewUser(done, true); + } + ]); + }); + context("non-owner", function() { + it('can not edit challenge', function(done) { + challenge.name = 'foobar'; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to edit this challenge"); + return done(); + }); + }); + it('can not close challenge', function(done) { + return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to close this challenge"); + return done(); + }); + }); + return it('can not delete challenge', function(done) { + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to delete this challenge"); + return done(); + }); + }); + }); + return context("non-owner that is an admin", function() { + beforeEach(function(done) { + return User.findByIdAndUpdate(user._id, { + 'contributor.admin': true + }, { + "new": true + }, done); + }); + it('can edit challenge', function(done) { + challenge.name = 'foobar'; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + expect(res.body.err).to.not.exist; + return Challenge.findById(challenge._id, function(err, chal) { + expect(chal.name).to.eql('foobar'); + return done(); + }); + }); + }); + it('can close challenge', function(done) { + return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { + expect(res.body.err).to.not.exist; + return User.findById(user._id, function(err, usr) { + expect(usr.achievements.challenges[0]).to.eql(challenge.name); + return done(); + }); + }); + }); + return it('can delete challenge', function(done) { + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + expect(res.body.err).to.not.exist; + return request.get(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("Challenge " + challenge._id + " not found"); + return done(); + }); + }); + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/chat.coffee b/test/api-legacy/chat.coffee deleted file mode 100644 index fe889a93f4..0000000000 --- a/test/api-legacy/chat.coffee +++ /dev/null @@ -1,73 +0,0 @@ -'use strict' - -diff = require("deep-diff") - -Group = require("../../website/src/models/group").model -app = require("../../website/src/server") - -describe "Chat", -> - group = undefined - before (done) -> - async.waterfall [ - (cb) -> - registerNewUser(cb, true) - - (user, cb) -> - request.post(baseURL + "/groups").send( - name: "TestGroup" - type: "party" - ).end (err, res) -> - expectCode res, 200 - group = res.body - expect(group.members.length).to.equal 1 - expect(group.leader).to.equal user._id - cb() - - ], done - - chat = undefined - - it "removes a user's chat notifications when user is kicked", (done) -> - userToRemove = null - async.waterfall [ - (cb) -> - registerManyUsers 1, cb - - (members, cb) -> - userToRemove = members[0] - request.post(baseURL + "/groups/" + group._id + "/invite").send( - uuids: [userToRemove._id] - ) - .end -> cb() - - (cb) -> - request.post(baseURL + "/groups/" + group._id + "/join") - .set("X-API-User", userToRemove._id) - .set("X-API-Key", userToRemove.apiToken) - .end (err, res) -> cb() - - (cb) -> - msg = "TestMsg" - request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg) - .end (err, res) -> cb() - - (cb) -> - request.get(baseURL + "/user") - .set("X-API-User", userToRemove._id) - .set("X-API-Key", userToRemove.apiToken) - .end (err, res) -> - expect(res.body.newMessages[group._id]).to.exist - cb() - - (cb) -> - request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id) - .end (err, res) -> cb() - - (cb) -> - request.get(baseURL + "/user") - .set("X-API-User", userToRemove._id) - .set("X-API-Key", userToRemove.apiToken) - .end (err, res) -> - expect(res.body.newMessages[group._id]).to.not.exist - cb() - ], done diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js new file mode 100644 index 0000000000..d1b1a0709b --- /dev/null +++ b/test/api-legacy/chat.js @@ -0,0 +1,75 @@ +(function() { + 'use strict'; + var Group, app, diff; + + diff = require("deep-diff"); + + Group = require("../../website/src/models/group").model; + + app = require("../../website/src/server"); + + describe("Chat", function() { + var chat, group; + group = void 0; + before(function(done) { + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); + }); + } + ], done); + }); + chat = void 0; + return it("removes a user's chat notifications when user is kicked", function(done) { + var userToRemove; + userToRemove = null; + return async.waterfall([ + function(cb) { + return registerManyUsers(1, cb); + }, function(members, cb) { + userToRemove = members[0]; + return request.post(baseURL + "/groups/" + group._id + "/invite").send({ + uuids: [userToRemove._id] + }).end(function() { + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + return cb(); + }); + }, function(cb) { + var msg; + msg = "TestMsg"; + return request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + expect(res.body.newMessages[group._id]).to.exist; + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + expect(res.body.newMessages[group._id]).to.not.exist; + return cb(); + }); + } + ], done); + }); + }); + +}).call(this); diff --git a/test/api-legacy/coupons.coffee b/test/api-legacy/coupons.coffee deleted file mode 100644 index e0fd380bf5..0000000000 --- a/test/api-legacy/coupons.coffee +++ /dev/null @@ -1,191 +0,0 @@ -'use strict' - -app = require("../../website/src/server") -Coupon = require("../../website/src/models/coupon").model - -makeSudoUser = (usr, cb) -> - registerNewUser -> - sudoUpdate = { "$set" : { "contributor.sudo" : true } } - User.findByIdAndUpdate user._id, sudoUpdate, {new: true}, (err, _user) -> - usr = _user - cb() - , true - -describe "Coupons", -> - before (done) -> - async.parallel [ - (cb) -> - mongoose.connection.collections['coupons'].drop (err) -> - cb() - (cb) -> - mongoose.connection.collections['users'].drop (err) -> - cb() - ], done - - coupons = null - - describe "POST /api/v2/coupons/generate/:event", -> - - context "while sudo user", -> - before (done) -> - makeSudoUser(user, done) - - it "generates coupons", (done) -> - queries = '?count=10' - request - .post(baseURL + '/coupons/generate/wondercon' + queries) - .end (err, res) -> - expectCode res, 200 - Coupon.find { event: 'wondercon' }, (err, _coupons) -> - coupons = _coupons - expect(coupons.length).to.equal 10 - _(coupons).each (c)-> - expect(c.event).to.equal 'wondercon' - done() - - context "while regular user", -> - - before (done) -> - registerNewUser(done, true) - - it "does not generate coupons", (done) -> - queries = '?count=10' - request - .post(baseURL + '/coupons/generate/wondercon' + queries) - .end (err, res) -> - expectCode res, 401 - expect(res.body.err).to.equal 'You don\'t have admin access' - done() - - describe "GET /api/v2/coupons", -> - - context "while sudo user", -> - - before (done) -> - makeSudoUser(user, done) - - it "gets coupons", (done) -> - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken - request - .get(baseURL + '/coupons' + queries) - .end (err, res) -> - expectCode res, 200 - codes = res.text - expect(codes).to.contain('code') - # Expect each coupon code _id to exist in response - _(coupons).each (c) -> expect(codes).to.contain(c._id) - - done() - - it "gets first 5 coupons out of 10 when a limit of 5 is set", (done) -> - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5' - request - .get(baseURL + '/coupons' + queries) - .end (err, res) -> - expectCode res, 200 - codes = res.text - sortedCoupons = _.sortBy(coupons, 'seq') - firstHalf = sortedCoupons[0..4] - secondHalf = sortedCoupons[5..9] - - # First five coupons should be present in codes - _(firstHalf).each (c) -> expect(codes).to.contain(c._id) - # Second five coupons should not be present in codes - _(secondHalf).each (c) -> expect(codes).to.not.contain(c._id) - done() - - it "gets last 5 coupons out of 10 when a limit of 5 is set", (done) -> - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5' - request - .get(baseURL + '/coupons' + queries) - .end (err, res) -> - expectCode res, 200 - codes = res.text - sortedCoupons = _.sortBy(coupons, 'seq') - firstHalf = sortedCoupons[0..4] - secondHalf = sortedCoupons[5..9] - - # First five coupons should not be present in codes - _(firstHalf).each (c) -> expect(codes).to.not.contain(c._id) - # Second five coupons should be present in codes - _(secondHalf).each (c) -> expect(codes).to.contain(c._id) - done() - - context "while regular user", -> - - before (done) -> - registerNewUser(done, true) - - it "does not get coupons", (done) -> - - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken - request - .get(baseURL + '/coupons' + queries) - .end (err, res) -> - expectCode res, 401 - expect(res.body.err).to.equal 'You don\'t have admin access' - done() - - describe "POST /api/v2/user/coupon/:code", -> - specialGear = (gear, has) -> - items = ['body_special_wondercon_gold' - 'body_special_wondercon_black' - 'body_special_wondercon_red' - 'back_special_wondercon_red' - 'back_special_wondercon_black' - 'back_special_wondercon_red' - 'eyewear_special_wondercon_black' - 'eyewear_special_wondercon_red'] - - _(items).each (i) -> - if(has) - expect(gear[i]).to.exist - else - expect(gear[i]).to.not.exist - - beforeEach (done) -> - registerNewUser -> - gear = user.items.gear.owned - specialGear(gear, false) - done() - , true - - context "unused coupon", -> - it "applies coupon and awards equipment", (done) -> - - code = coupons[0]._id - request - .post(baseURL + '/user/coupon/' + code) - .end (err, res) -> - expectCode res, 200 - gear = res.body.items.gear.owned - specialGear(gear, true) - done() - - context "already used coupon", -> - it "does not apply coupon and does not award equipment", (done) -> - - code = coupons[0]._id - request - .post(baseURL + '/user/coupon/' + code) - .end (err, res) -> - expectCode res, 400 - expect(res.body.err).to.equal "Coupon already used" - User.findById user._id, (err, _user) -> - gear = _user.items.gear.owned - specialGear(gear, false) - done() - - context "invalid coupon", -> - it "does not apply coupon and does not award equipment", (done) -> - - code = "not-a-real-coupon" - request - .post(baseURL + '/user/coupon/' + code) - .end (err, res) -> - expectCode res, 400 - expect(res.body.err).to.equal "Invalid coupon code" - User.findById user._id, (err, _user) -> - gear = _user.items.gear.owned - specialGear(gear, false) - done() diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js new file mode 100644 index 0000000000..51ded4c0fc --- /dev/null +++ b/test/api-legacy/coupons.js @@ -0,0 +1,222 @@ +(function() { + 'use strict'; + var Coupon, app, makeSudoUser; + + app = require("../../website/src/server"); + + Coupon = require("../../website/src/models/coupon").model; + + makeSudoUser = function(usr, cb) { + return registerNewUser(function() { + var sudoUpdate; + sudoUpdate = { + "$set": { + "contributor.sudo": true + } + }; + return User.findByIdAndUpdate(user._id, sudoUpdate, { + "new": true + }, function(err, _user) { + usr = _user; + return cb(); + }); + }, true); + }; + + describe("Coupons", function() { + var coupons; + before(function(done) { + return async.parallel([ + function(cb) { + return mongoose.connection.collections['coupons'].drop(function(err) { + return cb(); + }); + }, function(cb) { + return mongoose.connection.collections['users'].drop(function(err) { + return cb(); + }); + } + ], done); + }); + coupons = null; + describe("POST /api/v2/coupons/generate/:event", function() { + context("while sudo user", function() { + before(function(done) { + return makeSudoUser(user, done); + }); + return it("generates coupons", function(done) { + var queries; + queries = '?count=10'; + return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { + expectCode(res, 200); + return Coupon.find({ + event: 'wondercon' + }, function(err, _coupons) { + coupons = _coupons; + expect(coupons.length).to.equal(10); + _(coupons).each(function(c) { + return expect(c.event).to.equal('wondercon'); + }); + return done(); + }); + }); + }); + }); + return context("while regular user", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return it("does not generate coupons", function(done) { + var queries; + queries = '?count=10'; + return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { + expectCode(res, 401); + expect(res.body.err).to.equal('You don\'t have admin access'); + return done(); + }); + }); + }); + }); + describe("GET /api/v2/coupons", function() { + context("while sudo user", function() { + before(function(done) { + return makeSudoUser(user, done); + }); + it("gets coupons", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes; + expectCode(res, 200); + codes = res.text; + expect(codes).to.contain('code'); + _(coupons).each(function(c) { + return expect(codes).to.contain(c._id); + }); + return done(); + }); + }); + it("gets first 5 coupons out of 10 when a limit of 5 is set", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5'; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes, firstHalf, secondHalf, sortedCoupons; + expectCode(res, 200); + codes = res.text; + sortedCoupons = _.sortBy(coupons, 'seq'); + firstHalf = sortedCoupons.slice(0, 5); + secondHalf = sortedCoupons.slice(5, 10); + _(firstHalf).each(function(c) { + return expect(codes).to.contain(c._id); + }); + _(secondHalf).each(function(c) { + return expect(codes).to.not.contain(c._id); + }); + return done(); + }); + }); + return it("gets last 5 coupons out of 10 when a limit of 5 is set", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5'; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes, firstHalf, secondHalf, sortedCoupons; + expectCode(res, 200); + codes = res.text; + sortedCoupons = _.sortBy(coupons, 'seq'); + firstHalf = sortedCoupons.slice(0, 5); + secondHalf = sortedCoupons.slice(5, 10); + _(firstHalf).each(function(c) { + return expect(codes).to.not.contain(c._id); + }); + _(secondHalf).each(function(c) { + return expect(codes).to.contain(c._id); + }); + return done(); + }); + }); + }); + return context("while regular user", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return it("does not get coupons", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + expectCode(res, 401); + expect(res.body.err).to.equal('You don\'t have admin access'); + return done(); + }); + }); + }); + }); + return describe("POST /api/v2/user/coupon/:code", function() { + var specialGear; + specialGear = function(gear, has) { + var items; + items = ['body_special_wondercon_gold', 'body_special_wondercon_black', 'body_special_wondercon_red', 'back_special_wondercon_red', 'back_special_wondercon_black', 'back_special_wondercon_red', 'eyewear_special_wondercon_black', 'eyewear_special_wondercon_red']; + return _(items).each(function(i) { + if (has) { + return expect(gear[i]).to.exist; + } else { + return expect(gear[i]).to.not.exist; + } + }); + }; + beforeEach(function(done) { + return registerNewUser(function() { + var gear; + gear = user.items.gear.owned; + specialGear(gear, false); + return done(); + }, true); + }); + context("unused coupon", function() { + return it("applies coupon and awards equipment", function(done) { + var code; + code = coupons[0]._id; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + var gear; + expectCode(res, 200); + gear = res.body.items.gear.owned; + specialGear(gear, true); + return done(); + }); + }); + }); + context("already used coupon", function() { + return it("does not apply coupon and does not award equipment", function(done) { + var code; + code = coupons[0]._id; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + expectCode(res, 400); + expect(res.body.err).to.equal("Coupon already used"); + return User.findById(user._id, function(err, _user) { + var gear; + gear = _user.items.gear.owned; + specialGear(gear, false); + return done(); + }); + }); + }); + }); + return context("invalid coupon", function() { + return it("does not apply coupon and does not award equipment", function(done) { + var code; + code = "not-a-real-coupon"; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + expectCode(res, 400); + expect(res.body.err).to.equal("Invalid coupon code"); + return User.findById(user._id, function(err, _user) { + var gear; + gear = _user.items.gear.owned; + specialGear(gear, false); + return done(); + }); + }); + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/inAppPurchases.coffee b/test/api-legacy/inAppPurchases.coffee deleted file mode 100644 index 0256b4bd83..0000000000 --- a/test/api-legacy/inAppPurchases.coffee +++ /dev/null @@ -1,281 +0,0 @@ -'use strict' - -app = require('../../website/src/server') -rewire = require('rewire') -sinon = require('sinon') -inApp = rewire('../../website/src/controllers/payments/iap') -iapMock = { } -inApp.__set__('iap', iapMock) - -describe 'In-App Purchases', -> - describe 'Android', -> - req = { - body: { - transaction: { - reciept: 'foo' - signature: 'sig' - } - } - } - res = { - locals: { user: { _id: 'user' } } - json: sinon.spy() - } - next = -> true - paymentSpy = sinon.spy() - - before -> - inApp.__set__('payments.buyGems', paymentSpy) - - afterEach -> - paymentSpy.reset() - res.json.reset() - - context 'successful app purchase', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb(null, true) - iapMock.isValidated = (googleRes)-> return googleRes - iapMock.GOOGLE = 'google' - - it 'calls res.json with succesful result object', -> - expectedResObj = { - ok: true - data: true - } - - inApp.androidVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'calls payments.buyGems function', -> - inApp.androidVerify(req, res, next) - - expect(paymentSpy).to.be.calledOnce - expect(paymentSpy).to.be.calledWith({user: res.locals.user, paymentMethod:'IAP GooglePlay'}) - - context 'error in setup', -> - before -> - iapMock.setup = (cb)-> return cb("error in setup") - - it 'calls res.json with setup error object', -> - expectedResObj = { - ok: false - data: 'IAP Error' - } - - inApp.androidVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.androidVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'error in validation', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb('error in validation', true) - - it 'calls res.json with validation error object', -> - expectedResObj = { - ok: false - data: { - code: 6778001 - message: 'error in validation' - } - } - - inApp.androidVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.androidVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'iap is not valid', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb(null, false) - iapMock.isValidated = (googleRes)-> return googleRes - - it 'does not call res.json', -> - inApp.androidVerify(req, res, next) - - expect(res.json).to.not.be.called - - it 'does not calls payments.buyGems function', -> - inApp.androidVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - describe 'iOS', -> - req = { body: { transaction: { reciept: 'foo' } } } - res = { - locals: { user: { _id: 'user' } } - json: sinon.spy() - } - next = -> true - paymentSpy = sinon.spy() - - before -> - inApp.__set__('payments.buyGems', paymentSpy) - - afterEach -> - paymentSpy.reset() - res.json.reset() - - context 'successful app purchase', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true) - iapMock.isValidated = (appleRes)-> return appleRes - iapMock.getPurchaseData = (appleRes)-> - return [{ productId: 'com.habitrpg.ios.Habitica.20gems' }] - iapMock.APPLE = 'apple' - - it 'calls res.json with succesful result object', -> - expectedResObj = { - ok: true - data: true - } - - inApp.iosVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.be.calledOnce - expect(paymentSpy).to.be.calledWith({user: res.locals.user, paymentMethod:'IAP AppleStore', amount: 5.25}) - - context 'error in setup', -> - before -> - iapMock.setup = (cb)-> return cb("error in setup") - - it 'calls res.json with setup error object', -> - expectedResObj = { - ok: false - data: 'IAP Error' - } - - inApp.iosVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'error in validation', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb('error in validation', true) - - it 'calls res.json with validation error object', -> - expectedResObj = { - ok: false - data: { - code: 6778001 - message: 'error in validation' - } - } - - inApp.iosVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'iap is not valid', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, false) - iapMock.isValidated = (appleRes)-> return appleRes - - it 'does not call res.json', -> - inApp.iosVerify(req, res, next) - expectedResObj = { - ok: false - data: { - code: 6778001 - message: 'Invalid receipt' - } - } - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'iap is valid but has no purchaseDataList', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true) - iapMock.isValidated = (appleRes)-> return appleRes - iapMock.getPurchaseData = (appleRes)-> - return [] - iapMock.APPLE = 'apple' - - it 'calls res.json with succesful result object', -> - expectedResObj = { - ok: false - data: { - code: 6778001 - message: 'Incorrect receipt content' - } - } - - inApp.iosVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.not.be.called - - context 'iap is valid, has purchaseDataList, but productId does not match', -> - before -> - iapMock.setup = (cb)-> return cb(null) - iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true) - iapMock.isValidated = (appleRes)-> return appleRes - iapMock.getPurchaseData = (appleRes)-> - return [{ productId: 'com.another.company' }] - iapMock.APPLE = 'apple' - - it 'calls res.json with incorrect reciept obj', -> - expectedResObj = { - ok: false - data: { - code: 6778001 - message: 'Incorrect receipt content' - } - } - - inApp.iosVerify(req, res, next) - - expect(res.json).to.be.calledOnce - expect(res.json).to.be.calledWith(expectedResObj) - - it 'does not calls payments.buyGems function', -> - inApp.iosVerify(req, res, next) - - expect(paymentSpy).to.not.be.called diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js new file mode 100644 index 0000000000..7eaea6c1be --- /dev/null +++ b/test/api-legacy/inAppPurchases.js @@ -0,0 +1,370 @@ +(function() { + 'use strict'; + var app, iapMock, inApp, rewire, sinon; + + app = require('../../website/src/server'); + + rewire = require('rewire'); + + sinon = require('sinon'); + + inApp = rewire('../../website/src/controllers/payments/iap'); + + iapMock = {}; + + inApp.__set__('iap', iapMock); + + describe('In-App Purchases', function() { + describe('Android', function() { + var next, paymentSpy, req, res; + req = { + body: { + transaction: { + reciept: 'foo', + signature: 'sig' + } + } + }; + res = { + locals: { + user: { + _id: 'user' + } + }, + json: sinon.spy() + }; + next = function() { + return true; + }; + paymentSpy = sinon.spy(); + before(function() { + return inApp.__set__('payments.buyGems', paymentSpy); + }); + afterEach(function() { + paymentSpy.reset(); + return res.json.reset(); + }); + context('successful app purchase', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(googleRes) { + return googleRes; + }; + return iapMock.GOOGLE = 'google'; + }); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: true, + data: true + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + expect(paymentSpy).to.be.calledOnce; + return expect(paymentSpy).to.be.calledWith({ + user: res.locals.user, + paymentMethod: 'IAP GooglePlay' + }); + }); + }); + context('error in setup', function() { + before(function() { + return iapMock.setup = function(cb) { + return cb("error in setup"); + }; + }); + it('calls res.json with setup error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: 'IAP Error' + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('error in validation', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + return iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb('error in validation', true); + }; + }); + it('calls res.json with validation error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'error in validation' + } + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + return context('iap is not valid', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb(null, false); + }; + return iapMock.isValidated = function(googleRes) { + return googleRes; + }; + }); + it('does not call res.json', function() { + inApp.androidVerify(req, res, next); + return expect(res.json).to.not.be.called; + }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + }); + return describe('iOS', function() { + var next, paymentSpy, req, res; + req = { + body: { + transaction: { + reciept: 'foo' + } + } + }; + res = { + locals: { + user: { + _id: 'user' + } + }, + json: sinon.spy() + }; + next = function() { + return true; + }; + paymentSpy = sinon.spy(); + before(function() { + return inApp.__set__('payments.buyGems', paymentSpy); + }); + afterEach(function() { + paymentSpy.reset(); + return res.json.reset(); + }); + context('successful app purchase', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return [ + { + productId: 'com.habitrpg.ios.Habitica.20gems' + } + ]; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: true, + data: true + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + expect(paymentSpy).to.be.calledOnce; + return expect(paymentSpy).to.be.calledWith({ + user: res.locals.user, + paymentMethod: 'IAP AppleStore', + amount: 5.25 + }); + }); + }); + context('error in setup', function() { + before(function() { + return iapMock.setup = function(cb) { + return cb("error in setup"); + }; + }); + it('calls res.json with setup error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: 'IAP Error' + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('error in validation', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + return iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb('error in validation', true); + }; + }); + it('calls res.json with validation error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'error in validation' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('iap is not valid', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, false); + }; + return iapMock.isValidated = function(appleRes) { + return appleRes; + }; + }); + it('does not call res.json', function() { + var expectedResObj; + inApp.iosVerify(req, res, next); + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Invalid receipt' + } + }; + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('iap is valid but has no purchaseDataList', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return []; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Incorrect receipt content' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + return context('iap is valid, has purchaseDataList, but productId does not match', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return [ + { + productId: 'com.another.company' + } + ]; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with incorrect reciept obj', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Incorrect receipt content' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/party.coffee b/test/api-legacy/party.coffee deleted file mode 100644 index 93dadf5b12..0000000000 --- a/test/api-legacy/party.coffee +++ /dev/null @@ -1,363 +0,0 @@ -'use strict' - -diff = require("deep-diff") - -Group = require("../../website/src/models/group").model -app = require("../../website/src/server") - -describe "Party", -> - context "Quests", -> - party = undefined - group = undefined - participating = [] - notParticipating = [] - beforeEach (done) -> - # Tavern boss, side-by-side - Group.update( - _id: "habitrpg" - , - $set: - quest: - key: "dilatory" - active: true - progress: - hp: shared.content.quests.dilatory.boss.hp - rage: 0 - ).exec() - - # Tally some progress for later. Later we want to test that progress made before the quest began gets - # counted after the quest starts - async.waterfall [ - (cb) -> - registerNewUser(cb, true) - - (user, cb) -> - request.post(baseURL + "/groups").send( - name: "TestGroup" - type: "party" - ).end (err, res) -> - expectCode res, 200 - group = res.body - expect(group.members.length).to.equal 1 - expect(group.leader).to.equal user._id - cb() - - (cb) -> - request.post(baseURL + '/user/tasks').send({ - type: 'daily' - text: 'daily one' - }).end (err, res) -> - cb() - (cb) -> - request.post(baseURL + '/user/tasks').send({ - type: 'daily' - text: 'daily two' - }).end (err, res) -> - cb() - (cb) -> - User.findByIdAndUpdate user._id, - $set: - "stats.lvl": 50 - , {new: true} - , (err, _user) -> - cb(null, _user) - (_user, cb) -> - user = _user - request.post(baseURL + "/user/batch-update").send([ - { - op: "score" - params: - direction: "up" - id: user.dailys[0].id - } - { - op: "score" - params: - direction: "up" - id: user.dailys[0].id - } - { - op: "update" - body: - "stats.lvl": 50 - } - ]).end (err, res) -> - user = res.body - expect(user.party.quest.progress.up).to.be.above 0 - - # Invite some members - async.waterfall [ - - # Register new users - (cb) -> - registerManyUsers 3, cb - - # Send them invitations - (_party, cb) -> - party = _party - inviteURL = baseURL + "/groups/" + group._id + "/invite" - async.parallel [ - (cb2) -> - request.post(inviteURL).send( - uuids: [party[0]._id] - ).end -> - cb2() - (cb2) -> - request.post(inviteURL).send( - uuids: [party[1]._id] - ).end -> - cb2() - (cb2) -> - request.post(inviteURL).send( - uuids: [party[2]._id] - ).end (err, res)-> - cb2() - ], cb - - # Accept / Reject - (results, cb) -> - - # series since they'll be modifying the same group record - series = _.reduce(party, (m, v, i) -> - m.push (cb2) -> - request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end -> - cb2() - m - , []) - async.series series, cb - - # Make sure the invites stuck - (whatever, cb) -> - Group.findById group._id, (err, g) -> - group = g - expect(g.members.length).to.equal 4 - cb() - - ], -> - - # Start the quest - async.waterfall [ - (cb) -> - request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (err, res) -> - expectCode res, 400 - User.findByIdAndUpdate user._id, - $set: - "items.quests.vice3": 1 - , {new: true} - , cb - - (_user, cb) -> - request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end (err, res) -> - expectCode res, 200 - Group.findById group._id, cb - - (_group, cb) -> - expect(_group.quest.key).to.equal "vice3" - expect(_group.quest.active).to.equal false - request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end -> - request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end (err, res) -> - request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end (err, res) -> - group = res.body - expect(group.quest.active).to.equal true - cb() - - ], done - ] - - it "Casts a spell", (done) -> - mp = user.stats.mp - request.get(baseURL + "/members/" + party[0]._id).end (err, res) -> - party[0] = res.body - request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end (err, res) -> - - #expect(res.body.stats.mp).to.be.below(mp); - request.get(baseURL + "/members/" + party[0]._id).end (err, res) -> - member = res.body - expect(member.achievements.snowball).to.equal 1 - expect(member.stats.buffs.snowball).to.exist - difference = diff(member, party[0]) - expect(_.size(difference)).to.equal 2 - - # level up user so str is > 0 - request.put(baseURL + "/user").send("stats.lvl": 5).end (err, res) -> - - # Refill mana so user can cast - request.put(baseURL + "/user").send("stats.mp": 100).end (err, res) -> - request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end (err, res) -> - request.get(baseURL + "/members/" + member._id).end (err, res) -> - expect(res.body.stats.buffs.str).to.be.above 0 - expect(diff(res.body, member).length).to.equal 1 - done() - - it "Doesn't include people who aren't participating", (done) -> - request.get(baseURL + "/groups/" + group._id).end (err, res) -> - expect(_.size(res.body.quest.members)).to.equal 3 - done() - - it "allows quest participants to leave quest", (done) -> - leavingMember = party[1] - expect(group.quest.members[leavingMember._id]).to.eql(true) - - request.post(baseURL + "/groups/" + group._id + "/questLeave") - .set("X-API-User", leavingMember._id) - .set("X-API-Key", leavingMember.apiToken) - .end (err, res) -> - expectCode res, 204 - request.get(baseURL + '/groups/party') - .end (err, res) -> - expect(res.body.quest.members[leavingMember._id]).to.not.be.ok - done() - - xit "Hurts the boss", (done) -> - request.post(baseURL + "/user/batch-update").end (err, res) -> - user = res.body - up = user.party.quest.progress.up - expect(up).to.be.above 0 - - #{op:'score',params:{direction:'up',id:user.dailys[3].id}}, // leave one daily undone so Trapper hurts party - # set day to yesterday, cron will then be triggered on next action - request.post(baseURL + "/user/batch-update").send([ - { - op: "score" - params: - direction: "up" - id: user.dailys[0].id - } - { - op: "update" - body: - lastCron: moment().subtract(1, "days") - } - ]).end (err, res) -> - expect(res.body.party.quest.progress.up).to.be.above up - request.post(baseURL + "/user/batch-update").end -> - request.get(baseURL + "/groups/party").end (err, res) -> - - # Check boss damage - async.waterfall [ - (cb) -> - async.parallel [ - - #tavern boss - (cb2) -> - Group.findById "habitrpg", - quest: 1 - , (err, tavern) -> - expect(tavern.quest.progress.hp).to.be.below shared.content.quests.dilatory.boss.hp - expect(tavern.quest.progress.rage).to.be.above 0 - cb2() - - # party boss - (cb2) -> - expect(res.body.quest.progress.hp).to.be.below shared.content.quests.vice3.boss.hp - _party = res.body.members - expect(_.find(_party, - _id: party[0]._id - ).stats.hp).to.be.below 50 - expect(_.find(_party, - _id: party[1]._id - ).stats.hp).to.be.below 50 - expect(_.find(_party, - _id: party[2]._id - ).stats.hp).to.be 50 - cb2() - ], cb - - # Kill the boss - (whatever, cb) -> - async.waterfall [ - - # tavern boss - (cb2) -> - expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok() - Group.update - _id: "habitrpg" - , - $set: - "quest.progress.hp": 0 - , cb2 - - # party boss - (arg1, arg2, cb2) -> - expect(user.items.gear.owned.weapon_special_2).to.not.be.ok() - Group.findByIdAndUpdate group._id, - $set: - "quest.progress.hp": 0 - , {new: true} - , cb2 - ], cb - (_group, cb) -> - # set day to yesterday, cron will then be triggered on next action - request.post(baseURL + "/user/batch-update").send([ - { - op: "score" - params: - direction: "up" - id: user.dailys[1].id - } - { - op: "update" - body: - lastCron: moment().subtract(1, "days") - } - ]).end -> - cb() - - (cb) -> - request.post(baseURL + "/user/batch-update").end (err, res) -> - cb null, res.body - - (_user, cb) -> - - # need to load the user again, since tavern boss does update after user's cron - User.findById _user._id, cb - (_user, cb) -> - user = _user - Group.findById group._id, cb - (_group, cb) -> - cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp - cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp - - #//FIXME check that user got exp, but user is leveling up making the exp check difficult - # expect(user.stats.exp).to.be.above(cummExp); - # expect(user.stats.gp).to.be.above(cummGp); - async.parallel [ - - # Tavern Boss - (cb2) -> - Group.findById "habitrpg", (err, tavern) -> - - #use an explicit get because mongoose wraps the null in an object - expect(_.isEmpty(tavern.get("quest"))).to.equal true - expect(user.items.pets["MantisShrimp-Base"]).to.equal 5 - expect(user.items.mounts["MantisShrimp-Base"]).to.equal true - expect(user.items.eggs.Dragon).to.equal 2 - expect(user.items.hatchingPotions.Shade).to.equal 2 - cb2() - - # Party Boss - (cb2) -> - - #use an explicit get because mongoose wraps the null in an object - expect(_.isEmpty(_group.get("quest"))).to.equal true - expect(user.items.gear.owned.weapon_special_2).to.equal true - expect(user.items.eggs.Dragon).to.equal 2 - expect(user.items.hatchingPotions.Shade).to.equal 2 - - # need to fetch users to get updated data - async.parallel [ - (cb3) -> - User.findById party[0].id, (err, mbr) -> - expect(mbr.items.gear.owned.weapon_special_2).to.equal true - cb3() - (cb3) -> - User.findById party[1].id, (err, mbr) -> - expect(mbr.items.gear.owned.weapon_special_2).to.equal true - cb3() - (cb3) -> - User.findById party[2].id, (err, mbr) -> - expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok() - cb3() - ], cb2 - ], cb - ], done diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js new file mode 100644 index 0000000000..0d84de8583 --- /dev/null +++ b/test/api-legacy/party.js @@ -0,0 +1,376 @@ +(function() { + 'use strict'; + var Group, app, diff; + + diff = require("deep-diff"); + + Group = require("../../website/src/models/group").model; + + app = require("../../website/src/server"); + + describe("Party", function() { + return context("Quests", function() { + var group, notParticipating, participating, party; + party = void 0; + group = void 0; + participating = []; + notParticipating = []; + beforeEach(function(done) { + Group.update({ + _id: "habitrpg" + }, { + $set: { + quest: { + key: "dilatory", + active: true, + progress: { + hp: shared.content.quests.dilatory.boss.hp, + rage: 0 + } + } + } + }).exec(); + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); + }); + }, function(cb) { + return request.post(baseURL + '/user/tasks').send({ + type: 'daily', + text: 'daily one' + }).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return request.post(baseURL + '/user/tasks').send({ + type: 'daily', + text: 'daily two' + }).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return User.findByIdAndUpdate(user._id, { + $set: { + "stats.lvl": 50 + } + }, { + "new": true + }, function(err, _user) { + return cb(null, _user); + }); + }, function(_user, cb) { + var user; + user = _user; + return request.post(baseURL + "/user/batch-update").send([ + { + op: "score", + params: { + direction: "up", + id: user.dailys[0].id + } + }, { + op: "score", + params: { + direction: "up", + id: user.dailys[0].id + } + }, { + op: "update", + body: { + "stats.lvl": 50 + } + } + ]).end(function(err, res) { + user = res.body; + expect(user.party.quest.progress.up).to.be.above(0); + return async.waterfall([ + function(cb) { + return registerManyUsers(3, cb); + }, function(_party, cb) { + var inviteURL; + party = _party; + inviteURL = baseURL + "/groups/" + group._id + "/invite"; + return async.parallel([ + function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[0]._id] + }).end(function() { + return cb2(); + }); + }, function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[1]._id] + }).end(function() { + return cb2(); + }); + }, function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[2]._id] + }).end(function(err, res) { + return cb2(); + }); + } + ], cb); + }, function(results, cb) { + var series; + series = _.reduce(party, function(m, v, i) { + m.push(function(cb2) { + return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end(function() { + return cb2(); + }); + }); + return m; + }, []); + return async.series(series, cb); + }, function(whatever, cb) { + return Group.findById(group._id, function(err, g) { + group = g; + expect(g.members.length).to.equal(4); + return cb(); + }); + } + ], function() { + return async.waterfall([ + function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { + expectCode(res, 400); + return User.findByIdAndUpdate(user._id, { + $set: { + "items.quests.vice3": 1 + } + }, { + "new": true + }, cb); + }); + }, function(_user, cb) { + return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { + expectCode(res, 200); + return Group.findById(group._id, cb); + }); + }, function(_group, cb) { + expect(_group.quest.key).to.equal("vice3"); + expect(_group.quest.active).to.equal(false); + return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end(function() { + return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end(function(err, res) { + return request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end(function(err, res) { + group = res.body; + expect(group.quest.active).to.equal(true); + return cb(); + }); + }); + }); + } + ], done); + }); + }); + } + ]); + }); + it("Casts a spell", function(done) { + var mp; + mp = user.stats.mp; + return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { + party[0] = res.body; + return request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end(function(err, res) { + return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { + var difference, member; + member = res.body; + expect(member.achievements.snowball).to.equal(1); + expect(member.stats.buffs.snowball).to.exist; + difference = diff(member, party[0]); + expect(_.size(difference)).to.equal(2); + return request.put(baseURL + "/user").send({ + "stats.lvl": 5 + }).end(function(err, res) { + return request.put(baseURL + "/user").send({ + "stats.mp": 100 + }).end(function(err, res) { + return request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end(function(err, res) { + return request.get(baseURL + "/members/" + member._id).end(function(err, res) { + expect(res.body.stats.buffs.str).to.be.above(0); + expect(diff(res.body, member).length).to.equal(1); + return done(); + }); + }); + }); + }); + }); + }); + }); + }); + it("Doesn't include people who aren't participating", function(done) { + return request.get(baseURL + "/groups/" + group._id).end(function(err, res) { + expect(_.size(res.body.quest.members)).to.equal(3); + return done(); + }); + }); + it("allows quest participants to leave quest", function(done) { + var leavingMember; + leavingMember = party[1]; + expect(group.quest.members[leavingMember._id]).to.eql(true); + return request.post(baseURL + "/groups/" + group._id + "/questLeave").set("X-API-User", leavingMember._id).set("X-API-Key", leavingMember.apiToken).end(function(err, res) { + expectCode(res, 204); + return request.get(baseURL + '/groups/party').end(function(err, res) { + expect(res.body.quest.members[leavingMember._id]).to.not.be.ok; + return done(); + }); + }); + }); + return xit("Hurts the boss", function(done) { + return request.post(baseURL + "/user/batch-update").end(function(err, res) { + var up, user; + user = res.body; + up = user.party.quest.progress.up; + expect(up).to.be.above(0); + return request.post(baseURL + "/user/batch-update").send([ + { + op: "score", + params: { + direction: "up", + id: user.dailys[0].id + } + }, { + op: "update", + body: { + lastCron: moment().subtract(1, "days") + } + } + ]).end(function(err, res) { + expect(res.body.party.quest.progress.up).to.be.above(up); + return request.post(baseURL + "/user/batch-update").end(function() { + return request.get(baseURL + "/groups/party").end(function(err, res) { + return async.waterfall([ + function(cb) { + return async.parallel([ + function(cb2) { + return Group.findById("habitrpg", { + quest: 1 + }, function(err, tavern) { + expect(tavern.quest.progress.hp).to.be.below(shared.content.quests.dilatory.boss.hp); + expect(tavern.quest.progress.rage).to.be.above(0); + return cb2(); + }); + }, function(cb2) { + var _party; + expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp); + _party = res.body.members; + expect(_.find(_party, { + _id: party[0]._id + }).stats.hp).to.be.below(50); + expect(_.find(_party, { + _id: party[1]._id + }).stats.hp).to.be.below(50); + expect(_.find(_party, { + _id: party[2]._id + }).stats.hp).to.be(50); + return cb2(); + } + ], cb); + }, function(whatever, cb) { + return async.waterfall([ + function(cb2) { + expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok(); + return Group.update({ + _id: "habitrpg" + }, { + $set: { + "quest.progress.hp": 0 + } + }, cb2); + }, function(arg1, arg2, cb2) { + expect(user.items.gear.owned.weapon_special_2).to.not.be.ok(); + return Group.findByIdAndUpdate(group._id, { + $set: { + "quest.progress.hp": 0 + } + }, { + "new": true + }, cb2); + } + ], cb); + }, function(_group, cb) { + return request.post(baseURL + "/user/batch-update").send([ + { + op: "score", + params: { + direction: "up", + id: user.dailys[1].id + } + }, { + op: "update", + body: { + lastCron: moment().subtract(1, "days") + } + } + ]).end(function() { + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/user/batch-update").end(function(err, res) { + return cb(null, res.body); + }); + }, function(_user, cb) { + return User.findById(_user._id, cb); + }, function(_user, cb) { + user = _user; + return Group.findById(group._id, cb); + }, function(_group, cb) { + var cummExp, cummGp; + cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp; + cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp; + return async.parallel([ + function(cb2) { + return Group.findById("habitrpg", function(err, tavern) { + expect(_.isEmpty(tavern.get("quest"))).to.equal(true); + expect(user.items.pets["MantisShrimp-Base"]).to.equal(5); + expect(user.items.mounts["MantisShrimp-Base"]).to.equal(true); + expect(user.items.eggs.Dragon).to.equal(2); + expect(user.items.hatchingPotions.Shade).to.equal(2); + return cb2(); + }); + }, function(cb2) { + expect(_.isEmpty(_group.get("quest"))).to.equal(true); + expect(user.items.gear.owned.weapon_special_2).to.equal(true); + expect(user.items.eggs.Dragon).to.equal(2); + expect(user.items.hatchingPotions.Shade).to.equal(2); + return async.parallel([ + function(cb3) { + return User.findById(party[0].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); + return cb3(); + }); + }, function(cb3) { + return User.findById(party[1].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); + return cb3(); + }); + }, function(cb3) { + return User.findById(party[2].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok(); + return cb3(); + }); + } + ], cb2); + } + ], cb); + } + ], done); + }); + }); + }); + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/pushNotifications.coffee b/test/api-legacy/pushNotifications.coffee deleted file mode 100644 index 6a0da92178..0000000000 --- a/test/api-legacy/pushNotifications.coffee +++ /dev/null @@ -1,346 +0,0 @@ -'use strict' -#@TODO: Have to mock most things to get to the parts that -#call pushNotify. Consider refactoring group controller -#so things are easier to test - -app = require("../../website/src/server") -rewire = require('rewire') -sinon = require('sinon') - -describe "Push-Notifications", -> - before (done) -> - registerNewUser(done, true) - - describe "Events that send push notifications", -> - pushSpy = { sendNotify: sinon.spy() } - - afterEach (done) -> - pushSpy.sendNotify.reset() - done() - - context "Challenges", -> - challenges = rewire("../../website/src/controllers/api-v2/challenges") - challenges.__set__('pushNotify', pushSpy) - challengeMock = { - findById: (arg, cb) -> - cb(null, {leader: user._id, name: 'challenge-name'}) - } - userMock = { - findById: (arg, cb) -> - cb(null, user) - } - - challenges.__set__('Challenge', challengeMock) - challenges.__set__('User', userMock) - challenges.__set__('closeChal', -> true) - - beforeEach (done) -> - registerNewUser -> - user.preferences.emailNotifications.wonChallenge = false - user.save = (cb) -> cb(null, user) - done() - , true - - it "sends a push notification when you win a challenge", (done) -> - req = { - params: { cid: 'challenge-id' } - query: {uid: 'user-id'} - } - res = { - locals: { user: user } - } - challenges.selectWinner req, res - - setTimeout -> # Allow selectWinner to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - user, - 'You won a Challenge!', - 'challenge-name' - ) - done() - , 100 - - context "Groups", -> - - recipient = null - - groups = rewire("../../website/src/controllers/api-v2/groups") - groups.__set__('pushNotify', pushSpy) - - before (done) -> - registerNewUser (err,_user)-> - recipient = _user - recipient.invitations.guilds = [] - recipient.save = (cb) -> cb(null, recipient) - recipient.preferences.emailNotifications.invitedGuild = false - recipient.preferences.emailNotifications.invitedParty = false - recipient.preferences.emailNotifications.invitedQuest = false - userMock = { - findById: (arg, cb) -> - cb(null, recipient) - find: (arg, arg2, cb) -> - cb(null, [recipient]) - update: (arg, arg2) -> - { exec: -> true} - } - groups.__set__('User', userMock) - done() - - , false - - it "sends a push notification when invited to a guild", (done) -> - group = { _id: 'guild-id', name: 'guild-name', type: 'guild', members: [user._id], invites: [] } - group.save = (cb) -> cb(null, group) - req = { - body: { uuids: [recipient._id] } - } - res = { - locals: { group: group, user: user } - json: -> return true - } - - groups.invite req, res - - setTimeout -> # Allow invite to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Invited To Guild', - group.name - ) - done() - , 100 - - it "sends a push notification when invited to a party", (done) -> - group = { _id: 'party-id', name: 'party-name', type: 'party', members: [user._id], invites: [] } - group.save = (cb) -> cb(null, group) - req = { - body: { uuids: [recipient._id] } - } - res = { - locals: { group: group, user: user } - json: -> return true - } - - groups.invite req, res - - setTimeout -> # Allow invite to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Invited To Party', - group.name - ) - done() - , 100 - - it "sends a push notification when invited to a quest", (done) -> - group = { _id: 'party-id', name: 'party-name', type: 'party', members: [user._id, recipient._id], invites: [], quest: {}} - user.items.quests.hedgehog = 5 - group.save = (cb) -> cb(null, group) - group.markModified = -> true - req = { - body: { uuids: [recipient._id] } - query: { key: 'hedgehog' } - } - res = { - locals: { group: group, user: user } - json: -> return true - } - - groups.questAccept req, res - - setTimeout -> # Allow questAccept to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Quest Invitation', - 'Invitation for the Quest The Hedgebeast' - ) - done() - , 100 - - it "sends a push notification to participating members when quest starts", (done) -> - group = { _id: 'party-id', name: 'party-name', type: 'party', members: [user._id, recipient._id], invites: []} - group.quest = { - key: 'hedgehog' - progress: { hp: 100 } - members: {} - } - group.quest.members[recipient._id] = true - group.save = (cb) -> cb(null, group) - group.markModified = -> true - req = { - body: { uuids: [recipient._id] } - query: { } - # force: true - } - res = { - locals: { group: group, user: user } - json: -> return true - } - userMock = { - findOne: (arg, arg2, cb) -> - cb(null, recipient) - update: (arg, arg2, cb) -> - if (cb) - return cb(null, user) - else - return { - exec: -> true - } - } - groups.__set__('User', userMock) - groups.__set__('populateQuery', - (arg, arg2, arg3) -> - return { - exec: -> group.members - } - ) - - groups.questAccept req, res - - setTimeout -> # Allow questAccept to finish - expect(pushSpy.sendNotify).to.have.been.calledTwice - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'HabitRPG', - 'Your Quest has Begun: The Hedgebeast' - ) - done() - , 100 - - describe "Gifts", -> - - recipient = null - - before (done) -> - registerNewUser (err, _user) -> - recipient = _user - recipient.preferences.emailNotifications.giftedGems = false - user.balance = 4 - user.save = -> return true - recipient.save = -> return true - done() - , false - - context "sending gems from balance", -> - members = rewire("../../website/src/controllers/api-v2/members") - members.sendMessage = -> true - - members.__set__('pushNotify', pushSpy) - members.__set__ 'fetchMember', (id) -> - return (cb) -> cb(null, recipient) - - it "sends a push notification", (done) -> - req = { - params: { uuid: "uuid" }, - body: { - type: 'gems', - gems: { amount: 1 } - } - } - res = { locals: { user: user } } - - members.sendGift req, res - - setTimeout -> # Allow sendGift to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Gifted Gems', - '1 Gems - by ' + user.profile.name - ) - done() - , 100 - - describe "Purchases", -> - - payments = rewire("../../website/src/controllers/payments") - - payments.__set__('pushNotify', pushSpy) - membersMock = { sendMessage: -> true } - payments.__set__('members', membersMock) - - context "buying gems as a purchased gift", -> - - it "sends a push notification", (done) -> - data = { - user: user, - gift: { - member: recipient, - gems: { amount: 1 } - } - } - - payments.buyGems data - - setTimeout -> # Allow buyGems to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Gifted Gems', - '1 Gems - by ' + user.profile.name - ) - - done() - , 100 - - it "does not send a push notification if buying gems for self", (done) -> - data = { - user: user, - gift: { - member: user - gems: { amount: 1 } - } - } - - payments.buyGems data - - setTimeout -> # Allow buyGems to finish - expect(pushSpy.sendNotify).to.not.have.been.called - - done() - , 100 - - context "sending a subscription as a purchased gift", -> - - it "sends a push notification", (done) -> - data = { - user: user, - gift: { - member: recipient - subscription: { key: 'basic_6mo' } - } - } - - payments.createSubscription data - - setTimeout -> # Allow createSubscription to finish - expect(pushSpy.sendNotify).to.have.been.calledOnce - expect(pushSpy.sendNotify).to.have.been.calledWith( - recipient, - 'Gifted Subscription', - '6 months - by ' + user.profile.name - ) - - done() - , 100 - - it "does not send a push notification if buying subscription for self", (done) -> - data = { - user: user, - gift: { - member: user - subscription: { key: 'basic_6mo' } - } - } - - payments.createSubscription data - - setTimeout -> # Allow buyGems to finish - expect(pushSpy.sendNotify).to.not.have.been.called - - done() - , 100 diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js new file mode 100644 index 0000000000..9b414cf8da --- /dev/null +++ b/test/api-legacy/pushNotifications.js @@ -0,0 +1,434 @@ +(function() { + 'use strict'; + var app, rewire, sinon; + + app = require("../../website/src/server"); + + rewire = require('rewire'); + + sinon = require('sinon'); + + describe("Push-Notifications", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return describe("Events that send push notifications", function() { + var pushSpy; + pushSpy = { + sendNotify: sinon.spy() + }; + afterEach(function(done) { + pushSpy.sendNotify.reset(); + return done(); + }); + context("Challenges", function() { + var challengeMock, challenges, userMock; + challenges = rewire("../../website/src/controllers/api-v2/challenges"); + challenges.__set__('pushNotify', pushSpy); + challengeMock = { + findById: function(arg, cb) { + return cb(null, { + leader: user._id, + name: 'challenge-name' + }); + } + }; + userMock = { + findById: function(arg, cb) { + return cb(null, user); + } + }; + challenges.__set__('Challenge', challengeMock); + challenges.__set__('User', userMock); + challenges.__set__('closeChal', function() { + return true; + }); + beforeEach(function(done) { + return registerNewUser(function() { + user.preferences.emailNotifications.wonChallenge = false; + user.save = function(cb) { + return cb(null, user); + }; + return done(); + }, true); + }); + return it("sends a push notification when you win a challenge", function(done) { + var req, res; + req = { + params: { + cid: 'challenge-id' + }, + query: { + uid: 'user-id' + } + }; + res = { + locals: { + user: user + } + }; + challenges.selectWinner(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(user, 'You won a Challenge!', 'challenge-name'); + return done(); + }, 100); + }); + }); + context("Groups", function() { + var groups, recipient; + recipient = null; + groups = rewire("../../website/src/controllers/api-v2/groups"); + groups.__set__('pushNotify', pushSpy); + before(function(done) { + return registerNewUser(function(err, _user) { + var userMock; + recipient = _user; + recipient.invitations.guilds = []; + recipient.save = function(cb) { + return cb(null, recipient); + }; + recipient.preferences.emailNotifications.invitedGuild = false; + recipient.preferences.emailNotifications.invitedParty = false; + recipient.preferences.emailNotifications.invitedQuest = false; + userMock = { + findById: function(arg, cb) { + return cb(null, recipient); + }, + find: function(arg, arg2, cb) { + return cb(null, [recipient]); + }, + update: function(arg, arg2) { + return { + exec: function() { + return true; + } + }; + } + }; + groups.__set__('User', userMock); + return done(); + }, false); + }); + it("sends a push notification when invited to a guild", function(done) { + var group, req, res; + group = { + _id: 'guild-id', + name: 'guild-name', + type: 'guild', + members: [user._id], + invites: [] + }; + group.save = function(cb) { + return cb(null, group); + }; + req = { + body: { + uuids: [recipient._id] + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.invite(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Guild', group.name); + return done(); + }, 100); + }); + it("sends a push notification when invited to a party", function(done) { + var group, req, res; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id], + invites: [] + }; + group.save = function(cb) { + return cb(null, group); + }; + req = { + body: { + uuids: [recipient._id] + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.invite(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Party', group.name); + return done(); + }, 100); + }); + it("sends a push notification when invited to a quest", function(done) { + var group, req, res; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id, recipient._id], + invites: [], + quest: {} + }; + user.items.quests.hedgehog = 5; + group.save = function(cb) { + return cb(null, group); + }; + group.markModified = function() { + return true; + }; + req = { + body: { + uuids: [recipient._id] + }, + query: { + key: 'hedgehog' + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.questAccept(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Quest Invitation', 'Invitation for the Quest The Hedgebeast'); + return done(); + }, 100); + }); + return it("sends a push notification to participating members when quest starts", function(done) { + var group, req, res, userMock; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id, recipient._id], + invites: [] + }; + group.quest = { + key: 'hedgehog', + progress: { + hp: 100 + }, + members: {} + }; + group.quest.members[recipient._id] = true; + group.save = function(cb) { + return cb(null, group); + }; + group.markModified = function() { + return true; + }; + req = { + body: { + uuids: [recipient._id] + }, + query: {} + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + userMock = { + findOne: function(arg, arg2, cb) { + return cb(null, recipient); + }, + update: function(arg, arg2, cb) { + if (cb) { + return cb(null, user); + } else { + return { + exec: function() { + return true; + } + }; + } + } + }; + groups.__set__('User', userMock); + groups.__set__('populateQuery', function(arg, arg2, arg3) { + return { + exec: function() { + return group.members; + } + }; + }); + groups.questAccept(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledTwice; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'HabitRPG', 'Your Quest has Begun: The Hedgebeast'); + return done(); + }, 100); + }); + }); + return describe("Gifts", function() { + var recipient; + recipient = null; + before(function(done) { + return registerNewUser(function(err, _user) { + recipient = _user; + recipient.preferences.emailNotifications.giftedGems = false; + user.balance = 4; + user.save = function() { + return true; + }; + recipient.save = function() { + return true; + }; + return done(); + }, false); + }); + context("sending gems from balance", function() { + var members; + members = rewire("../../website/src/controllers/api-v2/members"); + members.sendMessage = function() { + return true; + }; + members.__set__('pushNotify', pushSpy); + members.__set__('fetchMember', function(id) { + return function(cb) { + return cb(null, recipient); + }; + }); + return it("sends a push notification", function(done) { + var req, res; + req = { + params: { + uuid: "uuid" + }, + body: { + type: 'gems', + gems: { + amount: 1 + } + } + }; + res = { + locals: { + user: user + } + }; + members.sendGift(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); + return done(); + }, 100); + }); + }); + return describe("Purchases", function() { + var membersMock, payments; + payments = rewire("../../website/src/controllers/payments"); + payments.__set__('pushNotify', pushSpy); + membersMock = { + sendMessage: function() { + return true; + } + }; + payments.__set__('members', membersMock); + context("buying gems as a purchased gift", function() { + it("sends a push notification", function(done) { + var data; + data = { + user: user, + gift: { + member: recipient, + gems: { + amount: 1 + } + } + }; + payments.buyGems(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); + return done(); + }, 100); + }); + return it("does not send a push notification if buying gems for self", function(done) { + var data; + data = { + user: user, + gift: { + member: user, + gems: { + amount: 1 + } + } + }; + payments.buyGems(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.not.have.been.called; + return done(); + }, 100); + }); + }); + return context("sending a subscription as a purchased gift", function() { + it("sends a push notification", function(done) { + var data; + data = { + user: user, + gift: { + member: recipient, + subscription: { + key: 'basic_6mo' + } + } + }; + payments.createSubscription(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Subscription', '6 months - by ' + user.profile.name); + return done(); + }, 100); + }); + return it("does not send a push notification if buying subscription for self", function(done) { + var data; + data = { + user: user, + gift: { + member: user, + subscription: { + key: 'basic_6mo' + } + } + }; + payments.createSubscription(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.not.have.been.called; + return done(); + }, 100); + }); + }); + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/score.coffee b/test/api-legacy/score.coffee deleted file mode 100644 index 57e455f068..0000000000 --- a/test/api-legacy/score.coffee +++ /dev/null @@ -1,114 +0,0 @@ -'use strict' - -require("../../website/src/server") - -describe "Score", -> - before (done) -> - registerNewUser done, true - - context "Todos that did not previously exist", -> - it "creates a completed a todo when using up url", (done) -> - request.post(baseURL + "/user/tasks/withUp/up").send( - type: "todo" - text: "withUp" - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/withUp").end (err, res) -> - upTodo = res.body - expect(upTodo.completed).to.equal true - done() - - it "creates an uncompleted todo when using down url", (done) -> - request.post(baseURL + "/user/tasks/withDown/down").send( - type: "todo" - text: "withDown" - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/withDown").end (err, res) -> - downTodo = res.body - expect(downTodo.completed).to.equal false - done() - - it "creates a completed a todo overriding the complete parameter when using up url", (done) -> - request.post(baseURL + "/user/tasks/withUpWithComplete/up").send( - type: "todo" - text: "withUpWithComplete" - completed: false - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/withUpWithComplete").end (err, res) -> - upTodo = res.body - expect(upTodo.completed).to.equal true - done() - - it "Creates an uncompleted todo overriding the completed parameter when using down url", (done) -> - request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send( - type: "todo" - text: "withDownWithUncomplete" - completed: true - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/withDownWithUncomplete").end (err, res) -> - downTodo = res.body - expect(downTodo.completed).to.equal false - done() - - context "Todo that already exists", -> - it "It completes a todo when using up url", (done) -> - request.post(baseURL + "/user/tasks").send( - type: "todo" - text: "Sample Todo" - ).end (err, res) -> - expectCode res, 200 - unCompletedTodo = res.body - expect(unCompletedTodo.completed).to.equal false - request.post(baseURL + "/user/tasks/"+unCompletedTodo._id+"/up").end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/"+unCompletedTodo._id).end (err, res) -> - unCompletedTodo = res.body - expect(unCompletedTodo.completed).to.equal true - done() - - it "It uncompletes a todo when using down url", (done) -> - request.post(baseURL + "/user/tasks").send( - type: "todo" - text: "Sample Todo" - completed: true - ).end (err, res) -> - expectCode res, 200 - completedTodo = res.body - expect(completedTodo.completed).to.equal true - request.post(baseURL + "/user/tasks/"+completedTodo._id+"/down").end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/"+completedTodo._id).end (err, res) -> - completedTodo = res.body - expect(completedTodo.completed).to.equal false - done() - - it "Creates and scores up a habit when using up url", (done) -> - upHabit = undefined - request.post(baseURL + "/user/tasks/habitWithUp/up").send( - type: "habit" - text: "testTitle" - completed: true - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/habitWithUp").end (err, res) -> - upHabit = res.body - expect(upHabit.value).to.be.at.least(1) - expect(upHabit.type).to.equal("habit") - done() - - it "Creates and scores down a habit when using down url", (done) -> - downHabit = undefined - request.post(baseURL + "/user/tasks/habitWithDown/down").send( - type: "habit" - text: "testTitle" - completed: true - ).end (err, res) -> - expectCode res, 200 - request.get(baseURL + "/user/tasks/habitWithDown").end (err, res) -> - downHabit = res.body - expect(downHabit.value).to.have.at.most(-1) - expect(downHabit.type).to.equal("habit") - done() diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js new file mode 100644 index 0000000000..18a18b8a78 --- /dev/null +++ b/test/api-legacy/score.js @@ -0,0 +1,146 @@ +(function() { + 'use strict'; + require("../../website/src/server"); + + describe("Score", function() { + before(function(done) { + return registerNewUser(done, true); + }); + context("Todos that did not previously exist", function() { + it("creates a completed a todo when using up url", function(done) { + return request.post(baseURL + "/user/tasks/withUp/up").send({ + type: "todo", + text: "withUp" + }).end(function(err, res) { + expectCode(res, 200); + request.get(baseURL + "/user/tasks/withUp").end(function(err, res) { + var upTodo; + upTodo = res.body; + return expect(upTodo.completed).to.equal(true); + }); + return done(); + }); + }); + it("creates an uncompleted todo when using down url", function(done) { + return request.post(baseURL + "/user/tasks/withDown/down").send({ + type: "todo", + text: "withDown" + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/withDown").end(function(err, res) { + var downTodo; + downTodo = res.body; + expect(downTodo.completed).to.equal(false); + return done(); + }); + }); + }); + it("creates a completed a todo overriding the complete parameter when using up url", function(done) { + return request.post(baseURL + "/user/tasks/withUpWithComplete/up").send({ + type: "todo", + text: "withUpWithComplete", + completed: false + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/withUpWithComplete").end(function(err, res) { + var upTodo; + upTodo = res.body; + expect(upTodo.completed).to.equal(true); + return done(); + }); + }); + }); + return it("Creates an uncompleted todo overriding the completed parameter when using down url", function(done) { + return request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send({ + type: "todo", + text: "withDownWithUncomplete", + completed: true + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/withDownWithUncomplete").end(function(err, res) { + var downTodo; + downTodo = res.body; + expect(downTodo.completed).to.equal(false); + return done(); + }); + }); + }); + }); + context("Todo that already exists", function() { + it("It completes a todo when using up url", function(done) { + return request.post(baseURL + "/user/tasks").send({ + type: "todo", + text: "Sample Todo" + }).end(function(err, res) { + var unCompletedTodo; + expectCode(res, 200); + unCompletedTodo = res.body; + expect(unCompletedTodo.completed).to.equal(false); + return request.post(baseURL + "/user/tasks/" + unCompletedTodo._id + "/up").end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/" + unCompletedTodo._id).end(function(err, res) { + unCompletedTodo = res.body; + expect(unCompletedTodo.completed).to.equal(true); + return done(); + }); + }); + }); + }); + return it("It uncompletes a todo when using down url", function(done) { + return request.post(baseURL + "/user/tasks").send({ + type: "todo", + text: "Sample Todo", + completed: true + }).end(function(err, res) { + var completedTodo; + expectCode(res, 200); + completedTodo = res.body; + expect(completedTodo.completed).to.equal(true); + return request.post(baseURL + "/user/tasks/" + completedTodo._id + "/down").end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/" + completedTodo._id).end(function(err, res) { + completedTodo = res.body; + expect(completedTodo.completed).to.equal(false); + return done(); + }); + }); + }); + }); + }); + it("Creates and scores up a habit when using up url", function(done) { + var upHabit; + upHabit = void 0; + return request.post(baseURL + "/user/tasks/habitWithUp/up").send({ + type: "habit", + text: "testTitle", + completed: true + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/habitWithUp").end(function(err, res) { + upHabit = res.body; + expect(upHabit.value).to.be.at.least(1); + expect(upHabit.type).to.equal("habit"); + return done(); + }); + }); + }); + return it("Creates and scores down a habit when using down url", function(done) { + var downHabit; + downHabit = void 0; + return request.post(baseURL + "/user/tasks/habitWithDown/down").send({ + type: "habit", + text: "testTitle", + completed: true + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/habitWithDown").end(function(err, res) { + downHabit = res.body; + expect(downHabit.value).to.have.at.most(-1); + expect(downHabit.type).to.equal("habit"); + return done(); + }); + }); + }); + }); + +}).call(this); diff --git a/test/api-legacy/subscriptions.coffee b/test/api-legacy/subscriptions.coffee deleted file mode 100644 index ca4e1da242..0000000000 --- a/test/api-legacy/subscriptions.coffee +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -payments = require("../../website/src/controllers/payments") -app = require("../../website/src/server") - -describe "Subscriptions", -> - - before (done) -> - registerNewUser(done, true) - - it "Handles unsubscription", (done) -> - cron = -> - user.lastCron = moment().subtract(1, "d") - user.fns.cron() - - expect(user.purchased.plan.customerId).to.not.exist - payments.createSubscription - user: user - customerId: "123" - paymentMethod: "Stripe" - sub: {key: 'basic_6mo'} - - expect(user.purchased.plan.customerId).to.exist - shared.wrap user - cron() - expect(user.purchased.plan.customerId).to.exist - payments.cancelSubscription user: user - cron() - expect(user.purchased.plan.customerId).to.exist - expect(user.purchased.plan.dateTerminated).to.exist - user.purchased.plan.dateTerminated = moment().subtract(2, "d") - cron() - expect(user.purchased.plan.customerId).to.not.exist - payments.createSubscription - user: user - customerId: "123" - paymentMethod: "Stripe" - sub: {key: 'basic_6mo'} - - expect(user.purchased.plan.dateTerminated).to.not.exist - done() diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js new file mode 100644 index 0000000000..df008124e9 --- /dev/null +++ b/test/api-legacy/subscriptions.js @@ -0,0 +1,54 @@ +(function() { + 'use strict'; + var app, payments; + + payments = require("../../website/src/controllers/payments"); + + app = require("../../website/src/server"); + + describe("Subscriptions", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return it("Handles unsubscription", function(done) { + var cron; + cron = function() { + user.lastCron = moment().subtract(1, "d"); + return user.fns.cron(); + }; + expect(user.purchased.plan.customerId).to.not.exist; + payments.createSubscription({ + user: user, + customerId: "123", + paymentMethod: "Stripe", + sub: { + key: 'basic_6mo' + } + }); + expect(user.purchased.plan.customerId).to.exist; + shared.wrap(user); + cron(); + expect(user.purchased.plan.customerId).to.exist; + payments.cancelSubscription({ + user: user + }); + cron(); + expect(user.purchased.plan.customerId).to.exist; + expect(user.purchased.plan.dateTerminated).to.exist; + user.purchased.plan.dateTerminated = moment().subtract(2, "d"); + cron(); + expect(user.purchased.plan.customerId).to.not.exist; + payments.createSubscription({ + user: user, + customerId: "123", + paymentMethod: "Stripe", + sub: { + key: 'basic_6mo' + } + }); + expect(user.purchased.plan.dateTerminated).to.not.exist; + return done(); + }); + }); + +}).call(this); diff --git a/test/api-legacy/todos.coffee b/test/api-legacy/todos.coffee deleted file mode 100644 index e9566c9f5e..0000000000 --- a/test/api-legacy/todos.coffee +++ /dev/null @@ -1,82 +0,0 @@ -'use strict' - -require("../../website/src/server") - -describe "Todos", -> - - before (done) -> - registerNewUser done, true - - beforeEach (done) -> - User.findById user._id, (err, _user) -> - user = _user - shared.wrap user - done() - - it "Archives old todos", (done) -> - numTasks = _.size(user.todos) - request.post(baseURL + "/user/batch-update?_v=999").send([ - { - op: "addTask" - body: - type: "todo" - } - { - op: "addTask" - body: - type: "todo" - } - { - op: "addTask" - body: - type: "todo" - } - ]).end (err, res) -> - expectCode res, 200 - # Expect number of todos to be 3 greater than the number the user started with - expect(_.size(res.body.todos)).to.equal numTasks + 3 - # Assign new number to numTasks variable - numTasks += 3 - request.post(baseURL + "/user/batch-update?_v=998").send([ - { - op: "score" - params: - direction: "up" - id: res.body.todos[0].id - } - { - op: "score" - params: - direction: "up" - id: res.body.todos[1].id - } - { - op: "score" - params: - direction: "up" - id: res.body.todos[2].id - } - ]).end (err, res) -> - expectCode res, 200 - expect(_.size(res.body.todos)).to.equal numTasks - request.post(baseURL + "/user/batch-update?_v=997").send([ - { - op: "updateTask" - params: - id: res.body.todos[0].id - - body: - dateCompleted: moment().subtract(4, "days") - } - { - op: "updateTask" - params: - id: res.body.todos[1].id - - body: - dateCompleted: moment().subtract(4, "days") - } - ]).end (err, res) -> - # Expect todos to be 2 less than the total count - expect(_.size(res.body.todos)).to.equal numTasks - 2 - done() diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js new file mode 100644 index 0000000000..ea4163991f --- /dev/null +++ b/test/api-legacy/todos.js @@ -0,0 +1,91 @@ +(function() { + 'use strict'; + require("../../website/src/server"); + + describe("Todos", function() { + before(function(done) { + return registerNewUser(done, true); + }); + beforeEach(function(done) { + return User.findById(user._id, function(err, _user) { + var user; + user = _user; + shared.wrap(user); + return done(); + }); + }); + return it("Archives old todos", function(done) { + var numTasks; + numTasks = _.size(user.todos); + return request.post(baseURL + "/user/batch-update?_v=999").send([ + { + op: "addTask", + body: { + type: "todo" + } + }, { + op: "addTask", + body: { + type: "todo" + } + }, { + op: "addTask", + body: { + type: "todo" + } + } + ]).end(function(err, res) { + expectCode(res, 200); + expect(_.size(res.body.todos)).to.equal(numTasks + 3); + numTasks += 3; + return request.post(baseURL + "/user/batch-update?_v=998").send([ + { + op: "score", + params: { + direction: "up", + id: res.body.todos[0].id + } + }, { + op: "score", + params: { + direction: "up", + id: res.body.todos[1].id + } + }, { + op: "score", + params: { + direction: "up", + id: res.body.todos[2].id + } + } + ]).end(function(err, res) { + expectCode(res, 200); + expect(_.size(res.body.todos)).to.equal(numTasks); + return request.post(baseURL + "/user/batch-update?_v=997").send([ + { + op: "updateTask", + params: { + id: res.body.todos[0].id + }, + body: { + dateCompleted: moment().subtract(4, "days") + } + }, { + op: "updateTask", + params: { + id: res.body.todos[1].id + }, + body: { + dateCompleted: moment().subtract(4, "days") + } + } + ]).end(function(err, res) { + expect(_.size(res.body.todos)).to.equal(numTasks - 2); + return done(); + }); + }); + }); + }); + }); + +}).call(this); From 5be9b88cc632b68d42648e95054c9ba1ab713dc5 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:24:19 -0600 Subject: [PATCH 13/20] Add babel compiler to mocha.opts --- tasks/gulp-tests.js | 6 +++--- test/mocha.opts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index b2b528187d..b6e28e534d 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -18,10 +18,10 @@ let server; const TEST_DB_URI = `mongodb://localhost/${TEST_DB}` -const API_V2_TEST_COMMAND = 'mocha test/api/v2 --recursive --compilers js:babel/register'; +const API_V2_TEST_COMMAND = 'mocha test/api/v2 --recursive'; const LEGACY_API_TEST_COMMAND = 'mocha test/api-legacy'; -const COMMON_TEST_COMMAND = 'mocha test/common --compilers js:babel/register'; -const CONTENT_TEST_COMMAND = 'mocha test/content --compilers js:babel/register'; +const COMMON_TEST_COMMAND = 'mocha test/common'; +const CONTENT_TEST_COMMAND = 'mocha test/content'; const CONTENT_OPTIONS = {maxBuffer: 1024 * 500}; const KARMA_TEST_COMMAND = 'karma start'; const SERVER_SIDE_TEST_COMMAND = 'mocha test/server_side'; diff --git a/test/mocha.opts b/test/mocha.opts index 30b4268b07..51a9c44078 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -3,7 +3,7 @@ --timeout 8000 --check-leaks --growl ---compilers coffee:coffee-script --globals io +--compilers js:babel/register --require test/api-legacy/api-helper --require ./test/helpers/globals.helper From 037337d54f46c5da1c79c391dd8fe0895597e1f1 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:24:42 -0600 Subject: [PATCH 14/20] Remove extraneous anonymous function. --- test/api-legacy/challenges.js | 728 ++++++------ test/api-legacy/chat.js | 136 ++- test/api-legacy/coupons.js | 390 ++++--- test/api-legacy/inAppPurchases.js | 686 ++++++----- test/api-legacy/party.js | 686 ++++++----- test/api-legacy/pushNotifications.js | 774 +++++++------ test/api-legacy/score.js | 258 +++-- test/api-legacy/subscriptions.js | 96 +- test/api-legacy/todos.js | 128 +-- test/common/dailies.js | 1007 ++++++++--------- test/common/simulations/autoAllocate.js | 297 +++-- .../simulations/passive_active_attrs.js | 563 +++++---- test/common/user.fns.ultimateGear.test.js | 2 - 13 files changed, 2852 insertions(+), 2899 deletions(-) diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js index 5f2ddc7749..2f264655ef 100644 --- a/test/api-legacy/challenges.js +++ b/test/api-legacy/challenges.js @@ -1,60 +1,32 @@ -(function() { - 'use strict'; - var Challenge, Group, app; +var Challenge, Group, app; - app = require("../../website/src/server"); +app = require("../../website/src/server"); - Group = require("../../website/src/models/group").model; +Group = require("../../website/src/models/group").model; - Challenge = require("../../website/src/models/challenge").model; +Challenge = require("../../website/src/models/challenge").model; - describe("Challenges", function() { - var challenge, group, updateTodo; - challenge = void 0; - updateTodo = void 0; - group = void 0; - beforeEach(function(done) { - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [ - { - type: "daily", - text: "Challenge Daily" - } - ], - todos: [ - { - type: "todo", - text: "Challenge Todo 1", - notes: "Challenge Notes" - } - ], - rewards: [], - habits: [] - }).end(function(err, res) { - challenge = res.body; - return done(); - }); - } - ]); - }); - describe('POST /challenge', function() { - return it("Creates a challenge", function(done) { +describe("Challenges", function() { + var challenge, group, updateTodo; + challenge = void 0; + updateTodo = void 0; + group = void 0; + beforeEach(function(done) { + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); + }); + }, function(cb) { return request.post(baseURL + "/challenges").send({ group: group._id, dailys: [ @@ -68,343 +40,240 @@ type: "todo", text: "Challenge Todo 1", notes: "Challenge Notes" - }, { - type: "todo", - text: "Challenge Todo 2", - notes: "Challenge Notes" } ], rewards: [], - habits: [], - official: true + habits: [] }).end(function(err, res) { - expectCode(res, 200); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - var user; - user = results[0]; - challenge = results[1]; - expect(user.dailys[user.dailys.length - 1].text).to.equal("Challenge Daily"); - expect(challenge.official).to.equal(false); - return done(); - }); - }); - }); - }); - describe('POST /challenge/:cid', function() { - it("updates the notes on user's version of a challenge task's note without updating the challenge", function(done) { - updateTodo = challenge.todos[0]; - updateTodo.notes = "User overriden notes"; - return async.waterfall([ - function(cb) { - return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return Challenge.findById(challenge._id, cb); - }, function(chal, cb) { - expect(chal.todos[0].notes).to.eql("Challenge Notes"); - return cb(); - }, function(cb) { - return request.get(baseURL + "/user/tasks/" + updateTodo.id).end(function(err, res) { - expect(res.body.notes).to.eql("User overriden notes"); - return done(); - }); - } - ]); - }); - it("changes user's copy of challenge tasks when the challenge is updated", function(done) { - challenge.dailys[0].text = "Updated Daily"; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { challenge = res.body; - expect(challenge.dailys[0].text).to.equal("Updated Daily"); - return User.findById(user._id, function(err, _user) { - expectCode(res, 200); - expect(_user.dailys[_user.dailys.length - 1].text).to.equal("Updated Daily"); - return done(); - }); + return done(); }); - }); - it("does not changes user's notes on tasks when challenge task notes are updated", function(done) { - challenge.todos[0].notes = "Challenge Updated Todo Notes"; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - challenge = res.body; - expect(challenge.todos[0].notes).to.equal("Challenge Updated Todo Notes"); - return User.findById(user._id, function(err, _user) { - expectCode(res, 200); - expect(_user.todos[_user.todos.length - 1].notes).to.equal("Challenge Notes"); - return done(); - }); - }); - }); - return it("shows user notes on challenge page", function(done) { - updateTodo = challenge.todos[0]; - updateTodo.notes = "User overriden notes"; - return async.waterfall([ - function(cb) { - return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return Challenge.findById(challenge._id, cb); - }, function(chal, cb) { - expect(chal.todos[0].notes).to.eql("Challenge Notes"); - return cb(); - }, function(cb) { - return request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end(function(err, res) { - expect(res.body.todos[res.body.todos.length - 1].notes).to.equal("User overriden notes"); - return done(); - }); + } + ]); + }); + describe('POST /challenge', function() { + return it("Creates a challenge", function(done) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [ + { + type: "daily", + text: "Challenge Daily" } - ]); - }); - }); - it("Complete To-Dos", function(done) { - return User.findById(user._id, function(err, _user) { - var numTasks, u; - u = _user; - numTasks = _.size(u.todos); - return request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function(err, res) { - return request.post(baseURL + "/user/tasks/clear-completed").end(function(err, res) { - expect(_.size(res.body)).to.equal(numTasks - 1); - return done(); - }); - }); - }); - }); - it("Challenge deleted, breaks task link", function(done) { - var itThis; - itThis = this; - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, user) { - var daily, len, unset; - len = user.dailys.length - 1; - daily = user.dailys[user.dailys.length - 1]; - expect(daily.challenge.broken).to.equal("CHALLENGE_DELETED"); - unset = { - $unset: {} - }; - unset["$unset"]["dailys." + len + ".challenge.broken"] = 1; - return User.findByIdAndUpdate(user._id, unset, { - "new": true - }, function(err, user) { - expect(err).to.not.exist; - expect(user.dailys[len].challenge.broken).to.not.exist; - return request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function(err, res) { - return setTimeout((function() { - return User.findById(user._id, function(err, user) { - expect(user.dailys[len].challenge.broken).to.equal("CHALLENGE_DELETED"); - return done(); - }); - }), 100); - }); - }); - }); - }); - }); - it("admin creates a challenge", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "contributor.admin": true - } - }, { - "new": true - }, function(err, _user) { - expect(err).to.not.exist; + ], + todos: [ + { + type: "todo", + text: "Challenge Todo 1", + notes: "Challenge Notes" + }, { + type: "todo", + text: "Challenge Todo 2", + notes: "Challenge Notes" + } + ], + rewards: [], + habits: [], + official: true + }).end(function(err, res) { + expectCode(res, 200); return async.parallel([ function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - official: false - }).end(function(err, res) { - expect(res.body.official).to.equal(false); - return cb(); - }); + return User.findById(user._id, cb); }, function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - official: true - }).end(function(err, res) { - expect(res.body.official).to.equal(true); - return cb(); - }); + return Challenge.findById(res.body._id, cb); } - ], done); + ], function(err, results) { + var user; + user = results[0]; + challenge = results[1]; + expect(user.dailys[user.dailys.length - 1].text).to.equal("Challenge Daily"); + expect(challenge.official).to.equal(false); + return done(); + }); }); }); - it("User creates a non-tavern challenge with prize, deletes it, gets refund", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "balance": 8 + }); + describe('POST /challenge/:cid', function() { + it("updates the notes on user's version of a challenge task's note without updating the challenge", function(done) { + updateTodo = challenge.todos[0]; + updateTodo.notes = "User overriden notes"; + return async.waterfall([ + function(cb) { + return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return Challenge.findById(challenge._id, cb); + }, function(chal, cb) { + expect(chal.todos[0].notes).to.eql("Challenge Notes"); + return cb(); + }, function(cb) { + return request.get(baseURL + "/user/tasks/" + updateTodo.id).end(function(err, res) { + expect(res.body.notes).to.eql("User overriden notes"); + return done(); + }); } - }, { - "new": true - }, function(err, user) { - expect(err).to.not.be.ok; - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - prize: 10 - }).end(function(err, res) { - expect(res.body.prize).to.equal(10); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - user = results[0]; - challenge = results[1]; - expect(user.balance).to.equal(5.5); - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, _user) { - expect(_user.balance).to.equal(8); + ]); + }); + it("changes user's copy of challenge tasks when the challenge is updated", function(done) { + challenge.dailys[0].text = "Updated Daily"; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + challenge = res.body; + expect(challenge.dailys[0].text).to.equal("Updated Daily"); + return User.findById(user._id, function(err, _user) { + expectCode(res, 200); + expect(_user.dailys[_user.dailys.length - 1].text).to.equal("Updated Daily"); + return done(); + }); + }); + }); + it("does not changes user's notes on tasks when challenge task notes are updated", function(done) { + challenge.todos[0].notes = "Challenge Updated Todo Notes"; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + challenge = res.body; + expect(challenge.todos[0].notes).to.equal("Challenge Updated Todo Notes"); + return User.findById(user._id, function(err, _user) { + expectCode(res, 200); + expect(_user.todos[_user.todos.length - 1].notes).to.equal("Challenge Notes"); + return done(); + }); + }); + }); + return it("shows user notes on challenge page", function(done) { + updateTodo = challenge.todos[0]; + updateTodo.notes = "User overriden notes"; + return async.waterfall([ + function(cb) { + return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return Challenge.findById(challenge._id, cb); + }, function(chal, cb) { + expect(chal.todos[0].notes).to.eql("Challenge Notes"); + return cb(); + }, function(cb) { + return request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end(function(err, res) { + expect(res.body.todos[res.body.todos.length - 1].notes).to.equal("User overriden notes"); + return done(); + }); + } + ]); + }); + }); + it("Complete To-Dos", function(done) { + return User.findById(user._id, function(err, _user) { + var numTasks, u; + u = _user; + numTasks = _.size(u.todos); + return request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function(err, res) { + return request.post(baseURL + "/user/tasks/clear-completed").end(function(err, res) { + expect(_.size(res.body)).to.equal(numTasks - 1); + return done(); + }); + }); + }); + }); + it("Challenge deleted, breaks task link", function(done) { + var itThis; + itThis = this; + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + return User.findById(user._id, function(err, user) { + var daily, len, unset; + len = user.dailys.length - 1; + daily = user.dailys[user.dailys.length - 1]; + expect(daily.challenge.broken).to.equal("CHALLENGE_DELETED"); + unset = { + $unset: {} + }; + unset["$unset"]["dailys." + len + ".challenge.broken"] = 1; + return User.findByIdAndUpdate(user._id, unset, { + "new": true + }, function(err, user) { + expect(err).to.not.exist; + expect(user.dailys[len].challenge.broken).to.not.exist; + return request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function(err, res) { + return setTimeout((function() { + return User.findById(user._id, function(err, user) { + expect(user.dailys[len].challenge.broken).to.equal("CHALLENGE_DELETED"); return done(); }); - }); + }), 100); }); }); }); }); - it("User creates a tavern challenge with prize, deletes it, and does not get refund", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "balance": 8 - } - }, { - "new": true - }, function(err, user) { - expect(err).to.not.be.ok; - return request.post(baseURL + "/challenges").send({ - group: 'habitrpg', - dailys: [], - todos: [], - rewards: [], - habits: [], - prize: 10 - }).end(function(err, res) { - expect(res.body.prize).to.equal(10); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - user = results[0]; - challenge = results[1]; - expect(user.balance).to.equal(5.5); - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, _user) { - expect(_user.balance).to.equal(5.5); - return done(); - }); - }); + }); + it("admin creates a challenge", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "contributor.admin": true + } + }, { + "new": true + }, function(err, _user) { + expect(err).to.not.exist; + return async.parallel([ + function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + official: false + }).end(function(err, res) { + expect(res.body.official).to.equal(false); + return cb(); }); - }); - }); + }, function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + official: true + }).end(function(err, res) { + expect(res.body.official).to.equal(true); + return cb(); + }); + } + ], done); }); - return describe("non-owner permissions", function() { - challenge = void 0; - beforeEach(function(done) { - return async.waterfall([ + }); + it("User creates a non-tavern challenge with prize, deletes it, gets refund", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "balance": 8 + } + }, { + "new": true + }, function(err, user) { + expect(err).to.not.be.ok; + return request.post(baseURL + "/challenges").send({ + group: group._id, + dailys: [], + todos: [], + rewards: [], + habits: [], + prize: 10 + }).end(function(err, res) { + expect(res.body.prize).to.equal(10); + return async.parallel([ function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - name: 'challenge name', - dailys: [ - { - type: "daily", - text: "Challenge Daily" - } - ] - }).end(function(err, res) { - challenge = res.body; - return cb(); - }); + return User.findById(user._id, cb); }, function(cb) { - return registerNewUser(done, true); + return Challenge.findById(res.body._id, cb); } - ]); - }); - context("non-owner", function() { - it('can not edit challenge', function(done) { - challenge.name = 'foobar'; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to edit this challenge"); - return done(); - }); - }); - it('can not close challenge', function(done) { - return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to close this challenge"); - return done(); - }); - }); - return it('can not delete challenge', function(done) { + ], function(err, results) { + user = results[0]; + challenge = results[1]; + expect(user.balance).to.equal(5.5); return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to delete this challenge"); - return done(); - }); - }); - }); - return context("non-owner that is an admin", function() { - beforeEach(function(done) { - return User.findByIdAndUpdate(user._id, { - 'contributor.admin': true - }, { - "new": true - }, done); - }); - it('can edit challenge', function(done) { - challenge.name = 'foobar'; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - expect(res.body.err).to.not.exist; - return Challenge.findById(challenge._id, function(err, chal) { - expect(chal.name).to.eql('foobar'); - return done(); - }); - }); - }); - it('can close challenge', function(done) { - return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { - expect(res.body.err).to.not.exist; - return User.findById(user._id, function(err, usr) { - expect(usr.achievements.challenges[0]).to.eql(challenge.name); - return done(); - }); - }); - }); - return it('can delete challenge', function(done) { - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - expect(res.body.err).to.not.exist; - return request.get(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("Challenge " + challenge._id + " not found"); + return User.findById(user._id, function(err, _user) { + expect(_user.balance).to.equal(8); return done(); }); }); @@ -412,5 +281,132 @@ }); }); }); - -}).call(this); + it("User creates a tavern challenge with prize, deletes it, and does not get refund", function(done) { + return User.findByIdAndUpdate(user._id, { + $set: { + "balance": 8 + } + }, { + "new": true + }, function(err, user) { + expect(err).to.not.be.ok; + return request.post(baseURL + "/challenges").send({ + group: 'habitrpg', + dailys: [], + todos: [], + rewards: [], + habits: [], + prize: 10 + }).end(function(err, res) { + expect(res.body.prize).to.equal(10); + return async.parallel([ + function(cb) { + return User.findById(user._id, cb); + }, function(cb) { + return Challenge.findById(res.body._id, cb); + } + ], function(err, results) { + user = results[0]; + challenge = results[1]; + expect(user.balance).to.equal(5.5); + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + return User.findById(user._id, function(err, _user) { + expect(_user.balance).to.equal(5.5); + return done(); + }); + }); + }); + }); + }); + }); + return describe("non-owner permissions", function() { + challenge = void 0; + beforeEach(function(done) { + return async.waterfall([ + function(cb) { + return request.post(baseURL + "/challenges").send({ + group: group._id, + name: 'challenge name', + dailys: [ + { + type: "daily", + text: "Challenge Daily" + } + ] + }).end(function(err, res) { + challenge = res.body; + return cb(); + }); + }, function(cb) { + return registerNewUser(done, true); + } + ]); + }); + context("non-owner", function() { + it('can not edit challenge', function(done) { + challenge.name = 'foobar'; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to edit this challenge"); + return done(); + }); + }); + it('can not close challenge', function(done) { + return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to close this challenge"); + return done(); + }); + }); + return it('can not delete challenge', function(done) { + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("You don't have permissions to delete this challenge"); + return done(); + }); + }); + }); + return context("non-owner that is an admin", function() { + beforeEach(function(done) { + return User.findByIdAndUpdate(user._id, { + 'contributor.admin': true + }, { + "new": true + }, done); + }); + it('can edit challenge', function(done) { + challenge.name = 'foobar'; + return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { + expect(res.body.err).to.not.exist; + return Challenge.findById(challenge._id, function(err, chal) { + expect(chal.name).to.eql('foobar'); + return done(); + }); + }); + }); + it('can close challenge', function(done) { + return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { + expect(res.body.err).to.not.exist; + return User.findById(user._id, function(err, usr) { + expect(usr.achievements.challenges[0]).to.eql(challenge.name); + return done(); + }); + }); + }); + return it('can delete challenge', function(done) { + return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + expect(res.body.err).to.not.exist; + return request.get(baseURL + "/challenges/" + challenge._id).end(function(err, res) { + var error; + error = res.body.err; + expect(error).to.eql("Challenge " + challenge._id + " not found"); + return done(); + }); + }); + }); + }); + }); +}); diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js index d1b1a0709b..89b32b0c54 100644 --- a/test/api-legacy/chat.js +++ b/test/api-legacy/chat.js @@ -1,75 +1,71 @@ -(function() { - 'use strict'; - var Group, app, diff; +var Group, app, diff; - diff = require("deep-diff"); +diff = require("deep-diff"); - Group = require("../../website/src/models/group").model; +Group = require("../../website/src/models/group").model; - app = require("../../website/src/server"); +app = require("../../website/src/server"); - describe("Chat", function() { - var chat, group; - group = void 0; - before(function(done) { - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - } - ], done); - }); - chat = void 0; - return it("removes a user's chat notifications when user is kicked", function(done) { - var userToRemove; - userToRemove = null; - return async.waterfall([ - function(cb) { - return registerManyUsers(1, cb); - }, function(members, cb) { - userToRemove = members[0]; - return request.post(baseURL + "/groups/" + group._id + "/invite").send({ - uuids: [userToRemove._id] - }).end(function() { - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - return cb(); - }); - }, function(cb) { - var msg; - msg = "TestMsg"; - return request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - expect(res.body.newMessages[group._id]).to.exist; - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - expect(res.body.newMessages[group._id]).to.not.exist; - return cb(); - }); - } - ], done); - }); +describe("Chat", function() { + var chat, group; + group = void 0; + before(function(done) { + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); + }); + } + ], done); }); - -}).call(this); + chat = void 0; + return it("removes a user's chat notifications when user is kicked", function(done) { + var userToRemove; + userToRemove = null; + return async.waterfall([ + function(cb) { + return registerManyUsers(1, cb); + }, function(members, cb) { + userToRemove = members[0]; + return request.post(baseURL + "/groups/" + group._id + "/invite").send({ + uuids: [userToRemove._id] + }).end(function() { + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + return cb(); + }); + }, function(cb) { + var msg; + msg = "TestMsg"; + return request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + expect(res.body.newMessages[group._id]).to.exist; + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { + expect(res.body.newMessages[group._id]).to.not.exist; + return cb(); + }); + } + ], done); + }); +}); diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js index 51ded4c0fc..af315c549c 100644 --- a/test/api-legacy/coupons.js +++ b/test/api-legacy/coupons.js @@ -1,222 +1,218 @@ -(function() { - 'use strict'; - var Coupon, app, makeSudoUser; +var Coupon, app, makeSudoUser; - app = require("../../website/src/server"); +app = require("../../website/src/server"); - Coupon = require("../../website/src/models/coupon").model; +Coupon = require("../../website/src/models/coupon").model; - makeSudoUser = function(usr, cb) { - return registerNewUser(function() { - var sudoUpdate; - sudoUpdate = { - "$set": { - "contributor.sudo": true - } - }; - return User.findByIdAndUpdate(user._id, sudoUpdate, { - "new": true - }, function(err, _user) { - usr = _user; - return cb(); - }); - }, true); - }; - - describe("Coupons", function() { - var coupons; - before(function(done) { - return async.parallel([ - function(cb) { - return mongoose.connection.collections['coupons'].drop(function(err) { - return cb(); - }); - }, function(cb) { - return mongoose.connection.collections['users'].drop(function(err) { - return cb(); - }); - } - ], done); +makeSudoUser = function(usr, cb) { + return registerNewUser(function() { + var sudoUpdate; + sudoUpdate = { + "$set": { + "contributor.sudo": true + } + }; + return User.findByIdAndUpdate(user._id, sudoUpdate, { + "new": true + }, function(err, _user) { + usr = _user; + return cb(); }); - coupons = null; - describe("POST /api/v2/coupons/generate/:event", function() { - context("while sudo user", function() { - before(function(done) { - return makeSudoUser(user, done); + }, true); +}; + +describe("Coupons", function() { + var coupons; + before(function(done) { + return async.parallel([ + function(cb) { + return mongoose.connection.collections['coupons'].drop(function(err) { + return cb(); }); - return it("generates coupons", function(done) { - var queries; - queries = '?count=10'; - return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { - expectCode(res, 200); - return Coupon.find({ - event: 'wondercon' - }, function(err, _coupons) { - coupons = _coupons; - expect(coupons.length).to.equal(10); - _(coupons).each(function(c) { - return expect(c.event).to.equal('wondercon'); - }); - return done(); - }); - }); + }, function(cb) { + return mongoose.connection.collections['users'].drop(function(err) { + return cb(); }); + } + ], done); + }); + coupons = null; + describe("POST /api/v2/coupons/generate/:event", function() { + context("while sudo user", function() { + before(function(done) { + return makeSudoUser(user, done); }); - return context("while regular user", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("does not generate coupons", function(done) { - var queries; - queries = '?count=10'; - return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { - expectCode(res, 401); - expect(res.body.err).to.equal('You don\'t have admin access'); - return done(); - }); - }); - }); - }); - describe("GET /api/v2/coupons", function() { - context("while sudo user", function() { - before(function(done) { - return makeSudoUser(user, done); - }); - it("gets coupons", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes; - expectCode(res, 200); - codes = res.text; - expect(codes).to.contain('code'); + return it("generates coupons", function(done) { + var queries; + queries = '?count=10'; + return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { + expectCode(res, 200); + return Coupon.find({ + event: 'wondercon' + }, function(err, _coupons) { + coupons = _coupons; + expect(coupons.length).to.equal(10); _(coupons).each(function(c) { - return expect(codes).to.contain(c._id); + return expect(c.event).to.equal('wondercon'); }); return done(); }); }); - it("gets first 5 coupons out of 10 when a limit of 5 is set", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5'; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes, firstHalf, secondHalf, sortedCoupons; - expectCode(res, 200); - codes = res.text; - sortedCoupons = _.sortBy(coupons, 'seq'); - firstHalf = sortedCoupons.slice(0, 5); - secondHalf = sortedCoupons.slice(5, 10); - _(firstHalf).each(function(c) { - return expect(codes).to.contain(c._id); - }); - _(secondHalf).each(function(c) { - return expect(codes).to.not.contain(c._id); - }); - return done(); - }); - }); - return it("gets last 5 coupons out of 10 when a limit of 5 is set", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5'; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes, firstHalf, secondHalf, sortedCoupons; - expectCode(res, 200); - codes = res.text; - sortedCoupons = _.sortBy(coupons, 'seq'); - firstHalf = sortedCoupons.slice(0, 5); - secondHalf = sortedCoupons.slice(5, 10); - _(firstHalf).each(function(c) { - return expect(codes).to.not.contain(c._id); - }); - _(secondHalf).each(function(c) { - return expect(codes).to.contain(c._id); - }); - return done(); - }); - }); - }); - return context("while regular user", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("does not get coupons", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - expectCode(res, 401); - expect(res.body.err).to.equal('You don\'t have admin access'); - return done(); - }); - }); }); }); - return describe("POST /api/v2/user/coupon/:code", function() { - var specialGear; - specialGear = function(gear, has) { - var items; - items = ['body_special_wondercon_gold', 'body_special_wondercon_black', 'body_special_wondercon_red', 'back_special_wondercon_red', 'back_special_wondercon_black', 'back_special_wondercon_red', 'eyewear_special_wondercon_black', 'eyewear_special_wondercon_red']; - return _(items).each(function(i) { - if (has) { - return expect(gear[i]).to.exist; - } else { - return expect(gear[i]).to.not.exist; - } - }); - }; - beforeEach(function(done) { - return registerNewUser(function() { - var gear; - gear = user.items.gear.owned; - specialGear(gear, false); - return done(); - }, true); + return context("while regular user", function() { + before(function(done) { + return registerNewUser(done, true); }); - context("unused coupon", function() { - return it("applies coupon and awards equipment", function(done) { - var code; - code = coupons[0]._id; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + return it("does not generate coupons", function(done) { + var queries; + queries = '?count=10'; + return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { + expectCode(res, 401); + expect(res.body.err).to.equal('You don\'t have admin access'); + return done(); + }); + }); + }); + }); + describe("GET /api/v2/coupons", function() { + context("while sudo user", function() { + before(function(done) { + return makeSudoUser(user, done); + }); + it("gets coupons", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes; + expectCode(res, 200); + codes = res.text; + expect(codes).to.contain('code'); + _(coupons).each(function(c) { + return expect(codes).to.contain(c._id); + }); + return done(); + }); + }); + it("gets first 5 coupons out of 10 when a limit of 5 is set", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5'; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes, firstHalf, secondHalf, sortedCoupons; + expectCode(res, 200); + codes = res.text; + sortedCoupons = _.sortBy(coupons, 'seq'); + firstHalf = sortedCoupons.slice(0, 5); + secondHalf = sortedCoupons.slice(5, 10); + _(firstHalf).each(function(c) { + return expect(codes).to.contain(c._id); + }); + _(secondHalf).each(function(c) { + return expect(codes).to.not.contain(c._id); + }); + return done(); + }); + }); + return it("gets last 5 coupons out of 10 when a limit of 5 is set", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5'; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + var codes, firstHalf, secondHalf, sortedCoupons; + expectCode(res, 200); + codes = res.text; + sortedCoupons = _.sortBy(coupons, 'seq'); + firstHalf = sortedCoupons.slice(0, 5); + secondHalf = sortedCoupons.slice(5, 10); + _(firstHalf).each(function(c) { + return expect(codes).to.not.contain(c._id); + }); + _(secondHalf).each(function(c) { + return expect(codes).to.contain(c._id); + }); + return done(); + }); + }); + }); + return context("while regular user", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return it("does not get coupons", function(done) { + var queries; + queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; + return request.get(baseURL + '/coupons' + queries).end(function(err, res) { + expectCode(res, 401); + expect(res.body.err).to.equal('You don\'t have admin access'); + return done(); + }); + }); + }); + }); + return describe("POST /api/v2/user/coupon/:code", function() { + var specialGear; + specialGear = function(gear, has) { + var items; + items = ['body_special_wondercon_gold', 'body_special_wondercon_black', 'body_special_wondercon_red', 'back_special_wondercon_red', 'back_special_wondercon_black', 'back_special_wondercon_red', 'eyewear_special_wondercon_black', 'eyewear_special_wondercon_red']; + return _(items).each(function(i) { + if (has) { + return expect(gear[i]).to.exist; + } else { + return expect(gear[i]).to.not.exist; + } + }); + }; + beforeEach(function(done) { + return registerNewUser(function() { + var gear; + gear = user.items.gear.owned; + specialGear(gear, false); + return done(); + }, true); + }); + context("unused coupon", function() { + return it("applies coupon and awards equipment", function(done) { + var code; + code = coupons[0]._id; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + var gear; + expectCode(res, 200); + gear = res.body.items.gear.owned; + specialGear(gear, true); + return done(); + }); + }); + }); + context("already used coupon", function() { + return it("does not apply coupon and does not award equipment", function(done) { + var code; + code = coupons[0]._id; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + expectCode(res, 400); + expect(res.body.err).to.equal("Coupon already used"); + return User.findById(user._id, function(err, _user) { var gear; - expectCode(res, 200); - gear = res.body.items.gear.owned; - specialGear(gear, true); + gear = _user.items.gear.owned; + specialGear(gear, false); return done(); }); }); }); - context("already used coupon", function() { - return it("does not apply coupon and does not award equipment", function(done) { - var code; - code = coupons[0]._id; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { - expectCode(res, 400); - expect(res.body.err).to.equal("Coupon already used"); - return User.findById(user._id, function(err, _user) { - var gear; - gear = _user.items.gear.owned; - specialGear(gear, false); - return done(); - }); - }); - }); - }); - return context("invalid coupon", function() { - return it("does not apply coupon and does not award equipment", function(done) { - var code; - code = "not-a-real-coupon"; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { - expectCode(res, 400); - expect(res.body.err).to.equal("Invalid coupon code"); - return User.findById(user._id, function(err, _user) { - var gear; - gear = _user.items.gear.owned; - specialGear(gear, false); - return done(); - }); + }); + return context("invalid coupon", function() { + return it("does not apply coupon and does not award equipment", function(done) { + var code; + code = "not-a-real-coupon"; + return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { + expectCode(res, 400); + expect(res.body.err).to.equal("Invalid coupon code"); + return User.findById(user._id, function(err, _user) { + var gear; + gear = _user.items.gear.owned; + specialGear(gear, false); + return done(); }); }); }); }); }); - -}).call(this); +}); diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js index 7eaea6c1be..b50fecdf3d 100644 --- a/test/api-legacy/inAppPurchases.js +++ b/test/api-legacy/inAppPurchases.js @@ -1,370 +1,366 @@ -(function() { - 'use strict'; - var app, iapMock, inApp, rewire, sinon; +var app, iapMock, inApp, rewire, sinon; - app = require('../../website/src/server'); +app = require('../../website/src/server'); - rewire = require('rewire'); +rewire = require('rewire'); - sinon = require('sinon'); +sinon = require('sinon'); - inApp = rewire('../../website/src/controllers/payments/iap'); +inApp = rewire('../../website/src/controllers/payments/iap'); - iapMock = {}; +iapMock = {}; - inApp.__set__('iap', iapMock); +inApp.__set__('iap', iapMock); - describe('In-App Purchases', function() { - describe('Android', function() { - var next, paymentSpy, req, res; - req = { - body: { - transaction: { - reciept: 'foo', - signature: 'sig' - } +describe('In-App Purchases', function() { + describe('Android', function() { + var next, paymentSpy, req, res; + req = { + body: { + transaction: { + reciept: 'foo', + signature: 'sig' } - }; - res = { - locals: { - user: { - _id: 'user' - } - }, - json: sinon.spy() - }; - next = function() { - return true; - }; - paymentSpy = sinon.spy(); + } + }; + res = { + locals: { + user: { + _id: 'user' + } + }, + json: sinon.spy() + }; + next = function() { + return true; + }; + paymentSpy = sinon.spy(); + before(function() { + return inApp.__set__('payments.buyGems', paymentSpy); + }); + afterEach(function() { + paymentSpy.reset(); + return res.json.reset(); + }); + context('successful app purchase', function() { before(function() { - return inApp.__set__('payments.buyGems', paymentSpy); + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(googleRes) { + return googleRes; + }; + return iapMock.GOOGLE = 'google'; }); - afterEach(function() { - paymentSpy.reset(); - return res.json.reset(); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: true, + data: true + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); }); - context('successful app purchase', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(googleRes) { - return googleRes; - }; - return iapMock.GOOGLE = 'google'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: true, - data: true - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - expect(paymentSpy).to.be.calledOnce; - return expect(paymentSpy).to.be.calledWith({ - user: res.locals.user, - paymentMethod: 'IAP GooglePlay' - }); - }); - }); - context('error in setup', function() { - before(function() { - return iapMock.setup = function(cb) { - return cb("error in setup"); - }; - }); - it('calls res.json with setup error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: 'IAP Error' - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - context('error in validation', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - return iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb('error in validation', true); - }; - }); - it('calls res.json with validation error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'error in validation' - } - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - return context('iap is not valid', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb(null, false); - }; - return iapMock.isValidated = function(googleRes) { - return googleRes; - }; - }); - it('does not call res.json', function() { - inApp.androidVerify(req, res, next); - return expect(res.json).to.not.be.called; - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; + return it('calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + expect(paymentSpy).to.be.calledOnce; + return expect(paymentSpy).to.be.calledWith({ + user: res.locals.user, + paymentMethod: 'IAP GooglePlay' }); }); }); - return describe('iOS', function() { - var next, paymentSpy, req, res; - req = { - body: { - transaction: { - reciept: 'foo' - } - } - }; - res = { - locals: { - user: { - _id: 'user' - } - }, - json: sinon.spy() - }; - next = function() { - return true; - }; - paymentSpy = sinon.spy(); + context('error in setup', function() { before(function() { - return inApp.__set__('payments.buyGems', paymentSpy); + return iapMock.setup = function(cb) { + return cb("error in setup"); + }; }); - afterEach(function() { - paymentSpy.reset(); - return res.json.reset(); + it('calls res.json with setup error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: 'IAP Error' + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); }); - context('successful app purchase', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return [ - { - productId: 'com.habitrpg.ios.Habitica.20gems' - } - ]; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: true, - data: true - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - expect(paymentSpy).to.be.calledOnce; - return expect(paymentSpy).to.be.calledWith({ - user: res.locals.user, - paymentMethod: 'IAP AppleStore', - amount: 5.25 - }); - }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; }); - context('error in setup', function() { - before(function() { - return iapMock.setup = function(cb) { - return cb("error in setup"); - }; - }); - it('calls res.json with setup error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: 'IAP Error' - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); + }); + context('error in validation', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + return iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb('error in validation', true); + }; }); - context('error in validation', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - return iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb('error in validation', true); - }; - }); - it('calls res.json with validation error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'error in validation' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); + it('calls res.json with validation error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'error in validation' + } + }; + inApp.androidVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); }); - context('iap is not valid', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, false); - }; - return iapMock.isValidated = function(appleRes) { - return appleRes; - }; - }); - it('does not call res.json', function() { - var expectedResObj; - inApp.iosVerify(req, res, next); - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Invalid receipt' - } - }; - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; }); - context('iap is valid but has no purchaseDataList', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return []; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Incorrect receipt content' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); + }); + return context('iap is not valid', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { + return cb(null, false); + }; + return iapMock.isValidated = function(googleRes) { + return googleRes; + }; }); - return context('iap is valid, has purchaseDataList, but productId does not match', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return [ - { - productId: 'com.another.company' - } - ]; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with incorrect reciept obj', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Incorrect receipt content' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); + it('does not call res.json', function() { + inApp.androidVerify(req, res, next); + return expect(res.json).to.not.be.called; + }); + return it('does not calls payments.buyGems function', function() { + inApp.androidVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; }); }); }); - -}).call(this); + return describe('iOS', function() { + var next, paymentSpy, req, res; + req = { + body: { + transaction: { + reciept: 'foo' + } + } + }; + res = { + locals: { + user: { + _id: 'user' + } + }, + json: sinon.spy() + }; + next = function() { + return true; + }; + paymentSpy = sinon.spy(); + before(function() { + return inApp.__set__('payments.buyGems', paymentSpy); + }); + afterEach(function() { + paymentSpy.reset(); + return res.json.reset(); + }); + context('successful app purchase', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return [ + { + productId: 'com.habitrpg.ios.Habitica.20gems' + } + ]; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: true, + data: true + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + expect(paymentSpy).to.be.calledOnce; + return expect(paymentSpy).to.be.calledWith({ + user: res.locals.user, + paymentMethod: 'IAP AppleStore', + amount: 5.25 + }); + }); + }); + context('error in setup', function() { + before(function() { + return iapMock.setup = function(cb) { + return cb("error in setup"); + }; + }); + it('calls res.json with setup error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: 'IAP Error' + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('error in validation', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + return iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb('error in validation', true); + }; + }); + it('calls res.json with validation error object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'error in validation' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('iap is not valid', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, false); + }; + return iapMock.isValidated = function(appleRes) { + return appleRes; + }; + }); + it('does not call res.json', function() { + var expectedResObj; + inApp.iosVerify(req, res, next); + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Invalid receipt' + } + }; + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + context('iap is valid but has no purchaseDataList', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return []; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with succesful result object', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Incorrect receipt content' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + return context('iap is valid, has purchaseDataList, but productId does not match', function() { + before(function() { + iapMock.setup = function(cb) { + return cb(null); + }; + iapMock.validate = function(iapApple, iapBodyReciept, cb) { + return cb(null, true); + }; + iapMock.isValidated = function(appleRes) { + return appleRes; + }; + iapMock.getPurchaseData = function(appleRes) { + return [ + { + productId: 'com.another.company' + } + ]; + }; + return iapMock.APPLE = 'apple'; + }); + it('calls res.json with incorrect reciept obj', function() { + var expectedResObj; + expectedResObj = { + ok: false, + data: { + code: 6778001, + message: 'Incorrect receipt content' + } + }; + inApp.iosVerify(req, res, next); + expect(res.json).to.be.calledOnce; + return expect(res.json).to.be.calledWith(expectedResObj); + }); + return it('does not calls payments.buyGems function', function() { + inApp.iosVerify(req, res, next); + return expect(paymentSpy).to.not.be.called; + }); + }); + }); +}); diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js index 0d84de8583..8ea8188713 100644 --- a/test/api-legacy/party.js +++ b/test/api-legacy/party.js @@ -1,237 +1,74 @@ -(function() { - 'use strict'; - var Group, app, diff; +var Group, app, diff; - diff = require("deep-diff"); +diff = require("deep-diff"); - Group = require("../../website/src/models/group").model; +Group = require("../../website/src/models/group").model; - app = require("../../website/src/server"); +app = require("../../website/src/server"); - describe("Party", function() { - return context("Quests", function() { - var group, notParticipating, participating, party; - party = void 0; - group = void 0; - participating = []; - notParticipating = []; - beforeEach(function(done) { - Group.update({ - _id: "habitrpg" - }, { - $set: { - quest: { - key: "dilatory", - active: true, - progress: { - hp: shared.content.quests.dilatory.boss.hp, - rage: 0 - } +describe("Party", function() { + return context("Quests", function() { + var group, notParticipating, participating, party; + party = void 0; + group = void 0; + participating = []; + notParticipating = []; + beforeEach(function(done) { + Group.update({ + _id: "habitrpg" + }, { + $set: { + quest: { + key: "dilatory", + active: true, + progress: { + hp: shared.content.quests.dilatory.boss.hp, + rage: 0 } } - }).exec(); - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - }, function(cb) { - return request.post(baseURL + '/user/tasks').send({ - type: 'daily', - text: 'daily one' - }).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.post(baseURL + '/user/tasks').send({ - type: 'daily', - text: 'daily two' - }).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return User.findByIdAndUpdate(user._id, { - $set: { - "stats.lvl": 50 - } - }, { - "new": true - }, function(err, _user) { - return cb(null, _user); - }); - }, function(_user, cb) { - var user; - user = _user; - return request.post(baseURL + "/user/batch-update").send([ - { - op: "score", - params: { - direction: "up", - id: user.dailys[0].id - } - }, { - op: "score", - params: { - direction: "up", - id: user.dailys[0].id - } - }, { - op: "update", - body: { - "stats.lvl": 50 - } - } - ]).end(function(err, res) { - user = res.body; - expect(user.party.quest.progress.up).to.be.above(0); - return async.waterfall([ - function(cb) { - return registerManyUsers(3, cb); - }, function(_party, cb) { - var inviteURL; - party = _party; - inviteURL = baseURL + "/groups/" + group._id + "/invite"; - return async.parallel([ - function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[0]._id] - }).end(function() { - return cb2(); - }); - }, function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[1]._id] - }).end(function() { - return cb2(); - }); - }, function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[2]._id] - }).end(function(err, res) { - return cb2(); - }); - } - ], cb); - }, function(results, cb) { - var series; - series = _.reduce(party, function(m, v, i) { - m.push(function(cb2) { - return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end(function() { - return cb2(); - }); - }); - return m; - }, []); - return async.series(series, cb); - }, function(whatever, cb) { - return Group.findById(group._id, function(err, g) { - group = g; - expect(g.members.length).to.equal(4); - return cb(); - }); - } - ], function() { - return async.waterfall([ - function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { - expectCode(res, 400); - return User.findByIdAndUpdate(user._id, { - $set: { - "items.quests.vice3": 1 - } - }, { - "new": true - }, cb); - }); - }, function(_user, cb) { - return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { - expectCode(res, 200); - return Group.findById(group._id, cb); - }); - }, function(_group, cb) { - expect(_group.quest.key).to.equal("vice3"); - expect(_group.quest.active).to.equal(false); - return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end(function() { - return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end(function(err, res) { - return request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end(function(err, res) { - group = res.body; - expect(group.quest.active).to.equal(true); - return cb(); - }); - }); - }); - } - ], done); - }); - }); - } - ]); - }); - it("Casts a spell", function(done) { - var mp; - mp = user.stats.mp; - return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { - party[0] = res.body; - return request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end(function(err, res) { - return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { - var difference, member; - member = res.body; - expect(member.achievements.snowball).to.equal(1); - expect(member.stats.buffs.snowball).to.exist; - difference = diff(member, party[0]); - expect(_.size(difference)).to.equal(2); - return request.put(baseURL + "/user").send({ - "stats.lvl": 5 - }).end(function(err, res) { - return request.put(baseURL + "/user").send({ - "stats.mp": 100 - }).end(function(err, res) { - return request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end(function(err, res) { - return request.get(baseURL + "/members/" + member._id).end(function(err, res) { - expect(res.body.stats.buffs.str).to.be.above(0); - expect(diff(res.body, member).length).to.equal(1); - return done(); - }); - }); - }); - }); - }); + } + }).exec(); + return async.waterfall([ + function(cb) { + return registerNewUser(cb, true); + }, function(user, cb) { + return request.post(baseURL + "/groups").send({ + name: "TestGroup", + type: "party" + }).end(function(err, res) { + expectCode(res, 200); + group = res.body; + expect(group.members.length).to.equal(1); + expect(group.leader).to.equal(user._id); + return cb(); }); - }); - }); - it("Doesn't include people who aren't participating", function(done) { - return request.get(baseURL + "/groups/" + group._id).end(function(err, res) { - expect(_.size(res.body.quest.members)).to.equal(3); - return done(); - }); - }); - it("allows quest participants to leave quest", function(done) { - var leavingMember; - leavingMember = party[1]; - expect(group.quest.members[leavingMember._id]).to.eql(true); - return request.post(baseURL + "/groups/" + group._id + "/questLeave").set("X-API-User", leavingMember._id).set("X-API-Key", leavingMember.apiToken).end(function(err, res) { - expectCode(res, 204); - return request.get(baseURL + '/groups/party').end(function(err, res) { - expect(res.body.quest.members[leavingMember._id]).to.not.be.ok; - return done(); + }, function(cb) { + return request.post(baseURL + '/user/tasks').send({ + type: 'daily', + text: 'daily one' + }).end(function(err, res) { + return cb(); }); - }); - }); - return xit("Hurts the boss", function(done) { - return request.post(baseURL + "/user/batch-update").end(function(err, res) { - var up, user; - user = res.body; - up = user.party.quest.progress.up; - expect(up).to.be.above(0); + }, function(cb) { + return request.post(baseURL + '/user/tasks').send({ + type: 'daily', + text: 'daily two' + }).end(function(err, res) { + return cb(); + }); + }, function(cb) { + return User.findByIdAndUpdate(user._id, { + $set: { + "stats.lvl": 50 + } + }, { + "new": true + }, function(err, _user) { + return cb(null, _user); + }); + }, function(_user, cb) { + var user; + user = _user; return request.post(baseURL + "/user/batch-update").send([ { op: "score", @@ -239,138 +76,297 @@ direction: "up", id: user.dailys[0].id } + }, { + op: "score", + params: { + direction: "up", + id: user.dailys[0].id + } }, { op: "update", body: { - lastCron: moment().subtract(1, "days") + "stats.lvl": 50 } } ]).end(function(err, res) { - expect(res.body.party.quest.progress.up).to.be.above(up); - return request.post(baseURL + "/user/batch-update").end(function() { - return request.get(baseURL + "/groups/party").end(function(err, res) { - return async.waterfall([ - function(cb) { - return async.parallel([ - function(cb2) { - return Group.findById("habitrpg", { - quest: 1 - }, function(err, tavern) { - expect(tavern.quest.progress.hp).to.be.below(shared.content.quests.dilatory.boss.hp); - expect(tavern.quest.progress.rage).to.be.above(0); - return cb2(); - }); - }, function(cb2) { - var _party; - expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp); - _party = res.body.members; - expect(_.find(_party, { - _id: party[0]._id - }).stats.hp).to.be.below(50); - expect(_.find(_party, { - _id: party[1]._id - }).stats.hp).to.be.below(50); - expect(_.find(_party, { - _id: party[2]._id - }).stats.hp).to.be(50); - return cb2(); - } - ], cb); - }, function(whatever, cb) { - return async.waterfall([ - function(cb2) { - expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok(); - return Group.update({ - _id: "habitrpg" - }, { - $set: { - "quest.progress.hp": 0 - } - }, cb2); - }, function(arg1, arg2, cb2) { - expect(user.items.gear.owned.weapon_special_2).to.not.be.ok(); - return Group.findByIdAndUpdate(group._id, { - $set: { - "quest.progress.hp": 0 - } - }, { - "new": true - }, cb2); - } - ], cb); - }, function(_group, cb) { - return request.post(baseURL + "/user/batch-update").send([ - { - op: "score", - params: { - direction: "up", - id: user.dailys[1].id - } - }, { - op: "update", - body: { - lastCron: moment().subtract(1, "days") - } - } - ]).end(function() { - return cb(); + user = res.body; + expect(user.party.quest.progress.up).to.be.above(0); + return async.waterfall([ + function(cb) { + return registerManyUsers(3, cb); + }, function(_party, cb) { + var inviteURL; + party = _party; + inviteURL = baseURL + "/groups/" + group._id + "/invite"; + return async.parallel([ + function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[0]._id] + }).end(function() { + return cb2(); }); - }, function(cb) { - return request.post(baseURL + "/user/batch-update").end(function(err, res) { - return cb(null, res.body); + }, function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[1]._id] + }).end(function() { + return cb2(); + }); + }, function(cb2) { + return request.post(inviteURL).send({ + uuids: [party[2]._id] + }).end(function(err, res) { + return cb2(); }); - }, function(_user, cb) { - return User.findById(_user._id, cb); - }, function(_user, cb) { - user = _user; - return Group.findById(group._id, cb); - }, function(_group, cb) { - var cummExp, cummGp; - cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp; - cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp; - return async.parallel([ - function(cb2) { - return Group.findById("habitrpg", function(err, tavern) { - expect(_.isEmpty(tavern.get("quest"))).to.equal(true); - expect(user.items.pets["MantisShrimp-Base"]).to.equal(5); - expect(user.items.mounts["MantisShrimp-Base"]).to.equal(true); - expect(user.items.eggs.Dragon).to.equal(2); - expect(user.items.hatchingPotions.Shade).to.equal(2); - return cb2(); - }); - }, function(cb2) { - expect(_.isEmpty(_group.get("quest"))).to.equal(true); - expect(user.items.gear.owned.weapon_special_2).to.equal(true); - expect(user.items.eggs.Dragon).to.equal(2); - expect(user.items.hatchingPotions.Shade).to.equal(2); - return async.parallel([ - function(cb3) { - return User.findById(party[0].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); - return cb3(); - }); - }, function(cb3) { - return User.findById(party[1].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); - return cb3(); - }); - }, function(cb3) { - return User.findById(party[2].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok(); - return cb3(); - }); - } - ], cb2); - } - ], cb); } - ], done); + ], cb); + }, function(results, cb) { + var series; + series = _.reduce(party, function(m, v, i) { + m.push(function(cb2) { + return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end(function() { + return cb2(); + }); + }); + return m; + }, []); + return async.series(series, cb); + }, function(whatever, cb) { + return Group.findById(group._id, function(err, g) { + group = g; + expect(g.members.length).to.equal(4); + return cb(); + }); + } + ], function() { + return async.waterfall([ + function(cb) { + return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { + expectCode(res, 400); + return User.findByIdAndUpdate(user._id, { + $set: { + "items.quests.vice3": 1 + } + }, { + "new": true + }, cb); + }); + }, function(_user, cb) { + return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { + expectCode(res, 200); + return Group.findById(group._id, cb); + }); + }, function(_group, cb) { + expect(_group.quest.key).to.equal("vice3"); + expect(_group.quest.active).to.equal(false); + return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end(function() { + return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end(function(err, res) { + return request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end(function(err, res) { + group = res.body; + expect(group.quest.active).to.equal(true); + return cb(); + }); + }); + }); + } + ], done); + }); + }); + } + ]); + }); + it("Casts a spell", function(done) { + var mp; + mp = user.stats.mp; + return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { + party[0] = res.body; + return request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end(function(err, res) { + return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { + var difference, member; + member = res.body; + expect(member.achievements.snowball).to.equal(1); + expect(member.stats.buffs.snowball).to.exist; + difference = diff(member, party[0]); + expect(_.size(difference)).to.equal(2); + return request.put(baseURL + "/user").send({ + "stats.lvl": 5 + }).end(function(err, res) { + return request.put(baseURL + "/user").send({ + "stats.mp": 100 + }).end(function(err, res) { + return request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end(function(err, res) { + return request.get(baseURL + "/members/" + member._id).end(function(err, res) { + expect(res.body.stats.buffs.str).to.be.above(0); + expect(diff(res.body, member).length).to.equal(1); + return done(); + }); + }); }); }); }); }); }); }); + it("Doesn't include people who aren't participating", function(done) { + return request.get(baseURL + "/groups/" + group._id).end(function(err, res) { + expect(_.size(res.body.quest.members)).to.equal(3); + return done(); + }); + }); + it("allows quest participants to leave quest", function(done) { + var leavingMember; + leavingMember = party[1]; + expect(group.quest.members[leavingMember._id]).to.eql(true); + return request.post(baseURL + "/groups/" + group._id + "/questLeave").set("X-API-User", leavingMember._id).set("X-API-Key", leavingMember.apiToken).end(function(err, res) { + expectCode(res, 204); + return request.get(baseURL + '/groups/party').end(function(err, res) { + expect(res.body.quest.members[leavingMember._id]).to.not.be.ok; + return done(); + }); + }); + }); + return xit("Hurts the boss", function(done) { + return request.post(baseURL + "/user/batch-update").end(function(err, res) { + var up, user; + user = res.body; + up = user.party.quest.progress.up; + expect(up).to.be.above(0); + return request.post(baseURL + "/user/batch-update").send([ + { + op: "score", + params: { + direction: "up", + id: user.dailys[0].id + } + }, { + op: "update", + body: { + lastCron: moment().subtract(1, "days") + } + } + ]).end(function(err, res) { + expect(res.body.party.quest.progress.up).to.be.above(up); + return request.post(baseURL + "/user/batch-update").end(function() { + return request.get(baseURL + "/groups/party").end(function(err, res) { + return async.waterfall([ + function(cb) { + return async.parallel([ + function(cb2) { + return Group.findById("habitrpg", { + quest: 1 + }, function(err, tavern) { + expect(tavern.quest.progress.hp).to.be.below(shared.content.quests.dilatory.boss.hp); + expect(tavern.quest.progress.rage).to.be.above(0); + return cb2(); + }); + }, function(cb2) { + var _party; + expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp); + _party = res.body.members; + expect(_.find(_party, { + _id: party[0]._id + }).stats.hp).to.be.below(50); + expect(_.find(_party, { + _id: party[1]._id + }).stats.hp).to.be.below(50); + expect(_.find(_party, { + _id: party[2]._id + }).stats.hp).to.be(50); + return cb2(); + } + ], cb); + }, function(whatever, cb) { + return async.waterfall([ + function(cb2) { + expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok(); + return Group.update({ + _id: "habitrpg" + }, { + $set: { + "quest.progress.hp": 0 + } + }, cb2); + }, function(arg1, arg2, cb2) { + expect(user.items.gear.owned.weapon_special_2).to.not.be.ok(); + return Group.findByIdAndUpdate(group._id, { + $set: { + "quest.progress.hp": 0 + } + }, { + "new": true + }, cb2); + } + ], cb); + }, function(_group, cb) { + return request.post(baseURL + "/user/batch-update").send([ + { + op: "score", + params: { + direction: "up", + id: user.dailys[1].id + } + }, { + op: "update", + body: { + lastCron: moment().subtract(1, "days") + } + } + ]).end(function() { + return cb(); + }); + }, function(cb) { + return request.post(baseURL + "/user/batch-update").end(function(err, res) { + return cb(null, res.body); + }); + }, function(_user, cb) { + return User.findById(_user._id, cb); + }, function(_user, cb) { + user = _user; + return Group.findById(group._id, cb); + }, function(_group, cb) { + var cummExp, cummGp; + cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp; + cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp; + return async.parallel([ + function(cb2) { + return Group.findById("habitrpg", function(err, tavern) { + expect(_.isEmpty(tavern.get("quest"))).to.equal(true); + expect(user.items.pets["MantisShrimp-Base"]).to.equal(5); + expect(user.items.mounts["MantisShrimp-Base"]).to.equal(true); + expect(user.items.eggs.Dragon).to.equal(2); + expect(user.items.hatchingPotions.Shade).to.equal(2); + return cb2(); + }); + }, function(cb2) { + expect(_.isEmpty(_group.get("quest"))).to.equal(true); + expect(user.items.gear.owned.weapon_special_2).to.equal(true); + expect(user.items.eggs.Dragon).to.equal(2); + expect(user.items.hatchingPotions.Shade).to.equal(2); + return async.parallel([ + function(cb3) { + return User.findById(party[0].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); + return cb3(); + }); + }, function(cb3) { + return User.findById(party[1].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); + return cb3(); + }); + }, function(cb3) { + return User.findById(party[2].id, function(err, mbr) { + expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok(); + return cb3(); + }); + } + ], cb2); + } + ], cb); + } + ], done); + }); + }); + }); + }); + }); }); - -}).call(this); +}); diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js index 9b414cf8da..ce3d9672f8 100644 --- a/test/api-legacy/pushNotifications.js +++ b/test/api-legacy/pushNotifications.js @@ -1,434 +1,430 @@ -(function() { - 'use strict'; - var app, rewire, sinon; +var app, rewire, sinon; - app = require("../../website/src/server"); +app = require("../../website/src/server"); - rewire = require('rewire'); +rewire = require('rewire'); - sinon = require('sinon'); +sinon = require('sinon'); - describe("Push-Notifications", function() { - before(function(done) { - return registerNewUser(done, true); +describe("Push-Notifications", function() { + before(function(done) { + return registerNewUser(done, true); + }); + return describe("Events that send push notifications", function() { + var pushSpy; + pushSpy = { + sendNotify: sinon.spy() + }; + afterEach(function(done) { + pushSpy.sendNotify.reset(); + return done(); }); - return describe("Events that send push notifications", function() { - var pushSpy; - pushSpy = { - sendNotify: sinon.spy() + context("Challenges", function() { + var challengeMock, challenges, userMock; + challenges = rewire("../../website/src/controllers/api-v2/challenges"); + challenges.__set__('pushNotify', pushSpy); + challengeMock = { + findById: function(arg, cb) { + return cb(null, { + leader: user._id, + name: 'challenge-name' + }); + } }; - afterEach(function(done) { - pushSpy.sendNotify.reset(); - return done(); + userMock = { + findById: function(arg, cb) { + return cb(null, user); + } + }; + challenges.__set__('Challenge', challengeMock); + challenges.__set__('User', userMock); + challenges.__set__('closeChal', function() { + return true; }); - context("Challenges", function() { - var challengeMock, challenges, userMock; - challenges = rewire("../../website/src/controllers/api-v2/challenges"); - challenges.__set__('pushNotify', pushSpy); - challengeMock = { - findById: function(arg, cb) { - return cb(null, { - leader: user._id, - name: 'challenge-name' - }); - } - }; - userMock = { - findById: function(arg, cb) { + beforeEach(function(done) { + return registerNewUser(function() { + user.preferences.emailNotifications.wonChallenge = false; + user.save = function(cb) { return cb(null, user); + }; + return done(); + }, true); + }); + return it("sends a push notification when you win a challenge", function(done) { + var req, res; + req = { + params: { + cid: 'challenge-id' + }, + query: { + uid: 'user-id' } }; - challenges.__set__('Challenge', challengeMock); - challenges.__set__('User', userMock); - challenges.__set__('closeChal', function() { - return true; - }); - beforeEach(function(done) { - return registerNewUser(function() { - user.preferences.emailNotifications.wonChallenge = false; - user.save = function(cb) { - return cb(null, user); - }; - return done(); - }, true); - }); - return it("sends a push notification when you win a challenge", function(done) { - var req, res; - req = { - params: { - cid: 'challenge-id' - }, - query: { - uid: 'user-id' - } - }; - res = { - locals: { - user: user - } - }; - challenges.selectWinner(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(user, 'You won a Challenge!', 'challenge-name'); - return done(); - }, 100); - }); + res = { + locals: { + user: user + } + }; + challenges.selectWinner(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(user, 'You won a Challenge!', 'challenge-name'); + return done(); + }, 100); }); - context("Groups", function() { - var groups, recipient; - recipient = null; - groups = rewire("../../website/src/controllers/api-v2/groups"); - groups.__set__('pushNotify', pushSpy); - before(function(done) { - return registerNewUser(function(err, _user) { - var userMock; - recipient = _user; - recipient.invitations.guilds = []; - recipient.save = function(cb) { - return cb(null, recipient); - }; - recipient.preferences.emailNotifications.invitedGuild = false; - recipient.preferences.emailNotifications.invitedParty = false; - recipient.preferences.emailNotifications.invitedQuest = false; - userMock = { - findById: function(arg, cb) { - return cb(null, recipient); - }, - find: function(arg, arg2, cb) { - return cb(null, [recipient]); - }, - update: function(arg, arg2) { - return { - exec: function() { - return true; - } - }; - } - }; - groups.__set__('User', userMock); - return done(); - }, false); - }); - it("sends a push notification when invited to a guild", function(done) { - var group, req, res; - group = { - _id: 'guild-id', - name: 'guild-name', - type: 'guild', - members: [user._id], - invites: [] - }; - group.save = function(cb) { - return cb(null, group); - }; - req = { - body: { - uuids: [recipient._id] - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.invite(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Guild', group.name); - return done(); - }, 100); - }); - it("sends a push notification when invited to a party", function(done) { - var group, req, res; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id], - invites: [] - }; - group.save = function(cb) { - return cb(null, group); - }; - req = { - body: { - uuids: [recipient._id] - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.invite(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Party', group.name); - return done(); - }, 100); - }); - it("sends a push notification when invited to a quest", function(done) { - var group, req, res; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id, recipient._id], - invites: [], - quest: {} - }; - user.items.quests.hedgehog = 5; - group.save = function(cb) { - return cb(null, group); - }; - group.markModified = function() { - return true; - }; - req = { - body: { - uuids: [recipient._id] - }, - query: { - key: 'hedgehog' - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.questAccept(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Quest Invitation', 'Invitation for the Quest The Hedgebeast'); - return done(); - }, 100); - }); - return it("sends a push notification to participating members when quest starts", function(done) { - var group, req, res, userMock; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id, recipient._id], - invites: [] - }; - group.quest = { - key: 'hedgehog', - progress: { - hp: 100 - }, - members: {} - }; - group.quest.members[recipient._id] = true; - group.save = function(cb) { - return cb(null, group); - }; - group.markModified = function() { - return true; - }; - req = { - body: { - uuids: [recipient._id] - }, - query: {} - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } + }); + context("Groups", function() { + var groups, recipient; + recipient = null; + groups = rewire("../../website/src/controllers/api-v2/groups"); + groups.__set__('pushNotify', pushSpy); + before(function(done) { + return registerNewUser(function(err, _user) { + var userMock; + recipient = _user; + recipient.invitations.guilds = []; + recipient.save = function(cb) { + return cb(null, recipient); }; + recipient.preferences.emailNotifications.invitedGuild = false; + recipient.preferences.emailNotifications.invitedParty = false; + recipient.preferences.emailNotifications.invitedQuest = false; userMock = { - findOne: function(arg, arg2, cb) { + findById: function(arg, cb) { return cb(null, recipient); }, - update: function(arg, arg2, cb) { - if (cb) { - return cb(null, user); - } else { - return { - exec: function() { - return true; - } - }; - } + find: function(arg, arg2, cb) { + return cb(null, [recipient]); + }, + update: function(arg, arg2) { + return { + exec: function() { + return true; + } + }; } }; groups.__set__('User', userMock); - groups.__set__('populateQuery', function(arg, arg2, arg3) { - return { - exec: function() { - return group.members; + return done(); + }, false); + }); + it("sends a push notification when invited to a guild", function(done) { + var group, req, res; + group = { + _id: 'guild-id', + name: 'guild-name', + type: 'guild', + members: [user._id], + invites: [] + }; + group.save = function(cb) { + return cb(null, group); + }; + req = { + body: { + uuids: [recipient._id] + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.invite(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Guild', group.name); + return done(); + }, 100); + }); + it("sends a push notification when invited to a party", function(done) { + var group, req, res; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id], + invites: [] + }; + group.save = function(cb) { + return cb(null, group); + }; + req = { + body: { + uuids: [recipient._id] + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.invite(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Party', group.name); + return done(); + }, 100); + }); + it("sends a push notification when invited to a quest", function(done) { + var group, req, res; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id, recipient._id], + invites: [], + quest: {} + }; + user.items.quests.hedgehog = 5; + group.save = function(cb) { + return cb(null, group); + }; + group.markModified = function() { + return true; + }; + req = { + body: { + uuids: [recipient._id] + }, + query: { + key: 'hedgehog' + } + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + groups.questAccept(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Quest Invitation', 'Invitation for the Quest The Hedgebeast'); + return done(); + }, 100); + }); + return it("sends a push notification to participating members when quest starts", function(done) { + var group, req, res, userMock; + group = { + _id: 'party-id', + name: 'party-name', + type: 'party', + members: [user._id, recipient._id], + invites: [] + }; + group.quest = { + key: 'hedgehog', + progress: { + hp: 100 + }, + members: {} + }; + group.quest.members[recipient._id] = true; + group.save = function(cb) { + return cb(null, group); + }; + group.markModified = function() { + return true; + }; + req = { + body: { + uuids: [recipient._id] + }, + query: {} + }; + res = { + locals: { + group: group, + user: user + }, + json: function() { + return true; + } + }; + userMock = { + findOne: function(arg, arg2, cb) { + return cb(null, recipient); + }, + update: function(arg, arg2, cb) { + if (cb) { + return cb(null, user); + } else { + return { + exec: function() { + return true; + } + }; + } + } + }; + groups.__set__('User', userMock); + groups.__set__('populateQuery', function(arg, arg2, arg3) { + return { + exec: function() { + return group.members; + } + }; + }); + groups.questAccept(req, res); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledTwice; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'HabitRPG', 'Your Quest has Begun: The Hedgebeast'); + return done(); + }, 100); + }); + }); + return describe("Gifts", function() { + var recipient; + recipient = null; + before(function(done) { + return registerNewUser(function(err, _user) { + recipient = _user; + recipient.preferences.emailNotifications.giftedGems = false; + user.balance = 4; + user.save = function() { + return true; + }; + recipient.save = function() { + return true; + }; + return done(); + }, false); + }); + context("sending gems from balance", function() { + var members; + members = rewire("../../website/src/controllers/api-v2/members"); + members.sendMessage = function() { + return true; + }; + members.__set__('pushNotify', pushSpy); + members.__set__('fetchMember', function(id) { + return function(cb) { + return cb(null, recipient); + }; + }); + return it("sends a push notification", function(done) { + var req, res; + req = { + params: { + uuid: "uuid" + }, + body: { + type: 'gems', + gems: { + amount: 1 } - }; - }); - groups.questAccept(req, res); + } + }; + res = { + locals: { + user: user + } + }; + members.sendGift(req, res); return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledTwice; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'HabitRPG', 'Your Quest has Begun: The Hedgebeast'); + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); return done(); }, 100); }); }); - return describe("Gifts", function() { - var recipient; - recipient = null; - before(function(done) { - return registerNewUser(function(err, _user) { - recipient = _user; - recipient.preferences.emailNotifications.giftedGems = false; - user.balance = 4; - user.save = function() { - return true; - }; - recipient.save = function() { - return true; - }; - return done(); - }, false); - }); - context("sending gems from balance", function() { - var members; - members = rewire("../../website/src/controllers/api-v2/members"); - members.sendMessage = function() { + return describe("Purchases", function() { + var membersMock, payments; + payments = rewire("../../website/src/controllers/payments"); + payments.__set__('pushNotify', pushSpy); + membersMock = { + sendMessage: function() { return true; - }; - members.__set__('pushNotify', pushSpy); - members.__set__('fetchMember', function(id) { - return function(cb) { - return cb(null, recipient); - }; - }); - return it("sends a push notification", function(done) { - var req, res; - req = { - params: { - uuid: "uuid" - }, - body: { - type: 'gems', + } + }; + payments.__set__('members', membersMock); + context("buying gems as a purchased gift", function() { + it("sends a push notification", function(done) { + var data; + data = { + user: user, + gift: { + member: recipient, gems: { amount: 1 } } }; - res = { - locals: { - user: user - } - }; - members.sendGift(req, res); + payments.buyGems(data); return setTimeout(function() { expect(pushSpy.sendNotify).to.have.been.calledOnce; expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); return done(); }, 100); }); - }); - return describe("Purchases", function() { - var membersMock, payments; - payments = rewire("../../website/src/controllers/payments"); - payments.__set__('pushNotify', pushSpy); - membersMock = { - sendMessage: function() { - return true; - } - }; - payments.__set__('members', membersMock); - context("buying gems as a purchased gift", function() { - it("sends a push notification", function(done) { - var data; - data = { - user: user, - gift: { - member: recipient, - gems: { - amount: 1 - } + return it("does not send a push notification if buying gems for self", function(done) { + var data; + data = { + user: user, + gift: { + member: user, + gems: { + amount: 1 } - }; - payments.buyGems(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); - return done(); - }, 100); - }); - return it("does not send a push notification if buying gems for self", function(done) { - var data; - data = { - user: user, - gift: { - member: user, - gems: { - amount: 1 - } - } - }; - payments.buyGems(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.not.have.been.called; - return done(); - }, 100); - }); + } + }; + payments.buyGems(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.not.have.been.called; + return done(); + }, 100); }); - return context("sending a subscription as a purchased gift", function() { - it("sends a push notification", function(done) { - var data; - data = { - user: user, - gift: { - member: recipient, - subscription: { - key: 'basic_6mo' - } + }); + return context("sending a subscription as a purchased gift", function() { + it("sends a push notification", function(done) { + var data; + data = { + user: user, + gift: { + member: recipient, + subscription: { + key: 'basic_6mo' } - }; - payments.createSubscription(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Subscription', '6 months - by ' + user.profile.name); - return done(); - }, 100); - }); - return it("does not send a push notification if buying subscription for self", function(done) { - var data; - data = { - user: user, - gift: { - member: user, - subscription: { - key: 'basic_6mo' - } + } + }; + payments.createSubscription(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.have.been.calledOnce; + expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Subscription', '6 months - by ' + user.profile.name); + return done(); + }, 100); + }); + return it("does not send a push notification if buying subscription for self", function(done) { + var data; + data = { + user: user, + gift: { + member: user, + subscription: { + key: 'basic_6mo' } - }; - payments.createSubscription(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.not.have.been.called; - return done(); - }, 100); - }); + } + }; + payments.createSubscription(data); + return setTimeout(function() { + expect(pushSpy.sendNotify).to.not.have.been.called; + return done(); + }, 100); }); }); }); }); }); - -}).call(this); +}); diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js index 18a18b8a78..8c6906acfb 100644 --- a/test/api-legacy/score.js +++ b/test/api-legacy/score.js @@ -1,146 +1,142 @@ -(function() { - 'use strict'; - require("../../website/src/server"); +require("../../website/src/server"); - describe("Score", function() { - before(function(done) { - return registerNewUser(done, true); - }); - context("Todos that did not previously exist", function() { - it("creates a completed a todo when using up url", function(done) { - return request.post(baseURL + "/user/tasks/withUp/up").send({ - type: "todo", - text: "withUp" - }).end(function(err, res) { - expectCode(res, 200); - request.get(baseURL + "/user/tasks/withUp").end(function(err, res) { - var upTodo; - upTodo = res.body; - return expect(upTodo.completed).to.equal(true); - }); - return done(); - }); - }); - it("creates an uncompleted todo when using down url", function(done) { - return request.post(baseURL + "/user/tasks/withDown/down").send({ - type: "todo", - text: "withDown" - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withDown").end(function(err, res) { - var downTodo; - downTodo = res.body; - expect(downTodo.completed).to.equal(false); - return done(); - }); - }); - }); - it("creates a completed a todo overriding the complete parameter when using up url", function(done) { - return request.post(baseURL + "/user/tasks/withUpWithComplete/up").send({ - type: "todo", - text: "withUpWithComplete", - completed: false - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withUpWithComplete").end(function(err, res) { - var upTodo; - upTodo = res.body; - expect(upTodo.completed).to.equal(true); - return done(); - }); - }); - }); - return it("Creates an uncompleted todo overriding the completed parameter when using down url", function(done) { - return request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send({ - type: "todo", - text: "withDownWithUncomplete", - completed: true - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withDownWithUncomplete").end(function(err, res) { - var downTodo; - downTodo = res.body; - expect(downTodo.completed).to.equal(false); - return done(); - }); - }); - }); - }); - context("Todo that already exists", function() { - it("It completes a todo when using up url", function(done) { - return request.post(baseURL + "/user/tasks").send({ - type: "todo", - text: "Sample Todo" - }).end(function(err, res) { - var unCompletedTodo; - expectCode(res, 200); - unCompletedTodo = res.body; - expect(unCompletedTodo.completed).to.equal(false); - return request.post(baseURL + "/user/tasks/" + unCompletedTodo._id + "/up").end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/" + unCompletedTodo._id).end(function(err, res) { - unCompletedTodo = res.body; - expect(unCompletedTodo.completed).to.equal(true); - return done(); - }); - }); - }); - }); - return it("It uncompletes a todo when using down url", function(done) { - return request.post(baseURL + "/user/tasks").send({ - type: "todo", - text: "Sample Todo", - completed: true - }).end(function(err, res) { - var completedTodo; - expectCode(res, 200); - completedTodo = res.body; - expect(completedTodo.completed).to.equal(true); - return request.post(baseURL + "/user/tasks/" + completedTodo._id + "/down").end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/" + completedTodo._id).end(function(err, res) { - completedTodo = res.body; - expect(completedTodo.completed).to.equal(false); - return done(); - }); - }); - }); - }); - }); - it("Creates and scores up a habit when using up url", function(done) { - var upHabit; - upHabit = void 0; - return request.post(baseURL + "/user/tasks/habitWithUp/up").send({ - type: "habit", - text: "testTitle", - completed: true +describe("Score", function() { + before(function(done) { + return registerNewUser(done, true); + }); + context("Todos that did not previously exist", function() { + it("creates a completed a todo when using up url", function(done) { + return request.post(baseURL + "/user/tasks/withUp/up").send({ + type: "todo", + text: "withUp" }).end(function(err, res) { expectCode(res, 200); - return request.get(baseURL + "/user/tasks/habitWithUp").end(function(err, res) { - upHabit = res.body; - expect(upHabit.value).to.be.at.least(1); - expect(upHabit.type).to.equal("habit"); + request.get(baseURL + "/user/tasks/withUp").end(function(err, res) { + var upTodo; + upTodo = res.body; + return expect(upTodo.completed).to.equal(true); + }); + return done(); + }); + }); + it("creates an uncompleted todo when using down url", function(done) { + return request.post(baseURL + "/user/tasks/withDown/down").send({ + type: "todo", + text: "withDown" + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/withDown").end(function(err, res) { + var downTodo; + downTodo = res.body; + expect(downTodo.completed).to.equal(false); return done(); }); }); }); - return it("Creates and scores down a habit when using down url", function(done) { - var downHabit; - downHabit = void 0; - return request.post(baseURL + "/user/tasks/habitWithDown/down").send({ - type: "habit", - text: "testTitle", + it("creates a completed a todo overriding the complete parameter when using up url", function(done) { + return request.post(baseURL + "/user/tasks/withUpWithComplete/up").send({ + type: "todo", + text: "withUpWithComplete", + completed: false + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/withUpWithComplete").end(function(err, res) { + var upTodo; + upTodo = res.body; + expect(upTodo.completed).to.equal(true); + return done(); + }); + }); + }); + return it("Creates an uncompleted todo overriding the completed parameter when using down url", function(done) { + return request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send({ + type: "todo", + text: "withDownWithUncomplete", completed: true }).end(function(err, res) { expectCode(res, 200); - return request.get(baseURL + "/user/tasks/habitWithDown").end(function(err, res) { - downHabit = res.body; - expect(downHabit.value).to.have.at.most(-1); - expect(downHabit.type).to.equal("habit"); + return request.get(baseURL + "/user/tasks/withDownWithUncomplete").end(function(err, res) { + var downTodo; + downTodo = res.body; + expect(downTodo.completed).to.equal(false); return done(); }); }); }); }); - -}).call(this); + context("Todo that already exists", function() { + it("It completes a todo when using up url", function(done) { + return request.post(baseURL + "/user/tasks").send({ + type: "todo", + text: "Sample Todo" + }).end(function(err, res) { + var unCompletedTodo; + expectCode(res, 200); + unCompletedTodo = res.body; + expect(unCompletedTodo.completed).to.equal(false); + return request.post(baseURL + "/user/tasks/" + unCompletedTodo._id + "/up").end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/" + unCompletedTodo._id).end(function(err, res) { + unCompletedTodo = res.body; + expect(unCompletedTodo.completed).to.equal(true); + return done(); + }); + }); + }); + }); + return it("It uncompletes a todo when using down url", function(done) { + return request.post(baseURL + "/user/tasks").send({ + type: "todo", + text: "Sample Todo", + completed: true + }).end(function(err, res) { + var completedTodo; + expectCode(res, 200); + completedTodo = res.body; + expect(completedTodo.completed).to.equal(true); + return request.post(baseURL + "/user/tasks/" + completedTodo._id + "/down").end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/" + completedTodo._id).end(function(err, res) { + completedTodo = res.body; + expect(completedTodo.completed).to.equal(false); + return done(); + }); + }); + }); + }); + }); + it("Creates and scores up a habit when using up url", function(done) { + var upHabit; + upHabit = void 0; + return request.post(baseURL + "/user/tasks/habitWithUp/up").send({ + type: "habit", + text: "testTitle", + completed: true + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/habitWithUp").end(function(err, res) { + upHabit = res.body; + expect(upHabit.value).to.be.at.least(1); + expect(upHabit.type).to.equal("habit"); + return done(); + }); + }); + }); + return it("Creates and scores down a habit when using down url", function(done) { + var downHabit; + downHabit = void 0; + return request.post(baseURL + "/user/tasks/habitWithDown/down").send({ + type: "habit", + text: "testTitle", + completed: true + }).end(function(err, res) { + expectCode(res, 200); + return request.get(baseURL + "/user/tasks/habitWithDown").end(function(err, res) { + downHabit = res.body; + expect(downHabit.value).to.have.at.most(-1); + expect(downHabit.type).to.equal("habit"); + return done(); + }); + }); + }); +}); diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js index df008124e9..9d8624cb73 100644 --- a/test/api-legacy/subscriptions.js +++ b/test/api-legacy/subscriptions.js @@ -1,54 +1,50 @@ -(function() { - 'use strict'; - var app, payments; +var app, payments; - payments = require("../../website/src/controllers/payments"); +payments = require("../../website/src/controllers/payments"); - app = require("../../website/src/server"); +app = require("../../website/src/server"); - describe("Subscriptions", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("Handles unsubscription", function(done) { - var cron; - cron = function() { - user.lastCron = moment().subtract(1, "d"); - return user.fns.cron(); - }; - expect(user.purchased.plan.customerId).to.not.exist; - payments.createSubscription({ - user: user, - customerId: "123", - paymentMethod: "Stripe", - sub: { - key: 'basic_6mo' - } - }); - expect(user.purchased.plan.customerId).to.exist; - shared.wrap(user); - cron(); - expect(user.purchased.plan.customerId).to.exist; - payments.cancelSubscription({ - user: user - }); - cron(); - expect(user.purchased.plan.customerId).to.exist; - expect(user.purchased.plan.dateTerminated).to.exist; - user.purchased.plan.dateTerminated = moment().subtract(2, "d"); - cron(); - expect(user.purchased.plan.customerId).to.not.exist; - payments.createSubscription({ - user: user, - customerId: "123", - paymentMethod: "Stripe", - sub: { - key: 'basic_6mo' - } - }); - expect(user.purchased.plan.dateTerminated).to.not.exist; - return done(); - }); +describe("Subscriptions", function() { + before(function(done) { + return registerNewUser(done, true); }); - -}).call(this); + return it("Handles unsubscription", function(done) { + var cron; + cron = function() { + user.lastCron = moment().subtract(1, "d"); + return user.fns.cron(); + }; + expect(user.purchased.plan.customerId).to.not.exist; + payments.createSubscription({ + user: user, + customerId: "123", + paymentMethod: "Stripe", + sub: { + key: 'basic_6mo' + } + }); + expect(user.purchased.plan.customerId).to.exist; + shared.wrap(user); + cron(); + expect(user.purchased.plan.customerId).to.exist; + payments.cancelSubscription({ + user: user + }); + cron(); + expect(user.purchased.plan.customerId).to.exist; + expect(user.purchased.plan.dateTerminated).to.exist; + user.purchased.plan.dateTerminated = moment().subtract(2, "d"); + cron(); + expect(user.purchased.plan.customerId).to.not.exist; + payments.createSubscription({ + user: user, + customerId: "123", + paymentMethod: "Stripe", + sub: { + key: 'basic_6mo' + } + }); + expect(user.purchased.plan.dateTerminated).to.not.exist; + return done(); + }); +}); diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js index ea4163991f..5285847fd1 100644 --- a/test/api-legacy/todos.js +++ b/test/api-legacy/todos.js @@ -1,91 +1,87 @@ -(function() { - 'use strict'; - require("../../website/src/server"); +require("../../website/src/server"); - describe("Todos", function() { - before(function(done) { - return registerNewUser(done, true); +describe("Todos", function() { + before(function(done) { + return registerNewUser(done, true); + }); + beforeEach(function(done) { + return User.findById(user._id, function(err, _user) { + var user; + user = _user; + shared.wrap(user); + return done(); }); - beforeEach(function(done) { - return User.findById(user._id, function(err, _user) { - var user; - user = _user; - shared.wrap(user); - return done(); - }); - }); - return it("Archives old todos", function(done) { - var numTasks; - numTasks = _.size(user.todos); - return request.post(baseURL + "/user/batch-update?_v=999").send([ + }); + return it("Archives old todos", function(done) { + var numTasks; + numTasks = _.size(user.todos); + return request.post(baseURL + "/user/batch-update?_v=999").send([ + { + op: "addTask", + body: { + type: "todo" + } + }, { + op: "addTask", + body: { + type: "todo" + } + }, { + op: "addTask", + body: { + type: "todo" + } + } + ]).end(function(err, res) { + expectCode(res, 200); + expect(_.size(res.body.todos)).to.equal(numTasks + 3); + numTasks += 3; + return request.post(baseURL + "/user/batch-update?_v=998").send([ { - op: "addTask", - body: { - type: "todo" + op: "score", + params: { + direction: "up", + id: res.body.todos[0].id } }, { - op: "addTask", - body: { - type: "todo" + op: "score", + params: { + direction: "up", + id: res.body.todos[1].id } }, { - op: "addTask", - body: { - type: "todo" + op: "score", + params: { + direction: "up", + id: res.body.todos[2].id } } ]).end(function(err, res) { expectCode(res, 200); - expect(_.size(res.body.todos)).to.equal(numTasks + 3); - numTasks += 3; - return request.post(baseURL + "/user/batch-update?_v=998").send([ + expect(_.size(res.body.todos)).to.equal(numTasks); + return request.post(baseURL + "/user/batch-update?_v=997").send([ { - op: "score", + op: "updateTask", params: { - direction: "up", id: res.body.todos[0].id + }, + body: { + dateCompleted: moment().subtract(4, "days") } }, { - op: "score", + op: "updateTask", params: { - direction: "up", id: res.body.todos[1].id - } - }, { - op: "score", - params: { - direction: "up", - id: res.body.todos[2].id + }, + body: { + dateCompleted: moment().subtract(4, "days") } } ]).end(function(err, res) { - expectCode(res, 200); - expect(_.size(res.body.todos)).to.equal(numTasks); - return request.post(baseURL + "/user/batch-update?_v=997").send([ - { - op: "updateTask", - params: { - id: res.body.todos[0].id - }, - body: { - dateCompleted: moment().subtract(4, "days") - } - }, { - op: "updateTask", - params: { - id: res.body.todos[1].id - }, - body: { - dateCompleted: moment().subtract(4, "days") - } - } - ]).end(function(err, res) { - expect(_.size(res.body.todos)).to.equal(numTasks - 2); - return done(); - }); + expect(_.size(res.body.todos)).to.equal(numTasks - 2); + return done(); }); }); }); }); - -}).call(this); +}); diff --git a/test/common/dailies.js b/test/common/dailies.js index dcf9f9db32..92d80ee5ad 100644 --- a/test/common/dailies.js +++ b/test/common/dailies.js @@ -1,541 +1,538 @@ -(function() { - var _, cron, expect, moment, newUser, repeatWithoutLastWeekday, shared, sinon; +var _, cron, expect, moment, newUser, repeatWithoutLastWeekday, shared, sinon; - _ = require('lodash'); +_ = require('lodash'); - expect = require('expect.js'); +expect = require('expect.js'); - sinon = require('sinon'); +sinon = require('sinon'); - moment = require('moment'); +moment = require('moment'); - shared = require('../../common/script/index.js'); +shared = require('../../common/script/index.js'); - shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; - repeatWithoutLastWeekday = function() { - var repeat; - repeat = { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true - }; - if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { - repeat.su = false; - } else { - repeat.s = false; - } - return { - repeat: repeat - }; +repeatWithoutLastWeekday = function() { + var repeat; + repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true }; + if (shared.startOfWeek(moment().zone(0)).isoWeekday() === 1) { + repeat.su = false; + } else { + repeat.s = false; + } + return { + repeat: repeat + }; +}; - /* Helper Functions */ +/* Helper Functions */ - newUser = function(addTasks) { - var buffs, user; - if (addTasks == null) { - addTasks = true; - } - buffs = { - per: 0, - int: 0, - con: 0, - str: 0, - stealth: 0, - streaks: false - }; - user = { - auth: { - timestamps: {} +newUser = function(addTasks) { + var buffs, user; + if (addTasks == null) { + addTasks = true; + } + buffs = { + per: 0, + int: 0, + con: 0, + str: 0, + stealth: 0, + streaks: false + }; + user = { + auth: { + timestamps: {} + }, + stats: { + str: 1, + con: 1, + per: 1, + int: 1, + mp: 32, + "class": 'warrior', + buffs: buffs + }, + items: { + lastDrop: { + count: 0 }, - stats: { - str: 1, - con: 1, - per: 1, - int: 1, - mp: 32, - "class": 'warrior', - buffs: buffs - }, - items: { - lastDrop: { - count: 0 - }, - hatchingPotions: {}, - eggs: {}, - food: {}, - gear: { - equipped: {}, - costume: {} - } - }, - party: { - quest: { - progress: { - down: 0 - } - } - }, - preferences: {}, - dailys: [], - todos: [], - rewards: [], - flags: {}, - achievements: {}, - contributor: { - level: 2 + hatchingPotions: {}, + eggs: {}, + food: {}, + gear: { + equipped: {}, + costume: {} } - }; - shared.wrap(user); - user.ops.reset(null, function() {}); - if (addTasks) { - _.each(['habit', 'todo', 'daily'], function(task) { - return user.ops.addTask({ - body: { - type: task, - id: shared.uuid() - } - }); - }); + }, + party: { + quest: { + progress: { + down: 0 + } + } + }, + preferences: {}, + dailys: [], + todos: [], + rewards: [], + flags: {}, + achievements: {}, + contributor: { + level: 2 } - return user; }; - - cron = function(usr, missedDays) { - if (missedDays == null) { - missedDays = 1; - } - usr.lastCron = moment().subtract(missedDays, 'days'); - return 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() { - user = newUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().add(7, 'days'), - frequency: 'daily' - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().add(7, 'days'), - frequency: 'weekly', - repeat: { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true - } - }) - ]; - daily = user.dailys[0]; - return weekly = user.dailys[1]; - }); - it('does not damage user for not completing it', function() { - cron(user); - return expect(user.stats.hp).to.be(50); - }); - it('does not change value on cron if daily is incomplete', function() { - cron(user); - expect(daily.value).to.be(0); - return expect(weekly.value).to.be(0); - }); - it('does not reset checklists if daily is not marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - expect(daily.checklist[0].completed).to.be(true); - expect(daily.checklist[1].completed).to.be(true); - expect(daily.checklist[2].completed).to.be(false); - expect(weekly.checklist[0].completed).to.be(true); - expect(weekly.checklist[1].completed).to.be(true); - return expect(weekly.checklist[2].completed).to.be(false); - }); - it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - daily.completed = true; - weekly.completed = true; - cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - return _.each(weekly.checklist, function(box) { - return 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); - expect(daily_due_today).to.be(false); - expect(daily_due_on_start_date).to.be(true); - weekly_due_today = shared.shouldDo(moment(), weekly); - weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); - expect(weekly_due_today).to.be(false); - return expect(weekly_due_on_start_date).to.be(true); + shared.wrap(user); + user.ops.reset(null, function() {}); + if (addTasks) { + _.each(['habit', 'todo', 'daily'], function(task) { + return user.ops.addTask({ + body: { + type: task, + id: shared.uuid() + } }); }); - describe('when startDate is in the past', function() { - beforeEach(function() { - user = newUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - frequency: 'daily' - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - frequency: 'weekly' - }) - ]; - daily = user.dailys[0]; - return weekly = user.dailys[1]; - }); - it('does damage user for not completing it', function() { - cron(user); - return expect(user.stats.hp).to.be.lessThan(50); - }); - it('decreases value on cron if daily is incomplete', function() { - cron(user, 1); - expect(daily.value).to.be(-1); - return expect(weekly.value).to.be(-1); - }); - it('decreases value on cron once only if daily is incomplete and multiple days are missed', function() { - cron(user, 7); - expect(daily.value).to.be(-1); - return expect(weekly.value).to.be(-1); - }); - it('resets checklists if daily is not marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - daily.completed = true; - weekly.checklist = checklist; - weekly.completed = true; - cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - }); - }); - return describe('when startDate is today', function() { - beforeEach(function() { - user = newUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(1, 'days'), - frequency: 'daily' - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(1, 'days'), - frequency: 'weekly' - }) - ]; - daily = user.dailys[0]; - return weekly = user.dailys[1]; - }); - it('does damage user for not completing it', function() { - cron(user); - return expect(user.stats.hp).to.be.lessThan(50); - }); - it('decreases value on cron if daily is incomplete', function() { - cron(user); - expect(daily.value).to.be.lessThan(0); - return expect(weekly.value).to.be.lessThan(0); - }); - it('resets checklists if daily is not marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ - { - 'text': '1', - 'id': 'checklist-one', - 'completed': true - }, { - 'text': '2', - 'id': 'checklist-two', - 'completed': true - }, { - 'text': '3', - 'id': 'checklist-three', - 'completed': false - } - ]; - daily.checklist = checklist; - daily.completed = true; - weekly.checklist = checklist; - weekly.completed = true; - cron(user); - _.each(daily.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - return _.each(weekly.checklist, function(box) { - return expect(box.completed).to.be(false); - }); - }); - }); - }); + } + return user; +}; - describe('daily that repeats every x days', function() { - var daily, user; - user = null; - daily = null; +cron = function(usr, missedDays) { + if (missedDays == null) { + missedDays = 1; + } + usr.lastCron = moment().subtract(missedDays, 'days'); + return 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() { user = newUser(); user.dailys = [ shared.taskDefaults({ type: 'daily', - startDate: moment(), + startDate: moment().add(7, 'days'), frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().add(7, 'days'), + frequency: 'weekly', + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + } + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does not damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be(50); + }); + it('does not change value on cron if daily is incomplete', function() { + cron(user); + expect(daily.value).to.be(0); + return expect(weekly.value).to.be(0); + }); + it('does not reset checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + expect(daily.checklist[0].completed).to.be(true); + expect(daily.checklist[1].completed).to.be(true); + expect(daily.checklist[2].completed).to.be(false); + expect(weekly.checklist[0].completed).to.be(true); + expect(weekly.checklist[1].completed).to.be(true); + return expect(weekly.checklist[2].completed).to.be(false); + }); + it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + daily.completed = true; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return 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); + expect(daily_due_today).to.be(false); + expect(daily_due_on_start_date).to.be(true); + weekly_due_today = shared.shouldDo(moment(), weekly); + weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); + expect(weekly_due_today).to.be(false); + return expect(weekly_due_on_start_date).to.be(true); + }); + }); + describe('when startDate is in the past', function() { + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'weekly' + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be.lessThan(50); + }); + it('decreases value on cron if daily is incomplete', function() { + cron(user, 1); + expect(daily.value).to.be(-1); + return expect(weekly.value).to.be(-1); + }); + it('decreases value on cron once only if daily is incomplete and multiple days are missed', function() { + cron(user, 7); + expect(daily.value).to.be(-1); + return expect(weekly.value).to.be(-1); + }); + it('resets checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + }); + return describe('when startDate is today', function() { + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'daily' + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'weekly' + }) + ]; + daily = user.dailys[0]; + return weekly = user.dailys[1]; + }); + it('does damage user for not completing it', function() { + cron(user); + return expect(user.stats.hp).to.be.lessThan(50); + }); + it('decreases value on cron if daily is incomplete', function() { + cron(user); + expect(daily.value).to.be.lessThan(0); + return expect(weekly.value).to.be.lessThan(0); + }); + it('resets checklists if daily is not marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + 'text': '1', + 'id': 'checklist-one', + 'completed': true + }, { + 'text': '2', + 'id': 'checklist-two', + 'completed': true + }, { + 'text': '3', + 'id': 'checklist-three', + 'completed': false + } + ]; + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + return _.each(weekly.checklist, function(box) { + return expect(box.completed).to.be(false); + }); + }); + }); +}); + +describe('daily that repeats every x days', function() { + var daily, user; + user = null; + daily = null; + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment(), + frequency: 'daily' + }) + ]; + return daily = user.dailys[0]; + }); + return _.times(11, function(due) { + return it('where x equals ' + due, function() { + daily.everyX = due; + return _.times(30, function(day) { + var 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); + } + }); + }); + }); +}); + +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() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX: everyX }) ]; return daily = user.dailys[0]; }); - return _.times(11, function(due) { - return it('where x equals ' + due, function() { - daily.everyX = due; - return _.times(30, function(day) { - var 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); - } - }); + it('decreases value on cron once only if daily is incomplete', function() { + cron(user, missedDays); + return expect(daily.value).to.be(-1); + }); + it('resets checklists if daily is incomplete', function() { + var checklist; + checklist = [ + { + '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); + }); + }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + '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); }); }); }); - - 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() { - user = newUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(startDateDaysAgo, 'days'), - frequency: 'daily', - everyX: everyX - }) - ]; - return daily = user.dailys[0]; - }); - it('decreases value on cron once only if daily is incomplete', function() { - cron(user, missedDays); - return expect(daily.value).to.be(-1); - }); - it('resets checklists if daily is incomplete', function() { - var checklist; - checklist = [ - { - '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); - }); - }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ - { - '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); - }); + return describe('but not missing a due date', function() { + var missedDays; + missedDays = everyX - 1; + beforeEach(function() { + user = newUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX: everyX + }) + ]; + return daily = user.dailys[0]; + }); + it('does not decrease value on cron', function() { + cron(user, missedDays); + return expect(daily.value).to.be(0); + }); + it('does not reset checklists if daily is incomplete', function() { + var checklist; + checklist = [ + { + '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); }); }); - return describe('but not missing a due date', function() { - var missedDays; - missedDays = everyX - 1; - beforeEach(function() { - user = newUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(startDateDaysAgo, 'days'), - frequency: 'daily', - everyX: everyX - }) - ]; - return daily = user.dailys[0]; - }); - it('does not decrease value on cron', function() { - cron(user, missedDays); - return expect(daily.value).to.be(0); - }); - it('does not reset checklists if daily is incomplete', function() { - var checklist; - checklist = [ - { - '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); - }); - }); - return it('resets checklists if daily is marked as complete', function() { - var checklist; - checklist = [ - { - '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); - }); + return it('resets checklists if daily is marked as complete', function() { + var checklist; + checklist = [ + { + '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); }); }); }); - -}).call(this); +}); diff --git a/test/common/simulations/autoAllocate.js b/test/common/simulations/autoAllocate.js index 3621dbec3b..0b0348efee 100644 --- a/test/common/simulations/autoAllocate.js +++ b/test/common/simulations/autoAllocate.js @@ -1,164 +1,161 @@ -(function() { - var $w, _, id, modes, shared, user; +var $w, _, id, modes, shared, user; - shared = require('../../../common/script/index.js'); +shared = require('../../../common/script/index.js'); - _ = require('lodash'); +_ = require('lodash'); - $w = function(s) { - return s.split(' '); - }; +$w = function(s) { + return s.split(' '); +}; - id = shared.uuid(); +id = shared.uuid(); - user = { - stats: { - "class": 'warrior', - lvl: 1, - hp: 50, - gp: 0, - exp: 10, +user = { + stats: { + "class": 'warrior', + lvl: 1, + hp: 50, + gp: 0, + exp: 10, + per: 0, + int: 0, + con: 0, + str: 0, + buffs: { per: 0, int: 0, con: 0, - str: 0, - buffs: { + str: 0 + }, + training: { + int: 0, + con: 0, + per: 0, + str: 0 + } + }, + preferences: { + automaticAllocation: false + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + achievements: {}, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + { + id: 'a', + value: 1, + type: 'habit', + attribute: 'str' + } + ], + dailys: [ + { + id: 'b', + value: 1, + type: 'daily', + attribute: 'str' + } + ], + todos: [ + { + id: 'c', + value: 1, + type: 'todo', + attribute: 'con' + }, { + id: 'd', + value: 1, + type: 'todo', + attribute: 'per' + }, { + id: 'e', + value: 1, + type: 'todo', + attribute: 'int' + } + ], + rewards: [] +}; + +modes = { + flat: _.cloneDeep(user), + classbased_warrior: _.cloneDeep(user), + classbased_rogue: _.cloneDeep(user), + classbased_wizard: _.cloneDeep(user), + classbased_healer: _.cloneDeep(user), + taskbased: _.cloneDeep(user) +}; + +modes.classbased_warrior.stats["class"] = 'warrior'; + +modes.classbased_rogue.stats["class"] = 'rogue'; + +modes.classbased_wizard.stats["class"] = 'wizard'; + +modes.classbased_healer.stats["class"] = 'healer'; + +_.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + _.merge(modes[mode].preferences, { + automaticAllocation: true, + allocationMode: mode.indexOf('classbased') === 0 ? 'classbased' : mode + }); + return shared.wrap(modes[mode]); +}); + +console.log("\n\n================================================"); + +console.log("New Simulation"); + +console.log("================================================\n\n"); + +_.times([20], function(lvl) { + console.log("[lvl " + lvl + "]\n--------------\n"); + return _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + var str, u; + u = modes[mode]; + u.stats.exp = shared.tnl(lvl) + 1; + if (mode === 'taskbased') { + _.merge(u.stats, { per: 0, - int: 0, con: 0, + int: 0, str: 0 + }); + } + u.habits[0].attribute = u.fns.randomVal({ + str: 'str', + int: 'int', + per: 'per', + con: 'con' + }); + u.ops.score({ + params: { + id: u.habits[0].id }, - training: { - int: 0, - con: 0, - per: 0, - str: 0 - } - }, - preferences: { - automaticAllocation: false - }, - party: { - quest: { - key: 'evilsanta', - progress: { - up: 0, - down: 0 - } - } - }, - achievements: {}, - items: { - eggs: {}, - hatchingPotions: {}, - food: {}, - gear: { - equipped: { - weapon: 'weapon_warrior_4', - armor: 'armor_warrior_4', - shield: 'shield_warrior_4', - head: 'head_warrior_4' - } - } - }, - habits: [ - { - id: 'a', - value: 1, - type: 'habit', - attribute: 'str' - } - ], - dailys: [ - { - id: 'b', - value: 1, - type: 'daily', - attribute: 'str' - } - ], - todos: [ - { - id: 'c', - value: 1, - type: 'todo', - attribute: 'con' - }, { - id: 'd', - value: 1, - type: 'todo', - attribute: 'per' - }, { - id: 'e', - value: 1, - type: 'todo', - attribute: 'int' - } - ], - rewards: [] - }; - - modes = { - flat: _.cloneDeep(user), - classbased_warrior: _.cloneDeep(user), - classbased_rogue: _.cloneDeep(user), - classbased_wizard: _.cloneDeep(user), - classbased_healer: _.cloneDeep(user), - taskbased: _.cloneDeep(user) - }; - - modes.classbased_warrior.stats["class"] = 'warrior'; - - modes.classbased_rogue.stats["class"] = 'rogue'; - - modes.classbased_wizard.stats["class"] = 'wizard'; - - modes.classbased_healer.stats["class"] = 'healer'; - - _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { - _.merge(modes[mode].preferences, { - automaticAllocation: true, - allocationMode: mode.indexOf('classbased') === 0 ? 'classbased' : mode + direction: 'up' }); - return shared.wrap(modes[mode]); + u.fns.updateStats(u.stats); + str = mode + (mode === 'taskbased' ? " (" + u.habits[0].attribute + ")" : ""); + return console.log(str, _.pick(u.stats, $w('per int con str'))); }); - - console.log("\n\n================================================"); - - console.log("New Simulation"); - - console.log("================================================\n\n"); - - _.times([20], function(lvl) { - console.log("[lvl " + lvl + "]\n--------------\n"); - return _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { - var str, u; - u = modes[mode]; - u.stats.exp = shared.tnl(lvl) + 1; - if (mode === 'taskbased') { - _.merge(u.stats, { - per: 0, - con: 0, - int: 0, - str: 0 - }); - } - u.habits[0].attribute = u.fns.randomVal({ - str: 'str', - int: 'int', - per: 'per', - con: 'con' - }); - u.ops.score({ - params: { - id: u.habits[0].id - }, - direction: 'up' - }); - u.fns.updateStats(u.stats); - str = mode + (mode === 'taskbased' ? " (" + u.habits[0].attribute + ")" : ""); - return console.log(str, _.pick(u.stats, $w('per int con str'))); - }); - }); - -}).call(this); +}); diff --git a/test/common/simulations/passive_active_attrs.js b/test/common/simulations/passive_active_attrs.js index d22524cec3..f69a2cafc2 100644 --- a/test/common/simulations/passive_active_attrs.js +++ b/test/common/simulations/passive_active_attrs.js @@ -1,294 +1,291 @@ -(function() { - var _, clearUser, id, party, s, shared, task, user; +var _, clearUser, id, party, s, shared, task, user; - shared = require('../../../common/script/index.js'); +shared = require('../../../common/script/index.js'); - _ = require('lodash'); +_ = require('lodash'); - id = shared.uuid(); +id = shared.uuid(); - user = { - stats: { - "class": 'warrior', - buffs: { - per: 0, - int: 0, - con: 0, - str: 0 - } - }, - party: { - quest: { - key: 'evilsanta', - progress: { - up: 0, - down: 0 - } - } - }, - preferences: { - automaticAllocation: false - }, - achievements: {}, - flags: { - levelDrops: {} - }, - items: { - eggs: {}, - hatchingPotions: {}, - food: {}, - quests: {}, - gear: { - equipped: { - weapon: 'weapon_warrior_4', - armor: 'armor_warrior_4', - shield: 'shield_warrior_4', - head: 'head_warrior_4' - } - } - }, - habits: [ - shared.taskDefaults({ - id: id, - value: 0 - }) - ], - dailys: [ - { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - } - ], - todos: [], - rewards: [] - }; - - shared.wrap(user); - - s = user.stats; - - task = user.tasks[id]; - - party = [user]; - - console.log("\n\n================================================"); - - console.log("New Simulation"); - - console.log("================================================\n\n"); - - clearUser = function(lvl) { - if (lvl == null) { - lvl = 1; - } - _.merge(user.stats, { - exp: 0, - gp: 0, - hp: 50, - lvl: lvl, - str: lvl * 1.5, - con: lvl * 1.5, - per: lvl * 1.5, - int: lvl * 1.5, - mp: 100 - }); - _.merge(s.buffs, { - str: 0, - con: 0, +user = { + stats: { + "class": 'warrior', + buffs: { + per: 0, int: 0, - per: 0 - }); - _.merge(user.party.quest.progress, { - up: 0, - down: 0 - }); - return user.items.lastDrop = { - count: 0 - }; - }; + con: 0, + str: 0 + } + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + preferences: { + automaticAllocation: false + }, + achievements: {}, + flags: { + levelDrops: {} + }, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + quests: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + shared.taskDefaults({ + id: id, + value: 0 + }) + ], + dailys: [ + { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + } + ], + todos: [], + rewards: [] +}; - _.each([1, 25, 50, 75, 100], function(lvl) { - console.log("[LEVEL " + lvl + "] (" + (lvl * 2) + " points total in every attr)\n\n"); - _.each({ - red: -25, - yellow: 0, - green: 35 - }, function(taskVal, color) { - var _party, b4, str; - console.log("[task.value = " + taskVal + " (" + color + ")]"); - console.log("direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"); - _.each(['up', 'down'], function(direction) { - var b4, delta; - clearUser(lvl); - b4 = { - hp: s.hp, - taskVal: taskVal - }; - task.value = taskVal; - if (direction === 'up') { - task.type = 'daily'; - } - delta = user.ops.score({ - params: { - id: id, - direction: direction - } - }); - return console.log((direction === 'up' ? '↑' : '↓') + "\t\t" + s.exp + "/" + (shared.tnl(s.lvl)) + "\t\t" + ((b4.hp - s.hp).toFixed(1)) + "\t" + (s.gp.toFixed(1)) + "\t" + (delta.toFixed(1)) + "\t\t" + ((task.value - b4.taskVal - delta).toFixed(1)) + "\t\t\t" + (user.party.quest.progress.up.toFixed(1))); - }); - str = '- [Wizard]'; - task.value = taskVal; - clearUser(lvl); - b4 = { - taskVal: taskVal - }; - shared.content.spells.wizard.fireball.cast(user, task); - str += "\tfireball(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " bossHit:" + (user.party.quest.progress.up.toFixed(2)) + ")"; - task.value = taskVal; - clearUser(lvl); - _party = [ - user, { - stats: { - mp: 0 - } - } - ]; - shared.content.spells.wizard.mpheal.cast(user, _party); - str += "\t| mpheal(mp:" + _party[1].stats.mp + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.wizard.earth.cast(user, party); - str += "\t\t\t\t| earth(buffs.int:" + s.buffs.int + ")"; - s.buffs.int = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.wizard.frost.cast(user, {}); - str += "\t\t\t| frost(N/A)"; - console.log(str); - str = '- [Warrior]'; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.smash.cast(user, task); - b4 = { - taskVal: taskVal - }; - str += "\tsmash(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.defensiveStance.cast(user, {}); - str += "\t\t| defensiveStance(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.valorousPresence.cast(user, party); - str += "\t\t\t| valorousPresence(buffs.str:" + s.buffs.str + ")"; - s.buffs.str = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.intimidate.cast(user, party); - str += "\t\t| intimidate(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - console.log(str); - str = '- [Rogue]'; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.pickPocket.cast(user, task); - str += "\tpickPocket(gp:" + (s.gp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.backStab.cast(user, task); - b4 = { - taskVal: taskVal - }; - str += "\t\t| backStab(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " gp:" + (s.gp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.toolsOfTrade.cast(user, party); - str += "\t| toolsOfTrade(buffs.per:" + s.buffs.per + ")"; - s.buffs.per = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.stealth.cast(user, {}); - str += "\t\t| stealth(avoiding " + user.stats.buffs.stealth + " tasks)"; - user.stats.buffs.stealth = 0; - console.log(str); - str = '- [Healer]'; - task.value = taskVal; - clearUser(lvl); - s.hp = 0; - shared.content.spells.healer.heal.cast(user, {}); - str += "\theal(hp:" + (s.hp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.healer.brightness.cast(user, {}); - b4 = { - taskVal: taskVal - }; - str += "\t\t\t| brightness(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.healer.protectAura.cast(user, party); - str += "\t\t\t| protectAura(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - task.value = taskVal; - clearUser(lvl); - s.hp = 0; - shared.content.spells.healer.heallAll.cast(user, party); - str += "\t\t| heallAll(hp:" + (s.hp.toFixed(1)) + ")"; - console.log(str); - return console.log('\n'); - }); - return console.log('------------------------------------------------------------'); +shared.wrap(user); + +s = user.stats; + +task = user.tasks[id]; + +party = [user]; + +console.log("\n\n================================================"); + +console.log("New Simulation"); + +console.log("================================================\n\n"); + +clearUser = function(lvl) { + if (lvl == null) { + lvl = 1; + } + _.merge(user.stats, { + exp: 0, + gp: 0, + hp: 50, + lvl: lvl, + str: lvl * 1.5, + con: lvl * 1.5, + per: lvl * 1.5, + int: lvl * 1.5, + mp: 100 }); + _.merge(s.buffs, { + str: 0, + con: 0, + int: 0, + per: 0 + }); + _.merge(user.party.quest.progress, { + up: 0, + down: 0 + }); + return user.items.lastDrop = { + count: 0 + }; +}; + +_.each([1, 25, 50, 75, 100], function(lvl) { + console.log("[LEVEL " + lvl + "] (" + (lvl * 2) + " points total in every attr)\n\n"); + _.each({ + red: -25, + yellow: 0, + green: 35 + }, function(taskVal, color) { + var _party, b4, str; + console.log("[task.value = " + taskVal + " (" + color + ")]"); + console.log("direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"); + _.each(['up', 'down'], function(direction) { + var b4, delta; + clearUser(lvl); + b4 = { + hp: s.hp, + taskVal: taskVal + }; + task.value = taskVal; + if (direction === 'up') { + task.type = 'daily'; + } + delta = user.ops.score({ + params: { + id: id, + direction: direction + } + }); + return console.log((direction === 'up' ? '↑' : '↓') + "\t\t" + s.exp + "/" + (shared.tnl(s.lvl)) + "\t\t" + ((b4.hp - s.hp).toFixed(1)) + "\t" + (s.gp.toFixed(1)) + "\t" + (delta.toFixed(1)) + "\t\t" + ((task.value - b4.taskVal - delta).toFixed(1)) + "\t\t\t" + (user.party.quest.progress.up.toFixed(1))); + }); + str = '- [Wizard]'; + task.value = taskVal; + clearUser(lvl); + b4 = { + taskVal: taskVal + }; + shared.content.spells.wizard.fireball.cast(user, task); + str += "\tfireball(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " bossHit:" + (user.party.quest.progress.up.toFixed(2)) + ")"; + task.value = taskVal; + clearUser(lvl); + _party = [ + user, { + stats: { + mp: 0 + } + } + ]; + shared.content.spells.wizard.mpheal.cast(user, _party); + str += "\t| mpheal(mp:" + _party[1].stats.mp + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.earth.cast(user, party); + str += "\t\t\t\t| earth(buffs.int:" + s.buffs.int + ")"; + s.buffs.int = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.frost.cast(user, {}); + str += "\t\t\t| frost(N/A)"; + console.log(str); + str = '- [Warrior]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.smash.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\tsmash(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.defensiveStance.cast(user, {}); + str += "\t\t| defensiveStance(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.valorousPresence.cast(user, party); + str += "\t\t\t| valorousPresence(buffs.str:" + s.buffs.str + ")"; + s.buffs.str = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.intimidate.cast(user, party); + str += "\t\t| intimidate(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + console.log(str); + str = '- [Rogue]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.pickPocket.cast(user, task); + str += "\tpickPocket(gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.backStab.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\t\t| backStab(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.toolsOfTrade.cast(user, party); + str += "\t| toolsOfTrade(buffs.per:" + s.buffs.per + ")"; + s.buffs.per = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.stealth.cast(user, {}); + str += "\t\t| stealth(avoiding " + user.stats.buffs.stealth + " tasks)"; + user.stats.buffs.stealth = 0; + console.log(str); + str = '- [Healer]'; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heal.cast(user, {}); + str += "\theal(hp:" + (s.hp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.brightness.cast(user, {}); + b4 = { + taskVal: taskVal + }; + str += "\t\t\t| brightness(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.protectAura.cast(user, party); + str += "\t\t\t| protectAura(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heallAll.cast(user, party); + str += "\t\t| heallAll(hp:" + (s.hp.toFixed(1)) + ")"; + console.log(str); + return console.log('\n'); + }); + return console.log('------------------------------------------------------------'); +}); - /* - _.each [1,25,50,75,100,125], (lvl) -> - console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" - _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> - console.log "[task.value = #{taskVal} (#{color})]" - console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" - _.each ['up','down'], (direction) -> - clearUser(lvl) - b4 = {hp:s.hp, taskVal} - task.value = taskVal - task.type = 'daily' if direction is 'up' - delta = user.ops.score params:{id, direction} - console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" +/* +_.each [1,25,50,75,100,125], (lvl) -> + console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" + _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> + console.log "[task.value = #{taskVal} (#{color})]" + console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" + _.each ['up','down'], (direction) -> + clearUser(lvl) + b4 = {hp:s.hp, taskVal} + task.value = taskVal + task.type = 'daily' if direction is 'up' + delta = user.ops.score params:{id, direction} + console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.stealth.cast(user,{}) - console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" - user.stats.buffs.stealth = 0 + task.value = taskVal;clearUser(lvl) + shared.content.spells.rogue.stealth.cast(user,{}) + console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" + user.stats.buffs.stealth = 0 - console.log user.dailys.length - */ - -}).call(this); + console.log user.dailys.length + */ diff --git a/test/common/user.fns.ultimateGear.test.js b/test/common/user.fns.ultimateGear.test.js index 988313d5cc..9dc80eaea5 100644 --- a/test/common/user.fns.ultimateGear.test.js +++ b/test/common/user.fns.ultimateGear.test.js @@ -1,5 +1,3 @@ -'use strict'; - var shared = require('../../common/script/index.js'); shared.i18n.translations = require('../../website/src/libs/i18n.js').translations From 2e16bade2b8031f499438022bcc7ea46cfc63103 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:28:16 -0600 Subject: [PATCH 15/20] Add lint task to test command. --- tasks/gulp-tests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index b6e28e534d..8ed443b237 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -328,6 +328,7 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { gulp.task('test:all', (done) => { runSequence( + 'lint', 'test:e2e:safe', 'test:common:safe', // 'test:content:safe', From 01946b64e6c7fff4c76d01ce48fc2309ae7f00f3 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:29:54 -0600 Subject: [PATCH 16/20] Remove coffeescript from dependencies. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 660c305606..445d323052 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "babelify": "^6.x.x", "bower": "~1.3.12", "browserify": "~12.0.1", - "coffee-script": "1.6.x", "connect-ratelimit": "0.0.7", "coupon-code": "~0.3.0", "domain-middleware": "~0.1.0", From 9ee56a5622649d96b33ab48170b4c20805b3eadc Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:52:29 -0600 Subject: [PATCH 17/20] Remove rule --- .eslintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 69c945431e..a3bc6debda 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,7 +28,6 @@ "no-new": 2, "no-octal-escape": 2, "no-octal": 2, - "no-param-reassign": 2, "no-process-env": 2, "no-proto": 2, "no-implied-eval": 2, From a127bb8711c17d06862784012c0aac62042600e2 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 08:59:57 -0600 Subject: [PATCH 18/20] Switch to babel-core form babel. Babel includes the CLI, which we don't need. --- Gruntfile.js | 2 +- gulpfile.js | 2 +- package.json | 2 +- tasks/gulp-tests.js | 2 +- test/mocha.opts | 2 +- website/src/server.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 76ba62c18d..84b570da4c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,5 @@ /*global module:false*/ -require('babel/register'); +require('babel-core/register'); var _ = require('lodash'); module.exports = function(grunt) { diff --git a/gulpfile.js b/gulpfile.js index 9e32600ef4..84da7d11a3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ * directory, and it will automatically be included. */ -require('babel/register'); +require('babel-core/register'); if (process.env.NODE_ENV === 'production') { require('./tasks/gulp-newstuff'); diff --git a/package.json b/package.json index 445d323052..694f18c522 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "amplitude": "^2.0.1", "async": "~0.9.0", "aws-sdk": "^2.0.25", - "babel": "^5.5.4", + "babel-core": "^5.8.34", "babelify": "^6.x.x", "bower": "~1.3.12", "browserify": "~12.0.1", diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index 8ed443b237..4c085fd270 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -332,7 +332,7 @@ gulp.task('test:all', (done) => { 'test:e2e:safe', 'test:common:safe', // 'test:content:safe', - 'test:server_side:safe', + // 'test:server_side:safe', 'test:karma:safe', 'test:api-legacy:safe', 'test:api-v2:safe', diff --git a/test/mocha.opts b/test/mocha.opts index 51a9c44078..bcdefd3e55 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -4,6 +4,6 @@ --check-leaks --growl --globals io ---compilers js:babel/register +--compilers js:babel-core/register --require test/api-legacy/api-helper --require ./test/helpers/globals.helper diff --git a/website/src/server.js b/website/src/server.js index 0a8983f6a1..7040d82c3d 100644 --- a/website/src/server.js +++ b/website/src/server.js @@ -1,4 +1,4 @@ -require('babel/register'); +require('babel-core/register'); // Only do the minimal amount of work before forking just in case of a dyno restart var cluster = require("cluster"); var _ = require('lodash'); From b13b2d0f6276e538b3aecc72e92ef7dac72ddc8c Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 09:28:26 -0600 Subject: [PATCH 19/20] Remove coffee content index file --- common/script/content/index.coffee | 2460 ---------------------------- 1 file changed, 2460 deletions(-) delete mode 100644 common/script/content/index.coffee diff --git a/common/script/content/index.coffee b/common/script/content/index.coffee deleted file mode 100644 index c46b1dc902..0000000000 --- a/common/script/content/index.coffee +++ /dev/null @@ -1,2460 +0,0 @@ -api = module.exports - -_ = require 'lodash' -moment = require 'moment' -t = require './translation.js' - -### - --------------------------------------------------------------- - Gear (Weapons, Armor, Head, Shield) - Item definitions: {index, text, notes, value, str, def, int, per, classes, type} - --------------------------------------------------------------- -### - -classes = ['warrior', 'rogue', 'healer', 'wizard'] -gearTypes = [ 'weapon', 'armor', 'head', 'shield', 'body', 'back', 'headAccessory', 'eyewear'] - -events = -# IMPORTANT: The end date should be one to two days AFTER the actual end of -# the event, to allow people in different timezones to still buy the -# event gear up until at least the actual end of the event. - winter: {start:'2013-12-31',end:'2014-02-01'} - birthday: {start:'2013-01-30',end:'2014-02-01'} - spring: {start:'2014-03-21',end:'2014-05-01'} - summer: {start:'2014-06-20',end:'2014-08-01'} - gaymerx: {start:'2014-07-02',end:'2014-08-01'} - fall: {start:'2014-09-21',end:'2014-11-01'} - winter2015: {start:'2014-12-21',end:'2015-02-02'} - spring2015: {start:'2015-03-20',end:'2015-05-02'} - summer2015: {start:'2015-06-20',end:'2015-08-02'} - fall2015: {start:'2015-09-21',end:'2015-11-02'} - -api.mystery = - 201402: {start:'2014-02-22',end:'2014-02-28', text:'Winged Messenger Set'} - 201403: {start:'2014-03-24',end:'2014-04-02', text:'Forest Walker Set'} - 201404: {start:'2014-04-24',end:'2014-05-02', text:'Twilight Butterfly Set'} - 201405: {start:'2014-05-21',end:'2014-06-02', text:'Flame Wielder Set'} - 201406: {start:'2014-06-23',end:'2014-07-02', text:'Octomage Set'} - 201407: {start:'2014-07-23',end:'2014-08-02', text:'Undersea Explorer Set'} - 201408: {start:'2014-08-23',end:'2014-09-02', text:'Sun Sorcerer Set'} - 201409: {start:'2014-09-24',end:'2014-10-02', text:'Autumn Strider Set'} - 201410: {start:'2014-10-24',end:'2014-11-02', text:'Winged Goblin Set'} - 201411: {start:'2014-11-24',end:'2014-12-02', text:'Feast and Fun Set'} - 201412: {start:'2014-12-25',end:'2015-01-02', text:'Penguin Set'} - 201501: {start:'2015-01-26',end:'2015-02-02', text:'Starry Knight Set'} - 201502: {start:'2015-02-24',end:'2015-03-02', text:'Winged Enchanter Set'} - 201503: {start:'2015-03-25',end:'2015-04-02', text:'Aquamarine Set'} - 201504: {start:'2015-04-24',end:'2015-05-02', text:'Busy Bee Set'} - 201505: {start:'2015-05-25',end:'2015-06-02', text:'Green Knight Set'} - 201506: {start:'2015-06-25',end:'2015-07-02', text:'Neon Snorkeler Set'} - 201507: {start:'2015-07-24',end:'2015-08-02', text:'Rad Surfer Set'} - 201508: {start:'2015-08-23',end:'2015-09-02', text:'Cheetah Costume Set'} - 201509: {start:'2015-09-24',end:'2015-10-02', text:'Werewolf Set'} - 201510: {start:'2015-10-26',end:'2015-11-02', text:'Horned Goblin Set'} - 301404: {start:'3014-03-24',end:'3014-04-02', text:'Steampunk Standard Set'} - 301405: {start:'3014-04-24',end:'3014-05-02', text:'Steampunk Accessories Set'} - wondercon: {start:'2014-03-24',end:'2014-04-01'} # not really, but the mechanic works -_.each api.mystery, (v,k)->v.key = k - -api.itemList = - 'weapon': {localeKey: 'weapon', isEquipment: true} - 'armor' : {localeKey: 'armor', isEquipment: true} - 'head' : {localeKey: 'headgear', isEquipment: true} - 'shield' : {localeKey: 'offhand', isEquipment: true} - 'back' : {localeKey: 'back', isEquipment: true} - 'body' : {localeKey: 'body', isEquipment: true} - 'headAccessory' : {localeKey: 'headAccessory', isEquipment: true} - 'eyewear' : {localeKey: 'eyewear', isEquipment: true} - 'hatchingPotions' : {localeKey: 'hatchingPotion', isEquipment: false} - 'eggs' : {localeKey: 'eggSingular', isEquipment: false} - 'quests' : {localeKey: 'quest', isEquipment: false} - 'food' : {localeKey: 'foodText', isEquipment: false} - 'Saddle' : {localeKey: 'foodSaddleText', isEquipment: false} - -gear = - weapon: - base: - 0: - text: t('weaponBase0Text'), notes: t('weaponBase0Notes'), value:0 - warrior: - 0: text: t('weaponWarrior0Text'), notes: t('weaponWarrior0Notes'), value:1 - 1: text: t('weaponWarrior1Text'), notes: t('weaponWarrior1Notes', {str: 3}), str: 3, value:20 - 2: text: t('weaponWarrior2Text'), notes: t('weaponWarrior2Notes', {str: 6}), str: 6, value:30 - 3: text: t('weaponWarrior3Text'), notes: t('weaponWarrior3Notes', {str: 9}), str: 9, value:45 - 4: text: t('weaponWarrior4Text'), notes: t('weaponWarrior4Notes', {str: 12}), str: 12, value:65 - 5: text: t('weaponWarrior5Text'), notes: t('weaponWarrior5Notes', {str: 15}), str: 15, value:90 - 6: text: t('weaponWarrior6Text'), notes: t('weaponWarrior6Notes', {str: 18}), str: 18, value:120, last: true - rogue: - 0: text: t('weaponRogue0Text'), notes: t('weaponRogue0Notes'), str: 0, value: 0 - 1: text: t('weaponRogue1Text'), notes: t('weaponRogue1Notes', {str: 2}), str: 2, value: 20 - 2: text: t('weaponRogue2Text'), notes: t('weaponRogue2Notes', {str: 3}), str: 3, value: 35 - 3: text: t('weaponRogue3Text'), notes: t('weaponRogue3Notes', {str: 4}), str: 4, value: 50 - 4: text: t('weaponRogue4Text'), notes: t('weaponRogue4Notes', {str: 6}), str: 6, value: 70 - 5: text: t('weaponRogue5Text'), notes: t('weaponRogue5Notes', {str: 8}), str: 8, value: 90 - 6: text: t('weaponRogue6Text'), notes: t('weaponRogue6Notes', {str: 10}), str: 10, value: 120, last: true - wizard: - 0: twoHanded: true, text: t('weaponWizard0Text'), notes: t('weaponWizard0Notes'), value:0 - 1: twoHanded: true, text: t('weaponWizard1Text'), notes: t('weaponWizard1Notes', {int: 3, per: 1}), int: 3, per: 1, value:30 - 2: twoHanded: true, text: t('weaponWizard2Text'), notes: t('weaponWizard2Notes', {int: 6, per: 2}), int: 6, per: 2, value:50 - 3: twoHanded: true, text: t('weaponWizard3Text'), notes: t('weaponWizard3Notes', {int: 9, per: 3}), int: 9, per: 3, value:80 - 4: twoHanded: true, text: t('weaponWizard4Text'), notes: t('weaponWizard4Notes', {int: 12, per: 5}), int:12, per: 5, value:120 - 5: twoHanded: true, text: t('weaponWizard5Text'), notes: t('weaponWizard5Notes', {int: 15, per: 7}), int: 15, per: 7, value:160 - 6: twoHanded: true, text: t('weaponWizard6Text'), notes: t('weaponWizard6Notes', {int: 18, per: 10}), int: 18, per: 10, value:200, last: true - healer: - 0: text: t('weaponHealer0Text'), notes: t('weaponHealer0Notes'), value:0 - 1: text: t('weaponHealer1Text'), notes: t('weaponHealer1Notes', {int: 2}), int: 2, value:20 - 2: text: t('weaponHealer2Text'), notes: t('weaponHealer2Notes', {int: 3}), int: 3, value:30 - 3: text: t('weaponHealer3Text'), notes: t('weaponHealer3Notes', {int: 5}), int: 5, value:45 - 4: text: t('weaponHealer4Text'), notes: t('weaponHealer4Notes', {int: 7}), int:7, value:65 - 5: text: t('weaponHealer5Text'), notes: t('weaponHealer5Notes', {int: 9}), int: 9, value:90 - 6: text: t('weaponHealer6Text'), notes: t('weaponHealer6Notes', {int: 11}), int: 11, value:120, last: true - special: - # Backer, Contributor, and Quest Rewards - 0: text: t('weaponSpecial0Text'), notes: t('weaponSpecial0Notes', {str: 20}), str: 20, value:150, canOwn: ((u)-> +u.backer?.tier >= 70) - 1: text: t('weaponSpecial1Text'), notes: t('weaponSpecial1Notes', {attrs: 6}), str: 6, per: 6, con: 6, int: 6, value:170, canOwn: ((u)-> +u.contributor?.level >= 4) - 2: text: t('weaponSpecial2Text'), notes: t('weaponSpecial2Notes', {attrs: 25}), str: 25, per: 25, value:200, canOwn: ((u)-> (+u.backer?.tier >= 300) or u.items.gear.owned.weapon_special_2?) - 3: text: t('weaponSpecial3Text'), notes: t('weaponSpecial3Notes', {attrs: 17}), str: 17, int: 17, con: 17, value:200, canOwn: ((u)-> (+u.backer?.tier >= 300) or u.items.gear.owned.weapon_special_3?) - critical: text: t('weaponSpecialCriticalText'), notes: t('weaponSpecialCriticalNotes', {attrs: 40}), str: 40, per: 40, value:200, canOwn: ((u)-> !!u.contributor?.critical) - tridentOfCrashingTides: text: t('weaponSpecialTridentOfCrashingTidesText'), notes: t('weaponSpecialTridentOfCrashingTidesNotes', {int: 15}), int: 15, value: 130, canOwn: ((u)-> u.items.gear.owned.weapon_special_tridentOfCrashingTides?) - # Winter Wonderland - yeti: event: events.winter, specialClass: 'warrior', text: t('weaponSpecialYetiText'), notes: t('weaponSpecialYetiNotes', {str: 15}), str: 15, value:90 - ski: event: events.winter, specialClass: 'rogue', text: t('weaponSpecialSkiText'), notes: t('weaponSpecialSkiNotes', {str: 8}), str: 8, value: 90 - candycane: event: events.winter, specialClass: 'wizard', twoHanded: true, text: t('weaponSpecialCandycaneText'), notes: t('weaponSpecialCandycaneNotes', {int: 15, per: 7}), int: 15, per: 7, value:160 - snowflake: event: events.winter, specialClass: 'healer', text: t('weaponSpecialSnowflakeText'), notes: t('weaponSpecialSnowflakeNotes', {int: 9}), int: 9, value:90 - # Spring Fling - springRogue: event: events.spring, specialClass: 'rogue', text: t('weaponSpecialSpringRogueText'), notes: t('weaponSpecialSpringRogueNotes', {str: 8}), value: 80, str: 8 - springWarrior: event: events.spring, specialClass: 'warrior', text: t('weaponSpecialSpringWarriorText'), notes: t('weaponSpecialSpringWarriorNotes', {str: 15}), value: 90, str: 15 - springMage: event: events.spring, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialSpringMageText'), notes: t('weaponSpecialSpringMageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - springHealer: event: events.spring, specialClass: 'healer', text: t('weaponSpecialSpringHealerText'), notes: t('weaponSpecialSpringHealerNotes', {int: 9}), value: 90, int: 9 - # Summer Splash - summerRogue: event: events.summer, specialClass: 'rogue', text: t('weaponSpecialSummerRogueText'), notes: t('weaponSpecialSummerRogueNotes', {str: 8}), value: 80, str: 8 - summerWarrior: event: events.summer, specialClass: 'warrior', text: t('weaponSpecialSummerWarriorText'), notes: t('weaponSpecialSummerWarriorNotes', {str: 15}), value: 90, str: 15 - summerMage: event: events.summer, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialSummerMageText'), notes: t('weaponSpecialSummerMageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - summerHealer: event: events.summer, specialClass: 'healer', text: t('weaponSpecialSummerHealerText'), notes: t('weaponSpecialSummerHealerNotes', {int: 9}), value: 90, int: 9 - # Fall Festival - fallRogue: event: events.fall, specialClass: 'rogue', text: t('weaponSpecialFallRogueText'), notes: t('weaponSpecialFallRogueNotes', {str: 8}), value: 80, str: 8, canBuy: (()->true) - fallWarrior: event: events.fall, specialClass: 'warrior', text: t('weaponSpecialFallWarriorText'), notes: t('weaponSpecialFallWarriorNotes', {str: 15}), value: 90, str: 15, canBuy: (()->true) - fallMage: event: events.fall, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialFallMageText'), notes: t('weaponSpecialFallMageNotes', {int: 15, per: 7}), value: 160, int:15, per:7, canBuy: (()->true) - fallHealer: event: events.fall, specialClass: 'healer', text: t('weaponSpecialFallHealerText'), notes: t('weaponSpecialFallHealerNotes', {int: 9}), value: 90, int: 9, canBuy: (()->true) - # Winter 2015 - winter2015Rogue: event: events.winter2015, specialClass: 'rogue', text: t('weaponSpecialWinter2015RogueText'), notes: t('weaponSpecialWinter2015RogueNotes', {str: 8}), value: 80, str: 8 - winter2015Warrior: event: events.winter2015, specialClass: 'warrior', text: t('weaponSpecialWinter2015WarriorText'), notes: t('weaponSpecialWinter2015WarriorNotes', {str: 15}), value: 90, str: 15 - winter2015Mage: event: events.winter2015, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialWinter2015MageText'), notes: t('weaponSpecialWinter2015MageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - winter2015Healer: event: events.winter2015, specialClass: 'healer', text: t('weaponSpecialWinter2015HealerText'), notes: t('weaponSpecialWinter2015HealerNotes', {int: 9}), value: 90, int: 9 - # Spring 2015 - spring2015Rogue: event: events.spring2015, specialClass: 'rogue', text: t('weaponSpecialSpring2015RogueText'), notes: t('weaponSpecialSpring2015RogueNotes', {str: 8}), value: 80, str: 8 - spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('weaponSpecialSpring2015WarriorText'), notes: t('weaponSpecialSpring2015WarriorNotes', {str: 15}), value: 90, str: 15 - spring2015Mage: event: events.spring2015, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialSpring2015MageText'), notes: t('weaponSpecialSpring2015MageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('weaponSpecialSpring2015HealerText'), notes: t('weaponSpecialSpring2015HealerNotes', {int: 9}), value: 90, int: 9 - # Summer 2015 - summer2015Rogue: event: events.summer2015, specialClass: 'rogue', text: t('weaponSpecialSummer2015RogueText'), notes: t('weaponSpecialSummer2015RogueNotes', {str: 8}), value: 80, str: 8 - summer2015Warrior: event: events.summer2015, specialClass: 'warrior', text: t('weaponSpecialSummer2015WarriorText'), notes: t('weaponSpecialSummer2015WarriorNotes', {str: 15}), value: 90, str: 15 - summer2015Mage: event: events.summer2015, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialSummer2015MageText'), notes: t('weaponSpecialSummer2015MageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - summer2015Healer: event: events.summer2015, specialClass: 'healer', text: t('weaponSpecialSummer2015HealerText'), notes: t('weaponSpecialSummer2015HealerNotes', {int: 9}), value: 90, int: 9 - # Fall 2015 - fall2015Rogue: event: events.fall2015, specialClass: 'rogue', text: t('weaponSpecialFall2015RogueText'), notes: t('weaponSpecialFall2015RogueNotes', {str: 8}), value: 80, str: 8 - fall2015Warrior: event: events.fall2015, specialClass: 'warrior', text: t('weaponSpecialFall2015WarriorText'), notes: t('weaponSpecialFall2015WarriorNotes', {str: 15}), value: 90, str: 15 - fall2015Mage: event: events.fall2015, specialClass: 'wizard', twoHanded:true, text: t('weaponSpecialFall2015MageText'), notes: t('weaponSpecialFall2015MageNotes', {int: 15, per: 7}), value: 160, int:15, per:7 - fall2015Healer: event: events.fall2015, specialClass: 'healer', text: t('weaponSpecialFall2015HealerText'), notes: t('weaponSpecialFall2015HealerNotes', {int: 9}), value: 90, int: 9 - mystery: - 201411: text: t('weaponMystery201411Text'), notes: t('weaponMystery201411Notes'), mystery:'201411', value: 0 - 201502: text: t('weaponMystery201502Text'), notes: t('weaponMystery201502Notes'), mystery:'201502', value: 0 - 201505: text: t('weaponMystery201505Text'), notes: t('weaponMystery201505Notes'), mystery:'201505', value: 0 - 301404: text: t('weaponMystery301404Text'), notes: t('weaponMystery301404Notes'), mystery:'301404', value: 0 - armoire: - basicCrossbow: text: t('weaponArmoireBasicCrossbowText'), notes: t('weaponArmoireBasicCrossbowNotes', {str: 5, per: 5, con: 5}), value: 100, str: 5, per: 5, con: 5, canOwn: ((u)-> u.items.gear.owned.weapon_armoire_basicCrossbow?) - lunarSceptre: text: t('weaponArmoireLunarSceptreText'), notes: t('weaponArmoireLunarSceptreNotes', {con: 7, int: 7}), value: 100, con: 7, int: 7, set: 'soothing', canOwn: ((u)-> u.items.gear.owned.weapon_armoire_lunarSceptre?) - rancherLasso: twoHanded:true, text: t('weaponArmoireRancherLassoText'), notes: t('weaponArmoireRancherLassoNotes', {str: 5, per: 5, int: 5}), value: 100, str: 5, per: 5, int: 5, set: 'rancher', canOwn: ((u)-> u.items.gear.owned.weapon_armoire_rancherLasso?) - mythmakerSword: text: t('weaponArmoireMythmakerSwordText'), notes: t('weaponArmoireMythmakerSwordNotes', {attrs: 6}), value: 100, str: 6, per: 6, set: 'goldenToga', canOwn: ((u)-> u.items.gear.owned.weapon_armoire_mythmakerSword?) - ironCrook: text: t('weaponArmoireIronCrookText'), notes: t('weaponArmoireIronCrookNotes', {attrs: 7}), value: 100, str: 7, per: 7, set: 'hornedIron', canOwn: ((u)-> u.items.gear.owned.weapon_armoire_ironCrook?) - goldWingStaff: text: t('weaponArmoireGoldWingStaffText'), notes: t('weaponArmoireGoldWingStaffNotes', {attrs:4}), value: 100, con: 4, int: 4, per: 4, str: 4, canOwn: ((u)-> u.items.gear.owned.weapon_armoire_goldWingStaff?) - batWand: text: t('weaponArmoireBatWandText'), notes: t('weaponArmoireBatWandNotes', {int: 10, per: 2}), value: 100, int: 10, per: 2, canOwn: ((u)-> u.items.gear.owned.weapon_armoire_batWand?) - shepherdsCrook: text: t('weaponArmoireShepherdsCrookText'), notes: t('weaponArmoireShepherdsCrookNotes', {con: 9}), value: 100, con: 9, set: 'shepherd', canOwn: ((u)-> u.items.gear.owned.weapon_armoire_shepherdsCrook?) - - armor: - base: - 0: text: t('armorBase0Text'), notes: t('armorBase0Notes'), value:0 - warrior: - 1: text: t('armorWarrior1Text'), notes: t('armorWarrior1Notes', {con: 3}), con: 3, value:30 - 2: text: t('armorWarrior2Text'), notes: t('armorWarrior2Notes', {con: 5}), con: 5, value:45 - 3: text: t('armorWarrior3Text'), notes: t('armorWarrior3Notes', {con: 7}), con: 7, value:65 - 4: text: t('armorWarrior4Text'), notes: t('armorWarrior4Notes', {con: 9}), con: 9, value:90 - 5: text: t('armorWarrior5Text'), notes: t('armorWarrior5Notes', {con: 11}), con: 11, value:120, last: true - rogue: - 1: text: t('armorRogue1Text'), notes: t('armorRogue1Notes', {per: 6}), per: 6, value:30 - 2: text: t('armorRogue2Text'), notes: t('armorRogue2Notes', {per: 9}), per: 9, value:45 - 3: text: t('armorRogue3Text'), notes: t('armorRogue3Notes', {per: 12}), per: 12, value:65 - 4: text: t('armorRogue4Text'), notes: t('armorRogue4Notes', {per: 15}), per: 15, value:90 - 5: text: t('armorRogue5Text'), notes: t('armorRogue5Notes', {per: 18}), per: 18, value:120, last: true - wizard: - 1: text: t('armorWizard1Text'), notes: t('armorWizard1Notes', {int: 2}), int: 2, value:30 - 2: text: t('armorWizard2Text'), notes: t('armorWizard2Notes', {int: 4}), int: 4, value:45 - 3: text: t('armorWizard3Text'), notes: t('armorWizard3Notes', {int: 6}), int: 6, value:65 - 4: text: t('armorWizard4Text'), notes: t('armorWizard4Notes', {int: 9}), int: 9, value:90 - 5: text: t('armorWizard5Text'), notes: t('armorWizard5Notes', {int: 12}), int: 12, value:120, last: true - healer: - 1: text: t('armorHealer1Text'), notes: t('armorHealer1Notes', {con: 6}), con: 6, value:30 - 2: text: t('armorHealer2Text'), notes: t('armorHealer2Notes', {con: 9}), con: 9, value:45 - 3: text: t('armorHealer3Text'), notes: t('armorHealer3Notes', {con: 12}), con: 12, value:65 - 4: text: t('armorHealer4Text'), notes: t('armorHealer4Notes', {con: 15}), con: 15, value:90 - 5: text: t('armorHealer5Text'), notes: t('armorHealer5Notes', {con: 18}), con: 18, value:120, last: true - special: - # Backer, Contributor, and Quest Rewards - 0: text: t('armorSpecial0Text'), notes: t('armorSpecial0Notes', {con: 20}), con: 20, value:150, canOwn: ((u)-> +u.backer?.tier >= 45) - 1: text: t('armorSpecial1Text'), notes: t('armorSpecial1Notes', {attrs: 6}), con: 6, str: 6, per: 6, int: 6, value:170, canOwn: ((u)-> +u.contributor?.level >= 2) - 2: text: t('armorSpecial2Text'), notes: t('armorSpecial2Notes', {attrs: 25}), int: 25, con: 25, value:200, canOwn: ((u)-> +u.backer?.tier >= 300 or u.items.gear.owned.armor_special_2?) - finnedOceanicArmor: text: t('armorSpecialFinnedOceanicArmorText'), notes: t('armorSpecialFinnedOceanicArmorNotes', {str: 15}), str: 15, value: 130, canOwn: ((u)-> u.items.gear.owned.armor_special_finnedOceanicArmor?) - # Winter Wonderland - yeti: event: events.winter, specialClass: 'warrior', text: t('armorSpecialYetiText'), notes: t('armorSpecialYetiNotes', {con: 9}), con: 9, value:90 - ski: event: events.winter, specialClass: 'rogue', text: t('armorSpecialSkiText'), notes: t('armorSpecialSkiNotes', {per: 15}), per: 15, value:90 - candycane: event: events.winter, specialClass: 'wizard', text: t('armorSpecialCandycaneText'), notes: t('armorSpecialCandycaneNotes', {int: 9}), int: 9, value:90 - snowflake: event: events.winter, specialClass: 'healer', text: t('armorSpecialSnowflakeText'), notes: t('armorSpecialSnowflakeNotes', {con: 15}), con: 15, value:90 - birthday: event: events.birthday, text: t('armorSpecialBirthdayText'), notes: t('armorSpecialBirthdayNotes'), value: 0 - # Spring Fling - springRogue: event: events.spring, specialClass: 'rogue', text: t('armorSpecialSpringRogueText'), notes: t('armorSpecialSpringRogueNotes', {per: 15}), value: 90, per: 15 - springWarrior: event: events.spring, specialClass: 'warrior', text: t('armorSpecialSpringWarriorText'), notes: t('armorSpecialSpringWarriorNotes', {con: 9}), value: 90, con: 9 - springMage: event: events.spring, specialClass: 'wizard', text: t('armorSpecialSpringMageText'), notes: t('armorSpecialSpringMageNotes', {int: 9}), value: 90, int: 9 - springHealer: event: events.spring, specialClass: 'healer', text: t('armorSpecialSpringHealerText'), notes: t('armorSpecialSpringHealerNotes', {con: 15}), value: 90, con: 15 - # Summer Splash - summerRogue: event: events.summer, specialClass: 'rogue', text: t('armorSpecialSummerRogueText'), notes: t('armorSpecialSummerRogueNotes', {per: 15}), value: 90, per: 15 - summerWarrior: event: events.summer, specialClass: 'warrior', text: t('armorSpecialSummerWarriorText'), notes: t('armorSpecialSummerWarriorNotes', {con: 9}), value: 90, con: 9 - summerMage: event: events.summer, specialClass: 'wizard', text: t('armorSpecialSummerMageText'), notes: t('armorSpecialSummerMageNotes', {int: 9}), value: 90, int: 9 - summerHealer: event: events.summer, specialClass: 'healer', text: t('armorSpecialSummerHealerText'), notes: t('armorSpecialSummerHealerNotes', {con: 15}), value: 90, con: 15 - # Fall Festival - fallRogue: event: events.fall, specialClass: 'rogue', text: t('armorSpecialFallRogueText'), notes: t('armorSpecialFallRogueNotes', {per: 15}), value: 90, per: 15, canBuy: (()->true) - fallWarrior: event: events.fall, specialClass: 'warrior', text: t('armorSpecialFallWarriorText'), notes: t('armorSpecialFallWarriorNotes', {con: 9}), value: 90, con: 9, canBuy: (()->true) - fallMage: event: events.fall, specialClass: 'wizard', text: t('armorSpecialFallMageText'), notes: t('armorSpecialFallMageNotes', {int: 9}), value: 90, int: 9, canBuy: (()->true) - fallHealer: event: events.fall, specialClass: 'healer', text: t('armorSpecialFallHealerText'), notes: t('armorSpecialFallHealerNotes', {con: 15}), value: 90, con: 15, canBuy: (()->true) - # Winter 2015 - winter2015Rogue: event: events.winter2015, specialClass: 'rogue', text: t('armorSpecialWinter2015RogueText'), notes: t('armorSpecialWinter2015RogueNotes', {per: 15}), value: 90, per: 15 - winter2015Warrior: event: events.winter2015, specialClass: 'warrior', text: t('armorSpecialWinter2015WarriorText'), notes: t('armorSpecialWinter2015WarriorNotes', {con: 9}), value: 90, con: 9 - winter2015Mage: event: events.winter2015, specialClass: 'wizard', text: t('armorSpecialWinter2015MageText'), notes: t('armorSpecialWinter2015MageNotes', {int: 9}), value: 90, int: 9 - winter2015Healer: event: events.winter2015, specialClass: 'healer', text: t('armorSpecialWinter2015HealerText'), notes: t('armorSpecialWinter2015HealerNotes', {con: 15}), value: 90, con: 15 - birthday2015: text: t('armorSpecialBirthday2015Text'), notes: t('armorSpecialBirthday2015Notes'), value: 0, canOwn: ((u)-> u.items.gear.owned.armor_special_birthday2015?) - # Spring 2015 - spring2015Rogue: event: events.spring2015, specialClass: 'rogue', text: t('armorSpecialSpring2015RogueText'), notes: t('armorSpecialSpring2015RogueNotes', {per: 15}), value: 90, per: 15 - spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('armorSpecialSpring2015WarriorText'), notes: t('armorSpecialSpring2015WarriorNotes', {con: 9}), value: 90, con: 9 - spring2015Mage: event: events.spring2015, specialClass: 'wizard', text: t('armorSpecialSpring2015MageText'), notes: t('armorSpecialSpring2015MageNotes', {int: 9}), value: 90, int: 9 - spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('armorSpecialSpring2015HealerText'), notes: t('armorSpecialSpring2015HealerNotes', {con: 15}), value: 90, con: 15 - # Summer 2015 - summer2015Rogue: event: events.summer2015, specialClass: 'rogue', text: t('armorSpecialSummer2015RogueText'), notes: t('armorSpecialSummer2015RogueNotes', {per: 15}), value: 90, per: 15 - summer2015Warrior: event: events.summer2015, specialClass: 'warrior', text: t('armorSpecialSummer2015WarriorText'), notes: t('armorSpecialSummer2015WarriorNotes', {con: 9}), value: 90, con: 9 - summer2015Mage: event: events.summer2015, specialClass: 'wizard', text: t('armorSpecialSummer2015MageText'), notes: t('armorSpecialSummer2015MageNotes', {int: 9}), value: 90, int: 9 - summer2015Healer: event: events.summer2015, specialClass: 'healer', text: t('armorSpecialSummer2015HealerText'), notes: t('armorSpecialSummer2015HealerNotes', {con: 15}), value: 90, con: 15 - # Fall 2015 - fall2015Rogue: event: events.fall2015, specialClass: 'rogue', text: t('armorSpecialFall2015RogueText'), notes: t('armorSpecialFall2015RogueNotes', {per: 15}), value: 90, per: 15 - fall2015Warrior: event: events.fall2015, specialClass: 'warrior', text: t('armorSpecialFall2015WarriorText'), notes: t('armorSpecialFall2015WarriorNotes', {con: 9}), value: 90, con: 9 - fall2015Mage: event: events.fall2015, specialClass: 'wizard', text: t('armorSpecialFall2015MageText'), notes: t('armorSpecialFall2015MageNotes', {int: 9}), value: 90, int: 9 - fall2015Healer: event: events.fall2015, specialClass: 'healer', text: t('armorSpecialFall2015HealerText'), notes: t('armorSpecialFall2015HealerNotes', {con: 15}), value: 90, con: 15 - # Other - gaymerx: event: events.gaymerx, text: t('armorSpecialGaymerxText'), notes: t('armorSpecialGaymerxNotes'), value: 0 - mystery: - 201402: text: t('armorMystery201402Text'), notes: t('armorMystery201402Notes'), mystery:'201402', value: 0 - 201403: text: t('armorMystery201403Text'), notes: t('armorMystery201403Notes'), mystery:'201403', value: 0 - 201405: text: t('armorMystery201405Text'), notes: t('armorMystery201405Notes'), mystery:'201405', value: 0 - 201406: text: t('armorMystery201406Text'), notes: t('armorMystery201406Notes'), mystery:'201406', value: 0 - 201407: text: t('armorMystery201407Text'), notes: t('armorMystery201407Notes'), mystery:'201407', value: 0 - 201408: text: t('armorMystery201408Text'), notes: t('armorMystery201408Notes'), mystery:'201408', value: 0 - 201409: text: t('armorMystery201409Text'), notes: t('armorMystery201409Notes'), mystery:'201409', value: 0 - 201410: text: t('armorMystery201410Text'), notes: t('armorMystery201410Notes'), mystery:'201410', value: 0 - 201412: text: t('armorMystery201412Text'), notes: t('armorMystery201412Notes'), mystery:'201412', value: 0 - 201501: text: t('armorMystery201501Text'), notes: t('armorMystery201501Notes'), mystery:'201501', value: 0 - 201503: text: t('armorMystery201503Text'), notes: t('armorMystery201503Notes'), mystery:'201503', value: 0 - 201504: text: t('armorMystery201504Text'), notes: t('armorMystery201504Notes'), mystery:'201504', value: 0 - 201506: text: t('armorMystery201506Text'), notes: t('armorMystery201506Notes'), mystery:'201506', value: 0 - 201508: text: t('armorMystery201508Text'), notes: t('armorMystery201508Notes'), mystery:'201508', value: 0 - 201509: text: t('armorMystery201509Text'), notes: t('armorMystery201509Notes'), mystery:'201509', value: 0 - 301404: text: t('armorMystery301404Text'), notes: t('armorMystery301404Notes'), mystery:'301404', value: 0 - armoire: - lunarArmor: text: t('armorArmoireLunarArmorText'), notes: t('armorArmoireLunarArmorNotes', {str: 7, int: 7}), value: 100, str: 7, int: 7, set: 'soothing', canOwn: ((u)-> u.items.gear.owned.armor_armoire_lunarArmor?) - gladiatorArmor: text: t('armorArmoireGladiatorArmorText'), notes: t('armorArmoireGladiatorArmorNotes', {str: 7, per: 7}), value: 100, str: 7, per: 7, set: 'gladiator', canOwn: ((u)-> u.items.gear.owned.armor_armoire_gladiatorArmor?) - rancherRobes: text: t('armorArmoireRancherRobesText'), notes: t('armorArmoireRancherRobesNotes', {str: 5, per: 5, int: 5}), value: 100, str: 5, per: 5, int: 5, set: 'rancher', canOwn: ((u)-> u.items.gear.owned.armor_armoire_rancherRobes?) - goldenToga: text: t('armorArmoireGoldenTogaText'), notes: t('armorArmoireGoldenTogaNotes', {attrs: 8}), value: 100, str: 8, con: 8, set: 'goldenToga', canOwn: ((u)-> u.items.gear.owned.armor_armoire_goldenToga?) - hornedIronArmor: text: t('armorArmoireHornedIronArmorText'), notes: t('armorArmoireHornedIronArmorNotes', {con: 9, per: 7}), value: 100, con: 9, per: 7, set: 'hornedIron', canOwn: ((u)-> u.items.gear.owned.armor_armoire_hornedIronArmor?) - plagueDoctorOvercoat: text: t('armorArmoirePlagueDoctorOvercoatText'), notes: t('armorArmoirePlagueDoctorOvercoatNotes', {int: 6, str: 5, con: 6}), value: 100, int: 6, str: 5, con: 6, set: 'plagueDoctor', canOwn: ((u)-> u.items.gear.owned.armor_armoire_plagueDoctorOvercoat?) - shepherdRobes: text: t('armorArmoireShepherdRobesText'), notes: t('armorArmoireShepherdRobesNotes', {attrs: 9}), value: 100, str: 9, per: 9, set: 'shepherd', canOwn: ((u)-> u.items.gear.owned.armor_armoire_shepherdRobes?) - royalRobes: text: t('armorArmoireRoyalRobesText'), notes: t('armorArmoireRoyalRobesNotes', {attrs: 5}), value: 100, con: 5, per: 5, int: 5, set: 'royal', canOwn: ((u)-> u.items.gear.owned.armor_armoire_royalRobes?) - - head: - base: - 0: text: t('headBase0Text'), notes: t('headBase0Notes'), value:0 - warrior: - 1: text: t('headWarrior1Text'), notes: t('headWarrior1Notes', {str: 2}), str: 2, value:15 - 2: text: t('headWarrior2Text'), notes: t('headWarrior2Notes', {str: 4}), str: 4, value:25 - 3: text: t('headWarrior3Text'), notes: t('headWarrior3Notes', {str: 6}), str: 6, value:40 - 4: text: t('headWarrior4Text'), notes: t('headWarrior4Notes', {str: 9}), str: 9, value:60 - 5: text: t('headWarrior5Text'), notes: t('headWarrior5Notes', {str: 12}), str: 12, value:80, last: true - rogue: - 1: text: t('headRogue1Text'), notes: t('headRogue1Notes', {per: 2}), per: 2, value:15 - 2: text: t('headRogue2Text'), notes: t('headRogue2Notes', {per: 4}), per: 4, value:25 - 3: text: t('headRogue3Text'), notes: t('headRogue3Notes', {per: 6}), per: 6, value:40 - 4: text: t('headRogue4Text'), notes: t('headRogue4Notes', {per: 9}), per: 9, value:60 - 5: text: t('headRogue5Text'), notes: t('headRogue5Notes', {per: 12}), per: 12, value:80, last: true - wizard: - 1: text: t('headWizard1Text'), notes: t('headWizard1Notes', {per: 2}), per: 2, value:15 - 2: text: t('headWizard2Text'), notes: t('headWizard2Notes', {per: 3}), per: 3, value:25 - 3: text: t('headWizard3Text'), notes: t('headWizard3Notes', {per: 5}), per: 5, value:40 - 4: text: t('headWizard4Text'), notes: t('headWizard4Notes', {per: 7}), per: 7, value:60 - 5: text: t('headWizard5Text'), notes: t('headWizard5Notes', {per: 10}), per: 10, value:80, last: true - healer: - 1: text: t('headHealer1Text'), notes: t('headHealer1Notes', {int: 2}), int: 2, value:15 - 2: text: t('headHealer2Text'), notes: t('headHealer2Notes', {int: 3}), int: 3, value:25 - 3: text: t('headHealer3Text'), notes: t('headHealer3Notes', {int: 5}), int: 5, value:40 - 4: text: t('headHealer4Text'), notes: t('headHealer4Notes', {int: 7}), int: 7, value:60 - 5: text: t('headHealer5Text'), notes: t('headHealer5Notes', {int: 9}), int: 9, value:80, last: true - special: - # Backer, Contributor, and Quest Rewards - 0: text: t('headSpecial0Text'), notes: t('headSpecial0Notes', {int: 20}), int: 20, value:150, canOwn: ((u)-> +u.backer?.tier >= 45) - 1: text: t('headSpecial1Text'), notes: t('headSpecial1Notes', {attrs: 6}), con: 6, str: 6, per: 6, int: 6, value:170, canOwn: ((u)-> +u.contributor?.level >= 3) - 2: text: t('headSpecial2Text'), notes: t('headSpecial2Notes', {attrs: 25}), int: 25, str: 25, value:200, canOwn: ((u)-> (+u.backer?.tier >= 300) or u.items.gear.owned.head_special_2?) - fireCoralCirclet: text: t('headSpecialFireCoralCircletText'), notes: t('headSpecialFireCoralCircletNotes', {per: 15}), per: 15, value: 130, canOwn: ((u)-> u.items.gear.owned.head_special_fireCoralCirclet?) - # Winter Wonderland - nye: event: events.winter, text: t('headSpecialNyeText'), notes: t('headSpecialNyeNotes'), value: 0 - yeti: event: events.winter, specialClass: 'warrior', text: t('headSpecialYetiText'), notes: t('headSpecialYetiNotes', {str: 9}), str: 9, value:60 - ski: event: events.winter, specialClass: 'rogue', text: t('headSpecialSkiText'), notes: t('headSpecialSkiNotes', {per: 9}), per: 9, value:60 - candycane: event: events.winter, specialClass: 'wizard', text: t('headSpecialCandycaneText'), notes: t('headSpecialCandycaneNotes', {per: 7}), per: 7, value:60 - snowflake: event: events.winter, specialClass: 'healer', text: t('headSpecialSnowflakeText'), notes: t('headSpecialSnowflakeNotes', {int: 7}), int: 7, value:60 - # Spring Fling - springRogue: event: events.spring, specialClass: 'rogue', text: t('headSpecialSpringRogueText'), notes: t('headSpecialSpringRogueNotes', {per: 9}),value: 60,per: 9 - springWarrior: event: events.spring, specialClass: 'warrior', text: t('headSpecialSpringWarriorText'), notes: t('headSpecialSpringWarriorNotes', {str: 9}),value: 60,str: 9 - springMage: event: events.spring, specialClass: 'wizard', text: t('headSpecialSpringMageText'), notes: t('headSpecialSpringMageNotes', {per: 7}),value: 60,per: 7 - springHealer: event: events.spring, specialClass: 'healer', text: t('headSpecialSpringHealerText'), notes: t('headSpecialSpringHealerNotes', {int: 7}), value: 60, int: 7 - # Summer Splash - summerRogue: event: events.summer, specialClass: 'rogue', text: t('headSpecialSummerRogueText'), notes: t('headSpecialSummerRogueNotes', {per: 9}),value: 60,per: 9 - summerWarrior: event: events.summer, specialClass: 'warrior', text: t('headSpecialSummerWarriorText'), notes: t('headSpecialSummerWarriorNotes', {str: 9}),value: 60,str: 9 - summerMage: event: events.summer, specialClass: 'wizard', text: t('headSpecialSummerMageText'), notes: t('headSpecialSummerMageNotes', {per: 7}),value: 60,per: 7 - summerHealer: event: events.summer, specialClass: 'healer', text: t('headSpecialSummerHealerText'), notes: t('headSpecialSummerHealerNotes', {int: 7}), value: 60, int: 7 - # Fall Festival - fallRogue: event: events.fall, specialClass: 'rogue', text: t('headSpecialFallRogueText'), notes: t('headSpecialFallRogueNotes', {per: 9}),value: 60,per: 9, canBuy: (()->true) - fallWarrior: event: events.fall, specialClass: 'warrior', text: t('headSpecialFallWarriorText'), notes: t('headSpecialFallWarriorNotes', {str: 9}),value: 60,str: 9, canBuy: (()->true) - fallMage: event: events.fall, specialClass: 'wizard', text: t('headSpecialFallMageText'), notes: t('headSpecialFallMageNotes', {per: 7}),value: 60,per: 7, canBuy: (()->true) - fallHealer: event: events.fall, specialClass: 'healer', text: t('headSpecialFallHealerText'), notes: t('headSpecialFallHealerNotes', {int: 7}), value: 60, int: 7, canBuy: (()->true) - # Winter 2015 - winter2015Rogue: event: events.winter2015, specialClass: 'rogue', text: t('headSpecialWinter2015RogueText'), notes: t('headSpecialWinter2015RogueNotes', {per: 9}),value: 60,per: 9 - winter2015Warrior: event: events.winter2015, specialClass: 'warrior', text: t('headSpecialWinter2015WarriorText'), notes: t('headSpecialWinter2015WarriorNotes', {str: 9}),value: 60,str: 9 - winter2015Mage: event: events.winter2015, specialClass: 'wizard', text: t('headSpecialWinter2015MageText'), notes: t('headSpecialWinter2015MageNotes', {per: 7}),value: 60,per: 7 - winter2015Healer: event: events.winter2015, specialClass: 'healer', text: t('headSpecialWinter2015HealerText'), notes: t('headSpecialWinter2015HealerNotes', {int: 7}), value: 60, int: 7 - nye2014: text: t('headSpecialNye2014Text'), notes: t('headSpecialNye2014Notes'), value: 0, canOwn: ((u)-> u.items.gear.owned.head_special_nye2014?) - # Spring 2015 - spring2015Rogue: event: events.spring2015, specialClass: 'rogue', text: t('headSpecialSpring2015RogueText'), notes: t('headSpecialSpring2015RogueNotes', {per: 9}),value: 60,per: 9 - spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('headSpecialSpring2015WarriorText'), notes: t('headSpecialSpring2015WarriorNotes', {str: 9}),value: 60,str: 9 - spring2015Mage: event: events.spring2015, specialClass: 'wizard', text: t('headSpecialSpring2015MageText'), notes: t('headSpecialSpring2015MageNotes', {per: 7}),value: 60,per: 7 - spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('headSpecialSpring2015HealerText'), notes: t('headSpecialSpring2015HealerNotes', {int: 7}), value: 60, int: 7 - # Summer 2015 - summer2015Rogue: event: events.summer2015, specialClass: 'rogue', text: t('headSpecialSummer2015RogueText'), notes: t('headSpecialSummer2015RogueNotes', {per: 9}),value: 60,per: 9 - summer2015Warrior: event: events.summer2015, specialClass: 'warrior', text: t('headSpecialSummer2015WarriorText'), notes: t('headSpecialSummer2015WarriorNotes', {str: 9}),value: 60,str: 9 - summer2015Mage: event: events.summer2015, specialClass: 'wizard', text: t('headSpecialSummer2015MageText'), notes: t('headSpecialSummer2015MageNotes', {per: 7}),value: 60,per: 7 - summer2015Healer: event: events.summer2015, specialClass: 'healer', text: t('headSpecialSummer2015HealerText'), notes: t('headSpecialSummer2015HealerNotes', {int: 7}), value: 60, int: 7 - # Fall 2015 - fall2015Rogue: event: events.fall2015, specialClass: 'rogue', text: t('headSpecialFall2015RogueText'), notes: t('headSpecialFall2015RogueNotes', {per: 9}),value: 60,per: 9 - fall2015Warrior: event: events.fall2015, specialClass: 'warrior', text: t('headSpecialFall2015WarriorText'), notes: t('headSpecialFall2015WarriorNotes', {str: 9}),value: 60,str: 9 - fall2015Mage: event: events.fall2015, specialClass: 'wizard', text: t('headSpecialFall2015MageText'), notes: t('headSpecialFall2015MageNotes', {per: 7}),value: 60,per: 7 - fall2015Healer: event: events.fall2015, specialClass: 'healer', text: t('headSpecialFall2015HealerText'), notes: t('headSpecialFall2015HealerNotes', {int: 7}), value: 60, int: 7 - # Other - gaymerx: event: events.gaymerx, text: t('headSpecialGaymerxText'), notes: t('headSpecialGaymerxNotes'), value: 0 - mystery: - 201402: text: t('headMystery201402Text'), notes: t('headMystery201402Notes'), mystery:'201402', value: 0 - 201405: text: t('headMystery201405Text'), notes: t('headMystery201405Notes'), mystery:'201405', value: 0 - 201406: text: t('headMystery201406Text'), notes: t('headMystery201406Notes'), mystery:'201406', value: 0 - 201407: text: t('headMystery201407Text'), notes: t('headMystery201407Notes'), mystery:'201407', value: 0 - 201408: text: t('headMystery201408Text'), notes: t('headMystery201408Notes'), mystery:'201408', value: 0 - 201411: text: t('headMystery201411Text'), notes: t('headMystery201411Notes'), mystery:'201411', value: 0 - 201412: text: t('headMystery201412Text'), notes: t('headMystery201412Notes'), mystery:'201412', value: 0 - 201501: text: t('headMystery201501Text'), notes: t('headMystery201501Notes'), mystery:'201501', value: 0 - 201505: text: t('headMystery201505Text'), notes: t('headMystery201505Notes'), mystery:'201505', value: 0 - 201508: text: t('headMystery201508Text'), notes: t('headMystery201508Notes'), mystery:'201508', value: 0 - 201509: text: t('headMystery201509Text'), notes: t('headMystery201509Notes'), mystery:'201509', value: 0 - 301404: text: t('headMystery301404Text'), notes: t('headMystery301404Notes'), mystery:'301404', value: 0 - 301405: text: t('headMystery301405Text'), notes: t('headMystery301405Notes'), mystery:'301405', value: 0 - armoire: - lunarCrown: text: t('headArmoireLunarCrownText'), notes: t('headArmoireLunarCrownNotes', {con: 7, per: 7}), value: 100, con: 7, per: 7, set: 'soothing', canOwn: ((u)-> u.items.gear.owned.head_armoire_lunarCrown?) - redHairbow: text: t('headArmoireRedHairbowText'), notes: t('headArmoireRedHairbowNotes', {str: 5, int: 5, con: 5}), value: 100, str: 5, int: 5, con: 5, canOwn: ((u)-> u.items.gear.owned.head_armoire_redHairbow?) - violetFloppyHat: text: t('headArmoireVioletFloppyHatText'), notes: t('headArmoireVioletFloppyHatNotes', {per: 5, int: 5, con: 5}), value: 100, per: 5, int: 5, con: 5, canOwn: ((u)-> u.items.gear.owned.head_armoire_violetFloppyHat?) - gladiatorHelm: text: t('headArmoireGladiatorHelmText'), notes: t('headArmoireGladiatorHelmNotes', {per: 7, int: 7}), value: 100, per: 7, int: 7, set: 'gladiator', canOwn: ((u)-> u.items.gear.owned.head_armoire_gladiatorHelm?) - rancherHat: text: t('headArmoireRancherHatText'), notes: t('headArmoireRancherHatNotes', {str: 5, per: 5, int: 5}), value: 100, str: 5, per: 5, int: 5, set: 'rancher', canOwn: ((u)-> u.items.gear.owned.head_armoire_rancherHat?) - royalCrown: text: t('headArmoireRoyalCrownText'), notes: t('headArmoireRoyalCrownNotes', {str: 10}), value: 100, str: 10, set: 'royal', canOwn: ((u)-> u.items.gear.owned.head_armoire_royalCrown?) - blueHairbow: text: t('headArmoireBlueHairbowText'), notes: t('headArmoireBlueHairbowNotes', {per: 5, int: 5, con: 5}), value: 100, per: 5, int: 5, con: 5, canOwn: ((u)-> u.items.gear.owned.head_armoire_blueHairbow?) - goldenLaurels: text: t('headArmoireGoldenLaurelsText'), notes: t('headArmoireGoldenLaurelsNotes', {attrs: 8}), value: 100, per: 8, con: 8, set: 'goldenToga', canOwn: ((u)-> u.items.gear.owned.head_armoire_goldenLaurels?) - hornedIronHelm: text: t('headArmoireHornedIronHelmText'), notes: t('headArmoireHornedIronHelmNotes', {con: 9, str: 7}), value: 100, con: 9, str:7, set: 'hornedIron', canOwn: ((u)-> u.items.gear.owned.head_armoire_hornedIronHelm?) - yellowHairbow: text: t('headArmoireYellowHairbowText'), notes: t('headArmoireYellowHairbowNotes', {attrs: 5}), value: 100, int: 5, per: 5, str: 5, canOwn: ((u)-> u.items.gear.owned.head_armoire_yellowHairbow?) - redFloppyHat: text: t('headArmoireRedFloppyHatText'), notes: t('headArmoireRedFloppyHatNotes', {attrs: 6}), value: 100, con: 6, int: 6, per: 6, canOwn: ((u)-> u.items.gear.owned.head_armoire_redFloppyHat?) - plagueDoctorHat: text: t('headArmoirePlagueDoctorHatText'), notes: t('headArmoirePlagueDoctorHatNotes', {int: 5, str: 6, con: 5}), value: 100, int: 5, str: 6, con: 5, set: 'plagueDoctor', canOwn: ((u)-> u.items.gear.owned.head_armoire_plagueDoctorHat?) - blackCat: text: t('headArmoireBlackCatText'), notes: t('headArmoireBlackCatNotes', {attrs: 9}), value: 100, int: 9, per: 9, canOwn: ((u)-> u.items.gear.owned.head_armoire_blackCat?) - orangeCat: text: t('headArmoireOrangeCatText'), notes: t('headArmoireOrangeCatNotes', {attrs: 9}), value: 100, con: 9, str: 9, canOwn: ((u)-> u.items.gear.owned.head_armoire_orangeCat?) - blueFloppyHat: text: t('headArmoireBlueFloppyHatText'), notes: t('headArmoireBlueFloppyHatNotes', {attrs: 7}), value: 100, per: 7, int: 7, con: 7, canOwn: ((u)-> u.items.gear.owned.head_armoire_blueFloppyHat?) - shepherdHeaddress: text: t('headArmoireShepherdHeaddressText'), notes: t('headArmoireShepherdHeaddressNotes', {int: 9}), value: 100, int: 9, set: 'shepherd', canOwn: ((u)-> u.items.gear.owned.head_armoire_shepherdHeaddress?) - - shield: - base: - 0: text: t('shieldBase0Text'), notes: t('shieldBase0Notes'), value:0 - #changed because this is what shows up for all classes, including those without shields - warrior: - #0: text: "No Shield", notes:'No shield.', value:0 - 1: text: t('shieldWarrior1Text'), notes: t('shieldWarrior1Notes', {con: 2}), con: 2, value:20 - 2: text: t('shieldWarrior2Text'), notes: t('shieldWarrior2Notes', {con: 3}), con: 3, value:35 - 3: text: t('shieldWarrior3Text'), notes: t('shieldWarrior3Notes', {con: 5}), con: 5, value:50 - 4: text: t('shieldWarrior4Text'), notes: t('shieldWarrior4Notes', {con: 7}), con: 7, value:70 - 5: text: t('shieldWarrior5Text'), notes: t('shieldWarrior5Notes', {con: 9}), con: 9, value:90, last: true - rogue: - 0: text: t('weaponRogue0Text'), notes: t('weaponRogue0Notes'), str: 0, value: 0 - 1: text: t('weaponRogue1Text'), notes: t('weaponRogue1Notes', {str: 2}), str: 2, value: 20 - 2: text: t('weaponRogue2Text'), notes: t('weaponRogue2Notes', {str: 3}), str: 3, value: 35 - 3: text: t('weaponRogue3Text'), notes: t('weaponRogue3Notes', {str: 4}), str: 4, value: 50 - 4: text: t('weaponRogue4Text'), notes: t('weaponRogue4Notes', {str: 6}), str: 6, value: 70 - 5: text: t('weaponRogue5Text'), notes: t('weaponRogue5Notes', {str: 8}), str: 8, value: 90 - 6: text: t('weaponRogue6Text'), notes: t('weaponRogue6Notes', {str: 10}), str: 10, value: 120, last: true - wizard: {} - #0: text: "No Shield", notes:'No shield.', def: 0, value:0, last: true - healer: - #0: text: "No Shield", notes:'No shield.', def: 0, value:0 - 1: text: t('shieldHealer1Text'), notes: t('shieldHealer1Notes', {con: 2}), con: 2, value:20 - 2: text: t('shieldHealer2Text'), notes: t('shieldHealer2Notes', {con: 4}), con: 4, value:35 - 3: text: t('shieldHealer3Text'), notes: t('shieldHealer3Notes', {con: 6}), con: 6, value:50 - 4: text: t('shieldHealer4Text'), notes: t('shieldHealer4Notes', {con: 9}), con: 9, value:70 - 5: text: t('shieldHealer5Text'), notes: t('shieldHealer5Notes', {con: 12}), con: 12, value:90, last: true - special: - # Backer, Contributor, and Quest Rewards - 0: text: t('shieldSpecial0Text'), notes: t('shieldSpecial0Notes', {per: 20}), per: 20, value:150, canOwn: ((u)-> +u.backer?.tier >= 45) - 1: text: t('shieldSpecial1Text'), notes: t('shieldSpecial1Notes', {attrs: 6}), con: 6, str: 6, per: 6, int:6, value:170, canOwn: ((u)-> +u.contributor?.level >= 5) - goldenknight: text: t('shieldSpecialGoldenknightText'), notes: t('shieldSpecialGoldenknightNotes', {attrs: 25}), con: 25, per: 25, value:200, canOwn: ((u)-> u.items.gear.owned.shield_special_goldenknight?) - moonpearlShield: text: t('shieldSpecialMoonpearlShieldText'), notes: t('shieldSpecialMoonpearlShieldNotes', {con: 15}), con: 15, value: 130, canOwn: ((u)-> u.items.gear.owned.shield_special_moonpearlShield?) - # Winter Wonderland - yeti: event: events.winter, specialClass: 'warrior', text: t('shieldSpecialYetiText'), notes: t('shieldSpecialYetiNotes', {con: 7}), con: 7, value: 70 - ski: event: events.winter, specialClass: 'rogue', text: t('weaponSpecialSkiText'), notes: t('weaponSpecialSkiNotes', {str: 8}), str: 8, value: 90 - snowflake: event: events.winter, specialClass: 'healer', text: t('shieldSpecialSnowflakeText'), notes: t('shieldSpecialSnowflakeNotes', {con: 9}), con: 9, value: 70 - # Spring Fling - springRogue: event: events.spring, specialClass: 'rogue', text: t('shieldSpecialSpringRogueText'), notes: t('shieldSpecialSpringRogueNotes', {str: 8}), value: 80, str: 8 - springWarrior: event: events.spring, specialClass: 'warrior', text: t('shieldSpecialSpringWarriorText'), notes: t('shieldSpecialSpringWarriorNotes', {con: 7}), value: 70, con: 7 - springHealer: event: events.spring, specialClass: 'healer', text: t('shieldSpecialSpringHealerText'), notes: t('shieldSpecialSpringHealerNotes', {con: 9}), value: 70, con: 9 - # Summer Splash - summerRogue: event: events.summer, specialClass: 'rogue', text: t('shieldSpecialSummerRogueText'), notes: t('shieldSpecialSummerRogueNotes', {str: 8}), value: 80, str: 8 - summerWarrior: event: events.summer, specialClass: 'warrior', text: t('shieldSpecialSummerWarriorText'), notes: t('shieldSpecialSummerWarriorNotes', {con: 7}), value: 70, con: 7 - summerHealer: event: events.summer, specialClass: 'healer', text: t('shieldSpecialSummerHealerText'), notes: t('shieldSpecialSummerHealerNotes', {con: 9}), value: 70, con: 9 - # Fall Festival - fallRogue: event: events.fall, specialClass: 'rogue', text: t('shieldSpecialFallRogueText'), notes: t('shieldSpecialFallRogueNotes', {str: 8}), value: 80, str: 8, canBuy: (()->true) - fallWarrior: event: events.fall, specialClass: 'warrior', text: t('shieldSpecialFallWarriorText'), notes: t('shieldSpecialFallWarriorNotes', {con: 7}), value: 70, con: 7, canBuy: (()->true) - fallHealer: event: events.fall, specialClass: 'healer', text: t('shieldSpecialFallHealerText'), notes: t('shieldSpecialFallHealerNotes', {con: 9}), value: 70, con: 9, canBuy: (()->true) - # Winter 2015 - winter2015Rogue: event: events.winter2015, specialClass: 'rogue', text: t('shieldSpecialWinter2015RogueText'), notes: t('shieldSpecialWinter2015RogueNotes', {str: 8}), value: 80, str: 8 - winter2015Warrior: event: events.winter2015, specialClass: 'warrior', text: t('shieldSpecialWinter2015WarriorText'), notes: t('shieldSpecialWinter2015WarriorNotes', {con: 7}), value: 70, con: 7 - winter2015Healer: event: events.winter2015, specialClass: 'healer', text: t('shieldSpecialWinter2015HealerText'), notes: t('shieldSpecialWinter2015HealerNotes', {con: 9}), value: 70, con: 9 - # Spring 2015 - spring2015Rogue: event: events.spring2015, specialClass: 'rogue', text: t('shieldSpecialSpring2015RogueText'), notes: t('shieldSpecialSpring2015RogueNotes', {str: 8}), value: 80, str: 8 - spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('shieldSpecialSpring2015WarriorText'), notes: t('shieldSpecialSpring2015WarriorNotes', {con: 7}), value: 70, con: 7 - spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('shieldSpecialSpring2015HealerText'), notes: t('shieldSpecialSpring2015HealerNotes', {con: 9}), value: 70, con: 9 - # Summer 2015 - summer2015Rogue: event: events.summer2015, specialClass: 'rogue', text: t('shieldSpecialSummer2015RogueText'), notes: t('shieldSpecialSummer2015RogueNotes', {str: 8}), value: 80, str: 8 - summer2015Warrior: event: events.summer2015, specialClass: 'warrior', text: t('shieldSpecialSummer2015WarriorText'), notes: t('shieldSpecialSummer2015WarriorNotes', {con: 7}), value: 70, con: 7 - summer2015Healer: event: events.summer2015, specialClass: 'healer', text: t('shieldSpecialSummer2015HealerText'), notes: t('shieldSpecialSummer2015HealerNotes', {con: 9}), value: 70, con: 9 - # Fall 2015 - fall2015Rogue: event: events.fall2015, specialClass: 'rogue', text: t('shieldSpecialFall2015RogueText'), notes: t('shieldSpecialFall2015RogueNotes', {str: 8}), value: 80, str: 8 - fall2015Warrior: event: events.fall2015, specialClass: 'warrior', text: t('shieldSpecialFall2015WarriorText'), notes: t('shieldSpecialFall2015WarriorNotes', {con: 7}), value: 70, con: 7 - fall2015Healer: event: events.fall2015, specialClass: 'healer', text: t('shieldSpecialFall2015HealerText'), notes: t('shieldSpecialFall2015HealerNotes', {con: 9}), value: 70, con: 9 - mystery: - 301405: text: t('shieldMystery301405Text'), notes: t('shieldMystery301405Notes'), mystery:'301405', value: 0 - armoire: - gladiatorShield: text: t('shieldArmoireGladiatorShieldText'), notes: t('shieldArmoireGladiatorShieldNotes', {con: 5, str: 5}), value: 100, con: 5, str: 5, set: 'gladiator', canOwn: ((u)-> u.items.gear.owned.shield_armoire_gladiatorShield?) - midnightShield: text: t('shieldArmoireMidnightShieldText'), notes: t('shieldArmoireMidnightShieldNotes', {con: 10, str: 2}), value: 100, con: 10, str: 2, canOwn: ((u)-> u.items.gear.owned.shield_armoire_midnightShield?) - royalCane: text: t('shieldArmoireRoyalCaneText'), notes: t('shieldArmoireRoyalCaneNotes', {attrs: 5}), value: 100, con: 5, int: 5, per: 5, set: 'royal', canOwn: ((u)-> u.items.gear.owned.shield_armoire_royalCane?) - - back: - base: - 0: text: t('backBase0Text'), notes: t('backBase0Notes'), value:0 - mystery: - 201402: text: t('backMystery201402Text'), notes: t('backMystery201402Notes'), mystery:'201402', value: 0 - 201404: text: t('backMystery201404Text'), notes: t('backMystery201404Notes'), mystery:'201404', value: 0 - 201410: text: t('backMystery201410Text'), notes: t('backMystery201410Notes'), mystery:'201410', value: 0 - 201504: text: t('backMystery201504Text'), notes: t('backMystery201504Notes'), mystery:'201504', value: 0 - 201507: text: t('backMystery201507Text'), notes: t('backMystery201507Notes'), mystery:'201507', value: 0 - 201510: text: t('backMystery201510Text'), notes: t('backMystery201510Notes'), mystery:'201510', value: 0 - special: - wondercon_red: text: t('backSpecialWonderconRedText'), notes: t('backSpecialWonderconRedNotes'), value: 0, mystery:'wondercon' - wondercon_black: text: t('backSpecialWonderconBlackText'), notes: t('backSpecialWonderconBlackNotes'), value: 0, mystery:'wondercon' - - body: - base: - 0: text: t('bodyBase0Text'), notes:t('bodyBase0Notes'), value:0 - special: - wondercon_red: text: t('bodySpecialWonderconRedText'), notes: t('bodySpecialWonderconRedNotes'), value: 0, mystery:'wondercon' - wondercon_gold: text: t('bodySpecialWonderconGoldText'), notes: t('bodySpecialWonderconGoldNotes'), value: 0, mystery:'wondercon' - wondercon_black: text: t('bodySpecialWonderconBlackText'), notes: t('bodySpecialWonderconBlackNotes'), value: 0, mystery:'wondercon' - # Summer - summerHealer: event: events.summer, specialClass: 'healer', text: t('bodySpecialSummerHealerText'), notes: t('bodySpecialSummerHealerNotes'), value: 20 - summerMage: event: events.summer, specialClass: 'wizard', text: t('bodySpecialSummerMageText'), notes: t('bodySpecialSummerMageNotes'), value: 20 - # Summer 2015 - summer2015Healer: event: events.summer2015, specialClass: 'healer', text: t('bodySpecialSummer2015HealerText'), notes: t('bodySpecialSummer2015HealerNotes'), value: 20 - summer2015Mage: event: events.summer2015, specialClass: 'wizard', text: t('bodySpecialSummer2015MageText'), notes: t('bodySpecialSummer2015MageNotes'), value: 20 - summer2015Rogue: event: events.summer2015, specialClass: 'rogue', text: t('bodySpecialSummer2015RogueText'), notes: t('bodySpecialSummer2015RogueNotes'), value: 20 - summer2015Warrior: event: events.summer2015, specialClass: 'warrior', text: t('bodySpecialSummer2015WarriorText'), notes: t('bodySpecialSummer2015WarriorNotes'), value: 20 - - headAccessory: - base: - 0: text: t('headAccessoryBase0Text'), notes: t('headAccessoryBase0Notes'), value: 0, last: true - special: - # Spring Event - springRogue: event: events.spring, specialClass: 'rogue', text: t('headAccessorySpecialSpringRogueText'), notes: t('headAccessorySpecialSpringRogueNotes'), value: 20 - springWarrior: event: events.spring, specialClass: 'warrior', text: t('headAccessorySpecialSpringWarriorText'), notes: t('headAccessorySpecialSpringWarriorNotes'), value: 20 - springMage: event: events.spring, specialClass: 'wizard', text: t('headAccessorySpecialSpringMageText'), notes: t('headAccessorySpecialSpringMageNotes'), value: 20 - springHealer: event: events.spring, specialClass: 'healer', text: t('headAccessorySpecialSpringHealerText'), notes: t('headAccessorySpecialSpringHealerNotes'), value: 20 - # Spring 2015 - spring2015Rogue: event: events.spring2015, specialClass: 'rogue', text: t('headAccessorySpecialSpring2015RogueText'), notes: t('headAccessorySpecialSpring2015RogueNotes'), value: 20 - spring2015Warrior: event: events.spring2015, specialClass: 'warrior', text: t('headAccessorySpecialSpring2015WarriorText'), notes: t('headAccessorySpecialSpring2015WarriorNotes'), value: 20 - spring2015Mage: event: events.spring2015, specialClass: 'wizard', text: t('headAccessorySpecialSpring2015MageText'), notes: t('headAccessorySpecialSpring2015MageNotes'), value: 20 - spring2015Healer: event: events.spring2015, specialClass: 'healer', text: t('headAccessorySpecialSpring2015HealerText'), notes: t('headAccessorySpecialSpring2015HealerNotes'), value: 20 - # Animal ears - bearEars: gearSet: 'animal', text: t('headAccessoryBearEarsText'), notes: t('headAccessoryBearEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_bearEars?), canBuy: (()->true) - cactusEars: gearSet: 'animal', text: t('headAccessoryCactusEarsText'), notes: t('headAccessoryCactusEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_cactusEars?), canBuy: (()->true) - foxEars: gearSet: 'animal', text: t('headAccessoryFoxEarsText'), notes: t('headAccessoryFoxEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_foxEars?), canBuy: (()->true) - lionEars: gearSet: 'animal', text: t('headAccessoryLionEarsText'), notes: t('headAccessoryLionEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_lionEars?), canBuy: (()->true) - pandaEars: gearSet: 'animal', text: t('headAccessoryPandaEarsText'), notes: t('headAccessoryPandaEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_pandaEars?), canBuy: (()->true) - pigEars: gearSet: 'animal', text: t('headAccessoryPigEarsText'), notes: t('headAccessoryPigEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_pigEars?), canBuy: (()->true) - tigerEars: gearSet: 'animal', text: t('headAccessoryTigerEarsText'), notes: t('headAccessoryTigerEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_tigerEars?), canBuy: (()->true) - wolfEars: gearSet: 'animal', text: t('headAccessoryWolfEarsText'), notes: t('headAccessoryWolfEarsNotes'), value: 20, canOwn: ((u)-> u.items.gear.owned.headAccessory_special_wolfEars?), canBuy: (()->true) - mystery: - 201403: text: t('headAccessoryMystery201403Text'), notes: t('headAccessoryMystery201403Notes'), mystery:'201403', value: 0 - 201404: text: t('headAccessoryMystery201404Text'), notes: t('headAccessoryMystery201404Notes'), mystery:'201404', value: 0 - 201409: text: t('headAccessoryMystery201409Text'), notes: t('headAccessoryMystery201409Notes'), mystery:'201409', value: 0 - 201502: text: t('headAccessoryMystery201502Text'), notes: t('headAccessoryMystery201502Notes'), mystery:'201502', value: 0 - 201510: text: t('headAccessoryMystery201510Text'), notes: t('headAccessoryMystery201510Notes'), mystery:'201510', value: 0 - 301405: text: t('headAccessoryMystery301405Text'), notes: t('headAccessoryMystery301405Notes'), mystery:'301405', value: 0 - - eyewear: - base: - 0: text: t('eyewearBase0Text'), notes: t('eyewearBase0Notes'), value: 0, last: true - special: - wondercon_red: text: t('eyewearSpecialWonderconRedText'), notes: t('eyewearSpecialWonderconRedNotes'), value: 0, mystery:'wondercon' - wondercon_black: text: t('eyewearSpecialWonderconBlackText'), notes: t('eyewearSpecialWonderconBlackNotes'), value: 0, mystery:'wondercon' - #Summer - summerRogue: event: events.summer, specialClass: 'rogue', text: t('eyewearSpecialSummerRogueText'), notes: t('eyewearSpecialSummerRogueNotes'), value: 20 - summerWarrior: event: events.summer, specialClass: 'warrior', text: t('eyewearSpecialSummerWarriorText'), notes: t('eyewearSpecialSummerWarriorNotes'), value: 20 - mystery: - 201503: text: t('eyewearMystery201503Text'), notes: t('eyewearMystery201503Notes'), mystery:'201503', value: 0 - 201506: text: t('eyewearMystery201506Text'), notes: t('eyewearMystery201506Notes'), mystery:'201506', value: 0 - 201507: text: t('eyewearMystery201507Text'), notes: t('eyewearMystery201507Notes'), mystery:'201507', value: 0 - 301404: text: t('eyewearMystery301404Text'), notes: t('eyewearMystery301404Notes'), mystery:'301404', value: 0 - 301405: text: t('eyewearMystery301405Text'), notes: t('eyewearMystery301405Notes'), mystery:'301405', value: 0 - armoire: - plagueDoctorMask: text: t('eyewearArmoirePlagueDoctorMaskText'), notes: t('eyewearArmoirePlagueDoctorMaskNotes'), value: 100, set: 'plagueDoctor', canOwn: ((u)-> u.items.gear.owned.eyewear_armoire_plagueDoctorMask?) - -### - The gear is exported as a tree (defined above), and a flat list (eg, {weapon_healer_1: .., shield_special_0: ...}) since - they are needed in different forms at different points in the app -### -api.gear = - tree: gear - flat: {} - -_.each gearTypes, (type) -> - _.each classes.concat(['base', 'special', 'mystery', 'armoire']), (klass) -> - # add "type" to each item, so we can reference that as "weapon" or "armor" in the html - _.each gear[type][klass], (item, i) -> - key = "#{type}_#{klass}_#{i}" - _.defaults item, {type, key, klass, index: i, str:0, int:0, per:0, con:0, canBuy:(()->false)} - - if item.event - #? indicates null/undefined. true means they own currently, false means they once owned - and false is what we're - # after (they can buy back if they bought it during the event's timeframe) - _canOwn = item.canOwn or (->true) - item.canOwn = (u)-> - _canOwn(u) and - (u.items.gear.owned[key]? or (moment().isAfter(item.event.start) and moment().isBefore(item.event.end))) and - (if item.specialClass then (u.stats.class is item.specialClass) else true) - - if item.mystery - item.canOwn = (u)-> u.items.gear.owned[key]? - - api.gear.flat[key] = item - -### - Time Traveler Store, mystery sets need their items mapped in -### -_.each api.mystery, (v,k)-> v.items = _.where api.gear.flat, {mystery:k} -api.timeTravelerStore = (owned) -> - ownedKeys = _.keys owned.toObject?() or owned # mongoose workaround - _.reduce api.mystery, (m,v,k)-> - return m if k=='wondercon' or ~ownedKeys.indexOf(v.items[0].key) # skip wondercon and already-owned sets - m[k] = v;m - , {} - -### - --------------------------------------------------------------- - Unique Rewards: Potion and Armoire - --------------------------------------------------------------- -### - -api.potion = - type: 'potion', - text: t('potionText'), - notes: t('potionNotes'), - value: 25, - key: 'potion' - -api.armoire = - type: 'armoire', - text: t('armoireText'), - notes: ((user, count)-> - return t('armoireNotesEmpty')() if (user.flags.armoireEmpty) - return t('armoireNotesFull')() + count - ), - value: 100, - key: 'armoire', - canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true)) - -### - --------------------------------------------------------------- - Classes - --------------------------------------------------------------- -### - -api.classes = classes - -### - --------------------------------------------------------------- - Gear Types - --------------------------------------------------------------- -### - -api.gearTypes = gearTypes - -### - --------------------------------------------------------------- - Spells - --------------------------------------------------------------- - Text, notes, and mana are obvious. The rest: - - * {target}: one of [task, self, party, user]. This is very important, because if the cast() function is expecting one - thing and receives another, it will cause errors. `self` is used for self buffs, multi-task debuffs, AOEs (eg, meteor-shower), - etc. Basically, use self for anything that's not [task, party, user] and is an instant-cast - - * {cast}: the function that's run to perform the ability's action. This is pretty slick - because this is exported to the - web, this function can be performed on the client and on the server. `user` param is self (needed for determining your - own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells, - you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the - spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are - available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users, - so you'll want to iterate over them like: `_.each(target,function(member){...})` - - Note, user.stats.mp is docked after automatically (it's appended to functions automatically down below in an _.each) -### - -# -diminishingReturns = (bonus, max, halfway=max/2) -> max*(bonus/(bonus+halfway)) - -calculateBonus = (value, stat, crit=1, stat_scale=0.5) -> (if value < 0 then 1 else value+1) + (stat * stat_scale * crit) - -api.spells = - - wizard: - fireball: - # Burst of Flames - text: t('spellWizardFireballText') - mana: 10 - lvl: 11 - target: 'task' - notes: t('spellWizardFireballNotes') - cast: (user, target) -> - bonus = user._statsComputed.int * user.fns.crit('per') - bonus *= Math.ceil ((if target.value < 0 then 1 else target.value+1) *.075) - user.stats.exp += diminishingReturns(bonus,75) - user.party.quest.progress.up ?= 0 - user.party.quest.progress.up += Math.ceil(user._statsComputed.int * .1) - # Sync the user stats to see if we level the user - req = { language: user.preferences.language } - user.fns.updateStats( user.stats , req ) - - mpheal: - # Ethereal Surge - text: t('spellWizardMPHealText') - mana: 30 - lvl: 12 - target: 'party' - notes: t('spellWizardMPHealNotes'), - cast: (user, target)-> - _.each target, (member) -> - bonus = user._statsComputed.int - if user._id != member._id - member.stats.mp += Math.ceil(diminishingReturns(bonus, 25, 125)) # maxes out at 25 - - earth: - # Earthquake - text: t('spellWizardEarthText') - mana: 35 - lvl: 13 - target: 'party' - notes: t('spellWizardEarthNotes'), - cast: (user, target) -> - _.each target, (member) -> - bonus = user._statsComputed.int - user.stats.buffs.int - member.stats.buffs.int ?= 0 - member.stats.buffs.int += Math.ceil(diminishingReturns(bonus, 30,200)) - - frost: - # Chilling Frost - text: t('spellWizardFrostText'), - mana: 40 - lvl: 14 - target: 'self' - notes: t('spellWizardFrostNotes'), - cast: (user, target) -> - user.stats.buffs.streaks = true - - warrior: - smash: - # Brutal Smash - text: t('spellWarriorSmashText') - mana: 10 - lvl: 11 - target: 'task' - notes: t('spellWarriorSmashNotes') - cast: (user, target) -> - bonus = user._statsComputed.str * user.fns.crit('con') - target.value += diminishingReturns(bonus, 2.5, 35) - user.party.quest.progress.up ?= 0 - user.party.quest.progress.up += diminishingReturns(bonus, 55, 70) - - defensiveStance: - # Defensive Stance - text: t('spellWarriorDefensiveStanceText') - mana: 25 - lvl: 12 - target: 'self' - notes: t('spellWarriorDefensiveStanceNotes') - cast: (user, target) -> - bonus = user._statsComputed.con - user.stats.buffs.con - user.stats.buffs.con ?= 0 - user.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 40, 200)) - - valorousPresence: - # Valorous Presence - text: t('spellWarriorValorousPresenceText') - mana: 20 - lvl: 13 - target: 'party' - notes: t('spellWarriorValorousPresenceNotes') - cast: (user, target) -> - _.each target, (member) -> - bonus = user._statsComputed.str - user.stats.buffs.str - member.stats.buffs.str ?= 0 - member.stats.buffs.str += Math.ceil(diminishingReturns(bonus, 20, 200)) - - intimidate: - # Intimidating Gaze - text: t('spellWarriorIntimidateText') - mana: 15 - lvl: 14 - target: 'party' - notes: t('spellWarriorIntimidateNotes') - cast: (user, target) -> - _.each target, (member) -> - bonus = user._statsComputed.con - user.stats.buffs.con - member.stats.buffs.con ?= 0 - member.stats.buffs.con += Math.ceil(diminishingReturns(bonus,24,200)) - - rogue: - pickPocket: - # Pickpocket - text: t('spellRoguePickPocketText') - mana: 10 - lvl: 11 - target: 'task' - notes: t('spellRoguePickPocketNotes') - cast: (user, target) -> - bonus = calculateBonus(target.value, user._statsComputed.per) - user.stats.gp += diminishingReturns(bonus, 25, 75) - - backStab: - # Backstab - text: t('spellRogueBackStabText') - mana: 15 - lvl: 12 - target: 'task' - notes: t('spellRogueBackStabNotes') - cast: (user, target) -> - _crit = user.fns.crit('str', .3) - bonus = calculateBonus(target.value, user._statsComputed.str, _crit) - user.stats.exp += diminishingReturns(bonus, 75, 50) - user.stats.gp += diminishingReturns(bonus, 18, 75) - # Sync the user stats to see if we level the user - req = { language: user.preferences.language } - user.fns.updateStats( user.stats , req ) - - toolsOfTrade: - # Tools of the Trade - text: t('spellRogueToolsOfTradeText') - mana: 25 - lvl: 13 - target: 'party' - notes: t('spellRogueToolsOfTradeNotes') - cast: (user, target) -> - _.each target, (member) -> - bonus = user._statsComputed.per - user.stats.buffs.per - member.stats.buffs.per ?= 0 - member.stats.buffs.per += Math.ceil(diminishingReturns(bonus, 100, 50)) - - stealth: - # Stealth - text: t('spellRogueStealthText') - mana: 45 - lvl: 14 - target: 'self' - notes: t('spellRogueStealthNotes') - cast: (user, target) -> - user.stats.buffs.stealth ?= 0 - ## scales to user's # of dailies; Diminishing Returns, maxes out at 64%, halfway point at 55 PER## - user.stats.buffs.stealth += Math.ceil( diminishingReturns(user._statsComputed.per, user.dailys.length*0.64,55)) - - healer: - heal: - # Healing Light - text: t('spellHealerHealText') - mana: 15 - lvl: 11 - target: 'self' - notes: t('spellHealerHealNotes') - cast: (user, target) -> - user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .075 - user.stats.hp = 50 if user.stats.hp > 50 - - brightness: - # Searing Brightness - text: t('spellHealerBrightnessText') - mana: 15 - lvl: 12 - target: 'self' - notes: t('spellHealerBrightnessNotes') - cast: (user, target) -> - _.each user.tasks, (target) -> - return if target.type is 'reward' - target.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40)) - - protectAura: - # Protective Aura - text: t('spellHealerProtectAuraText') - mana: 30 - lvl: 13 - target: 'party' - notes: t('spellHealerProtectAuraNotes') - cast: (user, target) -> - _.each target, (member) -> - bonus = user._statsComputed.con - user.stats.buffs.con - member.stats.buffs.con ?= 0 - member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 200, 200)) - - heallAll: - # Blessing - text: t('spellHealerHealAllText') - mana: 25 - lvl: 14 - target: 'party' - notes: t('spellHealerHealAllNotes') - cast: (user, target) -> - _.each target, (member) -> - member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .04 - member.stats.hp = 50 if member.stats.hp > 50 - - special: - snowball: - text: t('spellSpecialSnowballAuraText') - mana: 0 - value: 15 - target: 'user' - notes: t('spellSpecialSnowballAuraNotes') - cast: (user, target) -> - target.stats.buffs.snowball = true - target.stats.buffs.spookDust = false - target.stats.buffs.shinySeed = false - target.stats.buffs.seafoam = false - target.achievements.snowball ?= 0 - target.achievements.snowball++ - user.items.special.snowball-- - - salt: - text: t('spellSpecialSaltText') - mana: 0 - value: 5 - immediateUse: true - target: 'self' - notes: t('spellSpecialSaltNotes') - cast: (user, target) -> - user.stats.buffs.snowball = false - user.stats.gp -= 5 - - spookDust: - text: t('spellSpecialSpookDustText') - mana: 0 - value: 15 - target: 'user' - notes: t('spellSpecialSpookDustNotes') - cast: (user, target) -> - target.stats.buffs.snowball = false - target.stats.buffs.spookDust = true - target.stats.buffs.shinySeed = false - target.stats.buffs.seafoam = false - target.achievements.spookDust ?= 0 - target.achievements.spookDust++ - user.items.special.spookDust-- - - opaquePotion: - text: t('spellSpecialOpaquePotionText') - mana: 0 - value: 5 - immediateUse: true - target: 'self' - notes: t('spellSpecialOpaquePotionNotes') - cast: (user, target) -> - user.stats.buffs.spookDust = false - user.stats.gp -= 5 - - shinySeed: - text: t('spellSpecialShinySeedText') - mana: 0 - value: 15 - target: 'user' - notes: t('spellSpecialShinySeedNotes') - cast: (user, target) -> - target.stats.buffs.snowball = false - target.stats.buffs.spookDust = false - target.stats.buffs.shinySeed = true - target.stats.buffs.seafoam = false - target.achievements.shinySeed ?= 0 - target.achievements.shinySeed++ - user.items.special.shinySeed-- - - petalFreePotion: - text: t('spellSpecialPetalFreePotionText') - mana: 0 - value: 5 - immediateUse: true - target: 'self' - notes: t('spellSpecialPetalFreePotionNotes') - cast: (user, target) -> - user.stats.buffs.shinySeed = false - user.stats.gp -= 5 - - seafoam: - text: t('spellSpecialSeafoamText') - mana: 0 - value: 15 - target: 'user' - notes: t('spellSpecialSeafoamNotes') - cast: (user, target) -> - target.stats.buffs.snowball = false - target.stats.buffs.spookDust = false - target.stats.buffs.shinySeed = false - target.stats.buffs.seafoam = true - target.achievements.seafoam ?= 0 - target.achievements.seafoam++ - user.items.special.seafoam-- - - sand: - text: t('spellSpecialSandText') - mana: 0 - value: 5 - immediateUse: true - target: 'self' - notes: t('spellSpecialSandNotes') - cast: (user, target) -> - user.stats.buffs.seafoam = false - user.stats.gp -= 5 - - nye: - text: t('nyeCard') - mana: 0 - value: 10 - immediateUse: true - silent: true - target: 'user' - notes: t('nyeCardNotes') - cast: (user, target) -> - if user == target - user.achievements.nye ?= 0 - user.achievements.nye++ - else - _.each [user,target], (t)-> - t.achievements.nye ?= 0 - t.achievements.nye++ - if !target.items.special.nyeReceived - target.items.special.nyeReceived = [] - target.items.special.nyeReceived.push user.profile.name - target.flags.cardReceived = true - - target.markModified? 'items.special.nyeReceived' - user.stats.gp -= 10 - - valentine: - text: t('valentineCard') - mana: 0 - value: 10 - immediateUse: true - silent: true - target: 'user' - notes: t('valentineCardNotes') - cast: (user, target) -> - if user == target - user.achievements.valentine ?= 0 - user.achievements.valentine++ - else - _.each [user,target], (t)-> - t.achievements.valentine ?= 0 - t.achievements.valentine++ - if !target.items.special.valentineReceived - target.items.special.valentineReceived = [] - target.items.special.valentineReceived.push user.profile.name - target.flags.cardReceived = true - - target.markModified? 'items.special.valentineReceived' - user.stats.gp -= 10 - - greeting: - text: t('greetingCard') - mana: 0 - value: 10 - immediateUse: true - silent: true - target: 'user' - notes: t('greetingCardNotes') - cast: (user, target) -> - if user == target - user.achievements.greeting ?= 0 - user.achievements.greeting++ - else - _.each [user,target], (t)-> - t.achievements.greeting ?= 0 - t.achievements.greeting++ - if !target.items.special.greetingReceived - target.items.special.greetingReceived = [] - target.items.special.greetingReceived.push user.profile.name - target.flags.cardReceived = true - - target.markModified? 'items.special.greetingReceived' - user.stats.gp -= 10 - - thankyou: - text: t('thankyouCard') - mana: 0 - value: 10 - immediateUse: true - silent: true - target: 'user' - notes: t('thankyouCardNotes') - cast: (user, target) -> - if user == target - user.achievements.thankyou ?= 0 - user.achievements.thankyou++ - else - _.each [user,target], (t)-> - t.achievements.thankyou ?= 0 - t.achievements.thankyou++ - if !target.items.special.thankyouReceived - target.items.special.thankyouReceived = [] - target.items.special.thankyouReceived.push user.profile.name - target.flags.cardReceived = true - - target.markModified? 'items.special.thankyouReceived' - user.stats.gp -= 10 - -api.cardTypes = - greeting: - key: 'greeting' - messageOptions: 4 - yearRound: true - nye: - key: 'nye' - messageOptions: 5 - thankyou: - key: 'thankyou' - messageOptions: 4 - yearRound: true - valentine: - key: 'valentine' - messageOptions: 4 - -# Intercept all spells to reduce user.stats.mp after casting the spell -_.each api.spells, (spellClass) -> - _.each spellClass, (spell, key) -> - spell.key = key - _cast = spell.cast - spell.cast = (user, target) -> - #return if spell.target and spell.target != (if target.type then 'task' else 'user') - _cast(user,target) - user.stats.mp -= spell.mana - -api.special = api.spells.special - -### - --------------------------------------------------------------- - Drops - --------------------------------------------------------------- -### - -api.dropEggs = - # value & other defaults set below - Wolf: text: t('dropEggWolfText'), adjective: t('dropEggWolfAdjective') - TigerCub: text: t('dropEggTigerCubText'), mountText: t('dropEggTigerCubMountText'), adjective: t('dropEggTigerCubAdjective') - PandaCub: text: t('dropEggPandaCubText'), mountText: t('dropEggPandaCubMountText'), adjective: t('dropEggPandaCubAdjective') - LionCub: text: t('dropEggLionCubText'), mountText: t('dropEggLionCubMountText'), adjective: t('dropEggLionCubAdjective') - Fox: text: t('dropEggFoxText'), adjective: t('dropEggFoxAdjective') - FlyingPig: text: t('dropEggFlyingPigText'), adjective: t('dropEggFlyingPigAdjective') - Dragon: text: t('dropEggDragonText'), adjective: t('dropEggDragonAdjective') - Cactus: text: t('dropEggCactusText'), adjective: t('dropEggCactusAdjective') - BearCub: text: t('dropEggBearCubText'), mountText: t('dropEggBearCubMountText'), adjective: t('dropEggBearCubAdjective') - -_.each api.dropEggs, (egg,key) -> - _.defaults egg, - canBuy: (()->true) - value: 3 - key: key - notes: t('eggNotes', {eggText: egg.text, eggAdjective: egg.adjective}) - mountText: egg.text - -api.questEggs = - # value & other defaults set below - Gryphon: text: t('questEggGryphonText'), adjective: t('questEggGryphonAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.gryphon? > 0) - Hedgehog: text: t('questEggHedgehogText'), adjective: t('questEggHedgehogAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.hedgehog? > 0) - Deer: text: t('questEggDeerText'), adjective: t('questEggDeerAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.ghost_stag? > 0) - Egg: text: t('questEggEggText'), adjective: t('questEggEggAdjective'), mountText: t('questEggEggMountText') - Rat: text: t('questEggRatText'), adjective: t('questEggRatAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.rat? > 0) - Octopus: text: t('questEggOctopusText'), adjective: t('questEggOctopusAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.octopus? > 0) - Seahorse: text: t('questEggSeahorseText'), adjective: t('questEggSeahorseAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.dilatory_derby? > 0) - Parrot: text: t('questEggParrotText'), adjective: t('questEggParrotAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.harpy? > 0) - Rooster: text: t('questEggRoosterText'), adjective: t('questEggRoosterAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.rooster? > 0) - Spider: text: t('questEggSpiderText'), adjective: t('questEggSpiderAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.spider? > 0) - Owl: text: t('questEggOwlText'), adjective: t('questEggOwlAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.owl? > 0) - Penguin: text: t('questEggPenguinText'), adjective: t('questEggPenguinAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.penguin? > 0) - TRex: text: t('questEggTRexText'), adjective: t('questEggTRexAdjective'), canBuy: ((u)-> (u.achievements.quests && (u.achievements.quests.trex? > 0 or u.achievements.quests.trex_undead? > 0))) - Rock: text: t('questEggRockText'), adjective: t('questEggRockAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.rock? > 0) - Bunny: text: t('questEggBunnyText'), adjective: t('questEggBunnyAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.bunny? > 0) - Slime: text: t('questEggSlimeText'), adjective: t('questEggSlimeAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.slime? > 0) - Sheep: text: t('questEggSheepText'), adjective: t('questEggSheepAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.sheep? > 0) - Cuttlefish: text: t('questEggCuttlefishText'), adjective: t('questEggCuttlefishAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.kraken? > 0) - Whale: text: t('questEggWhaleText'), adjective: t('questEggWhaleAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.whale? > 0) - Cheetah: text: t('questEggCheetahText'), adjective: t('questEggCheetahAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.cheetah? > 0) - Horse: text: t('questEggHorseText'), adjective: t('questEggHorseAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.horse? > 0) - Frog: text: t('questEggFrogText'), adjective: t('questEggFrogAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.frog? > 0) - Snake: text: t('questEggSnakeText'), adjective: t('questEggSnakeAdjective'), canBuy: ((u)-> u.achievements.quests && u.achievements.quests.snake? > 0) - -_.each api.questEggs, (egg,key) -> - _.defaults egg, - canBuy: (()->false) - value: 3 - key: key - notes: t('eggNotes', {eggText: egg.text, eggAdjective: egg.adjective}) - mountText: egg.text - -api.eggs = _.assign(_.cloneDeep(api.dropEggs), api.questEggs) - -# special pets & mounts are {key:i18n} -api.specialPets = - 'Wolf-Veteran': 'veteranWolf' - 'Wolf-Cerberus': 'cerberusPup' - 'Dragon-Hydra': 'hydra' - 'Turkey-Base': 'turkey' - 'BearCub-Polar': 'polarBearPup' - 'MantisShrimp-Base': 'mantisShrimp' - 'JackOLantern-Base': 'jackolantern' - 'Mammoth-Base': 'mammoth' - 'Tiger-Veteran': 'veteranTiger' - 'Phoenix-Base': 'phoenix' - -api.specialMounts = - 'BearCub-Polar': 'polarBear' - 'LionCub-Ethereal': 'etherealLion' - 'MantisShrimp-Base': 'mantisShrimp' - 'Turkey-Base': 'turkey' - 'Mammoth-Base': 'mammoth' - 'Orca-Base': 'orca' - 'Gryphon-RoyalPurple': 'royalPurpleGryphon' - 'Phoenix-Base': 'phoenix' - 'JackOLantern-Base': 'jackolantern' - -api.timeTravelStable = - pets: - 'Mammoth-Base': t('mammoth') - 'MantisShrimp-Base': t('mantisShrimp') - mounts: - 'Mammoth-Base': t('mammoth') - 'MantisShrimp-Base': t('mantisShrimp') - -api.dropHatchingPotions = - Base: value: 2, text: t('hatchingPotionBase') - White: value: 2, text: t('hatchingPotionWhite') - Desert: value: 2, text: t('hatchingPotionDesert') - Red: value: 3, text: t('hatchingPotionRed') - Shade: value: 3, text: t('hatchingPotionShade') - Skeleton: value: 3, text: t('hatchingPotionSkeleton') - Zombie: value: 4, text: t('hatchingPotionZombie') - CottonCandyPink: value: 4, text: t('hatchingPotionCottonCandyPink') - CottonCandyBlue: value: 4, text: t('hatchingPotionCottonCandyBlue') - Golden: value: 5, text: t('hatchingPotionGolden') - -api.premiumHatchingPotions = - Spooky: value: 2, text: t('hatchingPotionSpooky'), limited: true, canBuy: (()->false) - -_.each api.dropHatchingPotions, (pot,key) -> - _.defaults pot, {key, value: 2, notes: t('hatchingPotionNotes', {potText: pot.text}), premium: false, limited: false, canBuy: (()->true)} - -_.each api.premiumHatchingPotions, (pot,key) -> - _.defaults pot, {key, value: 2, notes: t('hatchingPotionNotes', {potText: pot.text}), addlNotes: t('premiumPotionAddlNotes'), premium: true, limited: false, canBuy: (()->true)} - -api.hatchingPotions = {} -_.merge(api.hatchingPotions, api.dropHatchingPotions) -_.merge(api.hatchingPotions, api.premiumHatchingPotions) - -api.pets = _.transform api.dropEggs, (m, egg) -> - _.defaults m, _.transform api.hatchingPotions, (m2, pot) -> - if not pot.premium - m2[egg.key + "-" + pot.key] = true - -api.premiumPets = _.transform api.dropEggs, (m, egg) -> - _.defaults m, _.transform api.hatchingPotions, (m2, pot) -> - if pot.premium - m2[egg.key + "-" + pot.key] = true - -api.questPets = _.transform api.questEggs, (m, egg) -> - _.defaults m, _.transform api.hatchingPotions, (m2, pot) -> - if not pot.premium - m2[egg.key + "-" + pot.key] = true - -api.mounts = _.transform api.dropEggs, (m, egg) -> - _.defaults m, _.transform api.hatchingPotions, (m2, pot) -> - if not pot.premium - m2[egg.key + "-" + pot.key] = true - -api.questMounts = _.transform api.questEggs, (m, egg) -> - _.defaults m, _.transform api.hatchingPotions, (m2, pot) -> - if not pot.premium - m2[egg.key + "-" + pot.key] = true - -api.food = - # Base - Meat: text: t('foodMeat'), canBuy:(()->true), canDrop:true, target: 'Base', article: '' - Milk: text: t('foodMilk'), canBuy:(()->true), canDrop:true, target: 'White', article: '' - Potatoe: text: t('foodPotatoe'), canBuy:(()->true), canDrop:true, target: 'Desert', article: 'a ' - Strawberry: text: t('foodStrawberry'), canBuy:(()->true), canDrop:true, target: 'Red', article: 'a ' - Chocolate: text: t('foodChocolate'), canBuy:(()->true), canDrop:true, target: 'Shade', article: '' - Fish: text: t('foodFish'), canBuy:(()->true), canDrop:true, target: 'Skeleton', article: 'a ' - RottenMeat: text: t('foodRottenMeat'), canBuy:(()->true), canDrop:true, target: 'Zombie', article: '' - CottonCandyPink: text: t('foodCottonCandyPink'), canBuy:(()->true), canDrop:true, target: 'CottonCandyPink', article: '' - CottonCandyBlue: text: t('foodCottonCandyBlue'), canBuy:(()->true), canDrop:true, target: 'CottonCandyBlue', article: '' - Honey: text: t('foodHoney'), canBuy:(()->true), canDrop:true, target: 'Golden', article: '' - - Saddle: canBuy:(()->true), text: t('foodSaddleText'), value: 5, notes: t('foodSaddleNotes') - - # Cake - Cake_Skeleton: text: t('foodCakeSkeleton'), target: 'Skeleton', article: '' - Cake_Base: text: t('foodCakeBase'), target: 'Base', article: '' - Cake_CottonCandyBlue: text: t('foodCakeCottonCandyBlue'), target: 'CottonCandyBlue', article: '' - Cake_CottonCandyPink: text: t('foodCakeCottonCandyPink'), target: 'CottonCandyPink', article: '' - Cake_Shade: text: t('foodCakeShade'), target: 'Shade', article: '' - Cake_White: text: t('foodCakeWhite'), target: 'White', article: '' - Cake_Golden: text: t('foodCakeGolden'), target: 'Golden', article: '' - Cake_Zombie: text: t('foodCakeZombie'), target: 'Zombie', article: '' - Cake_Desert: text: t('foodCakeDesert'), target: 'Desert', article: '' - Cake_Red: text: t('foodCakeRed'), target: 'Red', article: '' - - # Fall - Candy_Skeleton: text: t('foodCandySkeleton'), target: 'Skeleton', article: '' - Candy_Base: text: t('foodCandyBase'), target: 'Base', article: '' - Candy_CottonCandyBlue: text: t('foodCandyCottonCandyBlue'), target: 'CottonCandyBlue', article: '' - Candy_CottonCandyPink: text: t('foodCandyCottonCandyPink'), target: 'CottonCandyPink', article: '' - Candy_Shade: text: t('foodCandyShade'), target: 'Shade', article: '' - Candy_White: text: t('foodCandyWhite'), target: 'White', article: '' - Candy_Golden: text: t('foodCandyGolden'), target: 'Golden', article: '' - Candy_Zombie: text: t('foodCandyZombie'), target: 'Zombie', article: '' - Candy_Desert: text: t('foodCandyDesert'), target: 'Desert', article: '' - Candy_Red: text: t('foodCandyRed'), target: 'Red', article: '' - -_.each api.food, (food,key) -> - _.defaults food, {value: 1, key, notes: t('foodNotes'), canBuy:(()->false), canDrop:false} - -api.userCanOwnQuestCategories = [ - 'unlockable' - 'gold' - 'pet' -] - -api.quests = - - dilatory: - text: t("questDilatoryText") - notes: t("questDilatoryNotes") - completion: t("questDilatoryCompletion") - value: 0 - canBuy: (()->false) - category: 'world' - boss: - name: t("questDilatoryBoss") - # We ran an average of progress{up,down} on users over 5 days: {up:805025,down:1324423}. /5*30 (we want the - # event to last 30 days) = {hp:5mil,8mil}. Because Dilatory should cast Rage 3x during that time, 8mil/3=2.6mil (round up to 4) - hp: 5000000 - str: 1 - def: 1 - rage: - title: t("questDilatoryBossRageTitle") - description: t("questDilatoryBossRageDescription") - value: 4000000 - - # special, they won't always look like this - tavern:t('questDilatoryBossRageTavern') - stables:t('questDilatoryBossRageStables') - market:t('questDilatoryBossRageMarket') - drop: - items: [ - {type: 'pets', key: 'MantisShrimp-Base', text: t('questDilatoryDropMantisShrimpPet')} - {type: 'mounts', key: 'MantisShrimp-Base', text: t('questDilatoryDropMantisShrimpMount')} - - {type: 'food', key: 'Meat', text: t('foodMeat')} - {type: 'food', key: 'Milk', text: t('foodMilk')} - {type: 'food', key: 'Potatoe', text: t('foodPotatoe')} - {type: 'food', key: 'Strawberry', text: t('foodStrawberry')} - {type: 'food', key: 'Chocolate', text: t('foodChocolate')} - {type: 'food', key: 'Fish', text: t('foodFish')} - {type: 'food', key: 'RottenMeat', text: t('foodRottenMeat')} - {type: 'food', key: 'CottonCandyPink', text: t('foodCottonCandyPink')} - {type: 'food', key: 'CottonCandyBlue', text: t('foodCottonCandyBlue')} - {type: 'food', key: 'Honey', text: t('foodHoney')} - ] - gp: 0 - exp: 0 - - stressbeast: - text: t("questStressbeastText") - notes: t("questStressbeastNotes") - completion: t("questStressbeastCompletion") - completionChat: t("questStressbeastCompletionChat") - value: 0 - canBuy: (()->false) - category: 'world' - boss: - name: t("questStressbeastBoss") - hp: 2750000 - str: 1 - def: 1 - rage: - title: t("questStressbeastBossRageTitle") - description: t("questStressbeastBossRageDescription") - value: 1450000 - healing: .3 - stables:t('questStressbeastBossRageStables') - bailey:t('questStressbeastBossRageBailey') - guide:t('questStressbeastBossRageGuide') - desperation: - threshold: 500000 - str: 3.5 - def: 2 - text:t('questStressbeastDesperation') - drop: - items: [ - {type: 'pets', key: 'Mammoth-Base', text: t('questStressbeastDropMammothPet')} - {type: 'mounts', key: 'Mammoth-Base', text: t('questStressbeastDropMammothMount')} - - {type: 'food', key: 'Meat', text: t('foodMeat')} - {type: 'food', key: 'Milk', text: t('foodMilk')} - {type: 'food', key: 'Potatoe', text: t('foodPotatoe')} - {type: 'food', key: 'Strawberry', text: t('foodStrawberry')} - {type: 'food', key: 'Chocolate', text: t('foodChocolate')} - {type: 'food', key: 'Fish', text: t('foodFish')} - {type: 'food', key: 'RottenMeat', text: t('foodRottenMeat')} - {type: 'food', key: 'CottonCandyPink', text: t('foodCottonCandyPink')} - {type: 'food', key: 'CottonCandyBlue', text: t('foodCottonCandyBlue')} - {type: 'food', key: 'Honey', text: t('foodHoney')} - ] - gp: 0 - exp: 0 - - burnout: - text: t('questBurnoutText') - notes: t('questBurnoutNotes') - completion: t('questBurnoutCompletion') - completionChat: t('questBurnoutCompletionChat') - value: 0 - canBuy: (()->false) - category: 'world' - boss: - name: t('questBurnoutBoss') - hp: 11000000 - str: 2.5 - def: 1 - rage: - title: t('questBurnoutBossRageTitle') - description: t('questBurnoutBossRageDescription') - value: 1000000 - quests: t('questBurnoutBossRageQuests') - seasonalShop: t('questBurnoutBossRageSeasonalShop') - tavern: t('questBurnoutBossRageTavern') - drop: - items: [ - {type: 'pets', key: 'Phoenix-Base', text: t('questBurnoutDropPhoenixPet')} - {type: 'mounts', key: 'Phoenix-Base', text: t('questBurnoutDropPhoenixMount')} - - {type: 'food', key: 'Candy_Base', text: t('foodCandyBase')} - {type: 'food', key: 'Candy_White', text: t('foodCandyWhite')} - {type: 'food', key: 'Candy_Desert', text: t('foodCandyDesert')} - {type: 'food', key: 'Candy_Red', text: t('foodCandyRed')} - {type: 'food', key: 'Candy_Shade', text: t('foodCandyShade')} - {type: 'food', key: 'Candy_Skeleton', text: t('foodCandySkeleton')} - {type: 'food', key: 'Candy_Zombie', text: t('foodCandyZombie')} - {type: 'food', key: 'Candy_CottonCandyPink', text: t('foodCandyCottonCandyPink')} - {type: 'food', key: 'Candy_CottonCandyBlue', text: t('foodCandyCottonCandyBlue')} - {type: 'food', key: 'Candy_Golden', text: t('foodCandyGolden')} - ] - gp: 0 - exp: 0 - - evilsanta: - canBuy: (()->false) - text: t('questEvilSantaText') # title of the quest (eg, Deep into Vice's Layer) - notes: t('questEvilSantaNotes') - completion: t('questEvilSantaCompletion') - value: 4 # Gem cost to buy, GP sell-back - category: 'pet' - boss: - name: t('questEvilSantaBoss') # name of the boss himself (eg, Vice) - hp: 300 - str: 1 # Multiplier of users' missed dailies - drop: - items: [ - {type: 'mounts', key: 'BearCub-Polar', text: t('questEvilSantaDropBearCubPolarMount')} - ] - gp: 20 - exp: 100 # Exp bonus from defeating the boss - - evilsanta2: - canBuy: (()->false) - text: t('questEvilSanta2Text') - notes: t('questEvilSanta2Notes') - completion: t('questEvilSanta2Completion') - value: 4 - previous: 'evilsanta' - category: 'pet' - collect: - tracks: text: t('questEvilSanta2CollectTracks'), count: 20 - branches: text: t('questEvilSanta2CollectBranches'), count: 10 - drop: - items: [ - {type: 'pets', key: 'BearCub-Polar', text: t('questEvilSanta2DropBearCubPolarPet')} - ] - gp: 20 - exp: 100 - - gryphon: - text: t('questGryphonText') - notes: t('questGryphonNotes') - completion: t('questGryphonCompletion') - value: 4 # Gem cost to buy, GP sell-back - category: 'pet' - boss: - name: t('questGryphonBoss') # name of the boss himself (eg, Vice) - hp: 300 - str: 1.5 # Multiplier of users' missed dailies - drop: - items: [ - {type: 'eggs', key: 'Gryphon', text: t('questGryphonDropGryphonEgg')} - {type: 'eggs', key: 'Gryphon', text: t('questGryphonDropGryphonEgg')} - {type: 'eggs', key: 'Gryphon', text: t('questGryphonDropGryphonEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questGryphonUnlockText') - - hedgehog: - text: t('questHedgehogText') - notes: t('questHedgehogNotes') - completion: t('questHedgehogCompletion') - value: 4 # Gem cost to buy, GP sell-back - category: 'pet' - boss: - name: t('questHedgehogBoss') # name of the boss himself (eg, Vice) - hp: 400 - str: 1.25 # Multiplier of users' missed dailies - drop: - items: [ - {type: 'eggs', key: 'Hedgehog', text: t('questHedgehogDropHedgehogEgg')} - {type: 'eggs', key: 'Hedgehog', text: t('questHedgehogDropHedgehogEgg')} - {type: 'eggs', key: 'Hedgehog', text: t('questHedgehogDropHedgehogEgg')} - ] - gp: 30 - exp: 125 - unlock: t('questHedgehogUnlockText') - - ghost_stag: - text: t('questGhostStagText') - notes: t('questGhostStagNotes') - completion: t('questGhostStagCompletion') - value: 4 - category: 'pet' - boss: - name: t('questGhostStagBoss') - hp: 1200 - str: 2.5 - drop: - items: [ - {type: 'eggs', key: 'Deer', text: t('questGhostStagDropDeerEgg')} - {type: 'eggs', key: 'Deer', text: t('questGhostStagDropDeerEgg')} - {type: 'eggs', key: 'Deer', text: t('questGhostStagDropDeerEgg')} - ] - gp: 80 - exp: 800 - unlock: t('questGhostStagUnlockText') - - vice1: - text: t('questVice1Text') - notes: t('questVice1Notes') - value: 4 - lvl: 30 - category: 'unlockable' - boss: - name: t('questVice1Boss') - hp: 750 - str: 1.5 - drop: - items: [ - {type: 'quests', key: "vice2", text: t('questVice1DropVice2Quest')} - ] - gp: 20 - exp: 100 - - vice2: - text: t('questVice2Text') - notes: t('questVice2Notes') - value: 4 - lvl: 30 - category: 'unlockable' - previous: 'vice1' - collect: - lightCrystal: text: t('questVice2CollectLightCrystal'), count: 45 - drop: - items: [ - {type: 'quests', key: 'vice3', text: t('questVice2DropVice3Quest')} - ] - gp: 20 - exp: 75 - - vice3: - text: t('questVice3Text') - notes: t('questVice3Notes') - completion: t('questVice3Completion') - previous: 'vice2' - value: 4 - lvl: 30 - category: 'unlockable' - boss: - name: t('questVice3Boss') - hp: 1500 - str: 3 - drop: - items: [ - {type: 'gear', key: "weapon_special_2", text: t('questVice3DropWeaponSpecial2')} - {type: 'eggs', key: 'Dragon', text: t('questVice3DropDragonEgg')} - {type: 'eggs', key: 'Dragon', text: t('questVice3DropDragonEgg')} - {type: 'hatchingPotions', key: 'Shade', text: t('questVice3DropShadeHatchingPotion')} - {type: 'hatchingPotions', key: 'Shade', text: t('questVice3DropShadeHatchingPotion')} - ] - gp: 100 - exp: 1000 - - egg: - text: t('questEggHuntText') - notes: t('questEggHuntNotes') - completion: t('questEggHuntCompletion') - value: 1 - canBuy: (()->false) - category: 'pet' - collect: - plainEgg: text: t('questEggHuntCollectPlainEgg'), count: 100 - drop: - items: [ - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - {type: 'eggs', key: 'Egg', text: t('questEggHuntDropPlainEgg')} - ] - gp: 0 - exp: 0 - - rat: - text: t('questRatText') - notes: t('questRatNotes') - completion: t('questRatCompletion') - value: 4 - category: 'pet' - boss: - name: t('questRatBoss') - hp: 1200 - str: 2.5 - drop: - items: [ - {type: 'eggs', key: 'Rat', text: t('questRatDropRatEgg')} - {type: 'eggs', key: 'Rat', text: t('questRatDropRatEgg')} - {type: 'eggs', key: 'Rat', text: t('questRatDropRatEgg')} - ] - gp: 80 - exp: 800 - unlock: t('questRatUnlockText') - - octopus: - text: t('questOctopusText') - notes: t('questOctopusNotes') - completion: t('questOctopusCompletion') - value: 4 - category: 'pet' - boss: - name: t('questOctopusBoss') - hp: 1200 - str: 2.5 - drop: - items: [ - {type: 'eggs', key: 'Octopus', text: t('questOctopusDropOctopusEgg')} - {type: 'eggs', key: 'Octopus', text: t('questOctopusDropOctopusEgg')} - {type: 'eggs', key: 'Octopus', text: t('questOctopusDropOctopusEgg')} - ] - gp: 80 - exp: 800 - unlock: t('questOctopusUnlockText') - - dilatory_derby: - text: t('questSeahorseText') - notes: t('questSeahorseNotes') - completion: t('questSeahorseCompletion') - value: 4 - category: 'pet' - boss: - name: t('questSeahorseBoss') - hp: 300 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Seahorse', text: t('questSeahorseDropSeahorseEgg')} - {type: 'eggs', key: 'Seahorse', text: t('questSeahorseDropSeahorseEgg')} - {type: 'eggs', key: 'Seahorse', text: t('questSeahorseDropSeahorseEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questSeahorseUnlockText') - - atom1: - text: t('questAtom1Text') - notes: t('questAtom1Notes') - value: 4 - lvl: 15 - category: 'unlockable' - collect: - soapBars: text: t('questAtom1CollectSoapBars'), count: 20 - drop: - items: [ - {type: 'quests', key: "atom2", text: t('questAtom1Drop')} - ] - gp: 7 - exp: 50 - atom2: - text: t('questAtom2Text') - notes: t('questAtom2Notes') - previous: 'atom1' - value: 4 - lvl: 15 - category: 'unlockable' - boss: - name: t('questAtom2Boss') - hp: 300 - str: 1 - drop: - items: [ - {type: 'quests', key: "atom3", text: t('questAtom2Drop')} - ] - gp: 20 - exp: 100 - atom3: - text: t('questAtom3Text') - notes: t('questAtom3Notes') - previous: 'atom2' - completion: t('questAtom3Completion') - value: 4 - lvl: 15 - category: 'unlockable' - boss: - name: t('questAtom3Boss') - hp: 800 - str: 1.5 - drop: - items: [ - {type: 'gear', key: "head_special_2", text: t('headSpecial2Text')} - {type: 'hatchingPotions', key: "Base", text: t('questAtom3DropPotion')} - {type: 'hatchingPotions', key: "Base", text: t('questAtom3DropPotion')} - ] - gp: 25 - exp: 125 - - harpy: - text: t('questHarpyText') - notes: t('questHarpyNotes') - completion: t('questHarpyCompletion') - value: 4 - category: 'pet' - boss: - name: t('questHarpyBoss') - hp: 600 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Parrot', text: t('questHarpyDropParrotEgg')} - {type: 'eggs', key: 'Parrot', text: t('questHarpyDropParrotEgg')} - {type: 'eggs', key: 'Parrot', text: t('questHarpyDropParrotEgg')} - ] - gp: 43 - exp: 350 - unlock: t('questHarpyUnlockText') - - rooster: - text: t('questRoosterText') - notes: t('questRoosterNotes') - completion: t('questRoosterCompletion') - value: 4 - category: 'pet' - boss: - name: t('questRoosterBoss') - hp: 300 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Rooster', text: t('questRoosterDropRoosterEgg')} - {type: 'eggs', key: 'Rooster', text: t('questRoosterDropRoosterEgg')} - {type: 'eggs', key: 'Rooster', text: t('questRoosterDropRoosterEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questRoosterUnlockText') - - spider: - text: t('questSpiderText') - notes: t('questSpiderNotes') - completion: t('questSpiderCompletion') - value: 4 - category: 'pet' - boss: - name: t('questSpiderBoss') - hp: 400 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Spider', text: t('questSpiderDropSpiderEgg')} - {type: 'eggs', key: 'Spider', text: t('questSpiderDropSpiderEgg')} - {type: 'eggs', key: 'Spider', text: t('questSpiderDropSpiderEgg')} - ] - gp: 31 - exp: 200 - unlock: t('questSpiderUnlockText') - - moonstone1: - text: t('questMoonstone1Text') - notes: t('questMoonstone1Notes') - value: 4 - lvl: 60 - category: 'unlockable' - collect: - moonstone: text: t('questMoonstone1CollectMoonstone'), count: 500 - drop: - items: [ - {type: 'quests', key: "moonstone2", text: t('questMoonstone1DropMoonstone2Quest')} - ] - gp: 50 - exp: 100 - moonstone2: - text: t('questMoonstone2Text') - notes: t('questMoonstone2Notes') - value: 4 - lvl: 60 - previous: 'moonstone1' - category: 'unlockable' - boss: - name: t('questMoonstone2Boss') - hp: 1500 - str: 3 - drop: - items: [ - {type: 'quests', key: 'moonstone3', text: t('questMoonstone2DropMoonstone3Quest')} - ] - gp: 500 - exp: 1000 - moonstone3: - text: t('questMoonstone3Text') - notes: t('questMoonstone3Notes') - completion: t('questMoonstone3Completion') - previous: 'moonstone2' - value: 4 - lvl: 60 - category: 'unlockable' - boss: - name: t('questMoonstone3Boss') - hp: 2000 - str: 3.5 - drop: - items: [ - {type: 'gear', key: "armor_special_2", text: t('armorSpecial2Text')} - {type: 'food', key: 'RottenMeat', text: t('questMoonstone3DropRottenMeat')} - {type: 'food', key: 'RottenMeat', text: t('questMoonstone3DropRottenMeat')} - {type: 'food', key: 'RottenMeat', text: t('questMoonstone3DropRottenMeat')} - {type: 'food', key: 'RottenMeat', text: t('questMoonstone3DropRottenMeat')} - {type: 'food', key: 'RottenMeat', text: t('questMoonstone3DropRottenMeat')} - {type: 'hatchingPotions', key: 'Zombie', text: t('questMoonstone3DropZombiePotion')} - {type: 'hatchingPotions', key: 'Zombie', text: t('questMoonstone3DropZombiePotion')} - {type: 'hatchingPotions', key: 'Zombie', text: t('questMoonstone3DropZombiePotion')} - ] - gp: 900 - exp: 1500 - - goldenknight1: - text: t('questGoldenknight1Text') - notes: t('questGoldenknight1Notes') - value: 4 - lvl: 40 - category: 'unlockable' - collect: - testimony: text: t('questGoldenknight1CollectTestimony'), count: 300 - drop: - items: [ - {type: 'quests', key: "goldenknight2", text: t('questGoldenknight1DropGoldenknight2Quest')} - ] - gp: 15 - exp: 120 - goldenknight2: - text: t('questGoldenknight2Text') - notes: t('questGoldenknight2Notes') - value: 4 - previous: 'goldenknight1' - lvl: 40 - category: 'unlockable' - boss: - name: t('questGoldenknight2Boss') - hp: 1000 - str: 3 - drop: - items: [ - {type: 'quests', key: 'goldenknight3', text: t('questGoldenknight2DropGoldenknight3Quest')} - ] - gp: 75 - exp: 750 - goldenknight3: - text: t('questGoldenknight3Text') - notes: t('questGoldenknight3Notes') - completion: t('questGoldenknight3Completion') - previous: 'goldenknight2' - value: 4 - lvl: 40 - category: 'unlockable' - boss: - name: t('questGoldenknight3Boss') - hp: 1700 - str: 3.5 - drop: - items: [ - {type: 'food', key: 'Honey', text: t('questGoldenknight3DropHoney')} - {type: 'food', key: 'Honey', text: t('questGoldenknight3DropHoney')} - {type: 'food', key: 'Honey', text: t('questGoldenknight3DropHoney')} - {type: 'hatchingPotions', key: 'Golden', text: t('questGoldenknight3DropGoldenPotion')} - {type: 'hatchingPotions', key: 'Golden', text: t('questGoldenknight3DropGoldenPotion')} - {type: 'gear', key: 'shield_special_goldenknight', text: t('questGoldenknight3DropWeapon')} - ] - gp: 900 - exp: 1500 - - basilist: - text: t('questBasilistText') - notes: t('questBasilistNotes') - completion: t('questBasilistCompletion') - value: 4 - category: 'unlockable' - unlockCondition: - condition: 'party invite' - text: t('inviteFriends') - boss: - name: t('questBasilistBoss') - hp: 100 - str: 0.5 - drop: - gp: 8 - exp: 42 - - owl: - text: t('questOwlText') - notes: t('questOwlNotes') - completion: t('questOwlCompletion') - value: 4 - category: 'pet' - boss: - name: t('questOwlBoss') - hp: 500 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Owl', text: t('questOwlDropOwlEgg')} - {type: 'eggs', key: 'Owl', text: t('questOwlDropOwlEgg')} - {type: 'eggs', key: 'Owl', text: t('questOwlDropOwlEgg')} - ] - gp: 37 - exp: 275 - unlock: t('questOwlUnlockText') - - penguin: - text: t('questPenguinText') - notes: t('questPenguinNotes') - completion: t('questPenguinCompletion') - value: 4 - category: 'pet' - boss: - name: t('questPenguinBoss') - hp: 400 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Penguin', text: t('questPenguinDropPenguinEgg')} - {type: 'eggs', key: 'Penguin', text: t('questPenguinDropPenguinEgg')} - {type: 'eggs', key: 'Penguin', text: t('questPenguinDropPenguinEgg')} - ] - gp: 31 - exp: 200 - unlock: t('questPenguinUnlockText') - - trex: - text: t('questTRexText') - notes: t('questTRexNotes') - completion: t('questTRexCompletion') - value: 4 - category: 'pet' - boss: - name: t('questTRexBoss') - hp: 800 - str: 2 - drop: - items: [ - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - ] - gp: 55 - exp: 500 - unlock: t('questTRexUnlockText') - - trex_undead: - text: t('questTRexUndeadText') - notes: t('questTRexUndeadNotes') - completion: t('questTRexUndeadCompletion') - value: 4 - category: 'pet' - boss: - name: t('questTRexUndeadBoss') - hp: 500 - str: 2 - rage: - title: t("questTRexUndeadRageTitle") - description: t("questTRexUndeadRageDescription") - value: 50 - healing: .3 - effect:t('questTRexUndeadRageEffect') - drop: - items: [ - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - {type: 'eggs', key: 'TRex', text: t('questTRexDropTRexEgg')} - ] - gp: 55 - exp: 500 - unlock: t('questTRexUnlockText') - - rock: - text: t('questRockText') - notes: t('questRockNotes') - completion: t('questRockCompletion') - value: 4 - category: 'pet' - boss: - name: t('questRockBoss') - hp: 400 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Rock', text: t('questRockDropRockEgg')} - {type: 'eggs', key: 'Rock', text: t('questRockDropRockEgg')} - {type: 'eggs', key: 'Rock', text: t('questRockDropRockEgg')} - ] - gp: 31 - exp: 200 - unlock: t('questRockUnlockText') - - bunny: - text: t('questBunnyText') - notes: t('questBunnyNotes') - completion: t('questBunnyCompletion') - value: 4 - category: 'pet' - boss: - name: t('questBunnyBoss') - hp: 300 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Bunny', text: t('questBunnyDropBunnyEgg')} - {type: 'eggs', key: 'Bunny', text: t('questBunnyDropBunnyEgg')} - {type: 'eggs', key: 'Bunny', text: t('questBunnyDropBunnyEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questBunnyUnlockText') - - slime: - text: t('questSlimeText') - notes: t('questSlimeNotes') - completion: t('questSlimeCompletion') - value: 4 - category: 'pet' - boss: - name: t('questSlimeBoss') - hp: 400 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Slime', text: t('questSlimeDropSlimeEgg')} - {type: 'eggs', key: 'Slime', text: t('questSlimeDropSlimeEgg')} - {type: 'eggs', key: 'Slime', text: t('questSlimeDropSlimeEgg')} - ] - gp: 31 - exp: 200 - unlock: t('questSlimeUnlockText') - - sheep: - text: t('questSheepText') - notes: t('questSheepNotes') - completion: t('questSheepCompletion') - value: 4 - category: 'pet' - boss: - name: t('questSheepBoss') - hp: 300 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Sheep', text: t('questSheepDropSheepEgg')} - {type: 'eggs', key: 'Sheep', text: t('questSheepDropSheepEgg')} - {type: 'eggs', key: 'Sheep', text: t('questSheepDropSheepEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questSheepUnlockText') - - kraken: - text: t('questKrakenText') - notes: t('questKrakenNotes') - completion: t('questKrakenCompletion') - value: 4 - category: 'pet' - boss: - name: t('questKrakenBoss') - hp: 800 - str: 2 - drop: - items: [ - {type: 'eggs', key: 'Cuttlefish', text: t('questKrakenDropCuttlefishEgg')} - {type: 'eggs', key: 'Cuttlefish', text: t('questKrakenDropCuttlefishEgg')} - {type: 'eggs', key: 'Cuttlefish', text: t('questKrakenDropCuttlefishEgg')} - ] - gp: 55 - exp: 500 - unlock: t('questKrakenUnlockText') - - whale: - text: t('questWhaleText') - notes: t('questWhaleNotes') - completion: t('questWhaleCompletion') - value: 4 - category: 'pet' - boss: - name: t('questWhaleBoss') - hp: 500 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Whale', text: t('questWhaleDropWhaleEgg')} - {type: 'eggs', key: 'Whale', text: t('questWhaleDropWhaleEgg')} - {type: 'eggs', key: 'Whale', text: t('questWhaleDropWhaleEgg')} - ] - gp: 37 - exp: 275 - unlock: t('questWhaleUnlockText') - - dilatoryDistress1: - text: t('questDilatoryDistress1Text') - notes: t('questDilatoryDistress1Notes') - completion: t('questDilatoryDistress1Completion') - value: 4 - goldValue: 200 - category: 'gold' - collect: - fireCoral: text: t('questDilatoryDistress1CollectFireCoral'), count: 25 - blueFins: text: t('questDilatoryDistress1CollectBlueFins'), count: 25 - drop: - items: [ - {type: 'gear', key: "armor_special_finnedOceanicArmor", text: t('questDilatoryDistress1DropArmor')} - ] - gp: 0 - exp: 75 - dilatoryDistress2: - text: t('questDilatoryDistress2Text') - notes: t('questDilatoryDistress2Notes') - completion: t('questDilatoryDistress2Completion') - previous: 'dilatoryDistress1' - value: 4 - goldValue: 300 - category: 'gold' - boss: - name: t('questDilatoryDistress2Boss') - hp: 500 - rage: - title: t("questDilatoryDistress2RageTitle") - description: t("questDilatoryDistress2RageDescription") - value: 50 - healing: .3 - effect:t('questDilatoryDistress2RageEffect') - drop: - items: [ - {type: 'hatchingPotions', key: 'Skeleton', text: t('questDilatoryDistress2DropSkeletonPotion')} - {type: 'hatchingPotions', key: 'CottonCandyBlue', text: t('questDilatoryDistress2DropCottonCandyBluePotion')} - {type: 'gear', key: "head_special_fireCoralCirclet", text: t('questDilatoryDistress2DropHeadgear')} - ] - gp: 0 - exp: 500 - dilatoryDistress3: - text: t('questDilatoryDistress3Text') - notes: t('questDilatoryDistress3Notes') - completion: t('questDilatoryDistress3Completion') - previous: 'dilatoryDistress2' - value: 4 - goldValue: 400 - category: 'gold' - boss: - name: t('questDilatoryDistress3Boss') - hp: 1000 - str: 2 - drop: - items: [ - {type: 'food', key: 'Fish', text: t('questDilatoryDistress3DropFish')} - {type: 'food', key: 'Fish', text: t('questDilatoryDistress3DropFish')} - {type: 'food', key: 'Fish', text: t('questDilatoryDistress3DropFish')} - {type: 'gear', key: "weapon_special_tridentOfCrashingTides", text: t('questDilatoryDistress3DropWeapon')} - {type: 'gear', key: "shield_special_moonpearlShield", text: t('questDilatoryDistress3DropShield')} - ] - gp: 0 - exp: 650 - - cheetah: - text: t('questCheetahText') - notes: t('questCheetahNotes') - completion: t('questCheetahCompletion') - value: 4 - category: 'pet' - boss: - name: t('questCheetahBoss') - hp: 600 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Cheetah', text: t('questCheetahDropCheetahEgg')} - {type: 'eggs', key: 'Cheetah', text: t('questCheetahDropCheetahEgg')} - {type: 'eggs', key: 'Cheetah', text: t('questCheetahDropCheetahEgg')} - ] - gp: 43 - exp: 350 - unlock: t('questCheetahUnlockText') - - horse: - text: t('questHorseText') - notes: t('questHorseNotes') - completion: t('questHorseCompletion') - value: 4 - category: 'pet' - boss: - name: t('questHorseBoss') - hp: 500 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Horse', text: t('questHorseDropHorseEgg')} - {type: 'eggs', key: 'Horse', text: t('questHorseDropHorseEgg')} - {type: 'eggs', key: 'Horse', text: t('questHorseDropHorseEgg')} - ] - gp: 37 - exp: 275 - unlock: t('questHorseUnlockText') - - frog: - text: t('questFrogText') - notes: t('questFrogNotes') - completion: t('questFrogCompletion') - value: 4 - category: 'pet' - boss: - name: t('questFrogBoss') - hp: 300 - str: 1.5 - drop: - items: [ - {type: 'eggs', key: 'Frog', text: t('questFrogDropFrogEgg')} - {type: 'eggs', key: 'Frog', text: t('questFrogDropFrogEgg')} - {type: 'eggs', key: 'Frog', text: t('questFrogDropFrogEgg')} - ] - gp: 25 - exp: 125 - unlock: t('questFrogUnlockText') - - snake: - text: t('questSnakeText') - notes: t('questSnakeNotes') - completion: t('questSnakeCompletion') - value: 4 - category: 'pet' - boss: - name: t('questSnakeBoss') - hp: 1100 - str: 2.5 - drop: - items: [ - {type: 'eggs', key: 'Snake', text: t('questSnakeDropSnakeEgg')} - {type: 'eggs', key: 'Snake', text: t('questSnakeDropSnakeEgg')} - {type: 'eggs', key: 'Snake', text: t('questSnakeDropSnakeEgg')} - ] - gp: 73 - exp: 725 - unlock: t('questSnakeUnlockText') - -_.each api.quests, (v,key) -> - _.defaults v, {key,canBuy:(()->true)} - b = v.boss - if b - _.defaults b, {str:1,def:1} - if b.rage - _.defaults b.rage, {title:t('bossRageTitle'),description:t('bossRageDescription')} - -api.questsByLevel = - _.sortBy api.quests, (quest) -> - quest.lvl || 0 - -api.backgrounds = - backgrounds062014: - beach: - text: t('backgroundBeachText') - notes: t('backgroundBeachNotes') - fairy_ring: - text: t('backgroundFairyRingText') - notes: t('backgroundFairyRingNotes') - forest: - text: t('backgroundForestText') - notes: t('backgroundForestNotes') - backgrounds072014: - open_waters: - text: t('backgroundOpenWatersText') - notes: t('backgroundOpenWatersNotes') - coral_reef: - text: t('backgroundCoralReefText') - notes: t('backgroundCoralReefNotes') - seafarer_ship: - text: t('backgroundSeafarerShipText') - notes: t('backgroundSeafarerShipNotes') - backgrounds082014: - volcano: - text: t('backgroundVolcanoText') - notes: t('backgroundVolcanoNotes') - clouds: - text: t('backgroundCloudsText') - notes: t('backgroundCloudsNotes') - dusty_canyons: - text: t('backgroundDustyCanyonsText') - notes: t('backgroundDustyCanyonsNotes') - backgrounds092014: - thunderstorm: - text: t('backgroundThunderstormText') - notes: t('backgroundThunderstormNotes') - autumn_forest: - text: t('backgroundAutumnForestText') - notes: t('backgroundAutumnForestNotes') - harvest_fields: - text: t('backgroundHarvestFieldsText') - notes: t('backgroundHarvestFieldsNotes') - backgrounds102014: - graveyard: - text: t('backgroundGraveyardText') - notes: t('backgroundGraveyardNotes') - haunted_house: - text: t('backgroundHauntedHouseText') - notes: t('backgroundHauntedHouseNotes') - pumpkin_patch: - text: t('backgroundPumpkinPatchText') - notes: t('backgroundPumpkinPatchNotes') - backgrounds112014: - harvest_feast: - text: t('backgroundHarvestFeastText') - notes: t('backgroundHarvestFeastNotes') - sunset_meadow: - text: t('backgroundSunsetMeadowText') - notes: t('backgroundSunsetMeadowNotes') - starry_skies: - text: t('backgroundStarrySkiesText') - notes: t('backgroundStarrySkiesNotes') - backgrounds122014: - iceberg: - text: t('backgroundIcebergText') - notes: t('backgroundIcebergNotes') - twinkly_lights: - text: t('backgroundTwinklyLightsText') - notes: t('backgroundTwinklyLightsNotes') - south_pole: - text: t('backgroundSouthPoleText') - notes: t('backgroundSouthPoleNotes') - backgrounds012015: - ice_cave: - text: t('backgroundIceCaveText') - notes: t('backgroundIceCaveNotes') - frigid_peak: - text: t('backgroundFrigidPeakText') - notes: t('backgroundFrigidPeakNotes') - snowy_pines: - text: t('backgroundSnowyPinesText') - notes: t('backgroundSnowyPinesNotes') - backgrounds022015: - blacksmithy: - text: t('backgroundBlacksmithyText') - notes: t('backgroundBlacksmithyNotes') - crystal_cave: - text: t('backgroundCrystalCaveText') - notes: t('backgroundCrystalCaveNotes') - distant_castle: - text: t('backgroundDistantCastleText') - notes: t('backgroundDistantCastleNotes') - backgrounds032015: - spring_rain: - text: t('backgroundSpringRainText') - notes: t('backgroundSpringRainNotes') - stained_glass: - text: t('backgroundStainedGlassText') - notes: t('backgroundStainedGlassNotes') - rolling_hills: - text: t('backgroundRollingHillsText') - notes: t('backgroundRollingHillsNotes') - backgrounds042015: - cherry_trees: - text: t('backgroundCherryTreesText') - notes: t('backgroundCherryTreesNotes') - floral_meadow: - text: t('backgroundFloralMeadowText') - notes: t('backgroundFloralMeadowNotes') - gumdrop_land: - text: t('backgroundGumdropLandText') - notes: t('backgroundGumdropLandNotes') - backgrounds052015: - marble_temple: - text: t('backgroundMarbleTempleText') - notes: t('backgroundMarbleTempleNotes') - mountain_lake: - text: t('backgroundMountainLakeText') - notes: t('backgroundMountainLakeNotes') - pagodas: - text: t('backgroundPagodasText') - notes: t('backgroundPagodasNotes') - backgrounds062015: - drifting_raft: - text: t('backgroundDriftingRaftText') - notes: t('backgroundDriftingRaftNotes') - shimmery_bubbles: - text: t('backgroundShimmeryBubblesText') - notes: t('backgroundShimmeryBubblesNotes') - island_waterfalls: - text: t('backgroundIslandWaterfallsText') - notes: t('backgroundIslandWaterfallsNotes') - backgrounds072015: - dilatory_ruins: - text: t('backgroundDilatoryRuinsText') - notes: t('backgroundDilatoryRuinsNotes') - giant_wave: - text: t('backgroundGiantWaveText') - notes: t('backgroundGiantWaveNotes') - sunken_ship: - text: t('backgroundSunkenShipText') - notes: t('backgroundSunkenShipNotes') - backgrounds082015: - pyramids: - text: t('backgroundPyramidsText') - notes: t('backgroundPyramidsNotes') - sunset_savannah: - text: t('backgroundSunsetSavannahText') - notes: t('backgroundSunsetSavannahNotes') - twinkly_party_lights: - text: t('backgroundTwinklyPartyLightsText') - notes: t('backgroundTwinklyPartyLightsNotes') - backgrounds092015: - market: - text: t('backgroundMarketText') - notes: t('backgroundMarketNotes') - stable: - text: t('backgroundStableText') - notes: t('backgroundStableNotes') - tavern: - text: t('backgroundTavernText') - notes: t('backgroundTavernNotes') - backgrounds102015: - harvest_moon: - text: t('backgroundHarvestMoonText') - notes: t('backgroundHarvestMoonNotes') - slimy_swamp: - text: t('backgroundSlimySwampText') - notes: t('backgroundSlimySwampNotes') - swarming_darkness: - text: t('backgroundSwarmingDarknessText') - notes: t('backgroundSwarmingDarknessNotes') - backgrounds112015: - floating_islands: - text: t('backgroundFloatingIslandsText') - notes: t('backgroundFloatingIslandsNotes') - night_dunes: - text: t('backgroundNightDunesText') - notes: t('backgroundNightDunesNotes') - sunset_oasis: - text: t('backgroundSunsetOasisText') - notes: t('backgroundSunsetOasisNotes') - -api.subscriptionBlocks = - basic_earned: months:1, price:5 - basic_3mo: months:3, price:15 - basic_6mo: months:6, price:30 - google_6mo: months:6, price:24, discount:true, original:30 - basic_12mo: months:12, price:48 -_.each api.subscriptionBlocks, (b,k)->b.key = k - -# repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true} -api.userDefaults = - habits: [ - {type: 'habit', text: t('defaultHabit1Text'), value: 0, up: true, down: false, attribute: 'per' } - {type: 'habit', text: t('defaultHabit2Text'), value: 0, up: false, down: true, attribute: 'str'} - {type: 'habit', text: t('defaultHabit3Text'), value: 0, up: true, down: true, attribute: 'str'} - ] - - dailys: [ - ] - - todos: [ - {type: 'todo', text: t('defaultTodo1Text'), notes: t('defaultTodoNotes'), completed: false, attribute: 'int' } - ] - - rewards: [ - {type: 'reward', text: t('defaultReward1Text'), value: 10 } - ] - - tags: [ - {name: t('defaultTag1')} - {name: t('defaultTag2')} - {name: t('defaultTag3')} - ] - -api.faq = require './faq.js' From f7c88478bccbf935639c694466f13a03d253e191 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 15 Nov 2015 10:48:46 -0600 Subject: [PATCH 20/20] Explicitly bump ampitude version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 694f18c522..dc78f2970c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./website/src/server.js", "dependencies": { "amazon-payments": "0.0.4", - "amplitude": "^2.0.1", + "amplitude": "^2.0.3", "async": "~0.9.0", "aws-sdk": "^2.0.25", "babel-core": "^5.8.34",