Compare commits

..

88 Commits

Author SHA1 Message Date
Sabe Jones 552cf70abd 4.33.1 2018-03-24 02:51:01 +00:00
Sabe Jones 01c8ef9382 chore(i18n): update locales 2018-03-24 02:48:23 +00:00
Matteo Pagliazzi fa17ab7c17 avoid errors when trying to call createTasks with no tasks (#10170) 2018-03-23 17:15:50 +01:00
Keith Holliday 99852fcd89 Stringified mem report (#10167)
* Stringified mem report

* Passed info
2018-03-23 10:19:24 -05:00
Sabe Jones ddec458364 4.33.0 2018-03-22 19:58:55 +00:00
Sabe Jones bfe74a8dcb chore(i18n): update locales 2018-03-22 19:57:57 +00:00
SabreCat 13d9da404d chore(sprites): compile 2018-03-22 19:51:21 +00:00
SabreCat 85e0af0c0e feat(content): Mystery Items 2018/03 2018-03-22 19:50:47 +00:00
SabreCat a2e5548b1c Merge branch 'develop' into release 2018-03-22 18:53:40 +00:00
negue 36dabad2c9 fix seasonal shop (#10164) 2018-03-22 13:50:58 -05:00
Keith Holliday 25b9e6f330 Added memwatch for memory leak detection (#10166) 2018-03-22 12:44:45 -05:00
Keith Holliday d0a786554c Removed balance check test (#10159)
* Removed balance check test

* Removed balance check in common

* Removed gem logic and added achievement to tests
2018-03-21 11:53:47 -05:00
Alys de79e0e3c3 add swear word - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2018-03-21 19:19:13 +10:00
Sabe Jones 0e74d25ede Merge branch 'release' into develop 2018-03-20 23:12:23 +00:00
Sabe Jones bde8b76da7 4.32.1 2018-03-20 23:11:49 +00:00
Sabe Jones 4235be4b43 chore(i18n): update locales 2018-03-20 23:11:37 +00:00
Sabe Jones 3af7f89d10 Merge branch 'release' into develop 2018-03-20 22:57:52 +00:00
Sabe Jones 396362d27e 4.32.0 2018-03-20 22:57:28 +00:00
SabreCat 972efb1878 fix(seasonal-shop): remove quest
it was breaking the API. Wut? Just use canBuy logic and Quest Shop for now
2018-03-20 22:49:54 +00:00
SabreCat 6c18d19d95 fix(test): extra curly 2018-03-20 21:56:55 +00:00
SabreCat 9b8bdb90d8 chore(sprites): compile 2018-03-20 21:31:04 +00:00
SabreCat a84ea8b1b7 feat(event): Spring Fling 2018 2018-03-20 21:30:43 +00:00
Matteo Pagliazzi 480a839bc5 update package-lock 2018-03-18 16:36:36 +01:00
greenkeeper[bot] df9c54fe20 chore(package): update http-proxy-middleware to version 0.18.0 (#10131) 2018-03-18 16:33:33 +01:00
greenkeeper[bot] 8dbab1a976 chore(package): update sinon-chai to version 3.0.0 (#10096) 2018-03-18 16:33:19 +01:00
greenkeeper[bot] df6088bc6d fix(package): update url-loader to version 1.0.0 (#10088) 2018-03-18 16:31:03 +01:00
greenkeeper[bot] f0ff3a4eb6 fix(package): update html-webpack-plugin to version 3.0.0 (#10070) 2018-03-18 16:29:25 +01:00
greenkeeper[bot] 786e4419da chore(package): update eslint-loader to version 2.0.0 (#10056) 2018-03-18 16:28:20 +01:00
Chris Wang fff4ea3ad3 Added popover stats to costume equipment on profile stats - fixes #9280 (#10136)
* Added popover stats to costume equipment on the stats page of a profile (#9280)

* Changed popover position to bottom for equip and costume items.

* Fixed indent on line 160

* Changed escaped double quotes to single quotes for readability
2018-03-18 16:24:41 +01:00
kartik adur e18e89bc10 Task page - task filters v2 (#10053)
* update column.vue, getters/task.js, getters/user.js and add unittests and helpers

* add vue-test-utils pkg + unit test for column.vue

* add unit test column.vue

* update unit tests

* fix linting errors
2018-03-18 16:23:58 +01:00
Matteo Pagliazzi 68ea28305d fix linting issues 2018-03-17 22:36:17 +01:00
Travis 242b3508a1 Update Staff list in the tavern to make staff names clickable to pull up staff profiles. (#10107)
* Update Staff list in the tavern to make staff names clickable to pull up staff profiles.

fixes #9290

* Addressing PR comments.
2018-03-17 22:29:52 +01:00
Travis 8c316d939f Updated get challenges api to return challenges sorted by which challenges include the habitica_official category (#10079)
and removed sorting on the official flag of the challenges object.

fixes #9955
2018-03-17 22:26:24 +01:00
Travis 45eb19e992 Update the API to prevent the user from leaving a group if they are the only member and have a quest active. (#10091)
* Update the API to prevent the user from leaving a group if they are the only member and have a quest active.

fixes #10068

* fixing api doc
2018-03-17 22:26:07 +01:00
Travis 3ad0ffcaec Escaping regex characters from user input before searching for groups. (#10092)
fixes #9953
2018-03-17 22:25:50 +01:00
Travis fef8929dd9 Fixes start date updating the repeat on day for dailies repeated monthly (#10095)
fixes # 9200
2018-03-17 22:25:34 +01:00
Travis 911f894750 Fixing edit profile page so blurb can be editted after reload. (#10097)
fixes #10032
2018-03-17 22:25:17 +01:00
Travis d4e2f5ac9e fix: Fixing xml data export 500 error. (#10114)
* fix: Fixing xml data export 500 error.

fixes #10100

* Removing outdated comment.

* Making xml data export test pass consistently.
2018-03-17 22:24:40 +01:00
Mahendran Nadesan e43749bed1 Stats over 3 digits overlap (fixes #10033) (#10133)
* Formatted stats

* small changes

* Fixed issues as per comments
2018-03-17 22:23:55 +01:00
Sarah Boo 8be29e27d0 Hide "Delete Completed" To-Dos button on Challenge or Group Task Board - fixes #9935 (#10128)
* only show delete to-dos for user's task page

* remove stray spaces
2018-03-17 22:22:12 +01:00
Mark Kuba 301668fe22 fix: you are already in group message - fixes #10119 (#10122)
* add youAreAlreadyInGroup message

* add test for youAreAlreadyInGroup message

* update youAreAlreadyInGroup message
2018-03-17 22:21:16 +01:00
Gene Vityugov 767f3ebe12 Add space in profile modal to make it consistent with other text (#10103) 2018-03-17 22:19:58 +01:00
EthanEFung 25e6f8e26e Add Completion Text to Older Quests #10066 (#10081) 2018-03-17 22:18:54 +01:00
Dmitry Torba 1c6608d071 fix flower avatar in chat #10015 (#10040) 2018-03-17 22:17:47 +01:00
Toivo Mattila d2b160438c Added margin to buttons on Settings-page (#10034)
* Added margin to buttons on Settings-page

* Added bottom margin for 'Enable Class System' button
2018-03-17 22:16:55 +01:00
Александр 37d70f089c Removes video from presskit.zip (#10013)
* presskit without video

* Boss/ update pics

* Logo/ change broken pics

* updated broken files

* Delete Level Up.PNG

* Add files via upload

* update outdated icons

* update party

* upload updated presskit
2018-03-17 22:15:40 +01:00
Julius Jung 04b4912d59 Fix password reset when querying for emails with upcase characters (fixes #9059) (#9707)
* downcase updating an email to be consistent with creating

* add tests to ensure downcase of email for create/update

* create migration to downcase existing User objects

* delete 'only'

* change gmail to example

* add trailing comma from lint error

* search for emails with at least one capital letter

* fix query in order to search for any email with at least one capital letter

* batch process effected users with at least one capital in email

* update script for batch process effected users
2018-03-17 22:13:54 +01:00
Zack Sunderland b9a6d9ceec Allow Un-subscribed Users to Claim Mystery Items (#9270)
Remove restriction on mystery/special inventory items that prevents
non-subscribed users from seeing the items. Organize the imports on the
page.

Resolves: Issue #9228
2018-03-17 22:13:31 +01:00
negue 2a97915477 Purchase API Refactoring: Market Gear (#10010)
* convert buyGear to buyMarketGearOperation + tests

* move NotImplementedError
2018-03-17 21:56:19 +01:00
Alys 29a9deaeb8 prevent lint indent warning on multi-line reduce function (#10145) 2018-03-17 21:13:55 +01:00
negue 0fcc1f2080 correct position of pets in the profile overview - fixes #10104 (#10144) 2018-03-17 21:13:35 +01:00
negue 9287098e70 fix #10116 (#10143) 2018-03-17 21:13:12 +01:00
negue 1812f63812 raise coverage for user api calls (#10099)
* user integration tests

* fix lint
2018-03-17 21:12:53 +01:00
negue 3d4107db3e checkout of the Inn - banner (#9835)
* show resting banner

* resume damage

* replace colors by variables

* remove indentation
2018-03-17 21:12:25 +01:00
Sabe Jones 67e750a81c 4.31.1 2018-03-15 21:19:44 +00:00
Keith Holliday 7fe62a731b Fixed gem amount on master key (#10140)
* Fixed gem amount on master key

* fix(news): update Bailey text
2018-03-15 16:19:11 -05:00
Sabe Jones 3c224fe353 Merge branch 'release' into develop 2018-03-15 19:10:56 +00:00
Sabe Jones bb6f465ac8 4.31.0 2018-03-15 19:10:37 +00:00
Sabe Jones a2a79e4607 chore(i18n): update locales 2018-03-15 19:07:38 +00:00
SabreCat 5125cc5f59 chore(news): Bailey 2018-03-15 19:04:00 +00:00
Matteo Pagliazzi cb42a31c43 Node 8 (WIP) (#9946)
* start upgrade to node 8

* upgrade travis

* improve travis

* Remove bluebird, babel (except for modules) from server (WIP) (#9947)

* remove bluebird, babel from server (except for modules)

* fixes

* fix path

* fix path

* fix export

* fix export

* fix test

* fix tests

* remove plugin for transform-object-rest-spread since it is supported in node8

* babel: correct syntax rest spread

* remove bluebird

* update migrations archive readme

* fix package-lock.json

* fix typo

* add package-loc
2018-03-15 19:59:36 +01:00
Alys b1b1e512f5 adjust word order in mod slack flag message (#10137) 2018-03-15 19:27:59 +01:00
Alys 5ba09c45df adjust comments and word lists in slurs and banned words. TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2018-03-15 20:53:48 +10:00
Matteo Pagliazzi 70dec611e3 Make Kafka dep optional (#10129)
* Made kafka optional

* make kafka optional
2018-03-14 18:21:39 +01:00
Keith Holliday 2195464772 Flag comment (#9991)
* Added initial chat flag comment form

* Added user comment to slack message

* Updated copy and styles

* Fixed comma

* Updated admin messaging
2018-03-14 07:41:13 -05:00
Sabe Jones b662f8bdff Merge branch 'release' into develop 2018-03-13 21:00:00 +00:00
Sabe Jones 0e6d6336c6 4.30.0 2018-03-13 20:59:13 +00:00
Sabe Jones d8078adacd chore(i18n): update locales 2018-03-13 20:58:43 +00:00
SabreCat a88800df78 chore(sprites): compile 2018-03-13 20:54:54 +00:00
SabreCat 42e0095bbd feat(content): spring Hatching Potions 2018-03-13 20:54:27 +00:00
Sabe Jones ee1aa653f0 fix(groups): Guild badge fencepost errors 2018-03-12 14:43:39 -05:00
SabreCat 8d1b1ff794 chore(npm): update package lock 2018-03-09 21:22:25 +00:00
SabreCat 70500d7c98 Merge branch 'release' into develop 2018-03-09 21:18:56 +00:00
Travis 35849ebdd7 Fixing items sort by number in the market. (#10108)
fixes #10093
2018-03-09 15:17:26 -06:00
Matteo Pagliazzi 1332fd68b0 Mongoose 5 (#9870)
* mongoose 5: remove unused autoinc, remove promise option (it uses native promises now)

* remove mongodb package

* remove mongoskin

* migrate migration away from mongoskin

* fix mongoose hooks

* fix _updateUserWithRetries

* try without next

* remove init

* update sinon

* fix some integration tests

* fix remaining tests

* fix error message

* fix error message

* fix error message

* another fix

* fix mongoose options

* remove greenkeeper exception
2018-03-09 15:13:58 -06:00
Travis f9a47b1420 Update user pinned items on purchases. (#10085)
fixes #10049
2018-03-09 15:11:42 -06:00
Dexx Mandele 07b3824c4c Stop market background from covering sign (#10067) 2018-03-09 14:59:45 -06:00
Travis 92fa6805eb fix: Quest collected items displayed multiple times. (#10111)
fixes #10065
2018-03-09 14:56:59 -06:00
Dexx Mandele f64b34318f Set warrior as default for the choose class pop-up (#10084) 2018-03-09 14:40:53 -06:00
Sabe Jones fe61c0f29e Merge branch 'release' into develop 2018-03-08 21:43:35 +00:00
Sabe Jones 5f0b957dc2 fix(logging): only start Stackimpact in prod (#10112) 2018-03-08 13:12:45 -06:00
Sabe Jones 37650ca674 Merge branch 'release' into develop 2018-03-08 00:30:37 +00:00
Sabe Jones b4dab2e13c Merge branch 'release' into develop 2018-03-07 22:53:09 +00:00
Sabe Jones b827b17481 Don't include seasonal class gear in classless category (#10047)
* fix(market): don't include seasonal class gear in classless category

* refactor(shops): use standard indexOf check
2018-03-06 11:03:49 -06:00
Sabe Jones 27ef187e66 Merge branch 'release' into develop 2018-03-05 20:59:17 +00:00
Keith Holliday 9c9b67aa9d Keys kennel fixes (#9848)
* Show keys to pets immediately

* Ensured keys to pets dissapear after use

* Added resdirect to stable after purchase

* Added mount check and updated keys to mounts and to both

* Added api calls

* Added check for beastmaster progress

* Added mount check for release mounts. Added pets and mount check to release both

* Added actions

* Added catch to common tests

* Added beast count and reload

* Removed extra console log
2018-03-02 15:30:11 -06:00
Keith Holliday 7cff331800 Removed uri for client side oauth (#10058)
* Removed uri for client side oauth

* Fixed lint
2018-03-02 14:07:15 -07:00
Keith Holliday e7bc505b88 Modal popup fixes (#10080)
* Added validation for modal stack

* Lint fixes
2018-03-02 12:43:22 -07:00
644 changed files with 19487 additions and 15076 deletions
+2 -6
View File
@@ -1,10 +1,6 @@
{
"presets": ["es2015"],
"plugins": [
"transform-object-rest-spread",
["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
}]
"transform-es2015-modules-commonjs",
"syntax-object-rest-spread",
]
}
+1
View File
@@ -4,6 +4,7 @@ node_modules/**
.bower-registry/**
website/client-old/**
website/client/**
website/client/store/**
website/views/**
website/build/**
dist/**
+1 -1
View File
@@ -1 +1 @@
6
8
+1 -3
View File
@@ -1,6 +1,6 @@
language: node_js
node_js:
- '6'
- '8'
services:
- mongodb
cache:
@@ -8,8 +8,6 @@ cache:
- 'node_modules'
addons:
chrome: stable
before_install:
- npm install -g npm@5
before_script:
- npm run test:build
- cp config.json.example config.json
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:boron
FROM node:8
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:boron
FROM node:8
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
@@ -20,7 +20,7 @@ RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.29.7 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.32.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force
+2 -1
View File
@@ -112,5 +112,6 @@
"CLOUDKARAFKA_USERNAME": "",
"CLOUDKARAFKA_PASSWORD": "",
"CLOUDKARAFKA_TOPIC_PREFIX": ""
}
},
"STACK_IMPACT_KEY": "aaaabbbbccccddddeeeeffffgggg111100002222"
}
-1
View File
@@ -26,7 +26,6 @@ let improveRepl = (context) => {
const mongooseOptions = !isProd ? {} : {
keepAlive: 1,
connectTimeoutMS: 30000,
useMongoClient: true,
};
mongoose.connect(
nconf.get('NODE_DB_URI'),
+1 -2
View File
@@ -2,7 +2,6 @@ import { exec } from 'child_process';
import psTree from 'ps-tree';
import nconf from 'nconf';
import net from 'net';
import Bluebird from 'bluebird';
import { post } from 'superagent';
import { sync as glob } from 'glob';
import Mocha from 'mocha';
@@ -45,7 +44,7 @@ export function kill (proc) {
* before failing.
*/
export function awaitPort (port, max = 60) {
return new Bluebird((rej, res) => {
return new Promise((rej, res) => {
let socket;
let timeout;
let interval;
@@ -16,7 +16,6 @@
const authorName = 'Blade';
const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b';
global.Promise = require('bluebird');
const MongoClient = require('mongodb').MongoClient;
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
@@ -11,7 +11,6 @@
* pm'ed each user asking if they would like their tasks reset to the previous day
***************************************/
global.Promise = require('bluebird');
const MongoClient = require('mongodb').MongoClient;
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
@@ -12,7 +12,6 @@
* from an object to a number, hence this migration.
***************************************/
global.Promise = require('bluebird');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
const Timer = require('./utils/timer');
@@ -9,7 +9,6 @@
* and transfers a group's progress to it
***************************************/
global.Promise = require('bluebird');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
const Timer = require('./utils/timer');
@@ -12,7 +12,6 @@
* <userid>@example.com
***************************************/
global.Promise = require('bluebird');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
const Timer = require('./utils/timer');
@@ -9,7 +9,6 @@
* they support a type and options and label
* ***************************************/
global.Promise = require('bluebird');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
const Timer = require('./utils/timer');
@@ -12,7 +12,6 @@
* message into the chat for affected parties.
***************************************/
global.Promise = require('bluebird');
const uuid = require('uuid');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
@@ -0,0 +1,88 @@
var migrationName = '20171211_sanitize_emails.js';
var authorName = 'Julius'; // in case script author needs to know when their ...
var authorUuid = 'dd16c270-1d6d-44bd-b4f9-737342e79be6'; //... own data is done
/*
User creation saves email as lowercase, but updating an email did not.
Run this script to ensure all lowercased emails in db AFTER fix for updating emails is implemented.
This will fix inconsistent querying for an email when attempting to password reset.
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
var query = {
'auth.local.email': /[A-Z]/
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data)
'auth.local.email'
],
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var push;
var set = {
'auth.local.email': user.auth.local.email.toLowerCase()
};
dbUsers.update({_id: user._id}, {$set: set});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
+3 -1
View File
@@ -1,4 +1,6 @@
If you need to use a migration from this folder, move it to /migrations.
Note that /migrations files (excluding /archive) are linted, so to pass test you'll have to make sure
that the file is written correctly.
that the file is written correctly.
They might also be using some old deps that we don't use anymore like Bluebird, mongoskin, ...
+2 -4
View File
@@ -1,5 +1,3 @@
import Bluebird from 'Bluebird';
import { model as Challenges } from '../../website/server/models/challenge';
import { model as User } from '../../website/server/models/user';
@@ -17,10 +15,10 @@ async function syncChallengeToMembers (challenges) {
promises.push(user.save());
});
return Bluebird.all(promises);
return Promise.all(promises);
});
return await Bluebird.all(challengSyncPromises);
return await Promise.all(challengSyncPromises);
}
async function syncChallenges (lastChallengeDate) {
+1 -3
View File
@@ -1,5 +1,3 @@
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import { model as User } from '../../website/server/models/user';
@@ -16,7 +14,7 @@ async function createGroup (name, privacy, type, leaderId) {
group.leader = user._id;
user.guilds.push(group._id);
return Bluebird.all([group.save(), user.save()]);
return Promise.all([group.save(), user.save()]);
}
module.exports = async function groupCreator () {
+1 -2
View File
@@ -3,7 +3,6 @@
/*
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them
*/
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import { model as User } from '../../website/server/models/user';
@@ -38,7 +37,7 @@ async function handOutJackalopes () {
cursor.on('close', async () => {
console.log('done');
return await Bluebird.all(promises);
return await Promise.all(promises);
});
}
@@ -9,8 +9,6 @@ let authorUuid = ''; // ... own data is done
* subscription to all members
*/
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import * as payments from '../../website/server/libs/payments';
@@ -28,7 +26,7 @@ async function updateGroupsWithGroupPlans () {
});
cursor.on('close', async () => {
return await Bluebird.all(promises);
return await Promise.all(promises);
});
}
+2 -2
View File
@@ -1,14 +1,14 @@
require('babel-register');
require('babel-polyfill');
// This file must use ES5, everything required can be in ES6
function setUpServer () {
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
const Bluebird = require('bluebird'); // eslint-disable-line global-require, no-unused-vars
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
setupNconf();
// We require src/server and npt src/index because
// 1. nconf is already setup
// 2. we don't need clustering
+1 -2
View File
@@ -1,4 +1,3 @@
let Bluebird = require('bluebird');
let request = require('superagent');
let last = require('lodash/last');
let AWS = require('aws-sdk');
@@ -74,7 +73,7 @@ function uploadToS3 (start, end, filesUrls) {
});
console.log(promises.length);
return Bluebird.all(promises)
return Promise.all(promises)
.then(() => {
currentIndex += 50;
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
+1 -1
View File
@@ -5,7 +5,7 @@ const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is do
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['armor_mystery_201802', 'head_mystery_201802', 'shield_mystery_201802'];
const MYSTERY_ITEMS = ['back_mystery_201803', 'head_mystery_201803'];
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let monk = require('monk');
+4154 -3611
View File
File diff suppressed because it is too large Load Diff
+53 -54
View File
@@ -1,29 +1,24 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.29.8",
"version": "4.33.1",
"main": "./website/server/index.js",
"greenkeeper": {
"ignore": [
"mongoose"
]
},
"dependencies": {
"@slack/client": "^3.8.1",
"accepts": "^1.3.2",
"accepts": "^1.3.5",
"amazon-payments": "^0.2.6",
"amplitude": "^3.5.0",
"apidoc": "^0.17.5",
"autoprefixer": "^8.0.0",
"aws-sdk": "^2.200.0",
"autoprefixer": "^8.1.0",
"aws-sdk": "^2.211.0",
"axios": "^0.18.0",
"axios-progress-bar": "^1.1.8",
"babel-core": "^6.0.0",
"babel-eslint": "^8.2.2",
"babel-loader": "^7.1.2",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-async-to-module-method": "^6.8.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1",
@@ -31,24 +26,23 @@
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "^4.0.0",
"bootstrap-vue": "^2.0.0-rc.1",
"bootstrap-vue": "^2.0.0-rc.2",
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
"cross-env": "^5.1.3",
"css-loader": "^0.28.0",
"cross-env": "^5.1.4",
"css-loader": "^0.28.11",
"csv-stringify": "^2.0.4",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.2",
"express": "^4.16.3",
"express-basic-auth": "^1.1.4",
"express-validator": "^5.0.1",
"express-validator": "^5.0.3",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
"got": "^8.2.0",
"got": "^8.3.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
@@ -56,24 +50,24 @@
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0",
"hellojs": "^1.15.1",
"html-webpack-plugin": "^2.8.1",
"html-webpack-plugin": "^3.0.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.1.6",
"in-app-purchase": "^1.8.9",
"intro.js": "^2.6.0",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"lodash": "^4.17.4",
"memwatch-next": "^0.3.0",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.13.0",
"moment": "^2.21.0",
"moment-recur": "^1.0.7",
"mongoose": "^4.13.11",
"mongoose": "^5.0.10",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^0.14.4",
"node-rdkafka": "^2.2.3",
"node-sass": "^4.5.0",
"nodemailer": "^4.5.0",
"node-sass": "^4.8.2",
"nodemailer": "^4.6.3",
"ora": "^2.0.0",
"pageres": "^4.1.1",
"passport": "^0.4.0",
@@ -81,46 +75,46 @@
"passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.8.1",
"popper.js": "^1.13.0",
"popper.js": "^1.14.1",
"postcss-easy-import": "^3.0.0",
"ps-tree": "^1.0.0",
"pug": "^2.0.0-rc.4",
"pug": "^2.0.1",
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
"pusher": "^1.3.0",
"rimraf": "^2.4.3",
"sass-loader": "^6.0.2",
"sass-loader": "^6.0.7",
"shelljs": "^0.8.1",
"stackimpact": "^1.2.1",
"stripe": "^5.5.0",
"superagent": "^3.4.3",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^2.0.2",
"svgo": "^1.0.4",
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
"svgo-loader": "^2.1.0",
"universal-analytics": "^0.4.16",
"url-loader": "^0.6.2",
"url-loader": "^1.0.0",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^9.4.1",
"vinyl-buffer": "^1.0.1",
"vue": "^2.5.2",
"vue-loader": "^14.1.1",
"vue": "^2.5.16",
"vue-loader": "^14.2.1",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^3.0.0",
"vue-style-loader": "^4.0.2",
"vue-template-compiler": "^2.5.2",
"vue-template-compiler": "^2.5.16",
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.11.0",
"webpack-merge": "^4.0.0",
"winston": "^2.1.0",
"winston": "^2.4.1",
"winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4"
},
"private": true,
"engines": {
"node": "^6.9.1",
"npm": "^5.0.0"
"node": "^8.9.4",
"npm": "^5.6.0"
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
@@ -147,23 +141,25 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.12",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.3.1",
"chromedriver": "^2.27.2",
"chalk": "^2.3.2",
"chromedriver": "^2.36.0",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.0",
"cross-spawn": "^6.0.4",
"eslint": "^4.18.1",
"cross-spawn": "^6.0.5",
"eslint": "^4.19.0",
"eslint-config-habitrpg": "^4.0.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.3.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-html": "^4.0.2",
"eslint-plugin-mocha": "^4.7.0",
"eslint-plugin-mocha": "^4.12.1",
"eventsource-polyfill": "^0.9.6",
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.17.0",
"http-proxy-middleware": "^0.18.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^2.0.0",
"karma-babel-preprocessor": "^7.0.0",
@@ -176,19 +172,22 @@
"karma-sinon-stub-promise": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^2.0.2",
"karma-webpack": "^2.0.13",
"lcov-result-merger": "^2.0.0",
"mocha": "^5.0.1",
"mocha": "^5.0.4",
"monk": "^6.0.5",
"nightwatch": "^0.9.12",
"puppeteer": "^1.1.0",
"nightwatch": "^0.9.20",
"puppeteer": "^1.2.0",
"require-again": "^2.0.0",
"selenium-server": "^3.9.1",
"sinon": "^4.3.0",
"sinon-chai": "^2.8.0",
"selenium-server": "^3.11.0",
"sinon": "^4.4.5",
"sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.6.1"
"webpack-hot-middleware": "^2.21.2"
},
"optionalDependencies": {
"node-rdkafka": "^2.3.0"
}
}
-1
View File
@@ -1,5 +1,4 @@
require('babel-register');
require('babel-polyfill');
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
@@ -151,7 +151,10 @@ describe('GET challenges/groups/:groupId', () => {
});
officialChallenge = await generateChallenge(user, group, {
official: true,
categories: [{
name: 'habitica_official',
slug: 'habitica_official',
}],
});
challenge = await generateChallenge(user, group);
@@ -193,7 +193,10 @@ describe('GET challenges/user', () => {
});
officialChallenge = await generateChallenge(user, group, {
official: true,
categories: [{
name: 'habitica_official',
slug: 'habitica_official',
}],
});
challenge = await generateChallenge(user, group);
@@ -2,13 +2,12 @@ import {
generateUser,
} from '../../../../helpers/api-v3-integration.helper';
import xml2js from 'xml2js';
import Bluebird from 'bluebird';
import util from 'util';
let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
let parseStringAsync = util.promisify(xml2js.parseString).bind(xml2js);
describe('GET /export/userdata.xml', () => {
// TODO disabled because it randomly causes the build to fail
xit('should return a valid XML file with user data', async () => {
it('should return a valid XML file with user data', async () => {
let user = await generateUser();
let tasks = await user.post('/tasks/user', [
{type: 'habit', text: 'habit 1'},
@@ -31,13 +30,21 @@ describe('GET /export/userdata.xml', () => {
expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']);
expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']);
expect(res.tasks.habits.length).to.equal(2);
expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id);
let habitIds = _.map(res.tasks.habits, '_id');
expect(habitIds).to.have.deep.members([tasks[0]._id, tasks[4]._id]);
expect(res.tasks.dailys.length).to.equal(2);
expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id);
let dailyIds = _.map(res.tasks.dailys, '_id');
expect(dailyIds).to.have.deep.members([tasks[1]._id, tasks[5]._id]);
expect(res.tasks.rewards.length).to.equal(2);
expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id);
let rewardIds = _.map(res.tasks.rewards, '_id');
expect(rewardIds).to.have.deep.members([tasks[2]._id, tasks[6]._id]);
expect(res.tasks.todos.length).to.equal(3);
expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id);
let todoIds = _.map(res.tasks.todos, '_id');
expect(todoIds).to.deep.include.members([tasks[3]._id, tasks[7]._id]);
});
});
@@ -201,8 +201,8 @@ describe('GET /groups', () => {
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
.to.eventually.have.a.lengthOf(1 + 3); // 1 created now, 3 by other tests
expect(page2[3].name).to.equal('guild with less members');
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests
expect(page2[4].name).to.equal('guild with less members');
});
});
@@ -220,4 +220,18 @@ describe('GET /groups', () => {
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
});
it('returns a list of groups user has access to', async () => {
let group = await generateGroup(user, {
name: 'c++ coders',
type: 'guild',
privacy: 'public',
});
// search for 'c++ coders'
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
.to.eventually.have.lengthOf(1)
.and.to.have.nested.property('[0]')
.and.to.have.property('_id', group._id);
});
});
@@ -44,12 +44,12 @@ describe('POST /group/:groupId/join', () => {
expect(res.leader.profile.name).to.eql(user.profile.name);
});
it('returns an error is user was already a member', async () => {
it('returns an error if user was already a member', async () => {
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('userAlreadyInGroup'),
message: t('youAreAlreadyInGroup'),
});
});
@@ -262,6 +262,30 @@ describe('POST /group/:groupId/join', () => {
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
});
it('does not allow user to leave a party if a quest was active and they were the only member', async () => {
let userToInvite = await generateUser();
let oldParty = await userToInvite.post('/groups', { // add user to a party
name: 'Another Test Party',
type: 'party',
});
await userToInvite.update({
[`items.quests.${PET_QUEST}`]: 1,
});
await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`);
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
await user.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageCannotLeaveWhileQuesting'),
});
});
it('invites joining member to active quest', async () => {
await user.update({
[`items.quests.${PET_QUEST}`]: 1,
@@ -2,8 +2,8 @@ import {
createAndPopulateGroup,
translate as t,
generateUser,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
import Bluebird from 'bluebird';
describe('POST /groups/:groupId/quests/accept', () => {
const PET_QUEST = 'whale';
@@ -140,7 +140,7 @@ describe('POST /groups/:groupId/quests/accept', () => {
// quest will start after everyone has accepted
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
await Bluebird.delay(500);
await sleep(0.5);
await rejectingMember.sync();
@@ -2,8 +2,8 @@ import {
createAndPopulateGroup,
translate as t,
generateUser,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
import Bluebird from 'bluebird';
describe('POST /groups/:groupId/quests/force-start', () => {
const PET_QUEST = 'whale';
@@ -135,7 +135,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await Bluebird.delay(500);
await sleep(0.5);
await Promise.all([
partyMemberThatRejects.sync(),
@@ -161,7 +161,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await Bluebird.delay(500);
await sleep(0.5);
await questingGroup.sync();
@@ -184,7 +184,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await Bluebird.delay(500);
await sleep(0.5);
await questingGroup.sync();
@@ -201,7 +201,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await Bluebird.delay(500);
await sleep(0.5);
await questingGroup.sync();
@@ -222,7 +222,7 @@ describe('POST /groups/:groupId/quests/force-start', () => {
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await Bluebird.delay(500);
await sleep(0.5);
await questingGroup.sync();
@@ -2,9 +2,9 @@ import {
createAndPopulateGroup,
translate as t,
generateUser,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
import Bluebird from 'bluebird';
describe('POST /groups/:groupId/quests/reject', () => {
let questingGroup;
@@ -168,7 +168,7 @@ describe('POST /groups/:groupId/quests/reject', () => {
// quest will start after everyone has accepted or rejected
await rejectingMember.post(`/groups/${questingGroup._id}/quests/reject`);
await Bluebird.delay(500);
await sleep(0.5);
await questingGroup.sync();
@@ -2,7 +2,6 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import Bluebird from 'bluebird';
describe('GET /shops/market', () => {
let user;
@@ -42,13 +41,13 @@ describe('GET /shops/market', () => {
return array;
}, []);
let results = await Bluebird.each(items, (item) => {
let results = await Promise.all(items.map((item) => {
let { purchaseType, key } = item;
return user.post(`/user/purchase/${purchaseType}/${key}`);
});
}));
expect(results.length).to.be.greaterThan(0);
results.forEach((item) => {
items.forEach((item) => {
expect(item).to.include.keys('key', 'text', 'notes', 'class', 'value', 'currency');
});
});
@@ -2,8 +2,8 @@ import {
generateUser,
generateGroup,
generateChallenge,
sleep,
} from '../../../../../helpers/api-integration/v3';
import Bluebird from 'bluebird';
import { find } from 'lodash';
describe('POST /tasks/:id/score/:direction', () => {
@@ -27,7 +27,7 @@ describe('POST /tasks/:id/score/:direction', () => {
text: 'test habit',
type: 'habit',
});
await Bluebird.delay(1000);
await sleep(1);
let updatedUser = await user.get('/user');
usersChallengeTaskId = updatedUser.tasksOrder.habits[0];
});
@@ -65,7 +65,7 @@ describe('POST /tasks/:id/score/:direction', () => {
text: 'test daily',
type: 'daily',
});
await Bluebird.delay(1000);
await sleep(1);
let updatedUser = await user.get('/user');
usersChallengeTaskId = updatedUser.tasksOrder.dailys[0];
});
@@ -109,7 +109,7 @@ describe('POST /tasks/:id/score/:direction', () => {
text: 'test todo',
type: 'todo',
});
await Bluebird.delay(1000);
await sleep(1);
let updatedUser = await user.get('/user');
usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
});
@@ -134,7 +134,7 @@ describe('POST /tasks/:id/score/:direction', () => {
text: 'test reward',
type: 'reward',
});
await Bluebird.delay(1000);
await sleep(1);
let updatedUser = await user.get('/user');
usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
});
@@ -11,7 +11,6 @@ import {
each,
map,
} from 'lodash';
import Bluebird from 'bluebird';
import {
sha1MakeSalt,
sha1Encrypt as sha1EncryptPassword,
@@ -104,7 +103,7 @@ describe('DELETE /user', () => {
password,
});
await Bluebird.all(map(ids, id => {
await Promise.all(map(ids, id => {
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
}));
});
@@ -0,0 +1,24 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /user/in-app-rewards', () => {
let user;
before(async () => {
user = await generateUser();
});
it('returns the reward items available for purchase', async () => {
let buyList = await user.get('/user/in-app-rewards');
expect(_.find(buyList, item => {
return item.text === t('armorWarrior1Text');
})).to.exist;
expect(_.find(buyList, item => {
return item.text === t('armorWarrior2Text');
})).to.not.exist;
});
});
@@ -0,0 +1,27 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /user/toggle-pinned-item', () => {
let user;
before(async () => {
user = await generateUser();
});
it('cannot unpin potion', async () => {
await expect(user.get('/user/toggle-pinned-item/potion/potion'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cannotUnpinArmoirPotion'),
});
});
it('can pin shield_rogue_5', async () => {
let result = await user.get('/user/toggle-pinned-item/marketGear/gear.flat.shield_rogue_5');
expect(result.pinnedItems.length).to.be.eql(user.pinnedItems.length + 1);
});
});
@@ -258,11 +258,31 @@ describe('POST /user/class/cast/:spellId', () => {
expect(user.achievements.birthday).to.equal(1);
});
it('passes correct target to spell when targetType === \'task\'', async () => {
await user.update({'stats.class': 'wizard', 'stats.lvl': 11});
let task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
let result = await user.post(`/user/class/cast/fireball?targetId=${task._id}`);
expect(result.task._id).to.equal(task._id);
});
it('passes correct target to spell when targetType === \'self\'', async () => {
await user.update({'stats.class': 'wizard', 'stats.lvl': 14, 'stats.mp': 50});
let result = await user.post('/user/class/cast/frost');
expect(result.user.stats.mp).to.equal(10);
});
// TODO find a way to have sinon working in integration tests
// it doesn't work when tests are running separately from server
it('passes correct target to spell when targetType === \'task\'');
it('passes correct target to spell when targetType === \'tasks\'');
it('passes correct target to spell when targetType === \'self\'');
it('passes correct target to spell when targetType === \'party\'');
it('passes correct target to spell when targetType === \'user\'');
it('passes correct target to spell when targetType === \'party\' and user is not in a party');
@@ -30,10 +30,12 @@ describe('POST /user/release-both', () => {
'items.currentPet': animal,
'items.pets': loadPets(),
'items.mounts': loadMounts(),
'achievements.triadBingo': true,
});
});
it('returns an error when user balance is too low and user does not have triadBingo', async () => {
// @TODO: Traid is now free. Add this back if we need
xit('returns an error when user balance is too low and user does not have triadBingo', async () => {
await expect(user.post('/user/release-both'))
.to.eventually.be.rejected.and.to.eql({
code: 401,
@@ -45,9 +47,7 @@ describe('POST /user/release-both', () => {
// More tests in common code unit tests
it('grants triad bingo with gems', async () => {
await user.update({
balance: 1.5,
});
await user.update();
let response = await user.post('/user/release-both');
await user.sync();
@@ -27,6 +27,33 @@ describe('PUT /user', () => {
expect(user.stats.hp).to.eql(14);
});
it('tags must be an array', async () => {
await expect(user.put('/user', {
tags: {
tag: true,
},
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'mustBeArray',
});
});
it('update tags', async () => {
let userTags = user.tags;
await user.put('/user', {
tags: [...user.tags, {
name: 'new tag',
}],
});
await user.sync();
expect(user.tags.length).to.be.eql(userTags.length + 1);
});
it('profile.name cannot be an empty string or null', async () => {
await expect(user.put('/user', {
'profile.name': ' ', // string should be trimmed
@@ -357,6 +357,21 @@ describe('POST /user/auth/local/register', () => {
});
});
it('sanitizes email params to a lowercase string before creating the user', async () => {
let username = generateRandomUserName();
let email = 'ISANEmAiL@ExAmPle.coM';
let password = 'password';
let user = await api.post('/user/auth/local/register', {
username,
email,
password,
confirmPassword: password,
});
expect(user.auth.local.email).to.equal(email.toLowerCase());
});
it('fails on a habitica.com email', async () => {
let username = generateRandomUserName();
let email = `${username}@habitica.com`;
@@ -13,7 +13,7 @@ import nconf from 'nconf';
const ENDPOINT = '/user/auth/update-email';
describe('PUT /user/auth/update-email', () => {
let newEmail = 'some-new-email_2@example.net';
let newEmail = 'SOmE-nEw-emAIl_2@example.net';
let oldPassword = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
context('Local Authenticaion User', async () => {
@@ -53,14 +53,15 @@ describe('PUT /user/auth/update-email', () => {
});
it('changes email if new email and existing password are provided', async () => {
let lowerCaseNewEmail = newEmail.toLowerCase();
let response = await user.put(ENDPOINT, {
newEmail,
password: oldPassword,
});
expect(response).to.eql({ email: 'some-new-email_2@example.net' });
expect(response.email).to.eql(lowerCaseNewEmail);
await user.sync();
expect(user.auth.local.email).to.eql(newEmail);
expect(user.auth.local.email).to.eql(lowerCaseNewEmail);
});
it('rejects if email is already taken', async () => {
@@ -32,4 +32,11 @@ describe('GET /world-state', () => {
},
});
});
it('returns a string representing the current season for NPC sprites', async () => {
const res = await requester().get('/world-state');
expect(res).to.have.nested.property('npcImageSuffix');
expect(res.npcImageSuffix).to.be.a('string');
});
});
+4 -5
View File
@@ -1,7 +1,6 @@
/* eslint-disable global-require */
import moment from 'moment';
import nconf from 'nconf';
import Bluebird from 'bluebird';
import requireAgain from 'require-again';
import { recoverCron, cron } from '../../../../../website/server/libs/cron';
import { model as User } from '../../../../../website/server/models/user';
@@ -1363,7 +1362,7 @@ describe('recoverCron', () => {
});
it('throws an error if user cannot be found', async () => {
execStub.returns(Bluebird.resolve(null));
execStub.returns(Promise.resolve(null));
try {
await recoverCron(status, locals);
@@ -1374,8 +1373,8 @@ describe('recoverCron', () => {
});
it('increases status.times count and reruns up to 4 times', async () => {
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
execStub.onCall(4).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'}));
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
execStub.onCall(4).returns(Promise.resolve({_cronSignature: 'NOT_RUNNING'}));
await recoverCron(status, locals);
@@ -1384,7 +1383,7 @@ describe('recoverCron', () => {
});
it('throws an error if recoverCron runs 5 times', async () => {
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
try {
await recoverCron(status, locals);
+1 -1
View File
@@ -47,7 +47,7 @@ describe('slack', () => {
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: 'flagger (flagger-id) flagged a message (language: flagger-lang)',
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
attachments: [{
fallback: 'Flag Message',
color: 'danger',
@@ -8,7 +8,6 @@ import {
attachTranslateFunction,
} from '../../../../../website/server/middlewares/language';
import common from '../../../../../website/common';
import Bluebird from 'bluebird';
import { model as User } from '../../../../../website/server/models/user';
const i18n = common.i18n;
@@ -162,7 +161,7 @@ describe('language middleware', () => {
return this;
},
exec () {
return Bluebird.resolve({
return Promise.resolve({
preferences: {
language: 'it',
},
+2 -3
View File
@@ -1,4 +1,3 @@
import Bluebird from 'bluebird';
import moment from 'moment';
import { model as User } from '../../../../../website/server/models/user';
import { model as Group } from '../../../../../website/server/models/group';
@@ -123,7 +122,7 @@ describe('User Model', () => {
it('adds notifications without data for all given users via static method', async () => {
let user = new User();
let otherUser = new User();
await Bluebird.all([user.save(), otherUser.save()]);
await Promise.all([user.save(), otherUser.save()]);
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON');
@@ -149,7 +148,7 @@ describe('User Model', () => {
it('adds notifications with data and seen status for all given users via static method', async () => {
let user = new User();
let otherUser = new User();
await Bluebird.all([user.save(), otherUser.save()]);
await Promise.all([user.save(), otherUser.save()]);
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true);
@@ -0,0 +1,211 @@
import { shallow, createLocalVue } from '@vue/test-utils';
import TaskColumn from 'client/components/tasks/column.vue';
import Store from 'client/libs/store';
// eslint-disable no-exclusive-tests
const localVue = createLocalVue();
localVue.use(Store);
describe('Task Column', () => {
let wrapper;
let store, getters;
let habits, taskListOverride, tasks;
function makeWrapper (additionalSetup = {}) {
let type = 'habit';
let mocks = {
$t () {},
};
let stubs = ['b-modal']; // <b-modal> is a custom component and not tested here
return shallow(TaskColumn, {
propsData: {
type,
},
mocks,
stubs,
localVue,
...additionalSetup,
});
}
it('returns a vue instance', () => {
wrapper = makeWrapper();
expect(wrapper.isVueInstance()).to.be.true;
});
describe('Passed Properties', () => {
beforeEach(() => {
wrapper = makeWrapper();
});
it('defaults isUser to false', () => {
expect(wrapper.vm.isUser).to.be.false;
});
it('passes isUser to component instance', () => {
wrapper.setProps({ isUser: false });
expect(wrapper.vm.isUser).to.be.false;
wrapper.setProps({ isUser: true });
expect(wrapper.vm.isUser).to.be.true;
});
});
describe('Computed Properties', () => {
beforeEach(() => {
habits = [
{ id: 1 },
{ id: 2 },
];
taskListOverride = [
{ id: 3 },
{ id: 4 },
];
getters = {
// (...) => { ... } will return a value
// (...) => (...) => { ... } will return a function
// Task Column expects a function
'tasks:getFilteredTaskList': () => () => habits,
};
store = new Store({getters});
wrapper = makeWrapper({store});
});
it('returns task list from props for group-plan', () => {
wrapper.setProps({ taskListOverride });
wrapper.vm.taskList.forEach((el, i) => {
expect(el).to.eq(taskListOverride[i]);
});
wrapper.setProps({ isUser: false, taskListOverride });
wrapper.vm.taskList.forEach((el, i) => {
expect(el).to.eq(taskListOverride[i]);
});
});
it('returns task list from store for user', () => {
wrapper.setProps({ isUser: true, taskListOverride });
wrapper.vm.taskList.forEach((el, i) => {
expect(el).to.eq(habits[i]);
});
});
});
describe('Methods', () => {
describe('Filter By Tags', () => {
beforeEach(() => {
tasks = [
{ tags: [3, 4] },
{ tags: [2, 3] },
{ tags: [] },
{ tags: [1, 3] },
];
});
it('returns all tasks if no tag is given', () => {
let returnedTasks = wrapper.vm.filterByTagList(tasks);
expect(returnedTasks).to.have.lengthOf(tasks.length);
tasks.forEach((task, i) => {
expect(returnedTasks[i]).to.eq(task);
});
});
it('returns tasks for given single tag', () => {
let returnedTasks = wrapper.vm.filterByTagList(tasks, [3]);
expect(returnedTasks).to.have.lengthOf(3);
expect(returnedTasks[0]).to.eq(tasks[0]);
expect(returnedTasks[1]).to.eq(tasks[1]);
expect(returnedTasks[2]).to.eq(tasks[3]);
});
it('returns tasks for given multiple tags', () => {
let returnedTasks = wrapper.vm.filterByTagList(tasks, [2, 3]);
expect(returnedTasks).to.have.lengthOf(1);
expect(returnedTasks[0]).to.eq(tasks[1]);
});
});
describe('Filter By Search Text', () => {
beforeEach(() => {
tasks = [
{
text: 'Hello world 1',
note: '',
checklist: [],
},
{
text: 'Hello world 2',
note: '',
checklist: [],
},
{
text: 'Generic Task Title',
note: '',
checklist: [
{ text: 'Check 1' },
{ text: 'Check 2' },
{ text: 'Check 3' },
],
},
{
text: 'Hello world 3',
note: 'Generic Task Note',
checklist: [
{ text: 'Checkitem 1' },
{ text: 'Checkitem 2' },
{ text: 'Checkitem 3' },
],
},
];
});
it('returns all tasks for empty search term', () => {
let returnedTasks = wrapper.vm.filterBySearchText(tasks);
expect(returnedTasks).to.have.lengthOf(tasks.length);
tasks.forEach((task, i) => {
expect(returnedTasks[i]).to.eq(task);
});
});
it('returns tasks for search term in title /i', () => {
['Title', 'TITLE', 'title', 'tItLe'].forEach((term) => {
expect(wrapper.vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[2]);
});
});
it('returns tasks for search term in note /i', () => {
['Note', 'NOTE', 'note', 'nOtE'].forEach((term) => {
expect(wrapper.vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[3]);
});
});
it('returns tasks for search term in checklist title /i', () => {
['Check', 'CHECK', 'check', 'cHeCK'].forEach((term) => {
let returnedTasks = wrapper.vm.filterBySearchText(tasks, term);
expect(returnedTasks[0]).to.eq(tasks[2]);
expect(returnedTasks[1]).to.eq(tasks[3]);
});
['Checkitem', 'CHECKITEM', 'checkitem', 'cHeCKiTEm'].forEach((term) => {
expect(wrapper.vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[3]);
});
});
});
});
});
@@ -0,0 +1,62 @@
import {
getTypeLabel,
getFilterLabels,
getActiveFilter,
} from 'client/libs/store/helpers/filterTasks.js';
describe('Filter Category for Tasks', () => {
describe('getTypeLabel', () => {
it('should return correct task type labels', () => {
expect(getTypeLabel('habit')).to.eq('habits');
expect(getTypeLabel('daily')).to.eq('dailies');
expect(getTypeLabel('todo')).to.eq('todos');
expect(getTypeLabel('reward')).to.eq('rewards');
});
});
describe('getFilterLabels', () => {
let habit, daily, todo, reward;
beforeEach(() => {
habit = ['all', 'yellowred', 'greenblue'];
daily = ['all', 'due', 'notDue'];
todo = ['remaining', 'scheduled', 'complete2'];
reward = ['all', 'custom', 'wishlist'];
});
it('should return all task type filter labels by type', () => {
// habits
getFilterLabels('habit').forEach((item, i) => {
expect(item).to.eq(habit[i]);
});
// dailys
getFilterLabels('daily').forEach((item, i) => {
expect(item).to.eq(daily[i]);
});
// todos
getFilterLabels('todo').forEach((item, i) => {
expect(item).to.eq(todo[i]);
});
// rewards
getFilterLabels('reward').forEach((item, i) => {
expect(item).to.eq(reward[i]);
});
});
});
describe('getActiveFilter', () => {
it('should return single function by default', () => {
let activeFilter = getActiveFilter('habit');
expect(activeFilter).to.be.an('object');
expect(activeFilter).to.have.all.keys('label', 'filterFn', 'default');
expect(activeFilter.default).to.be.true;
});
it('should return single function for given filter type', () => {
let activeFilterLabel = 'yellowred';
let activeFilter = getActiveFilter('habit', activeFilterLabel);
expect(activeFilter).to.be.an('object');
expect(activeFilter).to.have.all.keys('label', 'filterFn');
expect(activeFilter.label).to.eq(activeFilterLabel);
});
});
});
@@ -0,0 +1,43 @@
import {
orderSingleTypeTasks,
// orderMultipleTypeTasks,
} from 'client/libs/store/helpers/orderTasks.js';
import shuffle from 'lodash/shuffle';
describe('Task Order Helper Function', () => {
let tasks, shuffledTasks, taskOrderList;
beforeEach(() => {
taskOrderList = [1, 2, 3, 4];
tasks = [];
taskOrderList.forEach(i => tasks.push({ _id: i, id: i }));
shuffledTasks = shuffle(tasks);
});
it('should return tasks as is for no task order', () => {
expect(orderSingleTypeTasks(shuffledTasks)).to.eq(shuffledTasks);
});
it('should return tasks in expected order', () => {
let newOrderedTasks = orderSingleTypeTasks(shuffledTasks, taskOrderList);
newOrderedTasks.forEach((item, index) => {
expect(item).to.eq(tasks[index]);
});
});
it('should return new tasks at end of expected order', () => {
let newTaskIds = [10, 15, 20];
newTaskIds.forEach(i => tasks.push({ _id: i, id: i }));
shuffledTasks = shuffle(tasks);
let newOrderedTasks = orderSingleTypeTasks(shuffledTasks, taskOrderList);
// checking tasks with order
newOrderedTasks.slice(0, taskOrderList.length).forEach((item, index) => {
expect(item).to.eq(tasks[index]);
});
// check for new task ids
newOrderedTasks.slice(-3).forEach(item => {
expect(item.id).to.be.oneOf(newTaskIds);
});
});
});
@@ -0,0 +1,118 @@
import generateStore from 'client/store';
describe('Store Getters for Tasks', () => {
let store, habits, dailys, todos, rewards;
beforeEach(() => {
store = generateStore();
// Get user preference data and user tasks order data
store.state.user.data = {
preferences: {},
tasksOrder: {
habits: [],
dailys: [],
todos: [],
rewards: [],
},
};
});
describe('Task List', () => {
beforeEach(() => {
habits = [
{ id: 1 },
{ id: 2 },
];
dailys = [
{ id: 3 },
{ id: 4 },
];
todos = [
{ id: 5 },
{ id: 6 },
];
rewards = [
{ id: 7 },
{ id: 8 },
];
store.state.tasks.data = {
habits,
dailys,
todos,
rewards,
};
});
it('should returns all tasks by task type', () => {
let returnedTasks = store.getters['tasks:getUnfilteredTaskList']('habit');
expect(returnedTasks).to.eq(habits);
returnedTasks = store.getters['tasks:getUnfilteredTaskList']('daily');
expect(returnedTasks).to.eq(dailys);
returnedTasks = store.getters['tasks:getUnfilteredTaskList']('todo');
expect(returnedTasks).to.eq(todos);
returnedTasks = store.getters['tasks:getUnfilteredTaskList']('reward');
expect(returnedTasks).to.eq(rewards);
});
});
// @TODO add task filter check for rewards and dailys
describe('Task Filters', () => {
beforeEach(() => {
habits = [
// weak habit
{ value: 0 },
// strong habit
{ value: 2 },
];
todos = [
// scheduled todos
{ completed: false, date: 'Mon, 15 Jan 2018 12:18:29 GMT' },
// completed todos
{ completed: true },
];
store.state.tasks.data = {
habits,
todos,
};
});
it('should return weak habits', () => {
let returnedTasks = store.getters['tasks:getFilteredTaskList']({
type: 'habit',
filterType: 'yellowred',
});
expect(returnedTasks[0]).to.eq(habits[0]);
});
it('should return strong habits', () => {
let returnedTasks = store.getters['tasks:getFilteredTaskList']({
type: 'habit',
filterType: 'greenblue',
});
expect(returnedTasks[0]).to.eq(habits[1]);
});
it('should return scheduled todos', () => {
let returnedTasks = store.getters['tasks:getFilteredTaskList']({
type: 'todo',
filterType: 'scheduled',
});
expect(returnedTasks[0]).to.eq(todos[0]);
});
it('should return completed todos', () => {
let returnedTasks = store.getters['tasks:getFilteredTaskList']({
type: 'todo',
filterType: 'complete2',
});
expect(returnedTasks[0]).to.eq(todos[1]);
});
});
});
@@ -4,14 +4,20 @@ import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../../helpers/common.helper';
import buyGear from '../../../../website/common/script/ops/buy/buyGear';
import {BuyMarketGearOperation} from '../../../../website/common/script/ops/buy/buyMarketGear';
import shared from '../../../../website/common/script';
import {
BadRequest, NotAuthorized, NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyGear', () => {
function buyGear (user, req, analytics) {
let buyOp = new BuyMarketGearOperation(user, req, analytics);
return buyOp.purchase();
}
describe('shared.ops.buyMarketGear', () => {
let user;
let analytics = {track () {}};
@@ -78,6 +84,17 @@ describe('shared.ops.buyGear', () => {
expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1');
});
it('updates the pinnedItems to the next item in the set if one exists', () => {
user.stats.gp = 31;
buyGear(user, {params: {key: 'armor_warrior_1'}});
expect(user.pinnedItems).to.deep.include({
type: 'marketGear',
path: 'gear.flat.armor_warrior_2',
});
});
it('buyGears equipment but does not auto-equip', () => {
user.stats.gp = 31;
user.preferences.autoEquip = false;
@@ -100,6 +117,31 @@ describe('shared.ops.buyGear', () => {
}
});
it('does not buy equipment of different class', (done) => {
user.stats.gp = 82;
user.stats.class = 'warrior';
try {
buyGear(user, {params: {key: 'weapon_special_winter2018Rogue'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('cannotBuyItem'));
done();
}
});
it('does not buy equipment in bulk', (done) => {
user.stats.gp = 82;
try {
buyGear(user, {params: {key: 'armor_warrior_1'}, quantity: 3});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAbleToBuyInBulk'));
done();
}
});
// TODO after user.ops.equip is done
xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => {
user.stats.gp = 100;
+44 -16
View File
@@ -26,10 +26,11 @@ describe('shared.ops.releaseBoth', () => {
user.items.currentMount = animal;
user.items.currentPet = animal;
user.balance = 1.5;
user.achievements.triadBingo = true;
});
it('returns an error when user balance is too low and user does not have triadBingo', (done) => {
xit('returns an error when user balance is too low and user does not have triadBingo', (done) => {
user.balance = 0;
try {
@@ -41,6 +42,22 @@ describe('shared.ops.releaseBoth', () => {
}
});
it('returns an error when user does not have all pets', (done) => {
const petKeys = Object.keys(user.items.pets);
delete user.items.pets[petKeys[0]];
const mountKeys = Object.keys(user.items.mounts);
delete user.items.mounts[mountKeys[0]];
try {
releaseBoth(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughPetsMounts'));
done();
}
});
it('grants triad bingo with gems', () => {
let message = releaseBoth(user)[1];
@@ -79,26 +96,33 @@ describe('shared.ops.releaseBoth', () => {
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
user.items.pets[animal] = 0;
releaseBoth(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releaseBoth(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
it('does not increment beastMasterCount if any pet is missing (null)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
user.items.pets[animal] = null;
releaseBoth(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releaseBoth(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
delete user.items.pets[animal];
releaseBoth(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releaseBoth(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
it('releases mounts', () => {
@@ -112,18 +136,22 @@ describe('shared.ops.releaseBoth', () => {
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
user.items.mounts[animal] = null;
releaseBoth(user);
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
try {
releaseBoth(user);
} catch (e) {
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
}
});
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
delete user.items.mounts[animal];
releaseBoth(user);
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
try {
releaseBoth(user);
} catch (e) {
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
}
});
it('removes drop currentPet', () => {
+23 -6
View File
@@ -35,6 +35,19 @@ describe('shared.ops.releaseMounts', () => {
}
});
it('returns an error when user does not have all pets', (done) => {
const mountsKeys = Object.keys(user.items.mounts);
delete user.items.mounts[mountsKeys[0]];
try {
releaseMounts(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughMounts'));
done();
}
});
it('releases mounts', () => {
let message = releaseMounts(user)[1];
@@ -71,18 +84,22 @@ describe('shared.ops.releaseMounts', () => {
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
user.items.mounts[animal] = null;
releaseMounts(user);
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
try {
releaseMounts(user);
} catch (e) {
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
}
});
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
delete user.items.mounts[animal];
releaseMounts(user);
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
try {
releaseMounts(user);
} catch (e) {
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
}
});
it('subtracts gems from balance', () => {
+29 -8
View File
@@ -35,6 +35,19 @@ describe('shared.ops.releasePets', () => {
}
});
it('returns an error when user does not have all pets', (done) => {
const petKeys = Object.keys(user.items.pets);
delete user.items.pets[petKeys[0]];
try {
releasePets(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughPets'));
done();
}
});
it('releases pets', () => {
let message = releasePets(user)[1];
@@ -75,27 +88,35 @@ describe('shared.ops.releasePets', () => {
});
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
const beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
user.items.pets[animal] = 0;
releasePets(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releasePets(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
it('does not increment beastMasterCount if any pet is missing (null)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
user.items.pets[animal] = null;
releasePets(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releasePets(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
delete user.items.pets[animal];
releasePets(user);
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
try {
releasePets(user);
} catch (e) {
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
}
});
});
@@ -1,7 +1,6 @@
import {
times,
} from 'lodash';
import Bluebird from 'bluebird';
import { v4 as generateUUID } from 'uuid';
import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes';
import { requester } from '../requester';
@@ -106,7 +105,7 @@ export async function createAndPopulateGroup (settings = {}) {
guild: { guilds: [group._id] },
};
let members = await Bluebird.all(
let members = await Promise.all(
times(numberOfMembers, () => {
return generateUser(groupMembershipTypes[group.type]);
})
@@ -114,7 +113,7 @@ export async function createAndPopulateGroup (settings = {}) {
await group.update({ memberCount: numberOfMembers + 1});
let invitees = await Bluebird.all(
let invitees = await Promise.all(
times(numberOfInvites, () => {
return generateUser();
})
@@ -126,9 +125,9 @@ export async function createAndPopulateGroup (settings = {}) {
});
});
await Bluebird.all(invitationPromises);
await Promise.all(invitationPromises);
await Bluebird.map(invitees, (invitee) => invitee.sync());
await Promise.all(invitees.map((invitee) => invitee.sync()));
return {
groupLeader,
-3
View File
@@ -2,8 +2,6 @@
/* eslint-disable global-require */
/* eslint-disable no-process-env */
import Bluebird from 'bluebird';
//------------------------------
// Global modules
//------------------------------
@@ -16,7 +14,6 @@ global.sinon = require('sinon');
let sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(global.sinon);
global.sandbox = sinon.sandbox.create();
global.Promise = Bluebird;
import setupNconf from '../../website/server/libs/setupNconf';
setupNconf('./config.json.example');
+1 -7
View File
@@ -1,7 +1 @@
export async function sleep (seconds = 1) {
let milliseconds = seconds * 1000;
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
export { default as sleep } from '../../website/server/libs/sleep';
-2
View File
@@ -1,13 +1,11 @@
/* eslint-disable no-process-env */
import nconf from 'nconf';
import mongoose from 'mongoose';
import Bluebird from 'bluebird';
import setupNconf from '../../website/server/libs/setupNconf';
if (process.env.LOAD_SERVER === '0') { // when the server is in a different process we simply connect to mongoose
setupNconf('./config.json');
// Use Q promises instead of mpromise in mongoose
mongoose.Promise = Bluebird;
mongoose.connect(nconf.get('TEST_DB_URI'));
} else { // When running tests and the server in the same process
setupNconf('./config.json.example');
-1
View File
@@ -3,7 +3,6 @@
--timeout 8000
--check-leaks
--globals io
-r babel-polyfill
--require babel-register
--require ./test/helpers/globals.helper
--exit
+3 -3
View File
@@ -11,12 +11,12 @@ source /home/vagrant/.profile
echo Setting up node...
cd /vagrant
nvm install
nvm use
nvm install 8
nvm use 8
nvm alias default current
echo Update npm...
npm install -g npm@4
npm install -g npm@5
echo Installing global modules...
npm install -g gulp mocha node-pre-gyp
+162 -55
View File
@@ -14,10 +14,17 @@ div
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
template(v-if="isUserLoaded")
div.resting-banner(v-if="showRestingBanner")
span.content
span.label {{ $t('innCheckOutBanner') }}
span.separator |
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
div.closepadding(@click="hideBanner()")
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
notifications-display
app-menu
app-menu(:class='{"restingInn": showRestingBanner}')
.container-fluid
app-header
app-header(:class='{"restingInn": showRestingBanner}')
buyModal(
:item="selectedItemToBuy || {}",
:withPin="true",
@@ -34,13 +41,15 @@ div
div(:class='{sticky: user.preferences.stickyHeader}')
router-view
app-footer
app-footer
audio#sound(autoplay, ref="sound")
source#oggSource(type="audio/ogg", :src="sound.oggSource")
source#mp3Source(type="audio/mp3", :src="sound.mp3Source")
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
#loading-screen-inapp {
#melior {
margin: 0 auto;
@@ -53,7 +62,7 @@ div
}
h2 {
color: #fff;
color: $white;
font-size: 32px;
font-weight: bold;
}
@@ -72,10 +81,10 @@ div
.notification {
border-radius: 1000px;
background-color: #24cc8f;
background-color: $green-10;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
padding: .5em 1em;
color: #fff;
color: $white;
margin-top: .5em;
margin-bottom: .5em;
}
@@ -93,7 +102,9 @@ div
}
</style>
<style>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal, .modal-open {
overflow-y: scroll !important;
@@ -101,13 +112,66 @@ div
.modal-backdrop.show {
opacity: 1 !important;
background-color: rgba(67, 40, 116, 0.9) !important;
background-color: $purple-100 !important;
}
/* Push progress bar above modals */
#nprogress .bar {
z-index: 1043 !important; /* Must stay above nav bar */
}
.restingInn {
.navbar {
top: 40px;
}
#app-header {
margin-top: 96px !important;
}
}
.resting-banner {
width: 100%;
height: 40px;
background-color: $blue-10;
position: fixed;
top: 0;
z-index: 1030;
display: flex;
.content {
height: 24px;
line-height: 1.71;
text-align: center;
color: $white;
margin: auto;
}
.closepadding {
margin: 11px 24px;
display: inline-block;
position: absolute;
right: 0;
top: 0;
cursor: pointer;
span svg path {
stroke: $blue-500;
}
}
.separator {
color: $blue-100;
margin: 0px 15px;
}
.resume {
font-weight: bold;
cursor: pointer;
}
}
</style>
<script>
@@ -128,6 +192,8 @@ import { setup as setupPayments } from 'client/libs/payments';
import amazonPaymentsModal from 'client/components/payments/amazonModal';
import spellsMixin from 'client/mixins/spells';
import svgClose from 'assets/svg/close.svg';
export default {
mixins: [notifications, spellsMixin],
name: 'app',
@@ -143,6 +209,9 @@ export default {
},
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
selectedItemToBuy: null,
selectedSpellToBuy: null,
@@ -152,6 +221,7 @@ export default {
},
loading: true,
currentTipNumber: 0,
bannerHidden: false,
};
},
computed: {
@@ -172,6 +242,9 @@ export default {
return this.$t(`tip${tipNumber}`);
},
showRestingBanner () {
return !this.bannerHidden && this.user.preferences.sleep;
},
},
created () {
this.$root.$on('playSound', (sound) => {
@@ -323,53 +396,7 @@ export default {
this.hideLoadingScreen();
}
// Manage modals
this.$root.$on('bv::show::modal', (modalId, data = {}) => {
if (data.fromRoot) return;
// Track opening of gems modal unless it's been already tracked
// For example the gems button in the menu already tracks the event by itself
if (modalId === 'buy-gems' && data.alreadyTracked !== true) {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Wallet',
});
}
// Get last modal on stack and hide
let modalStackLength = this.$store.state.modalStack.length;
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
// Add new modal to the stack
this.$store.state.modalStack.push(modalId);
// Hide the previous top modal
if (modalOnTop) this.$root.$emit('bv::hide::modal', modalOnTop, {fromRoot: true});
});
// @TODO: This part is hacky and could be solved with two options:
// 1 - Find a way to pass fromRoot to hidden
// 2 - Enforce that all modals use the hide::modal event
this.$root.$on('bv::modal::hidden', (bvEvent) => {
const modalId = bvEvent.target.id;
let modalStackLength = this.$store.state.modalStack.length;
let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
// Don't remove modal if hid was called from main app
// @TODO: I'd reather use this, but I don't know how to pass data to hidden event
// if (data && data.fromRoot) return;
if (modalId === modalSecondToTop) return;
// Remove modal from stack
this.$store.state.modalStack.pop();
// Recalculate and show the last modal if there is one
modalStackLength = this.$store.state.modalStack.length;
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
});
this.initializeModalStack();
},
beforeDestroy () {
this.$root.$off('playSound');
@@ -384,6 +411,80 @@ export default {
if (loadingScreen) document.body.removeChild(loadingScreen);
},
methods: {
initializeModalStack () {
// Manage modals
this.$root.$on('bv::show::modal', (modalId, data = {}) => {
if (data.fromRoot) return;
const modalStack = this.$store.state.modalStack;
this.trackGemPurchase(modalId, data);
// Add new modal to the stack
const prev = modalStack[modalStack.length - 1];
const prevId = prev ? prev.modalId : undefined;
modalStack.push({modalId, prev: prevId});
});
this.$root.$on('bv::modal::hidden', (bvEvent) => {
const modalId = bvEvent.target && bvEvent.target.id;
if (!modalId) return;
const modalStack = this.$store.state.modalStack;
const modalOnTop = modalStack[modalStack.length - 1];
// Check for invalid modal. Event systems can send multiples
if (!this.validStack(modalStack)) return;
// If we are moving forward
if (modalOnTop && modalOnTop.prev === modalId) return;
// Remove modal from stack
this.$store.state.modalStack.pop();
// Get previous modal
const modalBefore = modalOnTop ? modalOnTop.prev : undefined;
if (modalBefore) this.$root.$emit('bv::show::modal', modalBefore, {fromRoot: true});
});
},
validStack (modalStack) {
const modalsThatCanShowTwice = ['profile'];
const modalCount = {};
const prevAndCurrent = 2;
for (let index in modalStack) {
const current = modalStack[index];
if (!modalCount[current.modalId]) modalCount[current.modalId] = 0;
modalCount[current.modalId] += 1;
if (modalCount[current.modalId] > prevAndCurrent && modalsThatCanShowTwice.indexOf(current.modalId) === -1) {
this.$store.state.modalStack = [];
return false;
}
if (!current.prev) continue; // eslint-disable-line
if (!modalCount[current.prev]) modalCount[current.prev] = 0;
modalCount[current.prev] += 1;
if (modalCount[current.prev] > prevAndCurrent && modalsThatCanShowTwice.indexOf(current.prev) === -1) {
this.$store.state.modalStack = [];
return false;
}
}
return true;
},
trackGemPurchase (modalId, data) {
// Track opening of gems modal unless it's been already tracked
// For example the gems button in the menu already tracks the event by itself
if (modalId === 'buy-gems' && data.alreadyTracked !== true) {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Wallet',
});
}
},
resetItemToBuy ($event) {
// @TODO: Do we need this? I think selecting a new item
// overwrites. @negue might know
@@ -434,6 +535,12 @@ export default {
hideLoadingScreen () {
this.loading = false;
},
hideBanner () {
this.bannerHidden = true;
},
resumeDamage () {
this.$store.dispatch('user:sleep');
},
},
};
</script>
@@ -1,68 +1,74 @@
.promo_armoire_background_201803 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -735px;
background-position: -142px -327px;
width: 141px;
height: 441px;
}
.promo_cupid_potions {
.promo_egg_hunt {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -735px;
width: 138px;
height: 441px;
}
.promo_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -441px 0px;
width: 730px;
height: 170px;
}
.promo_hippogriff {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1172px -442px;
width: 105px;
height: 105px;
background-position: -814px 0px;
width: 354px;
height: 147px;
}
.promo_hugabug_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1172px 0px;
background-position: -441px -408px;
width: 141px;
height: 441px;
}
.promo_mystery_201802 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -327px;
background-position: -441px 0px;
width: 372px;
height: 196px;
}
.promo_seasonalshop_broken {
.promo_mystery_201803 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -751px -171px;
width: 198px;
height: 147px;
background-position: -977px -148px;
width: 114px;
height: 90px;
}
.promo_rainbow_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -583px -408px;
width: 141px;
height: 441px;
}
.promo_seasonalshop_spring {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -814px -148px;
width: 162px;
height: 138px;
}
.promo_spring_fling_2018 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -327px;
width: 141px;
height: 588px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1172px -548px;
background-position: -956px -287px;
width: 114px;
height: 87px;
}
.promo_valentines {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -441px -171px;
width: 309px;
height: 147px;
}
.scene_achievement {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -524px;
background-position: -441px -197px;
width: 339px;
height: 210px;
}
.scene_coding {
.scene_podcast {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -373px -327px;
width: 150px;
height: 150px;
background-position: -814px -287px;
width: 141px;
height: 141px;
}
.scene_sweeping {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -814px -429px;
width: 138px;
height: 144px;
}
.scene_tavern {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
@@ -1,12 +1,54 @@
.quest_TEMPLATE_FOR_MISSING_IMAGE {
.quest_atom2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -251px -1519px;
width: 221px;
height: 39px;
background-position: -642px -1519px;
width: 207px;
height: 138px;
}
.quest_atom3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -211px -1519px;
width: 216px;
height: 180px;
}
.quest_axolotl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px 0px;
width: 219px;
height: 219px;
}
.quest_badger {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -232px;
width: 219px;
height: 219px;
}
.quest_basilist {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -191px -1706px;
width: 189px;
height: 141px;
}
.quest_beetle {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1322px -1112px;
width: 204px;
height: 201px;
}
.quest_bunny {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1519px;
width: 210px;
height: 186px;
}
.quest_butterfly {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -220px;
width: 219px;
height: 219px;
}
.quest_cheetah {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -672px;
background-position: 0px -452px;
width: 219px;
height: 219px;
}
@@ -18,7 +60,7 @@
}
.quest_dilatory {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -232px;
background-position: -660px -452px;
width: 219px;
height: 219px;
}
@@ -30,55 +72,55 @@
}
.quest_dilatoryDistress2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -1023px;
background-position: -1757px -573px;
width: 150px;
height: 150px;
}
.quest_dilatoryDistress3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px 0px;
background-position: -880px -440px;
width: 219px;
height: 219px;
}
.quest_dilatory_derby {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px 0px;
background-position: -440px -452px;
width: 219px;
height: 219px;
}
.quest_dustbunnies {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -220px;
background-position: 0px -672px;
width: 219px;
height: 219px;
}
.quest_egg {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -362px;
background-position: -1757px -214px;
width: 165px;
height: 207px;
}
.quest_evilsanta {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -1174px;
background-position: -1757px -1026px;
width: 118px;
height: 131px;
}
.quest_evilsanta2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -452px;
background-position: -660px -672px;
width: 219px;
height: 219px;
}
.quest_falcon {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -452px;
background-position: -880px -672px;
width: 219px;
height: 219px;
}
.quest_ferret {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px 0px;
background-position: -1100px 0px;
width: 219px;
height: 219px;
}
@@ -90,19 +132,19 @@
}
.quest_ghost_stag {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -440px;
background-position: -1100px -440px;
width: 219px;
height: 219px;
}
.quest_goldenknight1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -672px;
background-position: -1100px -660px;
width: 219px;
height: 219px;
}
.quest_goldenknight2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1311px -1332px;
background-position: -1094px -1332px;
width: 250px;
height: 150px;
}
@@ -120,13 +162,13 @@
}
.quest_guineapig {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -672px;
background-position: -660px -892px;
width: 219px;
height: 219px;
}
.quest_harpy {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px 0px;
background-position: -880px -892px;
width: 219px;
height: 219px;
}
@@ -138,103 +180,103 @@
}
.quest_hippo {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -440px;
background-position: -1320px 0px;
width: 219px;
height: 219px;
}
.quest_horse {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -660px;
background-position: -1320px -220px;
width: 219px;
height: 219px;
}
.quest_kraken {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -877px -1332px;
background-position: -443px -1332px;
width: 216px;
height: 177px;
}
.quest_lostMasterclasser1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -892px;
background-position: -1320px -660px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -892px;
background-position: -220px 0px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -892px;
background-position: 0px -1112px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -570px;
background-position: -1757px -875px;
width: 150px;
height: 150px;
}
.quest_mayhemMistiflying2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -892px;
background-position: -440px -1112px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px 0px;
background-position: -660px -1112px;
width: 219px;
height: 219px;
}
.quest_monkey {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -220px;
background-position: -220px -1112px;
width: 219px;
height: 219px;
}
.quest_moon1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -651px;
background-position: -1540px -217px;
width: 216px;
height: 216px;
}
.quest_moon2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px 0px;
background-position: -1320px -880px;
width: 219px;
height: 219px;
}
.quest_moon3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -880px;
background-position: -1320px -440px;
width: 219px;
height: 219px;
}
.quest_moonstone1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1112px;
background-position: -1100px -892px;
width: 219px;
height: 219px;
}
.quest_moonstone2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -1112px;
background-position: -440px -892px;
width: 219px;
height: 219px;
}
.quest_moonstone3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -1112px;
background-position: 0px -892px;
width: 219px;
height: 219px;
}
.quest_nudibranch {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -434px;
background-position: -1540px -868px;
width: 216px;
height: 216px;
}
@@ -246,67 +288,67 @@
}
.quest_owl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -660px;
background-position: -1100px -220px;
width: 219px;
height: 219px;
}
.quest_peacock {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -217px;
background-position: -1540px -434px;
width: 216px;
height: 216px;
}
.quest_penguin {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -178px;
background-position: 0px -1706px;
width: 190px;
height: 183px;
}
.quest_pterodactyl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -440px;
background-position: -440px -672px;
width: 219px;
height: 219px;
}
.quest_rat {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -892px;
background-position: -220px -672px;
width: 219px;
height: 219px;
}
.quest_rock {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -868px;
background-position: -1540px -651px;
width: 216px;
height: 216px;
}
.quest_rooster {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1670px;
background-position: -428px -1519px;
width: 213px;
height: 174px;
}
.quest_sabretooth {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -892px;
background-position: -880px -220px;
width: 219px;
height: 219px;
}
.quest_sheep {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -220px;
background-position: -880px 0px;
width: 219px;
height: 219px;
}
.quest_slime {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -672px;
background-position: -220px -452px;
width: 219px;
height: 219px;
}
.quest_sloth {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -672px;
background-position: -660px 0px;
width: 219px;
height: 219px;
}
@@ -318,37 +360,37 @@
}
.quest_snake {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1322px -1112px;
background-position: -877px -1332px;
width: 216px;
height: 177px;
}
.quest_spider {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1519px;
background-position: -1345px -1332px;
width: 250px;
height: 150px;
}
.quest_stoikalmCalamity1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -721px;
background-position: -1757px -422px;
width: 150px;
height: 150px;
}
.quest_stoikalmCalamity2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -220px;
background-position: -440px -232px;
width: 219px;
height: 219px;
}
.quest_stoikalmCalamity3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -452px;
background-position: -220px -232px;
width: 219px;
height: 219px;
}
.quest_taskwoodsTerror1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px -872px;
background-position: -1757px -724px;
width: 150px;
height: 150px;
}
@@ -360,43 +402,7 @@
}
.quest_taskwoodsTerror3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -452px;
width: 219px;
height: 219px;
}
.quest_treeling {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -443px -1332px;
width: 216px;
height: 177px;
}
.quest_trex {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1932px 0px;
width: 204px;
height: 177px;
}
.quest_trex_undead {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1094px -1332px;
width: 216px;
height: 177px;
}
.quest_triceratops {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -232px;
width: 219px;
height: 219px;
}
.quest_turtle {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -232px;
width: 219px;
height: 219px;
}
.quest_unicorn {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -1112px;
background-position: -220px -892px;
width: 219px;
height: 219px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

After

Width:  |  Height:  |  Size: 426 KiB

Some files were not shown because too many files have changed in this diff Show More