diff options
Diffstat (limited to 'spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js')
-rw-r--r-- | spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js | 350 |
1 files changed, 222 insertions, 128 deletions
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js index d2b7b2e89c8..4d08ad54e58 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js @@ -1,195 +1,289 @@ -import { GlButton, GlBadge, GlIcon, GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import { TYPE_ISSUE, WORKSPACE_PROJECT } from '~/issues/constants'; +import { GlBadge, GlButton, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { + STATUS_CLOSED, + STATUS_OPEN, + STATUS_REOPENED, + TYPE_ISSUE, + WORKSPACE_PROJECT, +} from '~/issues/constants'; +import { __ } from '~/locale'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue'; -import { mockIssuableShowProps, mockIssuable } from '../mock_data'; +import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue'; +import { mockIssuable, mockIssuableShowProps } from '../mock_data'; -const issuableHeaderProps = { - ...mockIssuable, - ...mockIssuableShowProps, - issuableType: TYPE_ISSUE, - workspaceType: WORKSPACE_PROJECT, -}; - -describe('IssuableHeader', () => { +describe('IssuableHeader component', () => { let wrapper; - const findAvatar = () => wrapper.findByTestId('avatar'); - const findTaskStatusEl = () => wrapper.findByTestId('task-status'); - const findButton = () => wrapper.findComponent(GlButton); - const findGlAvatarLink = () => wrapper.findComponent(GlAvatarLink); + const findConfidentialityBadge = () => wrapper.findComponent(ConfidentialityBadge); + const findStatusBadge = () => wrapper.findComponent(GlBadge); + const findToggleButton = () => wrapper.findComponent(GlButton); + const findAuthorLink = () => wrapper.findComponent(GlLink); + const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip); + const findWorkItemTypeIcon = () => wrapper.findComponent(WorkItemTypeIcon); + const findGlIconWithName = (name) => + wrapper.findAllComponents(GlIcon).filter((component) => component.props('name') === name); + const findIcon = (name) => + findGlIconWithName(name).exists() ? findGlIconWithName(name).at(0) : undefined; + const findBlockedIcon = () => findIcon('lock'); + const findHiddenIcon = () => findIcon('spam'); + const findExternalLinkIcon = () => findIcon('external-link'); + const findFirstContributionIcon = () => findIcon('first-contribution'); + const findComponentTooltip = (component) => getBinding(component.element, 'gl-tooltip'); const createComponent = (props = {}, { stubs } = {}) => { - wrapper = shallowMountExtended(IssuableHeader, { + wrapper = shallowMount(IssuableHeader, { + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, propsData: { - ...issuableHeaderProps, + ...mockIssuable, + ...mockIssuableShowProps, + issuableState: STATUS_OPEN, + issuableType: TYPE_ISSUE, + workspaceType: WORKSPACE_PROJECT, ...props, }, slots: { - 'status-badge': 'Open', - 'header-actions': ` - <button class="js-close">Close issuable</button> - <a class="js-new" href="/gitlab-org/gitlab-shell/-/issues/new">New issuable</a> - `, + 'header-actions': `Header actions slot`, + }, + stubs: { + GlSprintf, + ...stubs, }, - stubs, }); }; - afterEach(() => { - resetHTMLFixture(); - }); + describe('status badge', () => { + describe('variant', () => { + it('is `success` when status is open', () => { + createComponent({ issuableState: STATUS_OPEN }); - describe('computed', () => { - describe('authorId', () => { - it('returns numeric ID from GraphQL ID of `author` prop', () => { - createComponent(); - expect(findGlAvatarLink().attributes('data-user-id')).toBe('1'); + expect(findStatusBadge().props('variant')).toBe('success'); + }); + + it('is `success` when status is reopened', () => { + createComponent({ issuableState: STATUS_REOPENED }); + + expect(findStatusBadge().props('variant')).toBe('success'); + }); + + it('is `info` when status is closed', () => { + createComponent({ issuableState: STATUS_CLOSED }); + + expect(findStatusBadge().props('variant')).toBe('info'); }); }); - }); - describe('handleRightSidebarToggleClick', () => { - beforeEach(() => { - setHTMLFixture('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>'); + describe('icon', () => { + it('renders when statusIcon prop exists', () => { + createComponent({ statusIcon: 'issues' }); + + expect(findStatusBadge().findComponent(GlIcon).props('name')).toBe('issues'); + }); + + it('does not render when statusIcon prop does not exist', () => { + createComponent({ statusIcon: '' }); + + expect(findStatusBadge().findComponent(GlIcon).exists()).toBe(false); + }); }); - it('emits a "toggle" event', () => { + it('renders status text', () => { createComponent(); - findButton().vm.$emit('click'); + expect(findStatusBadge().text()).toBe(__('Open')); + }); + }); + + describe('confidential badge', () => { + it('renders when issuable is confidential', () => { + createComponent({ confidential: true }); + + expect(findConfidentialityBadge().props()).toEqual({ + issuableType: 'issue', + workspaceType: 'project', + }); + }); + + it('does not render when issuable is not confidential', () => { + createComponent({ confidential: false }); - expect(wrapper.emitted('toggle')).toEqual([[]]); + expect(findConfidentialityBadge().exists()).toBe(false); }); + }); - it('dispatches `click` event on sidebar toggle button', () => { - createComponent(); - const toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button'); - const dispatchEvent = jest - .spyOn(toggleSidebarButtonEl, 'dispatchEvent') - .mockImplementation(jest.fn); + describe('blocked icon', () => { + it('renders when issuable is blocked', () => { + createComponent({ blocked: true }); - findButton().vm.$emit('click'); + expect(findBlockedIcon().props('ariaLabel')).toBe('Blocked'); + }); - expect(dispatchEvent).toHaveBeenCalledWith( - expect.objectContaining({ - type: 'click', - }), + it('has tooltip', () => { + createComponent({ blocked: true }); + + expect(findComponentTooltip(findBlockedIcon())).toBeDefined(); + expect(findBlockedIcon().attributes('title')).toBe( + 'This issue is locked. Only project members can comment.', ); }); + + it('does not render when issuable is not blocked', () => { + createComponent({ blocked: false }); + + expect(findBlockedIcon()).toBeUndefined(); + }); }); - describe('template', () => { - it('renders issuable status icon and text', () => { - createComponent(); - const statusBoxEl = wrapper.findComponent(GlBadge); - const statusIconEl = statusBoxEl.findComponent(GlIcon); + describe('hidden icon', () => { + it('renders when issuable is hidden', () => { + createComponent({ isHidden: true }); - expect(statusBoxEl.exists()).toBe(true); - expect(statusIconEl.props('name')).toBe(mockIssuableShowProps.statusIcon); - expect(statusIconEl.attributes('class')).toBe(mockIssuableShowProps.statusIconClass); - expect(statusBoxEl.text()).toContain('Open'); + expect(findHiddenIcon().props('ariaLabel')).toBe('Hidden'); }); - it('renders blocked icon when issuable is blocked', () => { - createComponent({ - blocked: true, - }); + it('has tooltip', () => { + createComponent({ isHidden: true }); - const blockedEl = wrapper.findByTestId('blocked'); + expect(findComponentTooltip(findHiddenIcon())).toBeDefined(); + expect(findHiddenIcon().attributes('title')).toBe( + 'This issue is hidden because its author has been banned', + ); + }); - expect(blockedEl.exists()).toBe(true); - expect(blockedEl.findComponent(GlIcon).props('name')).toBe('lock'); + it('does not render when issuable is not hidden', () => { + createComponent({ isHidden: false }); + + expect(findHiddenIcon()).toBeUndefined(); }); + }); - it('renders confidential icon when issuable is confidential', () => { - createComponent({ confidential: true }); + describe('work item type icon', () => { + it('renders when showWorkItemTypeIcon=true and work item type exists', () => { + createComponent({ showWorkItemTypeIcon: true, issuableType: 'issue' }); - expect(wrapper.findComponent(ConfidentialityBadge).props()).toEqual({ - issuableType: 'issue', - workspaceType: 'project', + expect(findWorkItemTypeIcon().props()).toMatchObject({ + showText: true, + workItemType: 'ISSUE', }); }); - it('renders issuable author avatar', () => { + it('does not render when showWorkItemTypeIcon=false', () => { + createComponent({ showWorkItemTypeIcon: false }); + + expect(findWorkItemTypeIcon().exists()).toBe(false); + }); + }); + + describe('timeago tooltip', () => { + it('renders', () => { createComponent(); - const { username, name, webUrl, avatarUrl } = mockIssuable.author; - const avatarElAttrs = { + + expect(findTimeAgoTooltip().props('time')).toBe('2020-06-29T13:52:56Z'); + }); + }); + + describe('author', () => { + it('renders link', () => { + createComponent(); + + expect(findAuthorLink().text()).toContain('Administrator'); + expect(findAuthorLink().attributes()).toMatchObject({ + href: 'http://0.0.0.0:3000/root', 'data-user-id': '1', - 'data-username': username, - 'data-name': name, - href: webUrl, - target: '_blank', - }; - const avatarEl = findAvatar(); - expect(avatarEl.exists()).toBe(true); - expect(avatarEl.attributes()).toMatchObject(avatarElAttrs); - expect(avatarEl.findComponent(GlAvatarLabeled).attributes()).toMatchObject({ - size: '24', - src: avatarUrl, - label: name, }); - expect(avatarEl.findComponent(GlAvatarLabeled).findComponent(GlIcon).exists()).toBe(false); + expect(findAuthorLink().classes()).toContain('js-user-link'); + }); + + describe('when author exists outside of GitLab', () => { + it('renders external link icon', () => { + createComponent({ author: { webUrl: 'https://example.com/test-user' } }); + + expect(findExternalLinkIcon().props('ariaLabel')).toBe('external link'); + }); + }); + }); + + describe('first contribution icon', () => { + it('renders when isFirstContribution=true', () => { + createComponent({ isFirstContribution: true }); + + expect(findFirstContributionIcon().props('ariaLabel')).toBe('1st contribution!'); + }); + + it('has tooltip', () => { + createComponent({ isFirstContribution: true }); + + expect(findComponentTooltip(findFirstContributionIcon())).toBeDefined(); + expect(findFirstContributionIcon().attributes('title')).toBe('1st contribution!'); }); + it('does not render when isFirstContribution=false', () => { + createComponent({ isFirstContribution: false }); + + expect(findFirstContributionIcon()).toBeUndefined(); + }); + }); + + describe('task status', () => { it('renders task status text when `taskCompletionStatus` prop is defined', () => { createComponent(); - expect(findTaskStatusEl().exists()).toBe(true); - expect(findTaskStatusEl().text()).toContain('0 of 5 checklist items completed'); + expect(wrapper.text()).toContain('0 of 5 checklist items completed'); }); it('does not render task status text when tasks count is 0', () => { - createComponent({ - taskCompletionStatus: { - count: 0, - completedCount: 0, - }, - }); + createComponent({ taskCompletionStatus: { count: 0, completedCount: 0 } }); - expect(findTaskStatusEl().exists()).toBe(false); + expect(wrapper.text()).not.toContain('checklist item'); }); + }); - it('renders sidebar toggle button', () => { + describe('sidebar toggle button', () => { + beforeEach(() => { + setHTMLFixture('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>'); createComponent(); - const toggleButtonEl = wrapper.findByTestId('sidebar-toggle'); - - expect(toggleButtonEl.exists()).toBe(true); - expect(toggleButtonEl.props('icon')).toBe('chevron-double-lg-left'); }); - it('renders header actions', () => { - createComponent(); - const actionsEl = wrapper.findByTestId('header-actions'); + afterEach(() => { + resetHTMLFixture(); + }); - expect(actionsEl.find('button.js-close').exists()).toBe(true); - expect(actionsEl.find('a.js-new').exists()).toBe(true); + it('renders', () => { + expect(findToggleButton().props('icon')).toBe('chevron-double-lg-left'); + expect(findToggleButton().attributes('aria-label')).toBe('Expand sidebar'); }); - describe('when author exists outside of GitLab', () => { - it("renders 'external-link' icon in avatar label", () => { - createComponent( - { - author: { - ...issuableHeaderProps.author, - webUrl: 'https://jira.com/test-user/author.jpg', - }, - }, - { - stubs: { - GlAvatarLabeled, - }, - }, - ); - - const avatarEl = wrapper.findComponent(GlAvatarLabeled); - const icon = avatarEl.findComponent(GlIcon); - - expect(icon.exists()).toBe(true); - expect(icon.props('name')).toBe('external-link'); + describe('when clicked', () => { + it('emits a "toggle" event', () => { + findToggleButton().vm.$emit('click'); + + expect(wrapper.emitted('toggle')).toEqual([[]]); + }); + + it('dispatches `click` event on sidebar toggle button', () => { + const toggleSidebarButton = document.querySelector('.js-toggle-right-sidebar-button'); + const dispatchEvent = jest + .spyOn(toggleSidebarButton, 'dispatchEvent') + .mockImplementation(jest.fn); + + findToggleButton().vm.$emit('click'); + + expect(dispatchEvent).toHaveBeenCalledWith(expect.objectContaining({ type: 'click' })); }); }); }); + + describe('header actions', () => { + it('renders slot', () => { + createComponent(); + + expect(wrapper.text()).toContain('Header actions slot'); + }); + }); }); |