summaryrefslogtreecommitdiff
path: root/spec/frontend/issue_show/components/header_actions_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/issue_show/components/header_actions_spec.js')
-rw-r--r--spec/frontend/issue_show/components/header_actions_spec.js328
1 files changed, 328 insertions, 0 deletions
diff --git a/spec/frontend/issue_show/components/header_actions_spec.js b/spec/frontend/issue_show/components/header_actions_spec.js
new file mode 100644
index 00000000000..67b8665a889
--- /dev/null
+++ b/spec/frontend/issue_show/components/header_actions_spec.js
@@ -0,0 +1,328 @@
+import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import createFlash, { FLASH_TYPES } from '~/flash';
+import { IssuableType } from '~/issuable_show/constants';
+import HeaderActions from '~/issue_show/components/header_actions.vue';
+import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
+import promoteToEpicMutation from '~/issue_show/queries/promote_to_epic.mutation.graphql';
+import * as urlUtility from '~/lib/utils/url_utility';
+import createStore from '~/notes/stores';
+
+jest.mock('~/flash');
+
+describe('HeaderActions component', () => {
+ let dispatchEventSpy;
+ let mutateMock;
+ let wrapper;
+ let visitUrlSpy;
+
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+ const store = createStore();
+
+ const defaultProps = {
+ canCreateIssue: true,
+ canPromoteToEpic: true,
+ canReopenIssue: true,
+ canReportSpam: true,
+ canUpdateIssue: true,
+ iid: '32',
+ isIssueAuthor: true,
+ issueType: IssuableType.Issue,
+ newIssuePath: 'gitlab-org/gitlab-test/-/issues/new',
+ projectPath: 'gitlab-org/gitlab-test',
+ reportAbusePath:
+ '-/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%2Fgitlab-org%2Fgitlab-test%2F-%2Fissues%2F32&user_id=1',
+ submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam',
+ };
+
+ const updateIssueMutationResponse = { data: { updateIssue: { errors: [] } } };
+
+ const promoteToEpicMutationResponse = {
+ data: {
+ promoteToEpic: {
+ errors: [],
+ epic: {
+ webPath: '/groups/gitlab-org/-/epics/1',
+ },
+ },
+ },
+ };
+
+ const promoteToEpicMutationErrorResponse = {
+ data: {
+ promoteToEpic: {
+ errors: ['The issue has already been promoted to an epic.'],
+ epic: {},
+ },
+ },
+ };
+
+ const findToggleIssueStateButton = () => wrapper.find(GlButton);
+
+ const findDropdownAt = index => wrapper.findAll(GlDropdown).at(index);
+
+ const findMobileDropdownItems = () => findDropdownAt(0).findAll(GlDropdownItem);
+
+ const findDesktopDropdownItems = () => findDropdownAt(1).findAll(GlDropdownItem);
+
+ const findModal = () => wrapper.find(GlModal);
+
+ const findModalLinkAt = index =>
+ findModal()
+ .findAll(GlLink)
+ .at(index);
+
+ const mountComponent = ({
+ props = {},
+ issueState = IssuableStatus.Open,
+ blockedByIssues = [],
+ mutateResponse = {},
+ } = {}) => {
+ mutateMock = jest.fn().mockResolvedValue(mutateResponse);
+
+ store.getters.getNoteableData.state = issueState;
+ store.getters.getNoteableData.blocked_by_issues = blockedByIssues;
+
+ return shallowMount(HeaderActions, {
+ localVue,
+ store,
+ provide: {
+ ...defaultProps,
+ ...props,
+ },
+ mocks: {
+ $apollo: {
+ mutate: mutateMock,
+ },
+ },
+ });
+ };
+
+ afterEach(() => {
+ if (dispatchEventSpy) {
+ dispatchEventSpy.mockRestore();
+ }
+ if (visitUrlSpy) {
+ visitUrlSpy.mockRestore();
+ }
+ wrapper.destroy();
+ });
+
+ describe.each`
+ issueType
+ ${IssuableType.Issue}
+ ${IssuableType.Incident}
+ `('when issue type is $issueType', ({ issueType }) => {
+ describe('close/reopen button', () => {
+ describe.each`
+ description | issueState | buttonText | newIssueState
+ ${`when the ${issueType} is open`} | ${IssuableStatus.Open} | ${`Close ${issueType}`} | ${IssueStateEvent.Close}
+ ${`when the ${issueType} is closed`} | ${IssuableStatus.Closed} | ${`Reopen ${issueType}`} | ${IssueStateEvent.Reopen}
+ `('$description', ({ issueState, buttonText, newIssueState }) => {
+ beforeEach(() => {
+ dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
+
+ wrapper = mountComponent({
+ props: { issueType },
+ issueState,
+ mutateResponse: updateIssueMutationResponse,
+ });
+ });
+
+ it(`has text "${buttonText}"`, () => {
+ expect(findToggleIssueStateButton().text()).toBe(buttonText);
+ });
+
+ it('calls apollo mutation', () => {
+ findToggleIssueStateButton().vm.$emit('click');
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ stateEvent: newIssueState,
+ },
+ },
+ }),
+ );
+ });
+
+ it('dispatches a custom event to update the issue page', async () => {
+ findToggleIssueStateButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
+ describe.each`
+ description | isCloseIssueItemVisible | findDropdownItems
+ ${'mobile dropdown'} | ${true} | ${findMobileDropdownItems}
+ ${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems}
+ `('$description', ({ isCloseIssueItemVisible, findDropdownItems }) => {
+ describe.each`
+ description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic
+ ${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} | ${true}
+ ${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true} | ${true}
+ ${`when user can create ${issueType}`} | ${`New ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
+ ${`when user cannot create ${issueType}`} | ${`New ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true} | ${true}
+ ${'when user can promote to epic'} | ${'Promote to epic'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
+ ${'when user cannot promote to epic'} | ${'Promote to epic'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${false}
+ ${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true}
+ ${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true}
+ ${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
+ ${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} | ${true}
+ `(
+ '$description',
+ ({
+ itemText,
+ isItemVisible,
+ canUpdateIssue,
+ canCreateIssue,
+ isIssueAuthor,
+ canReportSpam,
+ canPromoteToEpic,
+ }) => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ canUpdateIssue,
+ canCreateIssue,
+ isIssueAuthor,
+ issueType,
+ canReportSpam,
+ canPromoteToEpic,
+ },
+ });
+ });
+
+ it(`${isItemVisible ? 'shows' : 'hides'} "${itemText}" item`, () => {
+ expect(
+ findDropdownItems()
+ .filter(item => item.text() === itemText)
+ .exists(),
+ ).toBe(isItemVisible);
+ });
+ },
+ );
+ });
+ });
+
+ describe('when "Promote to epic" button is clicked', () => {
+ describe('when response is successful', () => {
+ beforeEach(() => {
+ visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
+
+ wrapper = mountComponent({
+ mutateResponse: promoteToEpicMutationResponse,
+ });
+
+ wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+ });
+
+ it('invokes GraphQL mutation when clicked', () => {
+ expect(mutateMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ mutation: promoteToEpicMutation,
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ },
+ },
+ }),
+ );
+ });
+
+ it('shows a success message and tells the user they are being redirected', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'The issue was successfully promoted to an epic. Redirecting to epic...',
+ type: FLASH_TYPES.SUCCESS,
+ });
+ });
+
+ it('redirects to newly created epic path', () => {
+ expect(visitUrlSpy).toHaveBeenCalledWith(
+ promoteToEpicMutationResponse.data.promoteToEpic.epic.webPath,
+ );
+ });
+ });
+
+ describe('when response contains errors', () => {
+ beforeEach(() => {
+ visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
+
+ wrapper = mountComponent({
+ mutateResponse: promoteToEpicMutationErrorResponse,
+ });
+
+ wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+ });
+
+ it('shows an error message', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: promoteToEpicMutationErrorResponse.data.promoteToEpic.errors.join('; '),
+ });
+ });
+ });
+ });
+
+ describe('modal', () => {
+ const blockedByIssues = [
+ { iid: 13, web_url: 'gitlab-org/gitlab-test/-/issues/13' },
+ { iid: 79, web_url: 'gitlab-org/gitlab-test/-/issues/79' },
+ ];
+
+ beforeEach(() => {
+ wrapper = mountComponent({ blockedByIssues });
+ });
+
+ it('has title text', () => {
+ expect(findModal().attributes('title')).toBe(
+ 'Are you sure you want to close this blocked issue?',
+ );
+ });
+
+ it('has body text', () => {
+ expect(findModal().text()).toContain(
+ 'This issue is currently blocked by the following issues:',
+ );
+ });
+
+ it('calls apollo mutation when primary button is clicked', () => {
+ findModal().vm.$emit('primary');
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ variables: {
+ input: {
+ iid: defaultProps.iid.toString(),
+ projectPath: defaultProps.projectPath,
+ stateEvent: IssueStateEvent.Close,
+ },
+ },
+ }),
+ );
+ });
+
+ describe.each`
+ ordinal | index
+ ${'first'} | ${0}
+ ${'second'} | ${1}
+ `('$ordinal blocked-by issue link', ({ index }) => {
+ it('has link text', () => {
+ expect(findModalLinkAt(index).text()).toBe(`#${blockedByIssues[index].iid}`);
+ });
+
+ it('has url', () => {
+ expect(findModalLinkAt(index).attributes('href')).toBe(blockedByIssues[index].web_url);
+ });
+ });
+ });
+});