mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-28 11:42:29 +01:00
* add drop cap notification * add drop cap notification * add dismissible notification * fix(notification): correct remove icon positioning * track events * add modal * add back files * fix links and add missing analytics * fix rounded borders and hide sub info for subscribers * a/b test * fix comparison * Translated using Weblate (Spanish) Currently translated at 98.2% (55 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/es/ Translated using Weblate (Spanish) Currently translated at 99.4% (179 of 180 strings) Translation: Habitica/Settings Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/ Merge branch 'origin/develop' into Weblate. Translated using Weblate (Spanish) Currently translated at 99.4% (175 of 176 strings) Translation: Habitica/Subscriber Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/ Translated using Weblate (Spanish (Latin America)) Currently translated at 98.6% (359 of 364 strings) Translation: Habitica/Groups Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/ Translated using Weblate (Spanish) Currently translated at 85.7% (151 of 176 strings) Translation: Habitica/Subscriber Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/ Translated using Weblate (Spanish) Currently translated at 95.3% (538 of 564 strings) Translation: Habitica/Backgrounds Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/ Translated using Weblate (Spanish (Latin America)) Currently translated at 98.6% (359 of 364 strings) Translation: Habitica/Groups Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/ Translated using Weblate (French) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/fr/ Translated using Weblate (German) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/de/ Translated using Weblate (French) Currently translated at 100.0% (718 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/ Translated using Weblate (German) Currently translated at 100.0% (718 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/ Translated using Weblate (Czech) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Spells Translate-URL: https://translate.habitica.com/projects/habitica/spells/cs/ Translated using Weblate (Japanese) Currently translated at 100.0% (175 of 175 strings) Translation: Habitica/Subscriber Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/ Translated using Weblate (Italian) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/ Translated using Weblate (Italian) Currently translated at 100.0% (718 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/ Translated using Weblate (Czech) Currently translated at 100.0% (180 of 180 strings) Translation: Habitica/Settings Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/ Translated using Weblate (Basque) Currently translated at 100.0% (2 of 2 strings) Translation: Habitica/Noscript Translate-URL: https://translate.habitica.com/projects/habitica/noscript/eu/ Translated using Weblate (Basque) Currently translated at 6.5% (8 of 123 strings) Translation: Habitica/Communityguidelines Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/eu/ Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/ Translated using Weblate (Japanese) Currently translated at 100.0% (56 of 56 strings) Translation: Habitica/Messages Translate-URL: https://translate.habitica.com/projects/habitica/messages/ja/ Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (718 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/ Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (718 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/ Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (717 of 718 strings) Translation: Habitica/Questscontent Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/ * clarify a/b test values * add tests * refactor user dropdown * fix hover state * fix user dropdown * fix user menu hierarchy * restore i18n files to release version Co-authored-by: Melior <admin@habitica.com>
293 lines
11 KiB
JavaScript
293 lines
11 KiB
JavaScript
import randomDrop from '../../../website/common/script/fns/randomDrop';
|
|
import i18n from '../../../website/common/script/i18n';
|
|
import {
|
|
generateUser,
|
|
generateTodo,
|
|
generateHabit,
|
|
generateDaily,
|
|
generateReward,
|
|
} from '../../helpers/common.helper';
|
|
|
|
describe('common.fns.randomDrop', () => {
|
|
let user;
|
|
let task;
|
|
let predictableRandom;
|
|
|
|
beforeEach(() => {
|
|
user = generateUser();
|
|
user._tmp = user._tmp ? user._tmp : {};
|
|
user.items.eggs.Wolf = 0;
|
|
user.items.food.Meat = 0;
|
|
user._id = `a${user._id.slice(1)}`;
|
|
task = generateTodo({ userId: user._id });
|
|
predictableRandom = sandbox.stub().returns(0.5);
|
|
});
|
|
|
|
it('drops an item for the user.party.quest.progress', () => {
|
|
expect(user.party.quest.progress.collectedItems).to.eql(0);
|
|
user.party.quest.key = 'vice2';
|
|
predictableRandom.returns(0.0001);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user.party.quest.progress.collectedItems).to.eql(1);
|
|
expect(user._tmp.quest.collection).to.eql(1);
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user.party.quest.progress.collectedItems).to.eql(2);
|
|
expect(user._tmp.quest.collection).to.eql(1);
|
|
});
|
|
|
|
context('drops enabled', () => {
|
|
beforeEach(() => {
|
|
task.priority = 100000;
|
|
});
|
|
|
|
it('awards an egg and a hatching potion if user has never received any', () => {
|
|
delete user.items.eggs.Wolf;
|
|
randomDrop(user, { task, predictableRandom });
|
|
|
|
expect(user._tmp.firstDrops.egg).to.be.a.string;
|
|
expect(user._tmp.firstDrops.hatchingPotion).to.be.a.string;
|
|
});
|
|
|
|
it('does nothing if user.items.lastDrop.count is exceeded', () => {
|
|
user.items.lastDrop.count = 100;
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop).to.be.undefined;
|
|
});
|
|
|
|
it('drops something when the task is a todo', () => {
|
|
expect(user._tmp).to.eql({});
|
|
predictableRandom.returns(0.1);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp).to.not.eql({});
|
|
});
|
|
|
|
it('drops something when the task is a habit', () => {
|
|
task = generateHabit({ userId: user._id });
|
|
expect(user._tmp).to.eql({});
|
|
predictableRandom.returns(0.1);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp).to.not.eql({});
|
|
});
|
|
|
|
it('drops something when the task is a daily', () => {
|
|
task = generateDaily({ userId: user._id });
|
|
expect(user._tmp).to.eql({});
|
|
predictableRandom.returns(0.1);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp).to.not.eql({});
|
|
});
|
|
|
|
it('drops something when the task is a reward', () => {
|
|
task = generateReward({ userId: user._id });
|
|
expect(user._tmp).to.eql({});
|
|
predictableRandom.returns(0.1);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp).to.not.eql({});
|
|
});
|
|
|
|
it('drops food', () => {
|
|
predictableRandom.returns(0.65);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('Food');
|
|
});
|
|
|
|
it('drops eggs', () => {
|
|
predictableRandom.returns(0.35);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('Egg');
|
|
});
|
|
|
|
context('drops hatching potion', () => {
|
|
it('drops a very rare potion', () => {
|
|
predictableRandom.returns(0.01);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('HatchingPotion');
|
|
expect(user._tmp.drop.value).to.eql(5);
|
|
expect(user._tmp.drop.key).to.eql('Golden');
|
|
});
|
|
|
|
it('drops a rare potion', () => {
|
|
predictableRandom.returns(0.08);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('HatchingPotion');
|
|
expect(user._tmp.drop.value).to.eql(4);
|
|
const acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'];
|
|
// deterministically 'CottonCandyBlue'
|
|
expect(acceptableDrops).to.contain(user._tmp.drop.key);
|
|
});
|
|
|
|
it('drops an uncommon potion', () => {
|
|
predictableRandom.returns(0.17);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('HatchingPotion');
|
|
expect(user._tmp.drop.value).to.eql(3);
|
|
const acceptableDrops = ['Red', 'Shade', 'Skeleton'];
|
|
expect(acceptableDrops).to.contain(user._tmp.drop.key); // always skeleton
|
|
});
|
|
|
|
it('drops a common potion', () => {
|
|
predictableRandom.returns(0.20);
|
|
|
|
randomDrop(user, { task, predictableRandom });
|
|
expect(user._tmp.drop.type).to.eql('HatchingPotion');
|
|
expect(user._tmp.drop.value).to.eql(2);
|
|
const acceptableDrops = ['Base', 'White', 'Desert'];
|
|
expect(acceptableDrops).to.contain(user._tmp.drop.key); // always Desert
|
|
});
|
|
});
|
|
|
|
context('drop cap notification', () => {
|
|
let analytics;
|
|
const req = {};
|
|
let isSubscribedStub;
|
|
|
|
beforeEach(() => {
|
|
user.addNotification = () => {};
|
|
sandbox.stub(user, 'addNotification');
|
|
user.isSubscribed = () => {};
|
|
isSubscribedStub = sandbox.stub(user, 'isSubscribed');
|
|
isSubscribedStub.returns(false);
|
|
analytics = { track () {} };
|
|
sandbox.stub(analytics, 'track');
|
|
});
|
|
|
|
it('sends a notification if A/B test is enabled when drop cap is reached', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-enabled';
|
|
predictableRandom.returns(0.1);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(user.addNotification).to.be.calledOnce;
|
|
expect(user.addNotification).to.be.calledWith('DROP_CAP_REACHED', {
|
|
message: i18n.t('dropCapReached'),
|
|
items: 5,
|
|
});
|
|
});
|
|
|
|
it('does not send a notification if user is enrolled in disabled A/B test group', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-disabled';
|
|
predictableRandom.returns(0.1);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(user.addNotification).to.not.be.called;
|
|
});
|
|
|
|
it('does not send a notification if user is enrolled in disabled A/B test group', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-not-enrolled';
|
|
predictableRandom.returns(0.1);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(user.addNotification).to.not.be.called;
|
|
});
|
|
|
|
it('does not send a notification if drop cap is not reached', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-enabled';
|
|
predictableRandom.returns(0.1);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(4);
|
|
expect(user.addNotification).to.not.be.called;
|
|
});
|
|
|
|
it('does not send a notification if user is subscribed', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-enabled';
|
|
predictableRandom.returns(0.1);
|
|
isSubscribedStub.returns(true);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(user.addNotification).to.not.be.called;
|
|
});
|
|
|
|
it('tracks drop cap reached event for enrolled users (notification enabled)', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-enabled';
|
|
predictableRandom.returns(0.1);
|
|
isSubscribedStub.returns(true);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(analytics.track).to.be.calledWith('drop cap reached');
|
|
});
|
|
|
|
it('tracks drop cap reached event for enrolled users (notification disabled)', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-disabled';
|
|
predictableRandom.returns(0.1);
|
|
isSubscribedStub.returns(true);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(analytics.track).to.be.calledWith('drop cap reached');
|
|
});
|
|
|
|
it('does not track drop cap reached event for users not enrolled in A/B test', () => {
|
|
user._ABtests.dropCapNotif = 'drop-cap-notif-not-enrolled';
|
|
predictableRandom.returns(0.1);
|
|
isSubscribedStub.returns(true);
|
|
|
|
// Max Drop Count is 5
|
|
expect(user.items.lastDrop.count).to.equal(0);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
randomDrop(user, { task, predictableRandom }, req, analytics);
|
|
expect(user.items.lastDrop.count).to.equal(5);
|
|
expect(analytics.track).to.not.be.calledWith('drop cap reached');
|
|
});
|
|
});
|
|
});
|
|
});
|