diff options
Diffstat (limited to 'spec/frontend/vue_merge_request_widget/components/states')
13 files changed, 490 insertions, 411 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js index 8eeba4d6274..e4448346685 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import MergeChecksFailed from '~/vue_merge_request_widget/components/states/merge_checks_failed.vue'; import { DETAILED_MERGE_STATUS } from '~/vue_merge_request_widget/constants'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; let wrapper; @@ -23,6 +24,7 @@ describe('Merge request widget merge checks failed state component', () => { `('display $displayText text for $mrState', ({ mrState, displayText }) => { factory({ mr: mrState }); - expect(wrapper.text()).toContain(MergeChecksFailed.i18n[displayText]); + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain(MergeChecksFailed.i18n[displayText]); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js index 5c07f4ce143..08700e834d7 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue'; import StateContainer from '~/vue_merge_request_widget/components/state_container.vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; describe('MRWidgetArchived', () => { let wrapper; @@ -20,8 +21,8 @@ describe('MRWidgetArchived', () => { }); it('renders information about merging', () => { - expect(wrapper.text()).toContain( - 'Merge unavailable: merge requests are read-only on archived projects.', - ); + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain('Merge unavailable:'); + expect(message).toContain('merge requests are read-only on archived projects.'); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js index 774e2bafed3..a6d3a6286a7 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js @@ -58,13 +58,8 @@ describe('Commits header component', () => { expect(findCommitToggle().attributes('aria-label')).toBe('Expand'); }); - it('has a chevron-right icon', async () => { + it('has a chevron-right icon', () => { createComponent(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ expanded: false }); - - await nextTick(); expect(findCommitToggle().props('icon')).toBe('chevron-right'); }); @@ -110,25 +105,21 @@ describe('Commits header component', () => { }); describe('when expanded', () => { - beforeEach(() => { + beforeEach(async () => { createComponent(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ expanded: true }); + findCommitToggle().trigger('click'); + await nextTick(); }); - it('toggle has aria-label equal to collapse', async () => { - await nextTick(); + it('toggle has aria-label equal to collapse', () => { expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); }); - it('has a chevron-down icon', async () => { - await nextTick(); + it('has a chevron-down icon', () => { expect(findCommitToggle().props('icon')).toBe('chevron-down'); }); - it('has a collapse text', async () => { - await nextTick(); + it('has a collapse text', () => { expect(findHeaderWrapper().text()).toBe('Collapse'); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js index a16e4d4a6ea..2ca9dc61745 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js @@ -12,9 +12,9 @@ describe('MRWidgetConflicts', () => { const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button'); const findMergeLocalButton = () => wrapper.findByTestId('merge-locally-button'); - const mergeConflictsText = 'Merge blocked: merge conflicts must be resolved.'; + const mergeConflictsText = 'merge conflicts must be resolved.'; const fastForwardMergeText = - 'Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.'; + 'fast-forward merge is not possible. To merge this request, first rebase locally.'; const userCannotMergeText = 'Users who can write to the source or target branches can resolve the conflicts.'; const resolveConflictsBtnText = 'Resolve conflicts'; @@ -76,8 +76,9 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain(mergeConflictsText); + expect(text).not.toContain(userCannotMergeText); }); it('should not allow you to resolve the conflicts', () => { @@ -102,8 +103,8 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).toContain(userCannotMergeText); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain(userCannotMergeText); }); it('should allow you to resolve the conflicts', () => { @@ -129,8 +130,9 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain(mergeConflictsText); + expect(text).not.toContain(userCannotMergeText); }); it('should allow you to resolve the conflicts', () => { diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js index 49bd3739fdb..5408f731b34 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import simplePoll from '~/lib/utils/simple_poll'; import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; jest.mock('~/lib/utils/simple_poll', () => jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default), @@ -33,14 +34,8 @@ describe('MRWidgetMerging', () => { }); it('renders information about merge request being merged', () => { - expect( - wrapper - .find('.media-body') - .text() - .trim() - .replace(/\s\s+/g, ' ') - .replace(/[\r\n]+/g, ' '), - ).toContain('Merging!'); + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain('Merging!'); }); describe('initiateMergePolling', () => { diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js index c6e7198c678..42515c597c5 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; describe('MRWidgetNotAllowed', () => { let wrapper; @@ -20,8 +21,9 @@ describe('MRWidgetNotAllowed', () => { }); it('renders informative text', () => { - expect(wrapper.text()).toContain('Ready to be merged automatically.'); - expect(wrapper.text()).toContain( + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain('Ready to be merged automatically.'); + expect(message).toContain( 'Ask someone with write access to this repository to merge this request', ); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js index 4219ad70b4c..c0197b5e20a 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; describe('MRWidgetPipelineBlocked', () => { let wrapper; @@ -20,8 +21,10 @@ describe('MRWidgetPipelineBlocked', () => { }); it('renders information text', () => { - expect(wrapper.text()).toBe( - "Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.", + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain('Merge blocked:'); + expect(message).toContain( + "pipeline must succeed. It's waiting for a manual action to continue.", ); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js index bd158d59d74..8bae2b62ed1 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js @@ -1,7 +1,9 @@ import { GlSprintf, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import { removeBreakLine } from 'helpers/text_helper'; import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; describe('PipelineFailed', () => { let wrapper; @@ -32,16 +34,20 @@ describe('PipelineFailed', () => { it('should render error message with a disabled merge button', () => { createComponent(); - expect(wrapper.text()).toContain('Merge blocked: pipeline must succeed.'); - expect(wrapper.text()).toContain('Push a commit that fixes the failure'); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain('Merge blocked:'); + expect(text).toContain('pipeline must succeed'); + expect(text).toContain('Push a commit that fixes the failure'); expect(wrapper.findComponent(GlLink).text()).toContain('learn about other solutions'); }); it('should render pipeline blocked message', () => { createComponent({ isPipelineBlocked: true }); - expect(wrapper.text()).toContain( - "Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.", + const message = wrapper.findComponent(BoldText).props('message'); + expect(message).toContain('Merge blocked:'); + expect(message).toContain( + "pipeline must succeed. It's waiting for a manual action to continue.", ); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js index d34fc0c1e61..1e4e089e7c1 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -79,13 +79,10 @@ const createTestService = () => ({ Vue.use(VueApollo); +let service; let wrapper; let readyToMergeResponseSpy; -const findMergeButton = () => wrapper.find('[data-testid="merge-button"]'); -const findPipelineFailedConfirmModal = () => - wrapper.findComponent(MergeFailedPipelineConfirmationDialog); - const createReadyToMergeResponse = (customMr) => { return produce(readyToMergeResponse, (draft) => { Object.assign(draft.data.project.mergeRequest, customMr); @@ -96,7 +93,7 @@ const createComponent = (customConfig = {}, createState = true) => { wrapper = shallowMount(ReadyToMerge, { propsData: { mr: createTestMr(customConfig), - service: createTestService(), + service, }, data() { if (createState) { @@ -119,6 +116,13 @@ const createComponent = (customConfig = {}, createState = true) => { }); }; +const findMergeButton = () => wrapper.find('[data-testid="merge-button"]'); +const findMergeImmediatelyDropdown = () => + wrapper.find('[data-testid="merge-immediately-dropdown"'); +const findSourceBranchDeletedText = () => + wrapper.find('[data-testid="source-branch-deleted-text"]'); +const findPipelineFailedConfirmModal = () => + wrapper.findComponent(MergeFailedPipelineConfirmationDialog); const findCheckboxElement = () => wrapper.findComponent(SquashBeforeMerge); const findCommitEditElements = () => wrapper.findAllComponents(CommitEdit); const findCommitDropdownElement = () => wrapper.findComponent(CommitMessageDropdown); @@ -129,33 +133,20 @@ const findCommitEditWithInputId = (inputId) => const findMergeCommitMessage = () => findCommitEditWithInputId('merge-message-edit').props('value'); const findSquashCommitMessage = () => findCommitEditWithInputId('squash-message-edit').props('value'); +const findDeleteSourceBranchCheckbox = () => + wrapper.find('[data-testid="delete-source-branch-checkbox"]'); const triggerApprovalUpdated = () => eventHub.$emit('ApprovalUpdated'); +const triggerEditCommitInput = () => + wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); describe('ReadyToMerge', () => { beforeEach(() => { + service = createTestService(); readyToMergeResponseSpy = jest.fn().mockResolvedValueOnce(readyToMergeResponse); }); - afterEach(() => { - wrapper.destroy(); - }); - describe('computed', () => { - describe('isAutoMergeAvailable', () => { - it('should return true when at least one merge strategy is available', () => { - createComponent({}); - - expect(wrapper.vm.isAutoMergeAvailable).toBe(true); - }); - - it('should return false when no merge strategies are available', () => { - createComponent({ mr: { availableAutoMergeStrategies: [] } }); - - expect(wrapper.vm.isAutoMergeAvailable).toBe(false); - }); - }); - describe('status', () => { it('defaults to success', () => { createComponent({ mr: { pipeline: true, availableAutoMergeStrategies: [] } }); @@ -190,16 +181,6 @@ describe('ReadyToMerge', () => { }); }); - describe('Merge Button Variant', () => { - it('defaults to confirm class', () => { - createComponent({ - mr: { availableAutoMergeStrategies: [], mergeable: true }, - }); - - expect(findMergeButton().attributes('variant')).toBe('confirm'); - }); - }); - describe('status icon', () => { it('defaults to tick icon', () => { createComponent({ mr: { mergeable: true } }); @@ -219,334 +200,313 @@ describe('ReadyToMerge', () => { expect(wrapper.vm.iconClass).toEqual('success'); }); }); + }); - describe('mergeButtonText', () => { - it('should return "Merge" when no auto merge strategies are available', () => { - createComponent({ mr: { availableAutoMergeStrategies: [] } }); + describe('merge button text', () => { + it('should return "Merge" when no auto merge strategies are available', () => { + createComponent({ mr: { availableAutoMergeStrategies: [] } }); + + expect(findMergeButton().text()).toBe('Merge'); + }); - expect(wrapper.vm.mergeButtonText).toEqual('Merge'); + it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => { + createComponent({ + mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY }, }); - it('should return "Merge in progress"', async () => { - createComponent(); + expect(findMergeButton().text()).toBe('Merge when pipeline succeeds'); + }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ isMergingImmediately: true }); + it('should return Merge when pipeline succeeds', () => { + createComponent({ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY } }); - await nextTick(); + expect(findMergeButton().text()).toBe('Merge when pipeline succeeds'); + }); + }); - expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress'); + describe('merge immediately dropdown', () => { + it('dropdown should be hidden if no pipeline is active', () => { + createComponent({ + mr: { isPipelineActive: false, onlyAllowMergeIfPipelineSucceeds: false }, }); - it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => { - createComponent({ - mr: { isMergingImmediately: false, preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY }, - }); + expect(findMergeImmediatelyDropdown().exists()).toBe(false); + }); - expect(wrapper.vm.mergeButtonText).toEqual('Merge when pipeline succeeds'); - }); + it('dropdown should be hidden if "Pipelines must succeed" is enabled', () => { + createComponent({ mr: { isPipelineActive: true, onlyAllowMergeIfPipelineSucceeds: true } }); + + expect(findMergeImmediatelyDropdown().exists()).toBe(false); }); + }); - describe('autoMergeText', () => { - it('should return Merge when pipeline succeeds', () => { - createComponent({ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY } }); + describe('merge button disabled state', () => { + it('should not be disabled initally', () => { + createComponent(); - expect(wrapper.vm.autoMergeText).toEqual('Merge when pipeline succeeds'); - }); + expect(findMergeButton().props('disabled')).toBe(false); }); - describe('shouldShowMergeImmediatelyDropdown', () => { - it('should return false if no pipeline is active', () => { - createComponent({ - mr: { isPipelineActive: false, onlyAllowMergeIfPipelineSucceeds: false }, - }); + it('should be disabled when there is no commit message', () => { + createComponent({ mr: { commitMessage: '' } }); - expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); + expect(findMergeButton().props('disabled')).toBe(true); + }); - it('should return false if "Pipelines must succeed" is enabled for the current project', () => { - createComponent({ mr: { isPipelineActive: true, onlyAllowMergeIfPipelineSucceeds: true } }); + it('should be disabled if merge is not allowed', () => { + createComponent({ mr: { preventMerge: true } }); - expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); + expect(findMergeButton().props('disabled')).toBe(true); }); - describe('isMergeButtonDisabled', () => { - it('should return false with initial data', () => { - createComponent({ mr: { isMergeAllowed: true, mergeable: false } }); + it('should be disabled when making request', async () => { + createComponent({ mr: { isMergeAllowed: true } }, true); - expect(wrapper.vm.isMergeButtonDisabled).toBe(false); - }); + findMergeButton().vm.$emit('click'); - it('should return true when there is no commit message', () => { - createComponent({ mr: { isMergeAllowed: true, commitMessage: '' } }); + await nextTick(); - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); + expect(findMergeButton().props('disabled')).toBe(true); + }); + }); - it('should return true if merge is not allowed', () => { + describe('sourceBranchDeletedText', () => { + const should = 'Source branch will be deleted.'; + const shouldNot = 'Source branch will not be deleted.'; + const did = 'Deleted the source branch.'; + const didNot = 'Did not delete the source branch.'; + const scenarios = [ + "the MR hasn't merged yet, and the backend-provided value expects to delete the branch", + "the MR hasn't merged yet, and the backend-provided value expects to leave the branch", + "the MR hasn't merged yet, and the backend-provided value is a non-boolean falsey value", + "the MR hasn't merged yet, and the backend-provided value is a non-boolean truthy value", + 'the MR has been merged, and the backend reports that the branch has been removed', + 'the MR has been merged, and the backend reports that the branch has not been removed', + 'the MR has been merged, and the backend reports a non-boolean falsey value', + 'the MR has been merged, and the backend reports a non-boolean truthy value', + ]; + + it.each` + describe | premerge | mrShould | mrRemoved | output + ${scenarios[0]} | ${true} | ${true} | ${null} | ${should} + ${scenarios[1]} | ${true} | ${false} | ${null} | ${shouldNot} + ${scenarios[2]} | ${true} | ${null} | ${null} | ${shouldNot} + ${scenarios[3]} | ${true} | ${'yeah'} | ${null} | ${should} + ${scenarios[4]} | ${false} | ${null} | ${true} | ${did} + ${scenarios[5]} | ${false} | ${null} | ${false} | ${didNot} + ${scenarios[6]} | ${false} | ${null} | ${null} | ${didNot} + ${scenarios[7]} | ${false} | ${null} | ${'yep'} | ${did} + `( + 'in the case that $describe, returns "$output"', + ({ premerge, mrShould, mrRemoved, output }) => { createComponent({ mr: { - isMergeAllowed: false, - availableAutoMergeStrategies: [], - onlyAllowMergeIfPipelineSucceeds: true, - mergeable: false, + state: !premerge ? 'merged' : 'literally-anything-else', + shouldRemoveSourceBranch: mrShould, + sourceBranchRemoved: mrRemoved, + autoMergeEnabled: true, }, }); - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); - - it('should return true when the vm instance is making request', async () => { - createComponent({ mr: { isMergeAllowed: true } }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ isMakingRequest: true }); - - await nextTick(); - - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); - }); - - describe('sourceBranchDeletedText', () => { - const should = 'Source branch will be deleted.'; - const shouldNot = 'Source branch will not be deleted.'; - const did = 'Deleted the source branch.'; - const didNot = 'Did not delete the source branch.'; - const scenarios = [ - "the MR hasn't merged yet, and the backend-provided value expects to delete the branch", - "the MR hasn't merged yet, and the backend-provided value expects to leave the branch", - "the MR hasn't merged yet, and the backend-provided value is a non-boolean falsey value", - "the MR hasn't merged yet, and the backend-provided value is a non-boolean truthy value", - 'the MR has been merged, and the backend reports that the branch has been removed', - 'the MR has been merged, and the backend reports that the branch has not been removed', - 'the MR has been merged, and the backend reports a non-boolean falsey value', - 'the MR has been merged, and the backend reports a non-boolean truthy value', - ]; - - it.each` - describe | premerge | mrShould | mrRemoved | output - ${scenarios[0]} | ${true} | ${true} | ${null} | ${should} - ${scenarios[1]} | ${true} | ${false} | ${null} | ${shouldNot} - ${scenarios[2]} | ${true} | ${null} | ${null} | ${shouldNot} - ${scenarios[3]} | ${true} | ${'yeah'} | ${null} | ${should} - ${scenarios[4]} | ${false} | ${null} | ${true} | ${did} - ${scenarios[5]} | ${false} | ${null} | ${false} | ${didNot} - ${scenarios[6]} | ${false} | ${null} | ${null} | ${didNot} - ${scenarios[7]} | ${false} | ${null} | ${'yep'} | ${did} - `( - 'in the case that $describe, returns "$output"', - ({ premerge, mrShould, mrRemoved, output }) => { - createComponent({ - mr: { - state: !premerge ? 'merged' : 'literally-anything-else', - shouldRemoveSourceBranch: mrShould, - sourceBranchRemoved: mrRemoved, - }, - }); - - expect(wrapper.vm.sourceBranchDeletedText).toBe(output); - }, - ); - }); + expect(findSourceBranchDeletedText().text()).toBe(output); + }, + ); }); - describe('methods', () => { - describe('handleMergeButtonClick', () => { - const response = (status) => ({ - data: { - status, - }, + describe('Merge Button Variant', () => { + it('defaults to confirm class', () => { + createComponent({ + mr: { availableAutoMergeStrategies: [], mergeable: true }, }); - beforeEach(() => { - readyToMergeResponseSpy = jest - .fn() - .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true })) - .mockResolvedValue( - createReadyToMergeResponse({ - squash: true, - squashOnMerge: true, - defaultMergeCommitMessage: '', - defaultSquashCommitMessage: '', - }), - ); - }); + expect(findMergeButton().attributes('variant')).toBe('confirm'); + }); + }); - it('should handle merge when pipeline succeeds', async () => { - createComponent(); + describe('Merge button click', () => { + const response = (status) => ({ + data: { + status, + }, + }); - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest - .spyOn(wrapper.vm.service, 'merge') - .mockResolvedValue(response('merge_when_pipeline_succeeds')); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ removeSourceBranch: false }); + beforeEach(() => { + readyToMergeResponseSpy = jest + .fn() + .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true })) + .mockResolvedValue( + createReadyToMergeResponse({ + squash: true, + squashOnMerge: true, + defaultMergeCommitMessage: '', + defaultSquashCommitMessage: '', + }), + ); + }); - wrapper.vm.handleMergeButtonClick(true); + it('should handle merge when pipeline succeeds', async () => { + createComponent({ mr: { shouldRemoveSourceBranch: false } }, true); - await waitForPromises(); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(service, 'merge').mockResolvedValue(response('merge_when_pipeline_succeeds')); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { - transition: 'start-auto-merge', - }); + findMergeButton().vm.$emit('click'); - const params = wrapper.vm.service.merge.mock.calls[0][0]; + await waitForPromises(); - expect(params).toEqual( - expect.objectContaining({ - sha: wrapper.vm.mr.sha, - commit_message: wrapper.vm.mr.commitMessage, - should_remove_source_branch: false, - auto_merge_strategy: 'merge_when_pipeline_succeeds', - }), - ); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { + transition: 'start-auto-merge', }); - it('should handle merge failed', async () => { - createComponent(); + const params = service.merge.mock.calls[0][0]; - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('failed')); - wrapper.vm.handleMergeButtonClick(false, true); + expect(params).toEqual( + expect.objectContaining({ + sha: '12345678', + commit_message: commitMessage, + should_remove_source_branch: false, + auto_merge_strategy: 'merge_when_pipeline_succeeds', + }), + ); + }); - await waitForPromises(); + it('should handle merge failed', async () => { + createComponent({ mr: { availableAutoMergeStrategies: [] } }); - expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(service, 'merge').mockResolvedValue(response('failed')); - const params = wrapper.vm.service.merge.mock.calls[0][0]; + findMergeButton().vm.$emit('click'); - expect(params.should_remove_source_branch).toBe(true); - expect(params.auto_merge_strategy).toBeUndefined(); - }); + await waitForPromises(); - it('should handle merge action accepted case', async () => { - createComponent(); + expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success')); - jest.spyOn(wrapper.vm.mr, 'transitionStateMachine'); - wrapper.vm.handleMergeButtonClick(); + const params = service.merge.mock.calls[0][0]; - expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { - transition: 'start-merge', - }); + expect(params.should_remove_source_branch).toBe(true); + expect(params.auto_merge_strategy).toBeUndefined(); + }); - await waitForPromises(); + it('should handle merge action accepted case', async () => { + createComponent({ mr: { availableAutoMergeStrategies: [] } }); - expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({ - transition: 'start-merge', - }); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(service, 'merge').mockResolvedValue(response('success')); + jest.spyOn(wrapper.vm.mr, 'transitionStateMachine'); - const params = wrapper.vm.service.merge.mock.calls[0][0]; + findMergeButton().vm.$emit('click'); - expect(params.should_remove_source_branch).toBe(true); - expect(params.auto_merge_strategy).toBeUndefined(); + expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { + transition: 'start-merge', }); - it('hides edit commit message', async () => { - createComponent({}, true, true); + await waitForPromises(); - await waitForPromises(); + expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({ + transition: 'start-merge', + }); - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success')); + const params = service.merge.mock.calls[0][0]; - await wrapper - .findComponent('[data-testid="widget_edit_commit_message"]') - .vm.$emit('input', true); + expect(params.should_remove_source_branch).toBe(true); + expect(params.auto_merge_strategy).toBeUndefined(); + }); - expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(true); + it('hides edit commit message', async () => { + createComponent(); - wrapper.vm.handleMergeButtonClick(); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(service, 'merge').mockResolvedValue(response('success')); - await waitForPromises(); + await triggerEditCommitInput(); - expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(false); - }); + expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(true); + + findMergeButton().vm.$emit('click'); + + await waitForPromises(); + + expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(false); }); + }); - describe('initiateRemoveSourceBranchPolling', () => { - it('should emit event and call simplePoll', () => { - createComponent(); + describe('initiateRemoveSourceBranchPolling', () => { + it('should emit event and call simplePoll', () => { + createComponent(); - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - wrapper.vm.initiateRemoveSourceBranchPolling(); + wrapper.vm.initiateRemoveSourceBranchPolling(); - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); - expect(simplePoll).toHaveBeenCalled(); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); + expect(simplePoll).toHaveBeenCalled(); }); + }); - describe('handleRemoveBranchPolling', () => { - const response = (state) => ({ - data: { - source_branch_exists: state, - }, - }); + describe('handleRemoveBranchPolling', () => { + const response = (state) => ({ + data: { + source_branch_exists: state, + }, + }); - it('should call start and stop polling when MR merged', async () => { - createComponent(); + it('should call start and stop polling when MR merged', async () => { + createComponent(); - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(false)); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(service, 'poll').mockResolvedValue(response(false)); - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled - wrapper.vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); + wrapper.vm.handleRemoveBranchPolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); - await waitForPromises(); + await waitForPromises(); - expect(wrapper.vm.service.poll).toHaveBeenCalled(); + expect(service.poll).toHaveBeenCalled(); - const args = eventHub.$emit.mock.calls[0]; + const args = eventHub.$emit.mock.calls[0]; - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).toBeDefined(); - args[1](); + expect(args[0]).toEqual('MRWidgetUpdateRequested'); + expect(args[1]).toBeDefined(); + args[1](); - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); + expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); - expect(cpc).toBe(false); - expect(spc).toBe(true); - }); + expect(cpc).toBe(false); + expect(spc).toBe(true); + }); - it('should continue polling until MR is merged', async () => { - createComponent(); + it('should continue polling until MR is merged', async () => { + createComponent(); - jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(true)); + jest.spyOn(service, 'poll').mockResolvedValue(response(true)); - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled - wrapper.vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); + wrapper.vm.handleRemoveBranchPolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); - await waitForPromises(); + await waitForPromises(); - expect(cpc).toBe(true); - expect(spc).toBe(false); - }); + expect(cpc).toBe(true); + expect(spc).toBe(false); }); }); @@ -563,7 +523,7 @@ describe('ReadyToMerge', () => { }, }); - expect(wrapper.find('#remove-source-branch-input').exists()).toBe(false); + expect(findDeleteSourceBranchCheckbox().exists()).toBe(false); }); }); @@ -575,7 +535,7 @@ describe('ReadyToMerge', () => { }); it('isRemoveSourceBranchButtonDisabled should be false', () => { - expect(wrapper.find('#remove-source-branch-input').props('disabled')).toBe(undefined); + expect(findDeleteSourceBranchCheckbox().props('disabled')).toBe(undefined); }); }); }); @@ -646,7 +606,7 @@ describe('ReadyToMerge', () => { }, }); - expect(findCommitEditElements().length).toBe(0); + expect(findCommitEditElements()).toHaveLength(0); }); it('should not be rendered if squash before merge is disabled', () => { @@ -659,7 +619,7 @@ describe('ReadyToMerge', () => { }, }); - expect(findCommitEditElements().length).toBe(0); + expect(findCommitEditElements()).toHaveLength(0); }); it('should not be rendered if there is only one commit', () => { @@ -672,7 +632,7 @@ describe('ReadyToMerge', () => { }, }); - expect(findCommitEditElements().length).toBe(0); + expect(findCommitEditElements()).toHaveLength(0); }); it('should have one edit component if squash is enabled and there is more than 1 commit', async () => { @@ -686,9 +646,9 @@ describe('ReadyToMerge', () => { }, }); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); - expect(findCommitEditElements().length).toBe(1); + expect(findCommitEditElements()).toHaveLength(1); expect(findFirstCommitEditLabel()).toBe('Squash commit message'); }); }); @@ -702,16 +662,15 @@ describe('ReadyToMerge', () => { }, }); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); - expect(findCommitEditElements().length).toBe(2); + expect(findCommitEditElements()).toHaveLength(2); }); - it('should have two edit components when squash is enabled and there is more than 1 commit and mergeRequestWidgetGraphql is enabled', async () => { + it('should have two edit components when squash is enabled', async () => { createComponent( { mr: { - commitsCount: 2, squashIsSelected: true, enableSquashBeforeMerge: true, }, @@ -719,37 +678,9 @@ describe('ReadyToMerge', () => { true, ); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - loading: false, - state: { - ...createTestMr({}), - userPermissions: {}, - squash: true, - mergeable: true, - commitCount: 2, - commitsWithoutMergeCommits: {}, - }, - }); - await nextTick(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); - - expect(findCommitEditElements().length).toBe(2); - }); - - it('should have one edit components when squash is enabled and there is 1 commit only', async () => { - createComponent({ - mr: { - commitsCount: 1, - squash: true, - enableSquashBeforeMerge: true, - }, - }); + await triggerEditCommitInput(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); - - expect(findCommitEditElements().length).toBe(1); + expect(findCommitEditElements()).toHaveLength(2); }); it('should have correct edit squash commit label', async () => { @@ -761,7 +692,7 @@ describe('ReadyToMerge', () => { }, }); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); expect(findFirstCommitEditLabel()).toBe('Squash commit message'); }); @@ -779,7 +710,7 @@ describe('ReadyToMerge', () => { mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 }, }); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); expect(findCommitDropdownElement().exists()).toBe(true); }); @@ -788,7 +719,7 @@ describe('ReadyToMerge', () => { it('renders a tip including a link to docs on templates', async () => { createComponent(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); expect(findTipLink().exists()).toBe(true); }); @@ -891,7 +822,8 @@ describe('ReadyToMerge', () => { createDefaultGqlComponent(); await waitForPromises(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + + await triggerEditCommitInput(); expect(finderFn()).toBe(initialValue); }); @@ -899,7 +831,7 @@ describe('ReadyToMerge', () => { it('should have updated value after graphql refetch', async () => { createDefaultGqlComponent(); await waitForPromises(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); triggerApprovalUpdated(); await waitForPromises(); @@ -910,7 +842,7 @@ describe('ReadyToMerge', () => { it('should not update if user has touched', async () => { createDefaultGqlComponent(); await waitForPromises(); - await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); + await triggerEditCommitInput(); const input = wrapper.find(inputId); input.element.value = USER_COMMIT_MESSAGE; diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js index 2a343997cf5..aaa4591d67d 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js @@ -25,7 +25,7 @@ describe('ShaMismatch', () => { }); it('should render warning message', () => { - expect(wrapper.element.innerText).toContain(I18N_SHA_MISMATCH.warningMessage); + expect(wrapper.text()).toContain('Merge blocked: new changes were just added.'); }); it('action button should have correct label', () => { diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js index e2d79c61b9b..c97b42f61ac 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; +import { removeBreakLine } from 'helpers/text_helper'; import notesEventHub from '~/notes/event_hub'; import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue'; @@ -42,7 +43,9 @@ describe('UnresolvedDiscussions', () => { }); it('should have correct elements', () => { - expect(wrapper.element.innerText).toContain(`Merge blocked: all threads must be resolved.`); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain('Merge blocked:'); + expect(text).toContain('all threads must be resolved.'); expect(wrapper.element.innerText).toContain('Jump to first unresolved thread'); expect(wrapper.element.innerText).toContain('Create issue to resolve all threads'); @@ -54,7 +57,9 @@ describe('UnresolvedDiscussions', () => { describe('without threads path', () => { it('should not show create issue link if user cannot create issue', () => { - expect(wrapper.element.innerText).toContain(`Merge blocked: all threads must be resolved.`); + const text = removeBreakLine(wrapper.text()).trim(); + expect(text).toContain('Merge blocked:'); + expect(text).toContain('all threads must be resolved.'); expect(wrapper.element.innerText).toContain('Jump to first unresolved thread'); expect(wrapper.element.innerText).not.toContain('Create issue to resolve all threads'); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js deleted file mode 100644 index 82aeac1a47d..00000000000 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mount } from '@vue/test-utils'; -import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; - -let wrapper; - -const createComponent = (updateMergeRequest = true) => { - wrapper = mount(WorkInProgress, { - propsData: { - mr: {}, - }, - data() { - return { - userPermissions: { - updateMergeRequest, - }, - }; - }, - }); -}; - -describe('Merge request widget draft state component', () => { - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - it('should have correct elements', () => { - createComponent(true); - - expect(wrapper.text()).toContain( - "Merge blocked: merge request must be marked as ready. It's still marked as draft.", - ); - expect(wrapper.find('[data-testid="removeWipButton"]').text()).toContain('Mark as ready'); - }); - - it('should not show removeWIP button is user cannot update MR', () => { - createComponent(false); - - expect(wrapper.find('[data-testid="removeWipButton"]').exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js new file mode 100644 index 00000000000..e610ceb2122 --- /dev/null +++ b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js @@ -0,0 +1,182 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json'; +import { createAlert } from '~/flash'; +import WorkInProgress, { + MSG_SOMETHING_WENT_WRONG, + MSG_MARK_READY, +} from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; +import draftQuery from '~/vue_merge_request_widget/queries/states/draft.query.graphql'; +import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql'; +import removeDraftMutation from '~/vue_merge_request_widget/queries/toggle_draft.mutation.graphql'; +import MergeRequest from '~/merge_request'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; + +Vue.use(VueApollo); + +const TEST_PROJECT_ID = getStateQueryResponse.data.project.id; +const TEST_MR_ID = getStateQueryResponse.data.project.mergeRequest.id; +const TEST_MR_IID = '23'; +const TEST_MR_TITLE = 'Test MR Title'; +const TEST_PROJECT_PATH = 'lorem/ipsum'; + +jest.mock('~/flash'); +jest.mock('~/merge_request'); + +describe('~/vue_merge_request_widget/components/states/work_in_progress.vue', () => { + let wrapper; + let apolloProvider; + + let draftQuerySpy; + let removeDraftMutationSpy; + + const findWIPButton = () => wrapper.findByTestId('removeWipButton'); + + const createDraftQueryResponse = (canUpdateMergeRequest) => ({ + data: { + project: { + __typename: 'Project', + id: TEST_PROJECT_ID, + mergeRequest: { + __typename: 'MergeRequest', + id: TEST_MR_ID, + userPermissions: { + updateMergeRequest: canUpdateMergeRequest, + }, + }, + }, + }, + }); + const createRemoveDraftMutationResponse = () => ({ + data: { + mergeRequestSetDraft: { + __typename: 'MergeRequestSetWipPayload', + errors: [], + mergeRequest: { + __typename: 'MergeRequest', + id: TEST_MR_ID, + title: TEST_MR_TITLE, + draft: false, + mergeableDiscussionsState: true, + }, + }, + }, + }); + + const createComponent = async () => { + wrapper = mountExtended(WorkInProgress, { + apolloProvider, + propsData: { + mr: { + issuableId: TEST_MR_ID, + title: TEST_MR_TITLE, + iid: TEST_MR_IID, + targetProjectFullPath: TEST_PROJECT_PATH, + }, + }, + }); + + await waitForPromises(); + + // why: work_in_progress.vue has some coupling that this query has been read before + // for some reason this has to happen **after** the component has mounted + // or apollo throws errors. + apolloProvider.defaultClient.cache.writeQuery({ + query: getStateQuery, + variables: { + projectPath: TEST_PROJECT_PATH, + iid: TEST_MR_IID, + }, + data: getStateQueryResponse.data, + }); + }; + + beforeEach(() => { + draftQuerySpy = jest.fn().mockResolvedValue(createDraftQueryResponse(true)); + removeDraftMutationSpy = jest.fn().mockResolvedValue(createRemoveDraftMutationResponse()); + + apolloProvider = createMockApollo([ + [draftQuery, draftQuerySpy], + [removeDraftMutation, removeDraftMutationSpy], + ]); + }); + + describe('when user can update MR', () => { + beforeEach(async () => { + await createComponent(); + }); + + it('renders text', () => { + const message = wrapper.text(); + expect(message).toContain('Merge blocked:'); + expect(message).toContain('Select Mark as ready to remove it from Draft status.'); + }); + + it('renders mark ready button', () => { + expect(findWIPButton().text()).toBe(MSG_MARK_READY); + }); + + it('does not call remove draft mutation', () => { + expect(removeDraftMutationSpy).not.toHaveBeenCalled(); + }); + + describe('when mark ready button is clicked', () => { + beforeEach(async () => { + findWIPButton().vm.$emit('click'); + + await waitForPromises(); + }); + + it('calls mutation spy', () => { + expect(removeDraftMutationSpy).toHaveBeenCalledWith({ + draft: false, + iid: TEST_MR_IID, + projectPath: TEST_PROJECT_PATH, + }); + }); + + it('does not create alert', () => { + expect(createAlert).not.toHaveBeenCalled(); + }); + + it('calls toggleDraftStatus', () => { + expect(MergeRequest.toggleDraftStatus).toHaveBeenCalledWith(TEST_MR_TITLE, true); + }); + }); + + describe('when mutation fails and ready button is clicked', () => { + beforeEach(async () => { + removeDraftMutationSpy.mockRejectedValue(new Error('TEST FAIL')); + findWIPButton().vm.$emit('click'); + + await waitForPromises(); + }); + + it('creates alert', () => { + expect(createAlert).toHaveBeenCalledWith({ + message: MSG_SOMETHING_WENT_WRONG, + }); + }); + + it('does not call toggleDraftStatus', () => { + expect(MergeRequest.toggleDraftStatus).not.toHaveBeenCalled(); + }); + }); + }); + + describe('when user cannot update MR', () => { + beforeEach(async () => { + draftQuerySpy.mockResolvedValue(createDraftQueryResponse(false)); + + createComponent(); + + await waitForPromises(); + }); + + it('does not render mark ready button', () => { + expect(findWIPButton().exists()).toBe(false); + }); + }); +}); |