Merge branch 'develop' into greenkeeper/nightwatch-1.0.16
@@ -4,7 +4,7 @@
|
||||
|
||||
# Pull Request
|
||||
|
||||
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
|
||||
[Please see these instructions for adding a pull request](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
|
||||
|
||||
# Requesting a feature
|
||||
|
||||
@@ -12,4 +12,4 @@ Habitica uses [Trello](https://trello.com/b/EpoYEYod/habitica) to track feature
|
||||
|
||||
# Contributing Code
|
||||
|
||||
See [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
|
||||
See [Contributing to Habitica](http://habitica.fandom.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
|
||||
[//]: # (Note: See http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
|
||||
|
||||
[//]: # (Put Issue # here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||
Fixes put_#_and_issue_numer_here
|
||||
|
||||
@@ -40,6 +40,7 @@ test/client/unit/coverage
|
||||
test/client/e2e/reports
|
||||
test/client-old/spec/mocks/translations.js
|
||||
yarn.lock
|
||||
.gitattributes
|
||||
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
|
||||
@@ -8,6 +8,7 @@ ENV BASE_URL https://habitica.com
|
||||
ENV FACEBOOK_KEY 128307497299777
|
||||
ENV GA_ID UA-33510635-1
|
||||
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
||||
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ Habitica [.
|
||||
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths).
|
||||
|
||||
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
@@ -8,4 +8,4 @@ minimal dependencies on the developer's local platform. It can be used
|
||||
on a variety of systems including Windows, Mac OS X, and Linux.
|
||||
|
||||
Instructions for using the Habitica Vagrant environment are in
|
||||
[Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
[Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
@@ -77,5 +77,7 @@
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
"ENABLE_STACKDRIVER_TRACING": "false"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ 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 message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.fandom.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
|
||||
|
||||
const timer = new Timer();
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20181231_nye';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import mongoose from 'mongoose';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {'flags.newStuff': true};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2018'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2018',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2017'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2017',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2016'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2016',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2015'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2015',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2014'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2014',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
set['items.gear.owned.head_special_nye'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190131_habit_birthday';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const inc = {
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1,
|
||||
'achievements.habitBirthdays': 1,
|
||||
};
|
||||
const set = {};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2019'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2019', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2018'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2017'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2016'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_birthday2015'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', _id: uuid()}};
|
||||
} else {
|
||||
set['items.gear.owned.armor_special_birthday'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', _id: uuid()}};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-01-15')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,7 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./users/20181122_turkey_day.js');
|
||||
const processUsers = require('./users/mystery-items.js');
|
||||
processUsers()
|
||||
.then(function success () {
|
||||
process.exit(0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201811';
|
||||
const MYSTERY_ITEMS = ['head_mystery_201811', 'weapon_mystery_201811'];
|
||||
const MIGRATION_NAME = 'mystery_items_201903';
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201903', 'head_mystery_201903'];
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20190314_pi_day';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const inc = {
|
||||
'items.food.Pie_Skeleton': 1,
|
||||
'items.food.Pie_Base': 1,
|
||||
'items.food.Pie_CottonCandyBlue': 1,
|
||||
'items.food.Pie_CottonCandyPink': 1,
|
||||
'items.food.Pie_Shade': 1,
|
||||
'items.food.Pie_White': 1,
|
||||
'items.food.Pie_Golden': 1,
|
||||
'items.food.Pie_Zombie': 1,
|
||||
'items.food.Pie_Desert': 1,
|
||||
'items.food.Pie_Red': 1,
|
||||
};
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.head_special_piDay'] = false;
|
||||
set['items.gear.owned.shield_special_piDay'] = false;
|
||||
const push = [
|
||||
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
|
||||
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
|
||||
];
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -1,17 +1,19 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.75.5",
|
||||
"version": "4.90.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/trace-agent": "^3.5.2",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amplitude": "^3.5.0",
|
||||
"amplitude-js": "^4.6.0-beta.2",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.329.0",
|
||||
"autoprefixer": "^9.4.0",
|
||||
"aws-sdk": "^2.400.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
@@ -29,13 +31,13 @@
|
||||
"bcrypt": "^3.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"bootstrap": "^4.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.9",
|
||||
"bootstrap-vue": "^2.0.0-rc.13",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^4.3.1",
|
||||
"csv-stringify": "^5.1.0",
|
||||
"cwait": "^1.1.1",
|
||||
"domain-middleware": "~0.1.0",
|
||||
"express": "^4.16.3",
|
||||
@@ -62,12 +64,12 @@
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.22.1",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.3.4",
|
||||
"mongoose": "^5.4.11",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-sass": "^4.9.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"nodemailer": "^5.0.0",
|
||||
"ora": "^3.0.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
@@ -98,12 +100,12 @@
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vue": "^2.5.16",
|
||||
"vue": "^2.6.4",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vue-template-compiler": "^2.6.4",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
@@ -144,7 +146,7 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.19",
|
||||
"@vue/test-utils": "^1.0.0-beta.29",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
|
||||
@@ -1 +1 @@
|
||||
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
|
||||
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
BadRequest,
|
||||
InternalServerError,
|
||||
NotFound,
|
||||
NotificationNotFound,
|
||||
} from '../../../../website/server/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('Custom Errors', () => {
|
||||
describe('CustomError', () => {
|
||||
@@ -66,6 +68,23 @@ describe('Custom Errors', () => {
|
||||
|
||||
expect(notAuthorizedError.message).to.eql('Custom Error Message');
|
||||
});
|
||||
|
||||
describe('NotificationNotFound', () => {
|
||||
it('is an instance of NotFound', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr).to.be.an.instanceOf(NotFound);
|
||||
});
|
||||
|
||||
it('it returns an http code of 404', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr.httpCode).to.eql(404);
|
||||
});
|
||||
|
||||
it('returns a standard message', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr.message).to.eql(i18n.t('messageNotificationNotFound'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BadRequest', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import moment from 'moment';
|
||||
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -49,7 +50,7 @@ describe('Apple Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -61,7 +62,7 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -71,7 +72,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -89,7 +90,7 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -131,7 +132,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
await applePayments.verifyGemPurchase({user, receipt, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -151,6 +152,38 @@ describe('Apple Payments', () => {
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('gifts gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: gemsCanPurchase[0].productId,
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
const gift = {uuid: receivingUser._id};
|
||||
await applePayments.verifyGemPurchase({user, gift, receipt, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user: receivingUser,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
amount: gemsCanPurchase[0].amount,
|
||||
headers,
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import moment from 'moment';
|
||||
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -44,7 +45,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -55,7 +56,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if productId is invalid', async () => {
|
||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -66,7 +67,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -78,7 +79,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
await googlePayments.verifyGemPurchase({user, receipt, signature, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -99,6 +100,34 @@ describe('Google Payments', () => {
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('gifts gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
const gift = {uuid: receivingUser._id};
|
||||
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
data: receipt,
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user: receivingUser,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('slack', () => {
|
||||
},
|
||||
message: {
|
||||
id: 'chat-id',
|
||||
username: 'author',
|
||||
user: 'Author',
|
||||
uuid: 'author-id',
|
||||
text: 'some text',
|
||||
@@ -50,11 +51,11 @@ describe('slack', () => {
|
||||
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
|
||||
text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `Author - author@example.com - author-id\n${timestamp}`,
|
||||
author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
|
||||
title: 'Flag in Some group - (private guild)',
|
||||
title_link: undefined,
|
||||
text: 'some text',
|
||||
|
||||
@@ -73,6 +73,56 @@ describe('redirects middleware', () => {
|
||||
|
||||
expect(res.redirect).to.have.not.been.called;
|
||||
});
|
||||
|
||||
it('does not redirect if passed skip ssl request param is passed with corrrect key', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'test-key';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.have.not.been.called;
|
||||
});
|
||||
|
||||
it('does redirect if skip ssl request param is passed with incorrrect key', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front?skipSSLCheck=INVALID';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.be.calledOnce;
|
||||
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front?skipSSLCheck=INVALID');
|
||||
});
|
||||
|
||||
it('does redirect if skip ssl check key is not set', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.be.calledOnce;
|
||||
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front');
|
||||
});
|
||||
});
|
||||
|
||||
context('forceHabitica', () => {
|
||||
|
||||
@@ -32,8 +32,19 @@ describe('Group Model', () => {
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
let _progress = {
|
||||
up: 10,
|
||||
down: 8,
|
||||
collectedItems: 5,
|
||||
};
|
||||
|
||||
questLeader = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Quest Leader' },
|
||||
items: {
|
||||
quests: {
|
||||
@@ -45,20 +56,40 @@ describe('Group Model', () => {
|
||||
party.leader = questLeader._id;
|
||||
|
||||
participatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Participating Member' },
|
||||
});
|
||||
sleepingParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Sleeping Participating Member' },
|
||||
preferences: { sleep: true },
|
||||
});
|
||||
nonParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Non-Participating Member' },
|
||||
});
|
||||
undecidedMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Undecided Member' },
|
||||
});
|
||||
|
||||
@@ -1163,16 +1194,17 @@ describe('Group Model', () => {
|
||||
expect(party.quest.members).to.eql(expectedQuestMembers);
|
||||
});
|
||||
|
||||
it('applies updates to user object directly if user is participating', async () => {
|
||||
it('applies updates to user object directly if user is participating (without resetting progress, except progress.down)', async () => {
|
||||
await party.startQuest(participatingMember);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
it('applies updates to other participating members', async () => {
|
||||
it('applies updates to other participating members (without resetting progress, except progress.down)', async () => {
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
@@ -1180,18 +1212,21 @@ describe('Group Model', () => {
|
||||
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
|
||||
expect(sleepingParticipatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(questLeader.party.quest.key).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.up).to.eql(10);
|
||||
expect(questLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(questLeader.party.quest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
@@ -1202,6 +1237,9 @@ describe('Group Model', () => {
|
||||
undecidedMember = await User.findById(undecidedMember._id);
|
||||
|
||||
expect(nonParticipatingMember.party.quest.key).to.not.eql('whale');
|
||||
expect(nonParticipatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(nonParticipatingMember.party.quest.progress.down).to.eql(8);
|
||||
expect(nonParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(undecidedMember.party.quest.key).to.not.eql('whale');
|
||||
});
|
||||
|
||||
@@ -1369,8 +1407,9 @@ describe('Group Model', () => {
|
||||
let userQuest = participatingMember.party.quest;
|
||||
|
||||
expect(userQuest.key).to.eql('whale');
|
||||
expect(userQuest.progress.up).to.eql(10);
|
||||
expect(userQuest.progress.down).to.eql(0);
|
||||
expect(userQuest.progress.collectedItems).to.eql(0);
|
||||
expect(userQuest.progress.collectedItems).to.eql(5);
|
||||
expect(userQuest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
@@ -1670,16 +1709,23 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets user quest object to a clean state', async () => {
|
||||
it('updates participating members quest object to a clean state (except for progress)', async () => {
|
||||
await party.finishQuest(quest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
participatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(updatedLeader.party.quest.completed).to.eql('whale');
|
||||
expect(updatedLeader.party.quest.progress.up).to.eql(0);
|
||||
expect(updatedLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(updatedLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(updatedLeader.party.quest.RSVPNeeded).to.eql(false);
|
||||
expect(questLeader.party.quest.completed).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.up).to.eql(10);
|
||||
expect(questLeader.party.quest.progress.down).to.eql(8);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
|
||||
|
||||
expect(participatingMember.party.quest.completed).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(8);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.RSVPNeeded).to.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -65,11 +65,11 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},${sortedMembers[3].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[5]).to.equal('');
|
||||
});
|
||||
|
||||
@@ -78,10 +78,10 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
||||
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
|
||||
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
|
||||
const splitRes = res.split('\n');
|
||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||
expect(splitRes[4]).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
|
||||
text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
|
||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
|
||||
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
|
||||
attachments: [{
|
||||
fallback: 'Flag Message',
|
||||
color: 'danger',
|
||||
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
|
||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||
title: 'Flag in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
||||
text: TEST_MESSAGE,
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('POST /chat', () => {
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
||||
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
|
||||
author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
|
||||
title: 'Slur in Party - (private party)',
|
||||
title_link: undefined,
|
||||
text: testSlurMessage,
|
||||
|
||||
@@ -25,9 +25,9 @@ describe('GET /heroes/:heroId', () => {
|
||||
|
||||
it('validates req.params.heroId', async () => {
|
||||
await expect(user.get('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('userWithIDNotFound', {userId: 'invalidUUID'}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('GET /heroes/:heroId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only necessary hero data', async () => {
|
||||
it('returns only necessary hero data given user id', async () => {
|
||||
let hero = await generateUser({
|
||||
contributor: {tier: 23},
|
||||
});
|
||||
@@ -53,4 +53,24 @@ describe('GET /heroes/:heroId', () => {
|
||||
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only necessary hero data given username', async () => {
|
||||
let hero = await generateUser({
|
||||
contributor: {tier: 23},
|
||||
});
|
||||
let heroRes = await user.get(`/hall/heroes/${hero.auth.local.username}`);
|
||||
|
||||
expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'balance', 'profile', 'purchased',
|
||||
'contributor', 'auth', 'items',
|
||||
]);
|
||||
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
|
||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns correct hero using search with difference case', async () => {
|
||||
await generateUser({}, { username: 'TestUpperCaseName123' });
|
||||
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
|
||||
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/see', () => {
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('POST /notifications/read', () => {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('POST /notifications/see', () => {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #norenewsubscribe', () => {
|
||||
let endpoint = '/iap/ios/norenew-subscribe';
|
||||
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies sub key', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
sku,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(applePayments, 'noRenewSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
applePayments.noRenewSubscribe.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {receipt: 'receipt'},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #verify', () => {
|
||||
@@ -9,6 +9,14 @@ describe('payments : apple #verify', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let verifyStub;
|
||||
|
||||
@@ -31,10 +39,31 @@ describe('payments : apple #verify', () => {
|
||||
}});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][1]).to.eql('receipt');
|
||||
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
}});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #norenewsubscribe', () => {
|
||||
let endpoint = '/iap/android/norenew-subscribe';
|
||||
let sku = 'com.habitrpg.android.habitica.subscription.3month';
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies sub key', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
sku,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(googlePayments, 'noRenewSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
googlePayments.noRenewSubscribe.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
signature: 'signature',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].signature).to.eql('signature');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
signature: 'signature',
|
||||
},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].signature).to.eql('signature');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #verify', () => {
|
||||
@@ -9,6 +9,14 @@ describe('payments : google #verify', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let verifyStub;
|
||||
|
||||
@@ -30,11 +38,30 @@ describe('payments : google #verify', () => {
|
||||
});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][1]).to.eql('receipt');
|
||||
expect(verifyStub.args[0][2]).to.eql('signature');
|
||||
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].signature).to.eql('signature');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
transaction: {receipt: 'receipt', signature: 'signature'},
|
||||
gift: {uuid: '1'},
|
||||
});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].signature).to.eql('signature');
|
||||
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
it('user uses social authentication and has no password', async () => {
|
||||
await user.unset({
|
||||
'auth.local.hashed_password': 1,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.hashed_password).to.be.undefined;
|
||||
|
||||
await expect(api.post(endpoint, {
|
||||
username: user.auth.local.username,
|
||||
password: 'any-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidLoginCredentialsLong'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /members/flag-private-message/:messageId', () => {
|
||||
let userToSendMessage;
|
||||
let messageToSend = 'Test Private Message';
|
||||
|
||||
beforeEach(async () => {
|
||||
userToSendMessage = await generateUser();
|
||||
});
|
||||
|
||||
it('Allows players to flag their own private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let senderMessages = await userToSendMessage.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Flags a private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a private message that is already flagged', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,300 @@
|
||||
import Avatar from 'client/components/avatar';
|
||||
import Vue from 'vue';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
context('avatar.vue', () => {
|
||||
let Constructr;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
Constructr = Vue.extend(Avatar);
|
||||
|
||||
vm = new Constructr({
|
||||
propsData: {
|
||||
member: {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
preferences: {
|
||||
hair: {},
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
vm.$store = generateStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('hasClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: { lvl: 17 },
|
||||
preferences: { disableClasses: true },
|
||||
flags: { classSelected: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports class status', () => {
|
||||
expect(vm.hasClass).to.equal(false);
|
||||
|
||||
vm.member.preferences.disableClasses = false;
|
||||
vm.member.flags.classSelected = true;
|
||||
|
||||
expect(vm.hasClass).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBuffed', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports if buffed', () => {
|
||||
expect(vm.isBuffed).to.equal(undefined);
|
||||
|
||||
vm.member.stats.buffs = { str: 1 };
|
||||
|
||||
expect(vm.isBuffed).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paddingTop', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('defaults to 28px', () => {
|
||||
vm.avatarOnly = true;
|
||||
expect(vm.paddingTop).to.equal('28px');
|
||||
});
|
||||
|
||||
it('is 24.5px if user has a pet', () => {
|
||||
vm.member.items = {
|
||||
currentPet: { name: 'Foo' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('24.5px');
|
||||
});
|
||||
|
||||
it('is 0px if user has a mount', () => {
|
||||
vm.member.items = {
|
||||
currentMount: { name: 'Bar' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('0px');
|
||||
});
|
||||
|
||||
it('can be overriden', () => {
|
||||
vm.overrideTopPadding = '27px';
|
||||
expect(vm.paddingTop).to.equal('27px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('costumeClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
preferences: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('returns if showing equiped gear', () => {
|
||||
expect(vm.costumeClass).to.equal('equipped');
|
||||
});
|
||||
it('returns if wearing a costume', () => {
|
||||
vm.member.preferences = { costume: true };
|
||||
expect(vm.costumeClass).to.equal('costume');
|
||||
});
|
||||
});
|
||||
|
||||
describe('visualBuffs', () => {
|
||||
it('returns an array of buffs', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'Warrior',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.visualBuffs).to.include({snowball: 'snowman'});
|
||||
expect(vm.visualBuffs).to.include({spookySparkles: 'ghost'});
|
||||
expect(vm.visualBuffs).to.include({shinySeed: 'avatar_floral_Warrior'});
|
||||
expect(vm.visualBuffs).to.include({seafoam: 'seafoam_star'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member.preferences = { background: 'pony' };
|
||||
});
|
||||
|
||||
it('shows the background', () => {
|
||||
expect(vm.backgroundClass).to.equal('background_pony');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { background: 'character' };
|
||||
|
||||
expect(vm.backgroundClass).to.equal('background_character');
|
||||
});
|
||||
|
||||
it('returns to a blank string if not showing background', () => {
|
||||
vm.withBackground = false;
|
||||
vm.avatarOnly = true;
|
||||
|
||||
expect(vm.backgroundClass).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialMountClass', () => {
|
||||
it('checks if riding a Kangaroo', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'None',
|
||||
},
|
||||
items: {},
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal(undefined);
|
||||
|
||||
vm.member.items = {
|
||||
currentMount: ['Kangaroo'],
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal('offset-kangaroo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('skinClass', () => {
|
||||
it('returns current skin color', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
});
|
||||
|
||||
it('returns if sleep or not', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
sleep: false,
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
|
||||
vm.member.preferences.sleep = true;
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue_sleep');
|
||||
});
|
||||
});
|
||||
|
||||
context('methods', () => {
|
||||
describe('getGearClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: { Hat: 'Fancy Tophat' },
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('returns undefined if no match', () => {
|
||||
expect(vm.getGearClass('foo')).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('returns the matching gear', () => {
|
||||
expect(vm.getGearClass('Hat')).to.equal('Fancy Tophat');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { Hat: 'Dapper Bowler' };
|
||||
|
||||
expect(vm.getGearClass('Hat')).to.equal('Dapper Bowler');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideGear', () => {
|
||||
it('returns no weapon equipped', () => {
|
||||
vm.member.items.gear.equipped = {};
|
||||
expect(vm.hideGear('weapon')).to.equal(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {
|
||||
weapon: {
|
||||
baseWeapon: 'Spoon',
|
||||
twoHanded: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('show avatar', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {
|
||||
snowball: false,
|
||||
seafoam: false,
|
||||
spookySparkles: false,
|
||||
shinySeed: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
it('does if not showing visual buffs', () => {
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
|
||||
let buffs = vm.member.stats.buffs;
|
||||
|
||||
buffs.snowball = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.snowball = false;
|
||||
buffs.spookySparkles = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.spookySparkles = false;
|
||||
buffs.shinySeed = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.shinySeed = false;
|
||||
buffs.seafoam = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.seafoam = false;
|
||||
vm.showVisualBuffs = false;
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
import {mount} from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
|
||||
import CategoryTags from 'client/components/categories/categoryTags.vue';
|
||||
|
||||
@@ -6,7 +7,7 @@ describe('Category Tags', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(CategoryTags, {
|
||||
wrapper = mount(CategoryTags, {
|
||||
propsData: {
|
||||
categories: [],
|
||||
},
|
||||
@@ -27,8 +28,10 @@ describe('Category Tags', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.contains('.category-label')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('test');
|
||||
return Vue.nextTick().then(() => {
|
||||
expect(wrapper.contains('.category-label')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('test');
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a habitica official in purple', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { shallow } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import SidebarSection from 'client/components/sidebarSection.vue';
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('Sidebar Section', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
wrapper = mount(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
},
|
||||
@@ -39,7 +39,7 @@ describe('Sidebar Section', () => {
|
||||
});
|
||||
|
||||
it('can hide contents by default', () => {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
wrapper = mount(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
show: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { shallow, createLocalVue } from '@vue/test-utils';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
import TaskColumn from 'client/components/tasks/column.vue';
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Task Column', () => {
|
||||
};
|
||||
let stubs = ['b-modal']; // <b-modal> is a custom component and not tested here
|
||||
|
||||
return shallow(TaskColumn, {
|
||||
return mount(TaskColumn, {
|
||||
propsData: {
|
||||
type,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {highlightUsers} from '../../../../../website/client/libs/highlightUsers';
|
||||
import habiticaMarkdown from 'habitica-markdown';
|
||||
|
||||
describe('highlightUserAndEmail', () => {
|
||||
it('highlights displayname', () => {
|
||||
const text = 'hello @displayedUser with text after';
|
||||
|
||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
});
|
||||
|
||||
it('highlights username', () => {
|
||||
const text = 'hello @user';
|
||||
|
||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@user</span>');
|
||||
});
|
||||
|
||||
it('not highlights any email', () => {
|
||||
const text = habiticaMarkdown.render('hello@example.com');
|
||||
|
||||
const result = highlightUsers(text, 'example', 'displayedUser');
|
||||
expect(result).to.not.contain('<span class="at-highlight">@example</span>');
|
||||
});
|
||||
|
||||
|
||||
it('complex highlight', () => {
|
||||
const plainText = 'a bit more @mentions to @use my@mentions.com broken.@mail.com';
|
||||
|
||||
const text = habiticaMarkdown.render(plainText);
|
||||
|
||||
const result = highlightUsers(text, 'use', 'mentions');
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { data, gems, buffs, preferences, tasksOrder } from 'client/store/getters/user';
|
||||
|
||||
context('user getters', () => {
|
||||
describe('data', () => {
|
||||
it('returns the user\'s data', () => {
|
||||
expect(data({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
lvl: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).lvl).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gems', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(gems({
|
||||
state: {
|
||||
user: {
|
||||
data: { balance: 4.5 },
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buffs', () => {
|
||||
it('returns the user\'s buffs', () => {
|
||||
expect(buffs({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
stats: {
|
||||
buffs: [1],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(0)).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preferences', () => {
|
||||
it('returns the user\'s preferences', () => {
|
||||
expect(preferences({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tasksOrder', () => {
|
||||
it('returns the user\'s tasksOrder', () => {
|
||||
expect(tasksOrder({
|
||||
state: {
|
||||
user: {
|
||||
tasksOrder: {
|
||||
masters: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})('master')).to.equal(1);
|
||||
|
||||
expect(tasksOrder()).to.not.equal('null');
|
||||
expect(tasksOrder()).to.not.equal('undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gems as userGems } from 'client/store/getters/user';
|
||||
|
||||
describe('userGems getter', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(userGems({
|
||||
state: {
|
||||
user: {
|
||||
data: {balance: 4.5},
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
@@ -158,7 +158,7 @@ describe('shared.ops.buyArmoire', () => {
|
||||
|
||||
expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
|
||||
expect(user.stats.gp).to.eql(100);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledTwice;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { requester } from './requester';
|
||||
import {
|
||||
getDocument as getDocumentFromMongo,
|
||||
updateDocument as updateDocumentInMongo,
|
||||
unsetDocument as unsetDocumentInMongo,
|
||||
} from '../mongo';
|
||||
import {
|
||||
assign,
|
||||
@@ -29,6 +30,18 @@ class ApiObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
async unset (options) {
|
||||
if (isEmpty(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await unsetDocumentInMongo(this._docType, this, options);
|
||||
|
||||
_updateLocalParameters((this, options));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async sync () {
|
||||
let updatedDoc = await getDocumentFromMongo(this._docType, this);
|
||||
|
||||
|
||||
@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// parameter, such as the number of wolf eggs the user has,
|
||||
// , you can do so by passing in the full path as a string:
|
||||
// { 'items.eggs.Wolf': 10 }
|
||||
export async function generateUser (update = {}) {
|
||||
let username = (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
//
|
||||
// To manually set a username, email or password pass it in as
|
||||
// an object for the second parameter. Only overrides need to be
|
||||
// added. Items that don't exist will be autogenerated.
|
||||
// Example: generateUser({}, { username: 'TestName' }) adds user
|
||||
// with the 'TestName' username.
|
||||
export async function generateUser (update = {}, overrides = {}) {
|
||||
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = overrides.password || 'password';
|
||||
let email = overrides.email || `${username}@example.com`;
|
||||
|
||||
let user = await requester().post('/user/auth/local/register', {
|
||||
username,
|
||||
|
||||
@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
|
||||
});
|
||||
}
|
||||
|
||||
// Unset a property in the database.
|
||||
// Useful for testing.
|
||||
export async function unsetDocument (collectionName, doc, update) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
|
||||
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDocument (collectionName, doc) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export async function mockFindById (response) {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
};
|
||||
sinon.stub(mongoose.Model, 'findById').returns(mockFind);
|
||||
}
|
||||
|
||||
export function restoreFindById () {
|
||||
return mongoose.Model.findById.restore();
|
||||
}
|
||||
@@ -65,7 +65,7 @@ apt-get install -qq ntp
|
||||
echo Installing nvm, node and global node modules...
|
||||
/vagrant/vagrant_scripts/install_node.sh
|
||||
|
||||
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally"
|
||||
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally"
|
||||
|
||||
# Uncomment both lines to autostart the habitica server when provisioning
|
||||
# echo Starting Habitica server...
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Running
|
||||
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
# Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
@@ -18,4 +18,4 @@ The project is developed directly in the `develop` branch as long as we'll be ab
|
||||
|
||||
So far most of the work has been on the template, so there's no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it's basically a Flux implementation: there's a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
|
||||
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
|
||||
For further resources, see [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
|
||||
|
||||
@@ -16,18 +16,18 @@ div
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
span.content
|
||||
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
|
||||
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
|
||||
div.closepadding(@click="hideBanner()")
|
||||
.closepadding(@click="hideBanner()")
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
|
||||
notifications-display
|
||||
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header(:class='{"restingInn": showRestingBanner}')
|
||||
app-header
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@@ -50,6 +50,13 @@ div
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#loading-screen-inapp {
|
||||
#melior {
|
||||
margin: 0 auto;
|
||||
@@ -79,6 +86,11 @@ div
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 1000px;
|
||||
background-color: $green-10;
|
||||
@@ -89,42 +101,10 @@ div
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1300;
|
||||
display: flex;
|
||||
@@ -140,14 +120,10 @@ div
|
||||
.closepadding {
|
||||
margin: 11px 24px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
|
||||
span svg path {
|
||||
stroke: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@@ -170,6 +146,30 @@ div
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.closepadding span svg path {
|
||||
stroke: #FFF;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
@@ -225,7 +225,6 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -318,6 +317,7 @@ export default {
|
||||
const errorMessage = errorData.message || errorData;
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
// TODO use a specific error like NotificationNotFound instead of checking for the string
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
@@ -327,12 +327,6 @@ export default {
|
||||
let snackbarTimeout = false;
|
||||
if (error.response.status === 502) snackbarTimeout = true;
|
||||
|
||||
const notificationNotFoundMessage = [
|
||||
this.$t('messageNotificationNotFound'),
|
||||
this.$t('messageNotificationNotFound', 'en'),
|
||||
];
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
let errorsToShow = [];
|
||||
// show only the first error for each param
|
||||
let paramErrorsFound = {};
|
||||
@@ -346,13 +340,17 @@ export default {
|
||||
} else {
|
||||
errorsToShow.push(errorMessage);
|
||||
}
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
|
||||
// Ignore NotificationNotFound errors, see https://github.com/HabitRPG/habitica/issues/10391
|
||||
if (errorData.error !== 'NotificationNotFound') {
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
@@ -423,14 +421,6 @@ export default {
|
||||
|
||||
this.hideLoadingScreen();
|
||||
|
||||
window.addEventListener('resize', this.setBannerOffset);
|
||||
// Adjust the positioning of the header banners
|
||||
this.$watch('showRestingBanner', () => {
|
||||
this.$nextTick(() => {
|
||||
this.setBannerOffset();
|
||||
});
|
||||
}, {immediate: true});
|
||||
|
||||
// Adjust the timezone offset
|
||||
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
|
||||
this.$store.dispatch('user:set', {
|
||||
@@ -465,7 +455,6 @@ export default {
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
window.removeEventListener('resize', this.setBannerOffset);
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
@@ -624,22 +613,10 @@ export default {
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
this.setBannerOffset();
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
setBannerOffset () {
|
||||
let contentPlacement = 0;
|
||||
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
|
||||
contentPlacement = this.$refs.restingBanner.clientHeight;
|
||||
}
|
||||
this.bannerHeight = contentPlacement;
|
||||
let smartBanner = document.getElementsByClassName('smartbanner')[0];
|
||||
if (smartBanner !== undefined) {
|
||||
smartBanner.style.top = `${contentPlacement}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -672,5 +649,6 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-23.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-24.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -1,96 +1,72 @@
|
||||
.achievement-costumeContest6x {
|
||||
.promo_armoire_backgrounds_201903 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -438px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_alligator {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -994px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201812 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -723px;
|
||||
background-position: -719px -767px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_bird_buddies_bundle {
|
||||
.promo_beffymaroo_wondercon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -723px;
|
||||
width: 420px;
|
||||
background-position: 0px 0px;
|
||||
width: 718px;
|
||||
height: 932px;
|
||||
}
|
||||
.promo_celestial_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -933px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_frost_potions {
|
||||
.promo_classes_spring2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -871px;
|
||||
width: 417px;
|
||||
background-position: -719px -604px;
|
||||
width: 432px;
|
||||
height: 162px;
|
||||
}
|
||||
.promo_egg_hunt {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -933px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
.promo_mystery_201902 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -361px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_mystery_201811 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -142px;
|
||||
width: 282px;
|
||||
background-position: -1160px -196px;
|
||||
width: 240px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_piyo {
|
||||
.promo_mystery_201903 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -290px;
|
||||
width: 279px;
|
||||
background-position: -779px -933px;
|
||||
width: 351px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonalshop_spring {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1160px -344px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1281px -438px;
|
||||
background-position: -1160px -483px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_turkey_day_2018 {
|
||||
.scene_dailies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -871px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
background-position: -719px -327px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
.scene_tavern {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px 0px;
|
||||
width: 363px;
|
||||
height: 141px;
|
||||
background-position: -719px 0px;
|
||||
width: 440px;
|
||||
height: 326px;
|
||||
}
|
||||
.scene_hiking {
|
||||
.scene_todos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -420px;
|
||||
width: 258px;
|
||||
height: 258px;
|
||||
}
|
||||
.scene_nametag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px 0px;
|
||||
width: 512px;
|
||||
height: 208px;
|
||||
}
|
||||
.scene_sleep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -209px;
|
||||
width: 390px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -595px;
|
||||
width: 242px;
|
||||
height: 62px;
|
||||
background-position: -1160px 0px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 968 KiB |
|
Before Width: | Height: | Size: 537 KiB After Width: | Height: | Size: 557 KiB |
|
Before Width: | Height: | Size: 519 KiB After Width: | Height: | Size: 562 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 65 KiB |