UNPKG

box-ui-elements-mlh

Version:
1,424 lines (1,250 loc) 50.1 kB
import commonMessages from '../../elements/common/messages'; import messages from '../messages'; import * as sorter from '../../utils/sorter'; import * as error from '../../utils/error'; import { IS_ERROR_DISPLAYED, TASK_NEW_NOT_STARTED, TASK_MAX_GROUP_ASSIGNEES } from '../../constants'; import Feed from '../Feed'; import { annotation as mockAnnotation } from '../../__mocks__/annotations'; const mockTask = { created_by: { type: 'task_collaborator', target: { name: 'Jay-Z', id: '100' }, id: '000', role: 'CREATOR', status: TASK_NEW_NOT_STARTED, }, created_at: '2019-01-01', due_at: '2019-02-02', id: '0', name: 'task message', type: 'task', assigned_to: { entries: [ { id: '1', target: { name: 'Beyonce', id: '2', avatar_url: '', type: 'user' }, status: TASK_NEW_NOT_STARTED, permissions: { can_delete: false, can_update: false, }, role: 'ASSIGNEE', type: 'task_collaborator', }, ], limit: 10, next_marker: null, }, permissions: { can_update: false, can_delete: false, can_create_task_collaborator: false, can_create_task_link: false, }, task_links: { entries: [ { id: '03', type: 'task_link', target: { type: 'file', id: '4', }, permissions: { can_delete: false, can_update: false, }, }, ], limit: 1, next_marker: null, }, status: TASK_NEW_NOT_STARTED, }; const mockErrors = [{ code: 'error_code_0' }, { code: 'error_code_1' }]; const mockFirstVersion = { action: 'upload', type: 'file_version', id: 123, created_at: 'Thu Sep 20 33658 19:45:39 GMT-0600 (CST)', trashed_at: 1234567891, modified_at: 1234567891, modified_by: { name: 'Akon', id: 11 }, }; const mockCurrentVersion = { action: 'restore', type: 'file_version', id: '123', }; const deleted_version = { action: 'delete', type: 'file_version', id: 234, created_at: 'Thu Sep 20 33658 19:45:39 GMT-0600 (CST)', trashed_at: 1234567891, modified_at: 1234567891, modified_by: { name: 'Akon', id: 11 }, }; const versions = { total_count: 1, entries: [mockFirstVersion, deleted_version], }; const versionsWithCurrent = { total_count: 3, entries: [mockCurrentVersion, mockFirstVersion, deleted_version], }; const annotations = { entries: [mockAnnotation], limit: 1000, next_marker: null, }; jest.mock('lodash/uniqueId', () => () => 'uniqueId'); const mockCreateTaskWithDeps = jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }); jest.mock('../tasks/TasksNew', () => { const task = mockTask; return jest.fn().mockImplementation(() => ({ createTaskWithDeps: mockCreateTaskWithDeps, updateTaskWithDeps: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), deleteTask: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), getTasksForFile: jest.fn().mockReturnValue({ entries: [task], next_marker: null, limit: 1000 }), getTask: jest.fn(({ successCallback }) => { successCallback(task); }), })); }); jest.mock('../tasks/TaskCollaborators', () => jest.fn().mockImplementation(() => ({ createTaskCollaborator: jest.fn().mockImplementation(({ successCallback }) => { successCallback([ { type: 'task_collaborator', id: '1', status: 'NOT_STARTED', role: 'ASSIGNEE', target: { type: 'user', id: '00000001', name: 'Box One', login: 'boxOne@box.com', }, permissions: { can_update: true, can_delete: true, }, }, ]); }), createTaskCollaboratorsforGroup: jest.fn().mockImplementation(({ successCallback }) => { successCallback({ type: 'task_collaborator', id: '2', status: 'NOT_STARTED', role: 'ASSIGNEE', target: { type: 'user', id: '00000002', name: 'Box two', login: 'boxTwo@box.com', }, permissions: { can_update: true, can_delete: true, }, }); }), updateTaskCollaborator: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), deleteTaskCollaborator: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), getTaskCollaborators: jest.fn().mockReturnValue({ entries: [ { id: '1', target: { name: 'Beyonce', id: '2', avatar_url: '', type: 'user' }, status: 'NOT_STARTED', permissions: { can_delete: false, can_update: false, }, role: 'ASSIGNEE', type: 'task_collaborator', }, ], next_marker: null, limit: 1000, }), })), ); jest.mock('../tasks/TaskLinks', () => jest.fn().mockImplementation(() => ({ createTaskLink: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), })), ); const mockGetGroupCount = jest.fn(); jest.mock('../Groups', () => { const GroupAPI = jest.fn().mockImplementation(() => ({ getGroupCount: mockGetGroupCount, })); return GroupAPI; }); jest.mock('../Comments', () => jest.fn().mockImplementation(() => ({ getComments: jest.fn().mockReturnValue({ total_count: 1, entries: [ { type: 'comment', id: '123', created_at: 'Thu Sep 26 33658 19:46:39 GMT-0600 (CST)', tagged_message: 'test @[123:Jeezy] @[10:Kanye West]', created_by: { name: 'Akon', id: 11 }, }, ], }), deleteComment: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), updateComment: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), createComment: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), })), ); jest.mock('../Versions', () => { return jest.fn().mockImplementation(() => ({ getVersions: jest.fn(() => mockFirstVersion), getVersion: jest.fn(() => mockCurrentVersion), })); }); jest.mock('../Annotations', () => jest.fn().mockImplementation(() => ({ deleteAnnotation: jest.fn().mockImplementation((file, id, permissions, successCallback) => { successCallback(); }), updateAnnotation: jest.fn().mockImplementation((file, id, text, permissions, successCallback) => { successCallback(); }), getAnnotations: jest.fn(), })), ); const MOCK_APP_ACTIVITY_ITEM = { activity_template: { id: '1', type: 'activity_template', }, app: { icon_url: 'https://some.cdn.com/12345.png', id: '123456', name: 'App activities test', type: 'app', }, created_by: { id: '1234556789876', login: 'some-account@box.com', name: 'John Doe', type: 'user', }, id: '3782', occurred_at: '2019-02-21T04:00:00Z', rendered_text: 'You shared this file in <a href="https://some-app.com" rel="noreferrer noopener">Some App</a>', type: 'app_activity', }; jest.mock('../AppActivity', () => jest.fn().mockImplementation(() => ({ getAppActivity: jest.fn().mockReturnValue({ total_count: 1, entries: [MOCK_APP_ACTIVITY_ITEM], }), deleteAppActivity: jest.fn().mockImplementation(({ successCallback }) => { successCallback(); }), })), ); describe('api/Feed', () => { let feed; const comments = { total_count: 1, entries: [ { type: 'comment', id: '123', created_at: 'Thu Sep 26 33658 19:46:39 GMT-0600 (CST)', tagged_message: 'test @[123:Jeezy] @[10:Kanye West]', created_by: { name: 'Akon', id: 11 }, }, ], }; const tasks = { entries: [mockTask], limit: 1000, next_marker: null, }; const appActivities = { total_count: 1, entries: [MOCK_APP_ACTIVITY_ITEM], }; const feedItems = [...comments.entries, ...tasks.entries, ...versions.entries, ...appActivities.entries]; const file = { id: '12345', permissions: { can_comment: true, can_create_annotations: true, can_view_annotations: true, }, modified_at: 1234567891, file_version: { id: 987, }, restored_from: { id: mockFirstVersion.id, type: mockFirstVersion.type, }, }; const user = { id: 'user1' }; const fileError = 'Bad box item!'; const errorCode = 'foo'; beforeEach(() => { feed = new Feed({}); jest.spyOn(global.console, 'error').mockImplementation(); }); afterEach(() => { feed.errors = []; jest.restoreAllMocks(); }); describe('getCacheKey()', () => { test('should return the cache key', () => { expect(feed.getCacheKey('1')).toBe('feedItems_1'); }); }); describe('getCachedItems()', () => { beforeEach(() => { feed.getCache = jest.fn().mockReturnValue({ set: jest.fn(), get: jest.fn().mockReturnValue(feedItems), }); feed.getCacheKey = jest.fn().mockReturnValue('baz'); }); test('should get the cached items', () => { const id = '1'; const result = feed.getCachedItems(id); expect(feed.getCacheKey).toHaveBeenCalledWith(id); expect(result).toEqual(feedItems); }); }); describe('setCachedItems()', () => { let cache; const cacheKey = 'baz'; beforeEach(() => { cache = { get: jest.fn().mockRejectedValue(feedItems), set: jest.fn(), }; feed.getCache = jest.fn().mockReturnValue(cache); feed.getCacheKey = jest.fn().mockReturnValue(cacheKey); }); test('should set the cached items', () => { const id = '1'; feed.setCachedItems(id, feedItems); expect(feed.getCacheKey).toHaveBeenCalledWith(id); expect(cache.set).toHaveBeenCalledWith(cacheKey, { errors: [], items: feedItems, }); }); }); describe('feedItems()', () => { const sortedItems = [ ...versionsWithCurrent.entries, ...tasks.entries, ...comments.entries, ...appActivities.entries, ...annotations.entries, ]; let successCb; let errorCb; beforeEach(() => { feed.fetchVersions = jest.fn().mockResolvedValue(versions); feed.fetchCurrentVersion = jest.fn().mockResolvedValue(mockCurrentVersion); feed.fetchTasksNew = jest.fn().mockResolvedValue(tasks); feed.fetchComments = jest.fn().mockResolvedValue(comments); feed.fetchAppActivity = jest.fn().mockReturnValue(appActivities); feed.fetchAnnotations = jest.fn().mockReturnValue(annotations); feed.setCachedItems = jest.fn(); feed.versionsAPI = { getVersion: jest.fn().mockReturnValue(versions), addCurrentVersion: jest.fn().mockReturnValue(versionsWithCurrent), }; successCb = jest.fn(); errorCb = jest.fn(); feed.isDestroyed = jest.fn().mockReturnValue(false); jest.spyOn(sorter, 'sortFeedItems').mockReturnValue(sortedItems); }); afterEach(() => { jest.restoreAllMocks(); }); test('should get feed items, sort, save to cache, and call the success callback', done => { feed.feedItems(file, false, successCb, errorCb, jest.fn(), { shouldShowAnnotations: true, shouldShowAppActivity: true, }); setImmediate(() => { expect(feed.versionsAPI.addCurrentVersion).toHaveBeenCalledWith(mockCurrentVersion, versions, file); expect(sorter.sortFeedItems).toHaveBeenCalledWith( versionsWithCurrent, comments, tasks, appActivities, annotations, ); expect(feed.setCachedItems).toHaveBeenCalledWith(file.id, sortedItems); expect(successCb).toHaveBeenCalledWith(sortedItems); done(); }); }); test('should get feed items, sort, save to cache, and call the error callback', done => { feed.fetchVersions = function fetchVersionsWithError() { this.errors = mockErrors; return []; }; feed.feedItems(file, false, successCb, errorCb); setImmediate(() => { expect(errorCb).toHaveBeenCalledWith(sortedItems, mockErrors); done(); }); }); test('should use the app activity api if the { shouldShowAppActivity } option in the last argument is true', done => { feed.feedItems(file, false, successCb, errorCb, errorCb, { shouldShowAppActivity: true }); setImmediate(() => { expect(feed.fetchAppActivity).toHaveBeenCalled(); done(); }); }); test('should not use the app activity api if the { shouldShowAppActivity } option in the last argument is false', done => { feed.feedItems(file, false, successCb, errorCb, errorCb, { shouldShowAppActivity: false }); setImmediate(() => { expect(feed.fetchAppActivity).not.toHaveBeenCalled(); done(); }); }); test('should use the annotations api if shouldShowannotations is true', done => { feed.feedItems(file, false, successCb, errorCb, errorCb, { shouldShowAnnotations: true }); setImmediate(() => { expect(feed.fetchAnnotations).toHaveBeenCalled(); done(); }); }); test('should not use the annotations api if shouldShowannotations is false', done => { feed.feedItems(file, false, successCb, errorCb, errorCb, { shouldShowAnnotations: false }); setImmediate(() => { expect(feed.fetchAnnotations).not.toHaveBeenCalled(); done(); }); }); test('should not call success or error callback if it is destroyed', done => { feed.isDestroyed = jest.fn().mockReturnValue(true); feed.feedItems(file, false, successCb, errorCb); setImmediate(() => { expect(successCb).not.toHaveBeenCalled(); expect(errorCb).not.toHaveBeenCalled(); done(); }); }); test('should return the cached items', () => { feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); feed.feedItems(file, false, successCb, errorCb); expect(feed.getCachedItems).toHaveBeenCalledWith(file.id); expect(successCb).toHaveBeenCalledWith(feedItems); }); test('should refresh the cache after returning the cached items', done => { feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); feed.feedItems(file, true, successCb, errorCb); expect(feed.getCachedItems).toHaveBeenCalledWith(file.id); expect(successCb).toHaveBeenCalledTimes(1); expect(successCb).toHaveBeenCalledWith(feedItems); // refresh cache setImmediate(() => { expect(successCb).toHaveBeenCalledTimes(2); done(); }); }); }); describe('fetchAnnotations()', () => { beforeEach(() => { feed.file = file; feed.fetchFeedItemErrorCallback = jest.fn(); }); test('should return a promise and call the annotations api', () => { const annotationItems = feed.fetchAnnotations(); expect(annotationItems instanceof Promise).toBeTruthy(); expect(feed.annotationsAPI.getAnnotations).toBeCalled(); }); }); describe('fetchComments()', () => { beforeEach(() => { feed.file = file; feed.fetchFeedItemErrorCallback = jest.fn(); }); test('should return a promise and call the comments api', () => { const commentItems = feed.fetchComments(); expect(commentItems instanceof Promise).toBeTruthy(); expect(feed.commentsAPI.getComments).toBeCalled(); }); }); describe('Fetching Base Items', () => { beforeEach(() => { feed.file = file; feed.fetchFeedItemErrorCallback = jest.fn(); }); describe('fetchVersions()', () => { test('should return a promise and call the versions api', () => { const versionItems = feed.fetchVersions(); expect(versionItems instanceof Promise).toBeTruthy(); expect(feed.versionsAPI.getVersions).toBeCalled(); }); }); describe('fetchCurrentVersion()', () => { test('should return a promise and call the versions api', () => { const currentVersion = feed.fetchCurrentVersion(); expect(currentVersion instanceof Promise).toBeTruthy(); expect(feed.versionsAPI.getVersion).toBeCalled(); }); }); describe('fetchAppActivity()', () => { test('should return a promise and call the app activity api', () => { const activityItems = feed.fetchAppActivity(); expect(activityItems instanceof Promise).toBeTruthy(); expect(feed.appActivityAPI.getAppActivity).toBeCalled(); }); }); describe('fetchTasksNew()', () => { test('should return a promise and call the tasks api', () => { const taskItems = feed.fetchTasksNew(); expect(taskItems instanceof Promise).toBeTruthy(); expect(feed.tasksNewAPI.getTasksForFile).toBeCalled(); }); }); }); describe('updateTaskCollaborator()', () => { beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.updateTaskCollaboratorSuccessCallback = jest.fn(); }); test('should throw if no file id', () => { expect(() => feed.updateTaskCollaborator({})).toThrow(fileError); }); test('should call the tasks collaborators api and if successful, the success callback', () => { feed.updateTaskCollaborator(file); expect(feed.taskCollaboratorsAPI.length).toBe(1); expect(feed.taskCollaboratorsAPI.pop().updateTaskCollaborator).toBeCalled(); expect(feed.updateTaskCollaboratorSuccessCallback).toBeCalled(); }); }); describe('updateTaskCollaboratorSuccessCallback()', () => { beforeEach(() => { feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); feed.updateFeedItem = jest.fn(); }); test('should refresh the task from the server and call the success callback', () => { const updatedStatus = 'COMPLETED'; const successCb = jest.fn(); const taskId = mockTask.id; feed.updateTaskCollaboratorSuccessCallback( taskId, file, { ...tasks.entries[0].assigned_to.entries[0], status: updatedStatus, }, successCb, ); expect(feed.tasksNewAPI.getTask).toBeCalled(); expect(feed.updateFeedItem).toBeCalledWith({ isPending: false }, taskId); expect(successCb).toBeCalled(); }); }); describe('createTaskNew()', () => { const currentUser = { id: 'bar', }; const message = 'hi'; const assignees = [ { id: '3086276240', type: 'group', name: 'Test Group', item: { id: '3086276240', name: 'Test User', type: 'group', }, }, ]; const taskType = 'GENERAL'; const taskCompletionRule = 'ALL_ASSIGNEES'; const dueAt = null; const code = 'group_exceeds_limit'; const hasError = false; beforeEach(() => { feed.feedErrorCallback = jest.fn(); }); test('should check group size by calling groups endpoint', async () => { const mockSuccessCallback = jest.fn(); const mockErrorCallback = jest.fn(); mockGetGroupCount.mockResolvedValueOnce({ total_count: TASK_MAX_GROUP_ASSIGNEES - 1 }); feed.createTaskNew( file, currentUser, message, assignees, taskType, dueAt, taskCompletionRule, mockSuccessCallback, mockErrorCallback, ); expect(feed.file.id).toBe(file.id); await new Promise(r => setTimeout(r, 0)); expect(mockGetGroupCount).toBeCalled(); expect(mockCreateTaskWithDeps).toBeCalled(); }); test('should call error handling when group size exceeds limit', async () => { const mockSuccessCallback = jest.fn(); const mockErrorCallback = jest.fn(); mockGetGroupCount.mockResolvedValueOnce({ total_count: TASK_MAX_GROUP_ASSIGNEES + 1 }); await feed.createTaskNew( file, currentUser, message, assignees, taskType, dueAt, taskCompletionRule, mockSuccessCallback, mockErrorCallback, ); await new Promise(r => setTimeout(r, 0)); expect(feed.file.id).toBe(file.id); expect(mockGetGroupCount).toBeCalled(); expect(mockCreateTaskWithDeps).not.toBeCalled(); expect(feed.feedErrorCallback).toBeCalledWith( hasError, { code: 'group_exceeds_limit', type: 'warning' }, code, ); }); }); describe('updateTaskNew()', () => { const code = 'group_exceeds_limit'; const hasError = false; beforeEach(() => { feed.updateFeedItem = jest.fn(); }); test('should throw if no file id', async () => { expect.assertions(1); const updatedTask = feed.updateTaskNew({}); await expect(updatedTask).rejects.toEqual(Error(fileError)); }); test('should check group size by calling groups endpoint', async () => { const mockSuccessCallback = jest.fn(); const mockErrorCallback = jest.fn(); mockGetGroupCount.mockResolvedValueOnce({ total_count: TASK_MAX_GROUP_ASSIGNEES - 1 }); const task = { id: '1', description: 'updated description', addedAssignees: [ { type: 'task_collaborator', id: '19283765', item: { type: 'group', id: '19283765', name: 'adding a group', login: '' }, role: 'ASSIGNEE', permissions: { can_delete: true, can_update: true, }, status: 'incomplete', }, ], }; feed.updateTaskNew(file, task, mockSuccessCallback, mockErrorCallback); expect(feed.file.id).toBe(file.id); await new Promise(r => setTimeout(r, 0)); expect(mockGetGroupCount).toBeCalled(); }); test('should call the feed error handling when group size exceeds limit on update', async () => { const mockSuccessCallback = jest.fn(); const mockErrorCallback = jest.fn(); feed.feedErrorCallback = jest.fn(); feed.createTaskCollaborator = jest.fn(); feed.createTaskCollaboratorsforGroup = jest.fn(); mockGetGroupCount.mockResolvedValueOnce({ total_count: TASK_MAX_GROUP_ASSIGNEES + 1 }); const task = { id: '1', description: 'updated description', addedAssignees: [ { type: 'task_collaborator', id: '19283765', item: { type: 'group', id: '19283765', name: 'adding a group', login: '' }, role: 'ASSIGNEE', permissions: { can_delete: true, can_update: true, }, status: 'incomplete', }, ], }; feed.updateTaskNew(file, task, mockSuccessCallback, mockErrorCallback); expect(feed.file.id).toBe(file.id); await new Promise(r => setTimeout(r, 0)); expect(mockGetGroupCount).toBeCalled(); expect(feed.feedErrorCallback).toBeCalledWith( hasError, { code: 'group_exceeds_limit', type: 'warning' }, code, ); expect(feed.tasksNewAPI.updateTaskWithDeps).not.toBeCalled(); }); test('should call the new task api and if successful, the success callback', async () => { const successCallback = jest.fn(); const task = { id: '1', description: 'updated description', addedAssignees: [], removedAssignees: [], }; feed.updateTaskNew(file, task, successCallback, jest.fn()); expect(feed.file.id).toBe(file.id); // push a new promise to trigger the promises in updateTaskNew await new Promise(r => setTimeout(r, 0)); expect(feed.tasksNewAPI.updateTaskWithDeps).toBeCalled(); expect(feed.tasksNewAPI.getTask).toBeCalled(); expect(feed.updateFeedItem).toBeCalledTimes(2); expect(successCallback).toBeCalled(); }); test('should call the appropriate new task APIs when adding new assignees', async () => { const successCallback = jest.fn(); const task = { id: '1', description: 'updated description', addedAssignees: [ { type: 'user', id: '3086276240', name: 'Test User', login: 'testuser@foo.com', }, { type: 'group', id: '3086276240', name: 'Test User', login: 'testuser@foo.com', }, ], removedAssignees: [ { type: 'task_collaborator', id: '19283765', target: { type: 'user', id: '19283765', name: 'remove Test User', login: 'testuser@foo.com' }, role: 'ASSIGNEE', permissions: { can_delete: true, can_update: true, }, status: 'incomplete', }, ], }; feed.updateTaskNew(file, task, successCallback, jest.fn()); await new Promise(r => setTimeout(r, 0)); expect(feed.tasksNewAPI.updateTaskWithDeps).toBeCalled(); expect(feed.updateFeedItem).toBeCalled(); expect(successCallback).toBeCalled(); }); }); describe('updateComment()', () => { beforeEach(() => { feed.updateFeedItem = jest.fn(); }); test('should throw if no file id', () => { expect(() => feed.updateComment({})).toThrow(fileError); }); test('should call the comments api and update the feed items', () => { const successCallback = jest.fn(); const comment = { id: '1', tagged_message: 'updated message', message: 'update message', permissions: { can_edit: true }, }; feed.updateComment(file, comment.id, comment.text, true, comment.permmissions, successCallback, jest.fn()); expect(feed.updateFeedItem).toBeCalled(); expect(successCallback).toBeCalled(); }); }); describe('deleteComment()', () => { beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.deleteFeedItem = jest.fn(); }); test('should throw if no file id', () => { expect(() => feed.deleteComment({})).toThrow(fileError); }); test('should call the comments api and if successful, the success callback', () => { feed.deleteComment(file); expect(feed.commentsAPI.deleteComment).toBeCalled(); expect(feed.deleteFeedItem).toBeCalled(); }); }); describe('deleteCommentErrorCallback()', () => { const e = new Error('foo'); beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.createFeedError = jest.fn().mockReturnValue(error); feed.feedErrorCallback = jest.fn(); }); test('should update the feed item and call the error callback', () => { const commentId = '1'; feed.deleteCommentErrorCallback(e, errorCode, commentId); expect(feed.updateFeedItem).toBeCalledWith(error, commentId); expect(feed.feedErrorCallback).toBeCalledWith(true, e, errorCode); }); }); describe('deleteAppActivity()', () => { beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.deleteFeedItem = jest.fn(); }); test('should throw if no file id', () => { expect(() => feed.deleteAppActivity({})).toThrow(fileError); }); test('should call the app activity api and if successful, the success callback', () => { const activityItemId = '12345'; feed.deleteAppActivity(file, activityItemId); expect(feed.appActivityAPI.deleteAppActivity).toBeCalled(); expect(feed.deleteFeedItem).toBeCalled(); }); }); describe('deleteTaskNew()', () => { beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.deleteFeedItem = jest.fn(); }); test('should throw if no file id', () => { expect(() => feed.deleteTaskNew({})).toThrow(fileError); }); test('should call the new task api and if successful, the success callback', () => { feed.deleteTaskNew(file, { id: '1' }); expect(feed.file.id).toBe(file.id); expect(feed.tasksNewAPI.deleteTask).toBeCalled(); expect(feed.deleteFeedItem).toBeCalled(); }); }); describe('deleteFeedItem()', () => { let successCb; const feedItemId = feedItems[0].id; beforeEach(() => { feed.setCachedItems = jest.fn(); feed.file = file; successCb = jest.fn(); }); test('should delete the feed item and call success callback', () => { feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); feed.deleteFeedItem(feedItemId, successCb); expect(feed.setCachedItems.mock.calls[0][1].length).toBe(feedItems.length - 1); expect(successCb).toBeCalled(); }); test('not call the success callback', () => { feed.isDestroyed = jest.fn().mockReturnValue(true); feed.deleteFeedItem(feedItemId, successCb); expect(successCb).not.toBeCalled(); }); }); describe('feedErrorCallback()', () => { const code = 'foo'; const e = new Error('bar'); beforeEach(() => { jest.spyOn(global.console, 'error').mockImplementation(); feed.errorCallback = jest.fn(); }); afterEach(() => { jest.restoreAllMocks(); }); test('should log the error and set the errors property if its not destroyed and hasError is set to true', () => { const hasError = true; feed.feedErrorCallback(hasError, e, code); expect(global.console.error).toBeCalledWith(e); expect(feed.errors).toEqual([{ ...e, code }]); expect(feed.errorCallback).toHaveBeenCalledWith(e, code, { error: e, [IS_ERROR_DISPLAYED]: hasError, }); }); test('should call the error callback with the value of hasError', () => { const hasError = false; feed.feedErrorCallback(hasError, e, code); expect(feed.errorCallback).toHaveBeenCalledWith(e, code, { error: e, [IS_ERROR_DISPLAYED]: hasError, }); }); test('should set errors only if hasError is true', () => { feed.errors = []; feed.feedErrorCallback(false, e, code); expect(feed.errors).toEqual([]); feed.feedErrorCallback(true, e, code); expect(feed.errors).toEqual([{ ...e, code }]); }); }); describe('addPendingItem()', () => { const itemBase = { my_prop: 'yay', }; beforeEach(() => { feed.file = file; feed.setCachedItems = jest.fn(); }); test('should create an item and add it to the feed with empty cache', () => { feed.getCachedItems = jest.fn(); const item = feed.addPendingItem(file.id, user, itemBase); expect(typeof item.created_at).toBe('string'); expect(item.created_by).toBe(user); expect(typeof item.modified_at).toBe('string'); expect(item.isPending).toBe(true); expect(feed.setCachedItems).toBeCalledWith(file.id, [item]); }); test('should create an item and add it to the feed with populated cache', () => { feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); const item = feed.addPendingItem(file.id, user, itemBase); expect(feed.setCachedItems).toBeCalledWith(file.id, [...feedItems, item]); }); }); describe('createCommentSuccessCallback()', () => { let successCb; beforeEach(() => { feed.updateFeedItem = jest.fn(); successCb = jest.fn(); }); test('should assign tagged_message of the comment with tagged_message value if it exists', () => { const id = '0987654321'; const commentData = { tagged_message: 'yay' }; feed.createCommentSuccessCallback(commentData, id, successCb); expect(feed.updateFeedItem).toBeCalledWith( { ...commentData, isPending: false, }, id, ); expect(successCb).toBeCalled(); }); test('should assign tagged_message of the comment with message value if it exists', () => { const id = '0987654321'; const commentData = { message: 'yay' }; feed.createCommentSuccessCallback(commentData, id, successCb); expect(feed.updateFeedItem).toBeCalledWith( { ...commentData, isPending: false, }, id, ); }); test('should not invoke success callback if destroyed', () => { const id = '0987654321'; const commentData = { message: 'yay' }; feed.isDestroyed = jest.fn().mockReturnValue(true); feed.createCommentSuccessCallback(commentData, id, successCb); expect(successCb).not.toBeCalled(); }); }); describe('createFeedError()', () => { test('should create a feed error with the message and title', () => { const feedError = feed.createFeedError('foo', 'bar'); expect(feedError).toEqual({ error: { message: 'foo', title: 'bar', }, }); }); test('should create a feed error with the message and default title', () => { const feedError = feed.createFeedError('foo'); expect(feedError).toEqual({ error: { message: 'foo', title: commonMessages.errorOccured, }, }); }); }); describe('updateFeedItem()', () => { const { id } = comments.entries[0]; const updates = { foo: 'bar', id: 'foo', }; beforeEach(() => { feed.setCachedItems = jest.fn(); feed.getCachedItems = jest.fn().mockReturnValue({ errors: [], items: feedItems, }); }); test('should throw if no file id', () => { feed.file = {}; expect(() => feed.updateFeedItem({}, id)).toThrow(fileError); }); test('should update the cache with the updated item', () => { feed.file = file; const updatedFeedItems = feed.updateFeedItem(updates, id); expect(updatedFeedItems).not.toBeNull(); expect(feed.setCachedItems).toBeCalledWith(file.id, updatedFeedItems); }); }); describe('createComment()', () => { let successCb; let errorCb; const currentUser = { id: 'bar', }; const text = 'textfoo'; const hasMention = true; beforeEach(() => { successCb = jest.fn(); errorCb = jest.fn(); feed.addPendingItem = jest.fn(); feed.createCommentSuccessCallback = jest.fn(); feed.createCommentErrorCallback = jest.fn(); feed.createFeedError = jest.fn().mockReturnValue('foo'); }); test('should throw if no file id', () => { expect(() => feed.createComment({}, currentUser, text, true, successCb, errorCb)).toThrow(fileError); }); test('should create a pending item', () => { feed.createComment(file, currentUser, text, true, successCb, errorCb); expect(feed.addPendingItem).toBeCalledWith(file.id, currentUser, { id: 'uniqueId', tagged_message: text, type: 'comment', }); }); test('should create the comment and invoke the success callback', done => { feed.createComment(file, currentUser, text, hasMention, successCb, errorCb); setImmediate(() => { expect(feed.commentsAPI.createComment).toBeCalled(); expect(feed.createCommentSuccessCallback).toBeCalled(); expect(feed.createCommentErrorCallback).not.toBeCalled(); done(); }); }); }); describe('createCommentErrorCallback()', () => { const message = 'foo'; const id = 'uniqueId'; beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.createFeedError = jest.fn().mockReturnValue(message); feed.feedErrorCallback = jest.fn(); }); test('should invoke update the feed item with an AF error and call the success callback', () => { const e = { status: 409 }; feed.createCommentErrorCallback(e, errorCode, id); expect(feed.createFeedError).toBeCalledWith(messages.commentCreateConflictMessage); expect(feed.updateFeedItem).toBeCalledWith(message, id); expect(feed.feedErrorCallback).toHaveBeenCalledWith(false, e, errorCode); }); test('should invoke update the feed item with an AF error', () => { const e = { status: 500 }; feed.isDestroyed = jest.fn().mockReturnValue(true); feed.createCommentErrorCallback(e, errorCode, id); expect(feed.createFeedError).toBeCalledWith(messages.commentCreateErrorMessage); expect(feed.updateFeedItem).toBeCalledWith(message, id); }); }); describe('destroy()', () => { let annotationFn; let commentFn; let versionFn; let taskFn; beforeEach(() => { annotationFn = jest.fn(); commentFn = jest.fn(); versionFn = jest.fn(); taskFn = jest.fn(); feed.annotationsAPI = { destroy: annotationFn, }; feed.tasksNewAPI = { destroy: taskFn, }; feed.versionsAPI = { destroy: versionFn, }; feed.commentsAPI = { destroy: commentFn, }; }); test('should destroy the APIs', () => { feed.destroy(); expect(versionFn).toBeCalled(); expect(commentFn).toBeCalled(); expect(taskFn).toBeCalled(); expect(annotationFn).toBeCalled(); }); }); describe('fetchFeedItemErrorCallback()', () => { let errorCb; const code = 'foo'; const e = new Error('bar'); beforeEach(() => { feed.feedErrorCallback = jest.fn(); errorCb = jest.fn(); }); afterEach(() => { jest.restoreAllMocks(); }); test('should call the error callback if error is internal server error', () => { const shouldDisplayError = true; jest.spyOn(error, 'isUserCorrectableError').mockReturnValue(shouldDisplayError); feed.fetchFeedItemErrorCallback(errorCb, e, code); expect(feed.feedErrorCallback).toHaveBeenCalledWith(shouldDisplayError, e, code); }); test('should not call the error callback if error is forbidden or another error', () => { const shouldDisplayError = false; jest.spyOn(error, 'isUserCorrectableError').mockReturnValue(shouldDisplayError); feed.fetchFeedItemErrorCallback(errorCb, e, code); expect(feed.feedErrorCallback).toHaveBeenCalledWith(shouldDisplayError, e, code); }); }); describe('addAnnotation()', () => { beforeEach(() => { feed.addPendingItem = jest.fn(); feed.updateFeedItem = jest.fn(); }); afterEach(() => { jest.restoreAllMocks(); }); test('should throw if no file id', () => { expect(() => feed.addAnnotation({})).toThrow('Bad box item!'); }); test('should add pending feedItem item if annotation event isPending', () => { const expectedAnnotation = { created_by: user, id: '123', type: 'annotation', }; feed.addAnnotation(file, user, {}, '123', true); expect(feed.addPendingItem).toBeCalledWith(file.id, user, expectedAnnotation); expect(feed.updateFeedItem).not.toBeCalled(); }); test('should update feedItem if annotation event is not pending', () => { const expectedAnnotation = { isPending: false, }; feed.addAnnotation(file, user, {}, '123', false); expect(feed.updateFeedItem).toBeCalledWith(expectedAnnotation, '123'); expect(feed.addPendingItem).not.toBeCalled(); }); }); describe('updateAnnotation()', () => { const annotationId = '123'; const text = 'hello'; const permissions = { can_edit: true }; let successCallback; let errorCallback; beforeEach(() => { successCallback = jest.fn(); errorCallback = jest.fn(); }); test('should throw if file does not have an id', () => { expect(() => feed.updateAnnotation({}, annotationId, text, permissions, successCallback, errorCallback), ).toThrow(fileError); }); test('should set error callback and file', () => { feed.updateAnnotation(file, annotationId, text, permissions, successCallback, errorCallback); expect(feed.errorCallback).toEqual(errorCallback); expect(feed.file).toEqual(file); }); test('should updateFeedItem with isPending set to true', () => { feed.updateFeedItem = jest.fn(); feed.updateAnnotation(file, annotationId, text, permissions, successCallback, errorCallback); expect(feed.updateFeedItem).toBeCalledWith({ message: text, isPending: true }, annotationId); }); test('should call the updateAnnotation API and call updateFeedItem on success', () => { feed.updateFeedItem = jest.fn(); feed.updateAnnotation(file, annotationId, permissions, text, successCallback, errorCallback); expect(feed.annotationsAPI.updateAnnotation).toHaveBeenCalled(); expect(feed.updateFeedItem).toHaveBeenCalled(); expect(successCallback).toHaveBeenCalled(); }); }); describe('updateCommentErrorCallback()', () => { const e = new Error('foo'); beforeEach(() => { feed.updateFeedItem = jest.fn(); feed.createFeedError = jest.fn().mockReturnValue(error); feed.feedErrorCallback = jest.fn(); }); test('should update the feed item and call the error callback', () => { const id = '1'; feed.updateCommentErrorCallback(e, errorCode, id); expect(feed.createFeedError).toBeCalledWith(messages.commentUpdateErrorMessage); expect(feed.updateFeedItem).toBeCalledWith(error, id); expect(feed.feedErrorCallback).toBeCalledWith(true, e, errorCode); }); }); describe('deleteAnnotation()', () => { const annotationId = '123'; let successCallback; let errorCallback; beforeEach(() => { successCallback = jest.fn(); errorCallback = jest.fn(); }); test('should throw if file does not have an id', () => { expect(() => feed.deleteAnnotation({}, annotationId, successCallback, errorCallback)).toThrow(fileError); }); test('should set error callback and file', () => { feed.deleteAnnotation(file, annotationId, { can_delete: true }, jest.fn(), errorCallback); expect(feed.errorCallback).toEqual(errorCallback); expect(feed.file).toEqual(file); }); test('should updateFeedItem with to pending state', () => { feed.updateFeedItem = jest.fn(); feed.deleteAnnotation(file, '123', successCallback, errorCallback); expect(feed.updateFeedItem).toBeCalledWith({ isPending: true }, annotationId); }); test('should call the deleteAnnotation API and call deleteFeedItem on success', () => { feed.deleteFeedItem = jest.fn().mockImplementation((id, cb) => cb()); feed.deleteAnnotation(file, '123', { can_delete: true }, successCallback, errorCallback); expect(feed.annotationsAPI.deleteAnnotation).toBeCalled(); expect(feed.deleteFeedItem).to