diff options
Diffstat (limited to 'spec/frontend/vue_mr_widget/components')
19 files changed, 311 insertions, 236 deletions
diff --git a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js index 1aeb080aa04..82526af7afa 100644 --- a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js @@ -1,5 +1,6 @@ import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_collapsible_extension.vue'; describe('Merge Request Collapsible Extension', () => { @@ -46,9 +47,9 @@ describe('Merge Request Collapsible Extension', () => { }); describe('onClick', () => { - beforeEach(() => { + beforeEach(async () => { wrapper.find('button').trigger('click'); - return wrapper.vm.$nextTick(); + await nextTick(); }); it('rendes the provided slot', () => { diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js index e7c10ab4c2d..8a42e2e2ce7 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; window.gl = window.gl || {}; @@ -50,7 +51,7 @@ describe('MrWidgetAuthor', () => { }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png'); }); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js index 3e111cd308a..631aef412a6 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js @@ -1,5 +1,6 @@ import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue'; describe('MrWidgetExpanableSection', () => { @@ -43,9 +44,9 @@ describe('MrWidgetExpanableSection', () => { }); describe('when collapse section is open', () => { - beforeEach(() => { + beforeEach(async () => { findButton().vm.$emit('click'); - return wrapper.vm.$nextTick(); + await nextTick(); }); it('renders button with collapse text', () => { diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js index f55d313a719..c0a30a5093d 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -1,6 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; @@ -152,23 +153,18 @@ describe('MemoryUsage', () => { }); describe('loadMetrics', () => { - const returnServicePromise = () => - new Promise((resolve) => { - resolve({ - data: metricsMockData, - }); + it('should load metrics data using MRWidgetService', async () => { + jest.spyOn(MRWidgetService, 'fetchMetrics').mockResolvedValue({ + data: metricsMockData, }); - - it('should load metrics data using MRWidgetService', (done) => { - jest.spyOn(MRWidgetService, 'fetchMetrics').mockReturnValue(returnServicePromise(true)); jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {}); vm.loadMetrics(); - setImmediate(() => { - expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); - expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); - done(); - }); + + await waitForPromises(); + + expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); + expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); }); }); }); @@ -184,7 +180,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = false; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); @@ -203,7 +199,7 @@ describe('MemoryUsage', () => { vm.loadFailed = false; vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.memory-graph-container')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); done(); @@ -215,7 +211,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = true; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); @@ -228,7 +224,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = false; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap index a124008b36a..98297630792 100644 --- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap +++ b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap @@ -16,7 +16,7 @@ exports[`PipelineFailed should render error message with a disabled merge button class="bold" > <gl-sprintf-stub - message="The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions." + message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}" /> </span> </div> diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js index c30f6f1dfd1..c0add94e6ed 100644 --- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; const testCommitMessage = 'Test commit message'; @@ -46,16 +47,15 @@ describe('Commits edit component', () => { expect(findTextarea().element.value).toBe(testCommitMessage); }); - it('emits an input event and receives changed value', () => { + it('emits an input event and receives changed value', async () => { const changedCommitMessage = 'Changed commit message'; findTextarea().element.value = changedCommitMessage; findTextarea().trigger('input'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]); - expect(findTextarea().element.value).toBe(changedCommitMessage); - }); + await nextTick(); + expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]); + expect(findTextarea().element.value).toBe(changedCommitMessage); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js b/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js new file mode 100644 index 00000000000..0e1c38437f0 --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js @@ -0,0 +1,78 @@ +import { shallowMount } from '@vue/test-utils'; +import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue'; +import { trimText } from 'helpers/text_helper'; + +describe('MergeFailedPipelineConfirmationDialog', () => { + let wrapper; + + const GlModal = { + template: ` + <div> + <slot></slot> + <slot name="modal-footer"></slot> + </div> + `, + methods: { + hide: jest.fn(), + }, + }; + + const createComponent = () => { + wrapper = shallowMount(MergeFailedPipelineConfirmationDialog, { + propsData: { + visible: true, + }, + stubs: { + GlModal, + }, + attachTo: document.body, + }); + }; + + const findModal = () => wrapper.findComponent(GlModal); + const findMergeBtn = () => wrapper.find('[data-testid="merge-unverified-changes"]'); + const findCancelBtn = () => wrapper.find('[data-testid="merge-cancel-btn"]'); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render informational text explaining why merging immediately can be dangerous', () => { + expect(trimText(wrapper.text())).toContain( + 'The latest pipeline for this merge request did not succeed. The latest changes are unverified. Are you sure you want to attempt to merge?', + ); + }); + + it('should emit the mergeWithFailedPipeline event', () => { + findMergeBtn().vm.$emit('click'); + + expect(wrapper.emitted('mergeWithFailedPipeline')).toBeTruthy(); + }); + + it('when the cancel button is clicked should emit cancel and call hide', () => { + jest.spyOn(findModal().vm, 'hide'); + + findCancelBtn().vm.$emit('click'); + + expect(wrapper.emitted('cancel')).toBeTruthy(); + expect(findModal().vm.hide).toHaveBeenCalled(); + }); + + it('should emit cancel when the hide event is emitted', () => { + findModal().vm.$emit('hide'); + + expect(wrapper.emitted('cancel')).toBeTruthy(); + }); + + it('when modal is shown it will focus the cancel button', () => { + jest.spyOn(findCancelBtn().element, 'focus'); + + findModal().vm.$emit('shown'); + + expect(findCancelBtn().element.focus).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js index 52a56af454f..7387ed2d5e9 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js @@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { trimText } from 'helpers/text_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue'; import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; import eventHub from '~/vue_merge_request_widget/event_hub'; @@ -185,7 +186,7 @@ describe('MRWidgetAutoMergeEnabled', () => { describe('methods', () => { describe('cancelAutomaticMerge', () => { - it('should set flag and call service then tell main component to update the widget with data', (done) => { + it('should set flag and call service then tell main component to update the widget with data', async () => { factory({ ...defaultMrProps(), }); @@ -201,20 +202,20 @@ describe('MRWidgetAutoMergeEnabled', () => { ); wrapper.vm.cancelAutomaticMerge(); - setImmediate(() => { - expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy(); - if (mergeRequestWidgetGraphql) { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - } else { - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - } - done(); - }); + + await waitForPromises(); + + expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy(); + if (mergeRequestWidgetGraphql) { + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + } else { + expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); + } }); }); describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', (done) => { + it('should set flag and call service then request main component to update the widget', async () => { factory({ ...defaultMrProps(), }); @@ -227,14 +228,14 @@ describe('MRWidgetAutoMergeEnabled', () => { ); wrapper.vm.removeSourceBranch(); - setImmediate(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(wrapper.vm.service.merge).toHaveBeenCalledWith({ - sha, - auto_merge_strategy: MWPS_MERGE_STRATEGY, - should_remove_source_branch: true, - }); - done(); + + await waitForPromises(); + + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(wrapper.vm.service.merge).toHaveBeenCalledWith({ + sha, + auto_merge_strategy: MWPS_MERGE_STRATEGY, + should_remove_source_branch: true, }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js index 4c763f40cbe..663fabb761c 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js @@ -1,5 +1,6 @@ import { GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; const commits = [ @@ -51,11 +52,10 @@ describe('Commits message dropdown component', () => { expect(findFirstDropdownElement().text()).toContain('Commit 1'); }); - it('should emit a commit title on selecting commit', () => { + it('should emit a commit title on selecting commit', async () => { findFirstDropdownElement().vm.$emit('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']); - }); + await nextTick(); + expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js index 4d05e732f48..2796403b7d0 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils'; import { GlSprintf } from '@gitlab/ui'; +import { nextTick } from 'vue'; import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; describe('Commits header component', () => { @@ -58,15 +59,14 @@ describe('Commits header component', () => { expect(findCommitToggle().attributes('aria-label')).toBe('Expand'); }); - it('has a chevron-right icon', () => { + it('has a chevron-right icon', 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: false }); - return wrapper.vm.$nextTick().then(() => { - expect(findCommitToggle().props('icon')).toBe('chevron-right'); - }); + await nextTick(); + expect(findCommitToggle().props('icon')).toBe('chevron-right'); }); describe('when squash is disabled', () => { @@ -118,25 +118,19 @@ describe('Commits header component', () => { wrapper.setData({ expanded: true }); }); - it('toggle has aria-label equal to collapse', (done) => { - wrapper.vm.$nextTick(() => { - expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); - done(); - }); + it('toggle has aria-label equal to collapse', async () => { + await nextTick(); + expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); }); - it('has a chevron-down icon', (done) => { - wrapper.vm.$nextTick(() => { - expect(findCommitToggle().props('icon')).toBe('chevron-down'); - done(); - }); + it('has a chevron-down icon', async () => { + await nextTick(); + expect(findCommitToggle().props('icon')).toBe('chevron-down'); }); - it('has a collapse text', (done) => { - wrapper.vm.$nextTick(() => { - expect(findHeaderWrapper().text()).toBe('Collapse'); - done(); - }); + it('has a collapse text', async () => { + await nextTick(); + expect(findHeaderWrapper().text()).toBe('Collapse'); }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index ec222e66a97..9dcde3e4f33 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import { TEST_HOST } from 'helpers/test_constants'; import { removeBreakLine } from 'helpers/text_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; @@ -20,7 +21,7 @@ describe('MRWidgetConflicts', () => { const resolveConflictsBtnText = 'Resolve conflicts'; const mergeLocallyBtnText = 'Merge locally'; - function createComponent(propsData = {}) { + async function createComponent(propsData = {}) { wrapper = extendedWrapper( shallowMount(ConflictsComponent, { propsData, @@ -55,7 +56,7 @@ describe('MRWidgetConflicts', () => { }); } - return wrapper.vm.$nextTick(); + await nextTick(); } afterEach(() => { diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js index e0f1f091129..7d86e453bc7 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -1,6 +1,7 @@ import { getByRole } from '@testing-library/dom'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import mountComponent from 'helpers/vue_mount_component_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants'; import modalEventHub from '~/projects/commit/event_hub'; import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue'; @@ -127,7 +128,7 @@ describe('MRWidgetMerged', () => { describe('methods', () => { describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', (done) => { + it('should set flag and call service then request main component to update the widget', async () => { jest.spyOn(vm.service, 'removeSourceBranch').mockReturnValue( new Promise((resolve) => { resolve({ @@ -139,14 +140,14 @@ describe('MRWidgetMerged', () => { ); vm.removeSourceBranch(); - setImmediate(() => { - const args = eventHub.$emit.mock.calls[0]; - - expect(vm.isMakingRequest).toEqual(true); - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).not.toThrow(); - done(); - }); + + await waitForPromises(); + + const args = eventHub.$emit.mock.calls[0]; + + expect(vm.isMakingRequest).toEqual(true); + expect(args[0]).toEqual('MRWidgetUpdateRequested'); + expect(args[1]).not.toThrow(); }); }); }); @@ -200,7 +201,7 @@ describe('MRWidgetMerged', () => { it('hides button to copy commit SHA if SHA does not exist', (done) => { vm.mr.mergeCommitSha = null; - Vue.nextTick(() => { + nextTick(() => { expect(selectors.copyMergeShaButton).toBe(null); expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with'); done(); @@ -216,7 +217,7 @@ describe('MRWidgetMerged', () => { it('should not show source branch deleted text', (done) => { vm.mr.sourceBranchRemoved = false; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); done(); }); @@ -226,7 +227,7 @@ describe('MRWidgetMerged', () => { vm.mr.isRemovingSourceBranch = true; vm.mr.sourceBranchRemoved = false; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.innerText).toContain('The source branch is being deleted'); expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); done(); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js index e6b2e9fa176..e16c897a49b 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js @@ -1,6 +1,11 @@ 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'; +jest.mock('~/lib/utils/simple_poll', () => + jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default), +); + describe('MRWidgetMerging', () => { let wrapper; @@ -11,6 +16,10 @@ describe('MRWidgetMerging', () => { mr: { targetBranchPath: '/branch-path', targetBranch: 'branch', + transitionStateMachine() {}, + }, + service: { + poll: jest.fn().mockResolvedValue(), }, }, stubs: { @@ -46,4 +55,20 @@ describe('MRWidgetMerging', () => { expect(wrapper.find('a').attributes('href')).toBe('/branch-path'); }); + + describe('initiateMergePolling', () => { + it('should call simplePoll', () => { + wrapper.vm.initiateMergePolling(); + + expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 }); + }); + + it('should call handleMergePolling', () => { + jest.spyOn(wrapper.vm, 'handleMergePolling').mockImplementation(() => {}); + + wrapper.vm.initiateMergePolling(); + + expect(wrapper.vm.handleMergePolling).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js index 936d673768c..ddce07954ab 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js @@ -1,9 +1,10 @@ import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import MissingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue'; let wrapper; -function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) { +async function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) { wrapper = shallowMount(MissingBranchComponent, { propsData: { mr: { sourceBranchRemoved }, @@ -19,7 +20,7 @@ function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) { wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } }); } - return wrapper.vm.$nextTick(); + await nextTick(); } describe('MRWidgetMissingBranch', () => { @@ -40,7 +41,7 @@ describe('MRWidgetMissingBranch', () => { async ({ sourceBranchRemoved, branchName }) => { await factory(sourceBranchRemoved, mergeRequestWidgetGraphql); - expect(wrapper.find('[data-testid="missingBranchName"]').text()).toContain(branchName); + expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName); }, ); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js index 2c04905d3a9..c7c0b69425d 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue'; describe('NothingToMerge', () => { @@ -20,7 +20,7 @@ describe('NothingToMerge', () => { it('should not show new blob link if there is no link available', () => { vm.mr.newBlobPath = null; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index f4ecebbb40c..78585ed75bc 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -1,12 +1,14 @@ import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import { GlSprintf } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; import simplePoll from '~/lib/utils/simple_poll'; import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; +import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue'; import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; import eventHub from '~/vue_merge_request_widget/event_hub'; @@ -61,6 +63,11 @@ const createTestService = () => ({ }); let wrapper; + +const findMergeButton = () => wrapper.find('[data-testid="merge-button"]'); +const findPipelineFailedConfirmModal = () => + wrapper.findComponent(MergeFailedPipelineConfirmationDialog); + const createComponent = (customConfig = {}, mergeRequestWidgetGraphql = false) => { wrapper = shallowMount(ReadyToMerge, { propsData: { @@ -132,33 +139,13 @@ describe('ReadyToMerge', () => { }); }); - describe('mergeButtonVariant', () => { + describe('Merge Button Variant', () => { it('defaults to confirm class', () => { createComponent({ mr: { availableAutoMergeStrategies: [] }, }); - expect(wrapper.vm.mergeButtonVariant).toEqual('confirm'); - }); - - it('returns confirm class for success status', () => { - createComponent({ - mr: { availableAutoMergeStrategies: [], pipeline: true }, - }); - - expect(wrapper.vm.mergeButtonVariant).toEqual('confirm'); - }); - - it('returns confirm class for pending status', () => { - createComponent(); - - expect(wrapper.vm.mergeButtonVariant).toEqual('confirm'); - }); - - it('returns danger class for failed status', () => { - createComponent({ mr: { hasCI: true } }); - - expect(wrapper.vm.mergeButtonVariant).toEqual('danger'); + expect(findMergeButton().attributes('variant')).toBe('confirm'); }); }); @@ -196,7 +183,7 @@ describe('ReadyToMerge', () => { // eslint-disable-next-line no-restricted-syntax wrapper.setData({ isMergingImmediately: true }); - await Vue.nextTick(); + await nextTick(); expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress'); }); @@ -266,7 +253,7 @@ describe('ReadyToMerge', () => { // eslint-disable-next-line no-restricted-syntax wrapper.setData({ isMakingRequest: true }); - await Vue.nextTick(); + await nextTick(); expect(wrapper.vm.isMergeButtonDisabled).toBe(true); }); @@ -275,110 +262,86 @@ describe('ReadyToMerge', () => { describe('methods', () => { describe('handleMergeButtonClick', () => { - const returnPromise = (status) => - new Promise((resolve) => { - resolve({ - data: { - status, - }, - }); - }); + const response = (status) => ({ + data: { + status, + }, + }); - it('should handle merge when pipeline succeeds', (done) => { + it('should handle merge when pipeline succeeds', async () => { createComponent(); jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); jest .spyOn(wrapper.vm.service, 'merge') - .mockReturnValue(returnPromise('merge_when_pipeline_succeeds')); + .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 }); wrapper.vm.handleMergeButtonClick(true); - setImmediate(() => { - expect(wrapper.vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { - transition: 'start-auto-merge', - }); + await waitForPromises(); - const params = wrapper.vm.service.merge.mock.calls[0][0]; - - 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', - }), - ); - done(); + expect(wrapper.vm.isMakingRequest).toBeTruthy(); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { + transition: 'start-auto-merge', }); + + const params = wrapper.vm.service.merge.mock.calls[0][0]; + + 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', + }), + ); }); - it('should handle merge failed', (done) => { + it('should handle merge failed', async () => { createComponent(); jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(returnPromise('failed')); + jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('failed')); wrapper.vm.handleMergeButtonClick(false, true); - setImmediate(() => { - expect(wrapper.vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); + await waitForPromises(); - const params = wrapper.vm.service.merge.mock.calls[0][0]; + expect(wrapper.vm.isMakingRequest).toBeTruthy(); + expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - done(); - }); + const params = wrapper.vm.service.merge.mock.calls[0][0]; + + expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.auto_merge_strategy).toBeUndefined(); }); - it('should handle merge action accepted case', (done) => { + it('should handle merge action accepted case', async () => { createComponent(); jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(returnPromise('success')); - jest.spyOn(wrapper.vm, 'initiateMergePolling').mockImplementation(() => {}); + jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success')); + jest.spyOn(wrapper.vm.mr, 'transitionStateMachine'); wrapper.vm.handleMergeButtonClick(); expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { transition: 'start-merge', }); - setImmediate(() => { - expect(wrapper.vm.isMakingRequest).toBeTruthy(); - expect(wrapper.vm.initiateMergePolling).toHaveBeenCalled(); - - const params = wrapper.vm.service.merge.mock.calls[0][0]; + await waitForPromises(); - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - done(); + expect(wrapper.vm.isMakingRequest).toBeTruthy(); + expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({ + transition: 'start-merge', }); - }); - }); - - describe('initiateMergePolling', () => { - it('should call simplePoll', () => { - createComponent(); - - wrapper.vm.initiateMergePolling(); - - expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 }); - }); - it('should call handleMergePolling', () => { - createComponent(); - - jest.spyOn(wrapper.vm, 'handleMergePolling').mockImplementation(() => {}); + const params = wrapper.vm.service.merge.mock.calls[0][0]; - wrapper.vm.initiateMergePolling(); - - expect(wrapper.vm.handleMergePolling).toHaveBeenCalled(); + expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.auto_merge_strategy).toBeUndefined(); }); }); @@ -396,20 +359,17 @@ describe('ReadyToMerge', () => { }); describe('handleRemoveBranchPolling', () => { - const returnPromise = (state) => - new Promise((resolve) => { - resolve({ - data: { - source_branch_exists: state, - }, - }); - }); + const response = (state) => ({ + data: { + source_branch_exists: state, + }, + }); - it('should call start and stop polling when MR merged', (done) => { + it('should call start and stop polling when MR merged', async () => { createComponent(); jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'poll').mockReturnValue(returnPromise(false)); + jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(false)); let cpc = false; // continuePollingCalled let spc = false; // stopPollingCalled @@ -422,28 +382,27 @@ describe('ReadyToMerge', () => { spc = true; }, ); - setImmediate(() => { - expect(wrapper.vm.service.poll).toHaveBeenCalled(); - const args = eventHub.$emit.mock.calls[0]; + await waitForPromises(); - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).toBeDefined(); - args[1](); + expect(wrapper.vm.service.poll).toHaveBeenCalled(); - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); + const args = eventHub.$emit.mock.calls[0]; - expect(cpc).toBeFalsy(); - expect(spc).toBeTruthy(); + expect(args[0]).toEqual('MRWidgetUpdateRequested'); + expect(args[1]).toBeDefined(); + args[1](); - done(); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); + + expect(cpc).toBeFalsy(); + expect(spc).toBeTruthy(); }); - it('should continue polling until MR is merged', (done) => { + it('should continue polling until MR is merged', async () => { createComponent(); - jest.spyOn(wrapper.vm.service, 'poll').mockReturnValue(returnPromise(true)); + jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(true)); let cpc = false; // continuePollingCalled let spc = false; // stopPollingCalled @@ -456,12 +415,11 @@ describe('ReadyToMerge', () => { spc = true; }, ); - setImmediate(() => { - expect(cpc).toBeTruthy(); - expect(spc).toBeFalsy(); - done(); - }); + await waitForPromises(); + + expect(cpc).toBeTruthy(); + expect(spc).toBeFalsy(); }); }); }); @@ -710,7 +668,7 @@ describe('ReadyToMerge', () => { commitsWithoutMergeCommits: {}, }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(findCommitEditElements().length).toBe(2); }); @@ -794,4 +752,24 @@ describe('ReadyToMerge', () => { }); }); }); + + describe('Merge button when pipeline has failed', () => { + beforeEach(() => { + createComponent({ + mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] }, + }); + }); + + it('should display the correct merge text', () => { + expect(findMergeButton().text()).toBe('Merge...'); + }); + + it('should display confirmation modal when merge button is clicked', async () => { + expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: false }); + + await findMergeButton().vm.$emit('click'); + + expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: true }); + }); + }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js index 6abdbd11f5e..6ea2e8675d3 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js @@ -1,16 +1,13 @@ import { GlFormCheckbox, GlLink } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n'; -const localVue = createLocalVue(); - describe('Squash before merge component', () => { let wrapper; const createComponent = (props) => { - wrapper = shallowMount(localVue.extend(SquashBeforeMerge), { - localVue, + wrapper = shallowMount(SquashBeforeMerge, { propsData: { ...props, }, diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js index 4070ca8d8dc..4998147c6b6 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -1,4 +1,5 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; import toast from '~/vue_shared/plugins/global_toast'; import eventHub from '~/vue_merge_request_widget/event_hub'; @@ -47,7 +48,7 @@ describe('Wip', () => { }; describe('handleRemoveDraft', () => { - it('should make a request to service and handle response', (done) => { + it('should make a request to service and handle response', async () => { const vm = createComponent(); jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); @@ -60,12 +61,12 @@ describe('Wip', () => { ); vm.handleRemoveDraft(); - setImmediate(() => { - expect(vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.'); - done(); - }); + + await waitForPromises(); + + expect(vm.isMakingRequest).toBeTruthy(); + expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); + expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.'); }); }); }); @@ -91,13 +92,12 @@ describe('Wip', () => { ); }); - it('should not show removeWIP button is user cannot update MR', (done) => { + it('should not show removeWIP button is user cannot update MR', async () => { vm.mr.removeWIPPath = ''; - Vue.nextTick(() => { - expect(el.querySelector('.js-remove-draft')).toEqual(null); - done(); - }); + await nextTick(); + + expect(el.querySelector('.js-remove-draft')).toEqual(null); }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js index 9048975875a..b7c22b403aa 100644 --- a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js +++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js @@ -1,6 +1,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import { nextTick } from 'vue'; import axios from '~/lib/utils/axios_utils'; import Poll from '~/lib/utils/poll'; import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue'; @@ -39,15 +40,14 @@ describe('MrWidgetTerraformConainer', () => { }); describe('when data is loading', () => { - beforeEach(() => { + beforeEach(async () => { mockPollingApi(200, plans, {}); - return mountWrapper().then(() => { - // 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: true }); - return wrapper.vm.$nextTick(); - }); + await mountWrapper(); + // 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: true }); + await nextTick(); }); it('diplays loading skeleton', () => { |