diff options
Diffstat (limited to 'spec/frontend/issuable/components/related_issuable_item_spec.js')
-rw-r--r-- | spec/frontend/issuable/components/related_issuable_item_spec.js | 233 |
1 files changed, 148 insertions, 85 deletions
diff --git a/spec/frontend/issuable/components/related_issuable_item_spec.js b/spec/frontend/issuable/components/related_issuable_item_spec.js index 6b48f83041a..3f9f048605a 100644 --- a/spec/frontend/issuable/components/related_issuable_item_spec.js +++ b/spec/frontend/issuable/components/related_issuable_item_spec.js @@ -1,23 +1,25 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import { GlIcon, GlLink, GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import IssueDueDate from '~/boards/components/issue_due_date.vue'; import { formatDate } from '~/lib/utils/datetime_utility'; +import { updateHistory } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue'; +import IssueMilestone from '~/issuable/components/issue_milestone.vue'; +import IssueAssignees from '~/issuable/components/issue_assignees.vue'; +import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data'; +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + updateHistory: jest.fn(), +})); + describe('RelatedIssuableItem', () => { let wrapper; - function mountComponent({ mountMethod = mount, stubs = {}, props = {}, slots = {} } = {}) { - wrapper = mountMethod(RelatedIssuableItem, { - propsData: props, - slots, - stubs, - }); - } - - const props = { + const defaultProps = { idKey: 1, displayReference: 'gitlab-org/gitlab-test#1', pathIdSeparator: '#', @@ -31,84 +33,94 @@ describe('RelatedIssuableItem', () => { assignees: defaultAssignees, eventNamespace: 'relatedIssue', }; - const slots = { - dueDate: '<div class="js-due-date-slot"></div>', - weight: '<div class="js-weight-slot"></div>', - }; - - const findRemoveButton = () => wrapper.find({ ref: 'removeButton' }); - const findLockIcon = () => wrapper.find({ ref: 'lockIcon' }); - beforeEach(() => { - mountComponent({ props, slots }); - }); + const findIcon = () => wrapper.findComponent(GlIcon); + const findIssueDueDate = () => wrapper.findComponent(IssueDueDate); + const findLockIcon = () => wrapper.find('[data-testid="lockIcon"]'); + const findRemoveButton = () => wrapper.findComponent(GlButton); + const findTitleLink = () => wrapper.findComponent(GlLink); + const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal); + + function mountComponent({ data = {}, props = {} } = {}) { + wrapper = shallowMount(RelatedIssuableItem, { + propsData: { + ...defaultProps, + ...props, + }, + data() { + return data; + }, + }); + } afterEach(() => { wrapper.destroy(); }); it('contains issuable-info-container class when canReorder is false', () => { - expect(wrapper.props('canReorder')).toBe(false); - expect(wrapper.find('.issuable-info-container').exists()).toBe(true); + mountComponent({ props: { canReorder: false } }); + + expect(wrapper.classes('issuable-info-container')).toBe(true); }); it('does not render token state', () => { + mountComponent(); + expect(wrapper.find('.text-secondary svg').exists()).toBe(false); }); it('does not render remove button', () => { - expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false); + mountComponent(); + + expect(findRemoveButton().exists()).toBe(false); }); describe('token title', () => { + beforeEach(() => { + mountComponent(); + }); + it('links to computedPath', () => { - expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path')); + expect(findTitleLink().attributes('href')).toBe(defaultProps.path); }); it('renders confidential icon', () => { - expect(wrapper.find('.confidential-icon').exists()).toBe(true); + expect(findIcon().attributes('title')).toBe(__('Confidential')); }); it('renders title', () => { - expect(wrapper.find('.item-title a').text()).toEqual(props.title); + expect(findTitleLink().text()).toBe(defaultProps.title); }); }); describe('token state', () => { - const tokenState = () => wrapper.find({ ref: 'iconElementXL' }); - - beforeEach(() => { - wrapper.setProps({ state: 'opened' }); - }); - - it('renders if hasState', () => { - expect(tokenState().exists()).toBe(true); - }); - it('renders state title', () => { - const stateTitle = tokenState().attributes('title'); - const formattedCreateDate = formatDate(props.createdAt); + mountComponent({ props: { state: 'opened' } }); + const stateTitle = findIcon().attributes('title'); + const formattedCreateDate = formatDate(defaultProps.createdAt); expect(stateTitle).toContain('<span class="bold">Created</span>'); expect(stateTitle).toContain(`<span class="text-tertiary">${formattedCreateDate}</span>`); }); it('renders aria label', () => { - expect(tokenState().attributes('aria-label')).toEqual('opened'); + mountComponent({ props: { state: 'opened' } }); + + expect(findIcon().attributes('arialabel')).toBe('opened'); }); it('renders open icon when open state', () => { - expect(tokenState().classes('issue-token-state-icon-open')).toBe(true); + mountComponent({ props: { state: 'opened' } }); + + expect(findIcon().props('name')).toBe('issue-open-m'); + expect(findIcon().classes('issue-token-state-icon-open')).toBe(true); }); - it('renders close icon when close state', async () => { - wrapper.setProps({ - state: 'closed', - closedAt: '2018-12-01T00:00:00.00Z', - }); - await nextTick(); + it('renders close icon when close state', () => { + mountComponent({ props: { state: 'closed', closedAt: '2018-12-01T00:00:00.00Z' } }); - expect(tokenState().classes('issue-token-state-icon-closed')).toBe(true); + expect(findIcon().props('name')).toBe('issue-close'); + expect(findIcon().classes('issue-token-state-icon-closed')).toBe(true); }); }); @@ -116,75 +128,66 @@ describe('RelatedIssuableItem', () => { const tokenMetadata = () => wrapper.find('.item-meta'); it('renders item path and ID', () => { + mountComponent(); const pathAndID = tokenMetadata().find('.item-path-id').text(); expect(pathAndID).toContain('gitlab-org/gitlab-test'); expect(pathAndID).toContain('#1'); }); - it('renders milestone icon and name', () => { - const milestoneIcon = tokenMetadata().find('.item-milestone svg'); - const milestoneTitle = tokenMetadata().find('.item-milestone .milestone-title'); + it('renders milestone', () => { + mountComponent(); - expect(milestoneIcon.attributes('data-testid')).toBe('clock-icon'); - expect(milestoneTitle.text()).toContain('Milestone title'); + expect(wrapper.findComponent(IssueMilestone).props('milestone')).toEqual( + defaultProps.milestone, + ); }); it('renders due date component with correct due date', () => { - expect(wrapper.find(IssueDueDate).props('date')).toBe(props.dueDate); + mountComponent(); + + expect(findIssueDueDate().props('date')).toBe(defaultProps.dueDate); }); - it('does not render red icon for overdue issue that is closed', async () => { - mountComponent({ - props: { - ...props, - closedAt: '2018-12-01T00:00:00.00Z', - }, - }); - await nextTick(); + it('does not render red icon for overdue issue that is closed', () => { + mountComponent({ props: { closedAt: '2018-12-01T00:00:00.00Z' } }); - expect(wrapper.find(IssueDueDate).props('closed')).toBe(true); + expect(findIssueDueDate().props('closed')).toBe(true); }); }); describe('token assignees', () => { it('renders assignees avatars', () => { - // Expect 2 times 2 because assignees are rendered twice, due to layout issues - expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBeDefined(); + mountComponent(); - expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2'); + expect(wrapper.findComponent(IssueAssignees).props('assignees')).toEqual( + defaultProps.assignees, + ); }); }); describe('remove button', () => { beforeEach(() => { - wrapper.setProps({ canRemove: true }); + mountComponent({ props: { canRemove: true }, data: { removeDisabled: true } }); }); it('renders if canRemove', () => { - expect(findRemoveButton().exists()).toBe(true); + expect(findRemoveButton().props('icon')).toBe('close'); + expect(findRemoveButton().attributes('aria-label')).toBe(__('Remove')); }); it('does not render the lock icon', () => { expect(findLockIcon().exists()).toBe(false); }); - it('renders disabled button when removeDisabled', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ removeDisabled: true }); - await nextTick(); - - expect(findRemoveButton().attributes('disabled')).toEqual('disabled'); + it('renders disabled button when removeDisabled', () => { + expect(findRemoveButton().attributes('disabled')).toBe('true'); }); - it('triggers onRemoveRequest when clicked', async () => { - findRemoveButton().trigger('click'); - await nextTick(); - const { relatedIssueRemoveRequest } = wrapper.emitted(); + it('triggers onRemoveRequest when clicked', () => { + findRemoveButton().vm.$emit('click'); - expect(relatedIssueRemoveRequest.length).toBe(1); - expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]); + expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[defaultProps.idKey]]); }); }); @@ -192,10 +195,7 @@ describe('RelatedIssuableItem', () => { const lockedMessage = 'Issues created from a vulnerability cannot be removed'; beforeEach(() => { - wrapper.setProps({ - isLocked: true, - lockedMessage, - }); + mountComponent({ props: { isLocked: true, lockedMessage } }); }); it('does not render the remove button', () => { @@ -206,4 +206,67 @@ describe('RelatedIssuableItem', () => { expect(findLockIcon().attributes('title')).toBe(lockedMessage); }); }); + + describe('work item modal', () => { + const workItem = 'gid://gitlab/WorkItem/1'; + + it('renders', () => { + mountComponent(); + + expect(findWorkItemDetailModal().props('workItemId')).toBe(workItem); + }); + + describe('when work item is issue and the related issue title is clicked', () => { + it('does not open', () => { + mountComponent({ props: { workItemType: 'ISSUE' } }); + wrapper.vm.$refs.modal.show = jest.fn(); + + findTitleLink().vm.$emit('click', { preventDefault: () => {} }); + + expect(wrapper.vm.$refs.modal.show).not.toHaveBeenCalled(); + }); + }); + + describe('when work item is task and the related issue title is clicked', () => { + beforeEach(() => { + mountComponent({ props: { workItemType: 'TASK' } }); + wrapper.vm.$refs.modal.show = jest.fn(); + findTitleLink().vm.$emit('click', { preventDefault: () => {} }); + }); + + it('opens', () => { + expect(wrapper.vm.$refs.modal.show).toHaveBeenCalled(); + }); + + it('updates the url params with the work item id', () => { + expect(updateHistory).toHaveBeenCalledWith({ + url: `${TEST_HOST}/?work_item_id=1`, + replace: true, + }); + }); + }); + + describe('when it emits "workItemDeleted" event', () => { + it('emits "relatedIssueRemoveRequest" event', () => { + mountComponent(); + + findWorkItemDetailModal().vm.$emit('workItemDeleted', workItem); + + expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[workItem]]); + }); + }); + + describe('when it emits "close" event', () => { + it('removes the work item id from the url params', () => { + mountComponent(); + + findWorkItemDetailModal().vm.$emit('close'); + + expect(updateHistory).toHaveBeenCalledWith({ + url: `${TEST_HOST}/`, + replace: true, + }); + }); + }); + }); }); |