import { GlLink, GlLabel, GlIcon, GlFormCheckbox } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; import IssuableItem from '~/issuable_list/components/issuable_item.vue'; import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data'; const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots = {} } = {}) => shallowMount(IssuableItem, { propsData: { issuableSymbol, issuable, enableLabelPermalinks: true, showDiscussions: true, showCheckbox: false, }, slots, }); const MOCK_GITLAB_URL = 'http://0.0.0.0:3000'; describe('IssuableItem', () => { // The mock data is dependent that this is after our default date useFakeDate(2020, 11, 11); const mockLabels = mockIssuable.labels.nodes; const mockAuthor = mockIssuable.author; const originalUrl = gon.gitlab_url; let wrapper; beforeEach(() => { gon.gitlab_url = MOCK_GITLAB_URL; wrapper = createComponent(); }); afterEach(() => { wrapper.destroy(); gon.gitlab_url = originalUrl; }); describe('computed', () => { describe('author', () => { it('returns `issuable.author` reference', () => { expect(wrapper.vm.author).toEqual(mockIssuable.author); }); }); describe('authorId', () => { it.each` authorId | returnValue ${1} | ${1} ${'1'} | ${1} ${'gid://gitlab/User/1'} | ${1} ${'foo'} | ${null} `( 'returns $returnValue when value of `issuable.author.id` is $authorId', async ({ authorId, returnValue }) => { wrapper.setProps({ issuable: { ...mockIssuable, author: { ...mockAuthor, id: authorId, }, }, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.authorId).toBe(returnValue); }, ); }); describe('isIssuableUrlExternal', () => { it.each` issuableWebUrl | urlType | returnValue ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false} ${`${MOCK_GITLAB_URL}/gitlab-org/gitlab-test/-/issues/1`} | ${'absolute and internal'} | ${false} ${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true} ${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true} `( 'returns $returnValue when `issuable.webUrl` is $urlType', async ({ issuableWebUrl, returnValue }) => { wrapper.setProps({ issuable: { ...mockIssuable, webUrl: issuableWebUrl, }, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.isIssuableUrlExternal).toBe(returnValue); }, ); }); describe('labels', () => { it('returns `issuable.labels.nodes` reference when it is available', () => { expect(wrapper.vm.labels).toEqual(mockLabels); }); it('returns `issuable.labels` reference when it is available', async () => { wrapper.setProps({ issuable: { ...mockIssuable, labels: mockLabels, }, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.labels).toEqual(mockLabels); }); it('returns empty array when none of `issuable.labels.nodes` or `issuable.labels` are available', async () => { wrapper.setProps({ issuable: { ...mockIssuable, labels: null, }, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.labels).toEqual([]); }); }); describe('assignees', () => { it('returns `issuable.assignees` reference when it is available', () => { expect(wrapper.vm.assignees).toBe(mockIssuable.assignees); }); }); describe('createdAt', () => { it('returns string containing timeago string based on `issuable.createdAt`', () => { expect(wrapper.vm.createdAt).toContain('created'); expect(wrapper.vm.createdAt).toContain('ago'); }); }); describe('updatedAt', () => { it('returns string containing timeago string based on `issuable.updatedAt`', () => { expect(wrapper.vm.updatedAt).toContain('updated'); expect(wrapper.vm.updatedAt).toContain('ago'); }); }); describe('showDiscussions', () => { it.each` userDiscussionsCount | returnValue ${0} | ${true} ${1} | ${true} ${undefined} | ${false} ${null} | ${false} `( 'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount', ({ userDiscussionsCount, returnValue }) => { const wrapperWithDiscussions = createComponent({ issuableSymbol: '#', issuable: { ...mockIssuable, userDiscussionsCount, }, }); expect(wrapperWithDiscussions.vm.showDiscussions).toBe(returnValue); wrapperWithDiscussions.destroy(); }, ); }); }); describe('methods', () => { describe('scopedLabel', () => { it.each` label | labelType | returnValue ${mockRegularLabel} | ${'regular'} | ${false} ${mockScopedLabel} | ${'scoped'} | ${true} `( 'return $returnValue when provided label param is a $labelType label', ({ label, returnValue }) => { expect(wrapper.vm.scopedLabel(label)).toBe(returnValue); }, ); }); describe('labelTitle', () => { it.each` label | propWithTitle | returnValue ${{ title: 'foo' }} | ${'title'} | ${'foo'} ${{ name: 'foo' }} | ${'name'} | ${'foo'} `('returns string value of `label.$propWithTitle`', ({ label, returnValue }) => { expect(wrapper.vm.labelTitle(label)).toBe(returnValue); }); }); describe('labelTarget', () => { it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => { expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe( '?label_name[]=Documentation%20Update', ); }); it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => { wrapper.setProps({ enableLabelPermalinks: false, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe('#'); }); }); }); describe('template', () => { it.each` gitlabWebUrl | webUrl | expectedHref | expectedTarget ${undefined} | ${`${MOCK_GITLAB_URL}/issue`} | ${`${MOCK_GITLAB_URL}/issue`} | ${undefined} ${undefined} | ${'https://jira.com/issue'} | ${'https://jira.com/issue'} | ${'_blank'} ${'/gitlab-org/issue'} | ${'https://jira.com/issue'} | ${'/gitlab-org/issue'} | ${undefined} `( 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`', async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => { wrapper.setProps({ issuable: { ...mockIssuable, webUrl, gitlabWebUrl, }, }); await wrapper.vm.$nextTick(); const titleEl = wrapper.find('[data-testid="issuable-title"]'); expect(titleEl.exists()).toBe(true); expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref); expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget); expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title); }, ); it('renders checkbox when `showCheckbox` prop is true', async () => { wrapper.setProps({ showCheckbox: true, }); await wrapper.vm.$nextTick(); expect(wrapper.find(GlFormCheckbox).exists()).toBe(true); expect(wrapper.find(GlFormCheckbox).attributes('checked')).not.toBeDefined(); wrapper.setProps({ checked: true, }); await wrapper.vm.$nextTick(); expect(wrapper.find(GlFormCheckbox).attributes('checked')).toBe('true'); }); it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => { wrapper.setProps({ issuable: { ...mockIssuable, webUrl: 'http://jira.atlassian.net/browse/IG-1', }, }); await wrapper.vm.$nextTick(); expect(wrapper.find('[data-testid="issuable-title"]').find(GlLink).attributes('target')).toBe( '_blank', ); }); it('renders issuable confidential icon when issuable is confidential', async () => { wrapper.setProps({ issuable: { ...mockIssuable, confidential: true, }, }); await wrapper.vm.$nextTick(); const confidentialEl = wrapper.find('[data-testid="issuable-title"]').find(GlIcon); expect(confidentialEl.exists()).toBe(true); expect(confidentialEl.props('name')).toBe('eye-slash'); expect(confidentialEl.attributes()).toMatchObject({ title: 'Confidential', arialabel: 'Confidential', }); }); it('renders task status', () => { const taskStatus = wrapper.find('[data-testid="task-status"]'); const expected = `${mockIssuable.taskCompletionStatus.completedCount} of ${mockIssuable.taskCompletionStatus.count} tasks completed`; expect(taskStatus.text()).toBe(expected); }); it('renders issuable reference', () => { const referenceEl = wrapper.find('[data-testid="issuable-reference"]'); expect(referenceEl.exists()).toBe(true); expect(referenceEl.text()).toBe(`#${mockIssuable.iid}`); }); it('renders issuable reference via slot', () => { const wrapperWithRefSlot = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { reference: ` ${mockIssuable.iid} `, }, }); const referenceEl = wrapperWithRefSlot.find('.js-reference'); expect(referenceEl.exists()).toBe(true); expect(referenceEl.text()).toBe(`${mockIssuable.iid}`); wrapperWithRefSlot.destroy(); }); it('renders issuable createdAt info', () => { const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]'); expect(createdAtEl.exists()).toBe(true); expect(createdAtEl.attributes('title')).toBe('Jun 29, 2020 1:52pm GMT+0000'); expect(createdAtEl.text()).toBe(wrapper.vm.createdAt); }); it('renders issuable author info', () => { const authorEl = wrapper.find('[data-testid="issuable-author"]'); expect(authorEl.exists()).toBe(true); expect(authorEl.attributes()).toMatchObject({ 'data-user-id': `${wrapper.vm.authorId}`, 'data-username': mockAuthor.username, 'data-name': mockAuthor.name, 'data-avatar-url': mockAuthor.avatarUrl, href: mockAuthor.webUrl, }); expect(authorEl.text()).toBe(mockAuthor.name); }); it('renders issuable author info via slot', () => { const wrapperWithAuthorSlot = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { reference: ` ${mockAuthor.name} `, }, }); const authorEl = wrapperWithAuthorSlot.find('.js-author'); expect(authorEl.exists()).toBe(true); expect(authorEl.text()).toBe(mockAuthor.name); wrapperWithAuthorSlot.destroy(); }); it('renders timeframe via slot', () => { const wrapperWithTimeframeSlot = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { timeframe: ` Jan 1, 2020 - Mar 31, 2020 `, }, }); const timeframeEl = wrapperWithTimeframeSlot.find('.js-timeframe'); expect(timeframeEl.exists()).toBe(true); expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020'); wrapperWithTimeframeSlot.destroy(); }); it('renders gl-label component for each label present within `issuable` prop', () => { const labelsEl = wrapper.findAll(GlLabel); expect(labelsEl.exists()).toBe(true); expect(labelsEl).toHaveLength(mockLabels.length); expect(labelsEl.at(0).props()).toMatchObject({ backgroundColor: mockLabels[0].color, title: mockLabels[0].title, description: mockLabels[0].description, scoped: false, target: wrapper.vm.labelTarget(mockLabels[0]), size: 'sm', }); }); it('renders issuable status via slot', () => { const wrapperWithStatusSlot = createComponent({ issuableSymbol: '#', issuable: mockIssuable, slots: { status: ` ${mockIssuable.state} `, }, }); const statusEl = wrapperWithStatusSlot.find('.js-status'); expect(statusEl.exists()).toBe(true); expect(statusEl.text()).toBe(`${mockIssuable.state}`); wrapperWithStatusSlot.destroy(); }); it('renders discussions count', () => { const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]'); expect(discussionsEl.exists()).toBe(true); expect(discussionsEl.find(GlLink).attributes()).toMatchObject({ title: 'Comments', href: `${mockIssuable.webUrl}#notes`, }); expect(discussionsEl.find(GlIcon).props('name')).toBe('comments'); expect(discussionsEl.find(GlLink).text()).toContain('2'); }); it('renders issuable-assignees component', () => { const assigneesEl = wrapper.find(IssuableAssignees); expect(assigneesEl.exists()).toBe(true); expect(assigneesEl.props()).toMatchObject({ assignees: mockIssuable.assignees, iconSize: 16, maxVisible: 4, }); }); it('renders issuable updatedAt info', () => { const updatedAtEl = wrapper.find('[data-testid="issuable-updated-at"]'); expect(updatedAtEl.exists()).toBe(true); expect(updatedAtEl.find('span').attributes('title')).toBe('Sep 10, 2020 11:41am GMT+0000'); expect(updatedAtEl.text()).toBe(wrapper.vm.updatedAt); }); describe('when issuable is closed', () => { it('renders issuable card with a closed style', () => { wrapper = createComponent({ issuable: { ...mockIssuable, closedAt: '2020-12-10' } }); expect(wrapper.classes()).toContain('closed'); }); }); describe('when issuable was created within the past 24 hours', () => { it('renders issuable card with a recently-created style', () => { wrapper = createComponent({ issuable: { ...mockIssuable, createdAt: '2020-12-10T12:34:56' }, }); expect(wrapper.classes()).toContain('today'); }); }); describe('when issuable was created earlier than the past 24 hours', () => { it('renders issuable card without a recently-created style', () => { wrapper = createComponent({ issuable: { ...mockIssuable, createdAt: '2020-12-09' } }); expect(wrapper.classes()).not.toContain('today'); }); }); }); });