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