Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324b16b8cd | ||
|
|
8c005aa05a | ||
|
|
71926cff51 | ||
|
|
e971f49c85 | ||
|
|
01657f573d | ||
|
|
95613dcfb8 | ||
|
|
0379f341b9 | ||
|
|
8498ab9fe2 | ||
|
|
95de11cf7a | ||
|
|
5bb336cedc | ||
|
|
790614a9d4 | ||
|
|
977968df19 | ||
|
|
3df056105c | ||
|
|
0eb7e10e7f | ||
|
|
0908fa2a48 | ||
|
|
71904d106f | ||
|
|
56040eebaf | ||
|
|
2048f3ef76 | ||
|
|
e9163a1bb2 | ||
|
|
54fd910db2 | ||
|
|
3c0cd7067a | ||
|
|
5a473eb0e0 | ||
|
|
e6fcdf62ef | ||
|
|
09e748bc92 | ||
|
|
9151690f86 | ||
|
|
c125ac4d93 | ||
|
|
aa61c1fe06 | ||
|
|
c6a7ee3f56 | ||
|
|
411213f381 | ||
|
|
b2dabcaf98 | ||
|
|
2f3927fcaa | ||
|
|
f84562446d | ||
|
|
0a840ca952 | ||
|
|
c0837e3b3c | ||
|
|
95c1893b0c | ||
|
|
d8ea3bd23a | ||
|
|
0ce11b82df | ||
|
|
b4c47b4afd | ||
|
|
4777850601 | ||
|
|
9a3e208c9b | ||
|
|
792d5998b0 | ||
|
|
53515fd3f4 | ||
|
|
946147a4aa | ||
|
|
983ea7c6c7 | ||
|
|
1135ab946e | ||
|
|
5a15c73fca | ||
|
|
3f99c14a37 | ||
|
|
9e515d96c3 | ||
|
|
40e0017b17 | ||
|
|
251563690e | ||
|
|
83070e211d | ||
|
|
3be075ad43 | ||
|
|
043e0fb819 | ||
|
|
9d473cc92e | ||
|
|
99dec2eb0c | ||
|
|
cbb04d221d | ||
|
|
1b93f20451 | ||
|
|
0f374abd27 | ||
|
|
a4a8ac6c5e | ||
|
|
3854c6f62f | ||
|
|
82cd53f8cb | ||
|
|
6f7cd96e9f | ||
|
|
821fc1d9c0 | ||
|
|
e4eca4b767 | ||
|
|
bd3de3c48c | ||
|
|
9f09a0396b | ||
|
|
f16e0bd044 | ||
|
|
c91de90fff | ||
|
|
380fb0abf4 | ||
|
|
43b607aedd | ||
|
|
4c832ad36c | ||
|
|
76ae41875d | ||
|
|
7a5a856ac6 | ||
|
|
0bfd709116 | ||
|
|
d7f6c3bc1b | ||
|
|
3eb164adcc | ||
|
|
7bd2cbcf04 | ||
|
|
087498760a | ||
|
|
0e26fcce98 | ||
|
|
8d0060d511 | ||
|
|
0af94b2b44 | ||
|
|
a7b5b6e20e | ||
|
|
5f93aad925 | ||
|
|
4289becccc | ||
|
|
eeddd3f366 | ||
|
|
6ce2caecf9 | ||
|
|
6b933914ef | ||
|
|
060e68ef95 | ||
|
|
a486ded6dd | ||
|
|
7884f4ce9a | ||
|
|
1c82fa012d | ||
|
|
924723bce6 | ||
|
|
b696dde6ba | ||
|
|
e8d0557cb6 | ||
|
|
cdb6acd4a0 | ||
|
|
128bd4b9cd | ||
|
|
fc5f6a31ee | ||
|
|
1f0fa500bb | ||
|
|
36276d5d3f | ||
|
|
5c2c87f523 | ||
|
|
c4f2dafc95 | ||
|
|
f09b65e108 | ||
|
|
a0f42b0e3e | ||
|
|
e10655a5b4 | ||
|
|
d519940931 | ||
|
|
58a43f51d8 | ||
|
|
d25a5fcd57 | ||
|
|
4085d7a5bb | ||
|
|
6c4e1a326f | ||
|
|
9cf8c0a824 | ||
|
|
e1f9643ffd | ||
|
|
1d5c1d5a5f | ||
|
|
df7c0a005c | ||
|
|
c60481ab34 | ||
|
|
13818b7634 | ||
|
|
b2e834c74c | ||
|
|
4a9cfe8ce5 | ||
|
|
2419b219ba | ||
|
|
0b8ce63c76 | ||
|
|
95e541ae75 | ||
|
|
85c6c19235 | ||
|
|
07e4b2c463 | ||
|
|
7b2081ab03 | ||
|
|
e749e42665 | ||
|
|
0b82722d27 | ||
|
|
f35ef3a046 | ||
|
|
5656b9c6ca | ||
|
|
1e3d7acf06 | ||
|
|
b7e391e074 | ||
|
|
b2368e7804 | ||
|
|
85880d6bb5 | ||
|
|
352b8143f3 | ||
|
|
7fe3870297 | ||
|
|
aff32b0e71 | ||
|
|
4ff56b17e7 | ||
|
|
03283d2e52 | ||
|
|
07909ac93a | ||
|
|
506b155cfa | ||
|
|
80bd41928c | ||
|
|
be3bd25f00 | ||
|
|
3e365f2b4e | ||
|
|
e1b08e3a20 | ||
|
|
01281b6414 | ||
|
|
0b65ac6c4f | ||
|
|
0d12686a10 | ||
|
|
8c4d1a67ac | ||
|
|
e58fbcc34c | ||
|
|
1a66a680dc | ||
|
|
fc08b753cd | ||
|
|
7a73f5bb83 | ||
|
|
6ce1f6f32e | ||
|
|
50278db1d6 | ||
|
|
1195560b0c | ||
|
|
33c639e28b | ||
|
|
c2b106564f | ||
|
|
fe636b9bd2 | ||
|
|
7835fe1deb | ||
|
|
8fb0d0899d | ||
|
|
abb8dc4dc1 | ||
|
|
910a76db68 | ||
|
|
2f792d51f8 | ||
|
|
6cc925b535 | ||
|
|
87d86ee632 | ||
|
|
b387d77128 | ||
|
|
e644ae83fd | ||
|
|
ae21680a5f | ||
|
|
c1767eca1b | ||
|
|
d20cd1bbf1 | ||
|
|
3e45f5af41 | ||
|
|
23cc2b9d21 | ||
|
|
58c4fcd506 | ||
|
|
2878abc130 | ||
|
|
a8cb6e3409 | ||
|
|
2caa540006 | ||
|
|
a2261e3591 | ||
|
|
90d498ff96 | ||
|
|
928327e02a | ||
|
|
f454700722 | ||
|
|
83bce24e1f | ||
|
|
e7979a99e6 | ||
|
|
5c648af2ea | ||
|
|
2ad4bee816 | ||
|
|
e0291cf432 | ||
|
|
bac9121153 | ||
|
|
d178928c45 | ||
|
|
9a6aa5f443 | ||
|
|
e277a088ee | ||
|
|
e083df64e4 | ||
|
|
dae37c17d6 | ||
|
|
1d81916674 | ||
|
|
2a225c2376 | ||
|
|
bd9d9d31c3 | ||
|
|
f1b5ce9c66 | ||
|
|
c08b25101b | ||
|
|
9fd0e27c66 | ||
|
|
994082a1d8 | ||
|
|
0e6e7adb06 | ||
|
|
8486b9631f | ||
|
|
3f04c8abf5 | ||
|
|
89e1c69728 | ||
|
|
5e935230a4 | ||
|
|
9ad50be6ca | ||
|
|
1ff3f3d4e7 | ||
|
|
0b35aefdc9 | ||
|
|
0d374d817c | ||
|
|
034058301d | ||
|
|
ae2d50c5b8 | ||
|
|
fff16a86a5 | ||
|
|
c7309ae179 | ||
|
|
ef42fba049 | ||
|
|
d75f926136 | ||
|
|
78612a91dd | ||
|
|
8bcd93075b | ||
|
|
f318afb8cf | ||
|
|
b954379f38 | ||
|
|
e5c060a80b | ||
|
|
6668aae89b | ||
|
|
9b62804a5c | ||
|
|
d21e29462c | ||
|
|
6bccd2a866 | ||
|
|
caea222330 | ||
|
|
8ecbdc1448 | ||
|
|
4004887ddd | ||
|
|
ef4d761e0c | ||
|
|
e956bbdf79 | ||
|
|
446154b97f | ||
|
|
f137342d88 | ||
|
|
94db493974 | ||
|
|
ee5c761680 | ||
|
|
ee09c76c08 | ||
|
|
c478748436 | ||
|
|
3d99a64e96 | ||
|
|
76f9204417 | ||
|
|
fad59b9a8d | ||
|
|
f218a432ec | ||
|
|
6b5173ecbf | ||
|
|
53d8d2fc6a | ||
|
|
fd13771088 | ||
|
|
c9d725ec20 | ||
|
|
302bc899d7 | ||
|
|
8048cf9a97 | ||
|
|
20548daccf |
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import { sendTxn } from '../../../website/server/libs/email';
|
||||
import { sendTxn } from '../../website/server/libs/email';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
|
||||
@@ -1,67 +1,24 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'full-stable';
|
||||
import each from 'lodash/each';
|
||||
import keys from 'lodash/keys';
|
||||
import content from '../../website/common/script/content/index';
|
||||
const migrationName = 'full-stable.js';
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
/*
|
||||
* Award users every extant pet and mount
|
||||
*/
|
||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
let monk = require('monk');
|
||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
'profile.name': 'SabreCat',
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
let set = {
|
||||
migration: migrationName,
|
||||
};
|
||||
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
each(keys(content.pets), (pet) => {
|
||||
set[`items.pets.${pet}`] = 5;
|
||||
@@ -88,30 +45,40 @@ function updateUser (user) {
|
||||
set[`items.mounts.${mount}`] = true;
|
||||
});
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.local.username': 'olson22',
|
||||
};
|
||||
|
||||
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);
|
||||
const fields = {
|
||||
_id: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201902';
|
||||
const MYSTERY_ITEMS = ['eyewear_mystery_201902', 'shield_mystery_201902'];
|
||||
const MIGRATION_NAME = 'mystery_items_201904';
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201904', 'head_mystery_201904'];
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||
|
||||
|
||||
73
migrations/users/pi-day.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190314_pi_day';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const inc = {
|
||||
'items.food.Pie_Skeleton': 1,
|
||||
'items.food.Pie_Base': 1,
|
||||
'items.food.Pie_CottonCandyBlue': 1,
|
||||
'items.food.Pie_CottonCandyPink': 1,
|
||||
'items.food.Pie_Shade': 1,
|
||||
'items.food.Pie_White': 1,
|
||||
'items.food.Pie_Golden': 1,
|
||||
'items.food.Pie_Zombie': 1,
|
||||
'items.food.Pie_Desert': 1,
|
||||
'items.food.Pie_Red': 1,
|
||||
};
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.head_special_piDay'] = false;
|
||||
set['items.gear.owned.shield_special_piDay'] = false;
|
||||
const push = [
|
||||
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
|
||||
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
|
||||
];
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
1925
package-lock.json
generated
54
package.json
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.86.1",
|
||||
"version": "4.96.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/trace-agent": "^3.5.2",
|
||||
"@google-cloud/trace-agent": "^3.6.0",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"amplitude-js": "^4.6.0-beta.2",
|
||||
"amplitude-js": "^5.0.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.400.0",
|
||||
"autoprefixer": "^9.4.0",
|
||||
"aws-sdk": "^2.432.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
@@ -28,16 +28,16 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^3.0.1",
|
||||
"bcrypt": "^3.0.5",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.13",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"bootstrap-vue": "^2.0.0-rc.18",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.3.3",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^4.3.1",
|
||||
"csv-stringify": "^5.1.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
@@ -52,10 +52,10 @@
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"hellojs": "^1.18.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.10.2",
|
||||
"image-size": "^0.7.0",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
@@ -64,13 +64,13 @@
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.4.11",
|
||||
"mongoose": "^5.4.19",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^5.0.0",
|
||||
"ora": "^3.0.0",
|
||||
"nodemailer": "^6.0.0",
|
||||
"ora": "^3.2.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
@@ -85,12 +85,12 @@
|
||||
"sass-loader": "^7.0.3",
|
||||
"shelljs": "^0.8.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"smartbanner.js": "^1.11.0",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^4.0.0",
|
||||
"superagent": "^5.0.2",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo": "^1.2.0",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"update": "^0.7.4",
|
||||
@@ -100,13 +100,13 @@
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.6.4",
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.6.4",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuedraggable": "^2.20.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.1.3",
|
||||
@@ -154,7 +154,7 @@
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.40.0",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.1",
|
||||
"coveralls": "^3.0.3",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
@@ -166,7 +166,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.19.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^3.1.3",
|
||||
"karma": "^4.0.1",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
@@ -181,11 +181,11 @@
|
||||
"lcov-result-merger": "^3.0.0",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.5.0",
|
||||
"nightwatch": "^1.0.16",
|
||||
"puppeteer": "^1.14.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^6.3.5",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
|
||||
@@ -53,7 +53,7 @@ async function _deleteHabiticaData (user, email) {
|
||||
|
||||
if (response) {
|
||||
console.log(`${response.status} ${response.statusText}`);
|
||||
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||
if (response.status === 200) console.log(`${user._id} (${email}) removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -335,14 +335,13 @@ describe('analyticsService', () => {
|
||||
let data, itemSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
Visitor.prototype.event.yields();
|
||||
|
||||
itemSpy = sandbox.stub().returnsThis();
|
||||
|
||||
Visitor.prototype.event.returns({
|
||||
send: sandbox.stub(),
|
||||
});
|
||||
Visitor.prototype.transaction.returns({
|
||||
item: itemSpy,
|
||||
send: sandbox.stub().returnsThis(),
|
||||
send: sandbox.stub().yields(),
|
||||
});
|
||||
|
||||
data = {
|
||||
|
||||
113
test/api/unit/libs/items/utils.test.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
validateItemPath,
|
||||
getDefaultOwnedGear,
|
||||
castItemVal,
|
||||
} from '../../../../../website/server/libs/items/utils';
|
||||
|
||||
describe('Items Utils', () => {
|
||||
describe('getDefaultOwnedGear', () => {
|
||||
it('clones the result object', () => {
|
||||
const res1 = getDefaultOwnedGear();
|
||||
res1.extraProperty = true;
|
||||
|
||||
const res2 = getDefaultOwnedGear();
|
||||
expect(res2).not.to.have.property('extraProperty');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateItemPath', () => {
|
||||
it('returns false if not an item path', () => {
|
||||
expect(validateItemPath('notitems.gear.owned.item')).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true if a valid schema path', () => {
|
||||
expect(validateItemPath('items.gear.equipped.weapon')).to.equal(true);
|
||||
expect(validateItemPath('items.currentPet')).to.equal(true);
|
||||
expect(validateItemPath('items.special.snowball')).to.equal(true);
|
||||
});
|
||||
|
||||
it('works with owned gear paths', () => {
|
||||
expect(validateItemPath('items.gear.owned.head_armoire_crownOfHearts')).to.equal(true);
|
||||
expect(validateItemPath('items.gear.owned.head_invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with pets paths', () => {
|
||||
expect(validateItemPath('items.pets.Wolf-CottonCandyPink')).to.equal(true);
|
||||
expect(validateItemPath('items.pets.Wolf-Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with eggs paths', () => {
|
||||
expect(validateItemPath('items.eggs.LionCub')).to.equal(true);
|
||||
expect(validateItemPath('items.eggs.Armadillo')).to.equal(true);
|
||||
expect(validateItemPath('items.eggs.NotAnArmadillo')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with hatching potions paths', () => {
|
||||
expect(validateItemPath('items.hatchingPotions.Base')).to.equal(true);
|
||||
expect(validateItemPath('items.hatchingPotions.StarryNight')).to.equal(true);
|
||||
expect(validateItemPath('items.hatchingPotions.Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with food paths', () => {
|
||||
expect(validateItemPath('items.food.Cake_Base')).to.equal(true);
|
||||
expect(validateItemPath('items.food.Cake_Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with mounts paths', () => {
|
||||
expect(validateItemPath('items.mounts.Cactus-Base')).to.equal(true);
|
||||
expect(validateItemPath('items.mounts.Aether-Invisible')).to.equal(true);
|
||||
expect(validateItemPath('items.mounts.Aether-Invalid')).to.equal(false);
|
||||
});
|
||||
|
||||
it('works with quests paths', () => {
|
||||
expect(validateItemPath('items.quests.atom3')).to.equal(true);
|
||||
expect(validateItemPath('items.quests.invalid')).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('castItemVal', () => {
|
||||
it('returns the item val untouched if not an item path', () => {
|
||||
expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string');
|
||||
});
|
||||
|
||||
it('returns the item val untouched if an unsupported path', () => {
|
||||
expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string');
|
||||
expect(castItemVal('items.currentPet', 'a string')).to.equal('a string');
|
||||
expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string');
|
||||
});
|
||||
|
||||
it('converts values for pets paths to numbers', () => {
|
||||
expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5);
|
||||
expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for eggs paths to numbers', () => {
|
||||
expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5);
|
||||
expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5);
|
||||
expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for hatching potions paths to numbers', () => {
|
||||
expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5);
|
||||
expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for food paths to numbers', () => {
|
||||
expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for mounts paths to numbers', () => {
|
||||
expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5);
|
||||
expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for quests paths to numbers', () => {
|
||||
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
|
||||
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -56,11 +56,11 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
tasksOrder: 'new order',
|
||||
official: true,
|
||||
shortName: 'new short name',
|
||||
leader: member._id,
|
||||
|
||||
// applied
|
||||
name: 'New Challenge Name',
|
||||
description: 'New challenge description.',
|
||||
leader: member._id,
|
||||
});
|
||||
|
||||
expect(res.prize).to.equal(0);
|
||||
@@ -76,12 +76,12 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
expect(res.shortName).not.to.equal('new short name');
|
||||
|
||||
expect(res.leader).to.eql({
|
||||
_id: member._id,
|
||||
id: member._id,
|
||||
profile: {name: member.profile.name},
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
auth: {
|
||||
local: {
|
||||
username: member.auth.local.username,
|
||||
username: user.auth.local.username,
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
|
||||
@@ -67,4 +67,10 @@ describe('GET /heroes/:heroId', () => {
|
||||
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns correct hero using search with difference case', async () => {
|
||||
await generateUser({}, { username: 'TestUpperCaseName123' });
|
||||
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
|
||||
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,8 +27,6 @@ describe('GET /inbox/messages', () => {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
});
|
||||
|
||||
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
|
||||
@@ -45,4 +43,27 @@ describe('GET /inbox/messages', () => {
|
||||
expect(messages[2].text).to.equal('second');
|
||||
expect(messages[3].text).to.equal('first');
|
||||
});
|
||||
|
||||
it('returns four messages when using page-query ', async () => {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const messages = await user.get('/inbox/messages?page=1');
|
||||
|
||||
expect(messages.length).to.equal(4);
|
||||
});
|
||||
|
||||
it('returns only the messages of one conversation', async () => {
|
||||
const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`);
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,10 +13,10 @@ describe('payments - stripe - #checkout', () => {
|
||||
});
|
||||
|
||||
it('verifies credentials', async () => {
|
||||
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({
|
||||
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.include({
|
||||
code: 401,
|
||||
error: 'Error',
|
||||
message: 'Invalid API Key provided: ****************************1111',
|
||||
message: 'Invalid API Key provided: aaaabbbb********************1111',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
|
||||
describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
let questingGroup;
|
||||
@@ -99,6 +100,10 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
it('cancels a quest', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
// partyMembers[1] hasn't accepted the invitation, because if he accepts, invitation phase ends.
|
||||
// The cancel command can be done only in the invitation phase.
|
||||
|
||||
let stub = sandbox.spy(Group.prototype, 'sendChat');
|
||||
|
||||
let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`);
|
||||
|
||||
@@ -135,5 +140,9 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
||||
},
|
||||
members: {},
|
||||
});
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/cancelled the party quest Wail of the Whale.`/);
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,6 +63,38 @@ describe('Groups DELETE /tasks/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes deleted taskʾs approval pending notifications from managers', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
await user.put(`/tasks/${task._id}/`, {
|
||||
requiresApproval: true,
|
||||
});
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
await member2.sync();
|
||||
expect(user.notifications.length).to.equal(2);
|
||||
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
expect(member2.notifications.length).to.equal(2);
|
||||
expect(member2.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
|
||||
await member2.del(`/tasks/${task._id}`);
|
||||
|
||||
await user.sync();
|
||||
await member2.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(member2.notifications.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('unlinks assigned user', async () => {
|
||||
await user.del(`/tasks/${task._id}`);
|
||||
|
||||
|
||||
@@ -53,18 +53,29 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
it('approves an assigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications.length).to.equal(3);
|
||||
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||
@@ -77,18 +88,28 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications.length).to.equal(3);
|
||||
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(member2._id);
|
||||
@@ -132,6 +153,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -141,6 +172,17 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents approving a task if it is not waiting for approval', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalWasNotRequested'),
|
||||
});
|
||||
});
|
||||
|
||||
it('completes master task when single-completion task is approved', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
@@ -151,6 +193,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
@@ -172,6 +224,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
@@ -193,6 +255,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||
@@ -214,6 +286,25 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
let member2SyncedTask = find(member2Tasks, findAssignedTask);
|
||||
await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('marks as task as needing more work', async () => {
|
||||
it('marks a task as needing more work', async () => {
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
@@ -77,7 +77,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
// Check that the notification is correct
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
@@ -167,6 +167,17 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
|
||||
@@ -129,6 +129,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
@@ -113,6 +113,17 @@ describe('POST /tasks/:taskId/assign/:memberId', () => {
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
it('sends a notification to assigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member.sync();
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
|
||||
expect(member.notifications.length).to.equal(1);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_ASSIGNED');
|
||||
expect(member.notifications[0].taskId).to.equal(groupTask._id);
|
||||
});
|
||||
|
||||
it('assigns a task to multiple users', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||
|
||||
@@ -86,6 +86,13 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('removes task assignment notification from unassigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
await member.sync();
|
||||
expect(member.notifications.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('unassigns a user and only that user from a task', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||
|
||||
|
||||
@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
it('user uses social authentication and has no password', async () => {
|
||||
await user.unset({
|
||||
'auth.local.hashed_password': 1,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.hashed_password).to.be.undefined;
|
||||
|
||||
await expect(api.post(endpoint, {
|
||||
username: user.auth.local.username,
|
||||
password: 'any-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidLoginCredentialsLong'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
44
test/api/v4/inbox/GET-inbox-conversations.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('GET /inbox/conversations', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
let thirdUser;
|
||||
|
||||
before(async () => {
|
||||
[user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: thirdUser.id,
|
||||
message: 'third',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
// message to yourself
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fifth',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the conversations', async () => {
|
||||
const result = await user.get('/inbox/conversations');
|
||||
|
||||
expect(result.length).to.be.equal(3);
|
||||
expect(result[0].user).to.be.equal(user.profile.name);
|
||||
expect(result[0].username).to.be.equal(user.auth.local.username);
|
||||
});
|
||||
});
|
||||
@@ -62,6 +62,18 @@ describe('inAppRewards', () => {
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('ignores null/undefined entries', () => {
|
||||
user.pinnedItems = testPinnedItems;
|
||||
user.pinnedItems.push(null);
|
||||
user.pinnedItems.push(undefined);
|
||||
user.pinnedItemsOrder = testPinnedItemsOrder;
|
||||
|
||||
let result = inAppRewards(user);
|
||||
|
||||
expect(result[2].path).to.eql('armoire');
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('does not return seasonal items which have been unpinned', () => {
|
||||
if (officialPinnedItems.length === 0) {
|
||||
return; // if no seasonal items, this test is not applicable
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
@@ -16,6 +17,10 @@ describe('shared.ops.buy', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -26,7 +31,6 @@ describe('shared.ops.buy', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(analytics, 'track');
|
||||
@@ -138,4 +142,52 @@ describe('shared.ops.buy', () => {
|
||||
buy(user, {params: {key: 'potion'}, quantity: 2});
|
||||
expect(user.stats.hp).to.eql(50);
|
||||
});
|
||||
|
||||
it('errors if user supplies a non-numeric quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: 'bogle',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('errors if user supplies a negative quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: -3,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('errors if user supplies a decimal quantity', (done) => {
|
||||
try {
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
quantity: 1.83,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(errorMessage('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
function buyGear (user, req, analytics) {
|
||||
let buyOp = new BuyMarketGearOperation(user, req, analytics);
|
||||
@@ -24,6 +25,10 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -34,7 +39,6 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(shared, 'randomVal');
|
||||
|
||||
@@ -108,6 +108,47 @@ describe('shared.ops.purchase', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a non-numeric quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: 'jamboree'}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a negative quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: -2}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when user supplies a decimal quantity', (done) => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
purchase(user, {params: {type, key}, quantity: 2.9}, analytics);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful purchase', () => {
|
||||
@@ -168,7 +209,7 @@ describe('shared.ops.purchase', () => {
|
||||
|
||||
it('purchases quest bundles', () => {
|
||||
let startingBalance = user.balance;
|
||||
let clock = sandbox.useFakeTimers(moment('2017-05-20').valueOf());
|
||||
let clock = sandbox.useFakeTimers(moment('2019-05-20').valueOf());
|
||||
let type = 'bundles';
|
||||
let key = 'featheredFriends';
|
||||
let price = 1.75;
|
||||
|
||||
@@ -93,6 +93,22 @@ describe('shared.ops.hatch', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow hatching quest pet egg using wacky potion', (done) => {
|
||||
user.items.eggs = {Bunny: 1};
|
||||
user.items.hatchingPotions = {Veggie: 1};
|
||||
user.items.pets = {};
|
||||
try {
|
||||
hatch(user, {params: {egg: 'Bunny', hatchingPotion: 'Veggie'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('messageInvalidEggPotionCombo'));
|
||||
expect(user.items.pets).to.be.empty;
|
||||
expect(user.items.eggs).to.eql({Bunny: 1});
|
||||
expect(user.items.hatchingPotions).to.eql({Veggie: 1});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful hatching', () => {
|
||||
|
||||
18
test/common/ops/pinnedGearUtils.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import {addPinnedGear} from '../../../website/common/script/ops/pinnedGearUtils';
|
||||
|
||||
describe('shared.ops.pinnedGearUtils.addPinnedGear', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('not adds an item with empty properties to pinnedItems', () => {
|
||||
addPinnedGear(user, undefined, undefined);
|
||||
|
||||
expect(user.pinnedItems.length).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
@@ -9,13 +9,14 @@ import hatchingPotions from '../../website/common/script/content/hatching-potion
|
||||
|
||||
describe('hatchingPotions', () => {
|
||||
describe('all', () => {
|
||||
it('is a combination of drop and premium potions', () => {
|
||||
it('is a combination of drop, premium, and wacky potions', () => {
|
||||
let dropNumber = Object.keys(hatchingPotions.drops).length;
|
||||
let premiumNumber = Object.keys(hatchingPotions.premium).length;
|
||||
let wackyNumber = Object.keys(hatchingPotions.wacky).length;
|
||||
let allNumber = Object.keys(hatchingPotions.all).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each potion', () => {
|
||||
|
||||
@@ -47,6 +47,18 @@ describe('stable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('wackyPets', () => {
|
||||
it('contains a pet for each wacky potion * each drop egg', () => {
|
||||
let numberOfWackyPotions = Object.keys(potions.wacky).length;
|
||||
let numberOfDropEggs = Object.keys(eggs.drops).length;
|
||||
let numberOfWackyPets = Object.keys(stable.wackyPets).length;
|
||||
let expectedTotal = numberOfWackyPotions * numberOfDropEggs;
|
||||
|
||||
expect(numberOfWackyPets).to.be.greaterThan(0);
|
||||
expect(numberOfWackyPets).to.equal(expectedTotal);
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialPets', () => {
|
||||
it('each value is a valid translation string', () => {
|
||||
each(stable.specialPets, (pet) => {
|
||||
@@ -107,10 +119,11 @@ describe('stable', () => {
|
||||
let questNumber = Object.keys(stable.questPets).length;
|
||||
let specialNumber = Object.keys(stable.specialPets).length;
|
||||
let premiumNumber = Object.keys(stable.premiumPets).length;
|
||||
let wackyNumber = Object.keys(stable.wackyPets).length;
|
||||
let allNumber = Object.keys(stable.petInfo).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each pet', () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { requester } from './requester';
|
||||
import {
|
||||
getDocument as getDocumentFromMongo,
|
||||
updateDocument as updateDocumentInMongo,
|
||||
unsetDocument as unsetDocumentInMongo,
|
||||
} from '../mongo';
|
||||
import {
|
||||
assign,
|
||||
@@ -29,6 +30,18 @@ class ApiObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
async unset (options) {
|
||||
if (isEmpty(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await unsetDocumentInMongo(this._docType, this, options);
|
||||
|
||||
_updateLocalParameters((this, options));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async sync () {
|
||||
let updatedDoc = await getDocumentFromMongo(this._docType, this);
|
||||
|
||||
|
||||
@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// parameter, such as the number of wolf eggs the user has,
|
||||
// , you can do so by passing in the full path as a string:
|
||||
// { 'items.eggs.Wolf': 10 }
|
||||
export async function generateUser (update = {}) {
|
||||
let username = (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
//
|
||||
// To manually set a username, email or password pass it in as
|
||||
// an object for the second parameter. Only overrides need to be
|
||||
// added. Items that don't exist will be autogenerated.
|
||||
// Example: generateUser({}, { username: 'TestName' }) adds user
|
||||
// with the 'TestName' username.
|
||||
export async function generateUser (update = {}, overrides = {}) {
|
||||
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = overrides.password || 'password';
|
||||
let email = overrides.email || `${username}@example.com`;
|
||||
|
||||
let user = await requester().post('/user/auth/local/register', {
|
||||
username,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../website/server/models/task';
|
||||
export {translate} from './translate';
|
||||
|
||||
|
||||
export function generateUser (options = {}) {
|
||||
let user = new User(options).toObject();
|
||||
|
||||
|
||||
@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
|
||||
});
|
||||
}
|
||||
|
||||
// Unset a property in the database.
|
||||
// Useful for testing.
|
||||
export async function unsetDocument (collectionName, doc, update) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
|
||||
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDocument (collectionName, doc) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ div
|
||||
banned-account-modal
|
||||
amazon-payments-modal(v-if='!isStaticPage')
|
||||
payments-success-modal
|
||||
sub-cancel-modal-confirm(v-if='isUserLoaded')
|
||||
sub-canceled-modal(v-if='isUserLoaded')
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
@@ -187,6 +189,8 @@ import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from 'client/components/payments/successModal';
|
||||
import subCancelModalConfirm from 'client/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from 'client/components/payments/canceledModal';
|
||||
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
|
||||
@@ -210,6 +214,8 @@ export default {
|
||||
amazonPaymentsModal,
|
||||
bannedAccountModal,
|
||||
paymentsSuccessModal,
|
||||
subCancelModalConfirm,
|
||||
subCanceledModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("~assets/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.Gems {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
|
||||
@@ -1,42 +1,66 @@
|
||||
.promo_armoire_backgrounds_201903 {
|
||||
.promo_armoire_backgrounds_201905 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -220px;
|
||||
background-position: 0px -786px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201902 {
|
||||
.promo_bronze_quest {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -220px;
|
||||
width: 240px;
|
||||
background-position: -499px 0px;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
}
|
||||
.promo_feathered_friends_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -638px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mythical_marvels_bundle {
|
||||
.promo_floral_sunshine_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -368px;
|
||||
background-position: -424px -638px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201904 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -860px -223px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -368px;
|
||||
background-position: -1068px -371px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_valentines_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -516px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_achievement {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -397px 0px;
|
||||
width: 339px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_cooking {
|
||||
.scene_gold {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 396px;
|
||||
height: 219px;
|
||||
width: 498px;
|
||||
height: 360px;
|
||||
}
|
||||
.scene_languages {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -328px -361px;
|
||||
width: 297px;
|
||||
height: 261px;
|
||||
}
|
||||
.scene_rewards {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -860px -371px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_spells {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -860px 0px;
|
||||
width: 312px;
|
||||
height: 222px;
|
||||
}
|
||||
.scene_yesterdailies_repeatables {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -361px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
|
||||
BIN
website/client/assets/images/Pet_HatchingPotion_Veggie.gif
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
website/client/assets/images/npc/aprilfools/npc_aprilFool.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
website/client/assets/images/npc/aprilfools/npc_bailey.png
Normal file
|
After Width: | Height: | Size: 915 B |
BIN
website/client/assets/images/npc/aprilfools/npc_justin.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
website/client/assets/images/npc/aprilfools/npc_matt.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
BIN
website/client/assets/images/npc/aprilfools/quest_shop_npc.png
Normal file
|
After Width: | Height: | Size: 8.7 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: 11 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
BIN
website/client/assets/images/npc/aprilfools/tavern_npc.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
BIN
website/client/assets/images/paypal-checkout.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 557 KiB After Width: | Height: | Size: 561 KiB |
|
Before Width: | Height: | Size: 562 KiB After Width: | Height: | Size: 580 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 156 KiB |