Merge branch 'release' into develop

This commit is contained in:
Sabe Jones
2019-04-23 15:30:37 -05:00
15 changed files with 133 additions and 29 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.92.5",
"version": "4.92.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.92.5",
"version": "4.92.6",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.6.0",

View File

@@ -142,4 +142,52 @@ describe('shared.ops.buy', () => {
buy(user, {params: {key: 'potion'}, quantity: 2});
expect(user.stats.hp).to.eql(50);
});
it('errors if user supplies a non-numeric quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: 'bogle',
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
it('errors if user supplies a negative quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: -3,
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
it('errors if user supplies a decimal quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: 1.83,
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
});

View File

@@ -108,6 +108,47 @@ describe('shared.ops.purchase', () => {
done();
}
});
it('returns error when user supplies a non-numeric quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
try {
purchase(user, {params: {type, key}, quantity: 'jamboree'}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when user supplies a negative quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
user.balance = 10;
try {
purchase(user, {params: {type, key}, quantity: -2}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when user supplies a decimal quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
user.balance = 10;
try {
purchase(user, {params: {type, key}, quantity: 2.9}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
});
context('successful purchase', () => {

View File

@@ -1,66 +1,72 @@
.promo_april_fools_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -425px;
background-position: 0px -840px;
width: 423px;
height: 147px;
}
.promo_armoire_backgrounds_201904 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -573px;
background-position: -424px -840px;
width: 423px;
height: 147px;
}
.promo_butterflies {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 676px;
height: 676px;
}
.promo_celestial_rainbow_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -277px;
background-position: -433px -677px;
width: 423px;
height: 147px;
}
.promo_classes_spring2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px 0px;
background-position: 0px -677px;
width: 432px;
height: 162px;
}
.promo_egg_hunt {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px 0px;
background-position: -1005px 0px;
width: 354px;
height: 147px;
}
.promo_mystery_201903 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -444px;
background-position: -1005px -444px;
width: 351px;
height: 147px;
}
.promo_seasonalshop_spring {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -277px;
background-position: -1005px -592px;
width: 162px;
height: 138px;
}
.promo_shiny_seeds {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -296px;
background-position: -1005px -296px;
width: 351px;
height: 147px;
}
.promo_spring_avatar_customizations {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -148px;
background-position: -1005px -148px;
width: 354px;
height: 147px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -592px;
background-position: -1168px -592px;
width: 96px;
height: 69px;
}
.scene_yesterdailies_repeatables {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
background-position: -677px 0px;
width: 327px;
height: 276px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -48,7 +48,7 @@
strong {{ $t('howManyToBuy') }}
div(v-if='showAmountToBuy(item)')
.box
input(type='number', min='0', v-model.number='selectedAmountToBuy')
input(type='number', min='0', step='1', v-model.number='selectedAmountToBuy')
span(:class="{'notEnough': notEnoughCurrency}")
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
span.cost(:class="getPriceClass()") {{ item.value }}
@@ -71,7 +71,7 @@
button.btn.btn-primary(
@click="buyItem()",
v-else,
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft',
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid',
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
) {{ $t('buyNow') }}
@@ -260,6 +260,7 @@
import * as Analytics from 'client/libs/analytics';
import spellsMixin from 'client/mixins/spells';
import planGemLimits from 'common/script/libs/planGemLimits';
import numberInvalid from 'client/mixins/numberInvalid';
import svgClose from 'assets/svg/close.svg';
import svgGold from 'assets/svg/gold.svg';
@@ -291,7 +292,7 @@
];
export default {
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
mixins: [buyMixin, currencyMixin, notifications, numberInvalid, spellsMixin],
components: {
BalanceInfo,
EquipmentAttributesGrid,

View File

@@ -22,7 +22,7 @@
.how-many-to-buy
strong {{ $t('howManyToBuy') }}
.box
input(type='number', min='0', v-model.number='selectedAmountToBuy')
input(type='number', min='0', step='1', v-model.number='selectedAmountToBuy')
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
span.value(:class="priceType") {{ item.value }}
@@ -34,7 +34,8 @@
button.btn.btn-primary(
@click="buyItem()",
v-else,
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}",
:disabled='numberInvalid',
) {{ $t('buyNow') }}
div.right-sidebar(v-if="item.drop")
@@ -207,12 +208,13 @@
import QuestInfo from './questInfo.vue';
import notifications from 'client/mixins/notifications';
import buyMixin from 'client/mixins/buy';
import numberInvalid from 'client/mixins/numberInvalid';
import questDialogDrops from './questDialogDrops';
import questDialogContent from './questDialogContent';
export default {
mixins: [currencyMixin, notifications, buyMixin],
mixins: [buyMixin, currencyMixin, notifications, numberInvalid],
components: {
BalanceInfo,
QuestInfo,
@@ -309,7 +311,6 @@
return `Unknown type: ${drop.type}`;
}
},
purchaseGems () {
this.$root.$emit('bv::show::modal', 'buy-gems');
},

View File

@@ -0,0 +1,7 @@
export default {
computed: {
numberInvalid () {
return this.selectedAmountToBuy < 1 || !Number.isInteger(this.selectedAmountToBuy);
},
},
};

View File

@@ -9,6 +9,7 @@ module.exports = {
itemNotFound: 'Item "<%= key %>" not found.',
questNotFound: 'Quest "<%= key %>" not found.',
spellNotFound: 'Skill "<%= spellId %>" not found.',
invalidQuantity: 'Quantity to purchase must be a positive whole number.',
invalidTypeEquip: '"type" must be one of "equipped", "pet", "mount", "costume"',
missingPetFoodFeed: '"pet" and "food" are required parameters.',
missingEggHatchingPotion: '"egg" and "hatchingPotion" are required parameters.',

View File

@@ -99,7 +99,7 @@
"unlocked": "Items have been unlocked",
"alreadyUnlocked": "Full set already unlocked.",
"alreadyUnlockedPart": "Full set already partially unlocked.",
"invalidQuantity": "Quantity to purchase must be a number.",
"invalidQuantity": "Quantity to purchase must be a positive whole number.",
"USD": "(USD)",
"newStuff": "New Stuff by Bailey",

View File

@@ -21,7 +21,7 @@ export class AbstractBuyOperation {
let quantity = _get(req, 'quantity');
this.quantity = quantity ? Number(quantity) : 1;
if (isNaN(this.quantity)) throw new BadRequest(this.i18n('invalidQuantity'));
if (this.quantity < 1 || !Number.isInteger(this.quantity)) throw new BadRequest(this.i18n('invalidQuantity'));
}
/**

View File

@@ -73,7 +73,7 @@ module.exports = function purchase (user, req = {}, analytics) {
let key = get(req.params, 'key');
let quantity = req.quantity ? Number(req.quantity) : 1;
if (isNaN(quantity)) throw new BadRequest(i18n.t('invalidQuantity', req.language));
if (quantity < 1 || !Number.isInteger(quantity)) throw new BadRequest(i18n.t('invalidQuantity', req.language));
if (!type) {
throw new BadRequest(i18n.t('typeRequired', req.language));

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 KiB

View File

@@ -3,7 +3,7 @@ import { authWithHeaders } from '../../middlewares/auth';
let api = {};
// @TODO export this const, cannot export it from here because only routes are exported from controllers
const LAST_ANNOUNCEMENT_TITLE = 'HABITICA BLOG: USE CASE SPOTLIGHT';
const LAST_ANNOUNCEMENT_TITLE = 'BEHIND THE SCENES: A BUTTERFLY GARDENING ADVENTURE WITH BEFFYMAROO!';
const worldDmg = { // @TODO
bailey: false,
};
@@ -30,14 +30,13 @@ api.getNews = {
<div class="mr-3 ${baileyClass}"></div>
<div class="media-body">
<h1 class="align-self-center">${res.t('newStuff')}</h1>
<h2>4/18/2019 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
<h2>4/23/2019 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
</div>
</div>
<hr/>
<div class="scene_yesterdailies_repeatables center-block"></div>
<p>This month's <a href='https://habitica.wordpress.com/2019/04/18/use-case-spotlight-reviewing-and-evaluating-your-tasks/' target='_blank'>Use Case Spotlight</a> is about Reviewing and Evaluating your Tasks! It features a number of great suggestions submitted by Habiticans in the <a href='/groups/guild/1d3a10bf-60aa-4806-a38b-82d1084a59e6'>Use Case Spotlights Guild</a>. We hope it helps any of you who might be considering a refresh for your Task Lists.</p>
<p>Plus, we're collecting user submissions for the next spotlight! How do you keep things fresh and interesting if you've been using Habitica for a long time? Well be featuring player-submitted examples in Use Case Spotlights on the Habitica Blog next month, so post your suggestions in the Use Case Spotlight Guild now. We look forward to learning more about how you use Habitica to improve your life and get things done!</p>
<div class="small mb-3">by shanaqui</div>
<p>There's a new <a href='https://habitica.wordpress.com/2019/04/23/butterfly-gardening/' target='_blank'>Behind the Scenes post</a> on the Habitica Blog! Beffymaroo shares some information about starting your own butterfly garden and enjoying watching these fascinating--and beneficial--creatures in your home and yard.</p>
<div class="small mb-3">by Beffymaroo</div>
<div class="promo_butterflies center-block"></div>
</div>
`,
});