Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a094e13352 | |||
| 83376a38de | |||
| 58a9e4a439 | |||
| 84e2b2f45e | |||
| 97ea510a34 | |||
| 99610b4916 | |||
| 9a43b85492 | |||
| ecbf39cee4 | |||
| dd7fa73961 | |||
| 33a8072d23 | |||
| 213316d807 | |||
| 44cd4d0708 | |||
| 063b7a9af0 | |||
| c08b5a4f1e | |||
| 90117625d7 | |||
| 1b3dad749e | |||
| a622a3ebe3 | |||
| 2c83c16644 | |||
| 1034675184 | |||
| a420876697 | |||
| dc265e26b3 | |||
| b5203dda61 | |||
| 80e92a8767 | |||
| a265bfac9d | |||
| 92e4d5cd68 | |||
| a18e9b3b18 | |||
| c1a6ba6242 | |||
| ed761a8b7b | |||
| 81d5971829 | |||
| eb2d320d1f | |||
| 67538a368e | |||
| d55b95834d | |||
| 9ff9cd3b35 | |||
| b1f24de3c4 | |||
| 2ce2100f89 | |||
| dbaae4183e | |||
| 2009bb97cb | |||
| 3fc9501bac | |||
| 2c2ded2b70 | |||
| d689010e38 | |||
| e173b7784c | |||
| c3db59aae8 | |||
| 44e063c035 | |||
| 4e2c08cfed | |||
| c845c337df | |||
| 418b57f9fb | |||
| 9725da258e | |||
| 4191ea1968 | |||
| a9340ee60f | |||
| c8d874d28a | |||
| 32a22f1545 | |||
| 8ffe302a49 | |||
| 5c50a40f39 | |||
| 84329e5fad | |||
| 64507a161e | |||
| 0f7fc27663 | |||
| 1545685a5b | |||
| 410355c3f1 | |||
| ac27cabf6a | |||
| 972631e7ac | |||
| d27ed7c406 | |||
| 031783b1d7 | |||
| 318aa7cbd9 | |||
| f802a41f75 | |||
| 1d597039ca | |||
| 8153674dc0 | |||
| 0002148326 | |||
| d198e23de6 | |||
| 4f4e141806 | |||
| 05e8d6f032 | |||
| 39847893d2 | |||
| e4dbf09dda | |||
| eb99b709e0 | |||
| 862b3453f8 | |||
| 7f48853d32 | |||
| 5c4f763bb1 | |||
| bc9401b2f7 | |||
| 6fb9030b96 | |||
| ba307af963 | |||
| cf4b920a67 | |||
| b0ff35a8f1 | |||
| 85b4c7825e | |||
| 5b7ea8ec5c | |||
| 5cfd0c863e | |||
| 10c6244c0c | |||
| 20e65be8bf | |||
| 8bac324ba7 | |||
| 2ee0288aaa | |||
| b7ef4c50b2 | |||
| 52be9c750f | |||
| b0200026aa | |||
| e6c8b977c8 | |||
| c78b5ecf7c | |||
| f27e9b02d8 | |||
| c06c19ca41 | |||
| d5d894b8a9 | |||
| 7bd4e6a5a9 | |||
| f13eed5663 | |||
| a9a2fe6314 | |||
| d6514bce8b | |||
| 603fc8c4dd | |||
| 3c602351f9 | |||
| 29ed33461c | |||
| fbc1044100 | |||
| 35e02d2871 | |||
| 6aa204c3f5 | |||
| eaaa5ad7f3 | |||
| 54468ff499 | |||
| 53405aa586 | |||
| 7630c02e13 | |||
| ec444384f4 | |||
| cce9b33844 | |||
| b977d42402 | |||
| 2672cbd790 | |||
| b7ca5be6ee | |||
| 5ae89761b0 | |||
| 8b0101c74c | |||
| dbd295e35b | |||
| b5428f4ac9 | |||
| 5b213b4f94 | |||
| 0142e332e8 | |||
| b4f955333b | |||
| 696121fb24 | |||
| 2a7dfff88a | |||
| 2c921609c1 | |||
| 1f7dd421d4 | |||
| 45ca090105 | |||
| 02b22170e2 | |||
| 1134c7748b | |||
| 7019e32eed | |||
| 485c528b45 | |||
| f1c1ba8efa | |||
| b0e4c2cb11 | |||
| 0e346f7050 | |||
| 1eb1fe76a8 | |||
| 72a0e05804 | |||
| 5f37b9727a | |||
| bf17b49046 | |||
| 36edf5265f | |||
| 6da243e034 | |||
| b87ff03210 | |||
| 9d994f8a77 | |||
| 75c3f7214b | |||
| f3f8fa3a42 | |||
| 041bde0cba | |||
| 8888e63005 | |||
| 7aa2fac14a | |||
| 4493e1d98c |
@@ -2,9 +2,13 @@ version: "3"
|
||||
services:
|
||||
|
||||
client:
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
server:
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
volumes:
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const migrationName = 'mystery-items-201807.js'; // Update per month
|
||||
const migrationName = 'mystery-items-201808.js'; // Update per month
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201807', 'head_mystery_201807'];
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201808', 'head_mystery_201808'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
let migrationName = '20180801_takeThis.js'; // Update per month
|
||||
let migrationName = '20180904_takeThis.js'; // Update per month
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
@@ -15,7 +15,7 @@ function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
challenges: {$in: ['081f8912-3526-47d5-984f-f71bbeec77fc']}, // Update per month
|
||||
challenges: {$in: ['1044ec0c-4a85-48c5-9f36-d51c0c62c7d3']}, // Update per month
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.56.1",
|
||||
"version": "4.61.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -9,14 +9,14 @@
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"aws-sdk": "^2.317.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
@@ -26,48 +26,48 @@
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"bcrypt": "^2.0.0",
|
||||
"bcrypt": "github:MylesBorins/node.bcrypt.js#update-nan",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap": "^4.1.3",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
"compression": "^1.7.2",
|
||||
"compression": "^1.7.3",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^2.1.0",
|
||||
"csv-stringify": "^3.0.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.2.0",
|
||||
"express-validator": "^5.3.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"glob": "^7.1.2",
|
||||
"got": "^8.3.1",
|
||||
"glob": "^7.1.3",
|
||||
"got": "^9.2.2",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-nodemon": "^2.2.1",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"hellojs": "^1.17.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"image-size": "^0.6.2",
|
||||
"in-app-purchase": "^1.9.4",
|
||||
"image-size": "^0.6.3",
|
||||
"in-app-purchase": "^1.10.1",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": ">=3.0.0",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"lodash": "^4.17.10",
|
||||
"lodash": "^4.17.11",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.1.2",
|
||||
"morgan": "^1.7.0",
|
||||
"mongoose": "^5.2.15",
|
||||
"morgan": "^1.9.1",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^0.14.4",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"node-sass": "^4.9.3",
|
||||
"nodemailer": "^4.6.8",
|
||||
"ora": "^2.1.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
@@ -75,40 +75,41 @@
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"popper.js": "^1.14.3",
|
||||
"popper.js": "^1.14.4",
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.3",
|
||||
"pusher": "^1.3.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.0.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"shelljs": "^0.8.2",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^3.8.3",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"svgo": "^1.1.1",
|
||||
"svgo-loader": "^2.2.0",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"update": "^0.7.4",
|
||||
"upgrade": "^1.1.0",
|
||||
"url-loader": "^1.0.0",
|
||||
"url-loader": "^1.1.1",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^9.4.1",
|
||||
"uuid": "^3.3.2",
|
||||
"validator": "^10.7.1",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.5.16",
|
||||
"vue": "^2.5.17",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.4.2",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"webpack-merge": "^4.1.4",
|
||||
"winston": "^2.4.4",
|
||||
"winston-loggly-bulk": "^2.0.3",
|
||||
"xml2js": "^0.4.4"
|
||||
},
|
||||
"private": true,
|
||||
@@ -143,51 +144,51 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.16",
|
||||
"@vue/test-utils": "^1.0.0-beta.25",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.38.3",
|
||||
"chromedriver": "^2.41.0",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.1",
|
||||
"coveralls": "^3.0.2",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-habitrpg": "^4.0.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-html": "^4.0.3",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eslint-loader": "^2.1.0",
|
||||
"eslint-plugin-html": "^4.0.5",
|
||||
"eslint-plugin-mocha": "^5.2.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^2.0.2",
|
||||
"karma": "^3.0.0",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sinon-chai": "^1.3.4",
|
||||
"karma-sinon-chai": "^2.0.0",
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lcov-result-merger": "^2.0.0",
|
||||
"karma-webpack": "^3.0.5",
|
||||
"lcov-result-merger": "^3.0.0",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.4.0",
|
||||
"puppeteer": "^1.8.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"selenium-server": "^3.14.0",
|
||||
"sinon": "^4.5.0",
|
||||
"sinon-chai": "^3.0.0",
|
||||
"sinon-chai": "^3.2.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"webpack-bundle-analyzer": "^2.12.0",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-hot-middleware": "^2.22.2"
|
||||
"webpack-hot-middleware": "^2.24.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"memwatch-next": "^0.3.0",
|
||||
|
||||
@@ -65,6 +65,12 @@ describe('cron', () => {
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls analytics when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', () => {
|
||||
beforeEach(() => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -655,76 +661,6 @@ describe('cron', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('user is sleeping', () => {
|
||||
beforeEach(() => {
|
||||
user.preferences.sleep = true;
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('clears user buffs', () => {
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 1,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('resets all dailies without damaging user', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
let healthBefore = user.stats.hp;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
expect(user.stats.hp).to.equal(healthBefore);
|
||||
});
|
||||
|
||||
it('sets isDue for daily', () => {
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.dailys[0].isDue).to.be.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('todos', () => {
|
||||
beforeEach(() => {
|
||||
let todo = {
|
||||
@@ -846,6 +782,15 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
it('computes isDue when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().toDate();
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||
});
|
||||
|
||||
it('computes nextDue', () => {
|
||||
tasksByType.dailys[0].frequency = 'daily';
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
@@ -865,6 +810,13 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should set tasks completed to false when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys', () => {
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
@@ -872,6 +824,14 @@ describe('cron', () => {
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for completed dailys when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
tasksByType.dailys[0].completed = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('should reset task checklist for dailys with scheduled misses', () => {
|
||||
daysMissed = 10;
|
||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||
@@ -884,12 +844,19 @@ describe('cron', () => {
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
});
|
||||
|
||||
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
@@ -930,7 +897,7 @@ describe('cron', () => {
|
||||
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
|
||||
});
|
||||
|
||||
it('should decrement quest progress down for missing a daily', () => {
|
||||
it('should decrement quest.progress.down for missing a daily', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
@@ -939,6 +906,16 @@ describe('cron', () => {
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
|
||||
it('should not decrement quest.progress.down for missing a daily when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let progress = cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(progress.down).to.equal(0);
|
||||
});
|
||||
|
||||
it('should do damage for only yesterday\'s dailies', () => {
|
||||
daysMissed = 3;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1017,7 +994,7 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset habit counters even if user is resting in the Inn', () => {
|
||||
it('should reset habit counters even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
@@ -1278,7 +1255,23 @@ describe('cron', () => {
|
||||
expect(user.achievements.perfect).to.equal(0);
|
||||
});
|
||||
|
||||
it('increments user buffs if all (at least 1) due dailies were completed', () => {
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('gives perfect day buff if all (at least 1) due dailies were completed when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
@@ -1317,6 +1310,31 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (no due dailys) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).add({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
@@ -1341,7 +1359,50 @@ describe('cron', () => {
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
|
||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed) when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
user.stats.buffs = {
|
||||
str: 1,
|
||||
int: 1,
|
||||
per: 1,
|
||||
con: 1,
|
||||
stealth: 0,
|
||||
streaks: true,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
expect(user.stats.buffs.int).to.equal(0);
|
||||
expect(user.stats.buffs.per).to.equal(0);
|
||||
expect(user.stats.buffs.con).to.equal(0);
|
||||
expect(user.stats.buffs.stealth).to.equal(0);
|
||||
expect(user.stats.buffs.streaks).to.be.false;
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
tasksByType.dailys[0].completed = false;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cronOverride({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||
});
|
||||
|
||||
it('always grants a perfect day buff when CRON_SAFE_MODE is set when user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||
daysMissed = 1;
|
||||
@@ -1373,6 +1434,20 @@ describe('cron', () => {
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('should not add mp to user when user is sleeping', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
|
||||
user.preferences.sleep = true;
|
||||
let mpBefore = user.stats.mp;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, {maxMP: 100}));
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.stats.mp).to.equal(mpBefore);
|
||||
|
||||
common.statsComputed.restore();
|
||||
});
|
||||
|
||||
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
|
||||
const statsComputedRes = common.statsComputed(user);
|
||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||
@@ -1514,27 +1589,6 @@ describe('cron', () => {
|
||||
flagCount: 0,
|
||||
};
|
||||
});
|
||||
|
||||
xit('does not clear pms under 200', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.inbox.messages[lastMessageId]).to.exist;
|
||||
});
|
||||
|
||||
xit('clears pms over 200', () => {
|
||||
let messageId = common.uuid();
|
||||
user.inbox.messages[messageId] = {
|
||||
id: messageId,
|
||||
text: `test ${messageId}`,
|
||||
timestamp: Number(new Date()),
|
||||
likes: {},
|
||||
flags: {},
|
||||
flagCount: 0,
|
||||
};
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.inbox.messages[messageId]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('login incentives', () => {
|
||||
@@ -1568,7 +1622,7 @@ describe('cron', () => {
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
||||
it('increments loginIncentives by 1 even if user is sleeping', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
|
||||
@@ -107,6 +107,25 @@ describe('Password Utilities', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('defaults to SHA1 encryption if salt is provided', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let hashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
let user = {
|
||||
auth: {
|
||||
local: {
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
passwordHashMethod: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let isValidPassword = await compare(user, textPassword);
|
||||
expect(isValidPassword).to.eql(true);
|
||||
});
|
||||
|
||||
it('throws an error if an invalid hashing method is used', async () => {
|
||||
try {
|
||||
await compare({
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('slack', () => {
|
||||
title: 'Flag in Some group - (private guild)',
|
||||
title_link: undefined,
|
||||
text: 'some text',
|
||||
footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message.>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { TAVERN_ID } from '../../../../website/common/script/';
|
||||
import shared from '../../../../website/common';
|
||||
|
||||
describe('Group Model', () => {
|
||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||
let party, questLeader, participatingMember, sleepingParticipatingMember, nonParticipatingMember, undecidedMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox.stub(email, 'sendTxn');
|
||||
@@ -48,6 +48,11 @@ describe('Group Model', () => {
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Participating Member' },
|
||||
});
|
||||
sleepingParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Sleeping Participating Member' },
|
||||
preferences: { sleep: true },
|
||||
});
|
||||
nonParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Non-Participating Member' },
|
||||
@@ -61,6 +66,7 @@ describe('Group Model', () => {
|
||||
party.save(),
|
||||
questLeader.save(),
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
nonParticipatingMember.save(),
|
||||
undecidedMember.save(),
|
||||
]);
|
||||
@@ -80,6 +86,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -175,6 +182,34 @@ describe('Group Model', () => {
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(Group.prototype._processCollectionQuest).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('does not call _processBossQuest when user is resting in the inn', async () => {
|
||||
party.quest.key = 'whale';
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(sleepingParticipatingMember, progress);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(party._processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not call _processCollectionQuest when user is resting in the inn', async () => {
|
||||
party.quest.key = 'evilsanta2';
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(sleepingParticipatingMember, progress);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
|
||||
expect(party._processBossQuest).to.not.be.called;
|
||||
expect(party._processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
context('Boss Quests', () => {
|
||||
@@ -216,17 +251,20 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
updatedNonParticipatingMember,
|
||||
updatedUndecidedMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
User.findById(nonParticipatingMember._id),
|
||||
User.findById(undecidedMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.hp).to.eql(42.5);
|
||||
expect(updatedParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedNonParticipatingMember.stats.hp).to.eql(50);
|
||||
expect(updatedUndecidedMember.stats.hp).to.eql(50);
|
||||
});
|
||||
@@ -236,6 +274,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -248,17 +287,20 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
updatedNonParticipatingMember,
|
||||
updatedUndecidedMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
User.findById(nonParticipatingMember._id),
|
||||
User.findById(undecidedMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.hp).to.eql(42.5);
|
||||
expect(updatedParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedNonParticipatingMember.stats.hp).to.eql(50);
|
||||
expect(updatedUndecidedMember.stats.hp).to.eql(50);
|
||||
});
|
||||
@@ -497,9 +539,11 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.quests[party.quest.key]).to.eql(1);
|
||||
@@ -508,6 +552,9 @@ describe('Group Model', () => {
|
||||
expect(updatedParticipatingMember.achievements.quests[party.quest.key]).to.eql(1);
|
||||
expect(updatedParticipatingMember.stats.exp).to.be.greaterThan(0);
|
||||
expect(updatedParticipatingMember.stats.gp).to.be.greaterThan(0);
|
||||
expect(updatedSleepingParticipatingMember.achievements.quests[party.quest.key]).to.eql(1);
|
||||
expect(updatedSleepingParticipatingMember.stats.exp).to.be.greaterThan(0);
|
||||
expect(updatedSleepingParticipatingMember.stats.gp).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -647,6 +694,7 @@ describe('Group Model', () => {
|
||||
it('returns an array of members whose quest status set to true', () => {
|
||||
party.quest.members = {
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[questLeader._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
@@ -654,6 +702,7 @@ describe('Group Model', () => {
|
||||
|
||||
expect(party.getParticipatingQuestMembers()).to.eql([
|
||||
participatingMember._id,
|
||||
sleepingParticipatingMember._id,
|
||||
questLeader._id,
|
||||
]);
|
||||
});
|
||||
@@ -756,11 +805,12 @@ describe('Group Model', () => {
|
||||
it('removes user from group quest', async () => {
|
||||
party.quest.members = {
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[questLeader._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
party.memberCount = 4;
|
||||
party.memberCount = 5;
|
||||
await party.save();
|
||||
|
||||
await party.leave(participatingMember);
|
||||
@@ -768,6 +818,7 @@ describe('Group Model', () => {
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party.quest.members).to.eql({
|
||||
[questLeader._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
});
|
||||
@@ -775,6 +826,7 @@ describe('Group Model', () => {
|
||||
|
||||
it('deletes a private party when the last member leaves', async () => {
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(sleepingParticipatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
@@ -846,6 +898,7 @@ describe('Group Model', () => {
|
||||
party.privacy = 'public';
|
||||
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(sleepingParticipatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
@@ -1074,6 +1127,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -1130,6 +1184,7 @@ describe('Group Model', () => {
|
||||
let expectedQuestMembers = {};
|
||||
expectedQuestMembers[questLeader._id] = true;
|
||||
expectedQuestMembers[participatingMember._id] = true;
|
||||
expectedQuestMembers[sleepingParticipatingMember._id] = true;
|
||||
|
||||
expect(party.quest.members).to.eql(expectedQuestMembers);
|
||||
});
|
||||
@@ -1148,12 +1203,18 @@ describe('Group Model', () => {
|
||||
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
participatingMember = await User.findById(participatingMember._id);
|
||||
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
|
||||
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(questLeader.party.quest.key).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
@@ -1172,9 +1233,11 @@ describe('Group Model', () => {
|
||||
|
||||
it('sends email to participating members that quest has started', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = true;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = true;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1187,8 +1250,9 @@ describe('Group Model', () => {
|
||||
let memberIds = _.map(email.sendTxn.args[0][0], '_id');
|
||||
let typeOfEmail = email.sendTxn.args[0][1];
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(2);
|
||||
expect(memberIds).to.have.a.lengthOf(3);
|
||||
expect(memberIds).to.include(participatingMember._id);
|
||||
expect(memberIds).to.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
expect(typeOfEmail).to.eql('quest-started');
|
||||
});
|
||||
@@ -1202,6 +1266,13 @@ describe('Group Model', () => {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
sleepingParticipatingMember.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
questStarted: true,
|
||||
},
|
||||
}];
|
||||
questLeader.webhooks = [{
|
||||
type: 'questActivity',
|
||||
url: 'http://someurl.com',
|
||||
@@ -1210,13 +1281,13 @@ describe('Group Model', () => {
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
expect(questActivityWebhook.send).to.be.calledTwice; // for 2 participating members
|
||||
expect(questActivityWebhook.send).to.be.calledThrice; // for 3 participating members
|
||||
|
||||
let args = questActivityWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
@@ -1226,6 +1297,8 @@ describe('Group Model', () => {
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
if (webhookOwner === questLeader._id) {
|
||||
expect(webhooks[0].id).to.eql(questLeader.webhooks[0].id);
|
||||
} else if (webhookOwner === sleepingParticipatingMember._id) {
|
||||
expect(webhooks[0].id).to.eql(sleepingParticipatingMember.webhooks[0].id);
|
||||
} else {
|
||||
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
|
||||
}
|
||||
@@ -1236,9 +1309,11 @@ describe('Group Model', () => {
|
||||
|
||||
it('sends email only to members who have not opted out', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = false;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = false;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1252,14 +1327,17 @@ describe('Group Model', () => {
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(1);
|
||||
expect(memberIds).to.not.include(participatingMember._id);
|
||||
expect(memberIds).to.not.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
});
|
||||
|
||||
it('does not send email to initiating member', async () => {
|
||||
participatingMember.preferences.emailNotifications.questStarted = true;
|
||||
sleepingParticipatingMember.preferences.emailNotifications.questStarted = true;
|
||||
questLeader.preferences.emailNotifications.questStarted = true;
|
||||
await Promise.all([
|
||||
participatingMember.save(),
|
||||
sleepingParticipatingMember.save(),
|
||||
questLeader.save(),
|
||||
]);
|
||||
|
||||
@@ -1271,8 +1349,9 @@ describe('Group Model', () => {
|
||||
|
||||
let memberIds = _.map(email.sendTxn.args[0][0], '_id');
|
||||
|
||||
expect(memberIds).to.have.a.lengthOf(1);
|
||||
expect(memberIds).to.have.a.lengthOf(2);
|
||||
expect(memberIds).to.not.include(participatingMember._id);
|
||||
expect(memberIds).to.include(sleepingParticipatingMember._id);
|
||||
expect(memberIds).to.include(questLeader._id);
|
||||
});
|
||||
|
||||
@@ -1281,7 +1360,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
let members = [questLeader._id, participatingMember._id];
|
||||
let members = [questLeader._id, participatingMember._id, sleepingParticipatingMember._id];
|
||||
|
||||
expect(User.update).to.be.calledWith(
|
||||
{ _id: { $in: members } },
|
||||
@@ -1346,6 +1425,7 @@ describe('Group Model', () => {
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[sleepingParticipatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
@@ -1368,7 +1448,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
@@ -1378,7 +1458,7 @@ describe('Group Model', () => {
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update.callCount).to.equal(4);
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
@@ -1386,7 +1466,7 @@ describe('Group Model', () => {
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.update.callCount).to.eql(10);
|
||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1396,17 +1476,19 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.achievements.quests[quest.key]).to.eql(1);
|
||||
expect(updatedParticipatingMember.achievements.quests[quest.key]).to.eql(1);
|
||||
expect(updatedSleepingParticipatingMember.achievements.quests[quest.key]).to.eql(1);
|
||||
});
|
||||
|
||||
// Disable test, it fails on TravisCI, but only there
|
||||
xit('gives out super awesome Masterclasser achievement to the deserving', async () => {
|
||||
it('gives out super awesome Masterclasser achievement to the deserving', async () => {
|
||||
quest = questScrolls.lostMasterclasser4;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
@@ -1433,17 +1515,19 @@ describe('Group Model', () => {
|
||||
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.lostMasterclasser).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
// Disable test, it fails on TravisCI, but only there
|
||||
xit('gives out super awesome Masterclasser achievement when quests done out of order', async () => {
|
||||
it('gives out super awesome Masterclasser achievement when quests done out of order', async () => {
|
||||
quest = questScrolls.lostMasterclasser1;
|
||||
party.quest.key = quest.key;
|
||||
|
||||
@@ -1470,13 +1554,16 @@ describe('Group Model', () => {
|
||||
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.lostMasterclasser).to.eql(true);
|
||||
expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
|
||||
});
|
||||
|
||||
it('gives xp and gold', async () => {
|
||||
@@ -1485,15 +1572,19 @@ describe('Group Model', () => {
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedSleepingParticipatingMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(sleepingParticipatingMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedLeader.stats.gp).to.eql(quest.drop.gp);
|
||||
expect(updatedParticipatingMember.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedParticipatingMember.stats.gp).to.eql(quest.drop.gp);
|
||||
expect(updatedSleepingParticipatingMember.stats.exp).to.eql(quest.drop.exp);
|
||||
expect(updatedSleepingParticipatingMember.stats.gp).to.eql(quest.drop.gp);
|
||||
});
|
||||
|
||||
context('drops', () => {
|
||||
@@ -1593,13 +1684,16 @@ describe('Group Model', () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: sleepingParticipatingMember._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets user quest object to a clean state', async () => {
|
||||
@@ -1632,7 +1726,7 @@ describe('Group Model', () => {
|
||||
},
|
||||
}];
|
||||
|
||||
await Promise.all([participatingMember.save(), questLeader.save()]);
|
||||
await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
|
||||
@@ -63,45 +63,48 @@ describe('GET /challenges/:challengeId', () => {
|
||||
|
||||
context('private guild', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let members;
|
||||
let user;
|
||||
let nonMember;
|
||||
let otherMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
nonMember = await generateUser();
|
||||
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'guild', privacy: 'private'},
|
||||
members: 1,
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
challengeLeader = members[0];
|
||||
otherMember = members[1];
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
it('fails if user isn\'t in the guild and isn\'t challenge leader', async () => {
|
||||
await expect(nonMember.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
let chal = await members[0].get(`/challenges/${challenge._id}`);
|
||||
it('returns challenge data for any user in the guild', async () => {
|
||||
let chal = await otherMember.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
@@ -114,53 +117,72 @@ describe('GET /challenges/:challengeId', () => {
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns challenge data if challenge leader isn\'t in the guild or challenge', async () => {
|
||||
await challengeLeader.post(`/groups/${group._id}/leave`);
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.guilds).to.be.empty; // check that leaving worked
|
||||
|
||||
let chal = await challengeLeader.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('party', () => {
|
||||
let groupLeader;
|
||||
let challengeLeader;
|
||||
let group;
|
||||
let challenge;
|
||||
let members;
|
||||
let user;
|
||||
let nonMember;
|
||||
let otherMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
nonMember = await generateUser();
|
||||
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'party'},
|
||||
members: 1,
|
||||
groupDetails: {type: 'party', privacy: 'private'},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
members = populatedGroup.members;
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
await members[0].post(`/challenges/${challenge._id}/join`);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
challengeLeader = members[0];
|
||||
otherMember = members[1];
|
||||
|
||||
challenge = await generateChallenge(challengeLeader, group);
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
it('fails if user isn\'t in the party and isn\'t challenge leader', async () => {
|
||||
await expect(nonMember.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return challenge data', async () => {
|
||||
let chal = await members[0].get(`/challenges/${challenge._id}`);
|
||||
it('returns challenge data for any user in the party', async () => {
|
||||
let chal = await otherMember.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader.id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
expect(chal.group).to.eql({
|
||||
_id: group._id,
|
||||
id: group.id,
|
||||
id: group._id,
|
||||
categories: [],
|
||||
name: group.name,
|
||||
summary: group.name,
|
||||
@@ -169,5 +191,21 @@ describe('GET /challenges/:challengeId', () => {
|
||||
leader: groupLeader.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns challenge data if challenge leader isn\'t in the party or challenge', async () => {
|
||||
await challengeLeader.post('/groups/party/leave');
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.party._id).to.be.undefined; // check that leaving worked
|
||||
|
||||
let chal = await challengeLeader.get(`/challenges/${challenge._id}`);
|
||||
expect(chal.name).to.equal(challenge.name);
|
||||
expect(chal._id).to.equal(challenge._id);
|
||||
|
||||
expect(chal.leader).to.eql({
|
||||
_id: challengeLeader._id,
|
||||
id: challengeLeader._id,
|
||||
profile: {name: challengeLeader.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
createAndPopulateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -10,7 +11,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({ balance: 1 });
|
||||
});
|
||||
|
||||
it('validates optional req.query.lastId to be an UUID', async () => {
|
||||
@@ -21,7 +22,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if challenge doesn\'t exists', async () => {
|
||||
it('fails if challenge doesn\'t exist', async () => {
|
||||
await expect(user.get(`/challenges/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
@@ -29,8 +30,8 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if user doesn\'t have access to the challenge', async () => {
|
||||
let group = await generateGroup(user);
|
||||
it('fails if user isn\'t in the private group and isn\'t challenge leader', async () => {
|
||||
let group = await generateGroup(user, {type: 'party', privacy: 'private'});
|
||||
let challenge = await generateChallenge(user, group);
|
||||
let anotherUser = await generateUser();
|
||||
|
||||
@@ -41,6 +42,27 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works if user isn\'t in the private group but is challenge leader', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
groupDetails: {type: 'party', privacy: 'private'},
|
||||
members: 1,
|
||||
});
|
||||
let groupLeader = populatedGroup.groupLeader;
|
||||
let challengeLeader = populatedGroup.members[0];
|
||||
let challenge = await generateChallenge(challengeLeader, populatedGroup.group);
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
await challengeLeader.post('/groups/party/leave');
|
||||
await challengeLeader.sync();
|
||||
expect(challengeLeader.party._id).to.be.undefined; // check that leaving worked
|
||||
|
||||
let res = await challengeLeader.get(`/challenges/${challenge._id}/members`);
|
||||
expect(res[0]).to.eql({
|
||||
_id: groupLeader._id,
|
||||
id: groupLeader._id,
|
||||
profile: {name: groupLeader.profile.name},
|
||||
});
|
||||
});
|
||||
|
||||
it('works with challenges belonging to public guild', async () => {
|
||||
let leader = await generateUser({balance: 4});
|
||||
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||
|
||||
@@ -94,16 +94,6 @@ describe('POST /challenges', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => {
|
||||
await expect(groupMember.post('/challenges', {
|
||||
group: group._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderChal'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows non-leader member to create a challenge', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
});
|
||||
|
||||
it('returns an error when user doesn\'t have permissions to access the challenge', async () => {
|
||||
it('returns an error when user isn\'t in the private group and isn\'t challenge leader', async () => {
|
||||
let unauthorizedUser = await generateUser();
|
||||
|
||||
await expect(unauthorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||
@@ -56,6 +56,16 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when user isn\'t in the private group but is challenge leader', async () => {
|
||||
await groupLeader.post(`/challenges/${challenge._id}/leave`);
|
||||
await groupLeader.post(`/groups/${group._id}/leave`);
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.guilds).to.be.empty; // check that leaving worked
|
||||
|
||||
let res = await groupLeader.post(`/challenges/${challenge._id}/join`);
|
||||
expect(res.name).to.equal(challenge.name);
|
||||
});
|
||||
|
||||
it('returns challenge data', async () => {
|
||||
let res = await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
|
||||
@@ -3,15 +3,23 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat/:chatId/flag', () => {
|
||||
let user, admin, anotherUser, group;
|
||||
let user, admin, anotherUser, newUser, group;
|
||||
const TEST_MESSAGE = 'Test Message';
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 1});
|
||||
user = await generateUser({balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
|
||||
admin = await generateUser({balance: 1, 'contributor.admin': true});
|
||||
anotherUser = await generateUser();
|
||||
anotherUser = await generateUser({'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
|
||||
newUser = await generateUser({'auth.timestamps.created': moment().subtract(1, 'days').toDate()});
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
|
||||
group = await user.post('/groups', {
|
||||
name: 'Test Guild',
|
||||
@@ -20,6 +28,10 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('Returns an error when chat message is not found', async () => {
|
||||
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
@@ -34,7 +46,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Flags a chat', async () => {
|
||||
it('Flags a chat and sends normal message to moderator Slack when user is not new', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
@@ -45,6 +57,62 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[user._id]).to.equal(true);
|
||||
|
||||
// Slack message to mods
|
||||
const timestamp = `${moment(message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
|
||||
let automatedComment = `The post's flag count has not been increased because the flagger's account is less than ${USER_AGE_FOR_FLAGGING} days old.`;
|
||||
let { message } = await newUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await newUser.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
expect(flagResult.flags[newUser._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(0);
|
||||
|
||||
let groupWithFlags = await admin.get(`/groups/${group._id}`);
|
||||
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
expect(messageToCheck.flags[newUser._id]).to.equal(true);
|
||||
|
||||
// Slack message to mods
|
||||
const timestamp = `${moment(message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-ensable camelcase */
|
||||
});
|
||||
|
||||
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||
@@ -117,7 +185,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a message that is already flagged', async () => {
|
||||
it('Returns an error when user tries to flag a message that they already flagged', async () => {
|
||||
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
@@ -15,8 +17,6 @@ import { getMatchesByWordArray } from '../../../../../website/server/libs/string
|
||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
@@ -80,12 +80,14 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
|
||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
describe('mute user', () => {
|
||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||
const userWithChatRevoked = await member.update({'flags.chatRevoked': true});
|
||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -259,7 +261,6 @@ describe('POST /chat', () => {
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
@@ -274,6 +275,7 @@ describe('POST /chat', () => {
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
|
||||
// @TODO: The next test should not depend on this. We should reset the user test in a beforeEach
|
||||
// Restore chat privileges to continue testing
|
||||
user.flags.chatRevoked = false;
|
||||
await user.update({'flags.chatRevoked': false});
|
||||
@@ -312,7 +314,6 @@ describe('POST /chat', () => {
|
||||
title: 'Slur in Party - (private party)',
|
||||
title_link: undefined,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
|
||||
@@ -4,23 +4,24 @@ import {
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import config from '../../../../../config.json';
|
||||
import moment from 'moment';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
const USER_AGE_FOR_FLAGGING = 3;
|
||||
let groupWithChat, message, author, nonAdmin, admin;
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
groupWithChat = group;
|
||||
author = groupLeader;
|
||||
nonAdmin = members[0];
|
||||
nonAdmin = await generateUser({'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
|
||||
admin = await generateUser({'contributor.admin': true});
|
||||
|
||||
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||
@@ -69,9 +70,14 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
privateMessage = privateMessage.message;
|
||||
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
|
||||
|
||||
// first test that the flag was actually successful
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(5);
|
||||
|
||||
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/clearflags`);
|
||||
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /inbox/messages', () => {
|
||||
let user;
|
||||
@@ -22,17 +22,26 @@ describe('GET /inbox/messages', () => {
|
||||
message: 'third',
|
||||
});
|
||||
|
||||
// message to yourself
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
});
|
||||
|
||||
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
|
||||
const messages = await user.get('/inbox/messages');
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
expect(messages.length).to.equal(Object.keys(user.inbox.messages).length);
|
||||
expect(messages.length).to.equal(4);
|
||||
|
||||
expect(messages[0].text).to.equal('third');
|
||||
expect(messages[1].text).to.equal('second');
|
||||
expect(messages[2].text).to.equal('first');
|
||||
// message to yourself
|
||||
expect(messages[0].text).to.equal('fourth');
|
||||
expect(messages[0].uuid).to.equal(user._id);
|
||||
|
||||
expect(messages[1].text).to.equal('third');
|
||||
expect(messages[2].text).to.equal('second');
|
||||
expect(messages[3].text).to.equal('first');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -100,7 +100,7 @@ describe('POST /members/send-private-message', () => {
|
||||
let receiver = await generateUser();
|
||||
// const initialNotifications = receiver.notifications.length;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
@@ -116,6 +116,9 @@ describe('POST /members/send-private-message', () => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(response.message.text).to.deep.equal(sendersMessageInSendersInbox.text);
|
||||
expect(response.message.uuid).to.deep.equal(sendersMessageInSendersInbox.uuid);
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
|
||||
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||
|
||||
describe('payments - stripe - #subscribeCancel', () => {
|
||||
let endpoint = '/stripe/subscribe/cancel?redirect=none';
|
||||
let endpoint = '/stripe/subscribe/cancel?noRedirect=true';
|
||||
let user, group, stripeCancelSubscriptionStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -3,25 +3,41 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('DELETE user message', () => {
|
||||
let user;
|
||||
let user, messagesId, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({ inbox: { messages: { first: 'message', second: 'message' } } });
|
||||
expect(user.inbox.messages.first).to.eql('message');
|
||||
expect(user.inbox.messages.second).to.eql('message');
|
||||
before(async () => {
|
||||
[user, otherUser] = await Promise.all([generateUser(), generateUser()]);
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
|
||||
let userRes = await user.get('/user');
|
||||
|
||||
messagesId = Object.keys(userRes.inbox.messages);
|
||||
expect(messagesId.length).to.eql(2);
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first');
|
||||
expect(userRes.inbox.messages[messagesId[1]].text).to.eql('second');
|
||||
});
|
||||
|
||||
it('one message', async () => {
|
||||
let result = await user.del('/user/messages/first');
|
||||
await user.sync();
|
||||
expect(result).to.eql({ second: 'message' });
|
||||
expect(user.inbox.messages).to.eql({ second: 'message' });
|
||||
let result = await user.del(`/user/messages/${messagesId[0]}`);
|
||||
messagesId = Object.keys(result);
|
||||
expect(messagesId.length).to.eql(1);
|
||||
|
||||
let userRes = await user.get('/user');
|
||||
expect(Object.keys(userRes.inbox.messages).length).to.eql(1);
|
||||
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second');
|
||||
});
|
||||
|
||||
it('clear all', async () => {
|
||||
let result = await user.del('/user/messages');
|
||||
await user.sync();
|
||||
expect(user.inbox.messages).to.eql({});
|
||||
let userRes = await user.get('/user');
|
||||
expect(userRes.inbox.messages).to.eql({});
|
||||
expect(result).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,21 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if use Healing Light spell with full health', async () => {
|
||||
await user.update({
|
||||
'stats.class': 'healer',
|
||||
'stats.lvl': 11,
|
||||
'stats.hp': 50,
|
||||
'stats.mp': 200,
|
||||
});
|
||||
await expect(user.post('/user/class/cast/heal'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('messageHealthAlreadyMax'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if spell.lvl > user.level', async () => {
|
||||
await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
|
||||
await expect(user.post('/user/class/cast/earth'))
|
||||
|
||||
@@ -50,11 +50,24 @@ describe('POST /user/push-devices', () => {
|
||||
});
|
||||
|
||||
it('adds a push device to the user', async () => {
|
||||
let response = await user.post('/user/push-devices', {type, regId});
|
||||
const response = await user.post('/user/push-devices', {type, regId});
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('pushDeviceAdded'));
|
||||
expect(response.data[0].type).to.equal(type);
|
||||
expect(response.data[0].regId).to.equal(regId);
|
||||
expect(user.pushDevices[0].type).to.equal(type);
|
||||
expect(user.pushDevices[0].regId).to.equal(regId);
|
||||
});
|
||||
|
||||
it('removes a push device to the user', async () => {
|
||||
await user.post('/user/push-devices', {type, regId});
|
||||
|
||||
const response = await user.del(`/user/push-devices/${regId}`);
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('pushDeviceRemoved'));
|
||||
expect(response.data[0]).to.not.exist;
|
||||
expect(user.pushDevices[0]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('DELETE /inbox/messages/:messageId', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
|
||||
before(async () => {
|
||||
[user, otherUser] = await Promise.all([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 otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'third',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the messageId parameter is not an UUID', async () => {
|
||||
await expect(user.del('/inbox/messages/123'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Invalid request parameters.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if the message does not exist', async () => {
|
||||
await expect(user.del(`/inbox/messages/${generateUUID()}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageGroupChatNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes one message', async () => {
|
||||
const messages = await user.get('/inbox/messages');
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
|
||||
expect(messages[0].text).to.equal('third');
|
||||
expect(messages[1].text).to.equal('second');
|
||||
expect(messages[2].text).to.equal('first');
|
||||
|
||||
await user.del(`/inbox/messages/${messages[1]._id}`);
|
||||
const updatedMessages = await user.get('/inbox/messages');
|
||||
expect(updatedMessages.length).to.equal(2);
|
||||
|
||||
expect(updatedMessages[0].text).to.equal('third');
|
||||
expect(updatedMessages[1].text).to.equal('first');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import NotificationsComponent from 'client/components/notifications.vue';
|
||||
import Store from 'client/libs/store';
|
||||
import { hasClass } from 'client/store/getters/members';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Store);
|
||||
|
||||
describe('Notifications', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
stats: {
|
||||
lvl: 0,
|
||||
},
|
||||
flags: {},
|
||||
preferences: {},
|
||||
party: {
|
||||
quest: {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
'user:fetch': () => {},
|
||||
'tasks:fetchUserTasks': () => {},
|
||||
},
|
||||
getters: {
|
||||
'members:hasClass': hasClass,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('set user has class computed prop', () => {
|
||||
const wrapper = shallowMount(NotificationsComponent, { store, localVue });
|
||||
|
||||
expect(wrapper.vm.userHasClass).to.be.false;
|
||||
|
||||
store.state.user.data.stats.lvl = 10;
|
||||
store.state.user.data.flags.classSelected = true;
|
||||
store.state.user.data.preferences.disableClasses = false;
|
||||
|
||||
expect(wrapper.vm.userHasClass).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
import { shallow } from '@vue/test-utils';
|
||||
|
||||
import SidebarSection from 'client/components/sidebarSection.vue';
|
||||
|
||||
@@ -51,4 +51,4 @@ describe('Sidebar Section', () => {
|
||||
|
||||
expect(wrapper.find('.section-body').element.style.display).to.eq('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('Task Column', () => {
|
||||
expect(el).to.eq(taskListOverride[i]);
|
||||
});
|
||||
|
||||
wrapper.setProps({ isUser: false, taskListOverride });
|
||||
wrapper.setProps({ isUser: false });
|
||||
|
||||
wrapper.vm.taskList.forEach((el, i) => {
|
||||
expect(el).to.eq(taskListOverride[i]);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import spells from '../../../website/common/script/content/spells';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
|
||||
// TODO complete the test suite...
|
||||
|
||||
describe('shared.ops.spells', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when healer tries to cast Healing Light with full health', (done) => {
|
||||
user.stats.class = 'healer';
|
||||
user.stats.lvl = 11;
|
||||
user.stats.hp = 50;
|
||||
user.stats.mp = 200;
|
||||
|
||||
let spell = spells.healer.heal;
|
||||
|
||||
try {
|
||||
spell.cast(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax'));
|
||||
expect(user.stats.hp).to.eql(50);
|
||||
expect(user.stats.mp).to.eql(200);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,6 @@ div
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="customPurchase($event)",
|
||||
:genericPurchase="genericPurchase(selectedItemToBuy)",
|
||||
|
||||
@@ -103,7 +102,7 @@ div
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
@@ -567,13 +566,6 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
// @TODO: Do we need this? I think selecting a new item
|
||||
// overwrites. @negue might know
|
||||
if (!$event && this.selectedItemToBuy.purchaseType !== 'card') {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
itemSelected (item) {
|
||||
this.selectedItemToBuy = item;
|
||||
},
|
||||
@@ -659,3 +651,4 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -1,48 +1,78 @@
|
||||
.promo_armoire_backgrounds_201808 {
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -365px;
|
||||
background-position: -284px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201809 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_ember_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -699px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_fall_festival_2017 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px 0px;
|
||||
width: 414px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_fall_festival_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -788px -211px;
|
||||
width: 393px;
|
||||
height: 213px;
|
||||
}
|
||||
.promo_forest_friends_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -426px -699px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -477px 0px;
|
||||
background-position: 0px -337px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_mystery_201807 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -561px;
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.promo_seaserpent {
|
||||
.promo_kangaroo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 476px;
|
||||
height: 364px;
|
||||
width: 420px;
|
||||
height: 336px;
|
||||
}
|
||||
.promo_mystery_201808 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -286px;
|
||||
width: 78px;
|
||||
height: 81px;
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -969px -425px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -257px -561px;
|
||||
background-position: -788px -606px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -591px -365px;
|
||||
background-position: -788px -425px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_rewards {
|
||||
.scene_tools {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -383px -365px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -365px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
background-position: -421px 0px;
|
||||
width: 366px;
|
||||
height: 285px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 549 KiB After Width: | Height: | Size: 551 KiB |
|
Before Width: | Height: | Size: 449 KiB After Width: | Height: | Size: 463 KiB |
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 116 KiB |
@@ -2,8 +2,8 @@
|
||||
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
|
||||
// more to be added on future seasons
|
||||
|
||||
$npc_market_flavor: 'normal';
|
||||
$npc_quests_flavor: 'normal';
|
||||
$npc_seasonal_flavor: 'normal';
|
||||
$npc_timetravelers_flavor: 'normal';
|
||||
$npc_tavern_flavor: 'normal';
|
||||
$npc_market_flavor: 'fall';
|
||||
$npc_quests_flavor: 'fall';
|
||||
$npc_seasonal_flavor: 'fall';
|
||||
$npc_timetravelers_flavor: 'fall';
|
||||
$npc_tavern_flavor: 'fall';
|
||||
|
||||
@@ -164,30 +164,30 @@ export default {
|
||||
classGear (heroClass) {
|
||||
if (heroClass === 'rogue') {
|
||||
return {
|
||||
armor: 'armor_rogue_5',
|
||||
head: 'head_rogue_5',
|
||||
shield: 'shield_rogue_6',
|
||||
weapon: 'weapon_rogue_6',
|
||||
armor: 'armor_special_fall2018Rogue',
|
||||
head: 'head_special_fall2018Rogue',
|
||||
shield: 'shield_special_fall2018Rogue',
|
||||
weapon: 'weapon_special_fall2018Rogue',
|
||||
};
|
||||
} else if (heroClass === 'wizard') {
|
||||
return {
|
||||
armor: 'armor_wizard_5',
|
||||
head: 'head_wizard_5',
|
||||
weapon: 'weapon_wizard_6',
|
||||
armor: 'armor_special_fall2018Mage',
|
||||
head: 'head_special_fall2018Mage',
|
||||
weapon: 'weapon_special_fall2018Mage',
|
||||
};
|
||||
} else if (heroClass === 'healer') {
|
||||
return {
|
||||
armor: 'armor_healer_5',
|
||||
head: 'head_healer_5',
|
||||
shield: 'shield_healer_5',
|
||||
weapon: 'weapon_healer_6',
|
||||
armor: 'armor_special_fall2018Healer',
|
||||
head: 'head_special_fall2018Healer',
|
||||
shield: 'shield_special_fall2018Healer',
|
||||
weapon: 'weapon_special_fall2018Healer',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
armor: 'armor_warrior_5',
|
||||
head: 'head_warrior_5',
|
||||
shield: 'shield_warrior_5',
|
||||
weapon: 'weapon_warrior_6',
|
||||
armor: 'armor_special_fall2018Warrior',
|
||||
head: 'head_special_fall2018Warrior',
|
||||
shield: 'shield_special_fall2018Warrior',
|
||||
weapon: 'weapon_special_fall2018Warrior',
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,21 +6,21 @@ b-modal#login-incentives(:title="data.message", size='md', :hide-footer="true")
|
||||
.row.reward-row
|
||||
.col-12
|
||||
avatar.avatar(:member='user', :avatarOnly='true', :withBackground='true')
|
||||
.text-center.col-12
|
||||
.text-center.col-12(v-if='nextReward')
|
||||
.reward-wrap(v-if="!data.rewardText")
|
||||
div(v-if="nextReward.rewardKey.length === 1", :class="nextReward.rewardKey[0]")
|
||||
.reward(v-for="reward in nextReward.rewardKey", v-if="nextReward.rewardKey.length > 1", :class='reward')
|
||||
.reward-wrap(v-if="data.rewardText")
|
||||
div(v-if="data.rewardKey.length === 1", :class="data.rewardKey[0]")
|
||||
.reward(v-for="reward in data.rewardKey", v-if="data.rewardKey.length > 1", :class='reward')
|
||||
.col-12.text-center(v-if="data.nextRewardAt")
|
||||
.col-12.text-center(v-if="data && data.nextRewardAt")
|
||||
h4 {{ $t('countLeft', {count: data.nextRewardAt - user.loginIncentives}) }}
|
||||
.row
|
||||
.col-12.text-center(v-if='data.rewardText')
|
||||
p {{ $t('earnedRewardForDevotion', {reward: data.rewardText}) }}
|
||||
.col-12.text-center
|
||||
p {{ $t('incentivesDescription') }}
|
||||
.col-12.text-center(v-if="data.nextRewardAt")
|
||||
.col-12.text-center(v-if="data && data.nextRewardAt")
|
||||
h3 {{ $t('nextRewardUnlocksIn', {numberOfCheckinsLeft: data.nextRewardAt - user.loginIncentives}) }}
|
||||
.modal-footer
|
||||
.col-12.text-center
|
||||
@@ -70,8 +70,9 @@ export default {
|
||||
user: 'user.data',
|
||||
}),
|
||||
nextReward () {
|
||||
let nextRewardKey = this.loginIncentives[this.user.loginIncentives].nextRewardAt;
|
||||
let nextReward = this.loginIncentives[nextRewardKey];
|
||||
if (!this.loginIncentives[this.user.loginIncentives]) return;
|
||||
const nextRewardKey = this.loginIncentives[this.user.loginIncentives].nextRewardAt;
|
||||
const nextReward = this.loginIncentives[nextRewardKey];
|
||||
return nextReward;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
|
||||
.social-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: inherit;
|
||||
text-align: center;
|
||||
|
||||
.text {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.col-12.col-md-6
|
||||
.btn.btn-secondary.social-button(@click='socialAuth("google")')
|
||||
.svg-icon.social-icon(v-html="icons.googleIcon")
|
||||
span {{registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
.text {{registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
.form-group(v-if='registering')
|
||||
label(for='usernameInput', v-once) {{$t('username')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username')
|
||||
@@ -207,6 +207,8 @@
|
||||
|
||||
.social-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: inherit;
|
||||
text-align: center;
|
||||
|
||||
.text {
|
||||
|
||||
@@ -15,22 +15,22 @@
|
||||
|
||||
// Show avatar only if not currently affected by visual buff
|
||||
template(v-if="showAvatar()")
|
||||
span(:class="'chair_' + member.preferences.chair")
|
||||
span(:class="getGearClass('back')")
|
||||
span(:class="skinClass")
|
||||
span(:class="member.preferences.size + '_shirt_' + member.preferences.shirt")
|
||||
span.head_0
|
||||
span(:class="member.preferences.size + '_' + getGearClass('armor')")
|
||||
span(:class="getGearClass('back_collar')")
|
||||
span(:class="['chair_' + member.preferences.chair, specialMountClass]")
|
||||
span(:class="[getGearClass('back'), specialMountClass]")
|
||||
span(:class="[skinClass, specialMountClass]")
|
||||
span(:class="[member.preferences.size + '_shirt_' + member.preferences.shirt, specialMountClass]")
|
||||
span(:class="['head_0', specialMountClass]")
|
||||
span(:class="[member.preferences.size + '_' + getGearClass('armor'), specialMountClass]")
|
||||
span(:class="[getGearClass('back_collar'), specialMountClass]")
|
||||
template(v-for="type in ['bangs', 'base', 'mustache', 'beard']")
|
||||
span(:class="'hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color")
|
||||
span(:class="getGearClass('body')")
|
||||
span(:class="getGearClass('eyewear')")
|
||||
span(:class="getGearClass('head')")
|
||||
span(:class="getGearClass('headAccessory')")
|
||||
span(:class="'hair_flower_' + member.preferences.hair.flower")
|
||||
span(v-if="!hideGear('shield')", :class="getGearClass('shield')")
|
||||
span(v-if="!hideGear('weapon')", :class="getGearClass('weapon')")
|
||||
span(:class="['hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color, specialMountClass]")
|
||||
span(:class="[getGearClass('body'), specialMountClass]")
|
||||
span(:class="[getGearClass('eyewear'), specialMountClass]")
|
||||
span(:class="[getGearClass('head'), specialMountClass]")
|
||||
span(:class="[getGearClass('headAccessory'), specialMountClass]")
|
||||
span(:class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]")
|
||||
span(v-if="!hideGear('shield')", :class="[getGearClass('shield'), specialMountClass]")
|
||||
span(v-if="!hideGear('weapon')", :class="[getGearClass('weapon'), specialMountClass]")
|
||||
|
||||
// Resting
|
||||
span.zzz(v-if="member.preferences.sleep")
|
||||
@@ -67,6 +67,10 @@
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.offset-kangaroo {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -172,6 +176,11 @@ export default {
|
||||
costumeClass () {
|
||||
return this.member.preferences.costume ? 'costume' : 'equipped';
|
||||
},
|
||||
specialMountClass () {
|
||||
if (!this.avatarOnly && this.member.items.currentMount && this.member.items.currentMount.indexOf('Kangaroo') !== -1) {
|
||||
return 'offset-kangaroo';
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGearClass (gearType) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.row
|
||||
challenge-modal(v-on:updatedChallenge='updatedChallenge')
|
||||
leave-challenge-modal(:challengeId='challenge._id')
|
||||
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
||||
close-challenge-modal(:members='members', :challengeId='challenge._id', :prize='challenge.prize')
|
||||
challenge-member-progress-modal(:challengeId='challenge._id')
|
||||
.col-12.col-md-8.standard-page
|
||||
.row
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
div.category-wrap(@click.prevent="toggleCategorySelect")
|
||||
span.category-select(v-if='workingChallenge.categories.length === 0') {{$t('none')}}
|
||||
.category-label(v-for='category in workingChallenge.categories') {{$t(categoriesHashByKey[category])}}
|
||||
.category-box(v-if="showCategorySelect")
|
||||
.category-box(v-if="showCategorySelect && creating")
|
||||
.form-check(
|
||||
v-for="group in categoryOptions",
|
||||
:key="group.key",
|
||||
|
||||
@@ -74,7 +74,7 @@ div
|
||||
import memberSearchDropdown from 'client/components/members/memberSearchDropdown';
|
||||
|
||||
export default {
|
||||
props: ['challengeId', 'members'],
|
||||
props: ['challengeId', 'members', 'prize'],
|
||||
components: {
|
||||
memberSearchDropdown,
|
||||
},
|
||||
@@ -102,7 +102,10 @@ export default {
|
||||
},
|
||||
async deleteChallenge () {
|
||||
if (!confirm('Are you sure you want to delete this challenge?')) return;
|
||||
this.challenge = await this.$store.dispatch('challenges:deleteChallenge', {challengeId: this.challengeId});
|
||||
this.challenge = await this.$store.dispatch('challenges:deleteChallenge', {
|
||||
challengeId: this.challengeId,
|
||||
prize: this.prize,
|
||||
});
|
||||
this.$router.push('/challenges/myChallenges');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,30 +7,31 @@ div
|
||||
h3.leader(
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
v-b-tooltip.hover.top="tierTitle",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="tierIcon", v-if='showShowTierStyle')
|
||||
p.time(v-b-tooltip="", :title="msg.timestamp | date") {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
.action(@click='like()', v-if='!inbox && msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
.svg-icon(v-html="icons.like")
|
||||
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
|
||||
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
|
||||
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
|
||||
.svg-icon(v-html="icons.copy")
|
||||
| {{$t('copyAsTodo')}}
|
||||
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
|
||||
.svg-icon(v-html="icons.report")
|
||||
| {{$t('report')}}
|
||||
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
|
||||
span.action(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove()')
|
||||
.svg-icon(v-html="icons.delete")
|
||||
| {{$t('delete')}}
|
||||
span.action.float-right.liked(v-if='likeCount > 0')
|
||||
.svg-icon(v-html="icons.liked")
|
||||
| + {{ likeCount }}
|
||||
div(v-if='msg.id')
|
||||
.action(@click='like()', v-if='!inbox && msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
.svg-icon(v-html="icons.like")
|
||||
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
|
||||
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
|
||||
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
|
||||
.svg-icon(v-html="icons.copy")
|
||||
| {{$t('copyAsTodo')}}
|
||||
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
|
||||
.svg-icon(v-html="icons.report")
|
||||
| {{$t('report')}}
|
||||
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
|
||||
span.action(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove()')
|
||||
.svg-icon(v-html="icons.delete")
|
||||
| {{$t('delete')}}
|
||||
span.action.float-right.liked(v-if='likeCount > 0')
|
||||
.svg-icon(v-html="icons.liked")
|
||||
| + {{ likeCount }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -118,6 +119,8 @@ import markdownDirective from 'client/directives/markdown';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import styleHelper from 'client/mixins/styleHelper';
|
||||
|
||||
import achievementsLib from '../../../common/script/libs/achievements';
|
||||
|
||||
import deleteIcon from 'assets/svg/delete.svg';
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import likeIcon from 'assets/svg/like.svg';
|
||||
@@ -220,6 +223,10 @@ export default {
|
||||
}
|
||||
return this.icons[`tier${message.contributor.level}`];
|
||||
},
|
||||
tierTitle () {
|
||||
const message = this.msg;
|
||||
return achievementsLib.getContribText(message.contributor, message.backer) || '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async like () {
|
||||
@@ -254,8 +261,7 @@ export default {
|
||||
this.$emit('message-removed', message);
|
||||
|
||||
if (this.inbox) {
|
||||
axios.delete(`/api/v4/user/messages/${message.id}`);
|
||||
this.$delete(this.user.inbox.messages, message.id);
|
||||
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.container
|
||||
.row
|
||||
.col-12
|
||||
copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
|
||||
copy-as-todo-modal(:group-type='groupType', :group-name='groupName', :group-id='groupId')
|
||||
report-flag-modal
|
||||
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
|
||||
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
|
||||
@@ -87,7 +87,7 @@ import reportFlagModal from './reportFlagModal';
|
||||
import chatCard from './chatCard';
|
||||
|
||||
export default {
|
||||
props: ['chat', 'groupId', 'groupName', 'inbox'],
|
||||
props: ['chat', 'groupType', 'groupId', 'groupName', 'inbox'],
|
||||
components: {
|
||||
copyAsTodoModal,
|
||||
reportFlagModal,
|
||||
@@ -202,8 +202,17 @@ export default {
|
||||
|
||||
if (!profile._id) {
|
||||
const result = await this.$store.dispatch('members:fetchMember', { memberId });
|
||||
this.cachedProfileData[memberId] = result.data.data;
|
||||
profile = result.data.data;
|
||||
if (result.response && result.response.status === 404) {
|
||||
return this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('messageDeletedUser'),
|
||||
type: 'error',
|
||||
timeout: false,
|
||||
});
|
||||
} else {
|
||||
this.cachedProfileData[memberId] = result.data.data;
|
||||
profile = result.data.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the modal only if the data is available
|
||||
@@ -221,6 +230,11 @@ export default {
|
||||
this.chat.splice(chatIndex, 1, message);
|
||||
},
|
||||
messageRemoved (message) {
|
||||
if (this.inbox) {
|
||||
this.$emit('message-removed', message);
|
||||
return;
|
||||
}
|
||||
|
||||
const chatIndex = findIndex(this.chat, chatMessage => {
|
||||
return chatMessage.id === message.id;
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import notificationsMixin from 'client/mixins/notifications';
|
||||
import Task from 'client/components/tasks/task';
|
||||
|
||||
import taskDefaults from 'common/script/libs/taskDefaults';
|
||||
import { TAVERN_ID } from '../../../common/script/constants';
|
||||
|
||||
const baseUrl = 'https://habitica.com';
|
||||
|
||||
@@ -29,7 +30,7 @@ export default {
|
||||
Task,
|
||||
},
|
||||
mixins: [notificationsMixin],
|
||||
props: ['copyingMessage', 'groupName', 'groupId'],
|
||||
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
|
||||
data () {
|
||||
return {
|
||||
isUser: true,
|
||||
@@ -38,7 +39,7 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica::copy-as-todo', message => {
|
||||
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${baseUrl}/groups/guild/${this.groupId})`;
|
||||
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
|
||||
const newTask = {
|
||||
text: message.text,
|
||||
type: 'todo',
|
||||
@@ -55,6 +56,15 @@ export default {
|
||||
...mapActions({
|
||||
createTask: 'tasks:create',
|
||||
}),
|
||||
groupPath () {
|
||||
if (this.groupId === TAVERN_ID) {
|
||||
return `${baseUrl}/groups/tavern`;
|
||||
} else if (this.groupType === 'party') {
|
||||
return `${baseUrl}/party`;
|
||||
} else {
|
||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
|
||||
},
|
||||
|
||||
@@ -96,16 +96,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.$root.$on('habitica::report-chat', data => {
|
||||
if (!data.message || !data.groupId) return;
|
||||
this.abuseObject = data.message;
|
||||
this.groupId = data.groupId;
|
||||
this.reportComment = '';
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
});
|
||||
this.$root.$on('habitica::report-chat', this.handleReport);
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica::report-chat');
|
||||
this.$root.$off('habitica::report-chat', this.handleReport);
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
@@ -129,6 +123,13 @@ export default {
|
||||
});
|
||||
this.close();
|
||||
},
|
||||
handleReport (data) {
|
||||
if (!data.message || !data.groupId) return;
|
||||
this.abuseObject = data.message;
|
||||
this.groupId = data.groupId;
|
||||
this.reportComment = '';
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -193,8 +193,10 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("flower")', :class='{active: activeSubPage === "flower"}')
|
||||
strong(v-once) {{$t('accent')}}
|
||||
.row.sub-menu(v-if='editing')
|
||||
.col-4.offset-2.text-center.sub-menu-item(@click='changeSubPage("ears")' :class='{active: activeSubPage === "ears"}')
|
||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("ears")' :class='{active: activeSubPage === "ears"}')
|
||||
strong(v-once) {{$t('animalEars')}}
|
||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("tails")' :class='{active: activeSubPage === "tails"}')
|
||||
strong(v-once) {{$t('animalTails')}}
|
||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("headband")' :class='{active: activeSubPage === "headband"}')
|
||||
strong(v-once) {{$t('headband')}}
|
||||
#glasses.row(v-if='activeSubPage === "glasses"')
|
||||
@@ -203,17 +205,30 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.sprite.customize-option(:class="`eyewear_special_${option.key}`", @click='option.click')
|
||||
#animal-ears.row(v-if='activeSubPage === "ears"')
|
||||
.section.col-12.customize-options
|
||||
.option(v-for='option in animalEars',
|
||||
.option(v-for='option in animalItems("headAccessory")',
|
||||
:class='{active: option.active, locked: option.locked}')
|
||||
.sprite.customize-option(:class="`headAccessory_special_${option.key}`", @click='option.click')
|
||||
.gem-lock(v-if='option.locked')
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 2
|
||||
.col-12.text-center(v-if='!animalEarsOwned')
|
||||
.col-12.text-center(v-if='!animalItemsOwned("headAccessory")')
|
||||
.gem-lock
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(animalEarsUnlockString)') {{ $t('purchaseAll') }}
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(animalItemsUnlockString("headAccessory"))') {{ $t('purchaseAll') }}
|
||||
#animal-tails.row(v-if='activeSubPage === "tails"')
|
||||
.section.col-12.customize-options
|
||||
.option(v-for='option in animalItems("back")',
|
||||
:class='{active: option.active, locked: option.locked}')
|
||||
.sprite.customize-option(:class="`icon_back_special_${option.key}`", @click='option.click')
|
||||
.gem-lock(v-if='option.locked')
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 2
|
||||
.col-12.text-center(v-if='!animalItemsOwned("back")')
|
||||
.gem-lock
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(animalItemsUnlockString("back"))') {{ $t('purchaseAll') }}
|
||||
#headband.row(v-if='activeSubPage === "headband"')
|
||||
.col-12.customize-options
|
||||
.option(v-for='option in headbands', :class='{active: option.active}')
|
||||
@@ -222,7 +237,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.col-12.customize-options
|
||||
.option(@click='set({"preferences.chair": "none"})', :class='{active: user.preferences.chair === "none"}')
|
||||
| None
|
||||
.option(v-for='option in ["black", "blue", "green", "pink", "red", "yellow"]',
|
||||
.option(v-for='option in chairKeys',
|
||||
:class='{active: user.preferences.chair === option}')
|
||||
.chair.sprite.customize-option(:class="`button_chair_${option}`", @click='set({"preferences.chair": option})')
|
||||
#flowers.row(v-if='activeSubPage === "flower"')
|
||||
@@ -856,7 +871,7 @@ import isPinned from 'common/script/libs/isPinned';
|
||||
const skinsBySet = groupBy(appearance.skin, 'set.key');
|
||||
const hairColorBySet = groupBy(appearance.hair.color, 'set.key');
|
||||
|
||||
let tasksByCategory = {
|
||||
const tasksByCategory = {
|
||||
work: [
|
||||
{
|
||||
type: 'habit',
|
||||
@@ -1013,7 +1028,11 @@ export default {
|
||||
baseHair4Keys: [15, 16, 17, 18, 19, 20],
|
||||
baseHair5Keys: [1, 2],
|
||||
baseHair6Keys: [1, 2, 3],
|
||||
animalEarsKeys: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
||||
animalItemKeys: {
|
||||
back: ['bearTail', 'cactusTail', 'foxTail', 'lionTail', 'pandaTail', 'pigTail', 'tigerTail', 'wolfTail'],
|
||||
headAccessory: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
||||
},
|
||||
chairKeys: ['black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'],
|
||||
icons: Object.freeze({
|
||||
logoPurple,
|
||||
bodyIcon,
|
||||
@@ -1074,44 +1093,6 @@ export default {
|
||||
});
|
||||
return options;
|
||||
},
|
||||
animalEarsUnlockString () {
|
||||
let animalItemKeys = this.animalEarsKeys.map(key => {
|
||||
return `items.gear.owned.headAccessory_special_${key}`;
|
||||
});
|
||||
|
||||
return animalItemKeys.join(',');
|
||||
},
|
||||
animalEarsOwned () {
|
||||
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
|
||||
let own = true;
|
||||
this.animalEarsKeys.forEach(key => {
|
||||
if (!this.user.items.gear.owned[`headAccessory_special_${key}`]) own = false;
|
||||
});
|
||||
return own;
|
||||
},
|
||||
animalEars () {
|
||||
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
let keys = this.animalEarsKeys;
|
||||
let options = keys.map(key => {
|
||||
let newKey = `headAccessory_special_${key}`;
|
||||
let userPurchased = this.user.items.gear.owned[newKey];
|
||||
let locked = !userPurchased;
|
||||
|
||||
let option = {};
|
||||
option.key = key;
|
||||
option.active = this.user.preferences.costume ? this.user.items.gear.costume.headAccessory === newKey : this.user.items.gear.equipped.headAccessory === newKey;
|
||||
option.locked = locked;
|
||||
option.click = () => {
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return locked ? this.unlock(`items.gear.owned.${newKey}`) : this.equip(newKey, type);
|
||||
};
|
||||
return option;
|
||||
});
|
||||
return options;
|
||||
},
|
||||
specialShirts () {
|
||||
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
@@ -1549,6 +1530,44 @@ export default {
|
||||
backgroundPurchased () {
|
||||
this.backgroundUpdate = new Date();
|
||||
},
|
||||
animalItemsUnlockString (category) {
|
||||
const keys = this.animalItemKeys[category].map(key => {
|
||||
return `items.gear.owned.${category}_special_${key}`;
|
||||
});
|
||||
|
||||
return keys.join(',');
|
||||
},
|
||||
animalItemsOwned (category) {
|
||||
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
|
||||
let own = true;
|
||||
this.animalItemKeys[category].forEach(key => {
|
||||
if (!this.user.items.gear.owned[`${category}_special_${key}`]) own = false;
|
||||
});
|
||||
return own;
|
||||
},
|
||||
animalItems (category) {
|
||||
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
let keys = this.animalItemKeys[category];
|
||||
let options = keys.map(key => {
|
||||
let newKey = `${category}_special_${key}`;
|
||||
let userPurchased = this.user.items.gear.owned[newKey];
|
||||
let locked = !userPurchased;
|
||||
|
||||
let option = {};
|
||||
option.key = key;
|
||||
option.active = this.user.preferences.costume ? this.user.items.gear.costume[category] === newKey : this.user.items.gear.equipped[category] === newKey;
|
||||
option.locked = locked;
|
||||
option.click = () => {
|
||||
let type = this.user.preferences.costume ? 'costume' : 'equipped';
|
||||
return locked ? this.unlock(`items.gear.owned.${newKey}`) : this.equip(newKey, type);
|
||||
};
|
||||
return option;
|
||||
});
|
||||
return options;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
.row
|
||||
.hr.col-12
|
||||
chat-message(:chat.sync='group.chat', :group-id='group._id', :group-name='group.name')
|
||||
chat-message(:chat.sync='group.chat', :group-type='group.type', :group-id='group._id', :group-name='group.name')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
p(v-markdown='group.description')
|
||||
sidebar-section(
|
||||
:title="$t('challenges')",
|
||||
:tooltip="isParty ? $t('challengeDetails') : $t('privateDescription')"
|
||||
:tooltip="$t('challengeDetails')"
|
||||
)
|
||||
group-challenges(:groupId='searchId')
|
||||
div.text-center
|
||||
@@ -478,11 +478,6 @@ export default {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId;
|
||||
});
|
||||
},
|
||||
deleteAllMessages () {
|
||||
if (confirm(this.$t('confirmDeleteAllMessages'))) {
|
||||
// User.clearPMs();
|
||||
}
|
||||
},
|
||||
checkForAchievements () {
|
||||
// Checks if user's party has reached 2 players for the first time.
|
||||
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
|
||||
|
||||
@@ -53,7 +53,7 @@ div
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.removeIcon")
|
||||
span.text {{$t('removeManager2')}}
|
||||
b-dropdown-item(@click='viewProgress(member)')
|
||||
b-dropdown-item(@click='viewProgress(member)', v-if='challengeId')
|
||||
span.dropdown-icon-item
|
||||
span.text {{ $t('viewProgress') }}
|
||||
.row(v-if='isLoadMoreAvailable')
|
||||
@@ -338,6 +338,9 @@ export default {
|
||||
// @TOOD: We might not need this since groupId is computed now
|
||||
this.getMembers();
|
||||
},
|
||||
challengeId () {
|
||||
this.getMembers();
|
||||
},
|
||||
group () {
|
||||
this.getMembers();
|
||||
},
|
||||
@@ -377,9 +380,7 @@ export default {
|
||||
this.invites = invites;
|
||||
}
|
||||
|
||||
if (this.$store.state.memberModalOptions.viewingMembers.length > 0) {
|
||||
this.members = this.$store.state.memberModalOptions.viewingMembers;
|
||||
}
|
||||
this.members = this.$store.state.memberModalOptions.viewingMembers;
|
||||
},
|
||||
async clickMember (uid, forceShow) {
|
||||
let user = this.$store.state.user.data;
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
.checkbox
|
||||
label
|
||||
input(type='checkbox', v-if='hero.flags', v-model='hero.flags.chatRevoked')
|
||||
| Chat Privileges Revoked
|
||||
strong Chat Privileges Revoked
|
||||
.form-group
|
||||
.checkbox
|
||||
label
|
||||
@@ -103,10 +103,10 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// import keys from 'lodash/keys';
|
||||
import each from 'lodash/each';
|
||||
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import styleHelper from 'client/mixins/styleHelper';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import quests from 'common/script/content/quests';
|
||||
import { mountInfo, petInfo } from 'common/script/content/stable';
|
||||
@@ -115,7 +115,7 @@ import gear from 'common/script/content/gear';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
mixins: [notifications, styleHelper],
|
||||
data () {
|
||||
return {
|
||||
heroes: [],
|
||||
@@ -174,7 +174,7 @@ export default {
|
||||
async loadHero (uuid, heroIndex) {
|
||||
this.currentHeroIndex = heroIndex;
|
||||
let hero = await this.$store.dispatch('hall:getHero', { uuid });
|
||||
this.hero = Object.assign({}, this.hero, hero);
|
||||
this.hero = Object.assign({}, hero);
|
||||
if (!this.hero.flags) {
|
||||
this.hero.flags = {
|
||||
chatRevoked: false,
|
||||
@@ -204,9 +204,6 @@ export default {
|
||||
startingPage: 'profile',
|
||||
});
|
||||
},
|
||||
userLevelStyle () {
|
||||
// @TODO: implement
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||