mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-10 19:20:21 -05:00
Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65eca22407 | |||
| cea1597ee1 | |||
| 3906952154 | |||
| 6169b9d0ae | |||
| 69cac7e504 | |||
| febf3f0024 | |||
| 563f40e4b7 | |||
| e2b06161e1 | |||
| e7de8b8e2f | |||
| a0624d9507 | |||
| cddd0df4f2 | |||
| 220bfb3517 | |||
| 2106a5ebd3 | |||
| bbffa9830b | |||
| caa546eb62 | |||
| 4e83059652 | |||
| 903cdb36ef | |||
| d8128cc3db | |||
| f888e80b01 | |||
| fd7aedbff2 | |||
| 6c5234313d | |||
| 08aa5758b4 | |||
| 1415e344c0 | |||
| 2ce7915f06 | |||
| 838c8b4e08 | |||
| 1590d955cd | |||
| 2690caed35 | |||
| dc2d4fa10b | |||
| 1540ec89ee | |||
| f304d4fe52 | |||
| 023e433a5c | |||
| ef4aeb29ab | |||
| 2b80931202 | |||
| 2950713712 | |||
| 118f3bd1bb | |||
| 69f1343ea8 | |||
| 918ee02d64 | |||
| 0cac34dd26 | |||
| 1c859fc91f | |||
| 857aa5827b | |||
| 28e8ec2d2c | |||
| 856f13d213 | |||
| 121fd38bd1 | |||
| 36d72f5f7a | |||
| f1b8bd80e7 | |||
| 84d2ce6a3f | |||
| 76010e6c8f | |||
| c707b6c99b | |||
| e4bd466cc7 | |||
| 001b8eb894 | |||
| 9abcfe8614 | |||
| bc6102551d | |||
| 959a3ff85b | |||
| 518b874f64 | |||
| 6cc359a935 | |||
| 514d35c0be | |||
| 13da92ea68 | |||
| 03c4d82b7d | |||
| d905ab7f86 | |||
| c6560b6b1b | |||
| c61f660255 | |||
| 2f1b683ec9 | |||
| 47bb217068 | |||
| f49fd05da1 | |||
| b0341aa06f | |||
| b07ec18e33 | |||
| 12930a2bac | |||
| 91f5c47d9d | |||
| fe7850d10f | |||
| c5c2da75bf | |||
| 969607cd3b | |||
| 2a1f52a359 | |||
| 47d9594679 | |||
| 97e40c81f3 | |||
| c8b61a2f7d | |||
| e9543f0d28 | |||
| 77b88490e4 | |||
| 7fc2500bfd | |||
| fb229acb58 | |||
| 6ce83d1fa4 | |||
| 2be4815aea | |||
| 1dbc42f48a | |||
| 89279c8aed | |||
| faedb13598 | |||
| c0c74659c5 | |||
| bf5ad2db1f | |||
| 7d99873960 | |||
| e02ef00397 | |||
| 23c5c4211c | |||
| 69cc134fff | |||
| ffd9400cb7 | |||
| 5be91ef842 | |||
| 3123183e46 | |||
| 49cca7a601 | |||
| 7fbd38d18c | |||
| 1f95376d39 | |||
| 2da0a1e88c | |||
| afacd3e1cf | |||
| a69b9e6705 | |||
| e4e5d10316 | |||
| 27c38bdf45 | |||
| ea24eeb019 | |||
| 55a8eef3e1 | |||
| 92cbb4a07d | |||
| 3f96d05365 | |||
| 0b72f6a613 | |||
| 5e1e6be518 | |||
| 472ec99291 | |||
| 0284e9a4e3 | |||
| 1a0721c078 | |||
| 6b6b548ac5 | |||
| 30f3d786bb | |||
| 07bbba6789 | |||
| 6afb2bd0d4 | |||
| f1a3bd5001 | |||
| 3f6a13d209 | |||
| 3658e41fec | |||
| c69d5c7ae6 | |||
| 747f9e6a99 | |||
| 7755ab090b | |||
| 9ed17df1e3 | |||
| faeb040a83 | |||
| 0a1ae1375e | |||
| 9756030fa2 | |||
| c66172b74b | |||
| 281f6d1806 | |||
| 237095d109 | |||
| fa788f49fc | |||
| 0817cf96e1 | |||
| 97e1d75dce | |||
| 52bf20c27d | |||
| 5dbaf39aba | |||
| 66d402c985 | |||
| 8048146223 | |||
| e2c07e458d | |||
| 90a9e8e192 | |||
| f8039f48a6 | |||
| 04337f8e83 | |||
| 45297f8bf9 | |||
| 6f112c29f2 | |||
| 4d1edb363c | |||
| 4e303cc592 | |||
| 798a975185 | |||
| eb2b46fc5d | |||
| 29854d3bdb | |||
| f8751b002c | |||
| cd545e08d5 | |||
| f69bb4f023 | |||
| 847081d2b2 | |||
| 8112d46ea4 | |||
| d13bded647 | |||
| 1de4ab3612 | |||
| f9f22f313f | |||
| f57eed85a8 | |||
| 10dd3318ab | |||
| cbef83c14a | |||
| 59709a8590 | |||
| f85f2a0c6d | |||
| 605a5a1d5c | |||
| 2d5d786c8e | |||
| 5efe5b7b10 | |||
| 3e92bb22fa | |||
| 1249b9d410 | |||
| 197aafe092 | |||
| 79829ca128 | |||
| adaa1d9a3e | |||
| 3e6691dbbb | |||
| 046761b9aa | |||
| 0b0466b960 | |||
| f8d4a2bd6b | |||
| 1af59a3770 | |||
| bbcb13c91b | |||
| d27dc46c50 | |||
| 679459b83b | |||
| 5a619773d5 | |||
| ad76ab1315 | |||
| 15eb8db925 | |||
| a0e92c5605 | |||
| eac3e36c07 | |||
| 0b8def555b | |||
| 5f5fa5c2eb | |||
| 1eac8bbbbe | |||
| 49c7580cd4 | |||
| dca958f2e2 | |||
| eae5f0d605 | |||
| 6ab091645c | |||
| d66041c280 | |||
| de070a450a | |||
| eaaab35f31 | |||
| 6a63f080ad | |||
| c42f81b629 | |||
| 9a78a7b896 | |||
| 8b70721137 | |||
| 44ffbd716d | |||
| 5bfc3a5ff4 | |||
| 0ba5df4164 | |||
| 52a59c8192 | |||
| c1a860494d | |||
| 395dafa127 | |||
| bab41647f5 | |||
| 8582a67308 | |||
| 0d58fb0fd3 | |||
| 1d2482f8bc | |||
| f4cf906127 | |||
| 559f9b1825 | |||
| c7039bc9ea | |||
| f929d36e1a | |||
| 254d1a3465 | |||
| 442aae8a35 | |||
| bcb0ed0a5c | |||
| a48b8f0e34 | |||
| 7eeeda2aae | |||
| a5ad9c30f0 | |||
| ac732b2c85 | |||
| a56b2d68fb | |||
| 25b0ff38c4 | |||
| dcc06931cc | |||
| bc3ebbd095 | |||
| e5b9581743 | |||
| 4b9fe49e3a | |||
| ab4c8b0a46 | |||
| f6c26fe869 | |||
| 80e9735b28 | |||
| aa6f188bd9 | |||
| e8b7660376 | |||
| 7d76622410 | |||
| 928e5f66c4 | |||
| 6a343535c0 | |||
| f58f6acb44 | |||
| 64754777ed | |||
| 3b5e4b6d84 | |||
| 9383578cb8 | |||
| 474672ec64 | |||
| 25c6691793 | |||
| 3ea7b72024 | |||
| 2d6f05a9a4 | |||
| 28637286d6 | |||
| 874887b790 | |||
| c977e5ebb5 | |||
| f040e668f3 | |||
| 55a15f938c | |||
| 8c4f35daf4 | |||
| 8f38ce3424 | |||
| b8f57a74d0 |
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.git
|
||||
website
|
||||
@@ -14,7 +14,7 @@ files:
|
||||
owner: root
|
||||
group: users
|
||||
content: |
|
||||
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@3
|
||||
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@4
|
||||
container_commands:
|
||||
01_makeBabel:
|
||||
command: "touch /tmp/.babel.json"
|
||||
|
||||
+1
-5
@@ -20,8 +20,4 @@ website/common/browserify.js
|
||||
test/content/**/*
|
||||
Gruntfile.js
|
||||
gulpfile.js
|
||||
gulp
|
||||
webpack
|
||||
test/client/e2e
|
||||
test/client/unit/index.js
|
||||
test/client/unit/karma.conf.js
|
||||
gulp
|
||||
@@ -4,7 +4,7 @@
|
||||
"node": true,
|
||||
},
|
||||
"extends": [
|
||||
"habitrpg/es6",
|
||||
"habitrpg"
|
||||
"habitrpg",
|
||||
"habitrpg/esnext"
|
||||
],
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '4.3.1'
|
||||
- '6'
|
||||
before_install:
|
||||
- npm install -g npm@3
|
||||
- npm install -g npm@4
|
||||
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
||||
before_script:
|
||||
- npm run test:build
|
||||
|
||||
+6
-4
@@ -17,20 +17,22 @@ RUN apt-get install -y \
|
||||
python
|
||||
|
||||
# Install NodeJS
|
||||
RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
|
||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install npm@latest
|
||||
RUN curl -sL https://www.npmjs.org/install.sh | sh
|
||||
|
||||
# Clean up package management
|
||||
RUN apt-get clean
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g npm@3
|
||||
RUN npm install -g gulp grunt-cli bower
|
||||
RUN npm install -g gulp grunt-cli bower mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
WORKDIR /habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitrpg.git /habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
|
||||
+2
-1
@@ -57,7 +57,7 @@ module.exports = function(grunt) {
|
||||
files: [
|
||||
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
|
||||
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
|
||||
@@ -78,6 +78,7 @@ module.exports = function(grunt) {
|
||||
'website/build/favicon.ico',
|
||||
'website/build/favicon_192x192.png',
|
||||
'website/build/*.png',
|
||||
'website/build/static/sprites/*.png',
|
||||
'website/build/*.gif',
|
||||
'website/build/bower_components/bootstrap/dist/fonts/*'
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Habitica [](https://travis-ci.org/HabitRPG/habitica) [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://coveralls.io/r/HabitRPG/habitrpg?branch=develop) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
Habitica [](https://travis-ci.org/HabitRPG/habitica) [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://coveralls.io/github/HabitRPG/habitica?branch=develop) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
===============
|
||||
|
||||
[Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
|
||||
|
||||
+2
-1
@@ -79,6 +79,7 @@
|
||||
},
|
||||
"SLACK": {
|
||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/"
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
web:
|
||||
volumes:
|
||||
- '.:/habitrpg'
|
||||
- '.:/habitrpg'
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3000:3000"
|
||||
links:
|
||||
- mongo
|
||||
- mongo
|
||||
environment:
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
- "27017:27017"
|
||||
|
||||
+12
-2
@@ -12,6 +12,9 @@ import {each} from 'lodash';
|
||||
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
||||
const DIST_PATH = 'website/assets/sprites/dist/';
|
||||
|
||||
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
|
||||
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
|
||||
|
||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||
|
||||
gulp.task('sprites:main', () => {
|
||||
@@ -25,7 +28,7 @@ gulp.task('sprites:largeSprites', () => {
|
||||
});
|
||||
|
||||
gulp.task('sprites:clean', (done) => {
|
||||
clean(`${DIST_PATH}spritesmith*`, done);
|
||||
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
|
||||
});
|
||||
|
||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||
@@ -66,14 +69,16 @@ function createSpritesStream (name, src) {
|
||||
algorithm: 'binary-tree',
|
||||
padding: 1,
|
||||
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
|
||||
cssVarMap: cssVarMap
|
||||
cssVarMap: cssVarMap,
|
||||
}));
|
||||
|
||||
let imgStream = spriteData.img
|
||||
.pipe(imagemin())
|
||||
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
|
||||
.pipe(gulp.dest(DIST_PATH));
|
||||
|
||||
let cssStream = spriteData.css
|
||||
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
|
||||
.pipe(gulp.dest(DIST_PATH));
|
||||
|
||||
stream.add(imgStream);
|
||||
@@ -148,4 +153,9 @@ function cssVarMap (sprite) {
|
||||
}
|
||||
if (~sprite.name.indexOf('shirt'))
|
||||
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
|
||||
if (~sprite.name.indexOf('hair_base')) {
|
||||
let styleArray = sprite.name.split('_').slice(2,3);
|
||||
if (Number(styleArray[0]) > 14)
|
||||
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ gulp.task('test:api-v3:integration:watch', () => {
|
||||
|
||||
gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
|
||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||
{maxBuffer: 500 * 1024},
|
||||
(err, stdout, stderr) => done(err)
|
||||
);
|
||||
|
||||
@@ -84,8 +84,8 @@ gulp.task('transifex:malformedStrings', () => {
|
||||
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
|
||||
stringsWithMalformedInterpolations.push(malformedString);
|
||||
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
|
||||
let missingInterploationString = `${lang} - ${file} - ${key} - ${translationString}`;
|
||||
stringsWithIncorrectNumberOfInterpolations.push(missingInterploationString);
|
||||
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
|
||||
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
var migrationName = '20161122_turkey_ladder.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Yearly Turkey Day award. Turkey pet, Turkey mount, Gilded Turkey pet, Gilded Turkey mount
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-10-31')} // Extend timeframe each run of migration
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'migration': 1,
|
||||
'items.mounts': 1,
|
||||
'items.pets': 1,
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (user.items.pets['Turkey-Gilded']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
|
||||
} else if (user.items.mounts['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
|
||||
} else if (user.items.pets['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
|
||||
}
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
var migrationName = '20161230_nye_hats.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Yearly New Year's party hat award
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2016-11-30')} // Remove after first run
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'items.gear.owned': 1,
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2016':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2015':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2014':false};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_nye':false};
|
||||
}
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['head_mystery_201610','armor_mystery_201610']
|
||||
$each:['head_mystery_201612','armor_mystery_201612']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+45
-22
@@ -6,49 +6,70 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
* set the newStuff flag in all user accounts so they see a Bailey message
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'flags.newStuff':{$ne:true}
|
||||
};
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'flags.newStuff': {$ne:true},
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
};
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPaymentPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPaymentPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {'flags.newStuff':true};
|
||||
var set = {'flags.newStuff': true};
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
dbUsers.update({_id: user._id}, {$set:set});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
@@ -58,3 +79,5 @@ function exiting(code, msg) {
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
processUsers()
|
||||
|
||||
@@ -6,14 +6,11 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
* Remove flag stating that the Enchanted Armoire is empty, for when new equipment is added
|
||||
*/
|
||||
|
||||
var dbserver = 'localhost:27017'; // FOR TEST DATABASE
|
||||
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
||||
var dbname = 'habitrpg';
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
@@ -22,7 +19,6 @@ var query = {
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'flags.armoireEmpty':1
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
@@ -32,7 +28,8 @@ dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
return displayData();
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
var migrationName = 'restore_profile_data.js';
|
||||
var authorName = 'ThehollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* Check if users have empty profile data in new database and update it with old database info
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = ''; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
var monk2 = require('monk');
|
||||
var oldDbConnectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var olDbUsers = monk2(oldDbConnectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId)
|
||||
{
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
// 'profile.name': 'profile name not found',
|
||||
'profile.blurb': null,
|
||||
// 'auth.timestamps.loggedin': {$gt: new Date('11/30/2016')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: ['_id', 'profile', 'auth.timestamps.loggedin'] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
|
||||
var userPaymentPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPaymentPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (!user.profile.name || user.profile.name === 'profile name not found' || !user.profile.imageUrl || !user.profile.blurb) {
|
||||
return olDbUsers.findOne({_id: user._id}, '_id profile')
|
||||
.then((oldUserData) => {
|
||||
if (!oldUserData) return;
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (oldUserData.profile.name === 'profile name not found') return;
|
||||
|
||||
var userNeedsProfileName = !user.profile.name || user.profile.name === 'profile name not found';
|
||||
if (userNeedsProfileName && oldUserData.profile.name) {
|
||||
set['profile.name'] = oldUserData.profile.name;
|
||||
}
|
||||
|
||||
if (!user.profile.imageUrl && oldUserData.profile.imageUrl) {
|
||||
set['profile.imageUrl'] = oldUserData.profile.imageUrl;
|
||||
}
|
||||
|
||||
if (!user.profile.blurb && oldUserData.profile.blurb) {
|
||||
set['profile.blurb'] = oldUserData.profile.blurb;
|
||||
}
|
||||
|
||||
if (Object.keys(set).length !== 0 && set.constructor === Object) {
|
||||
console.log(set)
|
||||
return dbUsers.update({_id: user._id}, {$set:set});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
||||
processUsers()
|
||||
@@ -0,0 +1,80 @@
|
||||
var migrationName = '20170103_takeThis.js'; // Update per month
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Take This ladder items to participants in this month's challenge
|
||||
*/
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
|
||||
var dbUsers = mongo.db(connectionString).collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'challenges':{$in:['ff674aba-a114-4a6f-8ebc-1de27ffb646e']}
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'items.gear.owned': 1
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
setTimeout(displayData, 300000);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
|
||||
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName};
|
||||
{ else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
|
||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
|
||||
}
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
||||
Generated
+4494
-736
File diff suppressed because it is too large
Load Diff
+19
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "3.54.0",
|
||||
"version": "3.68.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "3.6.0",
|
||||
@@ -13,10 +13,13 @@
|
||||
"async": "^1.5.0",
|
||||
"autoprefixer": "^6.4.0",
|
||||
"aws-sdk": "^2.0.25",
|
||||
"axios": "^0.15.3",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-regenerator": "^6.16.1",
|
||||
"babel-polyfill": "^6.6.1",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.6.0",
|
||||
@@ -29,7 +32,7 @@
|
||||
"compression": "^1.6.1",
|
||||
"connect-ratelimit": "0.0.7",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.3",
|
||||
"coupon-code": "^0.4.5",
|
||||
"css-loader": "^0.23.1",
|
||||
"csv-stringify": "^1.0.2",
|
||||
"cwait": "^1.0.0",
|
||||
@@ -64,17 +67,18 @@
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
"jade": "~1.11.0",
|
||||
"jquery": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz",
|
||||
"js2xmlparser": "~1.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"lodash": "^3.10.1",
|
||||
"lodash.setwith": "^4.2.0",
|
||||
"lodash.pickby": "^4.2.0",
|
||||
"lodash.setwith": "^4.2.0",
|
||||
"merge-stream": "^1.0.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.13.0",
|
||||
"mongoose": "^4.4.16",
|
||||
"mongoose": "^4.7.1",
|
||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "~0.8.2",
|
||||
@@ -90,6 +94,7 @@
|
||||
"passport-google-oauth20": "1.0.0",
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.2.1",
|
||||
"postcss-easy-import": "^1.0.1",
|
||||
"pretty-data": "^0.40.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
@@ -111,11 +116,11 @@
|
||||
"validator": "^4.9.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"vue": "^2.0.0-rc.6",
|
||||
"vue": "^2.1.0",
|
||||
"vue-hot-reload-api": "^1.2.0",
|
||||
"vue-loader": "^9.4.0",
|
||||
"vue-resource": "^1.0.2",
|
||||
"vue-loader": "^10.0.0",
|
||||
"vue-router": "^2.0.0-rc.5",
|
||||
"vue-template-compiler": "^2.1.0",
|
||||
"webpack": "^1.12.2",
|
||||
"webpack-merge": "^0.8.3",
|
||||
"winston": "^2.1.0",
|
||||
@@ -123,8 +128,8 @@
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^4.3.1",
|
||||
"npm": "^3.8.9"
|
||||
"node": "^6.9.1",
|
||||
"npm": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
@@ -155,7 +160,6 @@
|
||||
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^6.0.0",
|
||||
"chai": "^3.4.0",
|
||||
"chai-as-promised": "^5.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
@@ -165,13 +169,12 @@
|
||||
"cross-spawn": "^2.1.5",
|
||||
"csv": "~0.3.6",
|
||||
"deep-diff": "~0.1.4",
|
||||
"eslint": "~2.12.0",
|
||||
"eslint-config-habitrpg": "^1.0.0",
|
||||
"eslint": "^3.0.0",
|
||||
"eslint-config-habitrpg": "^2.0.0",
|
||||
"eslint-friendly-formatter": "^2.0.5",
|
||||
"eslint-loader": "^1.3.0",
|
||||
"eslint-plugin-babel": "^3.0.0",
|
||||
"eslint-plugin-html": "^1.3.0",
|
||||
"eslint-plugin-mocha": "^2.1.0",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
"event-stream": "^3.2.2",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"expect.js": "~0.2.0",
|
||||
@@ -196,6 +199,7 @@
|
||||
"mocha": "^2.3.3",
|
||||
"mongodb": "^2.0.46",
|
||||
"mongoskin": "~2.1.0",
|
||||
"monk": "^3.1.3",
|
||||
"nightwatch": "^0.8.18",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"protractor": "^3.1.1",
|
||||
@@ -204,6 +208,7 @@
|
||||
"selenium-server": "2.53.0",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"superagent-defaults": "^0.1.13",
|
||||
"vinyl-transform": "^1.0.0",
|
||||
"webpack-dev-middleware": "^1.4.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": [
|
||||
"habitrpg/mocha",
|
||||
"habitrpg/babel"
|
||||
"habitrpg/esnext"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
|
||||
@@ -35,6 +35,24 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when an empty message is provided', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ' '}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when an message containing only newlines is provided', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: '\n\n'}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when group is not found', async () => {
|
||||
await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
|
||||
@@ -7,15 +7,21 @@ import {
|
||||
import moment from 'moment';
|
||||
|
||||
describe('GET /export/history.csv', () => {
|
||||
it('should return a valid CSV file with tasks history data', async () => {
|
||||
// TODO disabled because it randomly causes the build to fail
|
||||
xit('should return a valid CSV file with tasks history data', async () => {
|
||||
let user = await generateUser();
|
||||
let tasks = await user.post('/tasks/user', [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'daily', text: 'daily 1'},
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'habit', text: 'habit 2'},
|
||||
{type: 'todo', text: 'todo 1'},
|
||||
]);
|
||||
|
||||
// to handle occasional inconsistency in task creation order
|
||||
tasks.sort(function (a, b) {
|
||||
return a.text.localeCompare(b.text);
|
||||
});
|
||||
|
||||
// score all the tasks twice
|
||||
await user.post(`/tasks/${tasks[0]._id}/score/up`);
|
||||
await user.post(`/tasks/${tasks[1]._id}/score/up`);
|
||||
@@ -28,7 +34,7 @@ describe('GET /export/history.csv', () => {
|
||||
await user.post(`/tasks/${tasks[3]._id}/score/up`);
|
||||
|
||||
// adding an history entry to daily 1 manually because cron didn't run yet
|
||||
await updateDocument('tasks', tasks[1], {
|
||||
await updateDocument('tasks', tasks[0], {
|
||||
history: [{value: 3.2, date: Number(new Date())}],
|
||||
});
|
||||
|
||||
@@ -41,9 +47,9 @@ describe('GET /export/history.csv', () => {
|
||||
let splitRes = res.split('\n');
|
||||
|
||||
expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
|
||||
expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
||||
expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
|
||||
expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
||||
expect(splitRes[1]).to.equal(`daily 1,${tasks[0]._id},daily,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
||||
expect(splitRes[2]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
||||
expect(splitRes[3]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[1].value}`);
|
||||
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
|
||||
expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
|
||||
expect(splitRes[6]).to.equal('');
|
||||
|
||||
@@ -7,7 +7,8 @@ import Bluebird from 'bluebird';
|
||||
let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
|
||||
|
||||
describe('GET /export/userdata.xml', () => {
|
||||
it('should return a valid XML file with user data', async () => {
|
||||
// TODO disabled because it randomly causes the build to fail
|
||||
xit('should return a valid XML file with user data', async () => {
|
||||
let user = await generateUser();
|
||||
let tasks = await user.post('/tasks/user', [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
|
||||
@@ -82,8 +82,10 @@ describe('GET /groups/:groupId/members', () => {
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background'].sort());
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
|
||||
@@ -134,6 +134,22 @@ describe('POST /group/:groupId/join', () => {
|
||||
|
||||
await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||
|
||||
let inviter = await user.get('/user');
|
||||
let expectedData = {
|
||||
headerText: t('invitationAcceptedHeader'),
|
||||
bodyText: t('invitationAcceptedBody', {
|
||||
username: invitedUser.auth.local.username,
|
||||
groupName: guild.name,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,6 +188,23 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
let inviter = await user.get('/user');
|
||||
|
||||
let expectedData = {
|
||||
headerText: t('invitationAcceptedHeader'),
|
||||
bodyText: t('invitationAcceptedBody', {
|
||||
username: invitedUser.auth.local.username,
|
||||
groupName: party.name,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
||||
});
|
||||
|
||||
it('clears invitation from user when joining party', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
let leader;
|
||||
@@ -60,6 +61,14 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
|
||||
context('Guilds', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can remove other members', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
let memberRemoved = await member.get('/user');
|
||||
@@ -80,6 +89,22 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
|
||||
});
|
||||
|
||||
it('sends email to user with rescinded invite', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(invitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('guild-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(member._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-guild');
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
@@ -105,6 +130,11 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
partyInvitedUser = invitees[0];
|
||||
partyMember = members[0];
|
||||
removedMember = members[1];
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('can remove other members', async () => {
|
||||
@@ -187,5 +217,21 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
expect(party.quest.members[partyLeader._id]).to.be.true;
|
||||
expect(party.quest.members[partyMember._id]).to.not.exist;
|
||||
});
|
||||
|
||||
it('sends email to user with rescinded invite', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyInvitedUser._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('party-invite-rescinded');
|
||||
});
|
||||
|
||||
it('sends email to removed user', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyMember._id);
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-party');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -300,6 +300,26 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
message: t('userAlreadyInGroup'),
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: Add this after we are able to mock the group plan route
|
||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||
let userToInvite = await generateUser();
|
||||
|
||||
let nonGroupLeader = await generateUser();
|
||||
await inviter.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [nonGroupLeader._id],
|
||||
});
|
||||
await nonGroupLeader.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('party invites', () => {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('GET /members/:memberId/achievements', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('validates req.params.memberId', async () => {
|
||||
await expect(user.get('/members/invalidUUID/achievements')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns achievements based on given user', async () => {
|
||||
let member = await generateUser({
|
||||
contributor: {level: 1},
|
||||
backer: {tier: 3},
|
||||
});
|
||||
let achievementsRes = await user.get(`/members/${member._id}/achievements`);
|
||||
|
||||
expect(achievementsRes.special.achievements.contributor.earned).to.equal(true);
|
||||
expect(achievementsRes.special.achievements.contributor.value).to.equal(1);
|
||||
|
||||
expect(achievementsRes.special.achievements.kickstarter.earned).to.equal(true);
|
||||
expect(achievementsRes.special.achievements.kickstarter.value).to.equal(3);
|
||||
});
|
||||
|
||||
it('handles non-existing members', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(user.get(`/members/${dummyId}/achievements`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: dummyId}),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -35,8 +35,10 @@ describe('GET /members/:memberId', () => {
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background'].sort());
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
|
||||
@@ -4,6 +4,14 @@ import {
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
function findMessage (messages, receiverId) {
|
||||
let message = _.find(messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiverId;
|
||||
});
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
describe('POST /members/transfer-gems', () => {
|
||||
let userToSendMessage;
|
||||
let receiver;
|
||||
@@ -116,19 +124,14 @@ describe('POST /members/transfer-gems', () => {
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === userToSendMessage._id;
|
||||
});
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiver._id;
|
||||
});
|
||||
|
||||
let messageSentContent = t('privateMessageGiftIntro', {
|
||||
let messageSentContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
});
|
||||
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
|
||||
messageSentContent = `\`${messageSentContent}\` `;
|
||||
messageSentContent += message;
|
||||
|
||||
@@ -150,19 +153,14 @@ describe('POST /members/transfer-gems', () => {
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === userToSendMessage._id;
|
||||
});
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
|
||||
return inboxMessage.uuid === receiver._id;
|
||||
});
|
||||
|
||||
let messageSentContent = t('privateMessageGiftIntro', {
|
||||
let messageSentContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
});
|
||||
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
|
||||
messageSentContent = `\`${messageSentContent}\` `;
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
@@ -173,4 +171,40 @@ describe('POST /members/transfer-gems', () => {
|
||||
expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent);
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
|
||||
it('sends transfer gems message in each participant\'s language', async () => {
|
||||
await receiver.update({
|
||||
'preferences.language': 'es',
|
||||
});
|
||||
await userToSendMessage.update({
|
||||
'preferences.language': 'cs',
|
||||
});
|
||||
await userToSendMessage.post('/members/transfer-gems', {
|
||||
gemAmount,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
|
||||
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
|
||||
|
||||
let [receieversMessageContent, sendersMessageContent] = ['es', 'cs'].map((lang) => {
|
||||
let messageContent = t('privateMessageGiftGemsMessage', {
|
||||
receiverName: receiver.profile.name,
|
||||
senderName: userToSendMessage.profile.name,
|
||||
gemAmount,
|
||||
}, lang);
|
||||
|
||||
return `\`${messageContent}\` `;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
expect(sendersMessageInReceiversInbox.text).to.equal(receieversMessageContent);
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
expect(sendersMessageInSendersInbox.text).to.equal(sendersMessageContent);
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('GET /models/:model/paths', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns an error when model is not accessible or doesn\'t exists', async () => {
|
||||
it('returns an error when model is not accessible or doesn\'t exist', async () => {
|
||||
await expect(user.get('/models/1234/paths')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/read', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(user.post(`/notifications/${dummyId}/read`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
xit('removes a notification', async () => {
|
||||
});
|
||||
});
|
||||
+6
-8
@@ -10,13 +10,11 @@ describe('payments - amazon - #createOrderReferenceId', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies billingAgreementId', async (done) => {
|
||||
try {
|
||||
await user.post(endpoint);
|
||||
} catch (e) {
|
||||
// Parameter AWSAccessKeyId cannot be empty.
|
||||
expect(e.error).to.eql('BadRequest');
|
||||
done();
|
||||
}
|
||||
it('verifies billingAgreementId', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Missing req.body.billingAgreementId',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
|
||||
describe('POST /groups/:groupId/quests/abort', () => {
|
||||
let questingGroup;
|
||||
@@ -85,6 +86,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
});
|
||||
|
||||
it('aborts a quest', async () => {
|
||||
sandbox.stub(Group.prototype, 'sendChat');
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
@@ -123,5 +125,8 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
||||
},
|
||||
members: {},
|
||||
});
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
|
||||
Group.prototype.sendChat.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('GET /tasks/user', () => {
|
||||
for (let i = 0; i < numberOfTodos; i++) {
|
||||
let id = todos[i]._id;
|
||||
|
||||
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line babel/no-await-in-loop
|
||||
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
await user.sync();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /tasks/clearCompletedTodos', () => {
|
||||
it('deletes all completed todos except the ones from a challenge', async () => {
|
||||
it('deletes all completed todos except the ones from a challenge and group', async () => {
|
||||
let user = await generateUser({balance: 1});
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
@@ -24,12 +24,18 @@ describe('POST /tasks/clearCompletedTodos', () => {
|
||||
type: 'todo',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo 7',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
let tasks = await user.get('/tasks/user?type=todos');
|
||||
expect(tasks.length).to.equal(initialTodoCount + 6);
|
||||
expect(tasks.length).to.equal(initialTodoCount + 7);
|
||||
|
||||
for (let task of tasks) {
|
||||
if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) {
|
||||
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop
|
||||
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +44,6 @@ describe('POST /tasks/clearCompletedTodos', () => {
|
||||
let todos = await user.get('/tasks/user?type=todos');
|
||||
let allTodos = todos.concat(completedTodos);
|
||||
expect(allTodos.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge)
|
||||
expect(allTodos[allTodos.length - 1].text).to.equal('todo 6');
|
||||
expect(allTodos[allTodos.length - 1].text).to.equal('todo 7');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,4 +69,48 @@ describe('DELETE /tasks/:id', () => {
|
||||
expect(syncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
expect(member2SyncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
});
|
||||
|
||||
it('prevents a user from deleting a task they are assigned to', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.del(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cantDeleteAssignedGroupTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a user to delete a broken task', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await user.del(`/tasks/${task._id}`);
|
||||
|
||||
await member.del(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await expect(member.get(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Task not found.',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a user to delete a task after leaving a group', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await member.post(`/groups/${guild._id}/leave`);
|
||||
|
||||
await member.del(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await expect(member.get(`/tasks/${syncedTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Task not found.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,9 +59,11 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(1);
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved'));
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('POST /tasks/:taskId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows user to assign themselves', async () => {
|
||||
it('allows user to assign themselves (claim)', async () => {
|
||||
await member.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
@@ -93,6 +93,15 @@ describe('POST /tasks/:taskId', () => {
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
it('sends a message to the group when a user claims a task', async () => {
|
||||
await member.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let updateGroup = await user.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updateGroup.chat[0].text).to.equal(t('userIsClamingTask', {username: member.profile.name, task: task.text}));
|
||||
expect(updateGroup.chat[0].uuid).to.equal('system');
|
||||
});
|
||||
|
||||
it('assigns a task to a user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
} from '../../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||
let user, guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 1});
|
||||
guild = await generateGroup(user, {type: 'guild'});
|
||||
});
|
||||
|
||||
it('can move task to new position', async () => {
|
||||
let tasks = await user.post(`/tasks/group/${guild._id}`, [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'habit', text: 'habit 2'},
|
||||
{type: 'daily', text: 'daily 1'},
|
||||
{type: 'habit', text: 'habit 3'},
|
||||
{type: 'habit', text: 'habit 4'},
|
||||
{type: 'todo', text: 'todo 1'},
|
||||
{type: 'habit', text: 'habit 5'},
|
||||
]);
|
||||
|
||||
let taskToMove = tasks[1];
|
||||
expect(taskToMove.text).to.equal('habit 2');
|
||||
let newOrder = await user.post(`/group-tasks/${tasks[1]._id}/move/to/3`);
|
||||
expect(newOrder[3]).to.equal(taskToMove._id);
|
||||
expect(newOrder.length).to.equal(5);
|
||||
});
|
||||
|
||||
it('can push to bottom', async () => {
|
||||
let tasks = await user.post(`/tasks/group/${guild._id}`, [
|
||||
{type: 'habit', text: 'habit 1'},
|
||||
{type: 'habit', text: 'habit 2'},
|
||||
{type: 'daily', text: 'daily 1'},
|
||||
{type: 'habit', text: 'habit 3'},
|
||||
{type: 'habit', text: 'habit 4'},
|
||||
{type: 'todo', text: 'todo 1'},
|
||||
{type: 'habit', text: 'habit 5'},
|
||||
]);
|
||||
|
||||
let taskToMove = tasks[1];
|
||||
expect(taskToMove.text).to.equal('habit 2');
|
||||
let newOrder = await user.post(`/group-tasks/${tasks[1]._id}/move/to/-1`);
|
||||
expect(newOrder[4]).to.equal(taskToMove._id);
|
||||
expect(newOrder.length).to.equal(5);
|
||||
});
|
||||
});
|
||||
@@ -62,15 +62,6 @@ describe('POST /user/buy/:key', () => {
|
||||
await user.post(`/user/buy/${key}`);
|
||||
await user.sync();
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,15 +31,6 @@ describe('POST /user/buy-gear/:key', () => {
|
||||
await user.post(`/user/buy-gear/${key}`);
|
||||
await user.sync();
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /user/class/cast/:spellId', () => {
|
||||
let user;
|
||||
@@ -120,6 +121,31 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if a group task was targeted', async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup();
|
||||
|
||||
let groupTask = await groupLeader.post(`/tasks/group/${group._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await groupLeader.post(`/tasks/${groupTask._id}/assign/${groupLeader._id}`);
|
||||
let memberTasks = await groupLeader.get('/tasks/user');
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === group._id;
|
||||
});
|
||||
|
||||
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
|
||||
await sleep(0.5);
|
||||
await groupLeader.sync();
|
||||
|
||||
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${syncedGroupTask._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('groupTasksNoCast'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if targeted party member doesn\'t exist', async () => {
|
||||
let {groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
|
||||
@@ -20,6 +20,6 @@ describe('POST /user/purchase-hourglass/:type/:key', () => {
|
||||
|
||||
expect(response.message).to.eql(t('hourglassPurchase'));
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
|
||||
expect(user.items.pets['MantisShrimp-Base']).to.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /user/reset', () => {
|
||||
let user;
|
||||
@@ -86,19 +87,34 @@ describe('POST /user/reset', () => {
|
||||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('does not delete challenge tasks', async () => {
|
||||
it('does not delete challenge or group tasks', async () => {
|
||||
let guild = await generateGroup(user);
|
||||
let challenge = await generateChallenge(user, guild);
|
||||
let task = await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||
text: 'test challenge habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'todo group',
|
||||
type: 'todo',
|
||||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.sync();
|
||||
|
||||
let userChallengeTask = await user.get(`/tasks/${task._id}`);
|
||||
let memberTasks = await user.get('/tasks/user');
|
||||
|
||||
expect(userChallengeTask).to.eql(task);
|
||||
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
});
|
||||
|
||||
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
|
||||
return memberTask.challenge.id === challenge._id;
|
||||
});
|
||||
|
||||
expect(userChallengeTask).to.exist;
|
||||
expect(syncedGroupTask).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,32 @@ describe('PUT /user', () => {
|
||||
expect(user.preferences.costume).to.eql(true);
|
||||
expect(user.stats.hp).to.eql(14);
|
||||
});
|
||||
|
||||
it('profile.name cannot be an empty string or null', async () => {
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': ' ', // string should be trimmed
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': null,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'User validation failed',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Top Level Protected Operations', () => {
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user._id).to.exist;
|
||||
expect(user.apiToken).to.exist;
|
||||
expect(user.auth.local.username).to.eql(username);
|
||||
expect(user.profile.name).to.eql(username);
|
||||
});
|
||||
|
||||
it('provides default tags and tasks', async () => {
|
||||
@@ -66,6 +67,7 @@ describe('POST /user/auth/local/register', () => {
|
||||
});
|
||||
|
||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||
});
|
||||
|
||||
it('requires password and confirmPassword to match', async () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('POST /user/auth/social', () => {
|
||||
|
||||
describe('facebook', () => {
|
||||
before(async () => {
|
||||
let expectedResult = {id: facebookId};
|
||||
let expectedResult = {id: facebookId, displayName: 'a facebook user'};
|
||||
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||
network = 'facebook';
|
||||
});
|
||||
@@ -47,6 +47,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
@@ -88,7 +89,7 @@ describe('POST /user/auth/social', () => {
|
||||
|
||||
describe('google', () => {
|
||||
before(async () => {
|
||||
let expectedResult = {id: googleId};
|
||||
let expectedResult = {id: googleId, displayName: 'a google user'};
|
||||
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||
network = 'google';
|
||||
});
|
||||
@@ -102,6 +103,7 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.apiToken).to.exist;
|
||||
expect(response.id).to.exist;
|
||||
expect(response.newUser).to.be.true;
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||
});
|
||||
|
||||
it('logs an existing user in', async () => {
|
||||
|
||||
@@ -278,6 +278,7 @@ describe('analyticsService', () => {
|
||||
todos: [{_id: 'todo'}],
|
||||
rewards: [{_id: 'reward'}],
|
||||
balance: 12,
|
||||
loginIncentives: 1,
|
||||
};
|
||||
|
||||
data.user = user;
|
||||
@@ -302,6 +303,8 @@ describe('analyticsService', () => {
|
||||
contributorLevel: 1,
|
||||
subscription: 'foo-plan',
|
||||
balance: 12,
|
||||
balanceGemAmount: 48,
|
||||
loginIncentives: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -351,7 +354,8 @@ describe('analyticsService', () => {
|
||||
purchaseType: 'checkout',
|
||||
gift: false,
|
||||
quantity: 1,
|
||||
headers: {'x-client': 'habitica-web',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { clone } from 'lodash';
|
||||
import common from '../../../../../website/common';
|
||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
// const scoreTask = common.ops.scoreTask;
|
||||
|
||||
@@ -17,9 +18,6 @@ describe('cron', () => {
|
||||
let user;
|
||||
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
||||
let daysMissed = 0;
|
||||
let analytics = {
|
||||
track: sinon.spy(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User({
|
||||
@@ -34,11 +32,17 @@ describe('cron', () => {
|
||||
},
|
||||
});
|
||||
|
||||
sinon.spy(analytics, 'track');
|
||||
|
||||
user._statsComputed = {
|
||||
mp: 10,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
it('updates user.preferences.timezoneOffsetAtLastCron', () => {
|
||||
let timezoneOffsetFromUserPrefs = 1;
|
||||
|
||||
@@ -59,6 +63,11 @@ describe('cron', () => {
|
||||
expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore);
|
||||
});
|
||||
|
||||
it('calls analytics', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', () => {
|
||||
beforeEach(() => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -257,6 +266,11 @@ describe('cron', () => {
|
||||
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,
|
||||
@@ -286,7 +300,7 @@ describe('cron', () => {
|
||||
startDate: new Date(),
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[0].completed = true;
|
||||
|
||||
@@ -307,7 +321,7 @@ describe('cron', () => {
|
||||
value: 0,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
});
|
||||
|
||||
@@ -333,7 +347,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -435,7 +449,7 @@ describe('cron', () => {
|
||||
type: 'habit',
|
||||
};
|
||||
|
||||
let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line new-cap
|
||||
tasksByType.habits = [];
|
||||
tasksByType.habits.push(task);
|
||||
});
|
||||
@@ -476,7 +490,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -619,7 +633,7 @@ describe('cron', () => {
|
||||
type: 'daily',
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys = [];
|
||||
tasksByType.dailys.push(task);
|
||||
|
||||
@@ -656,9 +670,9 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length).to.be.greaterThan(0);
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore,
|
||||
mp: user.stats.mp - mpBefore,
|
||||
});
|
||||
@@ -675,13 +689,14 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length).to.be.greaterThan(0);
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore1,
|
||||
mp: user.stats.mp - mpBefore1,
|
||||
});
|
||||
|
||||
let notifsBefore2 = user.notifications.length;
|
||||
let hpBefore2 = user.stats.hp;
|
||||
let mpBefore2 = user.stats.mp;
|
||||
|
||||
@@ -689,12 +704,14 @@ describe('cron', () => {
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].type).to.equal('CRON');
|
||||
expect(user.notifications[0].data).to.eql({
|
||||
expect(user.notifications.length - notifsBefore2).to.equal(0);
|
||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
||||
expect(user.notifications[1].type).to.equal('CRON');
|
||||
expect(user.notifications[1].data).to.eql({
|
||||
hp: user.stats.hp - hpBefore2 - (hpBefore2 - hpBefore1),
|
||||
mp: user.stats.mp - mpBefore2 - (mpBefore2 - mpBefore1),
|
||||
});
|
||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -747,6 +764,188 @@ describe('cron', () => {
|
||||
expect(user.inbox.messages[messageId]).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('login incentives', () => {
|
||||
it('increments incentive counter each cron', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
user.lastCron = moment(new Date()).subtract({days: 1});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
});
|
||||
|
||||
it('pushes a notification of the day\'s incentive each cron', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.notifications.length).to.be.greaterThan(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('replaces previous notifications', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
let filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE');
|
||||
|
||||
expect(filteredNotifications.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if days are skipped in between', () => {
|
||||
daysMissed = 3;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
||||
user.preferences.sleep = true;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards user bard robes if login incentive is 1', () => {
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user incentive backgrounds if login incentive is 2', () => {
|
||||
user.loginIncentives = 1;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
expect(user.purchased.background.blue).to.eql(true);
|
||||
expect(user.purchased.background.green).to.eql(true);
|
||||
expect(user.purchased.background.purple).to.eql(true);
|
||||
expect(user.purchased.background.red).to.eql(true);
|
||||
expect(user.purchased.background.yellow).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user Bard Hat if login incentive is 3', () => {
|
||||
user.loginIncentives = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(3);
|
||||
expect(user.items.gear.owned.head_special_bardHat).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 4', () => {
|
||||
user.loginIncentives = 3;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(4);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', () => {
|
||||
user.loginIncentives = 4;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(5);
|
||||
|
||||
expect(user.items.food.Chocolate).to.eql(1);
|
||||
expect(user.items.food.Meat).to.eql(1);
|
||||
expect(user.items.food.CottonCandyPink).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user moon quest if login incentive is 7', () => {
|
||||
user.loginIncentives = 6;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(7);
|
||||
expect(user.items.quests.moon1).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 10', () => {
|
||||
user.loginIncentives = 9;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(10);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', () => {
|
||||
user.loginIncentives = 13;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(14);
|
||||
|
||||
expect(user.items.food.Strawberry).to.eql(1);
|
||||
expect(user.items.food.Potatoe).to.eql(1);
|
||||
expect(user.items.food.CottonCandyBlue).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a bard instrument if login incentive is 18', () => {
|
||||
user.loginIncentives = 17;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(18);
|
||||
expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user second moon quest if login incentive is 22', () => {
|
||||
user.loginIncentives = 21;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(22);
|
||||
expect(user.items.quests.moon2).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 26', () => {
|
||||
user.loginIncentives = 25;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(26);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', () => {
|
||||
user.loginIncentives = 29;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(30);
|
||||
|
||||
expect(user.items.food.Fish).to.eql(1);
|
||||
expect(user.items.food.Milk).to.eql(1);
|
||||
expect(user.items.food.RottenMeat).to.eql(1);
|
||||
expect(user.items.food.Honey).to.eql(1);
|
||||
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 35', () => {
|
||||
user.loginIncentives = 34;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(35);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user the third moon quest if login incentive is 40', () => {
|
||||
user.loginIncentives = 39;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(40);
|
||||
expect(user.items.quests.moon3).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 45', () => {
|
||||
user.loginIncentives = 44;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(45);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
|
||||
it('awards user a saddle if login incentive is 50', () => {
|
||||
user.loginIncentives = 49;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.loginIncentives).to.eql(50);
|
||||
expect(user.items.food.Saddle).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recoverCron', () => {
|
||||
|
||||
@@ -4,14 +4,20 @@ import analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import notifications from '../../../../../website/server/libs/pushNotifications';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import stripeModule from 'stripe';
|
||||
import moment from 'moment';
|
||||
import { translate as t } from '../../../../helpers/api-v3-integration.helper';
|
||||
import {
|
||||
generateGroup,
|
||||
} from '../../../../helpers/api-unit.helper.js';
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import amzLib from '../../../../../website/server/libs/amazonPayments';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user, group, data, plan;
|
||||
|
||||
let stripe = stripeModule('test');
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
@@ -145,6 +151,14 @@ describe('payments/index', () => {
|
||||
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(recipient.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('does not change plan.customerId if it already exists', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
data.customerId = 'purchaserCustomerId';
|
||||
@@ -173,9 +187,10 @@ describe('payments/index', () => {
|
||||
|
||||
it('sends a private message about the gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledOnce;
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 3 months of subscription!\`');
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
});
|
||||
|
||||
it('sends an email about the gift', async () => {
|
||||
@@ -615,7 +630,40 @@ describe('payments/index', () => {
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledOnce;
|
||||
expect(sender.sendTxn).to.be.calledWith(user, 'cancel-subscription');
|
||||
expect(sender.sendTxn).to.be.calledWith(user, 'group-cancel-subscription');
|
||||
});
|
||||
|
||||
it('prevents non group leader from manging subscription', async () => {
|
||||
let groupMember = new User();
|
||||
data.user = groupMember;
|
||||
data.groupId = group._id;
|
||||
|
||||
await expect(api.cancelSubscription(data))
|
||||
.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('onlyGroupLeaderCanManageSubscription'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows old group leader to cancel if they created the subscription', async () => {
|
||||
data.groupId = group._id;
|
||||
data.sub = {
|
||||
key: 'group_monthly',
|
||||
};
|
||||
data.paymentMethod = 'Payment Method';
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
let newLeader = new User();
|
||||
updatedGroup.leader = newLeader._id;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
updatedGroup = await Group.findById(group._id).exec();
|
||||
|
||||
expect(updatedGroup.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -689,14 +737,190 @@ describe('payments/index', () => {
|
||||
|
||||
it('sends a message from purchaser to recipient', async () => {
|
||||
await api.buyGems(data);
|
||||
let msg = '\`Hello recipient, sender has sent you 4 gems!\`';
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, '\`Hello recipient, sender has sent you 4 gems!\`');
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
||||
});
|
||||
|
||||
it('sends a push notification if user did not gift to self', async () => {
|
||||
await api.buyGems(data);
|
||||
expect(notifications.sendNotification).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('sends gem donation message in each participant\'s language', async () => {
|
||||
// TODO using english for both users because other languages are not loaded
|
||||
// for api.buyGems
|
||||
await recipient.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await user.update({
|
||||
'preferences.language': 'en',
|
||||
});
|
||||
await api.buyGems(data);
|
||||
|
||||
let [recipientsMessageContent, sendersMessageContent] = ['en', 'en'].map((lang) => {
|
||||
let messageContent = t('giftedGemsFull', {
|
||||
username: recipient.profile.name,
|
||||
sender: user.profile.name,
|
||||
gemAmount: data.gift.gems.amount,
|
||||
}, lang);
|
||||
|
||||
return `\`${messageContent}\``;
|
||||
});
|
||||
|
||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgradeGroupPlan', () => {
|
||||
let spy;
|
||||
|
||||
beforeEach(function () {
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves([]);
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
});
|
||||
|
||||
it('updates a group plan quantity', async () => {
|
||||
data.paymentMethod = 'Stripe';
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.updateStripeGroupPlan(updatedGroup, stripe);
|
||||
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(4);
|
||||
});
|
||||
|
||||
it('does not update a group plan quantity that has a payment method other than stripe', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
let updatedGroup = await Group.findById(group._id).exec();
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
|
||||
updatedGroup.memberCount += 1;
|
||||
await updatedGroup.save();
|
||||
|
||||
await api.updateStripeGroupPlan(updatedGroup, stripe);
|
||||
|
||||
expect(spy.calledOnce).to.be.false;
|
||||
expect(updatedGroup.purchased.plan.quantity).to.eql(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('payWithStripe', () => {
|
||||
let spy;
|
||||
let stripeCreateCustomerSpy;
|
||||
let createSubSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||
spy.returnsPromise().resolves;
|
||||
|
||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||
let stripCustomerResponse = {
|
||||
subscriptions: {
|
||||
data: [{id: 'test-id'}],
|
||||
},
|
||||
};
|
||||
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
|
||||
|
||||
createSubSpy = sinon.stub(api, 'createSubscription');
|
||||
createSubSpy.returnsPromise().resolves({});
|
||||
|
||||
data.groupId = group._id;
|
||||
data.sub.quantity = 3;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore(stripe.subscriptions.update);
|
||||
stripe.customers.create.restore();
|
||||
api.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('subscribes with stripe', async () => {
|
||||
let token = 'test-token';
|
||||
let gift;
|
||||
let sub = data.sub;
|
||||
let groupId = group._id;
|
||||
let email = 'test@test.com';
|
||||
let headers = {};
|
||||
let coupon;
|
||||
|
||||
await api.payWithStripe({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
sub,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
expect(stripeCreateCustomerSpy.calledOnce).to.be.true;
|
||||
expect(createSubSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribeWithAmazon', () => {
|
||||
let amazonSetBillingAgreementDetailsSpy;
|
||||
let amazonConfirmBillingAgreementSpy;
|
||||
let amazongAuthorizeOnBillingAgreementSpy;
|
||||
let createSubSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
|
||||
amazonSetBillingAgreementDetailsSpy.returnsPromise().resolves({});
|
||||
|
||||
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
|
||||
amazonConfirmBillingAgreementSpy.returnsPromise().resolves({});
|
||||
|
||||
amazongAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||
amazongAuthorizeOnBillingAgreementSpy.returnsPromise().resolves({});
|
||||
|
||||
createSubSpy = sinon.stub(api, 'createSubscription');
|
||||
createSubSpy.returnsPromise().resolves({});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
amzLib.setBillingAgreementDetails.restore();
|
||||
amzLib.confirmBillingAgreement.restore();
|
||||
amzLib.authorizeOnBillingAgreement.restore();
|
||||
api.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('subscribes with amazon', async () => {
|
||||
let billingAgreementId = 'billingAgreementId';
|
||||
let sub = data.sub;
|
||||
let coupon;
|
||||
let groupId = group._id;
|
||||
let headers = {};
|
||||
|
||||
await api.subscribeWithAmazon({
|
||||
billingAgreementId,
|
||||
sub,
|
||||
coupon,
|
||||
user,
|
||||
groupId,
|
||||
headers,
|
||||
});
|
||||
|
||||
expect(amazonSetBillingAgreementDetailsSpy.calledOnce).to.be.true;
|
||||
expect(amazonConfirmBillingAgreementSpy.calledOnce).to.be.true;
|
||||
expect(amazongAuthorizeOnBillingAgreementSpy.calledOnce).to.be.true;
|
||||
expect(createSubSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
createTasks,
|
||||
getTasks,
|
||||
syncableAttrs,
|
||||
moveTask,
|
||||
} from '../../../../../website/server/libs/taskManager';
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import {
|
||||
@@ -169,4 +170,12 @@ describe('taskManager', () => {
|
||||
expect(syncableTask.notes).to.not.exist;
|
||||
expect(syncableTask.updatedAt).to.not.exist;
|
||||
});
|
||||
|
||||
it('moves tasks to a specified position', async() => {
|
||||
let order = ['task-id-1', 'task-id-2'];
|
||||
|
||||
moveTask(order, 'task-id-2', 0);
|
||||
|
||||
expect(order).to.eql(['task-id-2', 'task-id-1']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,6 @@ describe('response middleware', () => {
|
||||
{
|
||||
type: notification.type,
|
||||
id: notification.id,
|
||||
createdAt: notification.createdAt,
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -166,7 +166,7 @@ describe('Challenge Model', () => {
|
||||
|
||||
context('type specific updates', () => {
|
||||
it('updates habit specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.habit(Tasks.Task.sanitize(tasksToTest.habit)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.habit(Tasks.Task.sanitize(tasksToTest.habit)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -185,7 +185,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('updates todo specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -201,7 +201,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('does not update checklists on the user task', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
@@ -219,7 +219,7 @@ describe('Challenge Model', () => {
|
||||
});
|
||||
|
||||
it('updates daily specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.daily(Tasks.Task.sanitize(tasksToTest.daily)); // eslint-disable-line babel/new-cap
|
||||
task = new Tasks.daily(Tasks.Task.sanitize(tasksToTest.daily)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { sleep } from '../../../../helpers/api-unit.helper';
|
||||
import { model as Group, INVITES_LIMIT } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { BadRequest } from '../../../../../website/server/libs/errors';
|
||||
import {
|
||||
BadRequest,
|
||||
} from '../../../../../website/server/libs/errors';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content';
|
||||
import { groupChatReceivedWebhook } from '../../../../../website/server/libs/webhook';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import validator from 'validator';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import shared from '../../../../../website/common';
|
||||
|
||||
describe('Group Model', () => {
|
||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||
@@ -628,24 +631,105 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a private group when the last member leaves', async () => {
|
||||
party.memberCount = 1;
|
||||
|
||||
it('deletes a private party when the last member leaves', async () => {
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not delete a public group when the last member leaves', async () => {
|
||||
it('does not delete a private group when the last member leaves and a subscription is active', async () => {
|
||||
party.memberCount = 1;
|
||||
party.purchased.plan.customerId = '110002222333';
|
||||
|
||||
await expect(party.leave(participatingMember))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
name: 'NotAuthorized',
|
||||
httpCode: 401,
|
||||
message: shared.i18n.t('cannotDeleteActiveGroup'),
|
||||
});
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
expect(party.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('does not delete a public group when the last member leaves', async () => {
|
||||
party.privacy = 'public';
|
||||
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(questLeader);
|
||||
await party.leave(nonParticipatingMember);
|
||||
await party.leave(undecidedMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
});
|
||||
|
||||
it('does not delete a private party when the member count reaches zero if there are still members', async () => {
|
||||
party.memberCount = 1;
|
||||
|
||||
await party.leave(participatingMember);
|
||||
|
||||
party = await Group.findOne({_id: party._id});
|
||||
expect(party).to.exist;
|
||||
});
|
||||
|
||||
it('deletes a private guild when the last member leaves', async () => {
|
||||
let guild = new Group({
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
let leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
]);
|
||||
|
||||
await guild.leave(leader);
|
||||
|
||||
guild = await Group.findOne({_id: guild._id});
|
||||
expect(guild).to.not.exist;
|
||||
});
|
||||
|
||||
it('does not delete a private guild when the member count reaches zero if there are still members', async () => {
|
||||
let guild = new Group({
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
let leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
let member = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
member.save(),
|
||||
]);
|
||||
|
||||
await guild.leave(member);
|
||||
|
||||
guild = await Group.findOne({_id: guild._id});
|
||||
expect(guild).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sendChat', () => {
|
||||
@@ -1061,8 +1145,45 @@ describe('Group Model', () => {
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
});
|
||||
|
||||
sandbox.spy(User, 'update');
|
||||
describe('user update retry failures', () => {
|
||||
let successfulMock = {
|
||||
exec: () => {
|
||||
return Promise.resolve({raw: 'sucess'});
|
||||
},
|
||||
};
|
||||
let failedMock = {
|
||||
exec: () => {
|
||||
return Promise.reject(new Error('error'));
|
||||
},
|
||||
};
|
||||
|
||||
it('doesn\'t retry successful operations', async () => {
|
||||
sandbox.stub(User, 'update').returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledTwice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
let updateStub = sandbox.stub(User, 'update');
|
||||
updateStub.onCall(0).returns(failedMock);
|
||||
updateStub.returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
sandbox.stub(User, 'update').returns(failedMock);
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.update.callCount).to.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('gives out achievements', async () => {
|
||||
@@ -1138,14 +1259,34 @@ describe('Group Model', () => {
|
||||
expect(updatedParticipatingMember.items.hatchingPotions.Shade).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards quests', async () => {
|
||||
it('awards quest scrolls to owner', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.quests.vice3).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards non quest leader rewards to quest leader', async () => {
|
||||
let gearQuest = questScrolls.vice3;
|
||||
|
||||
await party.finishQuest(gearQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.gear.owned.weapon_special_2).to.eql(true);
|
||||
});
|
||||
|
||||
it('doesn\'t award quest owner rewards to all participants', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedParticipatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.eql(1);
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.not.exist;
|
||||
});
|
||||
|
||||
it('awards pets', async () => {
|
||||
@@ -1171,13 +1312,15 @@ describe('Group Model', () => {
|
||||
|
||||
context('Party quests', () => {
|
||||
it('updates participating members with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledTwice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: {
|
||||
$in: [questLeader._id, participatingMember._id],
|
||||
},
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1204,6 +1347,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates all users with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
await party.finishQuest(tavernQuest);
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { model as Challenge } from '../../../../../website/server/models/challen
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../../website/server/models/task';
|
||||
import { each, find } from 'lodash';
|
||||
import { each, find, findIndex } from 'lodash';
|
||||
|
||||
describe('Group Task Methods', () => {
|
||||
let guild, leader, challenge, task;
|
||||
@@ -68,11 +68,29 @@ describe('Group Task Methods', () => {
|
||||
task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue));
|
||||
task.group.id = guild._id;
|
||||
await task.save();
|
||||
if (task.checklist) {
|
||||
task.checklist.push({
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('syncs an assigned task to a user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let tagIndex = findIndex(updatedLeader.tags, {id: guild._id});
|
||||
let newTag = updatedLeader.tags[tagIndex];
|
||||
|
||||
expect(newTag.id).to.equal(guild._id);
|
||||
expect(newTag.name).to.equal(guild.name);
|
||||
expect(newTag.group).to.equal(guild._id);
|
||||
});
|
||||
|
||||
it('create tags for a user when task is synced', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
@@ -96,38 +114,124 @@ describe('Group Task Methods', () => {
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to all users', async () => {
|
||||
let newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
it('syncs checklist items to an assigned user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.syncTask(task, newMember);
|
||||
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
task.group.approval.required = true;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.group.approval.required).to.equal(true);
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(task.checklist[0].text);
|
||||
});
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
expect(syncedMemberTask.group.approval.required).to.equal(true);
|
||||
describe('syncs updated info', async() => {
|
||||
let newMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.syncTask(task, newMember);
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to all users', async () => {
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
task.group.approval.required = true;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.group.approval.required).to.equal(true);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
expect(syncedMemberTask.group.approval.required).to.equal(true);
|
||||
});
|
||||
|
||||
it('syncs a new checklist item to all assigned users', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let newCheckListItem = {
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
};
|
||||
|
||||
task.checklist.push(newCheckListItem);
|
||||
|
||||
await guild.updateTask(task, {newCheckListItem});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
});
|
||||
|
||||
it('syncs updated info for checklist in assigned task to all users when flag is passed', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let updateCheckListText = 'Updated checklist item';
|
||||
if (task.checklist) {
|
||||
task.checklist[0].text = updateCheckListText;
|
||||
}
|
||||
|
||||
await guild.updateTask(task, {updateCheckListItems: [task.checklist[0]]});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
});
|
||||
|
||||
it('removes a checklist item in assigned task to all users when flag is passed with checklist id', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
await guild.updateTask(task, {removedCheckListItemId: task.checklist[0].id});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(0);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes an assigned task and unlinks assignees', async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('Task Model', () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line babel/new-cap
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line new-cap
|
||||
text: 'some text',
|
||||
alias: 'short-name',
|
||||
userId: user.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
describe('User Model', () => {
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
@@ -48,28 +49,100 @@ describe('User Model', () => {
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications with data', () => {
|
||||
it('can add notifications without data', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON');
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('can add notifications without data', () => {
|
||||
it('can add notifications with data', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1});
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
it('adds notifications for a single member via static method', async() => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
await User.pushNotification({_id: user._id}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('validates notifications via static method', async() => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('adds notifications without data for all given users via static method', async() => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('adds notifications with data for all given users via static method', async() => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1});
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,7 +275,8 @@ describe('Challenges Controller', function() {
|
||||
describe('editTask', function() {
|
||||
it('is Tasks.editTask', function() {
|
||||
inject(function(Tasks) {
|
||||
expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
|
||||
// expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,10 @@ describe('Inventory Controller', function() {
|
||||
},
|
||||
preferences: {
|
||||
suppressModals: {}
|
||||
}
|
||||
},
|
||||
purchased: {
|
||||
plan: {}
|
||||
},
|
||||
});
|
||||
|
||||
Shared.wrap(user);
|
||||
@@ -450,4 +453,84 @@ describe('Inventory Controller', function() {
|
||||
expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Gear search filter', function() {
|
||||
var wrap = function(text) {
|
||||
return {'text': function() {return text;}};
|
||||
}
|
||||
|
||||
var toText = function(list) {
|
||||
return _.map(list, function(ele) { return ele.text(); });
|
||||
}
|
||||
|
||||
var gearByClass, gearByType;
|
||||
|
||||
beforeEach(function() {
|
||||
scope.$digest();
|
||||
gearByClass = {'raw': [wrap('kale'), wrap('sashimi')],
|
||||
'cooked': [wrap('chicken'), wrap('potato')]};
|
||||
|
||||
gearByType = {'veg': [wrap('kale'), wrap('potato')],
|
||||
'not': [wrap('chicken'), wrap('sashimi')]};
|
||||
scope.gearByClass = gearByClass;
|
||||
scope.gearByType = gearByType;
|
||||
scope.equipmentQuery.query = 'a';
|
||||
});
|
||||
|
||||
it('filters nothing if equipmentQuery is nothing', function() {
|
||||
scope.equipmentQuery.query = '';
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken', 'sashimi']);
|
||||
});
|
||||
|
||||
it('filters out gear if class gear changes', function() {
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato']);
|
||||
|
||||
scope.gearByClass['raw'].push(wrap('zucchini'));
|
||||
scope.gearByClass['cooked'].push(wrap('pizza'));
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql(['kale', 'sashimi']);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['potato', 'pizza']);
|
||||
});
|
||||
|
||||
it('filters out gear if typed gear changes', function() {
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi']);
|
||||
|
||||
scope.gearByType['veg'].push(wrap('zucchini'));
|
||||
scope.gearByType['not'].push(wrap('pizza'));
|
||||
|
||||
scope.$digest();
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql(['kale', 'potato']);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['sashimi', 'pizza']);
|
||||
});
|
||||
|
||||
it('filters out gear if filter query changes', function() {
|
||||
scope.equipmentQuery.query = 'c';
|
||||
scope.$digest();
|
||||
|
||||
expect(toText(scope.filteredGearByClass['raw'])).to.eql([]);
|
||||
expect(toText(scope.filteredGearByClass['cooked'])).to.eql(['chicken']);
|
||||
expect(toText(scope.filteredGearByType['veg'])).to.eql([]);
|
||||
expect(toText(scope.filteredGearByType['not'])).to.eql(['chicken']);
|
||||
});
|
||||
|
||||
it('returns the right filtered gear', function() {
|
||||
var equipment = [wrap('spicy tuna'), wrap('dragon'), wrap('rainbow'), wrap('caterpillar')];
|
||||
expect(toText(scope.equipmentSearch(equipment, 'ra'))).to.eql(['dragon', 'rainbow']);
|
||||
});
|
||||
|
||||
it('returns the right filtered gear if the source gear has unicode', function() {
|
||||
// blue hat, red hat, red shield
|
||||
var equipment = [wrap('藍色軟帽'), wrap('紅色軟帽'), wrap('紅色盾牌')];
|
||||
// searching for 'red' gives red hat, red shield
|
||||
expect(toText(scope.equipmentSearch(equipment, '紅色'))).to.eql(['紅色軟帽', '紅色盾牌']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('Notification Controller', function() {
|
||||
let User = {
|
||||
user,
|
||||
readNotification: function noop () {},
|
||||
readNotifications: function noop () {},
|
||||
sync: userSync
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ describe('Tasks Controller', function() {
|
||||
describe('editTask', function() {
|
||||
it('is Tasks.editTask', function() {
|
||||
inject(function(Tasks) {
|
||||
expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
// @TODO: Currently we override the task function in the challenge ctrl, but we should abstract it again
|
||||
// expect(scope.editTask).to.eql(Tasks.editTask);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,6 +194,7 @@ describe('Analytics Service', function () {
|
||||
rewards: 1
|
||||
};
|
||||
expectedProperties.balance = 12;
|
||||
expectedProperties.balanceGemAmount = 48;
|
||||
|
||||
beforeEach(function() {
|
||||
user._id = 'unique-user-id';
|
||||
@@ -243,7 +244,8 @@ describe('Analytics Service', function () {
|
||||
habits: 1,
|
||||
rewards: 1
|
||||
},
|
||||
balance: 12
|
||||
balance: 12,
|
||||
balanceGemAmount: 48
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
@@ -16,6 +16,15 @@ describe('Tasks Service', function() {
|
||||
rootScope.charts = {};
|
||||
tasks = Tasks;
|
||||
});
|
||||
|
||||
rootScope.openModal = function() {
|
||||
return {
|
||||
result: {
|
||||
then: function() {},
|
||||
catch: function() {},
|
||||
},
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
it('calls get user tasks endpoint', function() {
|
||||
@@ -81,6 +90,14 @@ describe('Tasks Service', function() {
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('calls group move task endpoint', function() {
|
||||
var taskId = 1;
|
||||
var position = 0;
|
||||
$httpBackend.expectPOST('/api/v3/group-tasks/' + taskId + '/move/to/' + position).respond({});
|
||||
tasks.moveGroupTask(taskId, position);
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('calls add check list item endpoint', function() {
|
||||
var taskId = 1;
|
||||
$httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist').respond({});
|
||||
@@ -151,7 +168,6 @@ describe('Tasks Service', function() {
|
||||
});
|
||||
|
||||
describe('editTask', function() {
|
||||
|
||||
var task;
|
||||
|
||||
beforeEach(function(){
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": ["transform-object-rest-spread"],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"syntax-async-functions",
|
||||
"transform-regenerator",
|
||||
],
|
||||
"comments": false
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
// for how to write custom assertions see
|
||||
// http://nightwatchjs.org/guide#writing-custom-assertions
|
||||
exports.assertion = function (selector, count) {
|
||||
this.message = 'Testing if element <' + selector + '> has count: ' + count;
|
||||
this.message = `Testing if element <${selector}> has count: ${count}`;
|
||||
this.expected = count;
|
||||
this.pass = function (val) {
|
||||
return val === this.expected;
|
||||
@@ -16,11 +16,10 @@ exports.assertion = function (selector, count) {
|
||||
return res.value;
|
||||
};
|
||||
this.command = function (cb) {
|
||||
var self = this;
|
||||
return this.api.execute(function (selector) {
|
||||
return document.querySelectorAll(selector).length;
|
||||
}, [selector], function (res) {
|
||||
cb.call(self, res);
|
||||
return this.api.execute((el) => {
|
||||
return document.querySelectorAll(el).length;
|
||||
}, [selector], (res) => {
|
||||
cb.call(this, res);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,45 +1,48 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
require('babel-register');
|
||||
var config = require('../../../webpack/config');
|
||||
const config = require('../../../webpack/config');
|
||||
const chromeDriverPath = require('chromedriver').path;
|
||||
|
||||
// http://nightwatchjs.org/guide#settings-file
|
||||
module.exports = {
|
||||
'src_folders': ['test/client/e2e/specs'],
|
||||
'output_folder': 'test/client/e2e/reports',
|
||||
'custom_assertions_path': ['test/client/e2e/custom-assertions'],
|
||||
src_folders: ['test/client/e2e/specs'],
|
||||
output_folder: 'test/client/e2e/reports',
|
||||
custom_assertions_path: ['test/client/e2e/custom-assertions'],
|
||||
|
||||
'selenium': {
|
||||
'start_process': true,
|
||||
'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar',
|
||||
'host': '127.0.0.1',
|
||||
'port': 4444,
|
||||
'cli_args': {
|
||||
'webdriver.chrome.driver': require('chromedriver').path,
|
||||
selenium: {
|
||||
start_process: true,
|
||||
server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar',
|
||||
host: '127.0.0.1',
|
||||
port: 4444,
|
||||
cli_args: {
|
||||
'webdriver.chrome.driver': chromeDriverPath,
|
||||
},
|
||||
},
|
||||
|
||||
'test_settings': {
|
||||
'default': {
|
||||
'selenium_port': 4444,
|
||||
'selenium_host': 'localhost',
|
||||
'silent': true,
|
||||
'globals': {
|
||||
'devServerURL': 'http://localhost:' + (process.env.PORT || config.dev.port),
|
||||
test_settings: {
|
||||
default: {
|
||||
selenium_port: 4444,
|
||||
selenium_host: 'localhost',
|
||||
silent: true,
|
||||
globals: {
|
||||
devServerURL: `http://localhost:${process.env.PORT || config.dev.port}`, // eslint-disable-line no-process-env
|
||||
},
|
||||
},
|
||||
|
||||
'chrome': {
|
||||
'desiredCapabilities': {
|
||||
'browserName': 'chrome',
|
||||
'javascriptEnabled': true,
|
||||
'acceptSslCerts': true,
|
||||
chrome: {
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
javascriptEnabled: true,
|
||||
acceptSslCerts: true,
|
||||
},
|
||||
},
|
||||
|
||||
'firefox': {
|
||||
'desiredCapabilities': {
|
||||
'browserName': 'firefox',
|
||||
'javascriptEnabled': true,
|
||||
'acceptSslCerts': true,
|
||||
firefox: {
|
||||
desiredCapabilities: {
|
||||
browserName: 'firefox',
|
||||
javascriptEnabled: true,
|
||||
acceptSslCerts: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 1. start the dev server using production config
|
||||
process.env.NODE_ENV = 'testing';
|
||||
var server = require('../../../webpack/dev-server.js');
|
||||
process.env.NODE_ENV = 'testing'; // eslint-disable-line no-process-env
|
||||
const server = require('../../../webpack/dev-server.js');
|
||||
|
||||
// 2. run the nightwatch test suite against it
|
||||
// to run in additional browsers:
|
||||
@@ -9,7 +9,7 @@ var server = require('../../../webpack/dev-server.js');
|
||||
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
|
||||
// For more information on Nightwatch's config file, see
|
||||
// http://nightwatchjs.org/guide#settings-file
|
||||
var opts = process.argv.slice(2);
|
||||
let opts = process.argv.slice(2);
|
||||
if (opts.indexOf('--config') === -1) {
|
||||
opts = opts.concat(['--config', 'test/client/e2e/nightwatch.conf.js']);
|
||||
}
|
||||
@@ -17,8 +17,8 @@ if (opts.indexOf('--env') === -1) {
|
||||
opts = opts.concat(['--env', 'chrome']);
|
||||
}
|
||||
|
||||
var spawn = require('cross-spawn');
|
||||
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });
|
||||
const spawn = require('cross-spawn');
|
||||
const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });
|
||||
|
||||
runner.on('exit', function (code) {
|
||||
server.close();
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
// http://nightwatchjs.org/guide#usage
|
||||
|
||||
module.exports = {
|
||||
'default e2e tests': function (browser) {
|
||||
|
||||
'default e2e tests' (browser) {
|
||||
// automatically uses dev Server port from /config.index.js
|
||||
// default: http://localhost:8080
|
||||
// see nightwatch.conf.js
|
||||
var devServer = browser.globals.devServerURL;
|
||||
const devServer = browser.globals.devServerURL;
|
||||
|
||||
browser
|
||||
.url(devServer)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// TODO verify if it's needed, added because Vuex require Promise in the global scope
|
||||
// TODO verify if it's needed, added because Axios require Promise in the global scope
|
||||
// and babel-runtime doesn't affect external libraries
|
||||
require('babel-polyfill');
|
||||
|
||||
// require all test files (files that ends with .spec.js)
|
||||
var testsContext = require.context('./specs', true, /\.spec$/);
|
||||
let testsContext = require.context('./specs', true, /\.spec$/);
|
||||
testsContext.keys().forEach(testsContext);
|
||||
|
||||
// require all .vue and .js files except main.js for coverage.
|
||||
var srcContext = require.context('../../../website/client', true, /^\.\/(?=(?!main(\.js)?$))(?=(.*\.(vue|js)$))/);
|
||||
let srcContext = require.context('../../../website/client', true, /^\.\/(?=(?!main(\.js)?$))(?=(.*\.(vue|js)$))/);
|
||||
srcContext.keys().forEach(srcContext);
|
||||
@@ -3,14 +3,15 @@
|
||||
// we are also using it with karma-webpack
|
||||
// https://github.com/webpack/karma-webpack
|
||||
|
||||
var path = require('path');
|
||||
var merge = require('webpack-merge');
|
||||
var baseConfig = require('../../../webpack/webpack.base.conf');
|
||||
var utils = require('../../../webpack/utils');
|
||||
var webpack = require('webpack');
|
||||
var projectRoot = path.resolve(__dirname, '../../../');
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const baseConfig = require('../../../webpack/webpack.base.conf');
|
||||
const utils = require('../../../webpack/utils');
|
||||
const webpack = require('webpack');
|
||||
const projectRoot = path.resolve(__dirname, '../../../');
|
||||
const testEnv = require('../../../webpack/config/test.env');
|
||||
|
||||
var webpackConfig = merge(baseConfig, {
|
||||
const webpackConfig = merge(baseConfig, {
|
||||
// use inline sourcemap for karma-sourcemap-loader
|
||||
module: {
|
||||
loaders: utils.styleLoaders(),
|
||||
@@ -23,7 +24,7 @@ var webpackConfig = merge(baseConfig, {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../../../webpack/config/test.env'),
|
||||
'process.env': testEnv,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -36,11 +37,14 @@ webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || [];
|
||||
webpackConfig.module.preLoaders.unshift({
|
||||
test: /\.js$/,
|
||||
loader: 'isparta',
|
||||
include: path.resolve(projectRoot, 'website/client'),
|
||||
include: [
|
||||
path.resolve(projectRoot, 'website/client'),
|
||||
path.resolve(projectRoot, 'website/common'),
|
||||
],
|
||||
});
|
||||
|
||||
// only apply babel for test files when using isparta
|
||||
webpackConfig.module.loaders.some(function (loader, i) {
|
||||
webpackConfig.module.loaders.some((loader) => {
|
||||
if (loader.loader === 'babel') {
|
||||
loader.include = path.resolve(projectRoot, 'test/client/unit');
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import floorFilter from 'client/filters/floor';
|
||||
|
||||
describe('floor filter', () => {
|
||||
it('can floor a decimal number', () => {
|
||||
expect(floorFilter(4.567)).to.equal(4.56);
|
||||
expect(floorFilter(4.562)).to.equal(4.56);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import roundFilter from 'client/filters/round';
|
||||
|
||||
describe('round filter', () => {
|
||||
it('can round a decimal number', () => {
|
||||
expect(roundFilter(4.567)).to.equal(4.57);
|
||||
expect(roundFilter(4.562)).to.equal(4.56);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { gems as userGems } from 'client/store/getters/user';
|
||||
|
||||
describe('userGems getter', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(userGems({
|
||||
state: {
|
||||
user: {
|
||||
balance: 4.5,
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import i18n from 'client/plugins/i18n';
|
||||
import commoni18n from 'common/script/i18n';
|
||||
import Vue from 'vue';
|
||||
|
||||
describe('i18n plugin', () => {
|
||||
before(() => {
|
||||
i18n.install(Vue);
|
||||
});
|
||||
|
||||
it('adds $t to Vue.prototype', () => {
|
||||
expect(Vue.prototype.$t).to.be.a.function;
|
||||
});
|
||||
|
||||
it('$t is a proxy for common/i18n.t', () => {
|
||||
const result = (new Vue()).$t('reportBug');
|
||||
expect(result).to.equal(commoni18n.t('reportBug'));
|
||||
expect(result).to.equal('Report a Bug');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import storeInjector from 'inject?-vue!client/store';
|
||||
import { mapState, mapGetters, mapActions } from 'client/store';
|
||||
import { flattenAndNamespace } from 'client/store/helpers/internals';
|
||||
|
||||
describe('Store', () => {
|
||||
let injectedStore;
|
||||
@@ -14,11 +15,25 @@ describe('Store', () => {
|
||||
computedName ({ state }) {
|
||||
return `${state.name} computed!`;
|
||||
},
|
||||
...flattenAndNamespace({
|
||||
nested: {
|
||||
computedName ({ state }) {
|
||||
return `${state.name} computed!`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
'./actions': {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
...flattenAndNamespace({
|
||||
nested: {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}).default;
|
||||
});
|
||||
@@ -41,17 +56,29 @@ describe('Store', () => {
|
||||
injectedStore.state.name = 'test updated';
|
||||
});
|
||||
|
||||
it('supports getters', () => {
|
||||
expect(injectedStore.getters.computedName).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters.computedName).to.equal('test updated computed!');
|
||||
describe('getters', () => {
|
||||
it('supports getters', () => {
|
||||
expect(injectedStore.getters.computedName).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters.computedName).to.equal('test updated computed!');
|
||||
});
|
||||
|
||||
it('supports nested getters', () => {
|
||||
expect(injectedStore.getters['nested:computedName']).to.equal('test computed!');
|
||||
injectedStore.state.name = 'test updated';
|
||||
expect(injectedStore.getters['nested:computedName']).to.equal('test updated computed!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('can be dispatched', () => {
|
||||
it('can dispatch an action', () => {
|
||||
expect(injectedStore.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
|
||||
});
|
||||
|
||||
it('can dispatch a nested action', () => {
|
||||
expect(injectedStore.dispatch('nested:getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
|
||||
});
|
||||
|
||||
it('throws an error is the action doesn\'t exists', () => {
|
||||
expect(() => injectedStore.dispatched('wrong')).to.throw;
|
||||
});
|
||||
@@ -116,5 +143,26 @@ describe('Store', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('flattenAndNamespace', () => {
|
||||
let result = flattenAndNamespace({
|
||||
nested: {
|
||||
computed ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
nested2: {
|
||||
getName ({ state }, ...args) {
|
||||
return [state.name, ...args];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(Object.keys(result).length).to.equal(3);
|
||||
expect(Object.keys(result).sort()).to.deep.equal(['nested2:getName', 'nested:computed', 'nested:getName']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
import shared from '../../../website/common';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
|
||||
describe('achievements', () => {
|
||||
describe('general well-formedness', () => {
|
||||
let user = generateUser();
|
||||
let achievements = shared.achievements.getAchievementsForProfile(user);
|
||||
|
||||
it('each category has \'label\' and \'achievements\' fields', () => {
|
||||
_.each(achievements, (category) => {
|
||||
expect(category).to.have.property('label')
|
||||
.that.is.a('string');
|
||||
expect(category).to.have.property('achievements')
|
||||
.that.is.a('object');
|
||||
});
|
||||
});
|
||||
|
||||
it('each achievement has all required fields of correct types', () => {
|
||||
_.each(achievements, (category) => {
|
||||
_.each(category.achievements, (achiev) => {
|
||||
// May have additional fields (such as 'value' and 'optionalCount').
|
||||
expect(achiev).to.contain.all.keys(['title', 'text', 'icon', 'earned', 'index']);
|
||||
expect(achiev.title).to.be.a('string');
|
||||
expect(achiev.text).to.be.a('string');
|
||||
expect(achiev.icon).to.be.a('string');
|
||||
expect(achiev.earned).to.be.a('boolean');
|
||||
expect(achiev.index).to.be.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('categories have unique labels', () => {
|
||||
let achievementsArray = _.values(achievements).map(cat => cat.label);
|
||||
let labels = _.uniq(achievementsArray);
|
||||
|
||||
expect(labels.length).to.be.greaterThan(0);
|
||||
expect(labels.length).to.eql(_.size(achievements));
|
||||
});
|
||||
|
||||
it('achievements have unique keys', () => {
|
||||
let keysSoFar = {};
|
||||
|
||||
_.each(achievements, (category) => {
|
||||
_.keys(category.achievements).forEach((key) => {
|
||||
expect(keysSoFar[key]).to.be.undefined;
|
||||
keysSoFar[key] = key;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('achievements have unique indices', () => {
|
||||
let indicesSoFar = {};
|
||||
|
||||
_.each(achievements, (category) => {
|
||||
_.each(category.achievements, (achiev) => {
|
||||
let i = achiev.index;
|
||||
expect(indicesSoFar[i]).to.be.undefined;
|
||||
indicesSoFar[i] = i;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('all categories have at least 1 achievement', () => {
|
||||
_.each(achievements, (category) => {
|
||||
expect(_.size(category.achievements)).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned basic achievements', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
it('streak and perfect day achievements exist with counts', () => {
|
||||
let streak = basicAchievs.streak;
|
||||
let perfect = basicAchievs.perfect;
|
||||
|
||||
expect(streak).to.exist;
|
||||
expect(streak).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(perfect).to.exist;
|
||||
expect(perfect).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('party up/on achievements exist with no counts', () => {
|
||||
let partyUp = basicAchievs.partyUp;
|
||||
let partyOn = basicAchievs.partyOn;
|
||||
|
||||
expect(partyUp).to.exist;
|
||||
expect(partyUp.optionalCount).to.be.undefined;
|
||||
expect(partyOn).to.exist;
|
||||
expect(partyOn.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('pet/mount master and triad bingo achievements exist with counts', () => {
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster).to.exist;
|
||||
expect(beastMaster).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(mountMaster).to.exist;
|
||||
expect(mountMaster).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(triadBingo).to.exist;
|
||||
expect(triadBingo).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('ultimate gear achievements exist with no counts', () => {
|
||||
let gearTypes = ['healer', 'rogue', 'warrior', 'mage'];
|
||||
gearTypes.forEach((gear) => {
|
||||
let gearAchiev = basicAchievs[`${gear}UltimateGear`];
|
||||
|
||||
expect(gearAchiev).to.exist;
|
||||
expect(gearAchiev.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('rebirth achievement exists with no count', () => {
|
||||
let rebirth = basicAchievs.rebirth;
|
||||
|
||||
expect(rebirth).to.exist;
|
||||
expect(rebirth.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned seasonal achievements', () => {
|
||||
let user = generateUser();
|
||||
let seasonalAchievs = shared.achievements.getAchievementsForProfile(user).seasonal.achievements;
|
||||
|
||||
it('habiticaDays and habitBirthdays achievements exist with counts', () => {
|
||||
let habiticaDays = seasonalAchievs.habiticaDays;
|
||||
let habitBirthdays = seasonalAchievs.habitBirthdays;
|
||||
|
||||
expect(habiticaDays).to.exist;
|
||||
expect(habiticaDays).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
expect(habitBirthdays).to.exist;
|
||||
expect(habitBirthdays).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('spell achievements exist with counts', () => {
|
||||
let spellTypes = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
||||
spellTypes.forEach((spell) => {
|
||||
let spellAchiev = seasonalAchievs[spell];
|
||||
|
||||
expect(spellAchiev).to.exist;
|
||||
expect(spellAchiev).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
it('quest achievements do not exist', () => {
|
||||
let quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
||||
quests.forEach((quest) => {
|
||||
let questAchiev = seasonalAchievs[`${quest}Quest`];
|
||||
|
||||
expect(questAchiev).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('costumeContests achievement exists with count', () => {
|
||||
let costumeContests = seasonalAchievs.costumeContests;
|
||||
|
||||
expect(costumeContests).to.exist;
|
||||
expect(costumeContests).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = seasonalAchievs[`${card}Cards`];
|
||||
|
||||
expect(cardAchiev).to.exist;
|
||||
expect(cardAchiev).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned special achievements', () => {
|
||||
let user = generateUser();
|
||||
let specialAchievs = shared.achievements.getAchievementsForProfile(user).special.achievements;
|
||||
|
||||
it('habitSurveys achievement exists with count', () => {
|
||||
let habitSurveys = specialAchievs.habitSurveys;
|
||||
|
||||
expect(habitSurveys).to.exist;
|
||||
expect(habitSurveys).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
|
||||
it('contributor achievement exists with value and no count', () => {
|
||||
let contributor = specialAchievs.contributor;
|
||||
|
||||
expect(contributor).to.exist;
|
||||
expect(contributor).to.have.property('value')
|
||||
.that.is.a('number');
|
||||
expect(contributor.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('npc achievement is hidden if unachieved', () => {
|
||||
let npc = specialAchievs.npc;
|
||||
expect(npc).to.not.exist;
|
||||
});
|
||||
|
||||
it('kickstarter achievement is hidden if unachieved', () => {
|
||||
let kickstarter = specialAchievs.kickstarter;
|
||||
expect(kickstarter).to.not.exist;
|
||||
});
|
||||
|
||||
it('veteran achievement is hidden if unachieved', () => {
|
||||
let veteran = specialAchievs.veteran;
|
||||
expect(veteran).to.not.exist;
|
||||
});
|
||||
|
||||
it('originalUser achievement is hidden if unachieved', () => {
|
||||
let originalUser = specialAchievs.originalUser;
|
||||
expect(originalUser).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('earned seasonal achievements', () => {
|
||||
let user = generateUser();
|
||||
let quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
||||
quests.forEach((quest) => {
|
||||
user.achievements.quests[quest] = 1;
|
||||
});
|
||||
let seasonalAchievs = shared.achievements.getAchievementsForProfile(user).seasonal.achievements;
|
||||
|
||||
it('quest achievements exist', () => {
|
||||
quests.forEach((quest) => {
|
||||
let questAchiev = seasonalAchievs[`${quest}Quest`];
|
||||
|
||||
expect(questAchiev).to.exist;
|
||||
expect(questAchiev.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('earned special achievements', () => {
|
||||
let user = generateUser({
|
||||
achievements: {
|
||||
habitSurveys: 2,
|
||||
veteran: true,
|
||||
originalUser: true,
|
||||
},
|
||||
backer: {tier: 3},
|
||||
contributor: {level: 1},
|
||||
});
|
||||
let specialAchievs = shared.achievements.getAchievementsForProfile(user).special.achievements;
|
||||
|
||||
it('habitSurveys achievement is earned with correct value', () => {
|
||||
let habitSurveys = specialAchievs.habitSurveys;
|
||||
|
||||
expect(habitSurveys).to.exist;
|
||||
expect(habitSurveys.earned).to.eql(true);
|
||||
expect(habitSurveys.value).to.eql(2);
|
||||
});
|
||||
|
||||
it('contributor achievement is earned with correct value', () => {
|
||||
let contributor = specialAchievs.contributor;
|
||||
|
||||
expect(contributor).to.exist;
|
||||
expect(contributor.earned).to.eql(true);
|
||||
expect(contributor.value).to.eql(1);
|
||||
});
|
||||
|
||||
it('npc achievement is earned with correct value', () => {
|
||||
let npcUser = generateUser({
|
||||
backer: {npc: 'test'},
|
||||
});
|
||||
let npc = shared.achievements.getAchievementsForProfile(npcUser).special.achievements.npc;
|
||||
|
||||
expect(npc).to.exist;
|
||||
expect(npc.earned).to.eql(true);
|
||||
expect(npc.value).to.eql('test');
|
||||
});
|
||||
|
||||
it('kickstarter achievement is earned with correct value', () => {
|
||||
let kickstarter = specialAchievs.kickstarter;
|
||||
|
||||
expect(kickstarter).to.exist;
|
||||
expect(kickstarter.earned).to.eql(true);
|
||||
expect(kickstarter.value).to.eql(3);
|
||||
});
|
||||
|
||||
it('veteran achievement is earned', () => {
|
||||
let veteran = specialAchievs.veteran;
|
||||
|
||||
expect(veteran).to.exist;
|
||||
expect(veteran.earned).to.eql(true);
|
||||
});
|
||||
|
||||
it('originalUser achievement is earned', () => {
|
||||
let originalUser = specialAchievs.originalUser;
|
||||
|
||||
expect(originalUser).to.exist;
|
||||
expect(originalUser.earned).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mountMaster, beastMaster, and triadBingo achievements', () => {
|
||||
it('master and triad bingo achievements do not include *Text2 strings if no keys have been used', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster.text).to.not.match(/released/);
|
||||
expect(beastMaster.text).to.not.match(/0 time\(s\)/);
|
||||
expect(mountMaster.text).to.not.match(/released/);
|
||||
expect(mountMaster.text).to.not.match(/0 time\(s\)/);
|
||||
expect(triadBingo.text).to.not.match(/released/);
|
||||
expect(triadBingo.text).to.not.match(/0 time\(s\)/);
|
||||
});
|
||||
|
||||
it('master and triad bingo achievements includes *Text2 strings if keys have been used', () => {
|
||||
let user = generateUser({
|
||||
achievements: {
|
||||
beastMasterCount: 1,
|
||||
mountMasterCount: 2,
|
||||
triadBingoCount: 3,
|
||||
},
|
||||
});
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
|
||||
let beastMaster = basicAchievs.beastMaster;
|
||||
let mountMaster = basicAchievs.mountMaster;
|
||||
let triadBingo = basicAchievs.triadBingo;
|
||||
|
||||
expect(beastMaster.text).to.match(/released/);
|
||||
expect(beastMaster.text).to.match(/1 time\(s\)/);
|
||||
expect(mountMaster.text).to.match(/released/);
|
||||
expect(mountMaster.text).to.match(/2 time\(s\)/);
|
||||
expect(triadBingo.text).to.match(/released/);
|
||||
expect(triadBingo.text).to.match(/3 time\(s\)/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ultimateGear achievements', () => {
|
||||
it('title and text contain localized class info', () => {
|
||||
let user = generateUser();
|
||||
let basicAchievs = shared.achievements.getAchievementsForProfile(user).basic.achievements;
|
||||
let gearTypes = ['healer', 'rogue', 'warrior', 'mage'];
|
||||
|
||||
gearTypes.forEach((gear) => {
|
||||
let gearAchiev = basicAchievs[`${gear}UltimateGear`];
|
||||
let classNameRegex = new RegExp(gear.charAt(0).toUpperCase() + gear.slice(1));
|
||||
|
||||
expect(gearAchiev.title).to.match(classNameRegex);
|
||||
expect(gearAchiev.text).to.match(classNameRegex);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -147,6 +147,15 @@ describe('shared.ops.purchase', () => {
|
||||
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
|
||||
});
|
||||
|
||||
it('purchases gems with a different language than the default', () => {
|
||||
let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}, language: 'de'});
|
||||
|
||||
expect(message).to.equal(i18n.t('plusOneGem', 'de'));
|
||||
expect(user.balance).to.equal(userGemAmount + 0.5);
|
||||
expect(user.purchased.plan.gemsBought).to.equal(2);
|
||||
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
|
||||
});
|
||||
|
||||
it('purchases eggs', () => {
|
||||
let type = 'eggs';
|
||||
let key = 'Wolf';
|
||||
|
||||
@@ -157,6 +157,16 @@ describe('shared.ops.scoreTask', () => {
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(secondTaskDelta);
|
||||
});
|
||||
|
||||
it('does not modify stats when task need approval', () => {
|
||||
todo.group.approval.required = true;
|
||||
options = { user: ref.afterUser, task: todo, direction: 'up', times: 5, cron: false };
|
||||
scoreTask(options);
|
||||
|
||||
expect(ref.afterUser.stats.hp).to.eql(50);
|
||||
expect(ref.afterUser.stats.exp).to.equal(ref.beforeUser.stats.exp);
|
||||
expect(ref.afterUser.stats.gp).to.equal(ref.beforeUser.stats.gp);
|
||||
});
|
||||
|
||||
context('habits', () => {
|
||||
it('up', () => {
|
||||
options = { user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false };
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../helpers/common.helper';
|
||||
|
||||
import timeTravelers from '../../website/common/script/content/time-travelers'
|
||||
|
||||
describe('time-travelers store', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('removes owned sets from the time travelers store', () => {
|
||||
user.items.gear.owned['head_mystery_201602'] = true;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist;
|
||||
});
|
||||
|
||||
it('removes unopened mystery item sets from the time travelers store', () => {
|
||||
user.purchased = {
|
||||
plan: {
|
||||
mysteryItems: ['head_mystery_201602'],
|
||||
},
|
||||
};
|
||||
expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist;
|
||||
expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export function generateTodo (user) {
|
||||
completed: false,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
@@ -101,7 +101,7 @@ export function generateDaily (user) {
|
||||
completed: false,
|
||||
};
|
||||
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
task.userId = user._id;
|
||||
task.save();
|
||||
|
||||
|
||||
@@ -13,5 +13,7 @@ chai.use(require('sinon-chai'));
|
||||
chai.use(require('chai-as-promised'));
|
||||
global.expect = chai.expect;
|
||||
global.sinon = require('sinon');
|
||||
let sinonStubPromise = require('sinon-stub-promise');
|
||||
sinonStubPromise(global.sinon);
|
||||
global.sandbox = sinon.sandbox.create();
|
||||
global.Promise = Bluebird;
|
||||
|
||||
@@ -7,8 +7,8 @@ const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
||||
// Use this to verify error messages returned by the server
|
||||
// That way, if the translated string changes, the test
|
||||
// will not break. NOTE: it checks against errors with string as well.
|
||||
export function translate (key, variables) {
|
||||
let translatedString = i18n.t(key, variables);
|
||||
export function translate (key, variables, language) {
|
||||
let translatedString = i18n.t(key, variables, language);
|
||||
|
||||
expect(translatedString).to.not.be.empty;
|
||||
expect(translatedString).to.not.eql(STRING_ERROR_MSG);
|
||||
|
||||
+13
-11
@@ -1,35 +1,37 @@
|
||||
/* global env:true, rm:true, mkdir:true, cp:true */
|
||||
|
||||
// https://github.com/shelljs/shelljs
|
||||
require('shelljs/global');
|
||||
env.NODE_ENV = 'production';
|
||||
|
||||
var path = require('path');
|
||||
var config = require('./config');
|
||||
var ora = require('ora');
|
||||
var webpack = require('webpack');
|
||||
var webpackConfig = require('./webpack.prod.conf');
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
const ora = require('ora');
|
||||
const webpack = require('webpack');
|
||||
const webpackConfig = require('./webpack.prod.conf');
|
||||
|
||||
console.log(
|
||||
console.log( // eslint-disable-line no-console
|
||||
' Tip:\n' +
|
||||
' Built files are meant to be served over an HTTP server.\n' +
|
||||
' Opening index.html over file:// won\'t work.\n'
|
||||
);
|
||||
|
||||
var spinner = ora('building for production...');
|
||||
const spinner = ora('building for production...');
|
||||
spinner.start();
|
||||
|
||||
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory);
|
||||
const assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory);
|
||||
rm('-rf', assetsPath);
|
||||
mkdir('-p', assetsPath);
|
||||
cp('-R', config.build.staticAssetsDirectory, assetsPath);
|
||||
|
||||
webpack(webpackConfig, function (err, stats) {
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop();
|
||||
if (err) throw err;
|
||||
process.stdout.write(stats.toString({
|
||||
process.stdout.write(`${stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
}) + '\n');
|
||||
})}\n`);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var merge = require('webpack-merge');
|
||||
var prodEnv = require('./prod.env');
|
||||
const merge = require('webpack-merge');
|
||||
const prodEnv = require('./prod.env');
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"',
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
var path = require('path');
|
||||
const path = require('path');
|
||||
const staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
|
||||
const prodEnv = require('./prod.env');
|
||||
const devEnv = require('./dev.env');
|
||||
|
||||
var staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
|
||||
module.exports = {
|
||||
build: {
|
||||
env: require('./prod.env'),
|
||||
env: prodEnv,
|
||||
index: path.resolve(__dirname, '../../dist-client/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../../dist-client'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/new-app',
|
||||
staticAssetsDirectory: staticAssetsDirectory,
|
||||
staticAssetsDirectory,
|
||||
productionSourceMap: true,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
@@ -19,11 +21,11 @@ module.exports = {
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
},
|
||||
dev: {
|
||||
env: require('./dev.env'),
|
||||
env: devEnv,
|
||||
port: 8080,
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
staticAssetsDirectory: staticAssetsDirectory,
|
||||
staticAssetsDirectory,
|
||||
proxyTable: {
|
||||
// proxy all requests starting with /api/v3 to localhost:3000
|
||||
'/api/v3': {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var merge = require('webpack-merge');
|
||||
var devEnv = require('./dev.env');
|
||||
const merge = require('webpack-merge');
|
||||
const devEnv = require('./dev.env');
|
||||
|
||||
module.exports = merge(devEnv, {
|
||||
NODE_ENV: '"testing"',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable */
|
||||
require('eventsource-polyfill')
|
||||
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true&overlay=false')
|
||||
/* global window:true */
|
||||
|
||||
hotClient.subscribe(function (event) {
|
||||
require('eventsource-polyfill');
|
||||
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true&overlay=false');
|
||||
|
||||
hotClient.subscribe(event => {
|
||||
if (event.action === 'reload') {
|
||||
window.location.reload()
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
+23
-21
@@ -1,22 +1,24 @@
|
||||
var path = require('path');
|
||||
var express = require('express');
|
||||
var webpack = require('webpack');
|
||||
var config = require('./config');
|
||||
var proxyMiddleware = require('http-proxy-middleware');
|
||||
var webpackConfig = process.env.NODE_ENV === 'testing'
|
||||
? require('./webpack.prod.conf')
|
||||
: require('./webpack.dev.conf');
|
||||
/* eslint-disable no-process-env, no-console */
|
||||
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const webpack = require('webpack');
|
||||
const config = require('./config');
|
||||
const proxyMiddleware = require('http-proxy-middleware');
|
||||
const webpackConfig = process.env.NODE_ENV === 'testing' ?
|
||||
require('./webpack.prod.conf') :
|
||||
require('./webpack.dev.conf');
|
||||
|
||||
// default port where dev server listens for incoming traffic
|
||||
var port = process.env.PORT || config.dev.port;
|
||||
const port = process.env.PORT || config.dev.port;
|
||||
// Define HTTP proxies to your custom API backend
|
||||
// https://github.com/chimurai/http-proxy-middleware
|
||||
var proxyTable = config.dev.proxyTable;
|
||||
const proxyTable = config.dev.proxyTable;
|
||||
|
||||
var app = express();
|
||||
var compiler = webpack(webpackConfig);
|
||||
const app = express();
|
||||
const compiler = webpack(webpackConfig);
|
||||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
const devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
stats: {
|
||||
colors: true,
|
||||
@@ -24,18 +26,18 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
},
|
||||
});
|
||||
|
||||
var hotMiddleware = require('webpack-hot-middleware')(compiler);
|
||||
const hotMiddleware = require('webpack-hot-middleware')(compiler);
|
||||
// force page reload when html-webpack-plugin template changes
|
||||
compiler.plugin('compilation', function (compilation) {
|
||||
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
||||
compiler.plugin('compilation', (compilation) => {
|
||||
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' });
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
// proxy api requests
|
||||
Object.keys(proxyTable).forEach(function (context) {
|
||||
var options = proxyTable[context];
|
||||
Object.keys(proxyTable).forEach((context) => {
|
||||
let options = proxyTable[context];
|
||||
if (typeof options === 'string') {
|
||||
options = { target: options };
|
||||
}
|
||||
@@ -53,13 +55,13 @@ app.use(devMiddleware);
|
||||
app.use(hotMiddleware);
|
||||
|
||||
// serve pure static assets
|
||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
|
||||
const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
|
||||
app.use(staticPath, express.static(config.dev.staticAssetsDirectory));
|
||||
|
||||
module.exports = app.listen(port, function (err) {
|
||||
module.exports = app.listen(port, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
console.log('Listening at http://localhost:' + port + '\n');
|
||||
console.log(`Listening at http://localhost:${port}\n`);
|
||||
});
|
||||
|
||||
+21
-19
@@ -1,28 +1,30 @@
|
||||
var path = require('path');
|
||||
var config = require('./config');
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
/* eslint-disable no-process-env, no-console */
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory;
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
exports.assetsPath = (_path) => {
|
||||
const assetsSubDirectory = process.env.NODE_ENV === 'production' ?
|
||||
config.build.assetsSubDirectory :
|
||||
config.dev.assetsSubDirectory;
|
||||
return path.posix.join(assetsSubDirectory, _path);
|
||||
};
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
exports.cssLoaders = (options) => {
|
||||
options = options || {};
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders (loaders) {
|
||||
var sourceLoader = loaders.map(function (loader) {
|
||||
var extraParamChar;
|
||||
let sourceLoader = loaders.map((loader) => {
|
||||
let extraParamChar;
|
||||
if (/\?/.test(loader)) {
|
||||
loader = loader.replace(/\?/, '-loader?');
|
||||
extraParamChar = '&';
|
||||
} else {
|
||||
loader = loader + '-loader';
|
||||
loader = `${loader}-loader`;
|
||||
extraParamChar = '?';
|
||||
}
|
||||
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '');
|
||||
return `${loader}${(options.sourceMap ? extraParamChar + 'sourceMap' : '')}`; // eslint-disable-line prefer-template
|
||||
}).join('!');
|
||||
|
||||
if (options.extract) {
|
||||
@@ -45,14 +47,14 @@ exports.cssLoaders = function (options) {
|
||||
};
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
var output = [];
|
||||
var loaders = exports.cssLoaders(options);
|
||||
for (var extension in loaders) {
|
||||
var loader = loaders[extension];
|
||||
exports.styleLoaders = (options) => {
|
||||
const output = [];
|
||||
const loaders = exports.cssLoaders(options);
|
||||
for (let extension in loaders) {
|
||||
const loader = loaders[extension];
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
loader: loader,
|
||||
test: new RegExp(`\\.${extension}$`),
|
||||
loader,
|
||||
});
|
||||
}
|
||||
return output;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
var path = require('path');
|
||||
var config = require('./config');
|
||||
var utils = require('./utils');
|
||||
var projectRoot = path.resolve(__dirname, '../');
|
||||
/* eslint-disable no-process-env, no-console */
|
||||
|
||||
var IS_PROD = process.env.NODE_ENV === 'production';
|
||||
var baseConfig = {
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
const projectRoot = path.resolve(__dirname, '../');
|
||||
const webpack = require('webpack');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const postcssEasyImport = require('postcss-easy-import');
|
||||
const IS_PROD = process.env.NODE_ENV === 'production';
|
||||
|
||||
const baseConfig = {
|
||||
entry: {
|
||||
app: './website/client/main.js',
|
||||
},
|
||||
@@ -17,6 +22,9 @@ var baseConfig = {
|
||||
extensions: ['', '.js', '.vue'],
|
||||
fallback: [path.join(__dirname, '../node_modules')],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery',
|
||||
website: path.resolve(__dirname, '../website'),
|
||||
common: path.resolve(__dirname, '../website/common'),
|
||||
client: path.resolve(__dirname, '../website/client'),
|
||||
assets: path.resolve(__dirname, '../website/client/assets'),
|
||||
components: path.resolve(__dirname, '../website/client/components'),
|
||||
@@ -25,6 +33,12 @@ var baseConfig = {
|
||||
resolveLoader: {
|
||||
fallback: [path.join(__dirname, '../node_modules')],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
preLoaders: !IS_PROD ? [
|
||||
{
|
||||
@@ -76,16 +90,21 @@ var baseConfig = {
|
||||
vue: {
|
||||
loaders: utils.cssLoaders(),
|
||||
postcss: [
|
||||
require('autoprefixer')({
|
||||
autoprefixer({
|
||||
browsers: ['last 2 versions'],
|
||||
}),
|
||||
postcssEasyImport({
|
||||
glob: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (!IS_PROD) {
|
||||
const eslintFriendlyFormatter = require('eslint-friendly-formatter'); // eslint-disable-line global-require
|
||||
|
||||
baseConfig.eslint = {
|
||||
formatter: require('eslint-friendly-formatter'),
|
||||
formatter: eslintFriendlyFormatter,
|
||||
emitWarning: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
var config = require('./config');
|
||||
var webpack = require('webpack');
|
||||
var merge = require('webpack-merge');
|
||||
var utils = require('./utils');
|
||||
var baseWebpackConfig = require('./webpack.base.conf');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const config = require('./config');
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const utils = require('./utils');
|
||||
const baseWebpackConfig = require('./webpack.base.conf');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
||||
Object.keys(baseWebpackConfig.entry).forEach((name) => {
|
||||
baseWebpackConfig.entry[name] = ['./webpack/dev-client'].concat(baseWebpackConfig.entry[name]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
var path = require('path');
|
||||
var config = require('./config');
|
||||
var utils = require('./utils');
|
||||
var webpack = require('webpack');
|
||||
var merge = require('webpack-merge');
|
||||
var baseWebpackConfig = require('./webpack.base.conf');
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var env = process.env.NODE_ENV === 'testing'
|
||||
? require('./config/test.env')
|
||||
: config.build.env;
|
||||
/* eslint-disable no-process-env, no-console */
|
||||
|
||||
var webpackConfig = merge(baseWebpackConfig, {
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const baseWebpackConfig = require('./webpack.base.conf');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const env = process.env.NODE_ENV === 'testing' ?
|
||||
require('./config/test.env') :
|
||||
config.build.env;
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }),
|
||||
},
|
||||
@@ -43,9 +45,9 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: process.env.NODE_ENV === 'testing'
|
||||
? 'index.html'
|
||||
: config.build.index,
|
||||
filename: process.env.NODE_ENV === 'testing' ?
|
||||
'index.html' :
|
||||
config.build.index,
|
||||
template: './website/client/index.html',
|
||||
inject: true,
|
||||
minify: {
|
||||
@@ -61,12 +63,12 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: function (module, count) {
|
||||
minChunks (scriptModule) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
scriptModule.resource &&
|
||||
/\.js$/.test(scriptModule.resource) &&
|
||||
scriptModule.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
);
|
||||
@@ -82,17 +84,13 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||
});
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
var CompressionWebpackPlugin = require('compression-webpack-plugin');
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin'); // eslint-disable-line global-require
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
asset: '[path].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(
|
||||
'\\.(' +
|
||||
config.build.productionGzipExtensions.join('|') +
|
||||
')$'
|
||||
),
|
||||
test: new RegExp(`\\.(${config.build.productionGzipExtensions.join('|')})$`),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8,
|
||||
})
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user