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", "name": "habitica",
"version": "4.92.5", "version": "4.92.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

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

View File

@@ -142,4 +142,52 @@ describe('shared.ops.buy', () => {
buy(user, {params: {key: 'potion'}, quantity: 2}); buy(user, {params: {key: 'potion'}, quantity: 2});
expect(user.stats.hp).to.eql(50); 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(); 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', () => { context('successful purchase', () => {

View File

@@ -1,66 +1,72 @@
.promo_april_fools_2019 { .promo_april_fools_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -425px; background-position: 0px -840px;
width: 423px; width: 423px;
height: 147px; height: 147px;
} }
.promo_armoire_backgrounds_201904 { .promo_armoire_backgrounds_201904 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -573px; background-position: -424px -840px;
width: 423px; width: 423px;
height: 147px; 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 { .promo_celestial_rainbow_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -277px; background-position: -433px -677px;
width: 423px; width: 423px;
height: 147px; height: 147px;
} }
.promo_classes_spring2019 { .promo_classes_spring2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px 0px; background-position: 0px -677px;
width: 432px; width: 432px;
height: 162px; height: 162px;
} }
.promo_egg_hunt { .promo_egg_hunt {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px 0px; background-position: -1005px 0px;
width: 354px; width: 354px;
height: 147px; height: 147px;
} }
.promo_mystery_201903 { .promo_mystery_201903 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -444px; background-position: -1005px -444px;
width: 351px; width: 351px;
height: 147px; height: 147px;
} }
.promo_seasonalshop_spring { .promo_seasonalshop_spring {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -277px; background-position: -1005px -592px;
width: 162px; width: 162px;
height: 138px; height: 138px;
} }
.promo_shiny_seeds { .promo_shiny_seeds {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -296px; background-position: -1005px -296px;
width: 351px; width: 351px;
height: 147px; height: 147px;
} }
.promo_spring_avatar_customizations { .promo_spring_avatar_customizations {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -148px; background-position: -1005px -148px;
width: 354px; width: 354px;
height: 147px; height: 147px;
} }
.promo_take_this { .promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -761px -592px; background-position: -1168px -592px;
width: 96px; width: 96px;
height: 69px; height: 69px;
} }
.scene_yesterdailies_repeatables { .scene_yesterdailies_repeatables {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px; background-position: -677px 0px;
width: 327px; width: 327px;
height: 276px; 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') }} strong {{ $t('howManyToBuy') }}
div(v-if='showAmountToBuy(item)') div(v-if='showAmountToBuy(item)')
.box .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(:class="{'notEnough': notEnoughCurrency}")
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]") span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
span.cost(:class="getPriceClass()") {{ item.value }} span.cost(:class="getPriceClass()") {{ item.value }}
@@ -71,7 +71,7 @@
button.btn.btn-primary( button.btn.btn-primary(
@click="buyItem()", @click="buyItem()",
v-else, 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)}" :class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
) {{ $t('buyNow') }} ) {{ $t('buyNow') }}
@@ -260,6 +260,7 @@
import * as Analytics from 'client/libs/analytics'; import * as Analytics from 'client/libs/analytics';
import spellsMixin from 'client/mixins/spells'; import spellsMixin from 'client/mixins/spells';
import planGemLimits from 'common/script/libs/planGemLimits'; import planGemLimits from 'common/script/libs/planGemLimits';
import numberInvalid from 'client/mixins/numberInvalid';
import svgClose from 'assets/svg/close.svg'; import svgClose from 'assets/svg/close.svg';
import svgGold from 'assets/svg/gold.svg'; import svgGold from 'assets/svg/gold.svg';
@@ -291,7 +292,7 @@
]; ];
export default { export default {
mixins: [currencyMixin, notifications, spellsMixin, buyMixin], mixins: [buyMixin, currencyMixin, notifications, numberInvalid, spellsMixin],
components: { components: {
BalanceInfo, BalanceInfo,
EquipmentAttributesGrid, EquipmentAttributesGrid,

View File

@@ -22,7 +22,7 @@
.how-many-to-buy .how-many-to-buy
strong {{ $t('howManyToBuy') }} strong {{ $t('howManyToBuy') }}
.box .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.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
span.value(:class="priceType") {{ item.value }} span.value(:class="priceType") {{ item.value }}
@@ -34,7 +34,8 @@
button.btn.btn-primary( button.btn.btn-primary(
@click="buyItem()", @click="buyItem()",
v-else, v-else,
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}" :class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}",
:disabled='numberInvalid',
) {{ $t('buyNow') }} ) {{ $t('buyNow') }}
div.right-sidebar(v-if="item.drop") div.right-sidebar(v-if="item.drop")
@@ -207,12 +208,13 @@
import QuestInfo from './questInfo.vue'; import QuestInfo from './questInfo.vue';
import notifications from 'client/mixins/notifications'; import notifications from 'client/mixins/notifications';
import buyMixin from 'client/mixins/buy'; import buyMixin from 'client/mixins/buy';
import numberInvalid from 'client/mixins/numberInvalid';
import questDialogDrops from './questDialogDrops'; import questDialogDrops from './questDialogDrops';
import questDialogContent from './questDialogContent'; import questDialogContent from './questDialogContent';
export default { export default {
mixins: [currencyMixin, notifications, buyMixin], mixins: [buyMixin, currencyMixin, notifications, numberInvalid],
components: { components: {
BalanceInfo, BalanceInfo,
QuestInfo, QuestInfo,
@@ -309,7 +311,6 @@
return `Unknown type: ${drop.type}`; return `Unknown type: ${drop.type}`;
} }
}, },
purchaseGems () { purchaseGems () {
this.$root.$emit('bv::show::modal', 'buy-gems'); 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.', itemNotFound: 'Item "<%= key %>" not found.',
questNotFound: 'Quest "<%= key %>" not found.', questNotFound: 'Quest "<%= key %>" not found.',
spellNotFound: 'Skill "<%= spellId %>" 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"', invalidTypeEquip: '"type" must be one of "equipped", "pet", "mount", "costume"',
missingPetFoodFeed: '"pet" and "food" are required parameters.', missingPetFoodFeed: '"pet" and "food" are required parameters.',
missingEggHatchingPotion: '"egg" and "hatchingPotion" are required parameters.', missingEggHatchingPotion: '"egg" and "hatchingPotion" are required parameters.',

View File

@@ -99,7 +99,7 @@
"unlocked": "Items have been unlocked", "unlocked": "Items have been unlocked",
"alreadyUnlocked": "Full set already unlocked.", "alreadyUnlocked": "Full set already unlocked.",
"alreadyUnlockedPart": "Full set already partially 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)", "USD": "(USD)",
"newStuff": "New Stuff by Bailey", "newStuff": "New Stuff by Bailey",

View File

@@ -21,7 +21,7 @@ export class AbstractBuyOperation {
let quantity = _get(req, 'quantity'); let quantity = _get(req, 'quantity');
this.quantity = quantity ? Number(quantity) : 1; 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 key = get(req.params, 'key');
let quantity = req.quantity ? Number(req.quantity) : 1; 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) { if (!type) {
throw new BadRequest(i18n.t('typeRequired', req.language)); 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 = {}; let api = {};
// @TODO export this const, cannot export it from here because only routes are exported from controllers // @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 const worldDmg = { // @TODO
bailey: false, bailey: false,
}; };
@@ -30,14 +30,13 @@ api.getNews = {
<div class="mr-3 ${baileyClass}"></div> <div class="mr-3 ${baileyClass}"></div>
<div class="media-body"> <div class="media-body">
<h1 class="align-self-center">${res.t('newStuff')}</h1> <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>
</div> </div>
<hr/> <hr/>
<div class="scene_yesterdailies_repeatables center-block"></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>
<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> <div class="small mb-3">by Beffymaroo</div>
<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="promo_butterflies center-block"></div>
<div class="small mb-3">by shanaqui</div>
</div> </div>
`, `,
}); });