UNPKG

box-ui-elements

Version:
568 lines (464 loc) • 21.3 kB
import * as React from 'react'; import { mount } from 'enzyme'; import AnnotationActivity from '../AnnotationActivity'; import AnnotationActivityMenu from '../AnnotationActivityMenu'; import CommentForm from '../../comment-form/CommentForm'; import DeleteConfirmation from '../../common/delete-confirmation'; import Media from '../../../../../components/media'; import messages from '../messages'; import SelectableActivityCard from '../../SelectableActivityCard'; jest.mock('../../Avatar', () => () => 'Avatar'); const currentUser = { name: 'testuser', id: 11, }; const mentionSelectorContacts = []; const TIME_STRING_SEPT_27_2017 = '2017-09-27T10:40:41-07:00'; const TIME_STRING_SEPT_28_2017 = '2017-09-28T10:40:41-07:00'; const allHandlers = { contacts: { getMentionWithQuery: jest.fn(), }, }; describe('elements/content-sidebar/ActivityFeed/annotations/AnnotationActivity', () => { const mockAnnotation = { created_at: TIME_STRING_SEPT_27_2017, created_by: { name: 'Jane Doe', id: 10 }, description: { message: 'test' }, file_version: { id: '456', version_number: '2', }, id: '123', target: { location: { value: 1 } }, }; const mockActivity = { currentUser, handlers: allHandlers, hasVersions: true, isCurrentVersion: true, item: mockAnnotation, mentionSelectorContacts, }; const getWrapper = (props = {}) => mount(<AnnotationActivity {...mockActivity} {...props} />); beforeEach(() => { CommentForm.default = jest.fn().mockReturnValue(<div />); }); test('should not render annotation activity menu when can_delete is false and can_edit is false and can_resolve is false', () => { const item = { ...mockAnnotation, permissions: { can_delete: false, can_edit: false, can_resolve: false }, }; const wrapper = getWrapper({ item }); expect(wrapper.exists(AnnotationActivityMenu)).toBe(false); }); test.each` canDelete | canEdit | canResolve ${false} | ${false} | ${true} ${true} | ${false} | ${false} ${false} | ${true} | ${false} ${false} | ${true} | ${true} ${true} | ${true} | ${false} ${true} | ${false} | ${true} ${true} | ${true} | ${true} `( 'should correctly render annotation activity when canDelete: $canDelete and canEdit: $canEdit and canResolve: $canResolve', ({ canDelete, canEdit, canResolve }) => { const unixTime = new Date(TIME_STRING_SEPT_27_2017).getTime(); const item = { ...mockAnnotation, permissions: { can_delete: canDelete, can_edit: canEdit, can_resolve: canResolve }, }; const wrapper = getWrapper({ item }); expect(wrapper.find('ActivityTimestamp').prop('date')).toEqual(unixTime); expect(wrapper.find('AnnotationActivityLink').first().props()).toMatchObject({ 'data-resin-target': 'annotationLink', message: { ...messages.annotationActivityPageItem, values: { number: 1 }, }, }); expect(wrapper.exists(AnnotationActivityMenu)).toBe(true); expect(wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))').prop('tagged_message')).toEqual( mockActivity.item.description.message, ); }, ); test('should render CommentForm if user clicks on the Modify menu item', () => { const activity = { item: { ...mockAnnotation, isPending: false, permissions: { can_edit: true }, }, }; const wrapper = getWrapper({ ...mockActivity, ...activity }); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onEdit')(); }); wrapper.update(); expect(wrapper.exists('ForwardRef(withFeatureConsumer(CommentForm))')).toBe(true); // Firing the onCancel prop will remove the CommentForm React.act(() => { wrapper.find('ForwardRef(withFeatureConsumer(CommentForm))').props().onCancel(); }); wrapper.update(); expect(wrapper.exists('ForwardRef(withFeatureConsumer(CommentForm))')).toBe(false); }); test('should correctly render annotation activity of another file version', () => { const wrapper = getWrapper({ isCurrentVersion: false }); expect(wrapper.find('AnnotationActivityLink').first().prop('message')).toEqual({ ...messages.annotationActivityVersionLink, values: { number: '2' }, }); }); test('should render version unavailable if file version is null', () => { const wrapper = getWrapper({ item: { ...mockAnnotation, file_version: null } }); const activityLink = wrapper.find('AnnotationActivityLink').first(); expect(activityLink.prop('message')).toEqual({ ...messages.annotationActivityVersionUnavailable, }); expect(activityLink.prop('isDisabled')).toBe(true); }); test('should not render file version link if hasVersions is false', () => { const wrapper = getWrapper({ hasVersions: false }); expect(wrapper.exists('AnnotationActivityLink')).toBe(false); }); test('should render commenter as a link', () => { const wrapper = getWrapper(); expect(wrapper.find('UserLink').prop('name')).toEqual(mockActivity.item.created_by.name); }); test('should not show actions menu when annotation activity is pending', () => { const item = { ...mockAnnotation, permissions: { can_delete: true }, isPending: true, }; const wrapper = getWrapper({ item }); expect(wrapper.exists(AnnotationActivityMenu)).toBe(false); expect(wrapper.find(Media).hasClass('bcs-is-pending')).toBe(true); }); test('should render an error when one is defined', () => { const activity = { item: { ...mockAnnotation, error: { title: 'error', message: 'This is an error message', }, }, onDelete: jest.fn(), }; const wrapper = getWrapper(activity); expect(wrapper.find('ActivityError').length).toEqual(1); }); test('should render an error cta when an action is defined', () => { const onActionSpy = jest.fn(); const activity = { item: { ...mockAnnotation, error: { title: 'error', message: 'This is an error message', action: { text: 'click', onAction: onActionSpy, }, }, }, onDelete: jest.fn(), }; const wrapper = mount(<AnnotationActivity {...mockActivity} {...activity} />); const inlineErrorActionLink = wrapper.find('InlineError').find('button.bcs-ActivityError-action'); expect(inlineErrorActionLink.length).toEqual(1); const inlineErrorActionOnClick = inlineErrorActionLink.prop('onClick'); inlineErrorActionOnClick(); expect(onActionSpy).toHaveBeenCalledTimes(1); }); test.each` created_at | modified_at | status | expectedIsEdited ${TIME_STRING_SEPT_27_2017} | ${undefined} | ${'open'} | ${false} ${TIME_STRING_SEPT_27_2017} | ${TIME_STRING_SEPT_27_2017} | ${'open'} | ${false} ${TIME_STRING_SEPT_27_2017} | ${TIME_STRING_SEPT_28_2017} | ${'open'} | ${true} ${TIME_STRING_SEPT_27_2017} | ${undefined} | ${'resolved'} | ${false} ${TIME_STRING_SEPT_27_2017} | ${TIME_STRING_SEPT_27_2017} | ${'resolved'} | ${false} ${TIME_STRING_SEPT_27_2017} | ${TIME_STRING_SEPT_28_2017} | ${'resolved'} | ${false} `( `given created_at = $created_at, modified_at = $modified_at and status = $status, isEdited prop on ActivityMessage should be: $expectedIsEdited`, ({ created_at, modified_at, status, expectedIsEdited }) => { const wrapper = getWrapper({ item: { ...mockAnnotation, created_at, modified_at, status } }); expect(wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))').prop('isEdited')).toEqual( expectedIsEdited, ); }, ); describe('delete confirmation behavior', () => { test('should render the DeleteConfirmation when delete menu item is selected', () => { const item = { ...mockAnnotation, permissions: { can_delete: true }, }; const wrapper = getWrapper({ item }); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onDelete')(); }); wrapper.update(); expect(wrapper.exists(DeleteConfirmation)).toBe(true); }); test('should close the DeleteConfirmation when cancel is selected', () => { const item = { ...mockAnnotation, permissions: { can_delete: true }, }; const wrapper = getWrapper({ item }); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onDelete')(); }); wrapper.update(); React.act(() => { wrapper.find(DeleteConfirmation).prop('onDeleteCancel')(); }); wrapper.update(); expect(wrapper.exists(DeleteConfirmation)).toBe(false); }); test('should call onDelete when confirm delete is selected', () => { const onDelete = jest.fn(); const permissions = { can_delete: true }; const item = { ...mockAnnotation, permissions, }; const wrapper = getWrapper({ item, onDelete }); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onDelete')(); }); wrapper.update(); React.act(() => { wrapper.find(DeleteConfirmation).prop('onDeleteConfirm')(); }); wrapper.update(); expect(onDelete).toHaveBeenCalledWith({ id: mockAnnotation.id, permissions }); expect(wrapper.exists(DeleteConfirmation)).toBe(false); }); }); describe('SelectableActivityCard', () => { const getActivityItem = overrides => ({ ...mockAnnotation, permissions: { can_delete: true, can_edit: true }, ...overrides, }); test('should render as SelectableActivityCard', () => { const wrapper = getWrapper(); expect(wrapper.exists(SelectableActivityCard)).toBe(true); expect(wrapper.find(SelectableActivityCard).props()).toMatchObject({ className: 'bcs-AnnotationActivity', 'data-resin-iscurrent': true, 'data-resin-itemid': mockAnnotation.id, 'data-resin-feature': 'annotations', 'data-resin-target': 'annotationButton', isDisabled: false, onMouseDown: expect.any(Function), onSelect: expect.any(Function), }); }); test('should disable card if there is an error', () => { const activity = { item: { ...mockAnnotation, error: { title: 'error', message: 'This is an error message', action: { text: 'click', }, }, }, }; const wrapper = getWrapper(activity); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); }); test('should disable card if the overflow menu is open', () => { const wrapper = getWrapper({ item: getActivityItem() }); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(false); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onMenuOpen')(); }); wrapper.update(); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); }); test('should disable card if editing the comment', () => { const wrapper = getWrapper({ item: getActivityItem() }); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(false); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onEdit')(); }); wrapper.update(); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); }); test('should disable card if file version is unavailable', () => { const wrapper = getWrapper({ item: getActivityItem({ file_version: null }) }); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); }); test('should disable card if the delete confirmation is open', () => { const wrapper = getWrapper({ item: getActivityItem() }); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(false); React.act(() => { wrapper.find(AnnotationActivityMenu).prop('onDelete')(); }); wrapper.update(); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); }); test('should stop propagation of mousedown event from SelectableActivityCard', () => { const event = { stopPropagation: jest.fn() }; const wrapper = getWrapper({ item: getActivityItem() }); wrapper.find(SelectableActivityCard).simulate('mousedown', event); expect(event.stopPropagation).toHaveBeenCalled(); }); test('should not stop propagation of mousedown event from SelectableActivityCard when disabled', () => { const event = { stopPropagation: jest.fn() }; const wrapper = getWrapper({ item: getActivityItem({ file_version: null }) }); expect(wrapper.find(SelectableActivityCard).prop('isDisabled')).toBe(true); wrapper.find(SelectableActivityCard).simulate('mousedown', event); expect(event.stopPropagation).not.toHaveBeenCalled(); }); }); describe('video annotations', () => { const mockVideoAnnotation = { ...mockAnnotation, target: { location: { type: 'frame', value: 60000, // 1 minute in milliseconds }, }, }; test('should detect video annotation when target location type is frame', () => { const wrapper = getWrapper({ item: mockVideoAnnotation }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:01:00'); }); test('should not show Annotation Activity Link link for video annotations when is current version and hasVersions is true', () => { const wrapper = getWrapper({ item: mockVideoAnnotation, hasVersions: true, isCurrentVersion: true, }); expect(wrapper.exists('AnnotationActivityLink')).toBe(false); }); test('should show Annotation Activity Link for video annotations when is not current version and hasVersions is true', () => { const wrapper = getWrapper({ item: mockVideoAnnotation, hasVersions: true, isCurrentVersion: false, }); expect(wrapper.exists('AnnotationActivityLink')).toBe(true); expect(wrapper.find('AnnotationActivityLink').first().prop('message')).toEqual({ ...messages.annotationActivityVersionLink, values: { number: '2' }, }); }); test('should pass correct timestamp format to ActivityMessage for video annotations', () => { const videoAnnotationWithTimestamp = { ...mockVideoAnnotation, target: { location: { type: 'frame', value: 3661000, // 1 hour, 1 minute, 1 second }, }, }; const wrapper = getWrapper({ item: videoAnnotationWithTimestamp }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('1:01:01'); }); test('should handle zero timestamp for video annotations', () => { const videoAnnotationWithZeroTimestamp = { ...mockVideoAnnotation, target: { location: { type: 'frame', value: 0, }, }, }; const wrapper = getWrapper({ item: videoAnnotationWithZeroTimestamp }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:00:00'); }); test('should handle undefined timestamp for video annotations', () => { const videoAnnotationWithUndefinedTimestamp = { ...mockVideoAnnotation, target: { location: { type: 'frame', value: undefined, }, }, }; const wrapper = getWrapper({ item: videoAnnotationWithUndefinedTimestamp }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:00:00'); }); test('should not pass timestamp to ActivityMessage for non-video annotations', () => { const regularAnnotation = { ...mockAnnotation, target: { location: { type: 'page', value: 1, }, }, }; const wrapper = getWrapper({ item: regularAnnotation }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(wrapper.find('AnnotationActivityLink').first().prop('message')).toEqual({ ...messages.annotationActivityPageItem, values: { number: 1 }, }); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy(); }); test('should not pass timestamp to ActivityMessage when target location type is missing', () => { const annotationWithoutType = { ...mockAnnotation, target: { location: { value: 60000, }, }, }; const wrapper = getWrapper({ item: annotationWithoutType }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy(); }); test('should not pass timestamp to ActivityMessage when target is missing', () => { const annotationWithoutTarget = { ...mockAnnotation, target: { location: { value: 1, }, }, }; const wrapper = getWrapper({ item: annotationWithoutTarget }); const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))'); expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy(); }); test('should show version link for non-video annotations when hasVersions is true', () => { const regularAnnotation = { ...mockAnnotation, target: { location: { type: 'page', value: 1, }, }, }; const wrapper = getWrapper({ item: regularAnnotation, hasVersions: true, isCurrentVersion: true, }); expect(wrapper.exists('AnnotationActivityLink')).toBe(true); }); }); });