Notifications v2 and Bailey API (#9716)
* Added initial bailey api
* wip
* implement new panel header
* Fixed lint
* add ability to mark notification as seen
* add notification count, remove top badge from user and add ability to mark multiple notifications as seen
* add support dismissall and mark all as read
* do not dismiss actionable notif
* mark as seen when menu is opened instead of closed
* implement ordering, list of actionable notifications
* add groups messages and fix badges count
* add notifications for received cards
* send card received notification to target not sender
* rename notificaion field
* fix integration tests
* mark cards notifications as read and update tests
* add mystery items notifications
* add unallocated stats points notifications
* fix linting
* simplify code
* refactoring and fixes
* fix dropdown opening
* start splitting notifications into their own component
* add notifications for inbox messages
* fix unit tests
* fix default buttons styles
* add initial bailey support
* add title and tests to new stuff notification
* add notification if a group task needs more work
* add tests and fixes for marking a task as needing more work
* make sure user._v is updated
* remove console.log
* notification: hover status and margins
* start styling notifications, add separate files and basic functionalities
* fix tests
* start adding mystery items notification
* wip card notification
* fix cards text
* initial implementation inbox messages
* initial implementation group messages
* disable inbox notifications until mobile is ready
* wip group chat messages
* finish mystery and card notifications
* add bailey notification and fix a lot of stuff
* start adding guilds and parties invitations
* misc invitation fixes
* fix lint issues
* remove old code and add key to notifications
* fix tests
* remove unused code
* add link for public guilds invite
* starts to implement needs work notification design and feature
* fixes to needs work, add group task approved notification
* finish needs work feature
* lots of fixes
* implement quest notification
* bailey fixes and static page
* routing fixes
* fixes # this.$store.dispatch(guilds:join, {groupId: group.id, type: party});
* read notifications on click
* chat notifications
* fix tests for chat notifications
* fix chat notification test
* fix tests
* fix tests (again)
* try awaiting
* remove only
* more sleep
* add bailey tests
* fix icons alignment
* fix issue with multiple points notifications
* remove merge code
* fix rejecting guild invitation
* make remove area bigger
* fix error with notifications and add migration
* fix migration
* fix typos
* add cleanup migration too
* notifications empty state, new counter color, fix marking messages as seen in guilds
* fixes
* add image and install correct packages
* fix mongoose version
* update bailey
* typo
* make sure chat is marked as read after other requests
This commit is contained in:
@@ -426,6 +426,9 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id;
|
||||
})).to.exist;
|
||||
});
|
||||
|
||||
it('notifies other users of new messages for a party', async () => {
|
||||
@@ -443,6 +446,9 @@ describe('POST /chat', () => {
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||
expect(memberWithNotification.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id;
|
||||
})).to.exist;
|
||||
});
|
||||
|
||||
context('Spam prevention', () => {
|
||||
|
||||
@@ -24,10 +24,13 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
});
|
||||
|
||||
it('clears new messages for a guild', async () => {
|
||||
await guildMember.sync();
|
||||
const initialNotifications = guildMember.notifications.length;
|
||||
await guildMember.post(`/groups/${guild._id}/chat/seen`);
|
||||
|
||||
let guildThatHasSeenChat = await guildMember.get('/user');
|
||||
|
||||
expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||
expect(guildThatHasSeenChat.newMessages).to.be.empty;
|
||||
});
|
||||
});
|
||||
@@ -53,10 +56,13 @@ describe('POST /groups/:id/chat/seen', () => {
|
||||
});
|
||||
|
||||
it('clears new messages for a party', async () => {
|
||||
await partyMember.sync();
|
||||
const initialNotifications = partyMember.notifications.length;
|
||||
await partyMember.post(`/groups/${party._id}/chat/seen`);
|
||||
|
||||
let partyMemberThatHasSeenChat = await partyMember.get('/user');
|
||||
|
||||
expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,13 +70,21 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
it('removes new messages for that group from user', async () => {
|
||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||
})).to.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||
|
||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||
await leader.sync();
|
||||
|
||||
expect(leader.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||
})).to.not.exist;
|
||||
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
@@ -188,13 +189,20 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
|
||||
it('removes new messages from a member who is removed', async () => {
|
||||
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
|
||||
await sleep(0.5);
|
||||
await removedMember.sync();
|
||||
|
||||
expect(removedMember.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||
})).to.exist;
|
||||
expect(removedMember.newMessages[party._id]).to.not.be.empty;
|
||||
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
|
||||
await removedMember.sync();
|
||||
|
||||
expect(removedMember.notifications.find(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||
})).to.not.exist;
|
||||
expect(removedMember.newMessages[party._id]).to.be.empty;
|
||||
});
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
}]);
|
||||
|
||||
await expect(userToInvite.get('/user'))
|
||||
@@ -127,11 +128,13 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
{
|
||||
id: group._id,
|
||||
name: groupName,
|
||||
inviter: inviter._id,
|
||||
publicGuild: false,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('POST /members/send-private-message', () => {
|
||||
|
||||
it('sends a private message to a user', async () => {
|
||||
let receiver = await generateUser();
|
||||
// const initialNotifications = receiver.notifications.length;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
@@ -115,10 +116,44 @@ describe('POST /members/send-private-message', () => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
|
||||
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
// expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||
// expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||
// expect(notification.data.excerpt).to.equal(messageToSend);
|
||||
// expect(notification.data.sender.id).to.equal(updatedSender._id);
|
||||
// expect(notification.data.sender.name).to.equal(updatedSender.profile.name);
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
});
|
||||
|
||||
// @TODO waiting for mobile support
|
||||
xit('creates a notification with an excerpt if the message is too long', async () => {
|
||||
let receiver = await generateUser();
|
||||
let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars';
|
||||
let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`;
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: longerMessageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let updatedReceiver = await receiver.get('/user');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === longerMessageToSend;
|
||||
});
|
||||
|
||||
const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||
|
||||
expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||
expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
||||
});
|
||||
|
||||
it('allows admin to send when sender has blocked the admin', async () => {
|
||||
userToSendMessage = await generateUser({
|
||||
'contributor.admin': 1,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
requester,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('GET /news', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(async () => {
|
||||
api = requester();
|
||||
});
|
||||
|
||||
it('returns the latest news in html format, does not require authentication', async () => {
|
||||
const res = await api.get('/news');
|
||||
expect(res).to.be.a.string;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
|
||||
describe('POST /news/tell-me-later', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'flags.newStuff': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('marks new stuff as read and adds notification', async () => {
|
||||
expect(user.flags.newStuff).to.equal(true);
|
||||
const initialNotifications = user.notifications.length;
|
||||
|
||||
await user.post('/news/tell-me-later');
|
||||
await user.sync();
|
||||
|
||||
expect(user.flags.newStuff).to.equal(false);
|
||||
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||
|
||||
const notification = user.notifications[user.notifications.length - 1];
|
||||
|
||||
expect(notification.type).to.equal('NEW_STUFF');
|
||||
// should be marked as seen by default so it's not counted in the number of notifications
|
||||
expect(notification.seen).to.equal(true);
|
||||
expect(notification.data.title).to.be.a.string;
|
||||
});
|
||||
|
||||
it('never adds two notifications', async () => {
|
||||
const initialNotifications = user.notifications.length;
|
||||
|
||||
await user.post('/news/tell-me-later');
|
||||
await user.post('/news/tell-me-later');
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/see', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('mark a notification as seen', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}],
|
||||
});
|
||||
|
||||
const userObj = await user.get('/user'); // so we can check that defaults have been applied
|
||||
expect(userObj.notifications.length).to.equal(2);
|
||||
expect(userObj.notifications[0].seen).to.equal(false);
|
||||
|
||||
const res = await user.post(`/notifications/${id}/see`);
|
||||
expect(res).to.deep.equal({
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: true,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(2);
|
||||
expect(user.notifications[0].id).to.equal(id);
|
||||
expect(user.notifications[0].seen).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/read', () => {
|
||||
describe('POST /notifications/read', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
@@ -57,6 +57,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/see', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
|
||||
await expect(user.post('/notifications/see', {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('mark multiple notifications as seen', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
const id3 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id3,
|
||||
type: 'CRON',
|
||||
data: {},
|
||||
seen: false,
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(3);
|
||||
|
||||
const res = await user.post('/notifications/see', {
|
||||
notificationIds: [id, id3],
|
||||
});
|
||||
|
||||
expect(res).to.deep.equal([
|
||||
{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
seen: true,
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
seen: false,
|
||||
}, {
|
||||
id: id3,
|
||||
type: 'CRON',
|
||||
data: {},
|
||||
seen: true,
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(3);
|
||||
expect(user.notifications[0].id).to.equal(id);
|
||||
expect(user.notifications[0].seen).to.equal(true);
|
||||
|
||||
expect(user.notifications[1].id).to.equal(id2);
|
||||
expect(user.notifications[1].seen).to.equal(false);
|
||||
|
||||
expect(user.notifications[2].id).to.equal(id3);
|
||||
expect(user.notifications[2].seen).to.equal(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { find } from 'lodash';
|
||||
|
||||
describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
let user, guild, member, member2, task;
|
||||
|
||||
function findAssignedTask (memberTask) {
|
||||
return memberTask.group.id === guild._id;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
member = members[0];
|
||||
member2 = members[1];
|
||||
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user is not assigned', async () => {
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when user is not the group leader', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('marks as task as needing more work', async () => {
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// score task to require approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||
|
||||
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// Check that the notification approval request has been removed
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
// Check that the notification is correct
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
const taskText = syncedTask.text;
|
||||
const managerName = user.profile.name;
|
||||
|
||||
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||
|
||||
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||
expect(notification.data.task.text).to.equal(taskText);
|
||||
|
||||
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||
expect(notification.data.group.name).to.equal(guild.name);
|
||||
|
||||
expect(notification.data.manager.id).to.equal(user._id);
|
||||
expect(notification.data.manager.name).to.equal(managerName);
|
||||
|
||||
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('allows a manager to mark a task as needing work', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// score task to require approval
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await member2.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||
|
||||
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
// Check that the notification approval request has been removed
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
const taskText = syncedTask.text;
|
||||
const managerName = member2.profile.name;
|
||||
|
||||
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||
|
||||
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||
expect(notification.data.task.text).to.equal(taskText);
|
||||
|
||||
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||
expect(notification.data.group.name).to.equal(guild.name);
|
||||
|
||||
expect(notification.data.manager.id).to.equal(member2._id);
|
||||
expect(notification.data.manager.name).to.equal(managerName);
|
||||
|
||||
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||
await Promise.all([user.sync(), member2.sync()]);
|
||||
|
||||
expect(user.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
|
||||
expect(member2.notifications.find(n => {
|
||||
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||
})).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('prevents marking a task as needing work if it was already approved', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('canOnlyApproveTaskOnce'),
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents marking a task as needing work if it is not waiting for approval', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalWasNotRequested'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ describe('GET /user/anonymized', () => {
|
||||
'achievements.challenges': 'some',
|
||||
'inbox.messages': [{ text: 'some text' }],
|
||||
tags: [{ name: 'some name', challenge: 'some challenge' }],
|
||||
notifications: [],
|
||||
});
|
||||
|
||||
await generateHabit({ userId: user._id });
|
||||
@@ -65,6 +66,7 @@ describe('GET /user/anonymized', () => {
|
||||
expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
|
||||
expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
|
||||
expect(returnedUser.newMessages).to.not.exist;
|
||||
expect(returnedUser.notifications).to.not.exist;
|
||||
expect(returnedUser.profile).to.not.exist;
|
||||
expect(returnedUser.purchased.plan).to.not.exist;
|
||||
expect(returnedUser.contributor).to.not.exist;
|
||||
|
||||
@@ -13,15 +13,20 @@ describe('POST /user/open-mystery-item', () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'purchased.plan.mysteryItems': [mysteryItemKey],
|
||||
notifications: [
|
||||
{type: 'NEW_MYSTERY_ITEMS', data: { items: [mysteryItemKey] }},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('opens a mystery item', async () => {
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
let response = await user.post('/user/open-mystery-item');
|
||||
await user.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
|
||||
expect(response.message).to.equal(t('mysteryItemOpened'));
|
||||
expect(response.data.key).to.eql(mysteryItemKey);
|
||||
|
||||
@@ -26,13 +26,21 @@ describe('POST /user/read-card/:cardType', () => {
|
||||
await user.update({
|
||||
'items.special.greetingReceived': [true],
|
||||
'flags.cardReceived': true,
|
||||
notifications: [{
|
||||
type: 'CARD_RECEIVED',
|
||||
data: {card: cardType},
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
|
||||
let response = await user.post(`/user/read-card/${cardType}`);
|
||||
await user.sync();
|
||||
|
||||
expect(response.message).to.equal(t('readCard', {cardType}));
|
||||
expect(user.items.special[`${cardType}Received`]).to.be.empty;
|
||||
expect(user.flags.cardReceived).to.be.false;
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -420,11 +420,16 @@ describe('payments/index', () => {
|
||||
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.not.be.undefined;
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(2);
|
||||
expect(user.purchased.plan.mysteryItems).to.include('armor_mystery_201605');
|
||||
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('NEW_MYSTERY_ITEMS');
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
@@ -106,6 +106,7 @@ describe('response middleware', () => {
|
||||
type: notification.type,
|
||||
id: notification.id,
|
||||
data: {},
|
||||
seen: false,
|
||||
},
|
||||
],
|
||||
userV: res.locals.user._v,
|
||||
|
||||
@@ -1011,13 +1011,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: '' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${party._id}`]: {
|
||||
name: party.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1032,13 +1025,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
guilds: group._id,
|
||||
_id: { $ne: '' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${group._id}`]: {
|
||||
name: group.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1049,13 +1035,6 @@ describe('Group Model', () => {
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: 'user-id' },
|
||||
}, {
|
||||
$set: {
|
||||
[`newMessages.${party._id}`]: {
|
||||
name: party.name,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -58,21 +58,23 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('can add notifications with data', () => {
|
||||
it('can add notifications with data and already marked as seen', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1});
|
||||
user.addNotification('CRON', {field: 1}, true);
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
@@ -86,7 +88,7 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
@@ -96,6 +98,7 @@ describe('User Model', () => {
|
||||
await user.save();
|
||||
|
||||
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
|
||||
expect(User.pushNotification({_id: user._id}, 'CRON', null, 'INVALID_SEEN')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('adds notifications without data for all given users via static method', async() => {
|
||||
@@ -109,41 +112,45 @@ describe('User Model', () => {
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('adds notifications with data for all given users via static method', async() => {
|
||||
it('adds notifications with data and seen status for all given users via static method', async() => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Bluebird.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1});
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true);
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -322,6 +329,65 @@ describe('User Model', () => {
|
||||
user = await user.save();
|
||||
expect(user.achievements.beastMaster).to.not.equal(true);
|
||||
});
|
||||
|
||||
context('manage unallocated stats points notifications', () => {
|
||||
it('doesn\'t add a notification if there are no points to allocate', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
});
|
||||
|
||||
it('removes a notification if there are no more points to allocate', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount - 1);
|
||||
});
|
||||
|
||||
it('adds a notification if there are points to allocate', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 9;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
});
|
||||
|
||||
it('adds a notification if the points to allocate have changed', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
const oldNotificationsUUID = user.notifications[0].id;
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
|
||||
user.stats.points = 11;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(11);
|
||||
expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('days missed', () => {
|
||||
|
||||
Reference in New Issue
Block a user