import { GlIcon, GlLink, GlButton } from '@gitlab/ui'; import { nextTick } from 'vue'; 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 { stubComponent } from 'helpers/stub_component'; 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 AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import { mockWorkItemCommentNote } from 'jest/work_items/mock_data'; 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; let showModalSpy; const defaultProps = { idKey: 1, iid: 1, displayReference: 'gitlab-org/gitlab-test#1', pathIdSeparator: '#', path: `${TEST_HOST}/path`, title: 'title', confidential: true, dueDate: '1990-12-31', weight: 10, createdAt: '2018-12-01T00:00:00.00Z', milestone: defaultMilestone, assignees: defaultAssignees, eventNamespace: 'relatedIssue', }; 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); const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); function mountComponent({ data = {}, props = {} } = {}) { showModalSpy = jest.fn(); wrapper = shallowMount(RelatedIssuableItem, { propsData: { ...defaultProps, ...props, }, provide: { reportAbusePath: '/report/abuse/path', }, stubs: { WorkItemDetailModal: stubComponent(WorkItemDetailModal, { methods: { show: showModalSpy, }, }), }, data() { return data; }, }); } it('contains issuable-info-container class when canReorder is false', () => { 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', () => { mountComponent(); expect(findRemoveButton().exists()).toBe(false); }); describe('token title', () => { beforeEach(() => { mountComponent(); }); it('links to computedPath', () => { expect(findTitleLink().attributes('href')).toBe(defaultProps.path); }); it('renders confidential icon', () => { expect(findIcon().attributes('title')).toBe(__('Confidential')); }); it('renders title', () => { expect(findTitleLink().text()).toBe(defaultProps.title); }); }); describe('token state', () => { it('renders state title', () => { mountComponent({ props: { state: 'opened' } }); const stateTitle = findIcon().attributes('title'); const formattedCreateDate = formatDate(defaultProps.createdAt); expect(stateTitle).toContain('Created'); expect(stateTitle).toContain(`${formattedCreateDate}`); }); it('renders aria label', () => { mountComponent({ props: { state: 'opened' } }); expect(findIcon().attributes('arialabel')).toBe('opened'); }); it('renders open icon when open state', () => { 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', () => { mountComponent({ props: { state: 'closed', closedAt: '2018-12-01T00:00:00.00Z' } }); expect(findIcon().props('name')).toBe('issue-close'); expect(findIcon().classes('issue-token-state-icon-closed')).toBe(true); }); }); describe('token metadata', () => { 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', () => { mountComponent(); expect(wrapper.findComponent(IssueMilestone).props('milestone')).toEqual( defaultProps.milestone, ); }); it('renders due date component with correct due date', () => { mountComponent(); expect(findIssueDueDate().props('date')).toBe(defaultProps.dueDate); }); it('does not render red icon for overdue issue that is closed', () => { mountComponent({ props: { closedAt: '2018-12-01T00:00:00.00Z' } }); expect(findIssueDueDate().props('closed')).toBe(true); }); }); describe('token assignees', () => { it('renders assignees avatars', () => { mountComponent(); expect(wrapper.findComponent(IssueAssignees).props('assignees')).toEqual( defaultProps.assignees, ); }); }); describe('remove button', () => { beforeEach(() => { mountComponent({ props: { canRemove: true }, data: { removeDisabled: true } }); }); it('renders if canRemove', () => { 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', () => { expect(findRemoveButton().attributes('disabled')).toBeDefined(); }); it('triggers onRemoveRequest when clicked', () => { findRemoveButton().vm.$emit('click'); expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[defaultProps.idKey]]); }); }); describe('when issue is locked', () => { const lockedMessage = 'Issues created from a vulnerability cannot be removed'; beforeEach(() => { mountComponent({ props: { isLocked: true, lockedMessage } }); }); it('does not render the remove button', () => { expect(findRemoveButton().exists()).toBe(false); }); it('renders the lock icon with the correct title', () => { expect(findLockIcon().attributes('title')).toBe(lockedMessage); }); }); describe('work item modal', () => { const workItemId = 'gid://gitlab/WorkItem/1'; it('renders', () => { mountComponent(); expect(findWorkItemDetailModal().props()).toMatchObject({ workItemId, workItemIid: '1', }); }); 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_iid=1`, replace: true, }); }); }); describe('when it emits "workItemDeleted" event', () => { it('emits "relatedIssueRemoveRequest" event', () => { mountComponent(); findWorkItemDetailModal().vm.$emit('workItemDeleted', workItemId); expect(wrapper.emitted('relatedIssueRemoveRequest')).toEqual([[workItemId]]); }); }); 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, }); }); }); }); describe('abuse category selector', () => { beforeEach(() => { mountComponent({ props: { workItemType: 'TASK' } }); findTitleLink().vm.$emit('click', { preventDefault: () => {} }); }); it('should not be visible by default', () => { expect(showModalSpy).toHaveBeenCalled(); expect(findAbuseCategorySelector().exists()).toBe(false); }); it('should be visible when the work item modal emits `openReportAbuse` event', async () => { findWorkItemDetailModal().vm.$emit('openReportAbuse', mockWorkItemCommentNote); await nextTick(); expect(findAbuseCategorySelector().exists()).toBe(true); findAbuseCategorySelector().vm.$emit('close-drawer'); await nextTick(); expect(findAbuseCategorySelector().exists()).toBe(false); }); }); });