From aee0a117a889461ce8ced6fcf73207fe017f1d99 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Dec 2021 13:37:47 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-6-stable-ee --- .../components/extensions/utils_spec.js | 18 ++++ .../components/states/commit_edit_spec.js | 9 -- .../components/states/mr_widget_archived_spec.js | 2 +- .../components/states/mr_widget_conflicts_spec.js | 42 ++++----- .../states/mr_widget_ready_to_merge_spec.js | 12 ++- .../components/states/mr_widget_wip_spec.js | 4 +- .../components/terraform/terraform_plan_spec.js | 6 +- spec/frontend/vue_mr_widget/mock_data.js | 2 - .../vue_mr_widget/mr_widget_options_spec.js | 60 ++++++++++++- spec/frontend/vue_mr_widget/test_extension.js | 39 --------- spec/frontend/vue_mr_widget/test_extensions.js | 99 ++++++++++++++++++++++ 11 files changed, 212 insertions(+), 81 deletions(-) create mode 100644 spec/frontend/vue_mr_widget/components/extensions/utils_spec.js delete mode 100644 spec/frontend/vue_mr_widget/test_extension.js create mode 100644 spec/frontend/vue_mr_widget/test_extensions.js (limited to 'spec/frontend/vue_mr_widget') diff --git a/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js b/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js new file mode 100644 index 00000000000..64e802c4fa5 --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js @@ -0,0 +1,18 @@ +import { generateText } from '~/vue_merge_request_widget/components/extensions/utils'; + +describe('generateText', () => { + it.each` + text | expectedText + ${'%{strong_start}Hello world%{strong_end}'} | ${'Hello world'} + ${'%{success_start}Hello world%{success_end}'} | ${'Hello world'} + ${'%{danger_start}Hello world%{danger_end}'} | ${'Hello world'} + ${'%{critical_start}Hello world%{critical_end}'} | ${'Hello world'} + ${'%{same_start}Hello world%{same_end}'} | ${'Hello world'} + ${'%{small_start}Hello world%{small_end}'} | ${'Hello world'} + ${'%{strong_start}%{danger_start}Hello world%{danger_end}%{strong_end}'} | ${'Hello world'} + ${'%{no_exist_start}Hello world%{no_exist_end}'} | ${'Hello world'} + ${['array']} | ${null} + `('generates $expectedText from $text', ({ text, expectedText }) => { + expect(generateText(text)).toBe(expectedText); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js index f965fc32dc1..c30f6f1dfd1 100644 --- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js @@ -3,7 +3,6 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit const testCommitMessage = 'Test commit message'; const testLabel = 'Test label'; -const testTextMuted = 'Test text muted'; const testInputId = 'test-input-id'; describe('Commits edit component', () => { @@ -64,7 +63,6 @@ describe('Commits edit component', () => { beforeEach(() => { createComponent({ header: `
${testCommitMessage}
`, - 'text-muted': `

${testTextMuted}

`, }); }); @@ -74,12 +72,5 @@ describe('Commits edit component', () => { expect(headerSlotElement.exists()).toBe(true); expect(headerSlotElement.text()).toBe(testCommitMessage); }); - - it('renders text-muted slot correctly', () => { - const textMutedElement = wrapper.find('.test-text-muted'); - - expect(textMutedElement.exists()).toBe(true); - expect(textMutedElement.text()).toBe(testTextMuted); - }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js index 4bdc6c95f22..f3061d792d0 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js @@ -25,7 +25,7 @@ describe('MRWidgetArchived', () => { it('renders information', () => { expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual( - 'This project is archived, write access has been disabled', + 'Merge unavailable: merge requests are read-only on archived projects.', ); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index e1bce7f0474..89de160b02f 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -12,6 +12,14 @@ describe('MRWidgetConflicts', () => { const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button'); const findMergeLocalButton = () => wrapper.findByTestId('merge-locally-button'); + const mergeConflictsText = 'Merge blocked: merge conflicts must be resolved.'; + const fastForwardMergeText = + 'Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.'; + const userCannotMergeText = + 'Users who can write to the source or target branches can resolve the conflicts.'; + const resolveConflictsBtnText = 'Resolve conflicts'; + const mergeLocallyBtnText = 'Merge locally'; + function createComponent(propsData = {}) { wrapper = extendedWrapper( shallowMount(ConflictsComponent, { @@ -81,16 +89,16 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain('There are merge conflicts'); - expect(wrapper.text()).not.toContain('ask someone with write access'); + 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('Resolve conflicts'); + expect(wrapper.text()).not.toContain(resolveConflictsBtnText); }); it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain('Merge locally'); + expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); }); }); @@ -107,17 +115,17 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts', () => { - expect(wrapper.text()).toContain('There are merge conflicts'); - expect(wrapper.text()).toContain('ask someone with write access'); + expect(wrapper.text()).toContain(mergeConflictsText); + expect(wrapper.text()).toContain(userCannotMergeText); }); it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain('Resolve conflicts'); + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); expect(findResolveButton().attributes('href')).toEqual(path); }); it('should not have merge buttons', () => { - expect(wrapper.text()).not.toContain('Merge locally'); + expect(wrapper.text()).not.toContain(mergeLocallyBtnText); }); }); @@ -134,17 +142,17 @@ describe('MRWidgetConflicts', () => { }); it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain('There are merge conflicts'); - expect(wrapper.text()).not.toContain('ask someone with write access'); + expect(wrapper.text()).toContain(mergeConflictsText); + expect(wrapper.text()).not.toContain(userCannotMergeText); }); it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain('Resolve conflicts'); + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); expect(findResolveButton().attributes('href')).toEqual(path); }); it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain('Merge locally'); + expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); }); }); @@ -158,9 +166,7 @@ describe('MRWidgetConflicts', () => { }, }); - expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain( - 'ask someone with write access', - ); + expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText); }); it('should not have action buttons', async () => { @@ -198,9 +204,7 @@ describe('MRWidgetConflicts', () => { }, }); - expect(removeBreakLine(wrapper.text()).trim()).toContain( - 'Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.', - ); + expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText); }); }); @@ -236,7 +240,7 @@ describe('MRWidgetConflicts', () => { }); it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain('Resolve conflicts'); + expect(findResolveButton().text()).toContain(resolveConflictsBtnText); expect(findResolveButton().attributes('href')).toEqual(TEST_HOST); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 016b6b2220b..7082a19a8e7 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -1,5 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; +import { GlSprintf } from '@gitlab/ui'; import simplePoll from '~/lib/utils/simple_poll'; import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; @@ -487,6 +488,7 @@ describe('ReadyToMerge', () => { const findCommitEditElements = () => wrapper.findAll(CommitEdit); const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown); const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label'); + const findTipLink = () => wrapper.find(GlSprintf); describe('squash checkbox', () => { it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { @@ -503,10 +505,10 @@ describe('ReadyToMerge', () => { expect(findCheckboxElement().exists()).toBeFalsy(); }); - it('should not be rendered when there is only 1 commit', () => { + it('should be rendered when there is only 1 commit', () => { createComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } }); - expect(findCheckboxElement().exists()).toBeFalsy(); + expect(findCheckboxElement().exists()).toBe(true); }); describe('squash options', () => { @@ -751,6 +753,12 @@ describe('ReadyToMerge', () => { expect(findCommitDropdownElement().exists()).toBeTruthy(); }); }); + + it('renders a tip including a link to docs on templates', () => { + createComponent(); + + expect(findTipLink().exists()).toBe(true); + }); }); describe('Merge request project settings', () => { diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js index 0fb0d5b0b68..4070ca8d8dc 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -81,7 +81,9 @@ describe('Wip', () => { it('should have correct elements', () => { expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('This merge request is still a draft.'); + expect(el.innerText).toContain( + "Merge blocked: merge request must be marked as ready. It's still marked as draft.", + ); expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); expect(el.querySelector('button').innerText).toContain('Merge'); expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain( diff --git a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js index f95a92c2cb1..3c9f6c2e165 100644 --- a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js +++ b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js @@ -32,9 +32,7 @@ describe('TerraformPlan', () => { }); it('diplays the header text with a name', () => { - expect(wrapper.text()).toContain( - `The report ${validPlanWithName.job_name} was generated in your pipelines.`, - ); + expect(wrapper.text()).toContain(`The job ${validPlanWithName.job_name} generated a report.`); }); it('diplays the reported changes', () => { @@ -70,7 +68,7 @@ describe('TerraformPlan', () => { it('diplays the header text with a name', () => { expect(wrapper.text()).toContain( - `The report ${invalidPlanWithName.job_name} failed to generate.`, + `The job ${invalidPlanWithName.job_name} failed to generate a report.`, ); }); diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js index f0c1da346a1..4538c1320d0 100644 --- a/spec/frontend/vue_mr_widget/mock_data.js +++ b/spec/frontend/vue_mr_widget/mock_data.js @@ -271,8 +271,6 @@ export default { mr_troubleshooting_docs_path: 'help', ci_troubleshooting_docs_path: 'help2', merge_request_pipelines_docs_path: '/help/ci/pipelines/merge_request_pipelines.md', - merge_train_when_pipeline_succeeds_docs_path: - '/help/ci/pipelines/merge_trains.md#startadd-to-merge-train-when-pipeline-succeeds', squash: true, visual_review_app_available: true, merge_trains_enabled: true, diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js index 550f156d095..8d41f6620ff 100644 --- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js @@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import * as Sentry from '@sentry/browser'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; @@ -19,10 +20,15 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta import eventHub from '~/vue_merge_request_widget/event_hub'; import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; +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 { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data'; import mockData from './mock_data'; -import testExtension from './test_extension'; +import { + workingExtension, + collapsedDataErrorExtension, + fullDataErrorExtension, +} from './test_extensions'; jest.mock('~/api.js'); @@ -892,7 +898,7 @@ describe('MrWidgetOptions', () => { describe('mock extension', () => { beforeEach(() => { - registerExtension(testExtension); + registerExtension(workingExtension); createComponent(); }); @@ -914,7 +920,7 @@ describe('MrWidgetOptions', () => { .find('[data-testid="widget-extension"] [data-testid="toggle-button"]') .trigger('click'); - await Vue.nextTick(); + await nextTick(); expect(api.trackRedisHllUserEvent).toHaveBeenCalledWith('test_expand_event'); }); @@ -926,7 +932,7 @@ describe('MrWidgetOptions', () => { .find('[data-testid="widget-extension"] [data-testid="toggle-button"]') .trigger('click'); - await Vue.nextTick(); + await nextTick(); expect( wrapper.find('[data-testid="widget-extension-top-level"]').find(GlDropdown).exists(), @@ -952,4 +958,50 @@ describe('MrWidgetOptions', () => { expect(collapsedSection.find(GlButton).text()).toBe('Full report'); }); }); + + describe('mock extension errors', () => { + let captureException; + + const itHandlesTheException = () => { + expect(captureException).toHaveBeenCalledTimes(1); + expect(captureException).toHaveBeenCalledWith(new Error('Fetch error')); + expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('error'); + }; + + beforeEach(() => { + captureException = jest.spyOn(Sentry, 'captureException'); + }); + + afterEach(() => { + registeredExtensions.extensions = []; + captureException = null; + }); + + it('handles collapsed data fetch errors', async () => { + registerExtension(collapsedDataErrorExtension); + createComponent(); + await waitForPromises(); + + expect( + wrapper.find('[data-testid="widget-extension"] [data-testid="toggle-button"]').exists(), + ).toBe(false); + itHandlesTheException(); + }); + + it('handles full data fetch errors', async () => { + registerExtension(fullDataErrorExtension); + createComponent(); + await waitForPromises(); + + expect(wrapper.findComponent(StatusIcon).props('iconName')).not.toBe('error'); + wrapper + .find('[data-testid="widget-extension"] [data-testid="toggle-button"]') + .trigger('click'); + + await nextTick(); + await waitForPromises(); + + itHandlesTheException(); + }); + }); }); diff --git a/spec/frontend/vue_mr_widget/test_extension.js b/spec/frontend/vue_mr_widget/test_extension.js deleted file mode 100644 index 65c1bd8473b..00000000000 --- a/spec/frontend/vue_mr_widget/test_extension.js +++ /dev/null @@ -1,39 +0,0 @@ -import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants'; - -export default { - name: 'WidgetTestExtension', - props: ['targetProjectFullPath'], - expandEvent: 'test_expand_event', - computed: { - summary({ count, targetProjectFullPath }) { - return `Test extension summary count: ${count} & ${targetProjectFullPath}`; - }, - statusIcon({ count }) { - return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success; - }, - }, - methods: { - fetchCollapsedData({ targetProjectFullPath }) { - return Promise.resolve({ targetProjectFullPath, count: 1 }); - }, - fetchFullData() { - return Promise.resolve([ - { - id: 1, - text: 'Hello world', - icon: { - name: EXTENSION_ICONS.failed, - }, - badge: { - text: 'Closed', - }, - link: { - href: 'https://gitlab.com', - text: 'GitLab.com', - }, - actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }], - }, - ]); - }, - }, -}; diff --git a/spec/frontend/vue_mr_widget/test_extensions.js b/spec/frontend/vue_mr_widget/test_extensions.js new file mode 100644 index 00000000000..c7ff02ab726 --- /dev/null +++ b/spec/frontend/vue_mr_widget/test_extensions.js @@ -0,0 +1,99 @@ +import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants'; + +export const workingExtension = { + name: 'WidgetTestExtension', + props: ['targetProjectFullPath'], + expandEvent: 'test_expand_event', + computed: { + summary({ count, targetProjectFullPath }) { + return `Test extension summary count: ${count} & ${targetProjectFullPath}`; + }, + statusIcon({ count }) { + return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success; + }, + }, + methods: { + fetchCollapsedData({ targetProjectFullPath }) { + return Promise.resolve({ targetProjectFullPath, count: 1 }); + }, + fetchFullData() { + return Promise.resolve([ + { + id: 1, + text: 'Hello world', + icon: { + name: EXTENSION_ICONS.failed, + }, + badge: { + text: 'Closed', + }, + link: { + href: 'https://gitlab.com', + text: 'GitLab.com', + }, + actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }], + }, + ]); + }, + }, +}; + +export const collapsedDataErrorExtension = { + name: 'WidgetTestCollapsedErrorExtension', + props: ['targetProjectFullPath'], + expandEvent: 'test_expand_event', + computed: { + summary({ count, targetProjectFullPath }) { + return `Test extension summary count: ${count} & ${targetProjectFullPath}`; + }, + statusIcon({ count }) { + return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success; + }, + }, + methods: { + fetchCollapsedData() { + return Promise.reject(new Error('Fetch error')); + }, + fetchFullData() { + return Promise.resolve([ + { + id: 1, + text: 'Hello world', + icon: { + name: EXTENSION_ICONS.failed, + }, + badge: { + text: 'Closed', + }, + link: { + href: 'https://gitlab.com', + text: 'GitLab.com', + }, + actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }], + }, + ]); + }, + }, +}; + +export const fullDataErrorExtension = { + name: 'WidgetTestCollapsedErrorExtension', + props: ['targetProjectFullPath'], + expandEvent: 'test_expand_event', + computed: { + summary({ count, targetProjectFullPath }) { + return `Test extension summary count: ${count} & ${targetProjectFullPath}`; + }, + statusIcon({ count }) { + return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success; + }, + }, + methods: { + fetchCollapsedData({ targetProjectFullPath }) { + return Promise.resolve({ targetProjectFullPath, count: 1 }); + }, + fetchFullData() { + return Promise.reject(new Error('Fetch error')); + }, + }, +}; -- cgit v1.2.3