mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-10 11:09:46 -05:00
Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c54ce96033 | |||
| 85c4e93763 | |||
| 25e5e78373 | |||
| 06181d0a1a | |||
| d5a8259fdb | |||
| 4d67df4da6 | |||
| ab7459f4f3 | |||
| 469db7c0e2 | |||
| 952e813b30 | |||
| f04d05fee1 | |||
| 6d9aa43c07 | |||
| f527221079 | |||
| d9b852e1ea | |||
| a1207c1d8d | |||
| f4fb90013d | |||
| 73a7c0eebc | |||
| 1819398f41 | |||
| ab14312368 | |||
| 690d3e3fd2 | |||
| 36f9a4918f | |||
| a4b5e27614 | |||
| 0abfe86296 | |||
| e11c777325 | |||
| 63a04f36c9 | |||
| e58af6e3ea | |||
| 6ba28b5757 | |||
| ed607d2bae | |||
| 1f7fc594e5 | |||
| 45d0a4fac2 | |||
| e50bc189aa | |||
| 95f8867bf8 | |||
| 4968b291f7 | |||
| f7b9ca124d | |||
| 4623bcd877 | |||
| 4a368a1128 | |||
| bec8cb01e0 | |||
| f3c041a561 | |||
| c21726ec61 | |||
| df69208caa | |||
| 08d07cdd67 | |||
| a309e48183 | |||
| 70c539cc81 | |||
| 11f136ac89 | |||
| 567d5f74ba | |||
| 338781f57b | |||
| bd07f3cd38 | |||
| 0b735abd44 | |||
| a88cdaf1fc | |||
| 7cae5f1a37 | |||
| e453330535 | |||
| b1e5fcdeaf | |||
| 10e0848a5c | |||
| 64e86bad91 | |||
| 21cf5d2321 | |||
| a6106a801b | |||
| 769405ff34 | |||
| a0803796b2 | |||
| cb418882f3 | |||
| b17a09ac17 | |||
| 88bb4f6a72 | |||
| ae0c440846 | |||
| 2f69f4039e | |||
| 10370ea1dc | |||
| 0d65e5219e | |||
| fed2d3fb19 | |||
| 6ec50ed0c1 | |||
| 6f1a551d76 | |||
| bed97f0610 | |||
| f86f98f4a6 | |||
| 0e442a0076 | |||
| 89f047b15b | |||
| e64bc2e39a | |||
| 3b3fcbdfce | |||
| 7914a959b3 | |||
| 8a27524fa0 | |||
| a64fed97ac | |||
| e937d1722e | |||
| f537e8142f | |||
| e4b13eecd1 | |||
| b5872a9577 | |||
| 48fa78bef2 | |||
| 781256c917 | |||
| dcd680c293 | |||
| ec6f53bb1b | |||
| 9834afee4a | |||
| 9b279563ea | |||
| 347fe69667 | |||
| 552cf70abd | |||
| 01c8ef9382 | |||
| 298a6a743c | |||
| fa17ab7c17 | |||
| 0f339d8d3e | |||
| 99852fcd89 | |||
| ca3d044aa1 | |||
| b338e65dc9 | |||
| 1475c93962 | |||
| ddec458364 | |||
| bfe74a8dcb | |||
| 13d9da404d | |||
| 85e0af0c0e | |||
| a2e5548b1c | |||
| 36dabad2c9 | |||
| 25b9e6f330 | |||
| d0a786554c | |||
| de79e0e3c3 | |||
| 0e74d25ede | |||
| bde8b76da7 | |||
| 4235be4b43 | |||
| 3af7f89d10 | |||
| 396362d27e | |||
| 972efb1878 | |||
| 6c18d19d95 | |||
| 9b8bdb90d8 | |||
| a84ea8b1b7 | |||
| 480a839bc5 | |||
| df9c54fe20 | |||
| 8dbab1a976 | |||
| df6088bc6d | |||
| f0ff3a4eb6 | |||
| 786e4419da | |||
| fff4ea3ad3 | |||
| e18e89bc10 | |||
| 68ea28305d | |||
| 242b3508a1 | |||
| 8c316d939f | |||
| 45eb19e992 | |||
| 3ad0ffcaec | |||
| fef8929dd9 | |||
| 911f894750 | |||
| d4e2f5ac9e | |||
| e43749bed1 | |||
| 8be29e27d0 | |||
| 301668fe22 | |||
| 767f3ebe12 | |||
| 25e6f8e26e | |||
| 1c6608d071 | |||
| d2b160438c | |||
| 37d70f089c | |||
| 04b4912d59 | |||
| b9a6d9ceec | |||
| 2a97915477 | |||
| 29a9deaeb8 | |||
| 0fcc1f2080 | |||
| 9287098e70 | |||
| 1812f63812 | |||
| 3d4107db3e | |||
| 67e750a81c | |||
| 7fe62a731b | |||
| 3c224fe353 | |||
| cb42a31c43 | |||
| b1b1e512f5 |
@@ -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",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ node_modules/**
|
||||
.bower-registry/**
|
||||
website/client-old/**
|
||||
website/client/**
|
||||
website/client/store/**
|
||||
website/views/**
|
||||
website/build/**
|
||||
dist/**
|
||||
|
||||
+1
-3
@@ -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
-4
@@ -1,8 +1,5 @@
|
||||
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
|
||||
RUN yarn global add npm@5
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -11,16 +11,13 @@ ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleu
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
|
||||
# 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
|
||||
RUN yarn global add npm@5
|
||||
# Install global packages
|
||||
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.8 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v4.36.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN gulp build:prod --force
|
||||
|
||||
|
||||
+1
-2
@@ -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;
|
||||
@@ -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, ...
|
||||
@@ -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,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 () {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import each from 'lodash/each';
|
||||
import keys from 'lodash/keys';
|
||||
import content from '../../website/common/script/content/index';
|
||||
const migrationName = 'full-stable.js';
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award users every extant pet and mount
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'profile.name': 'SabreCat',
|
||||
};
|
||||
|
||||
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):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
let set = {
|
||||
migration: migrationName,
|
||||
};
|
||||
|
||||
each(keys(content.pets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.premiumPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.questPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.specialPets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
});
|
||||
each(keys(content.mounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.premiumMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.questMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
each(keys(content.specialMounts), (mount) => {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -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');
|
||||
|
||||
Generated
+922
-1543
File diff suppressed because it is too large
Load Diff
+19
-17
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.31.0",
|
||||
"version": "4.37.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -10,7 +10,7 @@
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"autoprefixer": "^8.1.0",
|
||||
"aws-sdk": "^2.209.0",
|
||||
"aws-sdk": "^2.211.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.1.8",
|
||||
"babel-core": "^6.0.0",
|
||||
@@ -18,7 +18,7 @@
|
||||
"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",
|
||||
@@ -26,7 +26,6 @@
|
||||
"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.2",
|
||||
@@ -34,7 +33,7 @@
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.4",
|
||||
"css-loader": "^0.28.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^2.0.4",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
@@ -51,13 +50,14 @@
|
||||
"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.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.21.0",
|
||||
@@ -92,7 +92,7 @@
|
||||
"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",
|
||||
@@ -113,8 +113,8 @@
|
||||
},
|
||||
"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 .",
|
||||
@@ -141,7 +141,9 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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.2",
|
||||
@@ -149,15 +151,15 @@
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.18.2",
|
||||
"eslint": "^4.19.0",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-friendly-formatter": "^4.0.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-html": "^4.0.2",
|
||||
"eslint-plugin-mocha": "^4.12.1",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"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",
|
||||
@@ -170,16 +172,16 @@
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "^2.0.13",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lcov-result-merger": "^2.0.0",
|
||||
"mocha": "^5.0.4",
|
||||
"monk": "^6.0.5",
|
||||
"nightwatch": "^0.9.20",
|
||||
"puppeteer": "^1.1.1",
|
||||
"puppeteer": "^1.2.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.11.0",
|
||||
"sinon": "^4.4.5",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
|
||||
@@ -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);
|
||||
@@ -224,4 +227,61 @@ describe('GET challenges/user', () => {
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('filters and paging', () => {
|
||||
let user, guild, member;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
guild = group;
|
||||
member = members[0];
|
||||
|
||||
await user.update({balance: 20});
|
||||
|
||||
for (let i = 0; i < 11; i += 1) {
|
||||
await generateChallenge(user, group); // eslint-disable-line
|
||||
}
|
||||
});
|
||||
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const categoryChallenge = await generateChallenge(user, guild, {categories});
|
||||
const challenges = await user.get(`/challenges/user?categories=${categories[0].slug}`);
|
||||
|
||||
expect(challenges[0]._id).to.eql(categoryChallenge._id);
|
||||
expect(challenges.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('does not page challenges if page parameter is absent', async () => {
|
||||
const challenges = await user.get('/challenges/user');
|
||||
|
||||
expect(challenges.length).to.be.above(11);
|
||||
});
|
||||
|
||||
it('paginates challenges', async () => {
|
||||
const challenges = await user.get('/challenges/user?page=0');
|
||||
const challengesPaged = await user.get('/challenges/user?page=1&owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(10);
|
||||
expect(challengesPaged.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('filters by owned', async () => {
|
||||
const challenges = await member.get('/challenges/user?owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,8 +23,8 @@ const BASE_URL = nconf.get('BASE_URL');
|
||||
describe('POST /chat', () => {
|
||||
let user, groupWithChat, member, additionalMember;
|
||||
let testMessage = 'Test Message';
|
||||
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
|
||||
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
|
||||
let testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||
let testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||
let bannedWordErrorMessage = t('bannedWordUsed').split('.');
|
||||
bannedWordErrorMessage[0] += ` (${removePunctuationFromString(testBannedWordMessage.toLowerCase())})`;
|
||||
bannedWordErrorMessage = bannedWordErrorMessage.join('.');
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
@@ -93,7 +93,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -136,6 +136,22 @@ describe('POST /group', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when a user with no chat privileges attempts to create a public guild', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
|
||||
await expect(
|
||||
user.post('/groups', {
|
||||
name: 'Test Public Guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
})
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotCreatePublicGuildWhenMuted'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('private guild', () => {
|
||||
@@ -163,6 +179,17 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a private guild when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
let privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
privacy: groupPrivacy,
|
||||
});
|
||||
|
||||
expect(privateGuild._id).to.exist;
|
||||
});
|
||||
|
||||
it('deducts gems from user and adds them to guild bank', async () => {
|
||||
let privateGuild = await user.post('/groups', {
|
||||
name: groupName,
|
||||
@@ -201,6 +228,16 @@ describe('POST /group', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a party when the user has no chat privileges', async () => {
|
||||
await user.update({ 'flags.chatRevoked': true });
|
||||
let party = await user.post('/groups', {
|
||||
name: partyName,
|
||||
type: partyType,
|
||||
});
|
||||
|
||||
expect(party._id).to.exist;
|
||||
});
|
||||
|
||||
it('does not require gems to create a party', async () => {
|
||||
await user.update({ balance: 0 });
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,6 +24,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
describe('user id invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is not found', async () => {
|
||||
let fakeID = generateUUID();
|
||||
|
||||
@@ -160,6 +173,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
describe('email invites', () => {
|
||||
let testInvite = {name: 'test', email: 'test@habitica.com'};
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
emails: [testInvite],
|
||||
inviter: 'inviter name',
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invite is missing an email', async () => {
|
||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||
emails: [{name: 'test'}],
|
||||
@@ -321,6 +347,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
|
||||
describe('guild invites', () => {
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user is already invited to the group', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
@@ -398,6 +437,19 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when inviter has no chat privileges', async () => {
|
||||
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
|
||||
let userToInvite = await generateUser();
|
||||
await expect(inviterMuted.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('GET /members/:memberId', () => {
|
||||
let memberRes = await user.get(`/members/${member._id}`);
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
+5
-5
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -187,6 +187,35 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
expect(group.chat[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('Ethereal Surge does not recover mp of other mages', async () => {
|
||||
let group = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 4,
|
||||
});
|
||||
|
||||
let promises = [];
|
||||
promises.push(group.groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[0].update({'stats.mp': 0, 'stats.class': 'warrior', 'stats.lvl': 20}));
|
||||
promises.push(group.members[1].update({'stats.mp': 0, 'stats.class': 'wizard', 'stats.lvl': 20}));
|
||||
promises.push(group.members[2].update({'stats.mp': 0, 'stats.class': 'rogue', 'stats.lvl': 20}));
|
||||
promises.push(group.members[3].update({'stats.mp': 0, 'stats.class': 'healer', 'stats.lvl': 20}));
|
||||
await Promise.all(promises);
|
||||
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
@@ -258,11 +287,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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -1403,8 +1403,45 @@ describe('Group Model', () => {
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(questLeader._id).exec(),
|
||||
User.findById(participatingMember._id).exec(),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.lostMasterclasser).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
// Disable test, it fails on TravisCI, but only there
|
||||
xit('gives out super awesome Masterclasser achievement when quests done out of order', async () => {
|
||||
quest = questScrolls.lostMasterclasser1;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
questLeader.achievements.quests = {
|
||||
mayhemMistiflying1: 1,
|
||||
mayhemMistiflying2: 1,
|
||||
mayhemMistiflying3: 1,
|
||||
stoikalmCalamity1: 1,
|
||||
stoikalmCalamity2: 1,
|
||||
stoikalmCalamity3: 1,
|
||||
taskwoodsTerror1: 1,
|
||||
taskwoodsTerror2: 1,
|
||||
taskwoodsTerror3: 1,
|
||||
dilatoryDistress1: 1,
|
||||
dilatoryDistress2: 1,
|
||||
dilatoryDistress3: 1,
|
||||
lostMasterclasser2: 1,
|
||||
lostMasterclasser3: 1,
|
||||
lostMasterclasser4: 1,
|
||||
};
|
||||
await questLeader.save();
|
||||
await party.finishQuest(quest);
|
||||
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id).exec(),
|
||||
User.findById(participatingMember._id).exec(),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.lostMasterclasser).to.eql(true);
|
||||
|
||||
@@ -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',
|
||||
notes: '',
|
||||
checklist: [],
|
||||
},
|
||||
{
|
||||
text: 'Hello world 2',
|
||||
notes: '',
|
||||
checklist: [],
|
||||
},
|
||||
{
|
||||
text: 'Generic Task Title',
|
||||
notes: '',
|
||||
checklist: [
|
||||
{ text: 'Check 1' },
|
||||
{ text: 'Check 2' },
|
||||
{ text: 'Check 3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Hello world 3',
|
||||
notes: '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,63 @@
|
||||
import { hasClass } from 'client/store/getters/members';
|
||||
|
||||
describe('hasClass getter', () => {
|
||||
it('returns false if level < 10', () => {
|
||||
const member = {
|
||||
stats: {
|
||||
lvl: 5,
|
||||
},
|
||||
preferences: {
|
||||
disableClasses: false,
|
||||
},
|
||||
flags: {
|
||||
classSelected: true,
|
||||
},
|
||||
};
|
||||
expect(hasClass()(member)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false if member has disabled classes', () => {
|
||||
const member = {
|
||||
stats: {
|
||||
lvl: 10,
|
||||
},
|
||||
preferences: {
|
||||
disableClasses: true,
|
||||
},
|
||||
flags: {
|
||||
classSelected: true,
|
||||
},
|
||||
};
|
||||
expect(hasClass()(member)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false if member has not yet selected a class', () => {
|
||||
const member = {
|
||||
stats: {
|
||||
lvl: 10,
|
||||
},
|
||||
preferences: {
|
||||
disableClasses: false,
|
||||
},
|
||||
flags: {
|
||||
classSelected: false,
|
||||
},
|
||||
};
|
||||
expect(hasClass()(member)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true when all conditions are met', () => {
|
||||
const member = {
|
||||
stats: {
|
||||
lvl: 10,
|
||||
},
|
||||
preferences: {
|
||||
disableClasses: false,
|
||||
},
|
||||
flags: {
|
||||
classSelected: true,
|
||||
},
|
||||
};
|
||||
expect(hasClass()(member)).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -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,7 +4,7 @@ import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import count from '../../../../website/common/script/count';
|
||||
import buyArmoire from '../../../../website/common/script/ops/buy/buyArmoire';
|
||||
import {BuyArmoireOperation} from '../../../../website/common/script/ops/buy/buyArmoire';
|
||||
import randomVal from '../../../../website/common/script/libs/randomVal';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import {
|
||||
@@ -33,6 +33,12 @@ describe('shared.ops.buyArmoire', () => {
|
||||
let YIELD_EXP = 0.9;
|
||||
let analytics = {track () {}};
|
||||
|
||||
function buyArmoire (_user, _req, _analytics) {
|
||||
const buyOp = new BuyArmoireOperation(_user, _req, _analytics);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import buyHealthPotion from '../../../../website/common/script/ops/buy/buyHealthPotion';
|
||||
import { BuyHealthPotionOperation } from '../../../../website/common/script/ops/buy/buyHealthPotion';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
@@ -12,6 +12,12 @@ describe('shared.ops.buyHealthPotion', () => {
|
||||
let user;
|
||||
let analytics = {track () {}};
|
||||
|
||||
function buyHealthPotion (_user, _req, _analytics) {
|
||||
const buyOp = new BuyHealthPotionOperation(_user, _req, _analytics);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
items: {
|
||||
|
||||
@@ -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 () {}};
|
||||
|
||||
@@ -111,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;
|
||||
@@ -36,6 +36,43 @@ describe('shared.ops.buyQuest', () => {
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('buys a Quest scroll with the right quantity if a string is passed for quantity', () => {
|
||||
user.stats.gp = 1000;
|
||||
buyQuest(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
}, analytics);
|
||||
buyQuest(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
quantity: '3',
|
||||
}, analytics);
|
||||
|
||||
expect(user.items.quests).to.eql({
|
||||
dilatoryDistress1: 4,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not buy a Quest scroll when an invalid quantity is passed', (done) => {
|
||||
user.stats.gp = 1000;
|
||||
try {
|
||||
buyQuest(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
quantity: 'a',
|
||||
}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
expect(user.items.quests).to.eql({});
|
||||
expect(user.stats.gp).to.equal(1000);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not buy Quests without enough Gold', (done) => {
|
||||
user.stats.gp = 1;
|
||||
try {
|
||||
|
||||
@@ -87,6 +87,19 @@ describe('shared.ops.purchase', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('prevents user from buying an invalid quantity', (done) => {
|
||||
user.stats.gp = goldPoints;
|
||||
user.purchased.plan.gemsBought = gemsBought;
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when unknown type is provided', (done) => {
|
||||
try {
|
||||
purchase(user, {params: {type: 'randomType', key: 'gem'}});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 +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';
|
||||
@@ -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');
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
--timeout 8000
|
||||
--check-leaks
|
||||
--globals io
|
||||
-r babel-polyfill
|
||||
--require babel-register
|
||||
--require ./test/helpers/globals.helper
|
||||
--exit
|
||||
|
||||
@@ -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
|
||||
|
||||
+99
-15
@@ -9,15 +9,22 @@ div
|
||||
h2 {{$t('tipTitle', {tipNumber: currentTipNumber})}}
|
||||
p {{currentTip}}
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
amazon-payments-modal
|
||||
amazon-payments-modal(v-if='!isStaticPage')
|
||||
snackbars
|
||||
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
|
||||
audio#sound(autoplay, ref="sound")
|
||||
source#oggSource(type="audio/ogg", :src="sound.oggSource")
|
||||
source#mp3Source(type="audio/mp3", :src="sound.mp3Source")
|
||||
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,21 +102,76 @@ 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;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: 1 !important;
|
||||
background-color: rgba(67, 40, 116, 0.9) !important;
|
||||
opacity: .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,13 +242,17 @@ export default {
|
||||
|
||||
return this.$t(`tip${tipNumber}`);
|
||||
},
|
||||
showRestingBanner () {
|
||||
return !this.bannerHidden && this.user.preferences.sleep;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$root.$on('playSound', (sound) => {
|
||||
let theme = this.user.preferences.sound;
|
||||
|
||||
if (!theme || theme === 'off')
|
||||
if (!theme || theme === 'off') {
|
||||
return;
|
||||
}
|
||||
|
||||
let file = `/static/audio/${theme}/${sound}`;
|
||||
this.sound = {
|
||||
@@ -426,7 +500,7 @@ export default {
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
if (item.purchaseType === 'card')
|
||||
if (['card', 'debuffPotion'].includes(item.purchaseType))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -447,6 +521,10 @@ export default {
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
}
|
||||
|
||||
if (item.purchaseType === 'debuffPotion') {
|
||||
this.castStart(item, this.user);
|
||||
}
|
||||
},
|
||||
async memberSelected (member) {
|
||||
await this.castStart(this.selectedSpellToBuy, member);
|
||||
@@ -462,6 +540,12 @@ export default {
|
||||
hideLoadingScreen () {
|
||||
this.loading = false;
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,84 +1,66 @@
|
||||
.promo_armoire_background_201803 {
|
||||
.promo_armoire_background_201804 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -735px;
|
||||
background-position: -142px -244px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_cupid_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -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 -587px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_hugabug_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1172px 0px;
|
||||
background-position: -532px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_mystery_201802 {
|
||||
.promo_mystery_201803 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -327px;
|
||||
width: 372px;
|
||||
height: 196px;
|
||||
background-position: -915px -296px;
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -735px;
|
||||
background-position: -284px -244px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seasonalshop_broken {
|
||||
.promo_seasonalshop_spring {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -751px -171px;
|
||||
width: 198px;
|
||||
background-position: -674px -492px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_shimmer_pastel {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -674px -148px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -674px 0px;
|
||||
width: 360px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_fling_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -244px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -950px -171px;
|
||||
background-position: -915px -387px;
|
||||
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;
|
||||
width: 339px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_coding {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -373px -327px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_sweeping {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1172px -442px;
|
||||
width: 138px;
|
||||
height: 144px;
|
||||
}
|
||||
.scene_tavern {
|
||||
.scene_positivity {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 440px;
|
||||
height: 326px;
|
||||
width: 531px;
|
||||
height: 243px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -674px -296px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,72 @@
|
||||
.quest_TEMPLATE_FOR_MISSING_IMAGE {
|
||||
.phobia_dysheartener {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -251px -1519px;
|
||||
width: 221px;
|
||||
height: 39px;
|
||||
background-position: 0px -1510px;
|
||||
width: 201px;
|
||||
height: 195px;
|
||||
}
|
||||
.quest_armadillo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1320px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_atom1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1376px -1332px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_atom2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -633px -1510px;
|
||||
width: 207px;
|
||||
height: 138px;
|
||||
}
|
||||
.quest_atom3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -202px -1510px;
|
||||
width: 216px;
|
||||
height: 180px;
|
||||
}
|
||||
.quest_axolotl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_badger {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -440px -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: -1540px -1079px;
|
||||
width: 204px;
|
||||
height: 201px;
|
||||
}
|
||||
.quest_bunny {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1322px -1112px;
|
||||
width: 210px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_butterfly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_cheetah {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -440px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -18,91 +78,91 @@
|
||||
}
|
||||
.quest_dilatory {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -232px;
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatoryDistress1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1540px -1085px;
|
||||
background-position: -1540px -868px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
.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: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatory_derby {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -440px 0px;
|
||||
background-position: -880px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -660px -220px;
|
||||
background-position: -440px -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 -875px;
|
||||
width: 118px;
|
||||
height: 131px;
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -440px -452px;
|
||||
background-position: -1100px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -660px -452px;
|
||||
background-position: -1100px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: -1100px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px -1112px;
|
||||
background-position: -660px -1112px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px -440px;
|
||||
background-position: -220px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: -220px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1311px -1332px;
|
||||
background-position: -874px -1332px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
@@ -114,115 +174,115 @@
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -660px -1332px;
|
||||
background-position: -657px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px -672px;
|
||||
background-position: -1100px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -1320px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_hedgehog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -1332px;
|
||||
background-position: -1102px -1112px;
|
||||
width: 219px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: -660px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -1320px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -877px -1332px;
|
||||
background-position: -223px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: 0px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: -220px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: -1320px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1932px -570px;
|
||||
background-position: -1757px -422px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -1320px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -440px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1320px -220px;
|
||||
background-position: -1100px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1540px -651px;
|
||||
background-position: -1540px -434px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px 0px;
|
||||
background-position: 0px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -234,19 +294,19 @@
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1540px -434px;
|
||||
background-position: -1540px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_octopus {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -1332px;
|
||||
background-position: 0px -1332px;
|
||||
width: 222px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -880px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -258,145 +318,85 @@
|
||||
}
|
||||
.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: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -660px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1540px -868px;
|
||||
background-position: -1540px 0px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_rooster {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -1670px;
|
||||
background-position: -419px -1510px;
|
||||
width: 213px;
|
||||
height: 174px;
|
||||
}
|
||||
.quest_sabretooth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sheep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: 0px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_slime {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sloth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -672px;
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_snail {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1102px -1112px;
|
||||
background-position: -882px -1112px;
|
||||
width: 219px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_snake {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1322px -1112px;
|
||||
background-position: -440px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_spider {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -1519px;
|
||||
background-position: -1125px -1332px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_squirrel {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: 0px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1932px -721px;
|
||||
background-position: -1757px -724px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_stoikalmCalamity2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_taskwoodsTerror1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1932px -872px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_taskwoodsTerror2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
|
||||
background-position: -1540px 0px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.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;
|
||||
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
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.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user