Skip to content

Commit

Permalink
Remove localstorage and add notifications (#7588)
Browse files Browse the repository at this point in the history
* move remaining files frm /common/script/public to website/public

* remove localstorage

* add back noscript template and put all javascript in the footer

* fixes client side tests

* remove double quotes where possible

* simplify jade code and add tests for buildManifest

* loading page with logo and spinner

* better loading screen in landscape mode

* icon on top of text logo

* wip: user.notifications

* notifications: simpler and working code

* finish implementing notifications

* correct loading screen css and re-inline images

* add tests for user notifications

* split User model in multiple files

* remove old comment about missing .catch()

* correctly setup hooks and methods for User model. Cleanup localstorage

* include UserNotificationsService in static page js and split loading-screen css in its own file

* add cron notification and misc fixes

* remove console.log

* fix tests

* fix multiple notifications
  • Loading branch information
paglias committed Jun 7, 2016
1 parent e0aff79 commit f7be720
Show file tree
Hide file tree
Showing 49 changed files with 918 additions and 439 deletions.
3 changes: 2 additions & 1 deletion common/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",

"messageUserOperationProtected": "path `<%= operation %>` was not saved, as it's a protected path.",
"messageUserOperationNotFound": "<%= operation %> operation not found"
"messageUserOperationNotFound": "<%= operation %> operation not found",
"messageNotificationNotFound": "Notification not found."
}
3 changes: 2 additions & 1 deletion common/script/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export const MAX_HEALTH = 50;
export const MAX_LEVEL = 100;
export const MAX_STAT_POINTS = MAX_LEVEL;
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';

export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
4 changes: 4 additions & 0 deletions common/script/fns/ultimateGear.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ module.exports = function ultimateGear (user) {
});
return soFarGood && (!found || owned[found.key] === true);
}, true);

if (user.achievements.ultimateGearSets[klass] === true) {
user.addNotification('ULTIMATE_GEAR_ACHIEVEMENT');
}
}
});

Expand Down
3 changes: 3 additions & 0 deletions common/script/fns/updateStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
}
if (!user.flags.dropsEnabled && user.stats.lvl >= 3) {
user.flags.dropsEnabled = true;
user.addNotification('DROPS_ENABLED');

if (user.items.eggs.Wolf > 0) {
user.items.eggs.Wolf++;
} else {
Expand Down Expand Up @@ -92,6 +94,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
}
});
if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
user.addNotification('REBIRTH_ENABLED');
user.flags.rebirthEnabled = true;
}
};
5 changes: 5 additions & 0 deletions common/script/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ api.wrap = function wrapUser (user, main = true) {
user.markModified = function noopMarkModified () {};
}

// same for addNotification
if (!user.addNotification) {
user.addNotification = function noopAddNotification () {};
}

if (main) {
user.ops = {
update: _.partial(importedOps.update, user),
Expand Down
1 change: 0 additions & 1 deletion common/script/ops/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import openMysteryItem from './openMysteryItem';
import scoreTask from './scoreTask';
import markPmsRead from './markPMSRead';


module.exports = {
update,
sleep,
Expand Down
2 changes: 2 additions & 0 deletions common/script/ops/rebirth.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ module.exports = function rebirth (user, tasks = [], req = {}, analytics) {
user.achievements.rebirthLevel = lvl;
}

user.addNotification('REBIRTH_ACHIEVEMENT');

user.stats.buffs = {};

if (req.v2 === true) {
Expand Down
5 changes: 4 additions & 1 deletion common/script/ops/scoreTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ module.exports = function scoreTask (options = {}, req = {}) {
if (direction === 'up') {
task.streak += 1;
// Give a streak achievement when the streak is a multiple of 21
if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
if (task.streak % 21 === 0) {
user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
user.addNotification('STREAK_ACHIEVEMENT');
}
task.completed = true;
} else if (direction === 'down') {
// Remove a streak achievement if streak was a multiple of 21 and the daily was undone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
await sleep(0.5);

await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
expect(winningUser.notifications.length).to.equal(1);
expect(winningUser.notifications[0].type).to.equal('WON_CHALLENGE');
});

it('gives winner gems as reward', async () => {
Expand Down
17 changes: 10 additions & 7 deletions test/api/v3/integration/dataexport/GET-export_history.csv.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ describe('GET /export/history.csv', () => {
]);

// score all the tasks twice
await Promise.all(tasks.map(task => {
return user.post(`/tasks/${task._id}/score/up`);
}));
await Promise.all(tasks.map(task => {
return user.post(`/tasks/${task._id}/score/up`);
}));
await user.post(`/tasks/${tasks[0]._id}/score/up`);
await user.post(`/tasks/${tasks[1]._id}/score/up`);
await user.post(`/tasks/${tasks[2]._id}/score/up`);
await user.post(`/tasks/${tasks[3]._id}/score/up`);

await user.post(`/tasks/${tasks[0]._id}/score/up`);
await user.post(`/tasks/${tasks[1]._id}/score/up`);
await user.post(`/tasks/${tasks[2]._id}/score/up`);
await user.post(`/tasks/${tasks[3]._id}/score/up`);

// adding an history entry to daily 1 manually because cron didn't run yet
await updateDocument('tasks', tasks[1], {
history: {value: 3.2, date: Number(new Date())},
history: [{value: 3.2, date: Number(new Date())}],
});

// get updated tasks
Expand Down
2 changes: 2 additions & 0 deletions test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ describe('PUT /heroes/:heroId', () => {
expect(hero.contributor.level).to.equal(1);
expect(hero.purchased.ads).to.equal(true);
expect(hero.auth.blocked).to.equal(true);
expect(hero.notifications.length).to.equal(1);
expect(hero.notifications[0].type).to.equal('NEW_CONTRIBUTOR_LEVEL');
});

it('updates contributor level', async () => {
Expand Down
3 changes: 3 additions & 0 deletions test/api/v3/integration/user/POST-user_rebirth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ describe('POST /user/rebirth', () => {
let response = await user.post('/user/rebirth');
await user.sync();

expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].type).to.equal('REBIRTH_ACHIEVEMENT');

let updatedDaily = await user.get(`/tasks/${daily._id}`);
let updatedReward = await user.get(`/tasks/${reward._id}`);

Expand Down
1 change: 1 addition & 0 deletions test/api/v3/integration/user/PUT-user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('PUT /user', () => {
backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'},
subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
notifications: [{type: 123}],
};

each(protectedOperations, (data, testName) => {
Expand Down
12 changes: 12 additions & 0 deletions test/api/v3/unit/libs/buildManifest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ describe('Build Manifest', () => {
expect(htmlCode.startsWith('<script') || htmlCode.startsWith('<link')).to.be.true;
});

it('can return only js files', () => {
let htmlCode = getManifestFiles('app', 'js');

expect(htmlCode.indexOf('<link') === -1).to.be.true;
});

it('can return only css files', () => {
let htmlCode = getManifestFiles('app', 'css');

expect(htmlCode.indexOf('<script') === -1).to.be.true;
});

it('throws an error in case the page does not exist', () => {
expect(() => {
getManifestFiles('strange name here');
Expand Down
54 changes: 54 additions & 0 deletions test/api/v3/unit/libs/cron.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,60 @@ describe('cron', () => {
});
});

describe('notifications', () => {
it('adds a user notification', () => {
let mpBefore = user.stats.mp;
tasksByType.dailys[0].completed = true;
user._statsComputed.maxMP = 100;

daysMissed = 1;
let hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});

cron({user, tasksByType, daysMissed, analytics});

expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].type).to.equal('CRON');
expect(user.notifications[0].data).to.eql({
hp: user.stats.hp - hpBefore,
mp: user.stats.mp - mpBefore,
});
});

it('condenses multiple notifications into one', () => {
let mpBefore1 = user.stats.mp;
tasksByType.dailys[0].completed = true;
user._statsComputed.maxMP = 100;

daysMissed = 1;
let hpBefore1 = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});

cron({user, tasksByType, daysMissed, analytics});

expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].type).to.equal('CRON');
expect(user.notifications[0].data).to.eql({
hp: user.stats.hp - hpBefore1,
mp: user.stats.mp - mpBefore1,
});

let hpBefore2 = user.stats.hp;
let mpBefore2 = user.stats.mp;

user.lastCron = moment(new Date()).subtract({days: 2});

cron({user, tasksByType, daysMissed, analytics});

expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].type).to.equal('CRON');
expect(user.notifications[0].data).to.eql({
hp: user.stats.hp - hpBefore2 - (hpBefore2 - hpBefore1),
mp: user.stats.mp - mpBefore2 - (mpBefore2 - mpBefore1),
});
});
});

describe('private messages', () => {
let lastMessageId;

Expand Down
41 changes: 41 additions & 0 deletions test/api/v3/unit/middlewares/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('response middleware', () => {
expect(res.json).to.be.calledWith({
success: true,
data: {field: 1},
notifications: [],
});
});

Expand All @@ -47,6 +48,7 @@ describe('response middleware', () => {
success: true,
data: {field: 1},
message: 'hello',
notifications: [],
});
});

Expand All @@ -61,6 +63,45 @@ describe('response middleware', () => {
expect(res.json).to.be.calledWith({
success: false,
data: {field: 1},
notifications: [],
});
});

it('returns userV if a user is authenticated req.query.userV is passed', () => {
responseMiddleware(req, res, next);
req.query.userV = 3;
res.respond(200, {field: 1});

expect(res.json).to.be.calledOnce;

expect(res.json).to.be.calledWith({
success: true,
data: {field: 1},
notifications: [],
userV: 0,
});
});

it('returns notifications if a user is authenticated', () => {
res.locals.user.notifications.push({type: 'NEW_CONTRIBUTOR_LEVEL'});
let notification = res.locals.user.notifications[0].toJSON();

responseMiddleware(req, res, next);
res.respond(200, {field: 1});

expect(res.json).to.be.calledOnce;

expect(res.json).to.be.calledWith({
success: true,
data: {field: 1},
notifications: [
{
type: notification.type,
id: notification.id,
createdAt: notification.createdAt,
data: {},
},
],
});
});
});
26 changes: 26 additions & 0 deletions test/api/v3/unit/models/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,30 @@ describe('User Model', () => {
expect(toJSON._tmp).to.eql({ok: true});
expect(toJSON).to.not.have.keys('_nonTmp');
});

context('notifications', () => {
it('can add notifications with data', () => {
let user = new User();

user.addNotification('CRON');

let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({});
});

it('can add notifications without data', () => {
let user = new User();

user.addNotification('CRON', {field: 1});

let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'createdAt']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({field: 1});
});
});
});
4 changes: 4 additions & 0 deletions test/common/fns/ultimateGear.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('shared.fns.ultimateGear', () => {
user.achievements.ultimateGearSets.toObject = function () {
return this;
};
user.addNotification = sinon.spy();
});

it('sets armoirEnabled when partial achievement already achieved', () => {
Expand All @@ -31,7 +32,10 @@ describe('shared.fns.ultimateGear', () => {

user.items = items;
ultimateGear(user);

expect(user.flags.armoireEnabled).to.equal(true);
expect(user.addNotification).to.be.calledOnce;
expect(user.addNotification).to.be.calledWith('ULTIMATE_GEAR_ACHIEVEMENT');
});

it('does not set armoirEnabled when gear is not owned', () => {
Expand Down
15 changes: 15 additions & 0 deletions test/common/fns/updateStats.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('common.fns.updateStats', () => {

beforeEach(() => {
user = generateUser();
user.addNotification = sinon.spy();
});

context('No Hp', () => {
Expand Down Expand Up @@ -109,6 +110,20 @@ describe('common.fns.updateStats', () => {
expect(user.stats.points).to.eql(10);
});

it('add user notification when drops are enabled', () => {
user.stats.lvl = 3;
updateStats(user, { });
expect(user.addNotification).to.be.calledOnce;
expect(user.addNotification).to.be.calledWith('DROPS_ENABLED');
});

it('add user notification when rebirth is enabled', () => {
user.stats.lvl = 51;
updateStats(user, { });
expect(user.addNotification).to.be.calledTwice; // once is for drops enabled
expect(user.addNotification).to.be.calledWith('REBIRTH_ENABLED');
});

context('assigns flags.levelDrops', () => {
it('for atom1', () => {
user.stats.lvl = 16;
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/api-unit.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ afterEach((done) => {
export { sleep } from './sleep';

export function generateUser (options = {}) {
return new User(options).toObject();
return new User(options);
}

export function generateGroup (options = {}) {
Expand Down
1 change: 1 addition & 0 deletions test/spec/controllers/filtersCtrlSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('Filters Controller', function() {
scope = $rootScope.$new();
// user.filters = {};
User.setUser(user);
User.user.filters = {};
userService = User;
$controller('FiltersCtrl', {$scope: scope, User: User});
}));
Expand Down
2 changes: 0 additions & 2 deletions test/spec/services/userServicesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ describe('userServices', function() {
it('saves user data to local storage', function(){
user.save();
var settings = JSON.parse(localStorage[STORAGE_SETTINGS_ID]);
var user_id = JSON.parse(localStorage[STORAGE_USER_ID]);
expect(settings).to.eql(user.settings);
expect(user_id).to.eql(user.user);
});

xit('alerts when not authenticated', function(){
Expand Down
Loading

0 comments on commit f7be720

Please sign in to comment.