Remove localstorage and add notifications (#7588)

* 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
This commit is contained in:
Matteo Pagliazzi
2016-06-07 16:14:19 +02:00
parent e0aff79ee4
commit f7be7205e7
49 changed files with 915 additions and 436 deletions

View File

@@ -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 () => {

View File

@@ -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

View File

@@ -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 () => {

View File

@@ -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}`);

View File

@@ -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) => {

View File

@@ -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');

View File

@@ -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;

View File

@@ -32,6 +32,7 @@ describe('response middleware', () => {
expect(res.json).to.be.calledWith({
success: true,
data: {field: 1},
notifications: [],
});
});
@@ -47,6 +48,7 @@ describe('response middleware', () => {
success: true,
data: {field: 1},
message: 'hello',
notifications: [],
});
});
@@ -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: {},
},
],
});
});
});

View File

@@ -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});
});
});
});