UNPKG

box-ui-elements-mlh

Version:
418 lines (369 loc) 15.8 kB
import * as React from 'react'; import { mount, shallow } from 'enzyme'; import cloneDeep from 'lodash/cloneDeep'; import { TaskComponent as Task } from '..'; const allHandlers = { tasks: { edit: jest.fn(), }, contacts: { getApproverWithQuery: jest.fn(), getMentionWithQuery: jest.fn(), }, }; const approverSelectorContacts = []; describe('elements/content-sidebar/ActivityFeed/task-new/Task', () => { const currentUser = { name: 'Jake Thomas', id: '1', type: 'user' }; const otherUser = { name: 'Patrick Paul', id: '3', type: 'user' }; const creatorUser = { name: 'Steven Yang', id: '5', type: 'user' }; const taskId = '123125'; const task = { assigned_to: { entries: [ { id: 'current-user-assignment-id', target: currentUser, status: 'NOT_STARTED', role: 'ASSIGNEE', permissions: { can_update: true, can_delete: true, }, type: 'task_collaborator', }, { id: 'other-user-assignment-id', target: otherUser, status: 'COMPLETED', role: 'ASSIGNEE', permissions: { can_update: true, can_delete: true, }, type: 'task_collaborator', }, ], limit: 10, next_marker: null, }, completion_rule: 'ALL_ASSIGNEES', created_at: '2010-01-01', created_by: { id: '0', target: creatorUser, role: 'CREATOR', status: 'NOT_STARTED', type: 'task_collaborator' }, due_at: null, id: taskId, description: 'This is where we tell each other what we need to do', status: 'NOT_STARTED', permissions: { can_update: true, can_delete: true, can_create_task_collaborator: true, can_create_task_link: true, }, task_links: { entries: [ { type: 'task_link', id: '6231775', task: { id: taskId, type: 'task', due_at: null }, target: { type: 'file', id: '7895970959', sequence_id: '1', etag: '1', sha1: '6cdf9453724469d11469d4f7c2f21dcb828073d5', name: 'file1.csv', }, description: '', permissions: { can_update: true, can_delete: true }, }, ], limit: 1000, next_marker: null, }, task_type: 'GENERAL', type: 'task', }; const taskMultifile = cloneDeep(task); taskMultifile.task_links.entries.push({ type: 'task_link', id: taskId, task: { id: '16431755', type: 'task', due_at: null }, target: { type: 'file', id: '7895975164', sequence_id: '1', etag: '1', sha1: 'b02ef8e024b1e654d050733c5bb12e6c83a5586c', name: 'skynet-file2.csv', }, description: '', permissions: { can_update: true, can_delete: true }, }); test('should correctly render task', () => { const wrapper = shallow(<Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...task} />); expect(wrapper).toMatchSnapshot(); }); test('should show assignment status badges for each assignee', () => { const wrapper = mount(<Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...task} />); expect(wrapper.find('[data-testid="avatar-group-avatar-container"]')).toHaveLength(2); }); test('should show multifile badge if task has multiple files', () => { const wrapper = mount(<Task currentUser={currentUser} {...taskMultifile} />); expect(wrapper.find('[data-testid="multifile-badge"]').hostNodes()).toHaveLength(1); }); test('should not show multifile badge if task does not have multiple files', () => { const wrapper = mount(<Task currentUser={currentUser} {...task} />); expect(wrapper.find('[data-testid="multifile-badge"]').hostNodes()).toHaveLength(0); }); test('should not show due date container if not set', () => { const wrapper = shallow(<Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...task} />); expect(wrapper.find('[data-testid="task-due-date"]')).toHaveLength(0); }); test('should show due date if set', () => { const wrapper = mount( <Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...task} due_at={new Date() + 1000} />, ); expect(wrapper.find('[data-testid="task-due-date"]')).toHaveLength(1); }); test('due date should have overdue class if task is incomplete and due date is in past', () => { const incompleteWrapper = mount( <Task {...task} currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} due_at={new Date() - 1000} status="NOT_STARTED" />, ); expect(incompleteWrapper.render().find('[data-testid="task-overdue-date"]')).toHaveLength(1); }); test('due date should not have overdue class if task is complete and due date is in past', () => { const completeWrapper = mount( <Task {...task} currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} due_at={new Date() - 1000} status="COMPLETED" />, ); expect(completeWrapper.find('[data-testid="task-overdue-date"]')).toHaveLength(0); }); test('should add pending class for isPending prop', () => { // this is for optimistic UI updates in the activity feed card list const myTask = { ...task, isPending: true, }; const wrapper = shallow(<Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...myTask} />); expect(wrapper.find('[data-testid="task-card"]').hasClass('bcs-is-pending')).toBe(true); }); test('should show actions when current user is assigned and task is incomplete', () => { [task, taskMultifile].forEach(eachTask => { const wrapper = shallow( <Task currentUser={currentUser} {...eachTask} isPending={false} onAssignmentUpdate={jest.fn()} />, ); expect(wrapper.find('TaskActions')).toHaveLength(1); }); }); test('should not show actions when current user is assigned and task is complete', () => { [task, taskMultifile].forEach(eachTask => { const wrapper = shallow( <Task currentUser={currentUser} {...eachTask} isPending={false} onAssignmentUpdate={jest.fn()} status="COMPLETED" />, ); expect(wrapper.find('TaskActions')).toHaveLength(0); }); }); test('should not show actions when current user is not assigned', () => { [task, taskMultifile].forEach(eachTask => { const wrapper = shallow( <Task currentUser={{ ...currentUser, id: 'something-else-1' }} {...eachTask} isPending={false} onAssignmentUpdate={jest.fn()} />, ); expect(wrapper.find('TaskActions')).toHaveLength(0); }); }); test.each` eachTask | expected ${task} | ${0} ${taskMultifile} | ${1} `('should show action for creator of task when task is multifile', ({ eachTask, expected }) => { const wrapper = shallow(<Task {...eachTask} currentUser={creatorUser} />); expect(wrapper.find('[data-testid="action-container"]')).toHaveLength(expected); }); test('should show actions for task type', () => { const approvalTask = mount(<Task {...task} task_type="APPROVAL" currentUser={currentUser} />).render(); const approvalBtns = global.queryAllByTestId(approvalTask, 'approve-task'); const rejectBtns = global.queryAllByTestId(approvalTask, 'reject-task'); expect(approvalBtns).toHaveLength(1); expect(rejectBtns).toHaveLength(1); const generalTask = mount(<Task {...task} task_type="GENERAL" currentUser={currentUser} />).render(); const completeBtns = global.queryAllByTestId(generalTask, 'complete-task'); expect(completeBtns).toHaveLength(1); }); test('should show proper icons for task avatar based on task type', () => { const approvalTask = mount(<Task {...task} task_type="APPROVAL" currentUser={currentUser} />); expect(approvalTask.find('IconTaskApproval')).toHaveLength(1); const generalTask = mount(<Task {...task} task_type="GENERAL" currentUser={currentUser} />); expect(generalTask.find('IconTaskGeneral')).toHaveLength(1); }); test('should call onAssignmentUpdate with completed status when task action complete is clicked', () => { const onAssignmentUpdateSpy = jest.fn(); const wrapper = mount( <Task {...task} currentUser={currentUser} onAssignmentUpdate={onAssignmentUpdateSpy} approverSelectorContacts={approverSelectorContacts} />, ); const checkButton = wrapper.find('[data-testid="complete-task"]').hostNodes(); checkButton.simulate('click'); expect(onAssignmentUpdateSpy).toHaveBeenCalledWith(taskId, 'current-user-assignment-id', 'COMPLETED'); }); test('should call onView when view-task-details button is clicked for multifile task', () => { const onViewSpy = jest.fn(); const wrapper = mount(<Task {...taskMultifile} currentUser={currentUser} onView={onViewSpy} />); wrapper .find('[data-testid="view-task"]') .hostNodes() .simulate('click'); expect(onViewSpy).toHaveBeenCalledWith(taskId, false); }); test('should not show view-task-details button for multifile task when onView callback is undefined', () => { const wrapper = mount(<Task {...taskMultifile} currentUser={currentUser} />); expect(wrapper.find('[data-testid="view-task"]').hostNodes()).toHaveLength(0); }); test('should not allow user to delete if the task permissions do not allow it', () => { const wrapper = shallow( <Task {...task} permissions={{ can_delete: false, can_update: true }} currentUser={otherUser} approverSelectorContacts={approverSelectorContacts} handlers={allHandlers} onDelete={jest.fn()} />, ); wrapper.find('MediaMenu[data-testid="task-actions-menu"]').simulate('click'); wrapper.update(); expect(wrapper.find('MenuItem[data-testid="delete-task"]')).toHaveLength(0); expect(wrapper.find('MenuItem[data-testid="edit-task"]')).toHaveLength(1); }); test('should not allow user to edit if the permissions do not allow it', () => { const wrapper = mount( <Task {...task} permissions={{ can_delete: true, can_update: false }} currentUser={otherUser} approverSelectorContacts={approverSelectorContacts} handlers={allHandlers} onEdit={jest.fn()} />, ); wrapper.find('MediaMenu[data-testid="task-actions-menu"]').simulate('click'); wrapper.update(); expect(wrapper.find('MenuItem[data-testid="edit-task"]')).toHaveLength(0); expect(wrapper.find('MenuItem[data-testid="delete-task"]')).toHaveLength(1); }); test('should show inline error for error prop', () => { const wrapper = mount( <Task {...task} currentUser={currentUser} error={{ title: 'blah', message: 'blah' }} onEdit={jest.fn()} onDelete={jest.fn()} />, ); expect(wrapper.find('ActivityError')).toHaveLength(1); }); test('should call getAllTaskCollaborators on modal open if there is a next_marker', async () => { const taskWithMarker = { ...task, assigned_to: { next_marker: 'foo', entries: [], }, }; const wrapper = mount( <Task {...taskWithMarker} currentUser={currentUser} error={{ title: 'blah', message: 'blah' }} onEdit={jest.fn()} onDelete={jest.fn()} />, ); const instance = wrapper.instance(); instance.getAllTaskCollaborators = jest.fn(); await instance.handleEditClick(); expect(instance.getAllTaskCollaborators).toBeCalled(); }); test('should be able to toggle expanded state', () => { const COUNT = 30; const INITIAL_DISPLAY_COUNT = 3; let assigneeList; const taskWithThirtyAssignees = { ...task, assigned_to: { next_marker: null, entries: Array.from({ length: COUNT }, (_, idx) => ({ id: `current-user-assignment-id-${idx}`, target: currentUser, status: 'NOT_STARTED', role: 'ASSIGNEE', permissions: { can_update: true, can_delete: true, }, type: 'task_collaborator', })), }, }; const wrapper = mount( <Task currentUser={currentUser} onEdit={jest.fn()} onDelete={jest.fn()} {...taskWithThirtyAssignees} due_at={new Date() + 1000} />, ); assigneeList = global.queryAllByTestId(wrapper, 'assignee-list-item'); expect(assigneeList).toHaveLength(INITIAL_DISPLAY_COUNT); const expandBtn = global.queryAllByTestId(wrapper, 'show-more-assignees').first(); expandBtn.simulate('click'); assigneeList = global.queryAllByTestId(wrapper, 'assignee-list-item'); expect(assigneeList).toHaveLength(COUNT); const collapseBtn = global.queryAllByTestId(wrapper, 'show-less-assignees').first(); collapseBtn.simulate('click'); assigneeList = global.queryAllByTestId(wrapper, 'assignee-list-item'); expect(assigneeList).toHaveLength(INITIAL_DISPLAY_COUNT); }); test('should call onModalClose prop when modal is closed', () => { const onModalClose = jest.fn(); const wrapper = mount(<Task {...task} currentUser={currentUser} onModalClose={onModalClose} />); const instance = wrapper.instance(); instance.handleEditModalClose(); expect(onModalClose).toBeCalled(); }); });