Compare commits
240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a0aa4a1dc | |||
| 7ffecf9206 | |||
| ea20bd5070 | |||
| c98d7aae37 | |||
| 21d27d223c | |||
| ea13aece21 | |||
| b0bb537501 | |||
| 75783345aa | |||
| 931e2565b6 | |||
| 2f6584378d | |||
| 519a580b7b | |||
| c99d62228f | |||
| 581a40ffad | |||
| 61c78355bd | |||
| f82c93a9ec | |||
| e8c6340af8 | |||
| 1e66cc9193 | |||
| ef6d835391 | |||
| 51f7ce3280 | |||
| 2b7891e788 | |||
| c640a648b9 | |||
| 116f0446e5 | |||
| 065e8303df | |||
| 74d3c9366a | |||
| c9a56e8f3e | |||
| d28e1708c5 | |||
| e091072367 | |||
| 7b1032e898 | |||
| 855470ed25 | |||
| 631efd1adc | |||
| 2e227909a4 | |||
| e5c52625ad | |||
| 8f9f95fd84 | |||
| ff334d08e2 | |||
| 25b443a7c6 | |||
| 6724413ce9 | |||
| 684a615a07 | |||
| babceaff42 | |||
| c29e9ddfc6 | |||
| 0cd0bcf874 | |||
| 544a703df5 | |||
| 0b32692290 | |||
| 20fc233ad7 | |||
| adbe58a0da | |||
| b1b90d6dba | |||
| ef29247526 | |||
| 1c59e78ea9 | |||
| 18e4e11a95 | |||
| 82f34ed437 | |||
| 3b268287b1 | |||
| 0e253fbb06 | |||
| 043696c225 | |||
| 69c2b0378f | |||
| fcfc44fd13 | |||
| 76773d27c6 | |||
| b3e81177ec | |||
| 020726f6f8 | |||
| 9f9b0c73c3 | |||
| 2c03c114da | |||
| 8e9500fed2 | |||
| 377f849f9e | |||
| 8bbada540d | |||
| ee867a9d30 | |||
| 14a4f42b50 | |||
| 2cbb0a85a4 | |||
| 975e068fe2 | |||
| 5a5f1d6895 | |||
| 1db0cda6b1 | |||
| f987585cf1 | |||
| 0325ae9636 | |||
| 4c4fbfe790 | |||
| 6b59262e3e | |||
| 5630e8cc8e | |||
| 5268bbb8a9 | |||
| 34faf2fd50 | |||
| 896950dbf0 | |||
| 424c50f2a4 | |||
| f235a64d96 | |||
| 286abe334e | |||
| 81333a3074 | |||
| 3c31945524 | |||
| 103945f7c5 | |||
| b9d9a17aca | |||
| a0b6f576d2 | |||
| 30036963b1 | |||
| e1fe48bee4 | |||
| 77b19ffe97 | |||
| e9a5e084fc | |||
| 12250a93f1 | |||
| 7094e75dd8 | |||
| 74c93955f8 | |||
| e4b2ef6599 | |||
| a26c2bce23 | |||
| 6cd00897ed | |||
| 3c442318d8 | |||
| fd3f7e2b0e | |||
| 6f0bd0b913 | |||
| 5b7621ceb3 | |||
| b8b414cae5 | |||
| 783338f1f0 | |||
| fb6ca0c73b | |||
| 5792f54aa0 | |||
| 3c943c8f23 | |||
| 4b2fd60e47 | |||
| 75281ab638 | |||
| d46c9967fb | |||
| 4132a39b90 | |||
| 36cc229dd2 | |||
| ef0c11c2bd | |||
| 3a8312832c | |||
| df17753bb6 | |||
| c9b3c646eb | |||
| cd67877ef3 | |||
| 251e1d45af | |||
| 1a97d69edd | |||
| 7baa7427a0 | |||
| 270078a030 | |||
| bb2768071d | |||
| ec75de5a90 | |||
| b9b944ba29 | |||
| fa9553b371 | |||
| 8d5cbbe9be | |||
| 3a339a4a09 | |||
| 324b16b8cd | |||
| 8c005aa05a | |||
| 664cf5a47b | |||
| 71926cff51 | |||
| e971f49c85 | |||
| 01657f573d | |||
| 95613dcfb8 | |||
| bcf304984d | |||
| 08d84ba691 | |||
| 4007c26801 | |||
| 344c20fd99 | |||
| f5c2c39f6a | |||
| 0379f341b9 | |||
| 8498ab9fe2 | |||
| bda3fb5f4d | |||
| eff8db0afd | |||
| 8cce38ede1 | |||
| ab0dae8df3 | |||
| 0824af05b7 | |||
| 28bf024990 | |||
| 8c59420d4e | |||
| 8a30ac0607 | |||
| e1984762b5 | |||
| 0360326f41 | |||
| f3f215abea | |||
| feb98a5ac7 | |||
| 95de11cf7a | |||
| 5bb336cedc | |||
| 790614a9d4 | |||
| 977968df19 | |||
| 3df056105c | |||
| 0eb7e10e7f | |||
| 0908fa2a48 | |||
| 71904d106f | |||
| 56040eebaf | |||
| 2094a4d4b8 | |||
| 2048f3ef76 | |||
| e9163a1bb2 | |||
| 54fd910db2 | |||
| 3c0cd7067a | |||
| 5a473eb0e0 | |||
| e6fcdf62ef | |||
| 09e748bc92 | |||
| 90532b0763 | |||
| 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 | |||
| 6fba71ea2c | |||
| 5755bfc952 | |||
| 08c6e8298c | |||
| 5a30b0cf1f | |||
| cf847cd1d8 | |||
| 5753d3e648 | |||
| 4718e5e5ea | |||
| 9a2dbace30 | |||
| ef07abfd28 | |||
| b28adc6b42 | |||
| c814eabb29 | |||
| 4dc19a8c60 | |||
| 89f6a9b07e | |||
| a23926f34f | |||
| 118198b594 | |||
| 97e80e2093 | |||
| 2588befceb | |||
| f09274225a | |||
| 03b66abe70 | |||
| 9d41fb0252 | |||
| 012fa2f8ef | |||
| 00d8d9d0cc | |||
| cc4772c75a | |||
| fabaaa6d92 | |||
| 854696728a | |||
| 314926cc06 | |||
| 68fa834946 | |||
| 09440d5adf | |||
| 1724bfc553 | |||
| 8304f99ecb | |||
| d2fcdf4493 | |||
| 649404ac6a | |||
| 774db2564f | |||
| 521a1e646d | |||
| 2619ac37d9 | |||
| 2ce9b319a0 | |||
| 477c23dd67 | |||
| 7af71d1457 | |||
| 3171550de2 | |||
| 6477801d3e | |||
| 14798ced82 | |||
| 34d37cefcc | |||
| bd21933cea |
@@ -13,3 +13,8 @@ Habitica uses [Trello](https://trello.com/b/EpoYEYod/habitica) to track feature
|
||||
# Contributing Code
|
||||
|
||||
See [Contributing to Habitica](http://habitica.fandom.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
|
||||
|
||||
## Issue Triage [](https://www.codetriage.com/habitrpg/habitica)
|
||||
|
||||
You can triage issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to habitrpg on CodeTriage](https://www.codetriage.com/habitrpg/habitica).
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
FROM node:10
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN cp config.json.example config.json
|
||||
RUN npm install
|
||||
|
||||
# Create Build dir
|
||||
RUN mkdir -p ./website/build
|
||||
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
FROM node:10
|
||||
WORKDIR /code
|
||||
COPY package*.json /code/
|
||||
RUN npm install
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
@@ -61,18 +61,12 @@
|
||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||
"SITE_HTTP_AUTH_ENABLED": "false",
|
||||
"SITE_HTTP_AUTH_PASSWORD": "password",
|
||||
"SITE_HTTP_AUTH_USERNAME": "admin",
|
||||
"SITE_HTTP_AUTH_PASSWORDS": "password,wordpass,passkey",
|
||||
"SITE_HTTP_AUTH_USERNAMES": "admin,tester,contributor",
|
||||
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"SMTP_HOST": "example.com",
|
||||
"SMTP_PASS": "password",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_SERVICE": "Gmail",
|
||||
"SMTP_TLS": "true",
|
||||
"SMTP_USER": "user@example.com",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
client:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-Dev
|
||||
command: ["npm", "run", "client:dev"]
|
||||
depends_on:
|
||||
- server
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- BASE_URL=http://server:3000
|
||||
image: habitica
|
||||
networks:
|
||||
- habitica
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
- .:/code
|
||||
- /code/node_modules
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-Dev
|
||||
command: ["npm", "start"]
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
image: habitica
|
||||
networks:
|
||||
- habitica
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
- .:/code
|
||||
- /code/node_modules
|
||||
mongo:
|
||||
image: mongo:3.4
|
||||
networks:
|
||||
- habitica
|
||||
ports:
|
||||
- "27017:27017"
|
||||
networks:
|
||||
habitica:
|
||||
driver: bridge
|
||||
|
||||
@@ -16,7 +16,7 @@ const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
|
||||
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
|
||||
|
||||
function checkForSpecialTreatment (name) {
|
||||
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
|
||||
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame|^eyewear_special_\w+HalfMoon/;
|
||||
return name.match(regex) || name === 'head_0';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190530_halfmoon_glasses';
|
||||
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 set = {
|
||||
'items.gear.owned.eyewear_special_blackHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_blueHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_greenHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_pinkHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_redHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_whiteHalfMoon': true,
|
||||
'items.gear.owned.eyewear_special_yellowHalfMoon': true,
|
||||
};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-05-01')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190618_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set;
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME };
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Orca-Base': 5 };
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Orca-Base': true };
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-05-18')},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190716_groups_fix';
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
let backupUsers;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set = { migration: MIGRATION_NAME };
|
||||
let addToSet;
|
||||
|
||||
const monkPromise = new Promise((resolve, reject) => {
|
||||
backupUsers.findOne(
|
||||
{ _id: user._id },
|
||||
{ fields: { _id: 1, party: 1, guilds: 1 }},
|
||||
).then(foundUserInBackup => {
|
||||
resolve(foundUserInBackup);
|
||||
}).catch(e => {
|
||||
reject(e);
|
||||
})
|
||||
});
|
||||
let backupUser = await monkPromise;
|
||||
if (!backupUser) return;
|
||||
|
||||
if (!user.party._id) {
|
||||
set.party = backupUser.party;
|
||||
}
|
||||
addToSet = { guilds: { $each: backupUser.guilds }};
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return User.update({ _id: user._id }, { $set: set, $addToSet: addToSet }).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
const query = {
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-07-15')},
|
||||
};
|
||||
|
||||
let backupDb = monk(CONNECTION_STRING);
|
||||
const backupDbPromise = new Promise((resolve, reject) => {
|
||||
backupDb.then(() => resolve()).catch((e) => reject(e));
|
||||
});
|
||||
|
||||
await backupDbPromise;
|
||||
console.log('Connected to backup db');
|
||||
backupUsers = backupDb.get('users', { castIds: false });
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
party: 1,
|
||||
guilds: 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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190717_groups_fix_2';
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import { sendTxn as sendTxnEmail } from '../../../website/server/libs/email';
|
||||
import shared from '../../../website/common';
|
||||
|
||||
const questScrolls = shared.content.quests;
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateGroup (group) {
|
||||
count++;
|
||||
|
||||
if (group && group.quest && group.quest.key && group.quest.leader) {
|
||||
const quest = questScrolls[group.quest.key];
|
||||
const leader = await User.findOne({_id: group.quest.leader}).exec();
|
||||
|
||||
if (leader && quest) {
|
||||
await User.update({
|
||||
_id: leader._id,
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
}, {
|
||||
$set: {migration: MIGRATION_NAME},
|
||||
$inc: {
|
||||
balance: 1,
|
||||
[`items.quests.${group.quest.key}`]: 1,
|
||||
},
|
||||
}).exec();
|
||||
|
||||
// unsubscribe from all is already checked by sendTxnEmail
|
||||
if (leader.preferences && leader.preferences.emailNotifications && leader.preferences.emailNotifications.majorUpdates !== false) {
|
||||
sendTxnEmail(leader, 'groups-outage');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${group._id}`);
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
const query = {
|
||||
type: 'party'
|
||||
};
|
||||
|
||||
let backupDb = monk(CONNECTION_STRING);
|
||||
const backupDbPromise = new Promise((resolve, reject) => {
|
||||
backupDb.then(() => resolve()).catch((e) => reject(e));
|
||||
});
|
||||
|
||||
await backupDbPromise;
|
||||
console.log('Connected to backup db');
|
||||
const backupGroups = backupDb.get('groups', { castIds: false });
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const groupsPromise = new Promise((resolve, reject) => {
|
||||
backupGroups
|
||||
.find(query, {
|
||||
limit: 250,
|
||||
sort: {_id: 1}
|
||||
})
|
||||
.then(foundGroupInBackup => {
|
||||
resolve(foundGroupInBackup);
|
||||
}).catch(e => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
const groups = await groupsPromise;
|
||||
|
||||
if (groups.length === 0) {
|
||||
console.warn('All appropriate groups found and modified.');
|
||||
console.warn(`\n${count} groups processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: groups[groups.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(groups.map(updateGroup)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,7 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./users/mystery-items.js');
|
||||
const processUsers = require('');
|
||||
processUsers()
|
||||
.then(function success () {
|
||||
process.exit(0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201904';
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201904', 'head_mystery_201904'];
|
||||
const MIGRATION_NAME = 'mystery_items_201907';
|
||||
const MYSTERY_ITEMS = ['head_mystery_201907', 'armor_mystery_201907', 'eyewear_mystery_201907'];
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.93.0",
|
||||
"version": "4.105.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/trace-agent": "^3.6.0",
|
||||
"@google-cloud/trace-agent": "^4.0.0",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.7",
|
||||
@@ -14,7 +14,7 @@
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^9.4.0",
|
||||
"aws-sdk": "^2.432.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios": "^0.19.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
@@ -48,7 +48,7 @@
|
||||
"got": "^9.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^5.0.3",
|
||||
"gulp-imagemin": "^6.0.0",
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
@@ -58,9 +58,9 @@
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"js2xmlparser": "^4.0.0",
|
||||
"lodash": "^4.17.10",
|
||||
"merge-stream": "^1.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
@@ -69,9 +69,8 @@
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^6.0.0",
|
||||
"ora": "^3.2.0",
|
||||
"pageres": "^4.1.1",
|
||||
"pageres": "^5.1.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
@@ -89,7 +88,7 @@
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^5.0.2",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svg-url-loader": "^3.0.0",
|
||||
"svgo": "^1.2.0",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
@@ -98,7 +97,7 @@
|
||||
"url-loader": "^1.0.0",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.0",
|
||||
"validator": "^11.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^14.2.2",
|
||||
@@ -152,7 +151,7 @@
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.40.0",
|
||||
"chromedriver": "^75.0.0",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.3",
|
||||
"cross-spawn": "^6.0.5",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/* eslint-disable global-require */
|
||||
import got from 'got';
|
||||
import nconf from 'nconf';
|
||||
import nodemailer from 'nodemailer';
|
||||
import requireAgain from 'require-again';
|
||||
import logger from '../../../../website/server/libs/logger';
|
||||
import { TAVERN_ID } from '../../../../website/server/models/group';
|
||||
import { defer } from '../../../helpers/api-unit.helper';
|
||||
|
||||
@@ -35,42 +33,6 @@ function getUser () {
|
||||
describe('emails', () => {
|
||||
let pathToEmailLib = '../../../../website/server/libs/email';
|
||||
|
||||
describe('sendEmail', () => {
|
||||
let sendMailSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
sendMailSpy = sandbox.stub().returns(defer().promise);
|
||||
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||
sendMail: sendMailSpy,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can send an email using the default transport', () => {
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
attachEmail.send();
|
||||
expect(sendMailSpy).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('logs errors', (done) => {
|
||||
sandbox.stub(logger, 'error');
|
||||
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
attachEmail.send();
|
||||
expect(sendMailSpy).to.be.calledOnce;
|
||||
defer().reject();
|
||||
|
||||
// wait for unhandledRejection event to fire
|
||||
setTimeout(() => {
|
||||
expect(logger.error).to.be.calledOnce;
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserInfo', () => {
|
||||
it('returns an empty object if no field request', () => {
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
@@ -84,7 +46,7 @@ describe('emails', () => {
|
||||
let user = getUser();
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.profile.name);
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).to.have.property('email', user.auth.local.email);
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
@@ -95,11 +57,11 @@ describe('emails', () => {
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
let user = getUser();
|
||||
delete user.profile.name;
|
||||
delete user.auth.local;
|
||||
delete user.auth.local.email;
|
||||
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.profile.name);
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
@@ -114,7 +76,7 @@ describe('emails', () => {
|
||||
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
||||
expect(data).to.have.property('name', user.profile.name);
|
||||
expect(data).to.have.property('name', user.auth.local.username);
|
||||
expect(data).not.to.have.property('email');
|
||||
expect(data).to.have.property('_id', user._id);
|
||||
expect(data).to.have.property('canSend', true);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
validateItemPath,
|
||||
getDefaultOwnedGear,
|
||||
castItemVal,
|
||||
} from '../../../../../website/server/libs/items/utils';
|
||||
|
||||
describe('Items Utils', () => {
|
||||
@@ -64,4 +65,49 @@ describe('Items Utils', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('payments/index', () => {
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.auth.local.username = 'sender';
|
||||
await user.save();
|
||||
|
||||
group = generateGroup({
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('auth middleware', () => {
|
||||
describe('auth with headers', () => {
|
||||
it('allows to specify a list of user field that we do not want to load', (done) => {
|
||||
const authWithHeaders = authWithHeadersFactory({
|
||||
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
|
||||
userFieldsToExclude: ['items'],
|
||||
});
|
||||
|
||||
req.headers['x-api-user'] = user._id;
|
||||
@@ -27,11 +27,34 @@ describe('auth middleware', () => {
|
||||
|
||||
const userToJSON = res.locals.user.toJSON();
|
||||
expect(userToJSON.items).to.not.exist;
|
||||
expect(userToJSON.flags).to.not.exist;
|
||||
expect(userToJSON.auth.timestamps).to.not.exist;
|
||||
expect(userToJSON.auth).to.exist;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('makes sure some fields are always included', (done) => {
|
||||
const authWithHeaders = authWithHeadersFactory({
|
||||
userFieldsToExclude: [
|
||||
'items', 'auth.timestamps',
|
||||
'preferences', 'notifications', '_id', 'flags', 'auth', // these are always loaded
|
||||
],
|
||||
});
|
||||
|
||||
req.headers['x-api-user'] = user._id;
|
||||
req.headers['x-api-key'] = user.apiToken;
|
||||
|
||||
authWithHeaders(req, res, (err) => {
|
||||
if (err) return done(err);
|
||||
|
||||
const userToJSON = res.locals.user.toJSON();
|
||||
expect(userToJSON.items).to.not.exist;
|
||||
expect(userToJSON.auth.timestamps).to.exist;
|
||||
expect(userToJSON.auth).to.exist;
|
||||
expect(userToJSON.notifications).to.exist;
|
||||
expect(userToJSON.preferences).to.exist;
|
||||
expect(userToJSON._id).to.exist;
|
||||
expect(userToJSON.flags).to.exist;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import validator from 'validator';
|
||||
import { sleep } from '../../../helpers/api-unit.helper';
|
||||
import { sleep, translationCheck } from '../../../helpers/api-unit.helper';
|
||||
import {
|
||||
SPAM_MESSAGE_LIMIT,
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
@@ -271,7 +271,16 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
|
||||
info: {
|
||||
bossDamage: '7.5',
|
||||
quest: 'whale',
|
||||
type: 'boss_damage',
|
||||
user: 'Participating Member',
|
||||
userDamage: '5.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('applies damage only to participating members of party', async () => {
|
||||
@@ -344,7 +353,10 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledTwice;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`You defeated Wailing Whale! Questing party members receive the rewards of victory.`',
|
||||
info: { quest: 'whale', type: 'boss_defeated' },
|
||||
});
|
||||
});
|
||||
|
||||
it('calls finishQuest when boss has <= 0 hp', async () => {
|
||||
@@ -387,7 +399,10 @@ describe('Group Model', () => {
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: quest.boss.rage.effect('en'),
|
||||
info: { quest: 'trex_undead', type: 'boss_rage' },
|
||||
});
|
||||
expect(party.quest.progress.hp).to.eql(383.5);
|
||||
expect(party.quest.progress.rage).to.eql(0);
|
||||
});
|
||||
@@ -437,7 +452,10 @@ describe('Group Model', () => {
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: quest.boss.rage.effect('en'),
|
||||
info: { quest: 'lostMasterclasser4', type: 'boss_rage' },
|
||||
});
|
||||
expect(party.quest.progress.rage).to.eql(0);
|
||||
|
||||
let drainedUser = await User.findById(participatingMember._id);
|
||||
@@ -488,7 +506,15 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member found 5 Bars of Soap.`',
|
||||
info: {
|
||||
items: { soapBars: 5 },
|
||||
quest: 'atom1',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a chat message if no progress is made', async () => {
|
||||
@@ -499,7 +525,15 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 0 Bars of Soap.`');
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member found 0 Bars of Soap.`',
|
||||
info: {
|
||||
items: { soapBars: 0 },
|
||||
quest: 'atom1',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a chat message if no progress is made on quest with multiple items', async () => {
|
||||
@@ -516,9 +550,15 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Blue Fins/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Fire Coral/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
|
||||
info: {
|
||||
items: { blueFins: 0, fireCoral: 0 },
|
||||
quest: 'dilatoryDistress1',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('handles collection quests with multiple items', async () => {
|
||||
@@ -535,8 +575,14 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/\d* (Tracks|Broken Twigs)/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
|
||||
info: {
|
||||
quest: 'evilsanta2',
|
||||
type: 'user_found_items',
|
||||
user: 'Participating Member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sends message about victory', async () => {
|
||||
@@ -547,7 +593,10 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledTwice;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`All items found! Party has received their rewards.`',
|
||||
info: { type: 'all_items_found' },
|
||||
});
|
||||
});
|
||||
|
||||
it('calls finishQuest when all items are found', async () => {
|
||||
@@ -718,6 +767,258 @@ describe('Group Model', () => {
|
||||
expect(res.t).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('translateSystemMessages', () => {
|
||||
it('translate quest_start', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'quest_start',
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate boss_damage', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'boss_damage',
|
||||
user: questLeader.profile.name,
|
||||
quest: 'basilist',
|
||||
userDamage: 15.3,
|
||||
bossDamage: 3.7,
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate boss_dont_attack', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'boss_dont_attack',
|
||||
user: questLeader.profile.name,
|
||||
quest: 'basilist',
|
||||
userDamage: 15.3,
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate boss_rage', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'boss_rage',
|
||||
quest: 'lostMasterclasser3',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate boss_defeated', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'boss_defeated',
|
||||
quest: 'lostMasterclasser3',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate user_found_items', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'user_found_items',
|
||||
user: questLeader.profile.name,
|
||||
quest: 'lostMasterclasser1',
|
||||
items: {
|
||||
ancientTome: 3,
|
||||
forbiddenTome: 2,
|
||||
hiddenTome: 1,
|
||||
},
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate all_items_found', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'all_items_found',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate spell_cast_party', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'spell_cast_party',
|
||||
user: questLeader.profile.name,
|
||||
class: 'wizard',
|
||||
spell: 'earth',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate spell_cast_user', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'spell_cast_user',
|
||||
user: questLeader.profile.name,
|
||||
class: 'special',
|
||||
spell: 'snowball',
|
||||
target: participatingMember.profile.name,
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate quest_cancel', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'quest_cancel',
|
||||
user: questLeader.profile.name,
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate quest_abort', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'quest_abort',
|
||||
user: questLeader.profile.name,
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate tavern_quest_completed', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'tavern_quest_completed',
|
||||
quest: 'stressbeast',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate tavern_boss_rage_tired', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'tavern_boss_rage_tired',
|
||||
quest: 'stressbeast',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate tavern_boss_rage', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'tavern_boss_rage',
|
||||
quest: 'dysheartener',
|
||||
scene: 'market',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate tavern_boss_desperation', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'tavern_boss_desperation',
|
||||
quest: 'stressbeast',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
|
||||
it('translate claim_task', async () => {
|
||||
questLeader.preferences.language = 'en';
|
||||
party.chat = [{
|
||||
info: {
|
||||
type: 'claim_task',
|
||||
user: questLeader.profile.name,
|
||||
task: 'Feed the pet',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
translationCheck(toJSON.chat[0].text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toJSONCleanChat', () => {
|
||||
it('shows messages with 1 flag to non-admins', async () => {
|
||||
party.chat = [{
|
||||
flagCount: 1,
|
||||
info: {
|
||||
type: 'quest_start',
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
expect(toJSON.chat.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('shows messages with >= 2 flag to admins', async () => {
|
||||
party.chat = [{
|
||||
flagCount: 3,
|
||||
info: {
|
||||
type: 'quest_start',
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
const admin = new User({'contributor.admin': true});
|
||||
let toJSON = await Group.toJSONCleanChat(party, admin);
|
||||
expect(toJSON.chat.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('doesn\'t show flagged messages to non-admins', async () => {
|
||||
party.chat = [{
|
||||
flagCount: 3,
|
||||
info: {
|
||||
type: 'quest_start',
|
||||
quest: 'basilist',
|
||||
},
|
||||
}];
|
||||
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||
expect(toJSON.chat.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Instance Methods', () => {
|
||||
@@ -1007,20 +1308,22 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('formats message', () => {
|
||||
const chatMessage = party.sendChat('a new message', {
|
||||
_id: 'user-id',
|
||||
profile: { name: 'user name' },
|
||||
contributor: {
|
||||
toObject () {
|
||||
return 'contributor object';
|
||||
const chatMessage = party.sendChat({
|
||||
message: 'a new message', user: {
|
||||
_id: 'user-id',
|
||||
profile: { name: 'user name' },
|
||||
contributor: {
|
||||
toObject () {
|
||||
return 'contributor object';
|
||||
},
|
||||
},
|
||||
},
|
||||
backer: {
|
||||
toObject () {
|
||||
return 'backer object';
|
||||
backer: {
|
||||
toObject () {
|
||||
return 'backer object';
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
);
|
||||
|
||||
const chat = chatMessage;
|
||||
|
||||
@@ -1037,7 +1340,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('formats message as system if no user is passed in', () => {
|
||||
const chat = party.sendChat('a system message');
|
||||
const chat = party.sendChat({message: 'a system message'});
|
||||
|
||||
expect(chat.text).to.eql('a system message');
|
||||
expect(validator.isUUID(chat.id)).to.eql(true);
|
||||
@@ -1052,7 +1355,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates users about new messages in party', () => {
|
||||
party.sendChat('message');
|
||||
party.sendChat({message: 'message'});
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
@@ -1066,7 +1369,7 @@ describe('Group Model', () => {
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
group.sendChat('message');
|
||||
group.sendChat({message: 'message'});
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
@@ -1076,7 +1379,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('does not send update to user that sent the message', () => {
|
||||
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
|
||||
party.sendChat({message: 'message', user: {_id: 'user-id', profile: { name: 'user' }}});
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
@@ -1088,7 +1391,7 @@ describe('Group Model', () => {
|
||||
it('skips sending new message notification for guilds with > 5000 members', () => {
|
||||
party.memberCount = 5001;
|
||||
|
||||
party.sendChat('message');
|
||||
party.sendChat({message: 'message'});
|
||||
|
||||
expect(User.update).to.not.be.called;
|
||||
});
|
||||
@@ -1096,7 +1399,7 @@ describe('Group Model', () => {
|
||||
it('skips sending messages to the tavern', () => {
|
||||
party._id = TAVERN_ID;
|
||||
|
||||
party.sendChat('message');
|
||||
party.sendChat({message: 'message'});
|
||||
|
||||
expect(User.update).to.not.be.called;
|
||||
});
|
||||
@@ -1431,7 +1734,7 @@ describe('Group Model', () => {
|
||||
let quest;
|
||||
|
||||
beforeEach(() => {
|
||||
quest = questScrolls.whale;
|
||||
quest = questScrolls.armadillo;
|
||||
party.quest.key = quest.key;
|
||||
party.quest.active = false;
|
||||
party.quest.leader = questLeader._id;
|
||||
@@ -1579,6 +1882,36 @@ describe('Group Model', () => {
|
||||
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
it('gives out other pet-related quest achievements', async () => {
|
||||
quest = questScrolls.rock;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
questLeader.achievements.quests = {
|
||||
mayhemMistiflying1: 1,
|
||||
yarn: 1,
|
||||
mayhemMistiflying2: 1,
|
||||
egg: 1,
|
||||
mayhemMistiflying3: 1,
|
||||
slime: 2,
|
||||
};
|
||||
await questLeader.save();
|
||||
await party.finishQuest(quest);
|
||||
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id).exec(),
|
||||
User.findById(participatingMember._id).exec(),
|
||||
User.findById(sleepingParticipatingMember._id).exec(),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.mindOverMatter).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
|
||||
expect(updatedSleepingParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
|
||||
});
|
||||
|
||||
it('gives xp and gold', async () => {
|
||||
await party.finishQuest(quest);
|
||||
|
||||
@@ -1715,13 +2048,13 @@ describe('Group Model', () => {
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
participatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(questLeader.party.quest.completed).to.eql('whale');
|
||||
expect(questLeader.party.quest.completed).to.eql('armadillo');
|
||||
expect(questLeader.party.quest.progress.up).to.eql(10);
|
||||
expect(questLeader.party.quest.progress.down).to.eql(8);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
|
||||
|
||||
expect(participatingMember.party.quest.completed).to.eql('whale');
|
||||
expect(participatingMember.party.quest.completed).to.eql('armadillo');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(8);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
@@ -1928,7 +2261,7 @@ describe('Group Model', () => {
|
||||
|
||||
await guild.save();
|
||||
|
||||
const groupMessage = guild.sendChat('Test message.');
|
||||
const groupMessage = guild.sendChat({message: 'Test message.'});
|
||||
await groupMessage.save();
|
||||
|
||||
await sleep();
|
||||
|
||||
@@ -171,7 +171,7 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return not return challenges in user groups if we send member true param', async () => {
|
||||
it('should not return challenges in user groups if we send member true param', async () => {
|
||||
let challenges = await member.get(`/challenges/user?member=${true}`);
|
||||
|
||||
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
@@ -214,6 +214,28 @@ describe('GET challenges/user', () => {
|
||||
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
|
||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestPrivateGuild',
|
||||
summary: 'summary for TestPrivateGuild',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
|
||||
let privateChallenge = await generateChallenge(groupLeader, group, {categories: [{
|
||||
name: 'academics',
|
||||
slug: 'academics',
|
||||
}]});
|
||||
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
|
||||
|
||||
let challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned');
|
||||
|
||||
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
|
||||
expect(foundChallenge).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('POST /group', () => {
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotCreatePublicGuildWhenMuted'),
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -262,7 +262,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -436,7 +436,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -526,7 +526,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotInviteWhenMuted'),
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('GET /inbox/messages', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
[user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
@@ -60,4 +60,10 @@ describe('GET /inbox/messages', () => {
|
||||
|
||||
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',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -127,7 +127,13 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
members: {},
|
||||
});
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||
message: sinon.match(/aborted the party quest Wail of the Whale.`/),
|
||||
info: {
|
||||
quest: 'whale',
|
||||
type: 'quest_abort',
|
||||
},
|
||||
});
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
@@ -141,7 +141,14 @@ 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.`/);
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||
message: sinon.match(/cancelled the party quest Wail of the Whale.`/),
|
||||
info: {
|
||||
quest: 'whale',
|
||||
type: 'quest_cancel',
|
||||
user: sinon.match.any,
|
||||
},
|
||||
});
|
||||
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
@@ -54,6 +54,21 @@ describe('POST /tasks/challenge/:challengeId', () => {
|
||||
expect(tasksOrder.habits).to.include(task.id);
|
||||
});
|
||||
|
||||
it('allows non-leader admin to add tasks to a challenge when not a member', async () => {
|
||||
const admin = await generateUser({'contributor.admin': true});
|
||||
let task = await admin.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit from admin',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 1976,
|
||||
});
|
||||
|
||||
let {tasksOrder} = await user.get(`/challenges/${challenge._id}`);
|
||||
|
||||
expect(tasksOrder.habits).to.include(task.id);
|
||||
});
|
||||
|
||||
it('returns error when user tries to create task with a alias', async () => {
|
||||
await expect(user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test habit',
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('DELETE /inbox/messages/:messageId', () => {
|
||||
});
|
||||
|
||||
it('deletes one message', async () => {
|
||||
const messages = await user.get('/inbox/messages');
|
||||
const messages = await user.get('/inbox/paged-messages');
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
|
||||
@@ -53,10 +53,10 @@ describe('DELETE /inbox/messages/:messageId', () => {
|
||||
expect(messages[2].text).to.equal('first');
|
||||
|
||||
await user.del(`/inbox/messages/${messages[1]._id}`);
|
||||
const updatedMessages = await user.get('/inbox/messages');
|
||||
const updatedMessages = await user.get('/inbox/paged-messages');
|
||||
expect(updatedMessages.length).to.equal(2);
|
||||
|
||||
expect(updatedMessages[0].text).to.equal('third');
|
||||
expect(updatedMessages[1].text).to.equal('first');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('GET /inbox/conversations', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
let thirdUser;
|
||||
|
||||
beforeEach(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);
|
||||
});
|
||||
|
||||
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
|
||||
const messages = await user.get('/inbox/paged-messages');
|
||||
|
||||
expect(messages.length).to.equal(5);
|
||||
|
||||
// message to yourself
|
||||
expect(messages[0].text).to.equal('fifth');
|
||||
expect(messages[0].sent).to.equal(false);
|
||||
expect(messages[0].uuid).to.equal(user._id);
|
||||
|
||||
expect(messages[1].text).to.equal('fourth');
|
||||
expect(messages[2].text).to.equal('third');
|
||||
expect(messages[3].text).to.equal('second');
|
||||
expect(messages[4].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/paged-messages?page=1');
|
||||
|
||||
expect(messages.length).to.equal(5);
|
||||
});
|
||||
|
||||
it('returns only the messages of one conversation', async () => {
|
||||
const messages = await user.get(`/inbox/paged-messages?conversation=${otherUser.id}`);
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
});
|
||||
|
||||
it('returns the correct message format', async () => {
|
||||
const messages = await otherUser.get(`/inbox/paged-messages?conversation=${user.id}`);
|
||||
|
||||
expect(messages[0].toUUID).to.equal(user.id); // from user
|
||||
expect(messages[1].toUUID).to.not.exist; // only filled if its from the chat partner
|
||||
expect(messages[2].toUUID).to.equal(user.id); // from user
|
||||
});
|
||||
});
|
||||
@@ -19,10 +19,10 @@ describe('POST /members/flag-private-message/:messageId', () => {
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let senderMessages = await userToSendMessage.get('/inbox/messages');
|
||||
let senderMessages = await userToSendMessage.get('/inbox/paged-messages');
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
return message.toUUID === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
@@ -37,7 +37,7 @@ describe('POST /members/flag-private-message/:messageId', () => {
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
let receiversMessages = await receiver.get('/inbox/paged-messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
@@ -55,7 +55,7 @@ describe('POST /members/flag-private-message/:messageId', () => {
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
let receiversMessages = await receiver.get('/inbox/paged-messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
|
||||
@@ -83,12 +83,12 @@ context('avatar.vue', () => {
|
||||
expect(vm.paddingTop).to.equal('28px');
|
||||
});
|
||||
|
||||
it('is 24.5px if user has a pet', () => {
|
||||
it('is 24px if user has a pet', () => {
|
||||
vm.member.items = {
|
||||
currentPet: { name: 'Foo' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('24.5px');
|
||||
expect(vm.paddingTop).to.equal('24px');
|
||||
});
|
||||
|
||||
it('is 0px if user has a mount', () => {
|
||||
@@ -297,4 +297,4 @@ context('avatar.vue', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import ChallengeDetailComponent from 'client/components/challenges/challengeDetail.vue';
|
||||
import Store from 'client/libs/store';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Store);
|
||||
|
||||
describe('Challenge Detail', () => {
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
contributor: {
|
||||
admin: false,
|
||||
},
|
||||
challenges: [],
|
||||
stats: {
|
||||
},
|
||||
flags: {},
|
||||
preferences: {},
|
||||
party: {
|
||||
quest: {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
'members:getChallengeMembers': () => {},
|
||||
'challenges:getChallenge': () => [
|
||||
{_id: '1', group: { name: '', type: ''}, memberCount: 1, name: '', summary: '', description: '', leader: '', price: 1},
|
||||
],
|
||||
'tasks:getChallengeTasks': () => [
|
||||
{_id: '1', type: 'habit'},
|
||||
{_id: '2', type: 'daily'},
|
||||
{_id: '3', type: 'reward'},
|
||||
{_id: '4', type: 'todo'},
|
||||
],
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
});
|
||||
wrapper = shallowMount(ChallengeDetailComponent, {
|
||||
store,
|
||||
localVue,
|
||||
mocks: {
|
||||
$t: (string) => string,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('removes a destroyed task from task list', () => {
|
||||
let taskToRemove = {_id: '1', type: 'habit'};
|
||||
wrapper.vm.taskDestroyed(taskToRemove);
|
||||
expect(wrapper.vm.tasksByType[taskToRemove.type].length).to.eq(0);
|
||||
});
|
||||
});
|
||||
@@ -80,6 +80,13 @@ describe('shared.ops.buy', () => {
|
||||
headAccessory_special_redHeadband: true,
|
||||
headAccessory_special_whiteHeadband: true,
|
||||
headAccessory_special_yellowHeadband: true,
|
||||
eyewear_special_blackHalfMoon: true,
|
||||
eyewear_special_blueHalfMoon: true,
|
||||
eyewear_special_greenHalfMoon: true,
|
||||
eyewear_special_pinkHalfMoon: true,
|
||||
eyewear_special_redHalfMoon: true,
|
||||
eyewear_special_whiteHalfMoon: true,
|
||||
eyewear_special_yellowHalfMoon: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -75,6 +75,13 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
headAccessory_special_redHeadband: true,
|
||||
headAccessory_special_whiteHeadband: true,
|
||||
headAccessory_special_yellowHeadband: true,
|
||||
eyewear_special_blackHalfMoon: true,
|
||||
eyewear_special_blueHalfMoon: true,
|
||||
eyewear_special_greenHalfMoon: true,
|
||||
eyewear_special_pinkHalfMoon: true,
|
||||
eyewear_special_redHalfMoon: true,
|
||||
eyewear_special_whiteHalfMoon: true,
|
||||
eyewear_special_yellowHalfMoon: true,
|
||||
});
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
@@ -209,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;
|
||||
|
||||
@@ -169,6 +169,24 @@ describe('shared.ops.feed', () => {
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(7);
|
||||
});
|
||||
|
||||
it('awards All Your Base achievement', () => {
|
||||
user.items.pets['Wolf-Spooky'] = 5;
|
||||
user.items.food.Milk = 2;
|
||||
user.items.mounts = {
|
||||
'Wolf-Base': true,
|
||||
'TigerCub-Base': true,
|
||||
'PandaCub-Base': true,
|
||||
'LionCub-Base': true,
|
||||
'Fox-Base': true,
|
||||
'FlyingPig-Base': true,
|
||||
'Dragon-Base': true,
|
||||
'Cactus-Base': true,
|
||||
'BearCub-Base': true,
|
||||
};
|
||||
feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}});
|
||||
expect(user.achievements.allYourBase).to.eql(true);
|
||||
});
|
||||
|
||||
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50', () => {
|
||||
user.items.pets['Wolf-Base'] = 49;
|
||||
user.items.food.Milk = 2;
|
||||
|
||||
@@ -159,6 +159,24 @@ describe('shared.ops.hatch', () => {
|
||||
expect(user.items.eggs).to.eql({Wolf: 0});
|
||||
expect(user.items.hatchingPotions).to.eql({Base: 0});
|
||||
});
|
||||
|
||||
it('awards Back to Basics achievement', () => {
|
||||
user.items.pets = {
|
||||
'Wolf-Base': 5,
|
||||
'TigerCub-Base': 5,
|
||||
'PandaCub-Base': 10,
|
||||
'LionCub-Base': 5,
|
||||
'Fox-Base': 5,
|
||||
'FlyingPig-Base': 5,
|
||||
'Dragon-Base': 5,
|
||||
'Cactus-Base': 15,
|
||||
'BearCub-Base': 5,
|
||||
};
|
||||
user.items.eggs = {Wolf: 1};
|
||||
user.items.hatchingPotions = {Spooky: 1};
|
||||
hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}});
|
||||
expect(user.achievements.backToBasics).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,6 +49,7 @@ describe('shared.ops.rebirth', () => {
|
||||
let [, message] = rebirth(user);
|
||||
|
||||
expect(message).to.equal(i18n.t('rebirthComplete'));
|
||||
expect(user.flags.lastFreeRebirth).to.exist;
|
||||
});
|
||||
|
||||
it('rebirths a user with not enough gems but more than max level', () => {
|
||||
@@ -60,6 +61,16 @@ describe('shared.ops.rebirth', () => {
|
||||
expect(message).to.equal(i18n.t('rebirthComplete'));
|
||||
});
|
||||
|
||||
it('rebirths a user using gems if over max level but rebirthed recently', () => {
|
||||
user.stats.lvl = MAX_LEVEL + 1;
|
||||
user.flags.lastFreeRebirth = new Date();
|
||||
|
||||
let [, message] = rebirth(user);
|
||||
|
||||
expect(message).to.equal(i18n.t('rebirthComplete'));
|
||||
expect(user.balance).to.equal(0);
|
||||
});
|
||||
|
||||
it('resets user\'s tasks values except for rewards to 0', () => {
|
||||
tasks[0].value = 1;
|
||||
tasks[1].value = 1;
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
||||
// This is generate group + the ability to create
|
||||
// real users to populate it. The settings object
|
||||
// takes in:
|
||||
// members: Number - the number of group members to create. Defaults to 0.
|
||||
// members: Number - the number of group members to create. Defaults to 0. Does not include group leader.
|
||||
// inivtes: Number - the number of users to create and invite to the group. Defaults to 0.
|
||||
// groupDetails: Object - how to initialize the group
|
||||
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
||||
// This is generate group + the ability to create
|
||||
// real users to populate it. The settings object
|
||||
// takes in:
|
||||
// members: Number - the number of group members to create. Defaults to 0.
|
||||
// members: Number - the number of group members to create. Defaults to 0. Does not include group leader.
|
||||
// inivtes: Number - the number of users to create and invite to the group. Defaults to 0.
|
||||
// groupDetails: Object - how to initialize the group
|
||||
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
|
||||
|
||||
@@ -8,6 +8,7 @@ import mongo from './mongo'; // eslint-disable-line
|
||||
import moment from 'moment';
|
||||
import i18n from '../../website/common/script/i18n';
|
||||
import * as Tasks from '../../website/server/models/task';
|
||||
export { translationCheck } from './translate';
|
||||
|
||||
afterEach((done) => {
|
||||
sandbox.restore();
|
||||
|
||||
@@ -16,3 +16,9 @@ export function translate (key, variables, language) {
|
||||
|
||||
return translatedString;
|
||||
}
|
||||
|
||||
export function translationCheck (translatedString) {
|
||||
expect(translatedString).to.not.be.empty;
|
||||
expect(translatedString).to.not.eql(STRING_ERROR_MSG);
|
||||
expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -55,6 +57,7 @@ div
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#loading-screen-inapp {
|
||||
@@ -87,7 +90,6 @@ div
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -650,5 +656,6 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-23.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-24.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-25.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -1,72 +1,78 @@
|
||||
.promo_april_fools_2019 {
|
||||
.promo_armoire_backgrounds_201907 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -840px;
|
||||
background-position: -776px 0px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201904 {
|
||||
.promo_glass_watery_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -840px;
|
||||
background-position: -776px -148px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_butterflies {
|
||||
.promo_mystery_201907 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 676px;
|
||||
height: 676px;
|
||||
}
|
||||
.promo_celestial_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -433px -677px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_classes_spring2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -677px;
|
||||
width: 432px;
|
||||
height: 162px;
|
||||
}
|
||||
.promo_egg_hunt {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px 0px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201904 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -444px;
|
||||
background-position: -776px -444px;
|
||||
width: 282px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_orcas {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -376px -286px;
|
||||
width: 219px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonalshop_spring {
|
||||
.promo_seafoam {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -592px;
|
||||
background-position: -313px -473px;
|
||||
width: 425px;
|
||||
height: 148px;
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -596px -286px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
height: 132px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
.promo_splashy_pals_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -296px;
|
||||
width: 351px;
|
||||
background-position: -776px -296px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_avatar_customizations {
|
||||
.promo_splashy_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1005px -148px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
background-position: 0px -286px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -25px -301px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_summer_splash_2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -367px 0px;
|
||||
width: 408px;
|
||||
height: 186px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1168px -592px;
|
||||
background-position: -1059px -444px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_yesterdailies_repeatables {
|
||||
.scene_casting_spells {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -677px 0px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
background-position: 0px -473px;
|
||||
width: 312px;
|
||||
height: 222px;
|
||||
}
|
||||
.scene_tools {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 366px;
|
||||
height: 285px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 556 KiB After Width: | Height: | Size: 491 KiB |
|
Before Width: | Height: | Size: 570 KiB After Width: | Height: | Size: 610 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 414 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 118 KiB |