Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac3ac73737 | ||
|
|
8004c1e759 | ||
|
|
d9e50e7632 | ||
|
|
4cd742c19a | ||
|
|
03ca65f2b8 | ||
|
|
ea74271484 | ||
|
|
0897367538 | ||
|
|
2566fcecb7 | ||
|
|
c99863f339 | ||
|
|
71d632f6f0 | ||
|
|
548cc2ceb0 | ||
|
|
bf03ac8e44 | ||
|
|
1853113aed | ||
|
|
1ce1ed7c6b | ||
|
|
c9f870a929 | ||
|
|
135df1dc48 | ||
|
|
a240d9581f | ||
|
|
3cd591ed4f | ||
|
|
87076ed07c | ||
|
|
33a39d3683 | ||
|
|
e4f5950ffc | ||
|
|
f2d81a8d9c | ||
|
|
43b6f71044 | ||
|
|
06de1670b4 | ||
|
|
7fd2522e93 | ||
|
|
acb4b79078 | ||
|
|
8299982484 | ||
|
|
207b1e91ca | ||
|
|
aee21edd5f | ||
|
|
6e1bbd05bc | ||
|
|
763c2c28ea | ||
|
|
7aa9ba45f6 | ||
|
|
d67398fba6 | ||
|
|
6fb4c1b576 | ||
|
|
7c5bd526b1 | ||
|
|
187a9f5f19 | ||
|
|
7954763738 | ||
|
|
d062fd8507 | ||
|
|
ceb10137a2 | ||
|
|
58330a4d01 | ||
|
|
678f48fde9 | ||
|
|
a642d94443 | ||
|
|
39a112b605 | ||
|
|
8c8f0ea201 | ||
|
|
ca8541e8c4 | ||
|
|
773d546e4f | ||
|
|
f269d381da | ||
|
|
3483f69559 | ||
|
|
9ada0c9b02 | ||
|
|
8bbe9ac36e | ||
|
|
75b93e6ec4 | ||
|
|
bd9bebe591 | ||
|
|
f7b298d506 | ||
|
|
4bd2932955 | ||
|
|
7060f5941d | ||
|
|
21379ee357 | ||
|
|
fe62713809 | ||
|
|
ca7272a987 | ||
|
|
cb46cd8eeb | ||
|
|
d9d02ca81d | ||
|
|
50c216eb41 | ||
|
|
2c60fade01 | ||
|
|
6bdf8fdabc | ||
|
|
3db304b6bf | ||
|
|
ea2788ab2f | ||
|
|
5e111f6f6c | ||
|
|
ea85a8a3a8 | ||
|
|
e811a2700e | ||
|
|
136dcd27a9 | ||
|
|
bf76f823d5 | ||
|
|
83b573dcfc | ||
|
|
c4fa9426b3 | ||
|
|
042e5a8d63 | ||
|
|
7a8857010e | ||
|
|
a52bd66871 | ||
|
|
f7ce269f3c | ||
|
|
c084f8a2b9 | ||
|
|
c1c42e17b8 | ||
|
|
306a782e7a | ||
|
|
20178c0722 | ||
|
|
256e2e809c | ||
|
|
592345e22c | ||
|
|
f738f550e7 | ||
|
|
495bc9aa50 | ||
|
|
292b2acb1e | ||
|
|
977f9d5174 | ||
|
|
36fa3ab06f | ||
|
|
7422d020b1 | ||
|
|
5d0fe0aac3 | ||
|
|
85644fdc1b | ||
|
|
138b5c4bdb | ||
|
|
52edb8a8da | ||
|
|
60de7c8f21 | ||
|
|
137636cb40 | ||
|
|
1999e1098e | ||
|
|
4d3a0c0571 | ||
|
|
706de95458 | ||
|
|
e3c1eaa9d2 | ||
|
|
17c0f795cc | ||
|
|
cb5ac9014e | ||
|
|
0be681b7a2 | ||
|
|
5360f9e587 | ||
|
|
4553a411f6 | ||
|
|
613f51b08d | ||
|
|
2292ba2694 | ||
|
|
befacca457 | ||
|
|
5cd30b430d | ||
|
|
c5d9ee1e0a | ||
|
|
234328f2ba | ||
|
|
029afa197e | ||
|
|
05b35c5147 | ||
|
|
c9427ad34c | ||
|
|
d6c62262f1 | ||
|
|
ec1d378504 | ||
|
|
3bb88f450a | ||
|
|
97a38e68c5 | ||
|
|
be948a1bf2 | ||
|
|
018976a723 | ||
|
|
00e5896ac6 | ||
|
|
36bc693545 | ||
|
|
f27706cb4b | ||
|
|
f6f99ec57e | ||
|
|
c852d9d581 | ||
|
|
4a78514308 | ||
|
|
265b48752d | ||
|
|
db0b0d6b6d | ||
|
|
20f1087552 | ||
|
|
2e9bc2c31c | ||
|
|
b606dd1c40 | ||
|
|
db1c2fd5a2 | ||
|
|
de1e477ce2 | ||
|
|
67318177a2 | ||
|
|
cd1be828ca | ||
|
|
9ffebc10a7 | ||
|
|
5cd11ed343 | ||
|
|
9de118f0d9 | ||
|
|
0e069e78d5 | ||
|
|
46ed1813c6 | ||
|
|
05ea2c1ce6 | ||
|
|
aeb8d4f500 | ||
|
|
58d910fe62 | ||
|
|
71f2f31606 | ||
|
|
cc532fa993 | ||
|
|
ba66a1c098 | ||
|
|
b4d5c634b3 | ||
|
|
216006beab | ||
|
|
c30c51f386 | ||
|
|
2de794c32b | ||
|
|
9e1f7f3811 |
2
.github/CONTRIBUTING.md
vendored
@@ -1,6 +1,6 @@
|
||||
# Reporting Bugs
|
||||
|
||||
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitrpg/issues/2760)
|
||||
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitica/issues/2760)
|
||||
|
||||
# Pull Request
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
[//]: # (If you have a feature request, use "Help > Request a Feature", not GitHub or the Report a Bug guild.)
|
||||
|
||||
[//]: # (For more guidelines see https://github.com/HabitRPG/habitrpg/issues/2760)
|
||||
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
|
||||
|
||||
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
|
||||
### General Info
|
||||
|
||||
@@ -33,3 +33,4 @@ env:
|
||||
- TEST="test:common" COVERAGE=true
|
||||
- TEST="test:karma" COVERAGE=true
|
||||
- TEST="client:unit" COVERAGE=true
|
||||
- TEST="apidoc"
|
||||
|
||||
18
Dockerfile-prod
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM node:boron
|
||||
|
||||
# Install global packages
|
||||
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.99.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
# Create Build dir
|
||||
RUN mkdir -p ./website/build
|
||||
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
@@ -3,7 +3,7 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||
|
||||
/**
|
||||
* database_reports/count_users_who_own_specified_gear.js
|
||||
* https://github.com/HabitRPG/habitrpg/pull/3884
|
||||
* https://github.com/HabitRPG/habitica/pull/3884
|
||||
*/
|
||||
|
||||
var thingsOfInterest = {
|
||||
|
||||
36
gulp/gulp-bootstrap.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import gulp from 'gulp';
|
||||
import fs from 'fs';
|
||||
|
||||
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
|
||||
// them into Git
|
||||
|
||||
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
|
||||
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
|
||||
|
||||
// https://stackoverflow.com/a/14387791/969528
|
||||
function copyFile(source, target, cb) {
|
||||
let cbCalled = false;
|
||||
|
||||
function done(err) {
|
||||
if (!cbCalled) {
|
||||
cb(err);
|
||||
cbCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let rd = fs.createReadStream(source);
|
||||
rd.on('error', done);
|
||||
let wr = fs.createWriteStream(target);
|
||||
wr.on('error', done);
|
||||
wr.on('close', () => done());
|
||||
rd.pipe(wr);
|
||||
}
|
||||
|
||||
gulp.task('bootstrap', (done) => {
|
||||
// use new config
|
||||
copyFile(
|
||||
BOOSTRAP_NEW_CONFIG_PATH,
|
||||
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
|
||||
done,
|
||||
);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
|
||||
});
|
||||
|
||||
if (numberOfSheetsThatAreTooBig > 0) {
|
||||
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitrpg/pull/6683#issuecomment-185462180
|
||||
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
||||
} else {
|
||||
console.log('All images are within the correct dimensions');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ if (process.env.NODE_ENV === 'production') {
|
||||
require('./gulp/gulp-newstuff');
|
||||
require('./gulp/gulp-build');
|
||||
require('./gulp/gulp-babelify');
|
||||
require('./gulp/gulp-bootstrap');
|
||||
} else {
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require);
|
||||
require('gulp').task('default', ['test']);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
/****************************************
|
||||
* Author: @Alys
|
||||
*
|
||||
* Reason: Collection quests are being changed
|
||||
* to require fewer items collected:
|
||||
* https://github.com/HabitRPG/habitrpg/pull/7987
|
||||
* This will cause existing quests to end sooner
|
||||
* than the party is expecting.
|
||||
* This script inserts an explanatory `system`
|
||||
* message into the chat for affected parties.
|
||||
***************************************/
|
||||
|
||||
global.Promise = require('bluebird');
|
||||
const uuid = require('uuid');
|
||||
const TaskQueue = require('cwait').TaskQueue;
|
||||
const logger = require('./utils/logger');
|
||||
const Timer = require('./utils/timer');
|
||||
const connectToDb = require('./utils/connect').connectToDb;
|
||||
const closeDb = require('./utils/connect').closeDb;
|
||||
|
||||
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.wikia.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
|
||||
|
||||
const timer = new Timer();
|
||||
|
||||
// PROD: Enable prod db
|
||||
// const DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX';
|
||||
const DB_URI = 'mongodb://localhost/habitrpg';
|
||||
|
||||
const COLLECTION_QUESTS = [
|
||||
'vice2',
|
||||
'egg',
|
||||
'moonstone1',
|
||||
'goldenknight1',
|
||||
'dilatoryDistress1',
|
||||
];
|
||||
|
||||
let Groups;
|
||||
|
||||
connectToDb(DB_URI).then((db) => {
|
||||
Groups = db.collection('groups');
|
||||
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(findPartiesWithCollectionQuest)
|
||||
// .then(displayGroups) // for testing only
|
||||
.then(addMessageToGroups)
|
||||
.then(() => {
|
||||
timer.stop();
|
||||
closeDb();
|
||||
}).catch(reportError);
|
||||
|
||||
function reportError (err) {
|
||||
logger.error('Uh oh, an error occurred');
|
||||
closeDb();
|
||||
timer.stop();
|
||||
throw err;
|
||||
}
|
||||
|
||||
function findPartiesWithCollectionQuest () {
|
||||
logger.info('Looking up groups on collection quests...');
|
||||
|
||||
return Groups.find({'quest.key': {$in: COLLECTION_QUESTS}}, ['name','quest']).toArray().then((groups) => {
|
||||
logger.success('Found', groups.length, 'parties on collection quests');
|
||||
|
||||
return Promise.resolve(groups);
|
||||
})
|
||||
}
|
||||
|
||||
function displayGroups (groups) { // for testing only
|
||||
logger.info('Displaying parties...');
|
||||
console.log(groups);
|
||||
return Promise.resolve(groups);
|
||||
}
|
||||
|
||||
function updateGroupById (group) {
|
||||
var newMessage = {
|
||||
'id' : uuid.v4(),
|
||||
'text' : message,
|
||||
'timestamp': Date.now(),
|
||||
'likes': {},
|
||||
'flags': {},
|
||||
'flagCount': 0,
|
||||
'uuid': 'system'
|
||||
};
|
||||
return Groups.findOneAndUpdate({_id: group._id}, {$push:{"chat" :{$each: [newMessage], $position:0}}}, {returnOriginal: false});
|
||||
// Does not set the newMessage flag for all party members because I don't think it's essential and
|
||||
// I don't want to run the extra code (extra database load, extra opportunity for bugs).
|
||||
}
|
||||
|
||||
function addMessageToGroups (groups) {
|
||||
let queue = new TaskQueue(Promise, 300);
|
||||
|
||||
logger.info('About to update', groups.length, 'parties...');
|
||||
|
||||
return Promise.map(groups, queue.wrap(updateGroupById)).then((result) => {
|
||||
let updates = result.filter(res => res.lastErrorObject && res.lastErrorObject.updatedExisting)
|
||||
let failures = result.filter(res => !(res.lastErrorObject && res.lastErrorObject.updatedExisting));
|
||||
|
||||
logger.success(updates.length, 'parties have been notified');
|
||||
|
||||
if (failures.length > 0) {
|
||||
logger.error(failures.length, 'parties could not be notified');
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
114
migrations/20170616_achievements.js
Normal file
@@ -0,0 +1,114 @@
|
||||
var migrationName = '20170616_achievements';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Updates to achievements for June 16, 2017 biweekly merge
|
||||
* 1. Multiply various collection quest achievements based on difficulty reduction
|
||||
* 2. Award Joined Challenge achievement to those who should have it already
|
||||
*/
|
||||
|
||||
import monk from '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 = {
|
||||
$or: [
|
||||
{'achievements.quests.dilatoryDistress1': {$gt:0}},
|
||||
{'achievements.quests.egg': {$gt:0}},
|
||||
{'achievements.quests.goldenknight1': {$gt:0}},
|
||||
{'achievements.quests.moonstone1': {$gt:0}},
|
||||
{'achievements.quests.vice2': {$gt:0}},
|
||||
{'achievements.challenges': {$exists: true, $ne: []}},
|
||||
{'challenges': {$exists: true, $ne: []}},
|
||||
],
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
'achievements',
|
||||
'challenges',
|
||||
],
|
||||
})
|
||||
.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 = {'migration': migrationName};
|
||||
|
||||
if (user.challenges.length > 0 || user.achievements.challenges.length > 0) {
|
||||
set['achievements.joinedChallenge'] = true;
|
||||
}
|
||||
if (user.achievements.quests.dilatoryDistress1) {
|
||||
set['achievements.quests.dilatoryDistress1'] = Math.ceil(user.achievements.quests.dilatoryDistress1 * 1.25);
|
||||
}
|
||||
if (user.achievements.quests.egg) {
|
||||
set['achievements.quests.egg'] = Math.ceil(user.achievements.quests.egg * 2.5);
|
||||
}
|
||||
if (user.achievements.quests.goldenknight1) {
|
||||
set['achievements.quests.goldenknight1'] = user.achievements.quests.goldenknight1 * 5;
|
||||
}
|
||||
if (user.achievements.quests.moonstone1) {
|
||||
set['achievements.quests.moonstone1'] = user.achievements.quests.moonstone1 * 5;
|
||||
}
|
||||
if (user.achievements.quests.vice2) {
|
||||
set['achievements.quests.vice2'] = Math.ceil(user.achievements.quests.vice2 * 1.5);
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set:set});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -1,47 +1,47 @@
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
|
||||
@@ -21,4 +21,4 @@ var processUsers = require('./groups/update-groups-with-group-plans');
|
||||
processUsers()
|
||||
.catch(function (err) {
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['body_mystery_201705','head_mystery_201705']
|
||||
$each:['body_mystery_201706','back_mystery_201706']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
83
migrations/tasks/tasks-set-yesterdailies.js
Normal file
@@ -0,0 +1,83 @@
|
||||
var migrationName = 'tasks-set-yesterdaily';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* Iterates over all tasks and sets the yseterDaily field to True
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||
|
||||
function processTasks(lastId) {
|
||||
// specify a query to limit the affected tasks (empty for all tasks):
|
||||
var query = {
|
||||
yesterDaily: false,
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbTasks.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
],
|
||||
})
|
||||
.then(updateTasks)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateTasks (tasks) {
|
||||
if (!tasks || tasks.length === 0) {
|
||||
console.warn('All appropriate tasks found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var taskPromises = tasks.map(updatetask);
|
||||
var lasttask = tasks[tasks.length - 1];
|
||||
|
||||
return Promise.all(taskPromises)
|
||||
.then(function () {
|
||||
processtasks(lasttask._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updatetask (task) {
|
||||
count++;
|
||||
var set = {'yesterDaily': true};
|
||||
|
||||
dbTasks.update({_id: task._id}, {$set:set});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + task._id);
|
||||
if (task._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' tasks 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 = processtasks;
|
||||
109
migrations/users/users-to-test.js
Normal file
@@ -0,0 +1,109 @@
|
||||
var migrationName = 'UserFromProdToTest';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* This migraition will copy user data from prod to test
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var testConnectionSting = ''; // FOR TEST DATABASE
|
||||
var usersTest = monk(testConnectionSting).get('users', { castIds: false });
|
||||
var groupsTest = monk(testConnectionSting).get('groups', { castIds: false });
|
||||
var challengesTest = monk(testConnectionSting).get('challenges', { castIds: false });
|
||||
var tasksTest = monk(testConnectionSting).get('tasks', { castIds: false });
|
||||
|
||||
var monk2 = require('monk');
|
||||
var liveConnectString = ''; // FOR TEST DATABASE
|
||||
var userLive = monk2(liveConnectString).get('users', { castIds: false });
|
||||
var groupsLive = monk2(liveConnectString).get('groups', { castIds: false });
|
||||
var challengesLive = monk2(liveConnectString).get('challenges', { castIds: false });
|
||||
var tasksLive = monk2(liveConnectString).get('tasks', { castIds: false });
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
// Variabls for updating
|
||||
let userIds = [
|
||||
'206039c6-24e4-4b9f-8a31-61cbb9aa3f66',
|
||||
];
|
||||
|
||||
let groupIds = [];
|
||||
let challengeIds = [];
|
||||
let tasksIds = [];
|
||||
|
||||
async function processUsers () {
|
||||
let userPromises = [];
|
||||
//{_id: {$in: userIds}}
|
||||
|
||||
return userLive.find({guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0'})
|
||||
.each((user, {close, pause, resume}) => {
|
||||
if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds);
|
||||
if (user.party._id) groupIds.push(user.party._id);
|
||||
if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges);
|
||||
if (user.tasksOrder.rewards.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.rewards);
|
||||
if (user.tasksOrder.todos.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.todos);
|
||||
if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys);
|
||||
if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits);
|
||||
|
||||
let userPromise = usersTest.update({'_id': user._id}, user, {upsert:true});
|
||||
userPromises.push(userPromise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(userPromises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done User");
|
||||
});
|
||||
}
|
||||
|
||||
function processGroups () {
|
||||
let promises = [];
|
||||
let groupsToQuery = uniq(groupIds);
|
||||
return groupsLive.find({_id: {$in: groupsToQuery}})
|
||||
.each((group, {close, pause, resume}) => {
|
||||
let promise = groupsTest.update({_id: group._id}, group, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Group");
|
||||
});
|
||||
}
|
||||
|
||||
function processChallenges () {
|
||||
let promises = [];
|
||||
let challengesToQuery = uniq(challengeIds);
|
||||
return challengesLive.find({_id: {$in: challengesToQuery}})
|
||||
.each((challenge, {close, pause, resume}) => {
|
||||
let promise = challengesTest.update({_id: challenge._id}, challenge, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Challenge");
|
||||
});
|
||||
}
|
||||
|
||||
function processTasks () {
|
||||
let promises = [];
|
||||
let tasksToQuery = uniq(tasksIds);
|
||||
return tasksLive.find({_id: {$in: tasksToQuery}})
|
||||
.each((task, {close, pause, resume}) => {
|
||||
let promise = tasksTest.update({_id: task._id}, task, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Tasks");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = async function prodToTest () {
|
||||
await processUsers();
|
||||
await processGroups();
|
||||
await processChallenges();
|
||||
await processTasks();
|
||||
};
|
||||
1043
npm-shrinkwrap.json
generated
18
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "3.93.2",
|
||||
"version": "3.101.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -15,8 +15,10 @@
|
||||
"aws-sdk": "^2.0.25",
|
||||
"axios": "^0.16.0",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-regenerator": "^6.16.1",
|
||||
@@ -67,6 +69,7 @@
|
||||
"gulp-uglify": "^1.4.2",
|
||||
"gulp.spritesmith": "^4.1.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
@@ -109,6 +112,9 @@
|
||||
"shelljs": "^0.7.6",
|
||||
"stripe": "^4.2.0",
|
||||
"superagent": "^3.4.3",
|
||||
"svg-inline-loader": "^0.7.1",
|
||||
"svg-url-loader": "^2.0.2",
|
||||
"svgo-loader": "^1.2.1",
|
||||
"universal-analytics": "~0.3.2",
|
||||
"url-loader": "^0.5.7",
|
||||
"useragent": "^2.1.9",
|
||||
@@ -135,7 +141,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
"test": "npm run lint && gulp test && npm run client:unit",
|
||||
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
|
||||
"test:build": "gulp test:prepare:build",
|
||||
"test:api-v3": "gulp test:api-v3",
|
||||
"test:api-v3:unit": "gulp test:api-v3:unit",
|
||||
@@ -152,14 +158,15 @@
|
||||
"test:nodemon": "gulp test:nodemon",
|
||||
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
||||
"sprites": "gulp sprites:compile",
|
||||
"client:dev": "node webpack/dev-server.js",
|
||||
"client:build": "node webpack/build.js",
|
||||
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
|
||||
"client:build": "gulp bootstrap && node webpack/build.js",
|
||||
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
|
||||
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
|
||||
"client:e2e": "node test/client/e2e/runner.js",
|
||||
"client:test": "npm run client:unit && npm run client:e2e",
|
||||
"start": "gulp run:dev",
|
||||
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
|
||||
"postinstall": "bower --config.interactive=false install -f && gulp build && npm run client:build",
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-istanbul": "^4.0.0",
|
||||
@@ -207,6 +214,7 @@
|
||||
"nightwatch": "^0.9.12",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"protractor": "^3.1.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"require-again": "^2.0.0",
|
||||
"rewire": "^2.3.3",
|
||||
"selenium-server": "^3.0.1",
|
||||
|
||||
@@ -304,5 +304,15 @@ describe('POST /challenges', () => {
|
||||
|
||||
await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
|
||||
});
|
||||
|
||||
it('awards achievement if this is creator\'s first challenge', async () => {
|
||||
await groupLeader.post('/challenges', {
|
||||
group: group._id,
|
||||
name: 'Test Challenge',
|
||||
shortName: 'TC Label',
|
||||
});
|
||||
groupLeader = await groupLeader.sync();
|
||||
expect(groupLeader.achievements.joinedChallenge).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,5 +123,12 @@ describe('POST /challenges/:challengeId/join', () => {
|
||||
|
||||
await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1);
|
||||
});
|
||||
|
||||
it('awards achievement if this is user\'s first challenge', async () => {
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await authorizedUser.sync();
|
||||
expect(authorizedUser.achievements.joinedChallenge).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,8 +100,8 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
await sleep(0.5);
|
||||
|
||||
await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
|
||||
expect(winningUser.notifications.length).to.equal(1);
|
||||
expect(winningUser.notifications[0].type).to.equal('WON_CHALLENGE');
|
||||
expect(winningUser.notifications.length).to.equal(2); // 2 because winningUser just joined the challenge, which now awards an achievement
|
||||
expect(winningUser.notifications[1].type).to.equal('WON_CHALLENGE');
|
||||
});
|
||||
|
||||
it('gives winner gems as reward', async () => {
|
||||
|
||||
@@ -84,6 +84,10 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
await user.post(`/groups/${privateGroup._id}/invite`, {
|
||||
uuids: [anotherUser._id],
|
||||
});
|
||||
await anotherUser.post(`/groups/${privateGroup._id}/join`);
|
||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
|
||||
@@ -91,7 +95,7 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||
expect(flagResult.flagCount).to.equal(5);
|
||||
|
||||
let groupWithFlags = await user.get(`/groups/${privateGroup._id}`);
|
||||
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(messageToCheck).to.not.exist;
|
||||
@@ -125,4 +129,20 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a hidden message to the original poster', async () => {
|
||||
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||
|
||||
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||
|
||||
let groupWithFlags = await user.get(`/groups/${group._id}`);
|
||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(messageToCheck).to.exist;
|
||||
|
||||
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
|
||||
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
|
||||
|
||||
expect(auMessageToCheck).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,9 +70,9 @@ describe('POST /chat', () => {
|
||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
|
||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: 'Your chat privileges have been revoked.',
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
let guild;
|
||||
let member;
|
||||
let member2;
|
||||
let adminUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
|
||||
@@ -28,6 +29,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
invitedUser = invitees[0];
|
||||
member = members[0];
|
||||
member2 = members[1];
|
||||
adminUser = await generateUser({ 'contributor.admin': true });
|
||||
});
|
||||
|
||||
context('All Groups', () => {
|
||||
@@ -42,7 +44,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when user is a non-leader member of a group', async () => {
|
||||
it('returns an error when user is a non-leader member of a group and not an admin', async () => {
|
||||
expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
@@ -87,7 +89,30 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
let invitedUserWithoutInvite = await invitedUser.get('/user');
|
||||
|
||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
|
||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, { id: guild._id })).eql(-1);
|
||||
});
|
||||
|
||||
it('allows an admin to remove other members', async () => {
|
||||
await adminUser.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||
let memberRemoved = await member.get('/user');
|
||||
|
||||
expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1);
|
||||
});
|
||||
|
||||
it('allows an admin to remove other invites', async () => {
|
||||
await adminUser.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
|
||||
|
||||
let invitedUserWithoutInvite = await invitedUser.get('/user');
|
||||
|
||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, { id: guild._id })).eql(-1);
|
||||
});
|
||||
|
||||
it('does not allow an admin to remove a leader', async () => {
|
||||
expect(adminUser.post(`/groups/${guild._id}/removeMember/${leader._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('cannotRemoveCurrentLeader'),
|
||||
});
|
||||
});
|
||||
|
||||
it('sends email to user with rescinded invite', async () => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('PUT /group', () => {
|
||||
let leader, nonLeader, groupToUpdate;
|
||||
let leader, nonLeader, groupToUpdate, adminUser;
|
||||
let groupName = 'Test Public Guild';
|
||||
let groupType = 'guild';
|
||||
let groupUpdatedName = 'Test Public Guild Updated';
|
||||
@@ -18,13 +19,13 @@ describe('PUT /group', () => {
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
adminUser = await generateUser({ 'contributor.admin': true });
|
||||
groupToUpdate = group;
|
||||
leader = groupLeader;
|
||||
nonLeader = members[0];
|
||||
});
|
||||
|
||||
it('returns an error when a non group leader tries to update', async () => {
|
||||
it('returns an error when a user that is not an admin or group leader tries to update', async () => {
|
||||
await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
@@ -44,6 +45,15 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('allows an admin to update a guild', async () => {
|
||||
let updatedGroup = await adminUser.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
});
|
||||
expect(updatedGroup.leader._id).to.eql(leader._id);
|
||||
expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name);
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('allows a leader to change leaders', async () => {
|
||||
let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('GET /members/:toUserId/objections/:interaction', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('validates req.params.memberId', async () => {
|
||||
await expect(
|
||||
user.get('/members/invalidUUID/objections/send-private-message')
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('handles non-existing members', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(
|
||||
user.get(`/members/${dummyId}/objections/send-private-message`)
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: dummyId}),
|
||||
});
|
||||
});
|
||||
|
||||
it('handles non-existing interactions', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await expect(
|
||||
user.get(`/members/${receiver._id}/objections/hug-a-whole-forest-of-trees`)
|
||||
).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty array if there are no objections', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await expect(
|
||||
user.get(`/members/${receiver._id}/objections/send-private-message`)
|
||||
).to.eventually.be.fulfilled.and.eql([]);
|
||||
});
|
||||
|
||||
it('returns an array of objections if any exist', async () => {
|
||||
let receiver = await generateUser({'inbox.blocks': [user._id]});
|
||||
|
||||
await expect(
|
||||
user.get(`/members/${receiver._id}/objections/send-private-message`)
|
||||
).to.eventually.be.fulfilled.and.eql([
|
||||
t('notAuthorizedToSendMessageToThisUser'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -82,6 +82,20 @@ describe('POST /members/send-private-message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat privileges are revoked', async () => {
|
||||
let userWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||
let receiver = await generateUser();
|
||||
|
||||
await expect(userWithChatRevoked.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a private message to a user', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('POST /members/transfer-gems', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when to user is not found', async () => {
|
||||
it('returns error when recipient is not found', async () => {
|
||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
@@ -55,7 +55,7 @@ describe('POST /members/transfer-gems', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when to user attempts to send gems to themselves', async () => {
|
||||
it('returns error when user attempts to send gems to themselves', async () => {
|
||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
@@ -67,6 +67,64 @@ describe('POST /members/transfer-gems', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when recipient has blocked the sender', async () => {
|
||||
let receiverWhoBlocksUser = await generateUser({'inbox.blocks': [userToSendMessage._id]});
|
||||
|
||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
toUserId: receiverWhoBlocksUser._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when sender has blocked recipient', async () => {
|
||||
let sender = await generateUser({'inbox.blocks': [receiver._id]});
|
||||
|
||||
await expect(sender.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
toUserId: receiver._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat privileges are revoked', async () => {
|
||||
let userWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||
|
||||
await expect(userWithChatRevoked.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
toUserId: receiver._id,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
});
|
||||
|
||||
it('works when only the recipient\'s chat privileges are revoked', async () => {
|
||||
let receiverWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||
|
||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||
message,
|
||||
gemAmount,
|
||||
toUserId: receiverWithChatRevoked._id,
|
||||
})).to.eventually.be.fulfilled;
|
||||
|
||||
let updatedReceiver = await receiverWithChatRevoked.get('/user');
|
||||
let updatedSender = await userToSendMessage.get('/user');
|
||||
|
||||
expect(updatedReceiver.balance).to.equal(gemAmount / 4);
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
|
||||
it('returns error when there is no gemAmount', async () => {
|
||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||
message,
|
||||
@@ -144,7 +202,7 @@ describe('POST /members/transfer-gems', () => {
|
||||
expect(updatedSender.balance).to.equal(0);
|
||||
});
|
||||
|
||||
it('does not requrie a message', async () => {
|
||||
it('does not require a message', async () => {
|
||||
await userToSendMessage.post('/members/transfer-gems', {
|
||||
gemAmount,
|
||||
toUserId: receiver._id,
|
||||
|
||||
25
test/api/v3/integration/shops/GET-shops_backgrounds.test.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('GET /shops/backgrounds', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('returns a valid shop object', async () => {
|
||||
let shop = await user.get('/shops/backgrounds');
|
||||
expect(shop.identifier).to.equal('backgroundShop');
|
||||
expect(shop.text).to.eql(t('backgroundShop'));
|
||||
expect(shop.notes).to.eql(t('backgroundShopText'));
|
||||
expect(shop.imageName).to.equal('background_shop');
|
||||
expect(shop.sets).to.be.an('array');
|
||||
|
||||
let sets = shop.sets.map(set => set.identifier);
|
||||
expect(sets).to.include('incentiveBackgrounds');
|
||||
expect(sets).to.include('backgrounds062014');
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -127,4 +128,26 @@ describe('GET /tasks/user', () => {
|
||||
let allCompletedTodos = await user.get('/tasks/user?type=_allCompletedTodos');
|
||||
expect(allCompletedTodos.length).to.equal(numberOfTodos);
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified', async () => {
|
||||
let startDate = moment().subtract('1', 'days').toDate();
|
||||
let createdTasks = await user.post('/tasks/user', [
|
||||
{
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate,
|
||||
frequency: 'daily',
|
||||
everyX: 2,
|
||||
},
|
||||
]);
|
||||
let dailys = await user.get('/tasks/user?type=dailys');
|
||||
|
||||
expect(dailys.length).to.be.at.least(1);
|
||||
expect(dailys[0]._id).to.equal(createdTasks._id);
|
||||
expect(dailys[0].isDue).to.be.false;
|
||||
|
||||
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${startDate}`);
|
||||
expect(dailys2[0]._id).to.equal(createdTasks._id);
|
||||
expect(dailys2[0].isDue).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,6 +133,7 @@ describe('POST /tasks/user', () => {
|
||||
expect(task.completed).to.equal(false);
|
||||
expect(task.streak).not.to.equal('never');
|
||||
expect(task.value).not.to.equal(324);
|
||||
expect(task.yesterDaily).to.equal(true);
|
||||
});
|
||||
|
||||
it('ignores invalid fields', async () => {
|
||||
@@ -615,6 +616,18 @@ describe('POST /tasks/user', () => {
|
||||
expect((new Date(task.startDate)).getDay()).to.eql(today);
|
||||
});
|
||||
|
||||
it('returns an error if the start date is empty', async () => {
|
||||
await expect(user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'daily validation failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('can create checklists', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test daily',
|
||||
|
||||
@@ -396,6 +396,7 @@ describe('PUT /tasks/:id', () => {
|
||||
notes: 'some new notes',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
yesterDaily: false,
|
||||
startDate: moment().add(1, 'days').toDate(),
|
||||
});
|
||||
|
||||
@@ -405,6 +406,7 @@ describe('PUT /tasks/:id', () => {
|
||||
expect(savedDaily.everyX).to.eql(5);
|
||||
expect(savedDaily.isDue).to.be.false;
|
||||
expect(savedDaily.nextDue.length).to.eql(6);
|
||||
expect(savedDaily.yesterDaily).to.be.false;
|
||||
});
|
||||
|
||||
it('can update checklists (replace it)', async () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
sha1MakeSalt,
|
||||
sha1Encrypt as sha1EncryptPassword,
|
||||
} from '../../../../../website/server/libs/password';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
describe('DELETE /user', () => {
|
||||
let user;
|
||||
@@ -25,7 +26,7 @@ describe('DELETE /user', () => {
|
||||
user = await generateUser({balance: 10});
|
||||
});
|
||||
|
||||
it('returns an errors if password is wrong', async () => {
|
||||
it('returns an error if password is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
@@ -35,6 +36,33 @@ describe('DELETE /user', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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'});
|
||||
|
||||
@@ -96,6 +124,32 @@ describe('DELETE /user', () => {
|
||||
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();
|
||||
|
||||
@@ -221,6 +221,27 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
expect(syncedGroupTask.value).to.equal(0);
|
||||
});
|
||||
|
||||
it('increases both user\'s achievement values', async () => {
|
||||
let party = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
let leader = party.groupLeader;
|
||||
let recipient = party.members[0];
|
||||
await leader.update({'stats.gp': 10});
|
||||
await leader.post(`/user/class/cast/birthday?targetId=${recipient._id}`);
|
||||
await leader.sync();
|
||||
await recipient.sync();
|
||||
expect(leader.achievements.birthday).to.equal(1);
|
||||
expect(recipient.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('only increases user\'s achievement one if target == caster', async () => {
|
||||
await user.update({'stats.gp': 10});
|
||||
await user.post(`/user/class/cast/birthday?targetId=${user._id}`);
|
||||
await user.sync();
|
||||
expect(user.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
// TODO find a way to have sinon working in integration tests
|
||||
// it doesn't work when tests are running separately from server
|
||||
it('passes correct target to spell when targetType === \'task\'');
|
||||
|
||||
@@ -525,6 +525,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
@@ -555,6 +556,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
amzLib.closeBillingAgreement.restore();
|
||||
});
|
||||
@@ -593,6 +595,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(group.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
@@ -623,6 +626,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(group.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
amzLib.closeBillingAgreement.restore();
|
||||
});
|
||||
|
||||
@@ -79,6 +79,13 @@ describe('cron', () => {
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('resets plan.gemsBought on a new month if user does not have purchased.plan.dateUpdated', () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
user.purchased.plan.dateUpdated = undefined;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
@@ -476,6 +483,25 @@ describe('cron', () => {
|
||||
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
|
||||
it('should do damage for only yesterday\'s dailies', () => {
|
||||
daysMissed = 3;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
};
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[1].startDate = moment(new Date()).subtract({days: 2});
|
||||
tasksByType.dailys[1].everyX = 2;
|
||||
tasksByType.dailys[1].frequency = 'daily';
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.hp).to.equal(48);
|
||||
});
|
||||
});
|
||||
|
||||
describe('habits', () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('payments/index', () => {
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
await user.save();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
@@ -504,6 +505,18 @@ describe('payments/index', () => {
|
||||
expect(daysTillTermination).to.be.within(13, 15);
|
||||
});
|
||||
|
||||
it('terminates at next billing date even if dateUpdated is prior to now', async () => {
|
||||
data.nextBill = moment().add({ days: 15 });
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract({ days: 10 });
|
||||
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
let now = new Date();
|
||||
let daysTillTermination = moment(user.purchased.plan.dateTerminated).diff(now, 'days');
|
||||
|
||||
expect(daysTillTermination).to.be.within(13, 15);
|
||||
});
|
||||
|
||||
it('resets plan.extraMonths', async () => {
|
||||
user.purchased.plan.extraMonths = 5;
|
||||
|
||||
@@ -653,5 +666,32 @@ describe('payments/index', () => {
|
||||
|
||||
expect(updatedUser.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
});
|
||||
|
||||
it('saves previously unused Mystery Items and Hourglasses for an expired subscription', async () => {
|
||||
let planExpirationDate = new Date();
|
||||
planExpirationDate.setDate(planExpirationDate.getDate() - 2);
|
||||
let mysteryItem = 'item';
|
||||
let mysteryItems = [mysteryItem];
|
||||
let consecutive = {
|
||||
trinkets: 3,
|
||||
};
|
||||
|
||||
// set expired plan with unused items
|
||||
plan.mysteryItems = mysteryItems;
|
||||
plan.consecutive = consecutive;
|
||||
plan.dateCreated = planExpirationDate;
|
||||
plan.dateTerminated = planExpirationDate;
|
||||
plan.customerId = null;
|
||||
|
||||
user.purchased.plan = plan;
|
||||
|
||||
await user.save();
|
||||
await api.addSubToGroupUser(user, group);
|
||||
|
||||
let updatedUser = await User.findById(user._id).exec();
|
||||
|
||||
expect(updatedUser.purchased.plan.mysteryItems[0]).to.eql(mysteryItem);
|
||||
expect(updatedUser.purchased.plan.consecutive.trinkets).to.equal(consecutive.trinkets);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ describe('Canceling a subscription for group', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('prevents non group leader from manging subscription', async () => {
|
||||
it('prevents non group leader from managing subscription', async () => {
|
||||
let groupMember = new User();
|
||||
data.user = groupMember;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import moment from 'moment';
|
||||
import stripeModule from 'stripe';
|
||||
import nconf from 'nconf';
|
||||
|
||||
import * as sender from '../../../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../../../website/server/libs/payments';
|
||||
@@ -12,17 +13,24 @@ import {
|
||||
generateGroup,
|
||||
} from '../../../../../../helpers/api-unit.helper.js';
|
||||
|
||||
describe('Purchasing a subscription for group', () => {
|
||||
describe('Purchasing a group plan for group', () => {
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS = 'iOS_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL = 'normal_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE = 'no_subscription';
|
||||
|
||||
let plan, group, user, data;
|
||||
let stripe = stripeModule('test');
|
||||
let groupLeaderName = 'sender';
|
||||
let groupName = 'test group';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.profile.name = groupLeaderName;
|
||||
await user.save();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
@@ -81,7 +89,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
sender.sendTxn.restore();
|
||||
});
|
||||
|
||||
it('creates a subscription', async () => {
|
||||
it('creates a group plan', async () => {
|
||||
expect(group.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -157,7 +165,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
expect(updatedLeader.items.mounts['Jackalope-RoyalPurple']).to.be.true;
|
||||
});
|
||||
|
||||
it('sends an email to members of group', async () => {
|
||||
it('sends an email to member of group who was not a subscriber', async () => {
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.guilds.push(group._id);
|
||||
@@ -169,11 +177,181 @@ describe('Purchasing a subscription for group', () => {
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-joining');
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE},
|
||||
]);
|
||||
// confirm that the other email sent is appropriate:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => {
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.key = 'basic_earned';
|
||||
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||
.returnsPromise()
|
||||
.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Closed'},
|
||||
},
|
||||
});
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.planId = 'basic_earned';
|
||||
plan.paymentMethod = amzLib.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
amzLib.getBillingAgreementDetails.restore();
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||
.returnsPromise().resolves({
|
||||
agreement_details: { // eslint-disable-line camelcase
|
||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||
cycles_completed: 1, // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.planId = 'basic_earned';
|
||||
plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
paypalPayments.paypalBillingAgreementGet.restore();
|
||||
paypalPayments.paypalBillingAgreementCancel.restore();
|
||||
});
|
||||
|
||||
it('sends appropriate emails when subscribed member of group must manually cancel recurring Android subscription', async () => {
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledFourTimes;
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
|
||||
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[1][2]).to.eql([
|
||||
{name: 'LEADER', content: groupLeaderName},
|
||||
{name: 'GROUP_NAME', content: groupName},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE},
|
||||
]);
|
||||
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends appropriate emails when subscribed member of group must manually cancel recurring iOS subscription', async () => {
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD;
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledFourTimes;
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
|
||||
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[1][2]).to.eql([
|
||||
{name: 'LEADER', content: groupLeaderName},
|
||||
{name: 'GROUP_NAME', content: groupName},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS},
|
||||
]);
|
||||
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('adds months to members with existing gift subscription', async () => {
|
||||
@@ -333,7 +511,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription (Android)');
|
||||
it('adds months to members with existing recurring subscription (iOs)');
|
||||
it('adds months to members with existing recurring subscription (iOS)');
|
||||
|
||||
it('adds months to members who already cancelled but not yet terminated recurring subscription', async () => {
|
||||
let recipient = new User();
|
||||
@@ -418,7 +596,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
|
||||
let updatedUser = await User.findById(recipient._id).exec();
|
||||
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 8);
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9);
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => {
|
||||
@@ -603,7 +781,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
expect(updatedUser.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('does not modify a user with a Google subscription', async () => {
|
||||
it('does not modify a user with an Android subscription', async () => {
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
|
||||
|
||||
|
||||
@@ -447,6 +447,7 @@ describe('Paypal Payments', () => {
|
||||
groupId,
|
||||
paymentMethod: 'Paypal',
|
||||
nextBill: nextBillingDate,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -464,6 +465,7 @@ describe('Paypal Payments', () => {
|
||||
groupId: group._id,
|
||||
paymentMethod: 'Paypal',
|
||||
nextBill: nextBillingDate,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -683,6 +683,7 @@ describe('Stripe Payments', () => {
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -702,6 +703,7 @@ describe('Stripe Payments', () => {
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -228,7 +228,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('applies damage only to participating members of party even under buggy conditions', async () => {
|
||||
// stops unfair damage from mbugs like https://github.com/HabitRPG/habitrpg/issues/7653
|
||||
// stops unfair damage from mbugs like https://github.com/HabitRPG/habitica/issues/7653
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
|
||||
@@ -112,6 +112,41 @@ describe('Groups Controller', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAbleToEditGroup', () => {
|
||||
var guild;
|
||||
|
||||
beforeEach(() => {
|
||||
user.contributor = {};
|
||||
guild = specHelper.newGroup({
|
||||
_id: 'unique-guild-id',
|
||||
type: 'guild',
|
||||
members: ['not-user-id'],
|
||||
$save: sandbox.spy(),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true if user is an admin', () => {
|
||||
guild.leader = 'not-user-id';
|
||||
user.contributor.admin = true;
|
||||
expect(scope.isAbleToEditGroup(guild)).to.be.ok;
|
||||
});
|
||||
|
||||
it('returns true if user is group leader', () => {
|
||||
guild.leader = {_id: user._id}
|
||||
expect(scope.isAbleToEditGroup(guild)).to.be.ok;
|
||||
});
|
||||
|
||||
it('returns false is user is not a leader or admin', () => {
|
||||
expect(scope.isAbleToEditGroup(guild)).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('returns false is user is an admin but group is a party', () => {
|
||||
guild.type = 'party';
|
||||
user.contributor.admin = true;
|
||||
expect(scope.isAbleToEditGroup(guild)).to.not.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe('editGroup', () => {
|
||||
var guild;
|
||||
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('closeMenu Directive', function() {
|
||||
var scope;
|
||||
|
||||
beforeEach(module('habitrpg'));
|
||||
|
||||
beforeEach(inject(function($rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.$digest();
|
||||
}));
|
||||
|
||||
it('closes a connected menu when element is clicked', inject(function($compile) {
|
||||
var menuElement = $compile('<a data-close-menu menu="mobile">')(scope);
|
||||
scope._expandedMenu = { menu: 'mobile' };
|
||||
|
||||
menuElement.appendTo(document.body);
|
||||
menuElement.triggerHandler('click');
|
||||
|
||||
expect(scope._expandedMenu.menu).to.eql(null)
|
||||
}));
|
||||
|
||||
it('closes a connected menu when child element is clicked', inject(function($compile) {
|
||||
var menuElementWithChild = $compile('<li></li>')(scope);
|
||||
var menuElementChild = $compile('<a data-close-menu></a>')(scope);
|
||||
scope._expandedMenu = { menu: 'mobile' };
|
||||
|
||||
menuElementWithChild.appendTo(document.body);
|
||||
menuElementChild.appendTo(menuElementWithChild);
|
||||
menuElementChild.triggerHandler('click');
|
||||
|
||||
expect(scope._expandedMenu.menu).to.eql(null)
|
||||
}));
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('expandMenu Directive', function() {
|
||||
var menuElement, scope;
|
||||
|
||||
beforeEach(module('habitrpg'));
|
||||
|
||||
beforeEach(inject(function($rootScope, $compile) {
|
||||
scope = $rootScope.$new();
|
||||
|
||||
var element = '<a data-expand-menu menu="mobile"></a>';
|
||||
|
||||
menuElement = $compile(element)(scope);
|
||||
scope.$digest();
|
||||
}));
|
||||
|
||||
it('expands a connected menu when element is clicked', function() {
|
||||
expect(scope._expandedMenu).to.not.exist;
|
||||
menuElement.appendTo(document.body);
|
||||
|
||||
menuElement.triggerHandler('click');
|
||||
|
||||
expect(scope._expandedMenu.menu).to.eql('mobile')
|
||||
});
|
||||
|
||||
it('closes a connected menu when it is already open', function() {
|
||||
scope._expandedMenu = {};
|
||||
scope._expandedMenu.menu = 'mobile';
|
||||
menuElement.appendTo(document.body);
|
||||
|
||||
menuElement.triggerHandler('click');
|
||||
|
||||
expect(scope._expandedMenu.menu).to.eql(null)
|
||||
});
|
||||
});
|
||||
20
test/client/unit/specs/filters/roundBigNumber.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import roundBigNumberFilter from 'client/filters/roundBigNumber';
|
||||
|
||||
describe('round big number filter', () => {
|
||||
it('can round a decimal number', () => {
|
||||
expect(roundBigNumberFilter(4.567)).to.equal(4.57);
|
||||
expect(roundBigNumberFilter(4.562)).to.equal(4.56);
|
||||
});
|
||||
|
||||
it('can round thousands', () => {
|
||||
expect(roundBigNumberFilter(70065)).to.equal('70.1k');
|
||||
});
|
||||
|
||||
it('can round milions', () => {
|
||||
expect(roundBigNumberFilter(10000987)).to.equal('10.0m');
|
||||
});
|
||||
|
||||
it('can round bilions', () => {
|
||||
expect(roundBigNumberFilter(1000000000)).to.equal('1.0b');
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ describe('tasks actions', () => {
|
||||
});
|
||||
|
||||
describe('fetchUserTasks', () => {
|
||||
it('fetches user tasks', async () => {
|
||||
xit('fetches user tasks', async () => {
|
||||
expect(store.state.tasks.loadingStatus).to.equal('NOT_LOADED');
|
||||
const tasks = [{_id: 1}];
|
||||
sandbox.stub(axios, 'get').withArgs('/api/v3/tasks/user').returns(Promise.resolve({data: {data: tasks}}));
|
||||
@@ -36,7 +36,7 @@ describe('tasks actions', () => {
|
||||
expect(store.state.tasks.loadingStatus).to.equal('LOADED');
|
||||
});
|
||||
|
||||
it('can reload tasks if forceLoad is true', async () => {
|
||||
xit('can reload tasks if forceLoad is true', async () => {
|
||||
store.state.tasks = {
|
||||
loadingStatus: 'LOADED',
|
||||
data: [{_id: 1}],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('tasks actions', () => {
|
||||
describe('user actions', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -121,6 +121,17 @@ describe('achievements', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = basicAchievs[`${card}Cards`];
|
||||
|
||||
expect(cardAchiev).to.exist;
|
||||
expect(cardAchiev).to.have.property('optionalCount')
|
||||
.that.is.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
it('rebirth achievement exists with no count', () => {
|
||||
let rebirth = basicAchievs.rebirth;
|
||||
|
||||
@@ -174,7 +185,7 @@ describe('achievements', () => {
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
||||
let cardTypes = ['nye', 'valentine'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = seasonalAchievs[`${card}Cards`];
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ describe('shops', () => {
|
||||
expect(shopCategories.length).to.be.greaterThan(2);
|
||||
});
|
||||
|
||||
it('does not contain an empty category', () => {
|
||||
_.each(shopCategories, (category) => {
|
||||
expect(category.items.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not duplicate identifiers', () => {
|
||||
let identifiers = Array.from(new Set(shopCategories.map(cat => cat.identifier)));
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'moment-recur';
|
||||
describe('shouldDo', () => {
|
||||
let day, dailyTask;
|
||||
let options = {};
|
||||
let nextDue = [];
|
||||
|
||||
beforeEach(() => {
|
||||
day = new Date();
|
||||
@@ -223,6 +224,25 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute daily nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'daily';
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-03').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-07').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-09').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-11').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-13').toDate());
|
||||
});
|
||||
|
||||
context('On multiples of x', () => {
|
||||
it('returns true when Custom Day Start is midnight', () => {
|
||||
dailyTask.startDate = moment().subtract(7, 'days').toDate();
|
||||
@@ -367,6 +387,73 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute weekly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'weekly';
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: true,
|
||||
m: true,
|
||||
t: true,
|
||||
w: true,
|
||||
th: true,
|
||||
f: true,
|
||||
s: true,
|
||||
};
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-02').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-03').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-04').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-05-06').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-05-07').toDate());
|
||||
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.repeat = {
|
||||
su: true,
|
||||
m: false,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: true,
|
||||
s: false,
|
||||
};
|
||||
dailyTask.startDate = day;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-05-05').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-05-14').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-05-19').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-05-28').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-06-02').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-06-11').toDate());
|
||||
});
|
||||
|
||||
it('should not go into an infinite loop with invalid values', () => {
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
dailyTask.frequency = 'weekly';
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.startDate = null;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue).to.eql(false);
|
||||
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 0;
|
||||
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue).to.eql(false);
|
||||
});
|
||||
|
||||
context('Day of the week matches', () => {
|
||||
const weekdayMap = {
|
||||
1: 'm',
|
||||
@@ -626,8 +713,9 @@ describe('shouldDo', () => {
|
||||
it('leaves daily inactive if not day of the month', () => {
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.frequency = 'monthly';
|
||||
dailyTask.daysOfMonth = [15];
|
||||
let tomorrow = moment().add(1, 'day').toDate();// @TODO: make sure this is not the 15
|
||||
let today = moment();
|
||||
dailyTask.daysOfMonth = [today.date()];
|
||||
let tomorrow = today.add(1, 'day').toDate();
|
||||
|
||||
expect(shouldDo(tomorrow, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
@@ -645,8 +733,9 @@ describe('shouldDo', () => {
|
||||
it('leaves daily inactive if not on date of the x month', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
dailyTask.daysOfMonth = [15];
|
||||
let tomorrow = moment().add(2, 'months').add(1, 'day').toDate();
|
||||
let today = moment();
|
||||
dailyTask.daysOfMonth = [today.date()];
|
||||
let tomorrow = today.add(2, 'months').add(1, 'day').toDate();
|
||||
|
||||
expect(shouldDo(tomorrow, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
@@ -667,6 +756,96 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute monthly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'monthly';
|
||||
dailyTask.everyX = 3;
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.daysOfMonth = [1];
|
||||
dailyTask.weeksOfMonth = [];
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-08-01').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-11-01').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-02-01').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-05-01').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-08-01').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-11-01').toDate());
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [0];
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-05').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-03').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-07').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-04').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-02').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-06').toDate());
|
||||
|
||||
day = moment('2017-05-08').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [1];
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-06-12').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-07-10').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2017-08-14').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2017-09-11').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2017-10-09').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2017-11-13').toDate());
|
||||
|
||||
day = moment('2017-05-29').toDate();
|
||||
|
||||
dailyTask.daysOfMonth = [];
|
||||
dailyTask.weeksOfMonth = [4];
|
||||
dailyTask.startDate = day;
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
m: true,
|
||||
t: false,
|
||||
w: false,
|
||||
th: false,
|
||||
f: false,
|
||||
s: false,
|
||||
};
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2017-07-31').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2017-10-30').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2018-01-29').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2018-04-30').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2018-07-30').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2018-10-29').toDate());
|
||||
});
|
||||
|
||||
context('Custom Day Start is 0 <= n < 24', () => {
|
||||
beforeEach(() => {
|
||||
options.dayStart = 7;
|
||||
@@ -734,6 +913,28 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns false when next due is requested and no repeats are available', () => {
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
s: false,
|
||||
f: false,
|
||||
th: false,
|
||||
w: false,
|
||||
t: false,
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-05-27T17:34:40.000Z');
|
||||
let week = today.monthWeek();
|
||||
dailyTask.startDate = today.toDate();
|
||||
dailyTask.weeksOfMonth = [week];
|
||||
dailyTask.everyX = 1;
|
||||
dailyTask.frequency = 'monthly';
|
||||
day = moment('2017-02-23');
|
||||
options.nextDue = true;
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
|
||||
it('activates Daily if correct week of the month on the day of the start date', () => {
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
@@ -918,6 +1119,25 @@ describe('shouldDo', () => {
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should compute yearly nextDue values', () => {
|
||||
options.timezoneOffset = 0;
|
||||
options.nextDue = true;
|
||||
|
||||
day = moment('2017-05-01').toDate();
|
||||
|
||||
dailyTask.frequency = 'yearly';
|
||||
dailyTask.everyX = 5;
|
||||
dailyTask.startDate = day;
|
||||
nextDue = shouldDo(day, dailyTask, options);
|
||||
expect(nextDue.length).to.eql(6);
|
||||
expect(moment(nextDue[0]).toDate()).to.eql(moment.utc('2022-05-01').toDate());
|
||||
expect(moment(nextDue[1]).toDate()).to.eql(moment.utc('2027-05-01').toDate());
|
||||
expect(moment(nextDue[2]).toDate()).to.eql(moment.utc('2032-05-01').toDate());
|
||||
expect(moment(nextDue[3]).toDate()).to.eql(moment.utc('2037-05-01').toDate());
|
||||
expect(moment(nextDue[4]).toDate()).to.eql(moment.utc('2042-05-01').toDate());
|
||||
expect(moment(nextDue[5]).toDate()).to.eql(moment.utc('2047-05-01').toDate());
|
||||
});
|
||||
|
||||
context('Custom Day Start is 0 <= n < 24', () => {
|
||||
beforeEach(() => {
|
||||
options.dayStart = 7;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Babel Paths for Production Environment
|
||||
|
||||
In development, we [transpile at server start](https://github.com/HabitRPG/habitrpg/blob/1ed7e21542519abe7a3c601f396e1a07f9b050ae/website/server/index.js#L6-L8). This allows us to work quickly while developing, but is not suitable for production. So, in production we transpile the server code before the app starts.
|
||||
In development, we [transpile at server start](https://github.com/HabitRPG/habitica/blob/1ed7e21542519abe7a3c601f396e1a07f9b050ae/website/server/index.js#L6-L8). This allows us to work quickly while developing, but is not suitable for production. So, in production we transpile the server code before the app starts.
|
||||
|
||||
This system means that requiring any files from `website/common/script` in `website/server/**/*.js` must be done through the `website/common/index.js` module. In development, it'll pass through to the pre-transpiled files, but in production it'll point to the transpiled versions. If you try to require or import a file directly, it will error in production as the server doesn't know what to do with some es2015isms (such as the import statement).
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = {
|
||||
index: path.resolve(__dirname, '../../dist-client/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../../dist-client'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/new-app',
|
||||
assetsPublicPath: '/new-app/',
|
||||
staticAssetsDirectory,
|
||||
productionSourceMap: true,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
|
||||
@@ -83,7 +83,7 @@ const baseConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
test: /\.(png|jpe?g|gif)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
@@ -98,6 +98,22 @@ const baseConfig = {
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]'),
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{ loader: 'svg-inline-loader' },
|
||||
{ loader: 'svgo-loader' },
|
||||
],
|
||||
exclude: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{ loader: 'svg-url-loader' },
|
||||
{ loader: 'svgo-loader' },
|
||||
],
|
||||
include: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
BIN
website/assets/audio/arashiTheme/Achievement_Unlocked.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Achievement_Unlocked.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Chat.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Chat.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Daily.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Daily.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Death.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Death.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Item_Drop.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Item_Drop.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Level_Up.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Level_Up.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Minus_Habit.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Minus_Habit.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Plus_Habit.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Plus_Habit.ogg
Normal file
BIN
website/assets/audio/arashiTheme/Reward.mp3
Normal file
BIN
website/assets/audio/arashiTheme/Reward.ogg
Normal file
BIN
website/assets/audio/arashiTheme/ToDo.mp3
Normal file
BIN
website/assets/audio/arashiTheme/ToDo.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Achievement_Unlocked.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Achievement_Unlocked.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Chat.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Chat.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Daily.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Daily.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Death.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Death.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Item_Drop.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Item_Drop.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Minus_Habit.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Minus_Habit.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Plus_Habit.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Plus_Habit.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Reward.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/Reward.ogg
Normal file
BIN
website/assets/audio/beatscribeNesTheme/ToDo.mp3
Normal file
BIN
website/assets/audio/beatscribeNesTheme/ToDo.ogg
Normal file
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
@@ -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;
|
||||
@@ -61,6 +61,10 @@
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.achievement-container {
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
[class*="Mount_Head_"],
|
||||
[class*="Mount_Body_"] {
|
||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
.promo_android {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1121px;
|
||||
background-position: -1713px -356px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201602 {
|
||||
.promo_aquatic_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -987px -1042px;
|
||||
background-position: -840px -148px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201603 {
|
||||
.promo_backgrounds_armoire_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -845px -1042px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -987px -1042px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px 0px;
|
||||
background-position: -1292px 0px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px -442px;
|
||||
background-position: -282px -1042px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -42,61 +48,73 @@
|
||||
}
|
||||
.promo_backgrounds_armoire_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -852px -600px;
|
||||
background-position: -564px -1042px;
|
||||
width: 140px;
|
||||
height: 439px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -993px -600px;
|
||||
background-position: -705px -1042px;
|
||||
width: 139px;
|
||||
height: 438px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px 0px;
|
||||
background-position: -1433px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px -442px;
|
||||
background-position: -1292px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1042px;
|
||||
background-position: -423px -1042px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px 0px;
|
||||
background-position: -1150px -441px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -426px -600px;
|
||||
background-position: -568px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -284px -600px;
|
||||
background-position: -710px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -568px -600px;
|
||||
background-position: -852px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -142px -600px;
|
||||
background-position: -994px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -426px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201707 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -284px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -108,7 +126,7 @@
|
||||
}
|
||||
.promo_bundle_feathered {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -982px -148px;
|
||||
background-position: -142px -600px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
@@ -120,163 +138,169 @@
|
||||
}
|
||||
.promo_chairs_glasses {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1856px 0px;
|
||||
background-position: -1819px -532px;
|
||||
width: 51px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_checkin_incentives {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -703px -1042px;
|
||||
background-position: -1129px -1042px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_classes_fall_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -703px -1337px;
|
||||
background-position: -277px -1484px;
|
||||
width: 321px;
|
||||
height: 100px;
|
||||
}
|
||||
.promo_classes_fall_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1129px -1190px;
|
||||
background-position: -845px -1337px;
|
||||
width: 377px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_classes_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -592px;
|
||||
background-position: -1574px -442px;
|
||||
width: 103px;
|
||||
height: 348px;
|
||||
}
|
||||
.promo_coffee_mug {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -941px;
|
||||
background-position: -1713px 0px;
|
||||
width: 200px;
|
||||
height: 179px;
|
||||
}
|
||||
.promo_contrib_spotlight_Keith {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -287px -1575px;
|
||||
background-position: -1803px -1477px;
|
||||
width: 87px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_alys {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1804px -1365px;
|
||||
width: 90px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_contrib_spotlight_beffymaroo {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1677px -768px;
|
||||
background-position: -1713px -806px;
|
||||
width: 114px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_contrib_spotlight_blade {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -197px -1575px;
|
||||
background-position: -1713px -1477px;
|
||||
width: 89px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_cantras {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1758px;
|
||||
background-position: -1574px -900px;
|
||||
width: 87px;
|
||||
height: 109px;
|
||||
}
|
||||
.promo_contrib_spotlight_dewines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -375px -1575px;
|
||||
background-position: -1574px -791px;
|
||||
width: 89px;
|
||||
height: 108px;
|
||||
}
|
||||
.promo_contrib_spotlight_megan {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -106px -1575px;
|
||||
background-position: -1713px -1365px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_shanaqui {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1412px -1042px;
|
||||
background-position: -1814px -1045px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_cow {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -141px -1042px;
|
||||
background-position: 0px -1042px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_cupid_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -564px -1042px;
|
||||
background-position: -1574px 0px;
|
||||
width: 138px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_dilatoryDistress {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1648px -1484px;
|
||||
background-position: -370px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_egg_mounts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -148px;
|
||||
background-position: -1271px -1042px;
|
||||
width: 280px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_enchanted_armoire {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1025px -1337px;
|
||||
background-position: -599px -1484px;
|
||||
width: 374px;
|
||||
height: 76px;
|
||||
}
|
||||
.promo_enchanted_armoire_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -106px -1484px;
|
||||
background-position: -1223px -1337px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -542px -1484px;
|
||||
background-position: -1713px -954px;
|
||||
width: 180px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1557px -1575px;
|
||||
background-position: -916px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px -884px;
|
||||
background-position: -1713px -1183px;
|
||||
width: 122px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1375px -1575px;
|
||||
background-position: -734px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_fairy_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -840px -148px;
|
||||
background-position: -982px -148px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_floral_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1484px;
|
||||
background-position: -1713px -532px;
|
||||
width: 105px;
|
||||
height: 273px;
|
||||
}
|
||||
.promo_ghost_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -423px -1042px;
|
||||
background-position: -141px -1042px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_habitica {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1677px -592px;
|
||||
background-position: -1713px -180px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
@@ -288,31 +312,31 @@
|
||||
}
|
||||
.promo_habitoween_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -282px -1042px;
|
||||
background-position: -1433px 0px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_haunted_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1749px -1121px;
|
||||
background-position: -1713px -1045px;
|
||||
width: 100px;
|
||||
height: 137px;
|
||||
}
|
||||
.promo_holly_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -710px -600px;
|
||||
background-position: -1150px 0px;
|
||||
width: 141px;
|
||||
height: 440px;
|
||||
}
|
||||
.promo_item_notif {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1297px;
|
||||
background-position: -1271px -1190px;
|
||||
width: 249px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_jackalope {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -444px;
|
||||
background-position: 0px -1484px;
|
||||
width: 276px;
|
||||
height: 147px;
|
||||
}
|
||||
@@ -324,187 +348,187 @@
|
||||
}
|
||||
.promo_mystery_201405 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1193px -1484px;
|
||||
background-position: -1189px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201406 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -829px -1575px;
|
||||
background-position: -1433px -884px;
|
||||
width: 90px;
|
||||
height: 96px;
|
||||
}
|
||||
.promo_mystery_201407 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1856px -412px;
|
||||
background-position: -1871px -669px;
|
||||
width: 42px;
|
||||
height: 62px;
|
||||
}
|
||||
.promo_mystery_201408 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1400px;
|
||||
background-position: -974px -1484px;
|
||||
width: 60px;
|
||||
height: 71px;
|
||||
}
|
||||
.promo_mystery_201409 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1648px -1575px;
|
||||
background-position: -1462px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201410 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1774px -1023px;
|
||||
background-position: -1834px -1274px;
|
||||
width: 72px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201411 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1375px -1484px;
|
||||
background-position: -643px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201412 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1856px -345px;
|
||||
background-position: -1871px -602px;
|
||||
width: 42px;
|
||||
height: 66px;
|
||||
}
|
||||
.promo_mystery_201501 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1856px -211px;
|
||||
background-position: -1664px -791px;
|
||||
width: 48px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201502 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1011px -1575px;
|
||||
background-position: -188px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201503 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1193px -1575px;
|
||||
background-position: -279px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201504 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1634px -1400px;
|
||||
background-position: -1035px -1484px;
|
||||
width: 60px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201505 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1466px -1575px;
|
||||
background-position: -461px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201506 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1856px -275px;
|
||||
background-position: -1871px -532px;
|
||||
width: 42px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -647px -1575px;
|
||||
background-position: -1574px -1222px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -723px -1484px;
|
||||
background-position: 0px -1632px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1284px -1484px;
|
||||
background-position: -825px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201510 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -817px -1484px;
|
||||
background-position: -1292px -884px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1466px -1484px;
|
||||
background-position: -1007px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201512 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1774px -941px;
|
||||
background-position: -1836px -1183px;
|
||||
width: 60px;
|
||||
height: 81px;
|
||||
}
|
||||
.promo_mystery_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px -884px;
|
||||
background-position: -1713px -1274px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1739px -1484px;
|
||||
background-position: -1280px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -920px -1575px;
|
||||
background-position: -1371px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -911px -1484px;
|
||||
background-position: -94px -1632px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1102px -1575px;
|
||||
background-position: -1098px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201606 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -556px -1575px;
|
||||
background-position: -1574px -1010px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201607 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1284px -1575px;
|
||||
background-position: -552px -1632px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1005px -1484px;
|
||||
background-position: -1441px -1337px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1099px -1484px;
|
||||
background-position: -1150px -883px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1792px -768px;
|
||||
background-position: -1828px -806px;
|
||||
width: 63px;
|
||||
height: 84px;
|
||||
}
|
||||
.promo_mystery_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -738px -1575px;
|
||||
background-position: -1574px -1328px;
|
||||
width: 90px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -516,49 +540,7 @@
|
||||
}
|
||||
.promo_mystery_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -465px -1575px;
|
||||
background-position: -1574px -1116px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -296px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1129px -1042px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1557px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px 0px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_3014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -324px -1484px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_new_hair_fall2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_orca {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px -884px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 934 KiB |
@@ -1,384 +1,456 @@
|
||||
.promo_mystery_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1538px -890px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1538px -299px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -833px -447px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1538px -447px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -796px -1695px;
|
||||
width: 111px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_3014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -578px -1695px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_new_hair_fall2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -565px -969px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_orca {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1254px -589px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_partyhats {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -1401px;
|
||||
background-position: -1679px -819px;
|
||||
width: 115px;
|
||||
height: 47px;
|
||||
}
|
||||
.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1126px -1274px;
|
||||
background-position: -793px -1411px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1151px -1289px;
|
||||
background-position: -818px -1426px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -401px -948px;
|
||||
background-position: -1107px -969px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -426px -963px;
|
||||
background-position: -1132px -984px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_peppermint_flame {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -601px;
|
||||
background-position: -1848px -755px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -453px;
|
||||
background-position: -1848px -903px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1760px -468px;
|
||||
background-position: -1873px -918px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pyromancer {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1340px;
|
||||
background-position: -1848px -1642px;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
}
|
||||
.promo_rainbow_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1635px -392px;
|
||||
background-position: -833px -343px;
|
||||
width: 92px;
|
||||
height: 103px;
|
||||
}
|
||||
.promo_seafoam {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seasonal_shop_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -608px -1099px;
|
||||
background-position: -1538px -1038px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_shimmer_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1455px;
|
||||
background-position: -1067px -1316px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.promo_shimmer_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -884px -277px;
|
||||
background-position: 0px -969px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_shinySeeds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1026px -277px;
|
||||
background-position: -1396px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -782px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -25px -797px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -1309px;
|
||||
background-position: 0px -1786px;
|
||||
width: 198px;
|
||||
height: 91px;
|
||||
}
|
||||
.customize-option.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1519px -1324px;
|
||||
background-position: -25px -1801px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_spooky_sparkles_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -392px;
|
||||
background-position: -1538px -595px;
|
||||
width: 140px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_spring_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -830px -728px;
|
||||
background-position: -430px -1411px;
|
||||
width: 362px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_spring_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1066px -948px;
|
||||
background-position: -1538px 0px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_springclasses2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -289px -1628px;
|
||||
background-position: 0px -1695px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_springclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1628px;
|
||||
background-position: -289px -1695px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_staff_spotlight_Lemoness {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1045px;
|
||||
background-position: -1848px -1347px;
|
||||
width: 102px;
|
||||
height: 146px;
|
||||
}
|
||||
.promo_staff_spotlight_Viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -897px;
|
||||
background-position: -1848px -1199px;
|
||||
width: 119px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_staff_spotlight_paglias {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1192px;
|
||||
background-position: -1848px -1494px;
|
||||
width: 99px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_steampunk_3017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1212px 0px;
|
||||
background-position: -283px -969px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_summer_classes_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -340px -605px;
|
||||
background-position: 0px -1411px;
|
||||
width: 429px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_summer_classes_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1539px;
|
||||
background-position: -534px -1514px;
|
||||
width: 300px;
|
||||
height: 88px;
|
||||
}
|
||||
.promo_summer_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -948px;
|
||||
background-position: -706px -969px;
|
||||
width: 400px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_summer_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1254px 0px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_takeThis_gear {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1618px;
|
||||
background-position: -835px -1514px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_takethis_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1530px;
|
||||
background-position: -950px -1514px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_task_planning {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px 0px;
|
||||
background-position: -706px -1120px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.promo_turkey_day_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1212px -442px;
|
||||
background-position: -424px -969px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1635px -571px;
|
||||
background-position: -1761px -1261px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_unconventional_armor2 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1635px -496px;
|
||||
background-position: -1761px -1186px;
|
||||
width: 70px;
|
||||
height: 74px;
|
||||
}
|
||||
.promo_updos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -859px;
|
||||
background-position: -1679px -595px;
|
||||
width: 156px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -1454px;
|
||||
background-position: -1124px -1411px;
|
||||
width: 146px;
|
||||
height: 75px;
|
||||
}
|
||||
.promo_winter_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -765px -1274px;
|
||||
background-position: -706px -1316px;
|
||||
width: 360px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_winter_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -397px -728px;
|
||||
background-position: -376px -782px;
|
||||
width: 432px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_winter_fireworks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -749px;
|
||||
background-position: -1848px -1051px;
|
||||
width: 138px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winterclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -888px -1099px;
|
||||
background-position: -208px -1514px;
|
||||
width: 325px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1353px 0px;
|
||||
background-position: -142px -969px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.customize-option.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1378px -15px;
|
||||
background-position: -167px -984px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_winteryhair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1214px -1099px;
|
||||
background-position: -1679px -743px;
|
||||
width: 152px;
|
||||
height: 75px;
|
||||
}
|
||||
.avatar_variety {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -385px -250px;
|
||||
background-position: -809px -782px;
|
||||
width: 498px;
|
||||
height: 95px;
|
||||
}
|
||||
.npc_viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1353px -442px;
|
||||
background-position: -908px -1695px;
|
||||
width: 108px;
|
||||
height: 90px;
|
||||
}
|
||||
.party_preview {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -340px -385px;
|
||||
background-position: 0px -562px;
|
||||
width: 451px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_backtoschool {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px 0px;
|
||||
background-position: -1848px -604px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_cooking {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -728px;
|
||||
background-position: -452px -562px;
|
||||
width: 396px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_startingover {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -151px;
|
||||
background-position: -1848px -151px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_valentines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -756px -948px;
|
||||
background-position: -1188px -1120px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_working_out {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -307px -1099px;
|
||||
background-position: -1538px -148px;
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_arts_crafts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px 0px;
|
||||
width: 384px;
|
||||
height: 384px;
|
||||
background-position: -926px -277px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
.scene_buying_rewards {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1514px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_coding {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -1007px;
|
||||
background-position: -1848px -302px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_dailies {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -884px 0px;
|
||||
background-position: -926px 0px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
.scene_eco_friendly {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -687px;
|
||||
background-position: -1538px -1186px;
|
||||
width: 222px;
|
||||
height: 171px;
|
||||
}
|
||||
.scene_guilds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -385px 0px;
|
||||
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: 0px -1099px;
|
||||
background-position: -926px -534px;
|
||||
width: 306px;
|
||||
height: 174px;
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -1158px;
|
||||
background-position: -1848px 0px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_phone_peek {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1735px -302px;
|
||||
background-position: -1848px -453px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_raking_leaves {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -586px -343px;
|
||||
width: 246px;
|
||||
height: 198px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1494px -196px;
|
||||
background-position: -947px -1120px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_video_games {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -385px;
|
||||
background-position: -586px 0px;
|
||||
width: 339px;
|
||||
height: 342px;
|
||||
}
|
||||
.welcome_basic_avatars {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -518px -1274px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.welcome_promo_party {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1274px;
|
||||
width: 270px;
|
||||
height: 180px;
|
||||
}
|
||||
.welcome_sample_tasks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -271px -1274px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 987 KiB |
18
website/assets/sprites/dist/spritesmith-largeSprites-2.css
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
.welcome_basic_avatars {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -271px 0px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.welcome_promo_party {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px 0px;
|
||||
width: 270px;
|
||||
height: 180px;
|
||||
}
|
||||
.welcome_sample_tasks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -181px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||