diff options
Diffstat (limited to 'spec/frontend/vue_merge_request_widget')
19 files changed, 786 insertions, 968 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js index 4e3e918f7fb..8dadb0c65d0 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js @@ -20,7 +20,7 @@ describe('MrWidgetContainer', () => { it('has layout', () => { factory(); - expect(wrapper.classes()).toContain('mr-widget-heading'); + expect(wrapper.classes()).toEqual(['mr-section-container', 'mr-widget-workflow']); expect(wrapper.find('.mr-widget-content').exists()).toBe(true); }); diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js index 7f0173b7445..144e176b0f0 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js @@ -222,6 +222,7 @@ describe('MRWidgetPipeline', () => { beforeEach(() => { ({ pipeline } = JSON.parse(JSON.stringify(mockData))); + pipeline.details.event_type_name = 'Pipeline'; pipeline.details.name = 'Pipeline'; pipeline.merge_request_event_type = undefined; pipeline.ref.tag = false; @@ -263,6 +264,7 @@ describe('MRWidgetPipeline', () => { describe('for a detached merge request pipeline', () => { it('renders a pipeline widget that reads "Merge request pipeline <ID> <status> for <SHA>"', () => { + pipeline.details.event_type_name = 'Merge request pipeline'; pipeline.details.name = 'Merge request pipeline'; pipeline.merge_request_event_type = 'detached'; diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js index 05c259de370..7b52773e92d 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js @@ -8,7 +8,7 @@ jest.mock('~/vue_shared/plugins/global_toast'); let wrapper; -function createWrapper(propsData, mergeRequestWidgetGraphql) { +function createWrapper(propsData) { wrapper = mount(WidgetRebase, { propsData, data() { @@ -22,7 +22,6 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) { }, }; }, - provide: { glFeatures: { mergeRequestWidgetGraphql } }, mocks: { $apollo: { queries: { @@ -43,276 +42,244 @@ describe('Merge request widget rebase component', () => { wrapper.destroy(); wrapper = null; }); - - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => { - describe('while rebasing', () => { - it('should show progress message', () => { - createWrapper( - { - mr: { rebaseInProgress: true }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); - - expect(findRebaseMessageText()).toContain('Rebase in progress'); - }); + describe('while rebasing', () => { + it('should show progress message', () => { + createWrapper({ + mr: { rebaseInProgress: true }, + service: {}, }); - describe('with permissions', () => { - const rebaseMock = jest.fn().mockResolvedValue(); - const pollMock = jest.fn().mockResolvedValue({}); + expect(findRebaseMessageText()).toContain('Rebase in progress'); + }); + }); + + describe('with permissions', () => { + const rebaseMock = jest.fn().mockResolvedValue(); + const pollMock = jest.fn().mockResolvedValue({}); - it('renders the warning message', () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); + it('renders the warning message', () => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, + }); - const text = findRebaseMessageText(); + const text = findRebaseMessageText(); - expect(text).toContain('Merge blocked'); - expect(text.replace(/\s\s+/g, ' ')).toContain( - 'the source branch must be rebased onto the target branch', - ); - }); + expect(text).toContain('Merge blocked'); + expect(text.replace(/\s\s+/g, ' ')).toContain( + 'the source branch must be rebased onto the target branch', + ); + }); - it('renders an error message when rebasing has failed', async () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); + it('renders an error message when rebasing has failed', async () => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, + }); + + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ rebasingError: 'Something went wrong!' }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ rebasingError: 'Something went wrong!' }); + await nextTick(); + expect(findRebaseMessageText()).toContain('Something went wrong!'); + }); - await nextTick(); - expect(findRebaseMessageText()).toContain('Something went wrong!'); + describe('Rebase buttons', () => { + beforeEach(() => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, }); + }); - describe('Rebase buttons', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - }); + it('renders both buttons', () => { + expect(findRebaseWithoutCiButton().exists()).toBe(true); + expect(findStandardRebaseButton().exists()).toBe(true); + }); - it('renders both buttons', () => { - expect(findRebaseWithoutCiButton().exists()).toBe(true); - expect(findStandardRebaseButton().exists()).toBe(true); - }); + it('starts the rebase when clicking', async () => { + findStandardRebaseButton().vm.$emit('click'); - it('starts the rebase when clicking', async () => { - findStandardRebaseButton().vm.$emit('click'); + await nextTick(); - await nextTick(); + expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); + }); - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); - }); + it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { + findRebaseWithoutCiButton().vm.$emit('click'); - it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { - findRebaseWithoutCiButton().vm.$emit('click'); + await nextTick(); - await nextTick(); + expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); + }); + }); - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); - }); + describe('Rebase when pipelines must succeed is enabled', () => { + beforeEach(() => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + onlyAllowMergeIfPipelineSucceeds: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, }); + }); - describe('Rebase when pipelines must succeed is enabled', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - onlyAllowMergeIfPipelineSucceeds: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - }); + it('renders only the rebase button', () => { + expect(findRebaseWithoutCiButton().exists()).toBe(false); + expect(findStandardRebaseButton().exists()).toBe(true); + }); - it('renders only the rebase button', () => { - expect(findRebaseWithoutCiButton().exists()).toBe(false); - expect(findStandardRebaseButton().exists()).toBe(true); - }); + it('starts the rebase when clicking', async () => { + findStandardRebaseButton().vm.$emit('click'); - it('starts the rebase when clicking', async () => { - findStandardRebaseButton().vm.$emit('click'); + await nextTick(); - await nextTick(); + expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); + }); + }); - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); - }); + describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => { + beforeEach(() => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + onlyAllowMergeIfPipelineSucceeds: true, + allowMergeOnSkippedPipeline: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, }); + }); - describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - onlyAllowMergeIfPipelineSucceeds: true, - allowMergeOnSkippedPipeline: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - }); + it('renders both rebase buttons', () => { + expect(findRebaseWithoutCiButton().exists()).toBe(true); + expect(findStandardRebaseButton().exists()).toBe(true); + }); + + it('starts the rebase when clicking', async () => { + findStandardRebaseButton().vm.$emit('click'); - it('renders both rebase buttons', () => { - expect(findRebaseWithoutCiButton().exists()).toBe(true); - expect(findStandardRebaseButton().exists()).toBe(true); - }); + await nextTick(); - it('starts the rebase when clicking', async () => { - findStandardRebaseButton().vm.$emit('click'); + expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); + }); - await nextTick(); + it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { + findRebaseWithoutCiButton().vm.$emit('click'); - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); - }); + await nextTick(); - it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { - findRebaseWithoutCiButton().vm.$emit('click'); + expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); + }); + }); + }); - await nextTick(); + describe('without permissions', () => { + const exampleTargetBranch = 'fake-branch-to-test-with'; - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); - }); + describe('UI text', () => { + beforeEach(() => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: false, + targetBranch: exampleTargetBranch, + }, + service: {}, }); }); - describe('without permissions', () => { - const exampleTargetBranch = 'fake-branch-to-test-with'; - - describe('UI text', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: exampleTargetBranch, - }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); - }); - - it('renders a message explaining user does not have permissions', () => { - const text = findRebaseMessageText(); - - expect(text).toContain( - 'Merge blocked: the source branch must be rebased onto the target branch.', - ); - expect(text).toContain('the source branch must be rebased'); - }); - - it('renders the correct target branch name', () => { - const elem = findRebaseMessage(); - - expect(elem.text()).toContain( - 'Merge blocked: the source branch must be rebased onto the target branch.', - ); - }); - }); + it('renders a message explaining user does not have permissions', () => { + const text = findRebaseMessageText(); - it('does render the "Rebase without pipeline" button', () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: exampleTargetBranch, - }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); + expect(text).toContain( + 'Merge blocked: the source branch must be rebased onto the target branch.', + ); + expect(text).toContain('the source branch must be rebased'); + }); - expect(findRebaseWithoutCiButton().exists()).toBe(true); - }); + it('renders the correct target branch name', () => { + const elem = findRebaseMessage(); + + expect(elem.text()).toContain( + 'Merge blocked: the source branch must be rebased onto the target branch.', + ); + }); + }); + + it('does render the "Rebase without pipeline" button', () => { + createWrapper({ + mr: { + rebaseInProgress: false, + canPushToSourceBranch: false, + targetBranch: exampleTargetBranch, + }, + service: {}, }); - describe('methods', () => { - it('checkRebaseStatus', async () => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - createWrapper( - { - mr: {}, - service: { - rebase() { - return Promise.resolve(); - }, - poll() { - return Promise.resolve({ - data: { - rebase_in_progress: false, - should_be_rebased: false, - merge_error: null, - }, - }); - }, + expect(findRebaseWithoutCiButton().exists()).toBe(true); + }); + }); + + describe('methods', () => { + it('checkRebaseStatus', async () => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + createWrapper({ + mr: {}, + service: { + rebase() { + return Promise.resolve(); + }, + poll() { + return Promise.resolve({ + data: { + rebase_in_progress: false, + should_be_rebased: false, + merge_error: null, }, - }, - mergeRequestWidgetGraphql, - ); + }); + }, + }, + }); - wrapper.vm.rebase(); + wrapper.vm.rebase(); - // Wait for the rebase request - await nextTick(); - // Wait for the polling request - await nextTick(); - // Wait for the eventHub to be called - await nextTick(); + // Wait for the rebase request + await nextTick(); + // Wait for the polling request + await nextTick(); + // Wait for the eventHub to be called + await nextTick(); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); - expect(toast).toHaveBeenCalledWith('Rebase completed'); - }); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); + expect(toast).toHaveBeenCalledWith('Rebase completed'); }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap index 5f383c468d8..bd40a968392 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have correct elements 1`] = ` +exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = ` <div - class="mr-widget-body media" + class="mr-widget-body media mr-widget-body-line-height-1 gl-line-height-normal" > <div class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3" @@ -44,210 +44,18 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have class="gl-display-flex gl-w-full" > <div - class="media-body gl-display-flex" + class="media-body gl-display-flex gl-align-items-center" > <h4 class="gl-mr-3" data-testid="statusText" > - Set by - <a - class="author-link inline" - > - <img - class="avatar avatar-inline s16" - src="no_avatar.png" - /> - - <span - class="author" - > - - </span> - </a> - to be merged automatically when the pipeline succeeds - </h4> - - <div - class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto" - > - <div - class="gl-display-flex gl-align-items-flex-start" - > - <div - class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group" - lazy="" - no-caret="" - title="Options" - > - <!----> - <button - aria-expanded="false" - aria-haspopup="true" - class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret" - type="button" - > - <!----> - - <svg - aria-hidden="true" - class="dropdown-icon gl-icon s16" - data-testid="ellipsis_v-icon" - role="img" - > - <use - href="#ellipsis_v" - /> - </svg> - - <span - class="gl-new-dropdown-button-text gl-sr-only" - > - - </span> - - <svg - aria-hidden="true" - class="gl-button-icon dropdown-chevron gl-icon s16" - data-testid="chevron-down-icon" - role="img" - > - <use - href="#chevron-down" - /> - </svg> - </button> - <ul - class="dropdown-menu dropdown-menu-right" - role="menu" - tabindex="-1" - > - <!----> - </ul> - </div> - - <button - class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge" - data-qa-selector="cancel_auto_merge_button" - data-testid="cancelAutomaticMergeButton" - type="button" - > - <!----> - - <!----> - - <span - class="gl-button-text" - > - - Cancel auto-merge - - </span> - </button> - </div> - </div> - </div> - - <div - class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1" - > - <button - class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon" - title="Collapse merge details" - type="button" - > - <!----> - - <svg - aria-hidden="true" - class="gl-button-icon gl-icon s16" - data-testid="chevron-lg-up-icon" - role="img" - > - <use - href="#chevron-lg-up" - /> - </svg> - - <!----> - </button> - </div> - </div> -</div> -`; - -exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have correct elements 1`] = ` -<div - class="mr-widget-body media" -> - <div - class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3" - > - <div - class="gl-display-flex gl-m-auto" - > - <div - class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2" - > - <div - class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon" - > - <div - class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto" - > - <div - class="gl-display-flex gl-m-auto gl-translate-y-n50" - > - <svg - aria-label="Scheduled " - class="gl-display-block gl-icon s12" - data-qa-selector="status_scheduled_icon" - data-testid="status-scheduled-icon" - role="img" - > - <use - href="#status-scheduled" - /> - </svg> - </div> - </div> - </div> - </div> - </div> - </div> - - <div - class="gl-display-flex gl-w-full" - > - <div - class="media-body gl-display-flex" - > - - <h4 - class="gl-mr-3" - data-testid="statusText" - > - Set by - <a - class="author-link inline" - > - <img - class="avatar avatar-inline s16" - src="no_avatar.png" - /> - - <span - class="author" - > - - </span> - </a> - to be merged automatically when the pipeline succeeds + Set by to be merged automatically when the pipeline succeeds </h4> <div - class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto" + class="gl-display-flex gl-font-size-0 gl-ml-auto gl-gap-3" > <div class="gl-display-flex gl-align-items-flex-start" 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 d85574262fe..8eeba4d6274 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,5 +1,6 @@ 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'; let wrapper; @@ -15,9 +16,10 @@ describe('Merge request widget merge checks failed state component', () => { }); it.each` - mrState | displayText - ${{ approvals: true, isApproved: false }} | ${'approvalNeeded'} - ${{ detailedMergeStatus: 'BLOCKED_STATUS' }} | ${'blockingMergeRequests'} + mrState | displayText + ${{ approvals: true, isApproved: false }} | ${'approvalNeeded'} + ${{ detailedMergeStatus: DETAILED_MERGE_STATUS.BLOCKED_STATUS }} | ${'blockingMergeRequests'} + ${{ detailedMergeStatus: DETAILED_MERGE_STATUS.EXTERNAL_STATUS_CHECKS }} | ${'externalStatusChecksFailed'} `('display $displayText text for $mrState', ({ mrState, displayText }) => { factory({ mr: mrState }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js index 28182793683..5b9f30dfb86 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js @@ -9,7 +9,6 @@ import eventHub from '~/vue_merge_request_widget/event_hub'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; let wrapper; -let mergeRequestWidgetGraphqlEnabled = false; function convertPropsToGraphqlState(props) { return { @@ -30,12 +29,6 @@ function convertPropsToGraphqlState(props) { } function factory(propsData, stateOverride = {}) { - let state = {}; - - if (mergeRequestWidgetGraphqlEnabled) { - state = { ...convertPropsToGraphqlState(propsData), ...stateOverride }; - } - wrapper = extendedWrapper( mount(autoMergeEnabledComponent, { propsData: { @@ -43,9 +36,8 @@ function factory(propsData, stateOverride = {}) { service: new MRWidgetService({}), }, data() { - return { state }; + return { ...convertPropsToGraphqlState(propsData), ...stateOverride }; }, - provide: { glFeatures: { mergeRequestWidgetGraphql: mergeRequestWidgetGraphqlEnabled } }, mocks: { $apollo: { queries: { @@ -95,130 +87,88 @@ describe('MRWidgetAutoMergeEnabled', () => { wrapper = null; }); - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => { - beforeEach(() => { - mergeRequestWidgetGraphqlEnabled = mergeRequestWidgetGraphql; + describe('computed', () => { + describe('cancelButtonText', () => { + it('should return "Cancel" if MWPS is selected', () => { + factory({ + ...defaultMrProps(), + autoMergeStrategy: MWPS_MERGE_STRATEGY, + }); + + expect(wrapper.findByTestId('cancelAutomaticMergeButton').text()).toBe('Cancel auto-merge'); }); + }); + }); - describe('computed', () => { - describe('cancelButtonText', () => { - it('should return "Cancel" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, + describe('methods', () => { + describe('cancelAutomaticMerge', () => { + it('should set flag and call service then tell main component to update the widget with data', async () => { + factory({ + ...defaultMrProps(), + }); + const mrObj = { + is_new_mr_data: true, + }; + jest.spyOn(wrapper.vm.service, 'cancelAutomaticMerge').mockReturnValue( + new Promise((resolve) => { + resolve({ + data: mrObj, }); + }), + ); - expect(wrapper.findByTestId('cancelAutomaticMergeButton').text()).toBe( - 'Cancel auto-merge', - ); - }); - }); - }); + wrapper.vm.cancelAutomaticMerge(); - describe('methods', () => { - describe('cancelAutomaticMerge', () => { - it('should set flag and call service then tell main component to update the widget with data', async () => { - factory({ - ...defaultMrProps(), - }); - const mrObj = { - is_new_mr_data: true, - }; - jest.spyOn(wrapper.vm.service, 'cancelAutomaticMerge').mockReturnValue( - new Promise((resolve) => { - resolve({ - data: mrObj, - }); - }), - ); - - wrapper.vm.cancelAutomaticMerge(); - - await waitForPromises(); - - expect(wrapper.vm.isCancellingAutoMerge).toBe(true); - if (mergeRequestWidgetGraphql) { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - } else { - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - } - }); - }); + await waitForPromises(); - describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', async () => { - factory({ - ...defaultMrProps(), - }); - jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue( - Promise.resolve({ - data: { - status: MWPS_MERGE_STRATEGY, - }, - }), - ); - - wrapper.vm.removeSourceBranch(); - - 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, - }); - }); - }); + expect(wrapper.vm.isCancellingAutoMerge).toBe(true); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); }); + }); + }); - describe('template', () => { - it('should have correct elements', () => { - factory({ - ...defaultMrProps(), - }); + describe('template', () => { + it('should have correct elements', () => { + factory({ + ...defaultMrProps(), + }); - expect(wrapper.element).toMatchSnapshot(); - }); + expect(wrapper.element).toMatchSnapshot(); + }); - it('should disable cancel auto merge button when the action is in progress', async () => { - factory({ - ...defaultMrProps(), - }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isCancellingAutoMerge: true, - }); + it('should disable cancel auto merge button when the action is in progress', async () => { + factory({ + ...defaultMrProps(), + }); + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + isCancellingAutoMerge: true, + }); - await nextTick(); + await nextTick(); - expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true); - }); + expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true); + }); - it('should render the status text as "...to merged automatically" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }); + it('should render the status text as "...to merged automatically" if MWPS is selected', () => { + factory({ + ...defaultMrProps(), + autoMergeStrategy: MWPS_MERGE_STRATEGY, + }); - expect(getStatusText()).toContain( - 'to be merged automatically when the pipeline succeeds', - ); - }); + expect(getStatusText()).toContain('to be merged automatically when the pipeline succeeds'); + }); - it('should render the cancel button as "Cancel" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }); + it('should render the cancel button as "Cancel" if MWPS is selected', () => { + factory({ + ...defaultMrProps(), + autoMergeStrategy: MWPS_MERGE_STRATEGY, + }); - const cancelButtonText = trimText(wrapper.find('.js-cancel-auto-merge').text()); + const cancelButtonText = trimText(wrapper.find('.js-cancel-auto-merge').text()); - expect(cancelButtonText).toBe('Cancel auto-merge'); - }); - }); + expect(cancelButtonText).toBe('Cancel auto-merge'); }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js index 398a3912882..826f708069c 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js @@ -9,18 +9,11 @@ describe('MRWidgetAutoMergeFailed', () => { const mergeError = 'This is the merge error'; const findButton = () => wrapper.findComponent(GlButton); - const createComponent = (props = {}, mergeRequestWidgetGraphql = false) => { + const createComponent = (props = {}) => { wrapper = mount(AutoMergeFailedComponent, { propsData: { ...props }, data() { - if (mergeRequestWidgetGraphql) { - return { mergeError: props.mr?.mergeError }; - } - - return {}; - }, - provide: { - glFeatures: { mergeRequestWidgetGraphql }, + return { mergeError: props.mr?.mergeError }; }, }); }; @@ -29,40 +22,33 @@ describe('MRWidgetAutoMergeFailed', () => { wrapper.destroy(); }); - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => { - beforeEach(() => { - createComponent( - { - mr: { mergeError }, - }, - mergeRequestWidgetGraphql, - ); - }); + beforeEach(() => { + createComponent({ + mr: { mergeError }, + }); + }); - it('renders failed message', () => { - expect(wrapper.text()).toContain('This merge request failed to be merged automatically'); - }); + it('renders failed message', () => { + expect(wrapper.text()).toContain('This merge request failed to be merged automatically'); + }); - it('renders merge error provided', () => { - expect(wrapper.text()).toContain(mergeError); - }); + it('renders merge error provided', () => { + expect(wrapper.text()).toContain(mergeError); + }); - it('render refresh button', () => { - expect(findButton().text()).toBe('Refresh'); - }); + it('render refresh button', () => { + expect(findButton().text()).toBe('Refresh'); + }); - it('emits event and shows loading icon when button is clicked', async () => { - jest.spyOn(eventHub, '$emit'); - findButton().vm.$emit('click'); + it('emits event and shows loading icon when button is clicked', async () => { + jest.spyOn(eventHub, '$emit'); + findButton().vm.$emit('click'); - expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested'); + expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested'); - await nextTick(); + await nextTick(); - expect(findButton().attributes('disabled')).toBe('disabled'); - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); - }); - }); + expect(findButton().attributes('disabled')).toBe('disabled'); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); }); 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 7a9fd5b002d..a16e4d4a6ea 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 @@ -7,7 +7,6 @@ import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_ describe('MRWidgetConflicts', () => { let wrapper; - let mergeRequestWidgetGraphql = null; const path = '/conflicts'; const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button'); @@ -25,10 +24,17 @@ describe('MRWidgetConflicts', () => { wrapper = extendedWrapper( mount(ConflictsComponent, { propsData, - provide: { - glFeatures: { - mergeRequestWidgetGraphql, - }, + data() { + return { + userPermissions: { + canMerge: propsData.mr.canMerge, + pushToSourceBranch: propsData.mr.canPushToSourceBranch, + }, + state: { + shouldBeRebased: propsData.mr.shouldBeRebased, + sourceBranchProtected: propsData.mr.sourceBranchProtected, + }, + }; }, mocks: { $apollo: { @@ -41,212 +47,188 @@ describe('MRWidgetConflicts', () => { }), ); - if (mergeRequestWidgetGraphql) { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - userPermissions: { - canMerge: propsData.mr.canMerge, - pushToSourceBranch: propsData.mr.canPushToSourceBranch, - }, - stateData: { - shouldBeRebased: propsData.mr.shouldBeRebased, - sourceBranchProtected: propsData.mr.sourceBranchProtected, - }, - }); - } - await nextTick(); } afterEach(() => { - mergeRequestWidgetGraphql = null; wrapper.destroy(); }); - [false, true].forEach((featureEnabled) => { - describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => { - beforeEach(() => { - mergeRequestWidgetGraphql = featureEnabled; + // There are two permissions we need to consider: + // + // 1. Is the user allowed to merge to the target branch? + // 2. Is the user allowed to push to the source branch? + // + // This yields 4 possible permutations that we need to test, and + // we test them below. A user who can push to the source + // branch should be allowed to resolve conflicts. This is + // consistent with what the backend does. + describe('when allowed to merge but not allowed to push to source branch', () => { + beforeEach(async () => { + await createComponent({ + mr: { + canMerge: true, + canPushToSourceBranch: false, + conflictResolutionPath: path, + conflictsDocsPath: '', + }, }); + }); - // There are two permissions we need to consider: - // - // 1. Is the user allowed to merge to the target branch? - // 2. Is the user allowed to push to the source branch? - // - // This yields 4 possible permutations that we need to test, and - // we test them below. A user who can push to the source - // branch should be allowed to resolve conflicts. This is - // consistent with what the backend does. - describe('when allowed to merge but not allowed to push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: false, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); + it('should tell you about conflicts without bothering other people', () => { + expect(wrapper.text()).toContain(mergeConflictsText); + expect(wrapper.text()).not.toContain(userCannotMergeText); + }); - it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); - }); + it('should not allow you to resolve the conflicts', () => { + expect(wrapper.text()).not.toContain(resolveConflictsBtnText); + }); - it('should not allow you to resolve the conflicts', () => { - expect(wrapper.text()).not.toContain(resolveConflictsBtnText); - }); + it('should have merge buttons', () => { + expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); + }); + }); - it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); - }); + describe('when not allowed to merge but allowed to push to source branch', () => { + beforeEach(async () => { + await createComponent({ + mr: { + canMerge: false, + canPushToSourceBranch: true, + conflictResolutionPath: path, + conflictsDocsPath: '', + }, }); + }); - describe('when not allowed to merge but allowed to push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).toContain(userCannotMergeText); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(path); - }); - - it('should not have merge buttons', () => { - expect(wrapper.text()).not.toContain(mergeLocallyBtnText); - }); + it('should tell you about conflicts', () => { + expect(wrapper.text()).toContain(mergeConflictsText); + expect(wrapper.text()).toContain(userCannotMergeText); + }); + + it('should allow you to resolve the conflicts', () => { + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); + expect(findResolveButton().attributes('href')).toEqual(path); + }); + + it('should not have merge buttons', () => { + expect(wrapper.text()).not.toContain(mergeLocallyBtnText); + }); + }); + + describe('when allowed to merge and push to source branch', () => { + beforeEach(async () => { + await createComponent({ + mr: { + canMerge: true, + canPushToSourceBranch: true, + conflictResolutionPath: path, + conflictsDocsPath: '', + }, }); + }); - describe('when allowed to merge and push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(path); - }); - - it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); - }); + it('should tell you about conflicts without bothering other people', () => { + expect(wrapper.text()).toContain(mergeConflictsText); + expect(wrapper.text()).not.toContain(userCannotMergeText); + }); + + it('should allow you to resolve the conflicts', () => { + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); + expect(findResolveButton().attributes('href')).toEqual(path); + }); + + it('should have merge buttons', () => { + expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); + }); + }); + + describe('when user does not have permission to push to source branch', () => { + it('should show proper message', async () => { + await createComponent({ + mr: { + canMerge: false, + canPushToSourceBranch: false, + conflictsDocsPath: '', + }, }); - describe('when user does not have permission to push to source branch', () => { - it('should show proper message', async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); + expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText); + }); - expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText); - }); + it('should not have action buttons', async () => { + await createComponent({ + mr: { + canMerge: false, + canPushToSourceBranch: false, + conflictsDocsPath: '', + }, + }); - it('should not have action buttons', async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); - - expect(findResolveButton().exists()).toBe(false); - expect(findMergeLocalButton().exists()).toBe(false); - }); - - it('should not have resolve button when no conflict resolution path', async () => { - await createComponent({ - mr: { - canMerge: true, - conflictResolutionPath: null, - conflictsDocsPath: '', - }, - }); + expect(findResolveButton().exists()).toBe(false); + expect(findMergeLocalButton().exists()).toBe(false); + }); - expect(findResolveButton().exists()).toBe(false); - }); + it('should not have resolve button when no conflict resolution path', async () => { + await createComponent({ + mr: { + canMerge: true, + conflictResolutionPath: null, + conflictsDocsPath: '', + }, }); - describe('when fast-forward or semi-linear merge enabled', () => { - it('should tell you to rebase locally', async () => { - await createComponent({ - mr: { - shouldBeRebased: true, - conflictsDocsPath: '', - }, - }); + expect(findResolveButton().exists()).toBe(false); + }); + }); - expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText); - }); + describe('when fast-forward or semi-linear merge enabled', () => { + it('should tell you to rebase locally', async () => { + await createComponent({ + mr: { + shouldBeRebased: true, + conflictsDocsPath: '', + }, }); - describe('when source branch protected', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: TEST_HOST, - sourceBranchProtected: true, - conflictsDocsPath: '', - }, - }); - }); + expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText); + }); + }); - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().exists()).toBe(true); - }); + describe('when source branch protected', () => { + beforeEach(async () => { + await createComponent({ + mr: { + canMerge: true, + canPushToSourceBranch: true, + conflictResolutionPath: TEST_HOST, + sourceBranchProtected: true, + conflictsDocsPath: '', + }, }); + }); - describe('when source branch not protected', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: TEST_HOST, - sourceBranchProtected: false, - conflictsDocsPath: '', - }, - }); - }); + it('should not allow you to resolve the conflicts', () => { + expect(findResolveButton().exists()).toBe(false); + }); + }); - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(TEST_HOST); - }); + describe('when source branch not protected', () => { + beforeEach(async () => { + await createComponent({ + mr: { + canMerge: true, + canPushToSourceBranch: true, + conflictResolutionPath: TEST_HOST, + sourceBranchProtected: false, + conflictsDocsPath: '', + }, }); }); + + it('should allow you to resolve the conflicts', () => { + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); + expect(findResolveButton().attributes('href')).toEqual(TEST_HOST); + }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js index ddce07954ab..f29cf55f7ce 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js @@ -1,26 +1,17 @@ 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; -async function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) { +function factory(sourceBranchRemoved) { wrapper = shallowMount(MissingBranchComponent, { propsData: { mr: { sourceBranchRemoved }, }, - provide: { - glFeatures: { mergeRequestWidgetGraphql }, + data() { + return { state: { sourceBranchExists: !sourceBranchRemoved } }; }, }); - - if (mergeRequestWidgetGraphql) { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } }); - } - - await nextTick(); } describe('MRWidgetMissingBranch', () => { @@ -28,22 +19,16 @@ describe('MRWidgetMissingBranch', () => { wrapper.destroy(); }); - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`widget GraphQL feature flag is ${ - mergeRequestWidgetGraphql ? 'enabled' : 'disabled' - }`, () => { - it.each` - sourceBranchRemoved | branchName - ${true} | ${'source'} - ${false} | ${'target'} - `( - 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved', - async ({ sourceBranchRemoved, branchName }) => { - await factory(sourceBranchRemoved, mergeRequestWidgetGraphql); + it.each` + sourceBranchRemoved | branchName + ${true} | ${'source'} + ${false} | ${'target'} + `( + 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved', + ({ sourceBranchRemoved, branchName }) => { + factory(sourceBranchRemoved); - expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName); - }, - ); - }); - }); + expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName); + }, + ); }); 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 48d3f15560b..407bd60b2b7 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 @@ -60,6 +60,11 @@ const createTestMr = (customConfig) => { translateStateToMachine: () => this.transitionStateMachine(), state: 'open', canMerge: true, + mergeable: true, + userPermissions: { + removeSourceBranch: true, + canMerge: true, + }, }; Object.assign(mr, customConfig.mr); @@ -68,7 +73,7 @@ const createTestMr = (customConfig) => { }; const createTestService = () => ({ - merge: jest.fn(), + merge: jest.fn().mockResolvedValue(), poll: jest.fn().mockResolvedValue(), }); @@ -87,21 +92,24 @@ const createReadyToMergeResponse = (customMr) => { }); }; -const createComponent = ( - customConfig = {}, - mergeRequestWidgetGraphql = false, - restructuredMrWidget = true, -) => { +const createComponent = (customConfig = {}, createState = true) => { wrapper = shallowMount(ReadyToMerge, { propsData: { mr: createTestMr(customConfig), service: createTestService(), }, - provide: { - glFeatures: { - mergeRequestWidgetGraphql, - restructuredMrWidget, - }, + data() { + if (createState) { + return { + loading: false, + state: { + ...createTestMr(customConfig), + }, + }; + } + return { + loading: true, + }; }, stubs: { CommitEdit, @@ -136,7 +144,7 @@ describe('ReadyToMerge', () => { describe('computed', () => { describe('isAutoMergeAvailable', () => { it('should return true when at least one merge strategy is available', () => { - createComponent(); + createComponent({}); expect(wrapper.vm.isAutoMergeAvailable).toBe(true); }); @@ -168,14 +176,14 @@ describe('ReadyToMerge', () => { }); it('returns pending when pipeline is active', () => { - createComponent({ mr: { pipeline: {}, isPipelineActive: true } }); + createComponent({ mr: { pipeline: { active: true }, isPipelineActive: true } }); expect(wrapper.vm.status).toEqual('pending'); }); it('returns failed when pipeline is failed', () => { createComponent({ - mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] }, + mr: { pipeline: { status: 'FAILED' }, availableAutoMergeStrategies: [], hasCI: true }, }); expect(wrapper.vm.status).toEqual('failed'); @@ -185,7 +193,7 @@ describe('ReadyToMerge', () => { describe('Merge Button Variant', () => { it('defaults to confirm class', () => { createComponent({ - mr: { availableAutoMergeStrategies: [] }, + mr: { availableAutoMergeStrategies: [], mergeable: true }, }); expect(findMergeButton().attributes('variant')).toBe('confirm'); @@ -194,19 +202,19 @@ describe('ReadyToMerge', () => { describe('status icon', () => { it('defaults to tick icon', () => { - createComponent(); + createComponent({ mr: { mergeable: true } }); expect(wrapper.vm.iconClass).toEqual('success'); }); it('shows tick for success status', () => { - createComponent({ mr: { pipeline: true } }); + createComponent({ mr: { pipeline: { status: 'SUCCESS' }, mergeable: true } }); expect(wrapper.vm.iconClass).toEqual('success'); }); it('shows tick for pending status', () => { - createComponent({ mr: { pipeline: {}, isPipelineActive: true } }); + createComponent({ mr: { pipeline: { active: true }, mergeable: true } }); expect(wrapper.vm.iconClass).toEqual('success'); }); @@ -266,7 +274,7 @@ describe('ReadyToMerge', () => { describe('isMergeButtonDisabled', () => { it('should return false with initial data', () => { - createComponent({ mr: { isMergeAllowed: true } }); + createComponent({ mr: { isMergeAllowed: true, mergeable: false } }); expect(wrapper.vm.isMergeButtonDisabled).toBe(false); }); @@ -283,6 +291,7 @@ describe('ReadyToMerge', () => { isMergeAllowed: false, availableAutoMergeStrategies: [], onlyAllowMergeIfPipelineSucceeds: true, + mergeable: false, }, }); @@ -544,7 +553,15 @@ describe('ReadyToMerge', () => { describe('Remove source branch checkbox', () => { describe('when user can merge but cannot delete branch', () => { it('should be disabled in the rendered output', () => { - createComponent(); + createComponent({ + mr: { + mergeable: true, + userPermissions: { + removeSourceBranch: false, + canMerge: true, + }, + }, + }); expect(wrapper.find('#remove-source-branch-input').exists()).toBe(false); }); @@ -553,7 +570,7 @@ describe('ReadyToMerge', () => { describe('when user can merge and can delete branch', () => { beforeEach(() => { createComponent({ - mr: { canRemoveSourceBranch: true }, + mr: { canRemoveSourceBranch: true, mergeable: true }, }); }); @@ -567,7 +584,7 @@ describe('ReadyToMerge', () => { describe('squash checkbox', () => { it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { createComponent({ - mr: { commitsCount: 2, enableSquashBeforeMerge: true }, + mr: { commitsCount: 2, enableSquashBeforeMerge: true, mergeable: true }, }); expect(findCheckboxElement().exists()).toBe(true); @@ -665,6 +682,7 @@ describe('ReadyToMerge', () => { squashIsSelected: true, enableSquashBeforeMerge: true, commitsCount: 2, + mergeRequestsFfOnlyEnabled: true, }, }); @@ -795,7 +813,9 @@ describe('ReadyToMerge', () => { }); it('shows the diverged commits text when the source branch is behind the target', () => { - createComponent({ mr: { divergedCommitsCount: 9001, canMerge: false } }); + createComponent({ + mr: { divergedCommitsCount: 9001, userPermissions: { canMerge: false }, canMerge: false }, + }); expect(wrapper.text()).toEqual( expect.stringContaining('The source branch is 9001 commits behind the target branch'), @@ -807,7 +827,7 @@ describe('ReadyToMerge', () => { describe('Merge button when pipeline has failed', () => { beforeEach(() => { createComponent({ - mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] }, + mr: { headPipeline: { status: 'FAILED' }, availableAutoMergeStrategies: [], hasCI: true }, }); }); @@ -830,7 +850,7 @@ describe('ReadyToMerge', () => { const USER_COMMIT_MESSAGE = 'Merge message provided manually by user'; const createDefaultGqlComponent = () => - createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, true); + createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, false); beforeEach(() => { readyToMergeResponseSpy = jest 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 index 7259f210b6e..82aeac1a47d 100644 --- 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 @@ -1,101 +1,42 @@ -import Vue, { nextTick } from 'vue'; -import waitForPromises from 'helpers/wait_for_promises'; +import { mount } from '@vue/test-utils'; 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'; -jest.mock('~/vue_shared/plugins/global_toast'); - -const createComponent = () => { - const Component = Vue.extend(WorkInProgress); - const mr = { - title: 'The best MR ever', - removeWIPPath: '/path/to/remove/wip', - }; - const service = { - removeWIP() {}, - }; - return new Component({ - el: document.createElement('div'), - propsData: { mr, service }, +let wrapper; + +const createComponent = (updateMergeRequest = true) => { + wrapper = mount(WorkInProgress, { + propsData: { + mr: {}, + }, + data() { + return { + userPermissions: { + updateMergeRequest, + }, + }; + }, }); }; -describe('Wip', () => { - describe('props', () => { - it('should have props', () => { - const { mr, service } = WorkInProgress.props; - - expect(mr.type instanceof Object).toBe(true); - expect(mr.required).toBe(true); - - expect(service.type instanceof Object).toBe(true); - expect(service.required).toBe(true); - }); - }); - - describe('data', () => { - it('should have default data', () => { - const vm = createComponent(); - - expect(vm.isMakingRequest).toBe(false); - }); - }); - - describe('methods', () => { - const mrObj = { - is_new_mr_data: true, - }; - - describe('handleRemoveDraft', () => { - it('should make a request to service and handle response', async () => { - const vm = createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(vm.service, 'removeWIP').mockReturnValue( - new Promise((resolve) => { - resolve({ - data: mrObj, - }); - }), - ); - - vm.handleRemoveDraft(); - - await waitForPromises(); - - expect(vm.isMakingRequest).toBe(true); - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.'); - }); - }); +describe('Merge request widget draft state component', () => { + afterEach(() => { + wrapper.destroy(); }); describe('template', () => { - let vm; - let el; - - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); - it('should have correct elements', () => { - expect(el.classList.contains('mr-widget-body')).toBe(true); - expect(el.innerText).toContain( + createComponent(true); + + expect(wrapper.text()).toContain( "Merge blocked: merge request must be marked as ready. It's still marked as draft.", ); - expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain( - 'Mark as ready', - ); + expect(wrapper.find('[data-testid="removeWipButton"]').text()).toContain('Mark as ready'); }); - it('should not show removeWIP button is user cannot update MR', async () => { - vm.mr.removeWIPPath = ''; - - await nextTick(); + it('should not show removeWIP button is user cannot update MR', () => { + createComponent(false); - expect(el.querySelector('.js-remove-draft')).toBeNull(); + expect(wrapper.find('[data-testid="removeWipButton"]').exists()).toBe(false); }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap index 08424077269..e9a34453930 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap +++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue renders given data 1`] = ` -"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\"> +"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\" helppopover=\\"[object Object]\\" actionbuttons=\\"\\"> <div class=\\"gl-display-flex gl-flex-direction-column\\"> <div> <p class=\\"gl-mb-0\\">Main text for the row</p> <gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub> <!----> - <gl-badge-stub size=\\"md\\" variant=\\"info\\"> + <gl-badge-stub size=\\"md\\" variant=\\"info\\" iconsize=\\"md\\"> Badge is optional. Text to be displayed inside badge </gl-badge-stub> <actions-stub widget=\\"MyWidget\\" tertiarybuttons=\\"\\" class=\\"gl-ml-auto gl-pl-3\\"></actions-stub> @@ -15,7 +15,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render </div> <ul class=\\"gl-m-0 gl-p-0 gl-list-style-none\\"> <li> - <content-row-stub level=\\"3\\" statusiconname=\\"\\" widgetname=\\"MyWidget\\" header=\\"Child row header\\" data-qa-selector=\\"child_content\\"> + <content-row-stub level=\\"3\\" statusiconname=\\"\\" widgetname=\\"MyWidget\\" header=\\"Child row header\\" actionbuttons=\\"\\" data-qa-selector=\\"child_content\\"> <div class=\\"gl-display-flex gl-flex-direction-column\\"> <div> <p class=\\"gl-mb-0\\">This is recursive. It will be listed in level 3.</p> diff --git a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js index b7753a58747..527e800ddcf 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js @@ -25,6 +25,10 @@ describe('~/vue_merge_request_widget/components/widget/dynamic_content.vue', () header: ['This is a header', 'This is a subheader'], text: 'Main text for the row', subtext: 'Optional: Smaller sub-text to be displayed below the main text', + helpPopover: { + options: { title: 'Widget help popover title' }, + content: { text: 'Widget help popover content' }, + }, icon: { name: EXTENSION_ICONS.success, }, diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js index 9eddd091ad0..e4bee6b8652 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js @@ -1,11 +1,15 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue'; import StatusIcon from '~/vue_merge_request_widget/components/widget/status_icon.vue'; +import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', () => { let wrapper; const findStatusIcon = () => wrapper.findComponent(StatusIcon); + const findHelpPopover = () => wrapper.findComponent(HelpPopover); + const findActionButtons = () => wrapper.findComponent(ActionButtons); const createComponent = ({ propsData, slots } = {}) => { wrapper = shallowMountExtended(WidgetContentRow, { @@ -61,5 +65,38 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', createComponent({ propsData: { header: '<b role="header">this is a header</b>' } }); expect(wrapper.findByText('<b role="header">this is a header</b>').exists()).toBe(true); }); + + it('renders a help popover', () => { + createComponent({ + propsData: { + helpPopover: { + options: { title: 'Help popover title' }, + content: { text: 'Help popover content', learnMorePath: '/path/to/docs' }, + }, + }, + }); + + expect(findHelpPopover().props('options')).toEqual({ title: 'Help popover title' }); + expect(wrapper.findByText('Help popover content').exists()).toBe(true); + expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs'); + expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank'); + }); + + it('does not render help popover when it is not provided', () => { + createComponent({}); + expect(findHelpPopover().exists()).toBe(false); + }); + + it('does not display action buttons if actionButtons is not provided', () => { + createComponent({}); + expect(findActionButtons().exists()).toBe(false); + }); + + it('does display action buttons if actionButtons is provided', () => { + const actionButtons = [{ text: 'click-me', href: '#' }]; + + createComponent({ propsData: { actionButtons } }); + expect(findActionButtons().props('tertiaryButtons')).toEqual(actionButtons); + }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js index 4826fecf98d..9635e050e4d 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js @@ -1,12 +1,21 @@ import { nextTick } from 'vue'; import * as Sentry from '@sentry/browser'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; import waitForPromises from 'helpers/wait_for_promises'; import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue'; import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue'; import Widget from '~/vue_merge_request_widget/components/widget/widget.vue'; import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue'; +jest.mock('~/vue_merge_request_widget/components/extensions/telemetry', () => ({ + createTelemetryHub: jest.fn().mockReturnValue({ + viewed: jest.fn(), + expanded: jest.fn(), + fullReportClicked: jest.fn(), + }), +})); + describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { let wrapper; @@ -14,13 +23,15 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section'); const findActionButtons = () => wrapper.findComponent(ActionButtons); const findToggleButton = () => wrapper.findByTestId('toggle-button'); + const findHelpPopover = () => wrapper.findComponent(HelpPopover); const createComponent = ({ propsData, slots } = {}) => { wrapper = shallowMountExtended(Widget, { propsData: { isCollapsible: false, loadingText: 'Loading widget', - widgetName: 'MyWidget', + widgetName: 'WidgetTest', + fetchCollapsedData: () => Promise.resolve([]), value: { collapsed: null, expanded: null, @@ -30,6 +41,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { slots, stubs: { StatusIcon, + ActionButtons, ContentRow: WidgetContentRow, }, }); @@ -52,8 +64,9 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); it('sets the error text when fetch method fails', async () => { - const fetchCollapsedData = jest.fn().mockReturnValue(() => Promise.reject()); - createComponent({ propsData: { fetchCollapsedData } }); + createComponent({ + propsData: { fetchCollapsedData: jest.fn().mockRejectedValue('Something went wrong') }, + }); await waitForPromises(); expect(wrapper.findByText('Failed to load').exists()).toBe(true); expect(findStatusIcon().props()).toMatchObject({ iconName: 'failed', isLoading: false }); @@ -79,12 +92,24 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); it('displays the loading text', async () => { - const fetchCollapsedData = jest.fn().mockReturnValue(() => Promise.reject()); - createComponent({ propsData: { fetchCollapsedData, statusIconName: 'warning' } }); + createComponent({ + propsData: { + statusIconName: 'warning', + }, + }); + expect(wrapper.text()).not.toContain('Loading'); await nextTick(); expect(wrapper.text()).toContain('Loading'); }); + + it('validates widget name', () => { + expect(() => { + createComponent({ + propsData: { widgetName: 'InvalidWidgetName' }, + }); + }).toThrow(); + }); }); describe('fetch', () => { @@ -136,7 +161,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { summary: 'Hello world', - fetchCollapsedData: () => Promise.resolve(), }, }); @@ -145,28 +169,22 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { it.todo('displays content property when content slot is not provided'); - it('displays the summary slot when provided', () => { + it('displays the summary slot when provided', async () => { createComponent({ - propsData: { - fetchCollapsedData: () => Promise.resolve(), - }, slots: { summary: '<b>More complex summary</b>', }, }); + await waitForPromises(); + expect(wrapper.findByTestId('widget-extension-top-level-summary').text()).toBe( 'More complex summary', ); }); it('does not display action buttons if actionButtons is not provided', () => { - createComponent({ - propsData: { - fetchCollapsedData: () => Promise.resolve(), - }, - }); - + createComponent(); expect(findActionButtons().exists()).toBe(false); }); @@ -175,7 +193,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { - fetchCollapsedData: () => Promise.resolve(), actionButtons, }, }); @@ -184,12 +201,34 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); }); + describe('help popover', () => { + it('renders a help popover', () => { + createComponent({ + propsData: { + helpPopover: { + options: { title: 'My help popover title' }, + content: { text: 'Help popover content', learnMorePath: '/path/to/docs' }, + }, + }, + }); + + expect(findHelpPopover().props('options')).toEqual({ title: 'My help popover title' }); + expect(wrapper.findByText('Help popover content').exists()).toBe(true); + expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs'); + expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank'); + }); + + it('does not render help popover when it is not provided', () => { + createComponent(); + expect(findHelpPopover().exists()).toBe(false); + }); + }); + describe('handle collapse toggle', () => { it('displays the toggle button correctly', () => { createComponent({ propsData: { isCollapsible: true, - fetchCollapsedData: () => Promise.resolve(), }, slots: { content: '<b>More complex content</b>', @@ -204,7 +243,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { isCollapsible: true, - fetchCollapsedData: () => Promise.resolve(), }, slots: { content: '<b>More complex content</b>', @@ -221,7 +259,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { isCollapsible: false, - fetchCollapsedData: () => Promise.resolve(), }, }); @@ -278,7 +315,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { isCollapsible: true, - fetchCollapsedData: () => Promise.resolve([]), fetchExpandedData, }, }); @@ -306,7 +342,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { createComponent({ propsData: { isCollapsible: true, - fetchCollapsedData: () => Promise.resolve([]), fetchExpandedData, }, }); @@ -323,4 +358,54 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { expect(wrapper.findByText('Failed to load').exists()).toBe(false); }); }); + + describe('telemetry - enabled', () => { + beforeEach(() => { + createComponent({ + propsData: { + isCollapsible: true, + actionButtons: [ + { + fullReport: true, + href: '#', + target: '_blank', + id: 'full-report-button', + text: 'Full Report', + }, + ], + }, + }); + }); + + it('should call create a telemetry hub', () => { + expect(wrapper.vm.telemetryHub).not.toBe(null); + }); + + it('should call the viewed state', async () => { + await nextTick(); + expect(wrapper.vm.telemetryHub.viewed).toHaveBeenCalledTimes(1); + }); + + it('when full report is clicked it should call the respective telemetry event', async () => { + expect(wrapper.vm.telemetryHub.fullReportClicked).not.toHaveBeenCalled(); + wrapper.findByText('Full Report').vm.$emit('click'); + await nextTick(); + expect(wrapper.vm.telemetryHub.fullReportClicked).toHaveBeenCalledTimes(1); + }); + }); + + describe('telemetry - disabled', () => { + beforeEach(() => { + createComponent({ + propsData: { + isCollapsible: true, + telemetry: false, + }, + }); + }); + + it('should not call create a telemetry hub', () => { + expect(wrapper.vm.telemetryHub).toBe(null); + }); + }); }); diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js index 58dadb2c679..41df485b0de 100644 --- a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js +++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js @@ -23,11 +23,7 @@ import { jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility'); -jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => { - return { - confirmAction: jest.fn(), - }; -}); +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); describe('DeploymentAction component', () => { let wrapper; diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_info_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_info_spec.js new file mode 100644 index 00000000000..c6b73f63301 --- /dev/null +++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_info_spec.js @@ -0,0 +1,42 @@ +import { mount } from '@vue/test-utils'; +import { GlTruncate, GlLink } from '@gitlab/ui'; +import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue'; +import { deploymentMockData } from './deployment_mock_data'; + +// This component is well covered in ./deployment_spec.js +// more component-specific tests are added below +describe('Deployment Info component', () => { + let wrapper; + + const defaultDeploymentInfoOptions = { + computedDeploymentStatus: 'computed deployment status', + deployment: deploymentMockData, + showMetrics: false, + }; + + const factory = (options = {}) => { + const componentProps = { ...defaultDeploymentInfoOptions, ...options }; + const componentOptions = { propsData: componentProps }; + wrapper = mount(DeploymentInfo, componentOptions); + }; + + beforeEach(() => { + factory(); + }); + + it('should render gl-truncate for environment name', () => { + const envNameComponent = wrapper.findComponent(GlTruncate); + expect(envNameComponent.exists()).toBe(true, 'We should use gl-truncate for environment name'); + expect(envNameComponent.props()).toEqual({ + text: deploymentMockData.name, + withTooltip: true, + position: 'middle', + }); + }); + + it('should have a link with a correct href to deployed environment', () => { + const envLink = wrapper.findComponent(GlLink); + expect(envLink.exists()).toBe(true, 'We should have gl-link pointing to deployed environment'); + expect(envLink.attributes().href).toBe(deploymentMockData.url); + }); +}); diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js index 6622749da92..0f4637d18d9 100644 --- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js +++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js @@ -4,6 +4,8 @@ import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import * as Sentry from '@sentry/browser'; +import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json'; +import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; @@ -22,6 +24,10 @@ import eventHub from '~/vue_merge_request_widget/event_hub'; import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql'; +import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql'; +import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql'; +import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql'; +import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql'; import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data'; import mockData from './mock_data'; import { @@ -83,7 +89,39 @@ describe('MrWidgetOptions', () => { propsData: { mrData: { ...mrData }, }, + data() { + return { loading: false }; + }, + ...options, + apolloProvider: createMockApollo([ + [ + getStateQuery, + jest.fn().mockResolvedValue({ + data: { + project: { + ...getStateQueryResponse.data.project, + mergeRequest: { + ...getStateQueryResponse.data.project.mergeRequest, + mergeError: mrData.mergeError || null, + }, + }, + }, + }), + ], + [readyToMergeQuery, jest.fn().mockResolvedValue(readyToMergeResponse)], + [ + userPermissionsQuery, + jest.fn().mockResolvedValue({ + data: { project: { mergeRequest: { userPermissions: {} } } }, + }), + ], + [ + conflictsStateQuery, + jest.fn().mockResolvedValue({ data: { project: { mergeRequest: {} } } }), + ], + ...(options.apolloMock || []), + ]), }); return axios.waitForAll(); @@ -563,21 +601,6 @@ describe('MrWidgetOptions', () => { }); }); - describe('code quality widget', () => { - beforeEach(() => { - jest.spyOn(document, 'dispatchEvent'); - }); - it('renders the component when refactorCodeQualityExtension is false', () => { - createComponent(mockData, {}, { refactorCodeQualityExtension: false }); - expect(wrapper.find('.js-codequality-widget').exists()).toBe(true); - }); - - it('does not render the component when refactorCodeQualityExtension is true', () => { - createComponent(mockData, {}, { refactorCodeQualityExtension: true }); - expect(wrapper.find('.js-codequality-widget').exists()).toBe(true); - }); - }); - describe('pipeline for target branch after merge', () => { describe('with information for target branch pipeline', () => { beforeEach(() => { @@ -784,12 +807,12 @@ describe('MrWidgetOptions', () => { mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, mrData]); return createComponent(mrData, { - apolloProvider: createMockApollo([ + apolloMock: [ [ securityReportMergeRequestDownloadPathsQuery, async () => ({ data: securityReportMergeRequestDownloadPathsQueryResponse }), ], - ]), + ], }); }; @@ -852,8 +875,10 @@ describe('MrWidgetOptions', () => { ${'closed'} | ${false} | ${'hides'} ${'merged'} | ${true} | ${'shows'} ${'open'} | ${true} | ${'shows'} - `('$showText merge error when state is $state', ({ state, show }) => { - createComponent({ ...mockData, state, merge_error: 'Error!' }); + `('$showText merge error when state is $state', async ({ state, show }) => { + createComponent({ ...mockData, state, mergeError: 'Error!' }); + + await waitForPromises(); expect(wrapper.find('[data-testid="merge_error"]').exists()).toBe(show); }); @@ -917,8 +942,7 @@ describe('MrWidgetOptions', () => { }); it('extension polling is not called if enablePolling flag is not passed', () => { - // called one time due to parent component polling (mount) - expect(pollRequest).toHaveBeenCalledTimes(1); + expect(pollRequest).toHaveBeenCalledTimes(0); }); }); @@ -1004,7 +1028,7 @@ describe('MrWidgetOptions', () => { await createComponent(); - expect(pollRequest).toHaveBeenCalledTimes(2); + expect(pollRequest).toHaveBeenCalledTimes(1); }); }); @@ -1042,7 +1066,7 @@ describe('MrWidgetOptions', () => { registerExtension(pollingErrorExtension); await createComponent(); - expect(pollRequest).toHaveBeenCalledTimes(2); + expect(pollRequest).toHaveBeenCalledTimes(1); }); it('captures sentry error and displays error when poll has failed', async () => { @@ -1085,7 +1109,7 @@ describe('MrWidgetOptions', () => { await nextTick(); await waitForPromises(); - expect(Sentry.captureException).toHaveBeenCalledTimes(1); + expect(Sentry.captureException).toHaveBeenCalledTimes(2); expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Fetch error')); expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed'); }); diff --git a/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js b/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js index 3cdb4265ef0..37df041210c 100644 --- a/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js +++ b/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js @@ -21,22 +21,9 @@ describe('MergeRequestStore', () => { }); describe('setData', () => { - it('should set isSHAMismatch when the diff SHA changes', () => { - store.setData({ ...mockData, diff_head_sha: 'a-different-string' }); - - expect(store.isSHAMismatch).toBe(true); - }); - - it('should not set isSHAMismatch when other data changes', () => { - store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress }); - - expect(store.isSHAMismatch).toBe(false); - }); - it('should update cached sha after rebasing', () => { store.setData({ ...mockData, diff_head_sha: 'abc123' }, true); - expect(store.isSHAMismatch).toBe(false); expect(store.sha).toBe('abc123'); }); |