Merge branch 'develop' into party-chat-translations

# Conflicts:
#	website/server/models/group.js
This commit is contained in:
Mateus Etto
2018-05-06 00:46:47 +09:00
524 changed files with 25193 additions and 23472 deletions
+1
View File
@@ -17,3 +17,4 @@ CHANGELOG.md
newrelic_agent.log
*.swp
*.swx
website/raw_sprites/**
+1 -1
View File
@@ -17,7 +17,7 @@ RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.37.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.41.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force
-1
View File
@@ -10,4 +10,3 @@ We need more programmers! Your assistance will be greatly appreciated.
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
+1 -2
View File
@@ -112,6 +112,5 @@
"CLOUDKARAFKA_USERNAME": "",
"CLOUDKARAFKA_PASSWORD": "",
"CLOUDKARAFKA_TOPIC_PREFIX": ""
},
"STACK_IMPACT_KEY": "aaaabbbbccccddddeeeeffffgggg111100002222"
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ services:
- mongo
mongo:
image: mongo
image: mongo:3.4
ports:
- "27017:27017"
networks:
+1 -1
View File
@@ -4,7 +4,7 @@
/*
* This migration move ass chat off of groups and into their own model
* This migration moves chat off of groups and into their own model
*/
import { model as Group } from '../../website/server/models/group';
+1 -1
View File
@@ -5,7 +5,7 @@ const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is do
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['back_mystery_201803', 'head_mystery_201803'];
const MYSTERY_ITEMS = ['back_mystery_201804', 'headAccessory_mystery_201804'];
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let monk = require('monk');
+709 -1068
View File
File diff suppressed because it is too large Load Diff
+29 -30
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.38.0",
"version": "4.41.5",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -9,26 +9,26 @@
"amazon-payments": "^0.2.6",
"amplitude": "^3.5.0",
"apidoc": "^0.17.5",
"autoprefixer": "^8.2.0",
"aws-sdk": "^2.224.1",
"autoprefixer": "^8.4.1",
"aws-sdk": "^2.230.1",
"axios": "^0.18.0",
"axios-progress-bar": "^1.1.8",
"babel-core": "^6.0.0",
"babel-eslint": "^8.2.2",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"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",
"babel-runtime": "^6.11.6",
"bcrypt": "^1.0.2",
"bcrypt": "^2.0.0",
"body-parser": "^1.15.0",
"bootstrap": "^4.1.0",
"bootstrap-vue": "^2.0.0-rc.6",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.0-rc.9",
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
@@ -38,11 +38,11 @@
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
"express-basic-auth": "^1.1.4",
"express-basic-auth": "^1.1.5",
"express-validator": "^5.1.2",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
"got": "^8.3.0",
"got": "^8.3.1",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
@@ -52,23 +52,22 @@
"hellojs": "^1.15.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.9.0",
"intro.js": "^2.6.0",
"in-app-purchase": "^1.9.3",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"lodash": "^4.17.4",
"memwatch-next": "^0.3.0",
"lodash": "^4.17.10",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.22.0",
"moment": "^2.22.1",
"moment-recur": "^1.0.7",
"mongoose": "^5.0.14",
"mongoose": "^5.0.17",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^0.14.4",
"node-sass": "^4.8.3",
"node-sass": "^4.9.0",
"nodemailer": "^4.6.4",
"ora": "^2.0.0",
"ora": "^2.1.0",
"pageres": "^4.1.1",
"passport": "^0.4.0",
"passport-facebook": "^2.0.0",
@@ -84,9 +83,8 @@
"rimraf": "^2.4.3",
"sass-loader": "^7.0.0",
"shelljs": "^0.8.1",
"stackimpact": "^1.3.0",
"stripe": "^5.8.0",
"superagent": "^3.4.3",
"superagent": "^3.8.3",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
@@ -107,7 +105,7 @@
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.11.0",
"webpack-merge": "^4.0.0",
"winston": "^2.4.1",
"winston": "^2.4.2",
"winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4"
},
@@ -141,13 +139,13 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.13",
"@vue/test-utils": "^1.0.0-beta.15",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.3.2",
"chromedriver": "^2.37.0",
"chalk": "^2.4.1",
"chromedriver": "^2.38.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.0",
"cross-spawn": "^6.0.5",
@@ -161,7 +159,7 @@
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^2.0.0",
"karma": "^2.0.2",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
@@ -174,9 +172,9 @@
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.0",
"lcov-result-merger": "^2.0.0",
"mocha": "^5.0.5",
"mocha": "^5.1.1",
"monk": "^6.0.5",
"nightwatch": "^0.9.20",
"nightwatch": "^0.9.21",
"puppeteer": "^1.3.0",
"require-again": "^2.0.0",
"selenium-server": "^3.11.0",
@@ -185,9 +183,10 @@
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.22.0"
"webpack-hot-middleware": "^2.22.1"
},
"optionalDependencies": {
"memwatch-next": "^0.3.0",
"node-rdkafka": "^2.3.0"
}
}
@@ -60,9 +60,9 @@ describe('GET /challenges/:challengeId/export/csv', () => {
});
it('should return a valid CSV file with export data', async () => {
let res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
let splitRes = res.split('\n');
const res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
@@ -71,4 +71,16 @@ describe('GET /challenges/:challengeId/export/csv', () => {
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[5]).to.equal('');
});
it('should successfully return when it contains erroneous residue user data', async () => {
await members[0].update({challenges: []});
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal('');
});
});
@@ -32,7 +32,8 @@ describe('GET /groups/:groupId/chat', () => {
it('returns Guild chat', async () => {
const chat = await user.get(`/groups/${group._id}/chat`);
expect(chat).to.eql(group.chat);
expect(chat[0].id).to.eql(group.chat[0].id);
expect(chat[1].id).to.eql(group.chat[1].id);
});
});
@@ -2,7 +2,7 @@ import {
generateUser,
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import apiMessages from '../../../../../website/server/libs/apiMessages';
import apiError from '../../../../../website/server/libs/apiError';
describe('GET /coupons/', () => {
let user;
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: apiMessages('noSudoAccess'),
message: apiError('noSudoAccess'),
});
});
@@ -4,7 +4,7 @@ import {
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import couponCode from 'coupon-code';
import apiMessages from '../../../../../website/server/libs/apiMessages';
import apiError from '../../../../../website/server/libs/apiError';
describe('POST /coupons/generate/:event', () => {
let user;
@@ -26,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: apiMessages('noSudoAccess'),
message: apiError('noSudoAccess'),
});
});
@@ -7,7 +7,7 @@ import {
import {
TAVERN_ID,
} from '../../../../../website/server/models/group';
import apiMessages from '../../../../../website/server/libs/apiMessages';
import apiError from '../../../../../website/server/libs/apiError';
describe('GET /groups', () => {
let user;
@@ -167,7 +167,7 @@ describe('GET /groups', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: apiMessages('guildsOnlyPaginate'),
message: apiError('guildsOnlyPaginate'),
});
});
@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import apiError from '../../../../../../website/server/libs/apiError';
describe('payments : paypal #checkoutSuccess', () => {
let endpoint = '/paypal/checkout/success';
@@ -17,7 +17,7 @@ describe('payments : paypal #checkoutSuccess', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingPaymentId'),
message: apiError('missingPaymentId'),
});
});
@@ -26,7 +26,7 @@ describe('payments : paypal #checkoutSuccess', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingCustomerId'),
message: apiError('missingCustomerId'),
});
});
@@ -1,9 +1,9 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import shared from '../../../../../../website/common';
import apiError from '../../../../../../website/server/libs/apiError';
describe('payments : paypal #subscribe', () => {
let endpoint = '/paypal/subscribe';
@@ -17,7 +17,7 @@ describe('payments : paypal #subscribe', () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubKey'),
message: apiError('missingSubKey'),
});
});
@@ -1,7 +1,7 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
describe('payments : paypal #subscribeSuccess', () => {
@@ -16,7 +16,7 @@ describe('payments : paypal #subscribeSuccess', () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingPaypalBlock'),
message: apiError('missingPaypalBlock'),
});
});
@@ -6,6 +6,7 @@ import {
import { v4 as generateUUID } from 'uuid';
import { quests as questScrolls } from '../../../../../website/common/script/content';
import { model as Chat } from '../../../../../website/server/models/chat';
import apiError from '../../../../../website/server/libs/apiError';
describe('POST /groups/:groupId/quests/invite/:questKey', () => {
let questingGroup;
@@ -69,7 +70,7 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${FAKE_QUEST}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('questNotFound', {key: FAKE_QUEST}),
message: apiError('questNotFound', {key: FAKE_QUEST}),
});
});
@@ -81,6 +81,49 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(body.direction).to.eql('up');
expect(body.delta).to.be.greaterThan(0);
});
context('sending user activity webhooks', () => {
before(async () => {
await server.start();
});
after(async () => {
await server.close();
});
it('sends user activity webhook when the user levels up', async () => {
let uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'userActivity',
enabled: true,
options: {
leveledUp: true,
},
});
const initialLvl = user.stats.lvl;
await user.update({
'stats.exp': 3000,
});
let task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await user.post(`/tasks/${task.id}/score/up`);
await user.sync();
await sleep();
let body = server.getWebhookData(uuid);
expect(body.type).to.eql('leveledUp');
expect(body.initialLvl).to.eql(initialLvl);
expect(body.finalLvl).to.eql(user.stats.lvl);
});
});
});
context('todos', () => {
@@ -1,6 +1,8 @@
import {
generateUser,
translate as t,
server,
sleep,
} from '../../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
@@ -94,4 +96,49 @@ describe('POST /tasks/:taskId/checklist/:itemId/score', () => {
message: t('checklistItemNotFound'),
});
});
context('sending task activity webhooks', () => {
before(async () => {
await server.start();
});
after(async () => {
await server.close();
});
it('sends task activity webhooks', async () => {
let uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'taskActivity',
enabled: true,
options: {
checklistScored: true,
updated: false,
},
});
let task = await user.post('/tasks/user', {
text: 'test daily',
type: 'daily',
});
let updatedTask = await user.post(`/tasks/${task.id}/checklist`, {
text: 'checklist item text',
});
let checklistItem = updatedTask.checklist[0];
let scoredItemTask = await user.post(`/tasks/${task.id}/checklist/${checklistItem.id}/score`);
await sleep();
let body = server.getWebhookData(uuid);
expect(body.type).to.eql('checklistScored');
expect(body.task).to.eql(scoredItemTask);
expect(body.item).to.eql(scoredItemTask.checklist[0]);
});
});
});
@@ -9,6 +9,7 @@ import {
import { v4 as generateUUID } from 'uuid';
import { find } from 'lodash';
import apiError from '../../../../../website/server/libs/apiError';
describe('POST /user/class/cast/:spellId', () => {
let user;
@@ -24,7 +25,7 @@ describe('POST /user/class/cast/:spellId', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('spellNotFound', {spellId}),
message: apiError('spellNotFound', {spellId}),
});
});
@@ -34,7 +35,7 @@ describe('POST /user/class/cast/:spellId', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('spellNotFound', {spellId}),
message: apiError('spellNotFound', {spellId}),
});
});
@@ -3,8 +3,11 @@
import {
generateUser,
translate as t,
server,
sleep,
} from '../../../../helpers/api-integration/v3';
import content from '../../../../../website/common/script/content';
import { v4 as generateUUID } from 'uuid';
describe('POST /user/feed/:pet/:food', () => {
let user;
@@ -37,4 +40,41 @@ describe('POST /user/feed/:pet/:food', () => {
expect(user.items.food.Milk).to.equal(1);
expect(user.items.pets['Wolf-Base']).to.equal(7);
});
context('sending user activity webhooks', () => {
before(async () => {
await server.start();
});
after(async () => {
await server.close();
});
it('sends user activity webhook when a new mount is raised', async () => {
let uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'userActivity',
enabled: true,
options: {
mountRaised: true,
},
});
await user.update({
'items.pets.Wolf-Base': 49,
'items.food.Milk': 2,
});
let res = await user.post('/user/feed/Wolf-Base/Milk');
await sleep();
let body = server.getWebhookData(uuid);
expect(body.type).to.eql('mountRaised');
expect(body.pet).to.eql('Wolf-Base');
expect(body.message).to.eql(res.message);
});
});
});
@@ -1,7 +1,10 @@
import {
generateUser,
translate as t,
server,
sleep,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
describe('POST /user/hatch/:egg/:hatchingPotion', () => {
let user;
@@ -28,4 +31,41 @@ describe('POST /user/hatch/:egg/:hatchingPotion', () => {
data: JSON.parse(JSON.stringify(user.items)),
});
});
context('sending user activity webhooks', () => {
before(async () => {
await server.start();
});
after(async () => {
await server.close();
});
it('sends user activity webhook when a new pet is hatched', async () => {
let uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'userActivity',
enabled: true,
options: {
petHatched: true,
},
});
await user.update({
'items.eggs.Wolf': 1,
'items.hatchingPotions.Base': 1,
});
let res = await user.post('/user/hatch/Wolf/Base');
await sleep();
let body = server.getWebhookData(uuid);
expect(body.type).to.eql('petHatched');
expect(body.pet).to.eql('Wolf-Base');
expect(body.message).to.eql(res.message);
});
});
});
@@ -5,6 +5,7 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
let content = shared.content;
@@ -24,7 +25,7 @@ describe('POST /user/buy/:key', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('itemNotFound', {key: 'notExisting'}),
message: apiError('itemNotFound', {key: 'notExisting'}),
});
});
@@ -2,8 +2,8 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
describe('POST /user/buy-gear/:key', () => {
let user;
@@ -21,7 +21,7 @@ describe('POST /user/buy-gear/:key', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('itemNotFound', {key: 'notExisting'}),
message: apiError('itemNotFound', {key: 'notExisting'}),
});
});
@@ -3,6 +3,7 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
let content = shared.content;
@@ -20,7 +21,7 @@ describe('POST /user/buy-quest/:key', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('questNotFound', {key: 'notExisting'}),
message: apiError('questNotFound', {key: 'notExisting'}),
});
});
@@ -3,6 +3,7 @@ import {
translate as t,
} from '../../../../../helpers/api-integration/v3';
import shared from '../../../../../../website/common/script';
import apiError from '../../../../../../website/server/libs/apiError';
let content = shared.content;
@@ -20,7 +21,7 @@ describe('POST /user/buy-special-spell/:key', () => {
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('spellNotFound', {spellId: 'notExisting'}),
message: apiError('spellNotFound', {spellId: 'notExisting'}),
});
});
@@ -2,6 +2,7 @@ import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import apiError from '../../../../../../website/server/libs/apiError';
describe('POST /user/allocate', () => {
let user;
@@ -17,7 +18,7 @@ describe('POST /user/allocate', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidAttribute', {attr: 'invalid'}),
message: apiError('invalidAttribute', {attr: 'invalid'}),
});
});
@@ -3,6 +3,7 @@ import {
translate as t,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
import apiError from '../../../../../website/server/libs/apiError';
describe('POST /user/webhook', () => {
let user, body;
@@ -116,6 +117,7 @@ describe('POST /user/webhook', () => {
let webhook = await user.post('/user/webhook', body);
expect(webhook.options).to.eql({
checklistScored: false,
created: false,
updated: false,
deleted: false,
@@ -126,6 +128,7 @@ describe('POST /user/webhook', () => {
it('can set taskActivity options', async () => {
body.type = 'taskActivity';
body.options = {
checklistScored: true,
created: true,
updated: true,
deleted: true,
@@ -135,6 +138,7 @@ describe('POST /user/webhook', () => {
let webhook = await user.post('/user/webhook', body);
expect(webhook.options).to.eql({
checklistScored: true,
created: true,
updated: true,
deleted: true,
@@ -145,6 +149,7 @@ describe('POST /user/webhook', () => {
it('discards extra properties in taskActivity options', async () => {
body.type = 'taskActivity';
body.options = {
checklistScored: false,
created: true,
updated: true,
deleted: true,
@@ -156,6 +161,7 @@ describe('POST /user/webhook', () => {
expect(webhook.options.foo).to.not.exist;
expect(webhook.options).to.eql({
checklistScored: false,
created: true,
updated: true,
deleted: true,
@@ -200,7 +206,7 @@ describe('POST /user/webhook', () => {
await expect(user.post('/user/webhook', body)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('groupIdRequired'),
message: apiError('groupIdRequired'),
});
});
@@ -218,4 +224,16 @@ describe('POST /user/webhook', () => {
groupId: body.options.groupId,
});
});
it('discards extra properties in globalActivity options', async () => {
body.type = 'globalActivity';
body.options = {
foo: 'bar',
};
let webhook = await user.post('/user/webhook', body);
expect(webhook.options.foo).to.not.exist;
expect(webhook.options).to.eql({});
});
});
@@ -3,6 +3,7 @@ import {
translate as t,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID} from 'uuid';
import apiError from '../../../../../website/server/libs/apiError';
describe('PUT /user/webhook/:id', () => {
let user, webhookToUpdate;
@@ -95,6 +96,7 @@ describe('PUT /user/webhook/:id', () => {
let webhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, {type, options});
expect(webhook.options).to.eql({
checklistScored: false, // starting value
created: true, // starting value
updated: false,
deleted: true,
@@ -126,7 +128,7 @@ describe('PUT /user/webhook/:id', () => {
await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, {type, options})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('groupIdRequired'),
message: apiError('groupIdRequired'),
});
});
});
@@ -1,19 +1,19 @@
import apiMessages from '../../../../../website/server/libs/apiMessages';
import apiError from '../../../../../website/server/libs/apiError';
describe('API Messages', () => {
const message = 'Only public guilds support pagination.';
it('returns an API message', () => {
expect(apiMessages('guildsOnlyPaginate')).to.equal(message);
expect(apiError('guildsOnlyPaginate')).to.equal(message);
});
it('throws if the API message does not exist', () => {
expect(() => apiMessages('iDoNotExist')).to.throw;
expect(() => apiError('iDoNotExist')).to.throw;
});
it('clones the passed variables', () => {
let vars = {a: 1};
sandbox.stub(_, 'clone').returns({});
apiMessages('guildsOnlyPaginate', vars);
apiError('guildsOnlyPaginate', vars);
expect(_.clone).to.have.been.calledOnce;
expect(_.clone).to.have.been.calledWith(vars);
});
@@ -22,7 +22,7 @@ describe('API Messages', () => {
let vars = {a: 1};
let stub = sinon.stub().returns('string');
sandbox.stub(_, 'template').returns(stub);
apiMessages('guildsOnlyPaginate', vars);
apiError('guildsOnlyPaginate', vars);
expect(_.template).to.have.been.calledOnce;
expect(_.template).to.have.been.calledWith(message);
expect(stub).to.have.been.calledOnce;
+283 -22
View File
@@ -4,11 +4,16 @@ import {
taskScoredWebhook,
groupChatReceivedWebhook,
taskActivityWebhook,
questActivityWebhook,
userActivityWebhook,
} from '../../../../../website/server/libs/webhook';
import {
generateUser,
} from '../../../../helpers/api-unit.helper.js';
import { defer } from '../../../../helpers/api-unit.helper';
describe('webhooks', () => {
let webhooks;
let webhooks, user;
beforeEach(() => {
sandbox.stub(got, 'post').returns(defer().promise);
@@ -23,6 +28,26 @@ describe('webhooks', () => {
updated: true,
deleted: true,
scored: true,
checklistScored: true,
},
}, {
id: 'questActivity',
url: 'http://quest-activity.com',
enabled: true,
type: 'questActivity',
options: {
questStarted: true,
questFinised: true,
},
}, {
id: 'userActivity',
url: 'http://user-activity.com',
enabled: true,
type: 'userActivity',
options: {
petHatched: true,
mountRaised: true,
leveledUp: true,
},
}, {
id: 'groupChatReceived',
@@ -33,6 +58,9 @@ describe('webhooks', () => {
groupId: 'group-id',
},
}];
user = generateUser();
user.webhooks = webhooks;
});
afterEach(() => {
@@ -57,7 +85,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
expect(got.post).to.be.calledOnce;
@@ -67,6 +96,30 @@ describe('webhooks', () => {
});
});
it('adds default data (user and webhookType) to the body', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
sandbox.spy(sendWebhook, 'attachDefaultData');
let body = { foo: 'bar' };
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(sendWebhook.attachDefaultData).to.be.calledOnce;
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
});
expect(body).to.eql({
foo: 'bar',
user: {_id: user._id},
webhookType: 'custom',
});
});
it('can pass in a data transformation function', () => {
sandbox.spy(WebhookSender, 'defaultTransformData');
let sendWebhook = new WebhookSender({
@@ -80,7 +133,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(WebhookSender.defaultTransformData).to.not.be.called;
expect(got.post).to.be.calledOnce;
@@ -93,7 +147,7 @@ describe('webhooks', () => {
});
});
it('provieds a default filter function', () => {
it('provides a default filter function', () => {
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
let sendWebhook = new WebhookSender({
type: 'custom',
@@ -101,7 +155,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(WebhookSender.defaultWebhookFilter).to.be.calledOnce;
});
@@ -117,7 +172,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(WebhookSender.defaultWebhookFilter).to.not.be.called;
expect(got.post).to.not.be.called;
@@ -134,10 +190,11 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([
user.webhooks = [
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom', options: { foo: 'bar' }},
{ id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }},
], body);
];
sendWebhook.send(user, body);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
@@ -150,7 +207,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}];
sendWebhook.send(user, body);
expect(got.post).to.not.be.called;
});
@@ -162,7 +220,8 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([{id: 'custom-webhook', url: 'httxp://custom-url!!', enabled: true, type: 'custom'}], body);
user.webhooks = [{id: 'custom-webhook', url: 'httxp://custom-url!!!', enabled: true, type: 'custom'}];
sendWebhook.send(user, body);
expect(got.post).to.not.be.called;
});
@@ -174,10 +233,30 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([
user.webhooks = [
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
{ id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'},
], body);
];
sendWebhook.send(user, body);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
body,
json: true,
});
});
it('sends every type of activity to global webhooks', () => {
let sendWebhook = new WebhookSender({
type: 'custom',
});
let body = { foo: 'bar' };
user.webhooks = [
{ id: 'global-webhook', url: 'http://custom-url.com', enabled: true, type: 'globalActivity'},
];
sendWebhook.send(user, body);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
@@ -193,10 +272,11 @@ describe('webhooks', () => {
let body = { foo: 'bar' };
sendWebhook.send([
user.webhooks = [
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
{ id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'},
], body);
];
sendWebhook.send(user, body);
expect(got.post).to.be.calledTwice;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
@@ -216,7 +296,6 @@ describe('webhooks', () => {
beforeEach(() => {
data = {
user: {
_id: 'user-id',
_tmp: {foo: 'bar'},
stats: {
lvl: 5,
@@ -248,15 +327,54 @@ describe('webhooks', () => {
});
it('sends task and stats data', () => {
taskScoredWebhook.send(webhooks, data);
taskScoredWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
type: 'scored',
webhookType: 'taskActivity',
user: {
_id: 'user-id',
_id: user._id,
_tmp: {foo: 'bar'},
stats: {
lvl: 5,
int: 10,
str: 5,
exp: 423,
toNextLevel: 40,
maxHealth: 50,
maxMP: 103,
},
},
task: {
text: 'text',
},
direction: 'up',
delta: 176,
},
});
});
it('sends task and stats data to globalActivity webhookd', () => {
user.webhooks = [{
id: 'globalActivity',
url: 'http://global-activity.com',
enabled: true,
type: 'globalActivity',
}];
taskScoredWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://global-activity.com', {
json: true,
body: {
type: 'scored',
webhookType: 'taskActivity',
user: {
_id: user._id,
_tmp: {foo: 'bar'},
stats: {
lvl: 5,
@@ -280,7 +398,7 @@ describe('webhooks', () => {
it('does not send task scored data if scored option is not true', () => {
webhooks[0].options.scored = false;
taskScoredWebhook.send(webhooks, data);
taskScoredWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
@@ -301,13 +419,17 @@ describe('webhooks', () => {
it(`sends ${type} tasks`, () => {
data.type = type;
taskActivityWebhook.send(webhooks, data);
taskActivityWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
type,
webhookType: 'taskActivity',
user: {
_id: user._id,
},
task: data.task,
},
});
@@ -317,7 +439,142 @@ describe('webhooks', () => {
data.type = type;
webhooks[0].options[type] = false;
taskActivityWebhook.send(webhooks, data);
taskActivityWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
});
describe('checklistScored', () => {
beforeEach(() => {
data = {
task: {
text: 'text',
},
item: {
text: 'item-text',
},
};
});
it('sends \'checklistScored\' tasks', () => {
data.type = 'checklistScored';
taskActivityWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
webhookType: 'taskActivity',
user: {
_id: user._id,
},
type: data.type,
task: data.task,
item: data.item,
},
});
});
it('does not send task \'checklistScored\' data if \'checklistScored\' option is not true', () => {
data.type = 'checklistScored';
webhooks[0].options.checklistScored = false;
taskActivityWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
});
});
describe('userActivityWebhook', () => {
let data;
beforeEach(() => {
data = {
something: true,
};
});
['petHatched', 'mountRaised', 'leveledUp'].forEach((type) => {
it(`sends ${type} webhooks`, () => {
data.type = type;
userActivityWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[2].url, {
json: true,
body: {
type,
webhookType: 'userActivity',
user: {
_id: user._id,
},
something: true,
},
});
});
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
data.type = type;
webhooks[2].options[type] = false;
userActivityWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
});
});
describe('questActivityWebhook', () => {
let data;
beforeEach(() => {
data = {
group: {
id: 'group-id',
name: 'some group',
otherData: 'foo',
},
quest: {
key: 'some-key',
},
};
});
['questStarted', 'questFinised'].forEach((type) => {
it(`sends ${type} webhooks`, () => {
data.type = type;
questActivityWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[1].url, {
json: true,
body: {
type,
webhookType: 'questActivity',
user: {
_id: user._id,
},
group: {
id: 'group-id',
name: 'some group',
},
quest: {
key: 'some-key',
},
},
});
});
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
data.type = type;
webhooks[1].options[type] = false;
userActivityWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
@@ -338,12 +595,16 @@ describe('webhooks', () => {
},
};
groupChatReceivedWebhook.send(webhooks, data);
groupChatReceivedWebhook.send(user, data);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, {
json: true,
body: {
webhookType: 'groupChatReceived',
user: {
_id: user._id,
},
group: {
id: 'group-id',
name: 'some group',
@@ -369,7 +630,7 @@ describe('webhooks', () => {
},
};
groupChatReceivedWebhook.send(webhooks, data);
groupChatReceivedWebhook.send(user, data);
expect(got.post).to.not.be.called;
});
+1 -1
View File
@@ -15,7 +15,7 @@ describe('auth middleware', () => {
describe('auth with headers', () => {
it('allows to specify a list of user field that we do not want to load', (done) => {
const authWithHeaders = authWithHeadersFactory(false, {
const authWithHeaders = authWithHeadersFactory({
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
});
@@ -7,7 +7,7 @@ import {
import i18n from '../../../../../website/common/script/i18n';
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
import { NotAuthorized } from '../../../../../website/server/libs/errors';
import apiMessages from '../../../../../website/server/libs/apiMessages';
import apiError from '../../../../../website/server/libs/apiError';
describe('ensure access middlewares', () => {
let res, req, next;
@@ -46,7 +46,7 @@ describe('ensure access middlewares', () => {
ensureSudo(req, res, next);
const calledWith = next.getCall(0).args;
expect(calledWith[0].message).to.equal(apiMessages('noSudoAccess'));
expect(calledWith[0].message).to.equal(apiError('noSudoAccess'));
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
});
+86 -5
View File
@@ -11,7 +11,10 @@ import {
} from '../../../../../website/server/models/group';
import { model as User } from '../../../../../website/server/models/user';
import { quests as questScrolls } from '../../../../../website/common/script/content';
import { groupChatReceivedWebhook } from '../../../../../website/server/libs/webhook';
import {
groupChatReceivedWebhook,
questActivityWebhook,
} from '../../../../../website/server/libs/webhook';
import * as email from '../../../../../website/server/libs/email';
import { TAVERN_ID } from '../../../../../website/common/script/';
import shared from '../../../../../website/common';
@@ -21,6 +24,7 @@ describe('Group Model', () => {
beforeEach(async () => {
sandbox.stub(email, 'sendTxn');
sandbox.stub(questActivityWebhook, 'send');
party = new Group({
name: 'test party',
@@ -1389,6 +1393,47 @@ describe('Group Model', () => {
expect(typeOfEmail).to.eql('quest-started');
});
it('sends webhook to participating members that quest has started', async () => {
// should receive webhook
participatingMember.webhooks = [{
type: 'questActivity',
url: 'http://someurl.com',
options: {
questStarted: true,
},
}];
questLeader.webhooks = [{
type: 'questActivity',
url: 'http://someurl.com',
options: {
questStarted: true,
},
}];
await Promise.all([participatingMember.save(), questLeader.save()]);
await party.startQuest(nonParticipatingMember);
await sleep(0.5);
expect(questActivityWebhook.send).to.be.calledTwice; // for 2 participating members
let args = questActivityWebhook.send.args[0];
let webhooks = args[0].webhooks;
let webhookOwner = args[0]._id;
let options = args[1];
expect(webhooks).to.have.a.lengthOf(1);
if (webhookOwner === questLeader._id) {
expect(webhooks[0].id).to.eql(questLeader.webhooks[0].id);
} else {
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
}
expect(webhooks[0].type).to.eql('questActivity');
expect(options.group).to.eql(party);
expect(options.quest.key).to.eql('whale');
});
it('sends email only to members who have not opted out', async () => {
participatingMember.preferences.emailNotifications.questStarted = false;
questLeader.preferences.emailNotifications.questStarted = true;
@@ -1770,6 +1815,42 @@ describe('Group Model', () => {
});
});
it('sends webhook to participating members that quest has finished', async () => {
// should receive webhook
participatingMember.webhooks = [{
type: 'questActivity',
url: 'http://someurl.com',
options: {
questFinished: true,
},
}];
questLeader.webhooks = [{
type: 'questActivity',
url: 'http://someurl.com',
options: {
questStarted: true, // will not receive the webhook
},
}];
await Promise.all([participatingMember.save(), questLeader.save()]);
await party.finishQuest(quest);
await sleep(0.5);
expect(questActivityWebhook.send).to.be.calledOnce;
let args = questActivityWebhook.send.args[0];
let webhooks = args[0].webhooks;
let options = args[1];
expect(webhooks).to.have.a.lengthOf(1);
expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id);
expect(webhooks[0].type).to.eql('questActivity');
expect(options.group).to.eql(party);
expect(options.quest.key).to.eql(quest.key);
});
context('World quests in Tavern', () => {
let tavernQuest;
@@ -1885,7 +1966,7 @@ describe('Group Model', () => {
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
let args = groupChatReceivedWebhook.send.args[0];
let webhooks = args[0];
let webhooks = args[0].webhooks;
let options = args[1];
expect(webhooks).to.have.a.lengthOf(1);
@@ -1949,9 +2030,9 @@ describe('Group Model', () => {
expect(groupChatReceivedWebhook.send).to.be.calledThrice;
let args = groupChatReceivedWebhook.send.args;
expect(args.find(arg => arg[0][0].id === memberWithWebhook.webhooks[0].id)).to.be.exist;
expect(args.find(arg => arg[0][0].id === memberWithWebhook2.webhooks[0].id)).to.be.exist;
expect(args.find(arg => arg[0][0].id === memberWithWebhook3.webhooks[0].id)).to.be.exist;
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook.webhooks[0].id)).to.be.exist;
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook2.webhooks[0].id)).to.be.exist;
expect(args.find(arg => arg[0].webhooks[0].id === memberWithWebhook3.webhooks[0].id)).to.be.exist;
});
});
+180 -3
View File
@@ -1,6 +1,7 @@
import { model as Webhook } from '../../../../../website/server/models/webhook';
import { BadRequest } from '../../../../../website/server/libs/errors';
import { v4 as generateUUID } from 'uuid';
import apiError from '../../../../../website/server/libs/apiError';
describe('Webhook Model', () => {
context('Instance Methods', () => {
@@ -24,6 +25,7 @@ describe('Webhook Model', () => {
updated: true,
deleted: true,
scored: true,
checklistScored: true,
},
};
});
@@ -36,6 +38,7 @@ describe('Webhook Model', () => {
wh.formatOptions(res);
expect(wh.options).to.eql({
checklistScored: false,
created: false,
updated: false,
deleted: false,
@@ -51,6 +54,7 @@ describe('Webhook Model', () => {
wh.formatOptions(res);
expect(wh.options).to.eql({
checklistScored: true,
created: false,
updated: true,
deleted: true,
@@ -67,6 +71,7 @@ describe('Webhook Model', () => {
expect(wh.options.foo).to.not.exist;
expect(wh.options).to.eql({
checklistScored: true,
created: true,
updated: true,
deleted: true,
@@ -74,7 +79,155 @@ describe('Webhook Model', () => {
});
});
['created', 'updated', 'deleted', 'scored'].forEach((option) => {
['created', 'updated', 'deleted', 'scored', 'checklistScored'].forEach((option) => {
it(`validates that ${option} is a boolean`, (done) => {
config.options[option] = 'not a boolean';
try {
let wh = new Webhook(config);
wh.formatOptions(res);
} catch (err) {
expect(err).to.be.an.instanceOf(BadRequest);
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
done();
}
});
});
});
context('type is userActivity', () => {
let config;
beforeEach(() => {
config = {
type: 'userActivity',
url: 'https//exmaple.com/endpoint',
options: {
petHatched: true,
mountRaised: true,
leveledUp: true,
},
};
});
it('it provides default values for options', () => {
delete config.options;
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options).to.eql({
petHatched: false,
mountRaised: false,
leveledUp: false,
});
});
it('provides missing user options', () => {
delete config.options.petHatched;
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options).to.eql({
petHatched: false,
mountRaised: true,
leveledUp: true,
});
});
it('discards additional options', () => {
config.options.foo = 'another option';
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options.foo).to.not.exist;
expect(wh.options).to.eql({
petHatched: true,
mountRaised: true,
leveledUp: true,
});
});
['petHatched', 'petHatched', 'leveledUp'].forEach((option) => {
it(`validates that ${option} is a boolean`, (done) => {
config.options[option] = 'not a boolean';
try {
let wh = new Webhook(config);
wh.formatOptions(res);
} catch (err) {
expect(err).to.be.an.instanceOf(BadRequest);
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
done();
}
});
});
});
context('type is questActivity', () => {
let config;
beforeEach(() => {
config = {
type: 'questActivity',
url: 'https//exmaple.com/endpoint',
options: {
questStarted: true,
questFinished: true,
},
};
});
it('it provides default values for options', () => {
delete config.options;
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options).to.eql({
questStarted: false,
questFinished: false,
});
});
it('provides missing user options', () => {
delete config.options.questStarted;
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options).to.eql({
questStarted: false,
questFinished: true,
});
});
it('discards additional options', () => {
config.options.foo = 'another option';
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options.foo).to.not.exist;
expect(wh.options).to.eql({
questStarted: true,
questFinished: true,
});
});
['questStarted', 'questFinished'].forEach((option) => {
it(`validates that ${option} is a boolean`, (done) => {
config.options[option] = 'not a boolean';
@@ -135,12 +288,36 @@ describe('Webhook Model', () => {
wh.formatOptions(res);
} catch (err) {
expect(err).to.be.an.instanceOf(BadRequest);
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('groupIdRequired');
expect(err.message).to.eql(apiError('groupIdRequired'));
done();
}
});
});
context('type is globalActivity', () => {
let config;
beforeEach(() => {
config = {
type: 'globalActivity',
url: 'https//exmaple.com/endpoint',
options: { },
};
});
it('discards additional objects', () => {
config.options.foo = 'another thing';
let wh = new Webhook(config);
wh.formatOptions(res);
expect(wh.options.foo).to.not.exist;
expect(wh.options).to.eql({});
});
});
});
});
});
-3
View File
@@ -1,3 +0,0 @@
This folder contains the test files for the new client side that is being developed.
The old client side tests can be found in /test/client-old.
+12
View File
@@ -117,6 +117,18 @@ describe('common.fns.updateStats', () => {
expect(user.addNotification).to.be.calledWith('DROPS_ENABLED');
});
it('add user notification when the user levels up', () => {
const initialLvl = user.stats.lvl;
updateStats(user, {
exp: 3000,
});
expect(user.addNotification).to.be.calledTwice; // once is for drops enabled
expect(user.addNotification).to.be.calledWith('LEVELED_UP', {
initialLvl,
newLvl: user.stats.lvl,
});
});
it('add user notification when rebirth is enabled', () => {
user.stats.lvl = 51;
updateStats(user, { });
+72
View File
@@ -59,6 +59,78 @@ describe('shops', () => {
expect(specialCategory.items.find((item) => item.key === 'weapon_special_critical'));
expect(specialCategory.items.find((item) => item.key === 'weapon_armoire_basicCrossbow'));// eslint-disable-line camelcase
});
it('does not show gear when it is all owned', () => {
let userWithItems = generateUser({
stats: {
class: 'wizard',
},
items: {
gear: {
owned: {
weapon_wizard_0: true, // eslint-disable-line camelcase
weapon_wizard_1: true, // eslint-disable-line camelcase
weapon_wizard_2: true, // eslint-disable-line camelcase
weapon_wizard_3: true, // eslint-disable-line camelcase
weapon_wizard_4: true, // eslint-disable-line camelcase
weapon_wizard_5: true, // eslint-disable-line camelcase
weapon_wizard_6: true, // eslint-disable-line camelcase
armor_wizard_1: true, // eslint-disable-line camelcase
armor_wizard_2: true, // eslint-disable-line camelcase
armor_wizard_3: true, // eslint-disable-line camelcase
armor_wizard_4: true, // eslint-disable-line camelcase
armor_wizard_5: true, // eslint-disable-line camelcase
head_wizard_1: true, // eslint-disable-line camelcase
head_wizard_2: true, // eslint-disable-line camelcase
head_wizard_3: true, // eslint-disable-line camelcase
head_wizard_4: true, // eslint-disable-line camelcase
head_wizard_5: true, // eslint-disable-line camelcase
},
},
},
});
let shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.length).to.eql(0);
});
it('shows available gear not yet purchased and previously owned', () => {
let userWithItems = generateUser({
stats: {
class: 'wizard',
},
items: {
gear: {
owned: {
weapon_wizard_0: true, // eslint-disable-line camelcase
weapon_wizard_1: true, // eslint-disable-line camelcase
weapon_wizard_2: true, // eslint-disable-line camelcase
weapon_wizard_3: true, // eslint-disable-line camelcase
weapon_wizard_4: true, // eslint-disable-line camelcase
armor_wizard_1: true, // eslint-disable-line camelcase
armor_wizard_2: true, // eslint-disable-line camelcase
armor_wizard_3: false, // eslint-disable-line camelcase
armor_wizard_4: false, // eslint-disable-line camelcase
head_wizard_1: true, // eslint-disable-line camelcase
head_wizard_2: false, // eslint-disable-line camelcase
head_wizard_3: true, // eslint-disable-line camelcase
head_wizard_4: false, // eslint-disable-line camelcase
head_wizard_5: true, // eslint-disable-line camelcase
},
},
},
});
let shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);
expect(shopWizardItems.find(item => item.key === 'armor_wizard_3').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'armor_wizard_4').locked).to.eql(true);
expect(shopWizardItems.find(item => item.key === 'head_wizard_2').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'head_wizard_4').locked).to.eql(true);
});
});
describe('questShop', () => {
+2 -1
View File
@@ -8,6 +8,7 @@ import {
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import content from '../../../../website/common/script/content/index';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buy', () => {
let user;
@@ -40,7 +41,7 @@ describe('shared.ops.buy', () => {
buy(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
expect(err.message).to.equal(errorMessage('missingKeyParam'));
done();
}
});
+141
View File
@@ -0,0 +1,141 @@
/* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../../helpers/common.helper';
import {
BadRequest, NotAuthorized,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {BuyGemOperation} from '../../../../website/common/script/ops/buy/buyGem';
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
function buyGem (user, req, analytics) {
let buyOp = new BuyGemOperation(user, req, analytics);
return buyOp.purchase();
}
describe('shared.ops.buyGem', () => {
let user;
let analytics = {track () {}};
let goldPoints = 40;
let gemsBought = 40;
let userGemAmount = 10;
beforeEach(() => {
user = generateUser({
stats: { gp: goldPoints },
balance: userGemAmount,
purchased: {
plan: {
gemsBought: 0,
customerId: 'costumer-id',
},
},
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
context('Gems', () => {
it('purchases gems', () => {
let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}}, analytics);
expect(message).to.equal(i18n.t('plusGem', {count: 1}));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
expect(analytics.track).to.be.calledOnce;
});
it('purchases gems with a different language than the default', () => {
let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}, language: 'de'});
expect(message).to.equal(i18n.t('plusGem', {count: 1}, 'de'));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
});
it('makes bulk purchases of gems', () => {
let [, message] = buyGem(user, {
params: {type: 'gems', key: 'gem'},
quantity: 2,
});
expect(message).to.equal(i18n.t('plusGem', {count: 2}));
expect(user.balance).to.equal(userGemAmount + 0.50);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
context('Failure conditions', () => {
it('returns an error when key is not provided', (done) => {
try {
buyGem(user, {params: {type: 'gems'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});
it('prevents unsubscribed user from buying gems', (done) => {
delete user.purchased.plan.customerId;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems'));
done();
}
});
it('prevents user with not enough gold from buying gems', (done) => {
user.stats.gp = 15;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
done();
}
});
it('prevents user that have reached the conversion cap from buying gems', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap}));
done();
}
});
it('prevents user from buying an invalid quantity', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
});
});
});
+3 -2
View File
@@ -10,6 +10,7 @@ import {
BadRequest, NotAuthorized, NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
function buyGear (user, req, analytics) {
let buyOp = new BuyMarketGearOperation(user, req, analytics);
@@ -190,7 +191,7 @@ describe('shared.ops.buyMarketGear', () => {
buyGear(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
expect(err.message).to.equal(errorMessage('missingKeyParam'));
done();
}
});
@@ -202,7 +203,7 @@ describe('shared.ops.buyMarketGear', () => {
buyGear(user, {params});
} catch (err) {
expect(err).to.be.an.instanceof(NotFound);
expect(err.message).to.equal(i18n.t('itemNotFound', params));
expect(err.message).to.equal(errorMessage('itemNotFound', params));
done();
}
});
+2 -1
View File
@@ -10,6 +10,7 @@ import {
NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buyMysterySet', () => {
let user;
@@ -70,7 +71,7 @@ describe('shared.ops.buyMysterySet', () => {
buyMysterySet(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
expect(err.message).to.equal(errorMessage('missingKeyParam'));
done();
}
});
+3 -2
View File
@@ -8,6 +8,7 @@ import {
NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buyQuest', () => {
let user;
@@ -106,7 +107,7 @@ describe('shared.ops.buyQuest', () => {
});
} catch (err) {
expect(err).to.be.an.instanceof(NotFound);
expect(err.message).to.equal(i18n.t('questNotFound', {key: 'snarfblatter'}));
expect(err.message).to.equal(errorMessage('questNotFound', {key: 'snarfblatter'}));
expect(user.items.quests).to.eql({});
expect(user.stats.gp).to.equal(9999);
done();
@@ -151,7 +152,7 @@ describe('shared.ops.buyQuest', () => {
buyQuest(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
expect(err.message).to.equal(errorMessage('missingKeyParam'));
done();
}
});
+3 -2
View File
@@ -9,6 +9,7 @@ import {
generateUser,
} from '../../../helpers/common.helper';
import content from '../../../../website/common/script/content/index';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.buySpecialSpell', () => {
let user;
@@ -28,7 +29,7 @@ describe('shared.ops.buySpecialSpell', () => {
buySpecialSpell(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
expect(err.message).to.equal(errorMessage('missingKeyParam'));
done();
}
});
@@ -42,7 +43,7 @@ describe('shared.ops.buySpecialSpell', () => {
});
} catch (err) {
expect(err).to.be.an.instanceof(NotFound);
expect(err.message).to.equal(i18n.t('spellNotFound', {spellId: 'notExisting'}));
expect(err.message).to.equal(errorMessage('spellNotFound', {spellId: 'notExisting'}));
done();
}
});
+3 -2
View File
@@ -8,6 +8,7 @@ import content from '../../../../website/common/script/content/index';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('common.ops.hourglassPurchase', () => {
let user;
@@ -28,7 +29,7 @@ describe('common.ops.hourglassPurchase', () => {
hourglassPurchase(user, {params: {}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.eql(i18n.t('missingKeyParam'));
expect(err.message).to.eql(errorMessage('missingKeyParam'));
done();
}
});
@@ -38,7 +39,7 @@ describe('common.ops.hourglassPurchase', () => {
hourglassPurchase(user, {params: {key: 'Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.eql(i18n.t('missingTypeParam'));
expect(err.message).to.eql(errorMessage('missingTypeParam'));
done();
}
});
-90
View File
@@ -1,6 +1,5 @@
import purchase from '../../../../website/common/script/ops/buy/purchase';
import pinnedGearUtils from '../../../../website/common/script/ops/pinnedGearUtils';
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
import {
BadRequest,
NotAuthorized,
@@ -17,7 +16,6 @@ describe('shared.ops.purchase', () => {
const SEASONAL_FOOD = 'Meat';
let user;
let goldPoints = 40;
let gemsBought = 40;
let analytics = {track () {}};
before(() => {
@@ -45,63 +43,6 @@ describe('shared.ops.purchase', () => {
}
});
it('returns an error when key is not provided', (done) => {
try {
purchase(user, {params: {type: 'gems'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('keyRequired'));
done();
}
});
it('prevents unsubscribed user from buying gems', (done) => {
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems'));
done();
}
});
it('prevents user with not enough gold from buying gems', (done) => {
user.purchased.plan.customerId = 'customer-id';
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
done();
}
});
it('prevents user that have reached the conversion cap from buying gems', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap}));
done();
}
});
it('prevents user from buying an invalid quantity', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
purchase(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when unknown type is provided', (done) => {
try {
@@ -185,25 +126,6 @@ describe('shared.ops.purchase', () => {
user.pinnedItems.push({type: 'bundles', key: 'featheredFriends'});
});
it('purchases gems', () => {
let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}}, analytics);
expect(message).to.equal(i18n.t('plusOneGem'));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
expect(analytics.track).to.be.calledOnce;
});
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';
@@ -307,18 +229,6 @@ describe('shared.ops.purchase', () => {
}
});
it('makes bulk purchases of gems', () => {
let [, message] = purchase(user, {
params: {type: 'gems', key: 'gem'},
quantity: 2,
});
expect(message).to.equal(i18n.t('plusOneGem'));
expect(user.balance).to.equal(userGemAmount + 0.50);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
it('makes bulk purchases of eggs', () => {
let type = 'eggs';
let key = 'TigerCub';
+4 -3
View File
@@ -9,6 +9,7 @@ import i18n from '../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
describe('shared.ops.feed', () => {
let user;
@@ -23,7 +24,7 @@ describe('shared.ops.feed', () => {
feed(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingPetFoodFeed'));
expect(err.message).to.equal(errorMessage('missingPetFoodFeed'));
done();
}
});
@@ -33,7 +34,7 @@ describe('shared.ops.feed', () => {
feed(user, {params: {pet: 'invalid', food: 'food'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidPetName'));
expect(err.message).to.equal(errorMessage('invalidPetName'));
done();
}
});
@@ -43,7 +44,7 @@ describe('shared.ops.feed', () => {
feed(user, {params: {pet: 'Wolf-Red', food: 'invalid food name'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotFound);
expect(err.message).to.equal(i18n.t('messageFoodNotFound'));
expect(err.message).to.equal(errorMessage('invalidFoodName'));
done();
}
});
+2 -1
View File
@@ -8,6 +8,7 @@ import i18n from '../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
import errorMessage from '../../../website/common/script/libs/errorMessage';
describe('shared.ops.hatch', () => {
let user;
@@ -24,7 +25,7 @@ describe('shared.ops.hatch', () => {
hatch(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingEggHatchingPotionHatch'));
expect(err.message).to.equal(errorMessage('missingEggHatchingPotion'));
expect(user.items.pets).to.be.empty;
}
});
+1 -1
View File
@@ -36,7 +36,7 @@ describe('shared.ops.sell', () => {
sell(user, {params: { type } });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('keyRequired'));
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});
+2 -1
View File
@@ -7,6 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.allocate', () => {
let user;
@@ -22,7 +23,7 @@ describe('shared.ops.allocate', () => {
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'notValid'}));
expect(err.message).to.equal(errorMessage('invalidAttribute', {attr: 'notValid'}));
done();
}
});
+3 -2
View File
@@ -7,6 +7,7 @@ import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
describe('shared.ops.allocateBulk', () => {
let user;
@@ -27,7 +28,7 @@ describe('shared.ops.allocateBulk', () => {
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'invalid'}));
expect(err.message).to.equal(errorMessage('invalidAttribute', {attr: 'invalid'}));
done();
}
});
@@ -37,7 +38,7 @@ describe('shared.ops.allocateBulk', () => {
allocateBulk(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('statsObjectRequired'));
expect(err.message).to.equal(errorMessage('statsObjectRequired'));
done();
}
});
+2 -2
View File
@@ -966,7 +966,7 @@ describe('shouldDo', () => {
m: false,
};
let today = moment('2017-01-27');
let today = moment('2017-01-27:00:00.000-00:00');
let week = today.monthWeek();
let dayOfWeek = today.day();
dailyTask.startDate = today.toDate();
@@ -974,7 +974,7 @@ describe('shouldDo', () => {
dailyTask.repeat[DAY_MAPPING[dayOfWeek]] = true;
dailyTask.everyX = 1;
dailyTask.frequency = 'monthly';
day = moment('2017-02-24');
day = moment('2017-02-24:00:00.000-00:00');
expect(shouldDo(day, dailyTask, options)).to.equal(true);
});
+1 -1
View File
@@ -12,7 +12,7 @@ if (process.env.LOAD_SERVER === '0') { // when the server is in a different proc
nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI'));
nconf.set('NODE_ENV', 'test');
nconf.set('IS_TEST', true);
// We require src/server and npt src/index because
// We require src/server and not src/index because
// 1. nconf is already setup
// 2. we don't need clustering
require('../../website/server/server'); // eslint-disable-line global-require
+5 -4
View File
@@ -1,8 +1,7 @@
#Running
- Open a terminal and type `npm run client:dev`
- Open a second terminal and type `npm start`
# Running
For information about installing Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally) and for information about running the local client, refer to the ["Run Habitica" section](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally#Run_Habitica) in that page.
#Preparation Reading
# Preparation Reading
- Vue 2 (https://vuejs.org)
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
@@ -18,3 +17,5 @@ The API is almost the same except that we dont use mutations but only actions
The project is developed directly in the `develop` branch as long as well be able to avoid splitting it into a different branch.
So far most of the work has been on the template, so theres no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: its basically a Flux implementation: theres a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
@@ -1,4 +1,4 @@
.promo_armoire_background_201804 {
.promo_armoire_backgrounds_201805 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -587px;
width: 141px;
@@ -10,9 +10,9 @@
width: 325px;
height: 336px;
}
.promo_mystery_201803 {
.promo_mystery_201804 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -695px -337px;
background-position: -340px -383px;
width: 114px;
height: 90px;
}
@@ -24,7 +24,7 @@
}
.promo_seasonalshop_spring {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -532px -337px;
background-position: -340px -244px;
width: 162px;
height: 138px;
}
@@ -48,7 +48,7 @@
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -532px -476px;
background-position: -340px -474px;
width: 114px;
height: 87px;
}
@@ -58,6 +58,12 @@
width: 531px;
height: 243px;
}
.scene_todos {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -532px -337px;
width: 240px;
height: 195px;
}
.scene_video_games {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -244px;
@@ -354,7 +354,7 @@
}
.background_aurora {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -444px;
background-position: -568px -444px;
width: 141px;
height: 147px;
}
@@ -366,19 +366,19 @@
}
.background_back_of_giant_beast {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -444px;
background-position: -710px 0px;
width: 141px;
height: 147px;
}
.background_bamboo_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -444px;
background-position: -710px -148px;
width: 141px;
height: 147px;
}
.background_beach {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px 0px;
background-position: -710px -296px;
width: 141px;
height: 147px;
}
@@ -396,7 +396,7 @@
}
.background_beside_well {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -148px;
background-position: -710px -444px;
width: 141px;
height: 147px;
}
@@ -426,355 +426,367 @@
}
.background_buried_treasure {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -296px;
background-position: 0px 0px;
width: 141px;
height: 147px;
}
.background_cherry_trees {
.background_champions_colosseum {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px 0px;
width: 140px;
height: 147px;
}
.background_cherry_trees {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -148px;
width: 140px;
height: 147px;
}
.background_chessboard_land {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -444px;
width: 141px;
height: 147px;
}
.background_clouds {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -296px;
width: 140px;
height: 147px;
}
.background_coral_reef {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -444px;
width: 140px;
height: 147px;
}
.background_cornfields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -592px;
width: 140px;
height: 147px;
}
.background_cozy_library {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px 0px;
width: 141px;
height: 147px;
}
.background_crosscountry_ski_trail {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -592px;
width: 141px;
height: 147px;
}
.background_crystal_cave {
.background_clouds {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -1036px;
background-position: -1277px -444px;
width: 140px;
height: 147px;
}
.background_deep_mine {
.background_coral_reef {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1184px;
background-position: -1277px -592px;
width: 140px;
height: 147px;
}
.background_deep_sea {
.background_cornfields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1184px;
background-position: -1277px -740px;
width: 140px;
height: 147px;
}
.background_desert_dunes {
.background_cozy_library {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -592px;
width: 141px;
height: 147px;
}
.background_dilatory_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1184px;
width: 140px;
height: 147px;
}
.background_dilatory_ruins {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1184px;
width: 140px;
height: 147px;
}
.background_distant_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1184px;
width: 140px;
height: 147px;
}
.background_drifting_raft {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1184px;
width: 140px;
height: 147px;
}
.background_driving_a_coach {
.background_crosscountry_ski_trail {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -592px;
width: 141px;
height: 147px;
}
.background_driving_a_sleigh {
.background_crystal_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1184px;
width: 140px;
height: 147px;
}
.background_deep_mine {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1184px;
width: 140px;
height: 147px;
}
.background_deep_sea {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1184px;
width: 140px;
height: 147px;
}
.background_desert_dunes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -592px;
width: 141px;
height: 147px;
}
.background_dusty_canyons {
.background_dilatory_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1184px;
background-position: -564px -1184px;
width: 140px;
height: 147px;
}
.background_elegant_balcony {
.background_dilatory_ruins {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1184px;
width: 140px;
height: 147px;
}
.background_distant_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1184px;
width: 140px;
height: 147px;
}
.background_drifting_raft {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1184px;
width: 140px;
height: 147px;
}
.background_driving_a_coach {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -592px;
width: 141px;
height: 147px;
}
.background_fairy_ring {
.background_driving_a_sleigh {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -148px;
background-position: -852px 0px;
width: 141px;
height: 147px;
}
.background_dusty_canyons {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px 0px;
width: 140px;
height: 147px;
}
.background_farmhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1184px;
width: 140px;
height: 147px;
}
.background_fiber_arts_room {
.background_elegant_balcony {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -148px;
width: 141px;
height: 147px;
}
.background_floating_islands {
.background_fairy_ring {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -740px;
width: 140px;
height: 147px;
}
.background_fantastical_shoe_store {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -444px;
width: 140px;
height: 147px;
}
.background_farmhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -592px;
width: 140px;
height: 147px;
}
.background_floral_meadow {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -740px;
width: 140px;
height: 147px;
}
.background_flying_over_a_field_of_wildflowers {
.background_fiber_arts_room {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -296px;
width: 141px;
height: 147px;
}
.customize-option.background_flying_over_a_field_of_wildflowers {
.background_floating_islands {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -877px -311px;
width: 60px;
height: 60px;
background-position: -1418px -888px;
width: 140px;
height: 147px;
}
.background_flying_over_an_ancient_forest {
.background_floral_meadow {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1036px;
width: 140px;
height: 147px;
}
.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -444px;
width: 141px;
height: 147px;
}
.background_flying_over_icy_steppes {
.customize-option.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -877px -459px;
width: 60px;
height: 60px;
}
.background_flying_over_an_ancient_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -592px;
width: 141px;
height: 147px;
}
.background_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1332px;
width: 140px;
height: 147px;
}
.background_frigid_peak {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1332px;
width: 140px;
height: 147px;
}
.background_frozen_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1332px;
width: 140px;
height: 147px;
}
.background_garden_shed {
.background_flying_over_icy_steppes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -740px;
width: 141px;
height: 147px;
}
.background_gazebo {
.background_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1332px;
width: 140px;
height: 147px;
}
.background_frigid_peak {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1332px;
width: 140px;
height: 147px;
}
.background_frozen_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1332px;
width: 140px;
height: 147px;
}
.background_giant_birdhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1332px;
width: 140px;
height: 147px;
}
.background_giant_florals {
.background_garden_shed {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -740px;
width: 141px;
height: 147px;
}
.background_giant_seashell {
.background_gazebo {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1332px;
width: 140px;
height: 147px;
}
.background_giant_birdhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1332px;
width: 140px;
height: 147px;
}
.background_giant_florals {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -740px;
width: 141px;
height: 147px;
}
.background_giant_wave {
.background_giant_seashell {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -740px;
width: 141px;
height: 147px;
}
.background_gorgeous_greenhouse {
.background_giant_wave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -740px;
width: 141px;
height: 147px;
}
.background_grand_staircase {
.background_gorgeous_greenhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -740px;
width: 141px;
height: 147px;
}
.background_graveyard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px 0px;
width: 140px;
height: 147px;
}
.background_green {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -148px;
width: 140px;
height: 147px;
}
.background_guardian_statues {
.background_grand_staircase {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -740px;
width: 141px;
height: 147px;
}
.background_gumdrop_land {
.background_graveyard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -296px;
width: 140px;
height: 147px;
}
.background_green {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -444px;
width: 140px;
height: 147px;
}
.background_habit_city_streets {
.background_guardian_statues {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px 0px;
width: 141px;
height: 147px;
}
.background_harvest_feast {
.background_gumdrop_land {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -740px;
width: 140px;
height: 147px;
}
.background_harvest_fields {
.background_habit_city_streets {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -148px;
width: 141px;
height: 147px;
}
.background_harvest_moon {
.background_harvest_feast {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1036px;
width: 140px;
height: 147px;
}
.background_harvest_fields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -296px;
width: 141px;
height: 147px;
}
.background_haunted_house {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1184px;
width: 140px;
height: 147px;
}
.background_ice_cave {
.background_harvest_moon {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -444px;
width: 141px;
height: 147px;
}
.background_iceberg {
.background_haunted_house {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1480px;
width: 140px;
height: 147px;
}
.background_idyllic_cabin {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1480px;
width: 140px;
height: 147px;
}
.background_island_waterfalls {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1480px;
width: 140px;
height: 147px;
}
.background_kelp_forest {
.background_ice_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -592px;
width: 141px;
height: 147px;
}
.background_lighthouse_shore {
.background_iceberg {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1480px;
width: 140px;
height: 147px;
}
.background_idyllic_cabin {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1480px;
width: 140px;
height: 147px;
}
.background_island_waterfalls {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1480px;
width: 140px;
height: 147px;
}
.background_lilypad {
.background_kelp_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1480px;
width: 140px;
background-position: -142px 0px;
width: 141px;
height: 147px;
}
.background_magic_beanstalk {
.background_lighthouse_shore {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1480px;
width: 140px;
height: 147px;
}
.background_lilypad {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1480px;
width: 140px;
height: 147px;
}
.background_magic_beanstalk {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -296px;
width: 140px;
height: 147px;
}
.background_magical_candles {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -740px;
background-position: -426px -444px;
width: 141px;
height: 147px;
}
.background_magical_museum {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px 0px;
background-position: -284px -444px;
width: 141px;
height: 147px;
}
@@ -786,13 +798,13 @@
}
.background_market {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -888px;
background-position: -564px -888px;
width: 140px;
height: 147px;
}
.background_meandering_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -888px;
background-position: -423px -888px;
width: 140px;
height: 147px;
}
@@ -804,7 +816,7 @@
}
.background_midnight_clouds {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -888px;
background-position: -141px -888px;
width: 140px;
height: 147px;
}
@@ -816,31 +828,31 @@
}
.background_mist_shrouded_mountain {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -888px;
background-position: -705px -1480px;
width: 140px;
height: 147px;
}
.background_mistiflying_circus {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -888px;
background-position: -141px -1480px;
width: 140px;
height: 147px;
}
.background_mountain_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1480px;
background-position: -1559px -1332px;
width: 140px;
height: 147px;
}
.background_mountain_pyramid {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1480px;
background-position: -1559px -1184px;
width: 140px;
height: 147px;
}
.background_night_dunes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1332px;
background-position: -1559px -888px;
width: 140px;
height: 147px;
}
@@ -864,13 +876,13 @@
}
.background_orchard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -296px;
background-position: -1410px -1332px;
width: 140px;
height: 147px;
}
.background_pagodas {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1410px -1332px;
background-position: -1269px -1332px;
width: 140px;
height: 147px;
}
@@ -882,13 +894,13 @@
}
.background_pumpkin_patch {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1332px;
background-position: -705px -1332px;
width: 140px;
height: 147px;
}
.background_purple {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1332px;
background-position: -141px -1332px;
width: 140px;
height: 147px;
}
@@ -912,13 +924,13 @@
}
.background_rainy_city {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1036px;
background-position: -1418px -148px;
width: 140px;
height: 147px;
}
.background_red {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -888px;
background-position: -1269px -1184px;
width: 140px;
height: 147px;
}
@@ -942,61 +954,61 @@
}
.background_seafarer_ship {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1184px;
background-position: -1277px -888px;
width: 140px;
height: 147px;
}
.background_shimmering_ice_prism {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1184px;
background-position: -1277px -296px;
width: 140px;
height: 147px;
}
.background_shimmery_bubbles {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1184px;
background-position: -1128px -1036px;
width: 140px;
height: 147px;
}
.background_slimy_swamp {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -888px;
background-position: -423px -1036px;
width: 140px;
height: 147px;
}
.background_snowman_army {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -740px;
background-position: 0px -1036px;
width: 140px;
height: 147px;
}
.background_snowy_pines {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -148px;
background-position: -1136px -888px;
width: 140px;
height: 147px;
}
.background_snowy_sunrise {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1036px;
background-position: -1136px -740px;
width: 140px;
height: 147px;
}
.background_south_pole {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1036px;
background-position: -1136px -444px;
width: 140px;
height: 147px;
}
.background_sparkling_snowflake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1036px;
background-position: -987px -888px;
width: 140px;
height: 147px;
}
.background_spider_web {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -888px;
background-position: -846px -888px;
width: 140px;
height: 147px;
}
@@ -1008,25 +1020,25 @@
}
.background_spring_rain {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -444px;
background-position: -282px -888px;
width: 140px;
height: 147px;
}
.background_stable {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -888px;
background-position: 0px -888px;
width: 140px;
height: 147px;
}
.background_stained_glass {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -888px;
background-position: -1559px -592px;
width: 140px;
height: 147px;
}
.background_starry_skies {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -888px;
background-position: -1559px -148px;
width: 140px;
height: 147px;
}
@@ -1038,31 +1050,31 @@
}
.background_stoikalm_volcanoes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -888px;
background-position: -1128px -1332px;
width: 140px;
height: 147px;
}
.background_stone_circle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -592px;
background-position: 0px -1332px;
width: 140px;
height: 147px;
}
.background_stormy_rooftops {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1332px;
background-position: -1418px -1184px;
width: 140px;
height: 147px;
}
.background_stormy_ship {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1332px;
background-position: -1418px -740px;
width: 140px;
height: 147px;
}
.background_strange_sewers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1332px;
background-position: -1128px -1184px;
width: 140px;
height: 147px;
}
@@ -1074,37 +1086,25 @@
}
.background_sunken_ship {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -444px;
background-position: -1277px -1036px;
width: 140px;
height: 147px;
}
.background_sunset_meadow {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -296px;
background-position: -705px -888px;
width: 140px;
height: 147px;
}
.background_sunset_oasis {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px 0px;
background-position: -1559px 0px;
width: 140px;
height: 147px;
}
.background_sunset_savannah {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -740px;
background-position: -423px -1184px;
width: 140px;
height: 147px;
}
.background_swarming_darkness {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1036px;
width: 140px;
height: 147px;
}
.background_tar_pits {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px 0px;
width: 141px;
height: 147px;
}
File diff suppressed because it is too large Load Diff
@@ -1,402 +1,420 @@
.npc_timetravelers {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -214px -1535px;
width: 195px;
height: 138px;
}
.npc_tyler {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1844px -1134px;
width: 90px;
height: 90px;
}
.npc_vicky {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1935px -1134px;
width: 59px;
height: 82px;
}
.seasonalshop_closed {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1844px -863px;
width: 162px;
height: 138px;
}
.seasonalshop_open {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1844px -724px;
width: 162px;
height: 138px;
}
.background_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px 0px;
width: 306px;
height: 202px;
}
.banner_flair_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1844px -1225px;
width: 69px;
height: 18px;
}
.phobia_dysheartener {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1510px;
background-position: -1627px -1281px;
width: 201px;
height: 195px;
}
.quest_armadillo {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -440px;
background-position: -747px 0px;
width: 219px;
height: 219px;
}
.quest_atom1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1376px -1332px;
background-position: -1136px -1315px;
width: 250px;
height: 150px;
}
.quest_atom2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -633px -1510px;
background-position: -1387px -1315px;
width: 207px;
height: 138px;
}
.quest_atom3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -202px -1510px;
background-position: -747px -440px;
width: 216px;
height: 180px;
}
.quest_axolotl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -232px;
background-position: -440px -655px;
width: 219px;
height: 219px;
}
.quest_badger {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -232px;
background-position: -660px -655px;
width: 219px;
height: 219px;
}
.quest_basilist {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -191px -1706px;
background-position: -191px -1710px;
width: 189px;
height: 141px;
}
.quest_beetle {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -1079px;
background-position: -1627px -1079px;
width: 204px;
height: 201px;
}
.quest_bunny {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1322px -1112px;
background-position: -967px -660px;
width: 210px;
height: 186px;
}
.quest_butterfly {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -452px;
background-position: 0px -875px;
width: 219px;
height: 219px;
}
.quest_cheetah {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -452px;
background-position: -220px -875px;
width: 219px;
height: 219px;
}
.quest_cow {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px 0px;
background-position: -1844px 0px;
width: 174px;
height: 213px;
}
.quest_dilatory {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -220px;
background-position: -880px -875px;
width: 219px;
height: 219px;
}
.quest_dilatoryDistress1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -868px;
background-position: -1627px -868px;
width: 210px;
height: 210px;
}
.quest_dilatoryDistress2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px -573px;
background-position: -1844px -573px;
width: 150px;
height: 150px;
}
.quest_dilatoryDistress3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -672px;
background-position: -1187px -440px;
width: 219px;
height: 219px;
}
.quest_dilatory_derby {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px 0px;
background-position: -660px -875px;
width: 219px;
height: 219px;
}
.quest_dustbunnies {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -672px;
background-position: -1187px -660px;
width: 219px;
height: 219px;
}
.quest_egg {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px -214px;
background-position: -1844px -214px;
width: 165px;
height: 207px;
}
.quest_evilsanta {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px -875px;
background-position: -1844px -1002px;
width: 118px;
height: 131px;
}
.quest_evilsanta2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px 0px;
background-position: -440px -1095px;
width: 219px;
height: 219px;
}
.quest_falcon {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -220px;
background-position: -660px -1095px;
width: 219px;
height: 219px;
}
.quest_ferret {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -440px;
background-position: -880px -1095px;
width: 219px;
height: 219px;
}
.quest_frog {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -1112px;
background-position: -440px -1315px;
width: 221px;
height: 213px;
}
.quest_ghost_stag {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px 0px;
background-position: -1407px 0px;
width: 219px;
height: 219px;
}
.quest_goldenknight1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -892px;
background-position: -1407px -220px;
width: 219px;
height: 219px;
}
.quest_goldenknight2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -874px -1332px;
background-position: -885px -1315px;
width: 250px;
height: 150px;
}
.quest_goldenknight3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px 0px;
background-position: 0px -203px;
width: 219px;
height: 231px;
}
.quest_gryphon {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -657px -1332px;
background-position: -527px -220px;
width: 216px;
height: 177px;
}
.quest_guineapig {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -892px;
background-position: 0px -1315px;
width: 219px;
height: 219px;
}
.quest_harpy {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px 0px;
background-position: -1100px -1095px;
width: 219px;
height: 219px;
}
.quest_hedgehog {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1102px -1112px;
background-position: -1187px -880px;
width: 219px;
height: 186px;
}
.quest_hippo {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -892px;
background-position: -967px -220px;
width: 219px;
height: 219px;
}
.quest_horse {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -660px;
background-position: -220px -655px;
width: 219px;
height: 219px;
}
.quest_kraken {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -223px -1332px;
background-position: -307px -220px;
width: 216px;
height: 177px;
}
.quest_lostMasterclasser1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1112px;
background-position: 0px -435px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -220px -1112px;
background-position: -440px -435px;
width: 219px;
height: 219px;
}
.quest_lostMasterclasser3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -880px;
background-position: -220px -1095px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px -422px;
background-position: -1844px -422px;
width: 150px;
height: 150px;
}
.quest_mayhemMistiflying2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1320px -220px;
background-position: -1407px -880px;
width: 219px;
height: 219px;
}
.quest_mayhemMistiflying3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -892px;
background-position: -1407px -660px;
width: 219px;
height: 219px;
}
.quest_monkey {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1100px -660px;
background-position: -1407px -440px;
width: 219px;
height: 219px;
}
.quest_moon1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -434px;
background-position: -1627px 0px;
width: 216px;
height: 216px;
}
.quest_moon2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -672px;
background-position: 0px -1095px;
width: 219px;
height: 219px;
}
.quest_moon3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -440px;
background-position: -1187px -220px;
width: 219px;
height: 219px;
}
.quest_moonstone1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -220px;
background-position: -220px -1315px;
width: 219px;
height: 219px;
}
.quest_moonstone2 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px 0px;
background-position: -440px -875px;
width: 219px;
height: 219px;
}
.quest_moonstone3 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -1112px;
background-position: -967px -440px;
width: 219px;
height: 219px;
}
.quest_nudibranch {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -651px;
background-position: -1627px -217px;
width: 216px;
height: 216px;
}
.quest_octopus {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1332px;
background-position: -662px -1315px;
width: 222px;
height: 177px;
}
.quest_owl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -892px;
background-position: -967px 0px;
width: 219px;
height: 219px;
}
.quest_peacock {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px -217px;
background-position: -1627px -434px;
width: 216px;
height: 216px;
}
.quest_penguin {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -1706px;
background-position: 0px -1710px;
width: 190px;
height: 183px;
}
.quest_pterodactyl {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -880px -672px;
background-position: 0px -655px;
width: 219px;
height: 219px;
}
.quest_rat {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -672px;
background-position: -747px -220px;
width: 219px;
height: 219px;
}
.quest_rock {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1540px 0px;
background-position: -1627px -651px;
width: 216px;
height: 216px;
}
.quest_rooster {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -419px -1510px;
background-position: 0px -1535px;
width: 213px;
height: 174px;
}
.quest_sabretooth {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px -452px;
background-position: -220px -435px;
width: 219px;
height: 219px;
}
.quest_sheep {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -452px;
background-position: -527px 0px;
width: 219px;
height: 219px;
}
.quest_slime {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -660px 0px;
background-position: -307px 0px;
width: 219px;
height: 219px;
}
.quest_sloth {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -232px;
background-position: -1187px 0px;
width: 219px;
height: 219px;
}
.quest_snail {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -882px -1112px;
background-position: -1407px -1100px;
width: 219px;
height: 213px;
}
.quest_snake {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -440px -1332px;
width: 216px;
height: 177px;
}
.quest_spider {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1125px -1332px;
width: 250px;
height: 150px;
}
.quest_squirrel {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: 0px -892px;
width: 219px;
height: 219px;
}
.quest_stoikalmCalamity1 {
background-image: url('~assets/images/sprites/spritesmith-main-10.png');
background-position: -1757px -724px;
width: 150px;
height: 150px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 KiB

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Some files were not shown because too many files have changed in this diff Show More