diff options
Diffstat (limited to 'spec/frontend/vue_merge_request_widget/components/states')
8 files changed, 345 insertions, 671 deletions
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); }); }); }); |