Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd55b6f5c | ||
|
|
31e7882c51 | ||
|
|
c831a05b9b | ||
|
|
4929a2dd79 | ||
|
|
1a99380a53 | ||
|
|
3a28aba986 | ||
|
|
42f86ff62b | ||
|
|
4ba97755a5 | ||
|
|
779ac3d4ab | ||
|
|
32c3a3886f | ||
|
|
99465d5995 | ||
|
|
df0dbaba63 | ||
|
|
d1e9a6a74a | ||
|
|
60a5599db4 | ||
|
|
68b19c931b | ||
|
|
07d2699898 | ||
|
|
f5ea24b4e6 | ||
|
|
9507f0758d | ||
|
|
58694f46e0 | ||
|
|
a7351b0082 | ||
|
|
2881566aed | ||
|
|
6d350d5974 | ||
|
|
2e8da50b3e | ||
|
|
1985d04bcf | ||
|
|
8d040873a1 | ||
|
|
5995dd235d | ||
|
|
f57c647e21 | ||
|
|
8d82566654 | ||
|
|
7ff4a72d77 | ||
|
|
41d04b0f18 | ||
|
|
c7c1ff816c | ||
|
|
b018f9cf90 | ||
|
|
a380090013 | ||
|
|
0b076311df | ||
|
|
1896984777 | ||
|
|
c3ba70f5d6 | ||
|
|
ac800a94f9 | ||
|
|
9b2b9ef54d | ||
|
|
3785a87221 | ||
|
|
8c5d4ca190 | ||
|
|
ab6e77dd9a | ||
|
|
75913842bc | ||
|
|
e61884ed08 | ||
|
|
026014b8d6 | ||
|
|
014a7197f0 | ||
|
|
1ad3292f18 | ||
|
|
add2743772 | ||
|
|
cf0ce90968 | ||
|
|
e3b10cdc2a | ||
|
|
3ab4c4114b | ||
|
|
576285c004 | ||
|
|
d3967d6567 | ||
|
|
ea25a4bf04 | ||
|
|
4f26ac66ac | ||
|
|
f01ca1f9be | ||
|
|
b51f622c52 | ||
|
|
0dba37008f | ||
|
|
bca52cb6fa | ||
|
|
ade6d9689f | ||
|
|
90f7390f84 | ||
|
|
5d6e8c8729 | ||
|
|
4659b1cc5c | ||
|
|
06c58bfae2 | ||
|
|
568e8840ed | ||
|
|
ffe46c0f07 | ||
|
|
aad6130b21 | ||
|
|
88d48f1e5d | ||
|
|
413626a971 | ||
|
|
26f39c9db6 | ||
|
|
c5e0bcfb0e | ||
|
|
c6c0e3660b | ||
|
|
d00131fc4a | ||
|
|
f7220e7e8c | ||
|
|
bf6dde6e63 | ||
|
|
c3024e5e58 | ||
|
|
ca8b7f6b67 | ||
|
|
521077ed4f | ||
|
|
f72f71fd32 | ||
|
|
18b04e713e | ||
|
|
6754c43317 | ||
|
|
0b13ba822e | ||
|
|
9071fa0073 | ||
|
|
7a5f01d516 | ||
|
|
0f3f54548d | ||
|
|
c84dc40c7d | ||
|
|
16b244d5c6 | ||
|
|
86a07a4949 | ||
|
|
31bbac1751 | ||
|
|
e6dd0d5e82 | ||
|
|
88fece1422 | ||
|
|
ecc18fc093 | ||
|
|
0a59b8e85b | ||
|
|
d677f5cfc7 | ||
|
|
88f872ed50 | ||
|
|
605391e4e7 | ||
|
|
ca90d88289 | ||
|
|
a4951c6478 | ||
|
|
95285cd85a | ||
|
|
1a03f8d7ae | ||
|
|
5d0cbd2456 | ||
|
|
cdc8473f60 | ||
|
|
11a4c1c95d | ||
|
|
625b159880 | ||
|
|
bf8b2db6b3 | ||
|
|
83a1b9c34e | ||
|
|
c350665076 | ||
|
|
89ee8b1648 | ||
|
|
75680ab6aa | ||
|
|
116ec0f9d9 | ||
|
|
a89633d17b | ||
|
|
2e3dd27414 | ||
|
|
3af756a90d | ||
|
|
a9195f0d96 | ||
|
|
ab777f7006 | ||
|
|
d918bc9f56 | ||
|
|
4a89ca3e11 | ||
|
|
cdbbf93b74 | ||
|
|
d822843bbf | ||
|
|
ea3ed26f42 | ||
|
|
0da1144635 | ||
|
|
5f3539da19 | ||
|
|
e4d006e5cd | ||
|
|
801b53857f | ||
|
|
f8571ec5d5 | ||
|
|
78ba596504 | ||
|
|
b1a4c1b4ff | ||
|
|
fb80dd7c57 |
1
.babelrc
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
["transform-async-to-module-method", {
|
||||
"module": "bluebird",
|
||||
"method": "coroutine"
|
||||
|
||||
@@ -2,6 +2,9 @@ language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
sudo: required
|
||||
dist: precise
|
||||
services:
|
||||
- mongodb
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
@@ -17,7 +20,7 @@ install:
|
||||
before_script:
|
||||
- npm run test:build
|
||||
- cp config.json.example config.json
|
||||
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
|
||||
- sleep 15
|
||||
script:
|
||||
- npm run $TEST
|
||||
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
||||
|
||||
@@ -7,6 +7,7 @@ RUN npm install -g gulp grunt-cli bower mocha
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN cp config.json.example config.json
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN npm install -g gulp grunt-cli bower mocha
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v3.102.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v3.111.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
RUN gulp build:prod --force
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
web:
|
||||
volumes:
|
||||
- '.:/habitrpg'
|
||||
- '.:/usr/src/habitrpg'
|
||||
|
||||
109
migrations/20170731_naming_day.js
Normal file
@@ -0,0 +1,109 @@
|
||||
var migrationName = '20170731_naming_day.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Royal Purple Gryphon Helm to Royal Purple Gryphon pet owners,
|
||||
* award Royal Purple Gryphon pet to Royal Purple Gryphon mount owners,
|
||||
* award Royal Purple Gryphon mount to everyone else
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2017-01-01')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.mounts',
|
||||
'items.pets',
|
||||
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {};
|
||||
var inc = {
|
||||
'achievements.habiticaDays': 1,
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1
|
||||
};
|
||||
|
||||
if (user.items.pets['Gryphon-RoyalPurple']) {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_namingDay2017': false};
|
||||
} else if (user.items.mounts['Gryphon-RoyalPurple']) {
|
||||
set = {'migration':migrationName, 'items.pets.Gryphon-RoyalPurple': 5};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.mounts.Gryphon-RoyalPurple': true};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['body_mystery_201706','back_mystery_201706']
|
||||
$each:['shield_mystery_201708','weapon_mystery_201708']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
2636
npm-shrinkwrap.json
generated
11
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "3.102.3",
|
||||
"version": "3.111.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -31,7 +31,7 @@
|
||||
"bluebird": "^3.3.5",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "^4.0.0-alpha.6",
|
||||
"bootstrap-vue": "^0.16.1",
|
||||
"bootstrap-vue": "^0.18.0",
|
||||
"bower": "~1.3.12",
|
||||
"browserify": "~12.0.1",
|
||||
"compression": "^1.6.1",
|
||||
@@ -40,7 +40,7 @@
|
||||
"coupon-code": "^0.4.5",
|
||||
"css-loader": "^0.28.0",
|
||||
"csv-stringify": "^1.0.2",
|
||||
"cwait": "^1.0.0",
|
||||
"cwait": "~1.0.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"estraverse": "^4.1.1",
|
||||
"express": "~4.14.0",
|
||||
@@ -73,6 +73,7 @@
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
"intro.js": "^2.6.0",
|
||||
"jade": "~1.11.0",
|
||||
"jquery": "^3.1.1",
|
||||
"js2xmlparser": "~1.0.0",
|
||||
@@ -81,7 +82,7 @@
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.13.0",
|
||||
"moment-recur": "habitrpg/moment-recur#v1.0.6",
|
||||
"mongoose": "^4.8.6",
|
||||
"mongoose": "~4.8.6",
|
||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "~0.8.2",
|
||||
@@ -125,9 +126,11 @@
|
||||
"vue": "^2.1.0",
|
||||
"vue-loader": "^11.0.0",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-notification": "^1.3.2",
|
||||
"vue-router": "^2.0.0-rc.5",
|
||||
"vue-style-loader": "^3.0.0",
|
||||
"vue-template-compiler": "^2.1.10",
|
||||
"vuejs-datepicker": "^0.9.4",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.1.0",
|
||||
|
||||
@@ -32,18 +32,20 @@ describe('POST /challenges/:challengeId/leave', () => {
|
||||
let group;
|
||||
let challenge;
|
||||
let notInChallengeUser;
|
||||
let notInGroupLeavingUser;
|
||||
let leavingUser;
|
||||
let taskText;
|
||||
|
||||
beforeEach(async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
members: 3,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
leavingUser = populatedGroup.members[0];
|
||||
notInChallengeUser = populatedGroup.members[1];
|
||||
notInGroupLeavingUser = populatedGroup.members[2];
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
|
||||
@@ -55,17 +57,16 @@ describe('POST /challenges/:challengeId/leave', () => {
|
||||
|
||||
await leavingUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await notInGroupLeavingUser.post(`/challenges/${challenge._id}/join`);
|
||||
await notInGroupLeavingUser.post(`/groups/${group._id}/leave`, {
|
||||
keepChallenges: 'remain-in-challenges',
|
||||
});
|
||||
|
||||
await challenge.sync();
|
||||
});
|
||||
|
||||
it('returns an error when user doesn\'t have permissions to view the challenge', async () => {
|
||||
let unauthorizedUser = await generateUser();
|
||||
|
||||
await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
it('lets user leave when not a member of the challenge group', async () => {
|
||||
await expect(notInGroupLeavingUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('returns an error when user isn\'t a member of the challenge', async () => {
|
||||
|
||||
@@ -114,6 +114,26 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
|
||||
});
|
||||
|
||||
it('doesn\'t gives winner gems if group policy prevents it', async () => {
|
||||
let oldBalance = winningUser.balance;
|
||||
let oldLeaderBalance = (await groupLeader.sync()).balance;
|
||||
|
||||
await winningUser.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
|
||||
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance);
|
||||
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldLeaderBalance + challenge.prize / 4);
|
||||
});
|
||||
|
||||
it('doesn\'t refund gems to group leader', async () => {
|
||||
let oldBalance = (await groupLeader.sync()).balance;
|
||||
|
||||
|
||||
@@ -10,11 +10,22 @@ import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
|
||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat', () => {
|
||||
let user, groupWithChat, member, additionalMember;
|
||||
let testMessage = 'Test Message';
|
||||
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
|
||||
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
|
||||
let bannedWordErrorMessage = t('bannedWordUsed').split('.');
|
||||
bannedWordErrorMessage[0] += ` (${removePunctuationFromString(testBannedWordMessage.toLowerCase())})`;
|
||||
bannedWordErrorMessage = bannedWordErrorMessage.join('.');
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
@@ -25,7 +36,6 @@ describe('POST /chat', () => {
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
groupWithChat = group;
|
||||
member = members[0];
|
||||
@@ -79,11 +89,11 @@ describe('POST /chat', () => {
|
||||
context('banned word', () => {
|
||||
it('returns an error when chat message contains a banned word in tavern', async () => {
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed'),
|
||||
});
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is part of a phrase', async () => {
|
||||
@@ -92,7 +102,7 @@ describe('POST /chat', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed'),
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,10 +112,26 @@ describe('POST /chat', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedWordUsed'),
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('checks error message has the banned words used', async () => {
|
||||
let randIndex = Math.floor(Math.random() * (bannedWords.length + 1));
|
||||
let testBannedWords = bannedWords.slice(randIndex, randIndex + 2).map((w) => w.replace(/\\/g, ''));
|
||||
let chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage}))
|
||||
.to.eventually.be.rejected
|
||||
.and.have.property('message')
|
||||
.that.includes(testBannedWords.join(', '));
|
||||
});
|
||||
|
||||
it('check all banned words are matched', async () => {
|
||||
let message = bannedWords.join(',').replace(/\\/g, '');
|
||||
let matches = getMatchesByWordArray(message, bannedWords);
|
||||
expect(matches.length).to.equal(bannedWords.length);
|
||||
});
|
||||
|
||||
it('does not error when bad word is suffix of a word', async () => {
|
||||
let wordAsSuffix = `prefix${testBannedWordMessage}`;
|
||||
let message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix});
|
||||
@@ -166,6 +192,114 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('banned slur', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('errors and revokes privileges when chat message contains a banned slur', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/#/options/groups/guilds/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
|
||||
// Restore chat privileges to continue testing
|
||||
user.flags.chatRevoked = false;
|
||||
await user.update({'flags.chatRevoked': false});
|
||||
});
|
||||
|
||||
it('does not allow slurs in private groups', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testSlurMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledThrice;
|
||||
expect(email.sendTxn.args[2][1]).to.be.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${members[0].profile.name} (${members[0].id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
|
||||
title: 'Slur in Party - (private party)',
|
||||
title_link: undefined,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(members[0].post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
|
||||
// Restore chat privileges to continue testing
|
||||
members[0].flags.chatRevoked = false;
|
||||
await members[0].update({'flags.chatRevoked': false});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when sending a message to a private guild with a user with revoked chat', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
|
||||
@@ -16,6 +16,12 @@ describe('GET /groups', () => {
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
const GUILD_PER_PAGE = 30;
|
||||
let categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
@@ -31,16 +37,18 @@ describe('GET /groups', () => {
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await generateGroup(leader, {
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
categories,
|
||||
});
|
||||
|
||||
let privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
});
|
||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
|
||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
||||
@@ -100,6 +108,50 @@ describe('GET /groups', () => {
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
let guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(publicGuildNotMember._id);
|
||||
});
|
||||
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
let guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
});
|
||||
|
||||
it('filters public guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
let guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
let guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
it('req.query.paginate must be a boolean string', async () => {
|
||||
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||
@@ -149,8 +201,8 @@ describe('GET /groups', () => {
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
.to.eventually.have.a.lengthOf(1 + 2); // 1 created now, 2 by other tests
|
||||
expect(page2[2].name).to.equal('guild with less members');
|
||||
.to.eventually.have.a.lengthOf(1 + 3); // 1 created now, 3 by other tests
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -66,11 +66,25 @@ describe('GET /groups/:groupId/members', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true only works with parties', async () => {
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
let group = await generateGroup(user, {type: 'guild', name: generateUUID()});
|
||||
let res = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
let [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background', 'tasks',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
|
||||
expect(memberRes.inbox.optOut).to.exist;
|
||||
expect(memberRes.inbox.messages).to.not.exist;
|
||||
});
|
||||
|
||||
it('populates all public fields if req.query.includeAllPublicFields === true and it is a party', async () => {
|
||||
|
||||
@@ -220,7 +220,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
it('clears invitation from user when joining party', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
|
||||
it('increments memberCount when joining party', async () => {
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
let userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.party).to.be.empty;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,13 +177,13 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
|
||||
it('can remove other invites', async () => {
|
||||
expect(partyInvitedUser.invitations.party).to.not.be.empty;
|
||||
expect(partyInvitedUser.invitations.parties[0]).to.not.be.empty;
|
||||
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
|
||||
|
||||
expect(invitedUserWithoutInvite.invitations.party).to.be.empty;
|
||||
expect(invitedUserWithoutInvite.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
|
||||
it('removes new messages from a member who is removed', async () => {
|
||||
|
||||
@@ -440,7 +440,38 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user to 2 different parties', async () => {
|
||||
// Create another inviter
|
||||
let inviter2 = await generateUser();
|
||||
|
||||
// Create user to invite
|
||||
let userToInvite = await generateUser();
|
||||
|
||||
// Create second group
|
||||
let party2 = await inviter2.post('/groups', {
|
||||
name: 'Test Party 2',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Invite to second party
|
||||
await inviter2.post(`/groups/${party2._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Get updated user
|
||||
let invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
@@ -451,7 +482,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
|
||||
@@ -45,6 +45,20 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('updates a group categories', async () => {
|
||||
let categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
|
||||
let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
categories,
|
||||
});
|
||||
|
||||
expect(updatedGroup.categories[0].slug).to.eql(categories[0].slug);
|
||||
expect(updatedGroup.categories[0].name).to.eql(categories[0].name);
|
||||
});
|
||||
|
||||
it('allows an admin to update a guild', async () => {
|
||||
let updatedGroup = await adminUser.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('GET /tasks/user', () => {
|
||||
expect(dailys2[0].isDue).to.be.true;
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 420;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
@@ -179,7 +179,7 @@ describe('GET /tasks/user', () => {
|
||||
});
|
||||
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 240;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
@@ -205,7 +205,7 @@ describe('GET /tasks/user', () => {
|
||||
expect(dailys2[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 540;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
|
||||
@@ -18,266 +18,328 @@ import {
|
||||
} from '../../../../../website/server/libs/password';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const DELETE_CONFIRMATION = 'DELETE';
|
||||
|
||||
describe('DELETE /user', () => {
|
||||
let user;
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 10});
|
||||
});
|
||||
|
||||
it('returns an error if password is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if password is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if excessive feedback is supplied', async () => {
|
||||
let feedbackText = 'spam feedback ';
|
||||
let feedback = feedbackText;
|
||||
while (feedback.length < 10000) {
|
||||
feedback = feedback + feedbackText;
|
||||
}
|
||||
|
||||
await expect(user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email admin@habitica.com.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user has active subscription', async () => {
|
||||
let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'});
|
||||
|
||||
await expect(userWithSubscription.del('/user', {
|
||||
password,
|
||||
})).to.be.rejected.and.to.eventually.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotDeleteActiveAccount'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user\'s tasks', async () => {
|
||||
// gets the user's tasks ids
|
||||
let ids = [];
|
||||
each(user.tasksOrder, (idsForOrder) => {
|
||||
ids.push(...idsForOrder);
|
||||
});
|
||||
|
||||
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
|
||||
it('reduces memberCount in challenges user is linked to', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
});
|
||||
|
||||
let group = populatedGroup.group;
|
||||
let authorizedUser = populatedGroup.members[1];
|
||||
|
||||
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(2);
|
||||
|
||||
await authorizedUser.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
it('sends feedback to the admin email', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
let feedback = 'Reasons for Deletion';
|
||||
await user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('does not send email if no feedback is supplied', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.not.be.called;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('deletes the user with a legacy sha1 password', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// delete the user
|
||||
await user.del('/user', {
|
||||
password: textPassword,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
context('last member of a party', () => {
|
||||
let party;
|
||||
|
||||
context('user with local auth', async () => {
|
||||
beforeEach(async () => {
|
||||
party = await generateGroup(user, {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
user = await generateUser({balance: 10});
|
||||
});
|
||||
|
||||
it('returns an error if password is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes party when user is the only member', async () => {
|
||||
it('returns an error if password is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
it('returns an error if excessive feedback is supplied', async () => {
|
||||
let feedbackText = 'spam feedback ';
|
||||
let feedback = feedbackText;
|
||||
while (feedback.length < 10000) {
|
||||
feedback = feedback + feedbackText;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
await expect(user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email admin@habitica.com.',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
it('returns an error if user has active subscription', async () => {
|
||||
let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'});
|
||||
|
||||
await expect(userWithSubscription.del('/user', {
|
||||
password,
|
||||
})).to.be.rejected.and.to.eventually.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotDeleteActiveAccount'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user\'s tasks', async () => {
|
||||
// gets the user's tasks ids
|
||||
let ids = [];
|
||||
each(user.tasksOrder, (idsForOrder) => {
|
||||
ids.push(...idsForOrder);
|
||||
});
|
||||
|
||||
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild, oldLeader, newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
it('reduces memberCount in challenges user is linked to', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0];
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
let group = populatedGroup.group;
|
||||
let authorizedUser = populatedGroup.members[1];
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(2);
|
||||
|
||||
await authorizedUser.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
await challenge.sync();
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1, group2, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({balance: 10});
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
let {group, members} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0];
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
expect(challenge.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
it('sends feedback to the admin email', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
let feedback = 'Reasons for Deletion';
|
||||
await user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('does not send email if no feedback is supplied', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
let userInGroup = find(updatedGroup2Members, (member) => {
|
||||
return member._id === userToDelete._id;
|
||||
expect(email.sendTxn).to.not.be.called;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('deletes the user with a legacy sha1 password', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// delete the user
|
||||
await user.del('/user', {
|
||||
password: textPassword,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
context('last member of a party', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
party = await generateGroup(user, {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes party when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild, oldLeader, newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0];
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1, group2, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({balance: 10});
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
let {group, members} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0];
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
let userInGroup = find(updatedGroup2Members, (member) => {
|
||||
return member._id === userToDelete._id;
|
||||
});
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Facebook auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
facebook: {
|
||||
id: 'facebook-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if confirmation phrase is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'just-do-it',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('incorrectDeletePhrase'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if confirmation phrase is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a Facebook user', async () => {
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
google: {
|
||||
id: 'google-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a Google user', async () => {
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -31,4 +32,70 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
|
||||
expect(user.items[type][key]).to.equal(1);
|
||||
});
|
||||
|
||||
it('can convert gold to gems if subscribed', async () => {
|
||||
let oldBalance = user.balance;
|
||||
await user.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await user.post('/user/purchase/gems/gem');
|
||||
await user.sync();
|
||||
expect(user.balance).to.equal(oldBalance + 0.25);
|
||||
});
|
||||
|
||||
it('leader can convert gold to gems even if the group plan prevents it', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
await groupLeader.sync();
|
||||
let oldBalance = groupLeader.balance;
|
||||
|
||||
await groupLeader.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await groupLeader.post('/user/purchase/gems/gem');
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.balance).to.equal(oldBalance + 0.25);
|
||||
});
|
||||
|
||||
it('cannot convert gold to gems if the group plan prevents it', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
let oldBalance = members[0].balance;
|
||||
|
||||
await members[0].update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await expect(members[0].post('/user/purchase/gems/gem'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
await members[0].sync();
|
||||
expect(members[0].balance).to.equal(oldBalance);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,17 +2,34 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-both', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
const loadPets = () => {
|
||||
let pets = {};
|
||||
for (let p in content.pets) {
|
||||
pets[p] = content.pets[p];
|
||||
pets[p] = 5;
|
||||
}
|
||||
return pets;
|
||||
};
|
||||
const loadMounts = () => {
|
||||
let mounts = {};
|
||||
for (let m in content.pets) {
|
||||
mounts[m] = content.pets[m];
|
||||
mounts[m] = true;
|
||||
}
|
||||
return mounts;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentMount': animal,
|
||||
'items.currentPet': animal,
|
||||
'items.pets': {animal: 5},
|
||||
'items.mounts': {animal: true},
|
||||
'items.pets': loadPets(),
|
||||
'items.mounts': loadMounts(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,15 +2,25 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-mounts', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
|
||||
const loadMounts = () => {
|
||||
let mounts = {};
|
||||
for (let m in content.pets) {
|
||||
mounts[m] = content.pets[m];
|
||||
mounts[m] = true;
|
||||
}
|
||||
return mounts;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentMount': animal,
|
||||
'items.mounts': {animal: true},
|
||||
'items.mounts': loadMounts(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,15 +2,25 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-pets', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
|
||||
const loadPets = () => {
|
||||
let pets = {};
|
||||
for (let p in content.pets) {
|
||||
pets[p] = content.pets[p];
|
||||
pets[p] = 5;
|
||||
}
|
||||
return pets;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentPet': animal,
|
||||
'items.pets': {animal: 5},
|
||||
'items.pets': loadPets(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -509,7 +509,73 @@ describe('POST /user/auth/local/register', () => {
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({
|
||||
expect(user.invitations.parties[0].id).to.eql(group._id);
|
||||
expect(user.invitations.parties[0].name).to.eql(group.name);
|
||||
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
||||
});
|
||||
|
||||
it('awards achievement to inviter', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.achievements.invitedFriend).to.be.true;
|
||||
});
|
||||
|
||||
it('user not added to a party on expired invite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now() - 6.912e8, // 8 days old
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
|
||||
@@ -102,6 +102,7 @@ describe('Amazon Payments', () => {
|
||||
});
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await amzLib.checkout({user, orderReferenceId, headers});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
@@ -111,6 +112,8 @@ describe('Amazon Payments', () => {
|
||||
headers,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should error if gem amount is too low', async () => {
|
||||
@@ -132,20 +135,29 @@ describe('Amazon Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if user cannot get gems gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
await receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
amount = 16 / 4;
|
||||
await amzLib.checkout({gift, user, orderReferenceId, headers});
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
|
||||
@@ -57,7 +57,20 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -74,6 +87,8 @@ describe('Apple Payments', () => {
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -566,6 +566,17 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset habit counters even if user is resting in the Inn', () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter each Monday', () => {
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
@@ -585,6 +596,114 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter with custom daily start', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 12am UTC
|
||||
let monday = new Date('May 22, 2017 00:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// cron runs at 2am
|
||||
user.preferences.dayStart = 2;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 3am UTC
|
||||
monday = new Date('May 22, 2017 03:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// should reset after user CDS
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a weekly habit counter when server tz is Monday but user\'s tz is Tuesday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 11pm UTC
|
||||
let monday = new Date('May 22, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: Tuesday 1am UTC + 2
|
||||
user.preferences.timezoneOffset = -120;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// User missed one cron, which will subtract User clock back to Monday 1am UTC + 2
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter when server tz is Sunday but user\'s tz is Monday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Sunday 11pm UTC
|
||||
let sunday = new Date('May 21, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(sunday);
|
||||
|
||||
// User clock: Monday 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a weekly habit counter when server tz is Monday but user\'s tz is Sunday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 2am UTC
|
||||
let monday = new Date('May 22, 2017 02:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: Sunday 11pm UTC - 3
|
||||
user.preferences.timezoneOffset = 180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
});
|
||||
|
||||
it('should reset a monthly habit counter the first day of each month', () => {
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
@@ -603,6 +722,59 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a monthly habit counter when server tz is last day of month but user tz is first day of the month', () => {
|
||||
clock.restore();
|
||||
daysMissed = 0;
|
||||
|
||||
// Server clock: 4/30/17 11pm UTC
|
||||
let monday = new Date('April 30, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: 5/1/17 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a monthly habit counter when server tz is first day of month but user tz is 2nd day of the month', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: 5/1/17 11pm UTC
|
||||
let monday = new Date('May 1, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: 5/2/17 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// User missed one day, which will subtract User clock back to 5/1/17 2am UTC + 3
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,21 @@ describe('Google Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -82,6 +96,8 @@ describe('Google Payments', () => {
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Paypal Payments', () => {
|
||||
});
|
||||
|
||||
it('creates a link for gem purchases', async () => {
|
||||
let link = await paypalPayments.checkout();
|
||||
let link = await paypalPayments.checkout({user: new User()});
|
||||
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
|
||||
@@ -87,13 +87,25 @@ describe('Paypal Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if the user cannot get gems', async () => {
|
||||
let user = new User();
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a link for gifting gems', async () => {
|
||||
let receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -75,8 +75,29 @@ describe('Stripe Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
@@ -102,16 +123,18 @@ describe('Stripe Payments', () => {
|
||||
paymentMethod: 'Stripe',
|
||||
gift,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
await receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -125,7 +148,6 @@ describe('Stripe Payments', () => {
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '400',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Bluebird from 'bluebird';
|
||||
import moment from 'moment';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import common from '../../../../../website/common';
|
||||
|
||||
describe('User Model', () => {
|
||||
@@ -179,6 +180,75 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('canGetGems', () => {
|
||||
let user;
|
||||
let group;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
let leader = new User();
|
||||
group = new Group({
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
leader: leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed', async () => {
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 123;
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group', async () => {
|
||||
user.guilds.push(group._id);
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with a subscription', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if leader is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leader = user._id;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with no subscription but canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false if user is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('hasNotCancelled', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -6,6 +6,7 @@ describe('Notification Controller', function() {
|
||||
beforeEach(function() {
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
user.needsCron = false;
|
||||
|
||||
var userSync = sinon.stub().returns({
|
||||
then: function then (f) { f(); }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import DrawerComponent from 'client/components/inventory/drawer.vue';
|
||||
import DrawerComponent from 'client/components/ui/drawer.vue';
|
||||
|
||||
describe('DrawerComponent', () => {
|
||||
it('sets the correct default data', () => {
|
||||
@@ -122,7 +122,7 @@ describe('achievements', () => {
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell'];
|
||||
let cardTypes = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell', 'goodluck'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = basicAchievs[`${card}Cards`];
|
||||
|
||||
|
||||
@@ -76,5 +76,35 @@ describe('shared.ops.buyHealthPotion', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow potion purchases when hp is zero', (done) => {
|
||||
user.stats.hp = 0;
|
||||
user.stats.gp = 40;
|
||||
try {
|
||||
buyHealthPotion(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMin'));
|
||||
expect(user.stats.hp).to.eql(0);
|
||||
expect(user.stats.gp).to.eql(40);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow potion purchases when hp is negative', (done) => {
|
||||
user.stats.hp = -8;
|
||||
user.stats.gp = 40;
|
||||
try {
|
||||
buyHealthPotion(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMin'));
|
||||
expect(user.stats.hp).to.eql(-8);
|
||||
expect(user.stats.gp).to.eql(40);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import changeClass from '../../../website/common/script/ops/changeClass';
|
||||
import {
|
||||
NotAuthorized,
|
||||
@@ -58,7 +60,8 @@ describe('shared.ops.changeClass', () => {
|
||||
|
||||
it('changes class', () => {
|
||||
user.stats.class = 'healer';
|
||||
user.items.gear.owned.armor_rogue_1 = true; // eslint-disable-line camelcase
|
||||
user.items.gear.owned.weapon_healer_3 = true;
|
||||
user.items.gear.equipped.weapon = 'weapon_healer_3';
|
||||
|
||||
let [data] = changeClass(user, {query: {class: 'rogue'}});
|
||||
expect(data).to.eql({
|
||||
@@ -70,13 +73,10 @@ describe('shared.ops.changeClass', () => {
|
||||
|
||||
expect(user.stats.class).to.equal('rogue');
|
||||
expect(user.flags.classSelected).to.be.true;
|
||||
expect(user.items.gear.equipped.weapon).to.equal('weapon_rogue_0');
|
||||
expect(user.items.gear.owned.weapon_rogue_0).to.be.true;
|
||||
expect(user.items.gear.equipped.armor).to.equal('armor_rogue_1');
|
||||
expect(user.items.gear.owned.armor_rogue_1).to.be.true;
|
||||
expect(user.items.gear.equipped.shield).to.equal('shield_rogue_0');
|
||||
expect(user.items.gear.owned.shield_rogue_0).to.be.true;
|
||||
expect(user.items.gear.equipped.head).to.equal('head_base_0');
|
||||
expect(user.items.gear.owned.weapon_healer_3).to.be.true;
|
||||
expect(user.items.gear.equipped.weapon).to.equal('weapon_healer_3');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,10 +14,18 @@ describe('shared.ops.releaseBoth', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let p in content.pets) {
|
||||
user.items.pets[p] = content.pets[p];
|
||||
user.items.pets[p] = 5;
|
||||
}
|
||||
|
||||
for (let m in content.pets) {
|
||||
user.items.mounts[m] = content.pets[m];
|
||||
user.items.mounts[m] = true;
|
||||
}
|
||||
|
||||
user.items.currentMount = animal;
|
||||
user.items.currentPet = animal;
|
||||
user.items.pets[animal] = 5;
|
||||
user.items.mounts[animal] = true;
|
||||
user.balance = 1.5;
|
||||
});
|
||||
|
||||
@@ -34,7 +42,7 @@ describe('shared.ops.releaseBoth', () => {
|
||||
});
|
||||
|
||||
it('grants triad bingo with gems', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(1);
|
||||
@@ -45,27 +53,79 @@ describe('shared.ops.releaseBoth', () => {
|
||||
user.achievements.triadBingo = 1;
|
||||
user.achievements.triadBingoCount = 1;
|
||||
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not grant triad bingo if any pet has not been previously found', () => {
|
||||
let triadBingoCountBeforeRelease = user.achievements.triadBingoCount;
|
||||
user.items.pets[animal] = -1;
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(triadBingoCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('releases pets', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.items.pets[animal]).to.be.empty;
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = 0;
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (null)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = null;
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
delete user.items.pets[animal];
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('releases mounts', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (null)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
user.items.mounts[animal] = null;
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
delete user.items.mounts[animal];
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('removes drop currentPet', () => {
|
||||
let petInfo = content.petInfo[user.items.currentPet];
|
||||
expect(petInfo.type).to.equal('drop');
|
||||
|
||||
@@ -14,8 +14,12 @@ describe('shared.ops.releaseMounts', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let k in content.pets) {
|
||||
user.items.mounts[k] = content.pets[k];
|
||||
user.items.mounts[k] = true;
|
||||
}
|
||||
|
||||
user.items.currentMount = animal;
|
||||
user.items.mounts[animal] = true;
|
||||
user.balance = 1;
|
||||
});
|
||||
|
||||
@@ -32,7 +36,7 @@ describe('shared.ops.releaseMounts', () => {
|
||||
});
|
||||
|
||||
it('releases mounts', () => {
|
||||
let [, message] = releaseMounts(user);
|
||||
let message = releaseMounts(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsReleased'));
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
@@ -60,10 +64,27 @@ describe('shared.ops.releaseMounts', () => {
|
||||
|
||||
it('increases mountMasterCount achievement', () => {
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (null)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
user.items.mounts[animal] = null;
|
||||
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
delete user.items.mounts[animal];
|
||||
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('subtracts gems from balance', () => {
|
||||
releaseMounts(user);
|
||||
|
||||
|
||||
@@ -14,8 +14,12 @@ describe('shared.ops.releasePets', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let k in content.pets) {
|
||||
user.items.pets[k] = content.pets[k];
|
||||
user.items.pets[k] = 5;
|
||||
}
|
||||
|
||||
user.items.currentPet = animal;
|
||||
user.items.pets[animal] = 5;
|
||||
user.balance = 1;
|
||||
});
|
||||
|
||||
@@ -32,7 +36,7 @@ describe('shared.ops.releasePets', () => {
|
||||
});
|
||||
|
||||
it('releases pets', () => {
|
||||
let [, message] = releasePets(user);
|
||||
let message = releasePets(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('petsReleased'));
|
||||
expect(user.items.pets[animal]).to.equal(0);
|
||||
@@ -69,4 +73,29 @@ describe('shared.ops.releasePets', () => {
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
|
||||
user.items.pets[animal] = 0;
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (null)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = null;
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
delete user.items.pets[animal];
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,16 @@ describe('shared.ops.sell', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error when the requested amount is above the available amount', (done) => {
|
||||
try {
|
||||
sell(user, {params: { type, key }, query: {amount: 2} });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('userItemsNotEnough', {type}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('reduces item count from user', () => {
|
||||
sell(user, {params: { type, key } });
|
||||
|
||||
|
||||
@@ -38,6 +38,18 @@ module.exports = {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/stripe': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/amazon': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/paypal': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* Comment out for holiday events */
|
||||
/* .npc_ian {
|
||||
.npc_ian {
|
||||
background: url("/npc_ian.gif") no-repeat;
|
||||
width: 78px;
|
||||
height: 135px;
|
||||
} */
|
||||
}
|
||||
|
||||
.quest_burnout {
|
||||
background: url("/quest_burnout.gif") no-repeat;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.promo_android {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px 0px;
|
||||
background-position: -139px -1483px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.promo_backgrounds_armoire_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1606px -295px;
|
||||
background-position: -565px -894px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1606px -590px;
|
||||
background-position: -707px -894px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1325px -441px;
|
||||
background-position: -1608px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -987px -894px;
|
||||
background-position: -1749px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -48,67 +48,67 @@
|
||||
}
|
||||
.promo_backgrounds_armoire_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1128px -894px;
|
||||
background-position: -142px -894px;
|
||||
width: 140px;
|
||||
height: 439px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1269px -894px;
|
||||
background-position: -283px -894px;
|
||||
width: 139px;
|
||||
height: 438px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -282px -894px;
|
||||
background-position: -1467px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -141px -894px;
|
||||
background-position: -1467px 0px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -894px;
|
||||
background-position: -1749px -884px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -705px -894px;
|
||||
background-position: -1749px 0px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1041px 0px;
|
||||
background-position: -1041px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1041px -442px;
|
||||
background-position: -1183px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1183px 0px;
|
||||
background-position: -1183px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -447px -148px;
|
||||
background-position: -1325px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1183px -442px;
|
||||
background-position: -1041px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -118,18 +118,24 @@
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bees {
|
||||
.promo_backgrounds_armoire_201708 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -899px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_feathered {
|
||||
.promo_bees {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -757px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_feathered {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1325px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_splashy {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -757px 0px;
|
||||
@@ -144,31 +150,31 @@
|
||||
}
|
||||
.promo_chairs_glasses {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1854px -352px;
|
||||
background-position: -1994px -412px;
|
||||
width: 51px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_checkin_incentives {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1606px 0px;
|
||||
background-position: -423px -894px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_classes_fall_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1484px;
|
||||
background-position: -423px -1189px;
|
||||
width: 321px;
|
||||
height: 100px;
|
||||
}
|
||||
.promo_classes_fall_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -823px -1336px;
|
||||
background-position: -849px -1042px;
|
||||
width: 377px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_classes_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1606px -885px;
|
||||
background-position: -1890px 0px;
|
||||
width: 103px;
|
||||
height: 348px;
|
||||
}
|
||||
@@ -180,109 +186,115 @@
|
||||
}
|
||||
.promo_contrib_spotlight_Keith {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -442px;
|
||||
background-position: -1994px -1070px;
|
||||
width: 87px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_alys {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -1318px;
|
||||
background-position: -1994px -847px;
|
||||
width: 90px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_contrib_spotlight_beffymaroo {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -626px;
|
||||
background-position: -491px -1483px;
|
||||
width: 114px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_contrib_spotlight_blade {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -1429px;
|
||||
background-position: -1994px -958px;
|
||||
width: 89px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_cantras {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -663px;
|
||||
background-position: -1994px -1291px;
|
||||
width: 87px;
|
||||
height: 109px;
|
||||
}
|
||||
.promo_contrib_spotlight_dewines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -554px;
|
||||
background-position: -1994px -1182px;
|
||||
width: 89px;
|
||||
height: 108px;
|
||||
}
|
||||
.promo_contrib_spotlight_megan {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -1206px;
|
||||
background-position: -1994px -623px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_shanaqui {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -1094px;
|
||||
background-position: -1994px -735px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_cow {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -564px -894px;
|
||||
background-position: -1608px 0px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_cupid_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px 0px;
|
||||
background-position: 0px -1335px;
|
||||
width: 138px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_dilatoryDistress {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1270px -1484px;
|
||||
background-position: -956px -1335px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_egg_mounts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1336px;
|
||||
background-position: -849px -894px;
|
||||
width: 280px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ember_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -447px -148px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_enchanted_armoire {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1201px -1336px;
|
||||
background-position: -745px -1189px;
|
||||
width: 374px;
|
||||
height: 76px;
|
||||
}
|
||||
.promo_enchanted_armoire_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -322px -1484px;
|
||||
background-position: -1227px -1042px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -540px -1484px;
|
||||
background-position: -654px -1335px;
|
||||
width: 180px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1088px -1484px;
|
||||
background-position: -1890px -1532px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -912px;
|
||||
background-position: -140px -747px;
|
||||
width: 122px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -906px -1484px;
|
||||
background-position: -1890px -895px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
@@ -294,19 +306,25 @@
|
||||
}
|
||||
.promo_floral_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -352px;
|
||||
background-position: -1994px 0px;
|
||||
width: 105px;
|
||||
height: 273px;
|
||||
}
|
||||
.promo_ghost_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -846px -894px;
|
||||
background-position: -1467px -884px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_good_luck {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -526px -776px;
|
||||
width: 110px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_habitica {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -176px;
|
||||
background-position: -315px -1483px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
@@ -318,37 +336,37 @@
|
||||
}
|
||||
.promo_habitoween_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -423px -894px;
|
||||
background-position: -1608px -884px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_haunted_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -774px;
|
||||
background-position: -1994px -274px;
|
||||
width: 100px;
|
||||
height: 137px;
|
||||
}
|
||||
.promo_holly_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1325px 0px;
|
||||
background-position: 0px -894px;
|
||||
width: 141px;
|
||||
height: 440px;
|
||||
}
|
||||
.promo_item_notif {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1585px;
|
||||
background-position: -404px -1335px;
|
||||
width: 249px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_jackalope {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -281px -1336px;
|
||||
background-position: -1130px -894px;
|
||||
width: 276px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_king_manta {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -558px -1336px;
|
||||
background-position: -139px -1335px;
|
||||
width: 264px;
|
||||
height: 147px;
|
||||
}
|
||||
@@ -360,181 +378,163 @@
|
||||
}
|
||||
.promo_mystery_201405 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1543px -1484px;
|
||||
background-position: -1890px -1077px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201406 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1606px -1234px;
|
||||
background-position: -1994px -1613px;
|
||||
width: 90px;
|
||||
height: 96px;
|
||||
}
|
||||
.promo_mystery_201407 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1854px -563px;
|
||||
background-position: -1890px -1714px;
|
||||
width: 42px;
|
||||
height: 62px;
|
||||
}
|
||||
.promo_mystery_201408 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1839px -1206px;
|
||||
background-position: -1120px -1189px;
|
||||
width: 60px;
|
||||
height: 71px;
|
||||
}
|
||||
.promo_mystery_201409 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1634px -1484px;
|
||||
background-position: -1890px -713px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201410 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1839px -1094px;
|
||||
background-position: -1994px -1710px;
|
||||
width: 72px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201411 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -614px -1585px;
|
||||
background-position: -1890px -622px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201412 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1869px -1003px;
|
||||
background-position: -2046px -546px;
|
||||
width: 42px;
|
||||
height: 66px;
|
||||
}
|
||||
.promo_mystery_201501 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1863px -708px;
|
||||
background-position: -2046px -412px;
|
||||
width: 48px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201502 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -815px -1484px;
|
||||
background-position: -1890px -1168px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201503 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1179px -1484px;
|
||||
background-position: -1890px -1259px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201504 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1839px -1318px;
|
||||
background-position: -1181px -1189px;
|
||||
width: 60px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201505 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1361px -1484px;
|
||||
background-position: -1890px -1441px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201506 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1871px -912px;
|
||||
background-position: -2046px -476px;
|
||||
width: 42px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -773px;
|
||||
background-position: -1994px -1507px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -140px -747px;
|
||||
background-position: -1890px -531px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -250px -1585px;
|
||||
background-position: -1047px -1335px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201510 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -721px -1484px;
|
||||
background-position: -1890px -440px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -432px -1585px;
|
||||
background-position: -1890px -1350px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201512 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1863px -626px;
|
||||
background-position: -1229px -1335px;
|
||||
width: 60px;
|
||||
height: 81px;
|
||||
}
|
||||
.promo_mystery_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1748px -1003px;
|
||||
background-position: -835px -1335px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -523px -1585px;
|
||||
background-position: -1890px -986px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1452px -1484px;
|
||||
background-position: -1890px -804px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -1076px;
|
||||
background-position: -1890px -349px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -997px -1484px;
|
||||
background-position: -1890px -1623px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201606 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -879px;
|
||||
background-position: -1994px -1401px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201607 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -341px -1585px;
|
||||
background-position: -1138px -1335px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -985px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -1167px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1849px -774px;
|
||||
width: 63px;
|
||||
height: 84px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 928 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -1,462 +1,486 @@
|
||||
.promo_mystery_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -949px -1619px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -855px -1619px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1845px -1219px;
|
||||
width: 63px;
|
||||
height: 84px;
|
||||
}
|
||||
.promo_mystery_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -1083px;
|
||||
background-position: -120px -1770px;
|
||||
width: 90px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_mystery_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -586px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 558px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_mystery_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1712px -300px;
|
||||
background-position: -977px -515px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -584px -1538px;
|
||||
background-position: -1625px -742px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1040px -1247px;
|
||||
background-position: -1625px -299px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1680px -992px;
|
||||
background-position: -1043px -1619px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -301px -1538px;
|
||||
background-position: -1625px -151px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -934px;
|
||||
background-position: -1084px -737px;
|
||||
width: 111px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201707 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -749px -1619px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201708 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1134px -1619px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_3014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1381px;
|
||||
background-position: -615px -1508px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_naming_day_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1766px -595px;
|
||||
width: 159px;
|
||||
height: 141px;
|
||||
}
|
||||
.promo_new_hair_fall2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1429px -442px;
|
||||
background-position: 0px -1066px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_orca {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -783px;
|
||||
background-position: -1377px -895px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_orcas {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -844px;
|
||||
background-position: -1625px -1219px;
|
||||
width: 219px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_partyhats {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -1025px;
|
||||
background-position: -1625px -1456px;
|
||||
width: 115px;
|
||||
height: 47px;
|
||||
}
|
||||
.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1110px -1422px;
|
||||
background-position: -273px -792px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1135px -1437px;
|
||||
background-position: -298px -807px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -824px -1096px;
|
||||
background-position: -965px -1066px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -849px -1111px;
|
||||
background-position: -990px -1081px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_peppermint_flame {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1953px -1085px;
|
||||
background-position: -1483px -884px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1085px;
|
||||
background-position: -1084px -589px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1837px -1100px;
|
||||
background-position: -1109px -604px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pyromancer {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -632px;
|
||||
background-position: -1368px -552px;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
}
|
||||
.promo_rainbow_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1712px -196px;
|
||||
background-position: -1833px -1038px;
|
||||
width: 92px;
|
||||
height: 103px;
|
||||
}
|
||||
.promo_redesign_header {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -849px -295px;
|
||||
width: 166px;
|
||||
height: 188px;
|
||||
}
|
||||
.promo_redesign_login {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -559px 0px;
|
||||
width: 524px;
|
||||
height: 274px;
|
||||
}
|
||||
.promo_redesign_tavern {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -601px -515px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.promo_sdcc_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -515px;
|
||||
width: 272px;
|
||||
height: 363px;
|
||||
}
|
||||
.promo_seafoam {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1287px 0px;
|
||||
background-position: -1226px -257px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seasonal_shop_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -864px -1538px;
|
||||
background-position: -1625px -890px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_shimmer_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1144px -1538px;
|
||||
background-position: -601px -702px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.promo_shimmer_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1429px 0px;
|
||||
background-position: -1483px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_shinySeeds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1287px -442px;
|
||||
background-position: -1483px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -725px -562px;
|
||||
background-position: 0px -879px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -750px -577px;
|
||||
background-position: -25px -894px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1472px;
|
||||
background-position: -441px -1619px;
|
||||
width: 198px;
|
||||
height: 91px;
|
||||
}
|
||||
.customize-option.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1837px -1487px;
|
||||
background-position: -466px -1634px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_spooky_sparkles_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -196px;
|
||||
background-position: -1625px -447px;
|
||||
width: 140px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_spring_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -687px -984px;
|
||||
background-position: -809px -879px;
|
||||
width: 362px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_spring_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -730px -1247px;
|
||||
background-position: -564px -1313px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_springclasses2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -91px;
|
||||
background-position: -1320px -1066px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_springclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px 0px;
|
||||
background-position: -326px -1508px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_staff_spotlight_Lemoness {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -330px;
|
||||
background-position: -1368px -257px;
|
||||
width: 102px;
|
||||
height: 146px;
|
||||
}
|
||||
.promo_staff_spotlight_Viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1969px -182px;
|
||||
background-position: 0px -1770px;
|
||||
width: 119px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_staff_spotlight_paglias {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1963px -481px;
|
||||
background-position: -1368px -404px;
|
||||
width: 99px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_steampunk_3017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -282px -1096px;
|
||||
background-position: -423px -1066px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_summer_classes_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -257px -984px;
|
||||
background-position: -1184px -1313px;
|
||||
width: 429px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_summer_classes_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1689px;
|
||||
background-position: -1625px -1367px;
|
||||
width: 300px;
|
||||
height: 88px;
|
||||
}
|
||||
.promo_summer_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -423px -1096px;
|
||||
background-position: -564px -1066px;
|
||||
width: 400px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_summer_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1145px 0px;
|
||||
background-position: -1084px 0px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_takeThis_gear {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1640px;
|
||||
background-position: -833px -1508px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_takethis_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1927px -1640px;
|
||||
background-position: -1424px -1217px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_task_planning {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px 0px;
|
||||
background-position: -1226px -699px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.promo_turkey_day_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -141px -1096px;
|
||||
background-position: -282px -1066px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1951px -1309px;
|
||||
background-position: -1845px -1304px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_unconventional_armor2 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -2030px -1381px;
|
||||
background-position: -1833px -1142px;
|
||||
width: 70px;
|
||||
height: 74px;
|
||||
}
|
||||
.promo_updos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -182px;
|
||||
background-position: -1766px -447px;
|
||||
width: 156px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1951px -1233px;
|
||||
background-position: -932px -702px;
|
||||
width: 146px;
|
||||
height: 75px;
|
||||
}
|
||||
.promo_winter_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -749px -1422px;
|
||||
background-position: -1063px -1217px;
|
||||
width: 360px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_winter_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -257px -839px;
|
||||
background-position: -376px -879px;
|
||||
width: 432px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_winter_fireworks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1233px;
|
||||
background-position: -302px -1619px;
|
||||
width: 138px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winterclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -423px -1422px;
|
||||
background-position: 0px -1508px;
|
||||
width: 325px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1096px;
|
||||
background-position: -141px -1066px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.customize-option.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -25px -1111px;
|
||||
background-position: -166px -1081px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_winteryhair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -1564px;
|
||||
background-position: -604px -792px;
|
||||
width: 152px;
|
||||
height: 75px;
|
||||
}
|
||||
.avatar_variety {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -690px -839px;
|
||||
background-position: -564px -1217px;
|
||||
width: 498px;
|
||||
height: 95px;
|
||||
}
|
||||
.npc_viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -992px;
|
||||
background-position: -640px -1619px;
|
||||
width: 108px;
|
||||
height: 90px;
|
||||
}
|
||||
.party_preview {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -499px -312px;
|
||||
background-position: 0px -295px;
|
||||
width: 451px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_backtoschool {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -330px;
|
||||
background-position: 0px -1619px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_cooking {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -328px -562px;
|
||||
background-position: -452px -295px;
|
||||
width: 396px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_startingover {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -783px;
|
||||
background-position: -151px -1619px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_valentines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1179px -1096px;
|
||||
background-position: -874px -1313px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_working_out {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1538px;
|
||||
background-position: -1625px 0px;
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_arts_crafts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -839px;
|
||||
background-position: -1226px 0px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
.scene_buying_rewards {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -663px;
|
||||
background-position: -1625px -1038px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_coding {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -632px;
|
||||
background-position: -1226px -895px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_dailies {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -562px;
|
||||
background-position: -273px -515px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
.scene_eco_friendly {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1571px -491px;
|
||||
width: 222px;
|
||||
height: 171px;
|
||||
}
|
||||
.scene_guilds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -312px;
|
||||
width: 498px;
|
||||
height: 249px;
|
||||
}
|
||||
.scene_habitica_house {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px 0px;
|
||||
width: 585px;
|
||||
height: 311px;
|
||||
}
|
||||
.scene_habits {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -423px -1247px;
|
||||
width: 306px;
|
||||
height: 174px;
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -481px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_phone_peek {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1812px -934px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 996 KiB After Width: | Height: | Size: 873 KiB |
@@ -1,36 +1,90 @@
|
||||
.scene_eco_friendly {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -518px -737px;
|
||||
width: 222px;
|
||||
height: 171px;
|
||||
}
|
||||
.scene_guilds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -312px;
|
||||
width: 498px;
|
||||
height: 249px;
|
||||
}
|
||||
.scene_habitica_house {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px 0px;
|
||||
width: 585px;
|
||||
height: 311px;
|
||||
}
|
||||
.scene_habits {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -562px;
|
||||
width: 306px;
|
||||
height: 174px;
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px -384px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_pet_hatching {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -586px -343px;
|
||||
width: 315px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_phone_peek {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px -233px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_raking_leaves {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -340px 0px;
|
||||
background-position: 0px -737px;
|
||||
width: 246px;
|
||||
height: 198px;
|
||||
}
|
||||
.scene_studying {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px 0px;
|
||||
width: 220px;
|
||||
height: 232px;
|
||||
}
|
||||
.scene_task_list {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -936px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -587px 0px;
|
||||
background-position: -241px -936px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_video_games {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px 0px;
|
||||
background-position: -586px 0px;
|
||||
width: 339px;
|
||||
height: 342px;
|
||||
}
|
||||
.welcome_basic_avatars {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -271px -343px;
|
||||
background-position: -307px -562px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.welcome_promo_party {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -343px;
|
||||
background-position: -247px -737px;
|
||||
width: 270px;
|
||||
height: 180px;
|
||||
}
|
||||
.welcome_sample_tasks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -524px;
|
||||
background-position: -554px -562px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 447 KiB |
938
website/assets/sprites/dist/spritesmith-main-0.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-0.png
vendored
|
Before Width: | Height: | Size: 518 KiB After Width: | Height: | Size: 566 KiB |
7268
website/assets/sprites/dist/spritesmith-main-1.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-1.png
vendored
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 248 KiB |
2050
website/assets/sprites/dist/spritesmith-main-10.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-10.png
vendored
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 155 KiB |
1530
website/assets/sprites/dist/spritesmith-main-11.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-11.png
vendored
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 152 KiB |
1584
website/assets/sprites/dist/spritesmith-main-12.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-12.png
vendored
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 142 KiB |
1812
website/assets/sprites/dist/spritesmith-main-13.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-13.png
vendored
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 148 KiB |
2548
website/assets/sprites/dist/spritesmith-main-14.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-14.png
vendored
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 144 KiB |
2636
website/assets/sprites/dist/spritesmith-main-15.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-15.png
vendored
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 156 KiB |
1084
website/assets/sprites/dist/spritesmith-main-16.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-16.png
vendored
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 174 KiB |
794
website/assets/sprites/dist/spritesmith-main-17.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-17.png
vendored
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 160 KiB |
1518
website/assets/sprites/dist/spritesmith-main-18.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-18.png
vendored
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 147 KiB |
2252
website/assets/sprites/dist/spritesmith-main-2.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-2.png
vendored
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 54 KiB |
2408
website/assets/sprites/dist/spritesmith-main-3.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-3.png
vendored
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 77 KiB |
2880
website/assets/sprites/dist/spritesmith-main-4.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-4.png
vendored
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 74 KiB |
2302
website/assets/sprites/dist/spritesmith-main-5.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-5.png
vendored
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 165 KiB |
2730
website/assets/sprites/dist/spritesmith-main-6.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-6.png
vendored
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 163 KiB |
2728
website/assets/sprites/dist/spritesmith-main-7.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-7.png
vendored
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 140 KiB |
1600
website/assets/sprites/dist/spritesmith-main-8.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-8.png
vendored
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 346 KiB |
1424
website/assets/sprites/dist/spritesmith-main-9.css
vendored
BIN
website/assets/sprites/dist/spritesmith-main-9.png
vendored
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |