diff options
Diffstat (limited to 'spec/frontend/vue_merge_request_widget')
11 files changed, 373 insertions, 228 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js index c253dc63f23..81f266d8070 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js @@ -42,8 +42,8 @@ describe('Merge Request Collapsible Extension', () => { expect(wrapper.find('[data-testid="collapsed-header"]').text()).toBe('hello there'); }); - it('renders chevron-lg-right icon', () => { - expect(findIcon().props('name')).toBe('chevron-lg-right'); + it('renders chevron-right icon', () => { + expect(findIcon().props('name')).toBe('chevron-right'); }); describe('onClick', () => { @@ -60,8 +60,8 @@ describe('Merge Request Collapsible Extension', () => { expect(findTitle().text()).toBe('Collapse'); }); - it('renders chevron-lg-down icon', () => { - expect(findIcon().props('name')).toBe('chevron-lg-down'); + it('renders chevron-down icon', () => { + expect(findIcon().props('name')).toBe('chevron-down'); }); }); }); 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 deleted file mode 100644 index 4077564486c..00000000000 --- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = ` -<div - class="mr-widget-body media gl-display-flex gl-align-items-center" -> - <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 gl-align-items-center" - > - - <h4 - class="gl-mr-3" - data-testid="statusText" - > - Set by to be merged automatically when the pipeline succeeds - </h4> - - <div - class="gl-display-flex gl-font-size-0 gl-ml-auto gl-gap-3" - > - <div - class="gl-display-flex gl-align-items-flex-start" - > - <div - class="dropdown b-dropdown gl-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-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> -`; 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 5b9f30dfb86..fef5fee5f19 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 @@ -128,14 +128,6 @@ describe('MRWidgetAutoMergeEnabled', () => { }); describe('template', () => { - it('should have correct elements', () => { - factory({ - ...defaultMrProps(), - }); - - expect(wrapper.element).toMatchSnapshot(); - }); - it('should disable cancel auto merge button when the action is in progress', async () => { factory({ ...defaultMrProps(), 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 4c93c88de16..7e941c5ceaa 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,6 +1,6 @@ import { nextTick } from 'vue'; import * as Sentry from '@sentry/browser'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended, mountExtended } 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'; @@ -26,8 +26,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { const findHelpPopover = () => wrapper.findComponent(HelpPopover); const findDynamicScroller = () => wrapper.findByTestId('dynamic-content-scroller'); - const createComponent = ({ propsData, slots } = {}) => { - wrapper = shallowMountExtended(Widget, { + const createComponent = ({ propsData, slots, mountFn = shallowMountExtended } = {}) => { + wrapper = mountFn(Widget, { propsData: { isCollapsible: false, loadingText: 'Loading widget', @@ -73,6 +73,13 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { expect(findStatusIcon().props()).toMatchObject({ iconName: 'failed', isLoading: false }); }); + it('displays the error text when :has-error is true', () => { + createComponent({ + propsData: { hasError: true, errorText: 'API error' }, + }); + expect(wrapper.findByText('API error').exists()).toBe(true); + }); + it('displays loading icon until request is made and then displays status icon when the request is complete', async () => { const fetchCollapsedData = jest .fn() @@ -425,6 +432,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { beforeEach(() => { createComponent({ + mountFn: mountExtended, propsData: { isCollapsible: true, content, @@ -437,5 +445,11 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { await waitForPromises(); expect(findDynamicScroller().props('items')).toEqual(content); }); + + it('renders the dynamic content inside the dynamic scroller', async () => { + findToggleButton().vm.$emit('click'); + await waitForPromises(); + expect(wrapper.findByText('Main text for the row').exists()).toBe(true); + }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/extensions/security_reports/mock_data.js b/spec/frontend/vue_merge_request_widget/extensions/security_reports/mock_data.js new file mode 100644 index 00000000000..c7354483e8b --- /dev/null +++ b/spec/frontend/vue_merge_request_widget/extensions/security_reports/mock_data.js @@ -0,0 +1,141 @@ +export const mockArtifacts = () => ({ + data: { + project: { + id: 'gid://gitlab/Project/9', + mergeRequest: { + id: 'gid://gitlab/MergeRequest/1', + headPipeline: { + id: 'gid://gitlab/Ci::Pipeline/1', + jobs: { + nodes: [ + { + id: 'gid://gitlab/Ci::Build/14', + name: 'sam_scan', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/14/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/root/security-reports/-/jobs/14/artifacts/download?file_type=sast', + fileType: 'SAST', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + id: 'gid://gitlab/Ci::Build/11', + name: 'sast-spotbugs', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/11/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/root/security-reports/-/jobs/11/artifacts/download?file_type=sast', + fileType: 'SAST', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + id: 'gid://gitlab/Ci::Build/10', + name: 'sast-sobelow', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/10/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + id: 'gid://gitlab/Ci::Build/9', + name: 'sast-pmd-apex', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/9/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + id: 'gid://gitlab/Ci::Build/8', + name: 'sast-eslint', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/8/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/root/security-reports/-/jobs/8/artifacts/download?file_type=sast', + fileType: 'SAST', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + { + id: 'gid://gitlab/Ci::Build/7', + name: 'secrets', + artifacts: { + nodes: [ + { + downloadPath: + '/root/security-reports/-/jobs/7/artifacts/download?file_type=trace', + fileType: 'TRACE', + __typename: 'CiJobArtifact', + }, + { + downloadPath: + '/root/security-reports/-/jobs/7/artifacts/download?file_type=secret_detection', + fileType: 'SECRET_DETECTION', + __typename: 'CiJobArtifact', + }, + ], + __typename: 'CiJobArtifactConnection', + }, + __typename: 'CiJob', + }, + ], + __typename: 'CiJobConnection', + }, + __typename: 'Pipeline', + }, + __typename: 'MergeRequest', + }, + __typename: 'Project', + }, + }, +}); diff --git a/spec/frontend/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports_spec.js b/spec/frontend/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports_spec.js new file mode 100644 index 00000000000..16c2adaffaf --- /dev/null +++ b/spec/frontend/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import { GlDropdown } from '@gitlab/ui'; +import VueApollo from 'vue-apollo'; +import MRSecurityWidget from '~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue'; +import Widget from '~/vue_merge_request_widget/components/widget/widget.vue'; +import securityReportMergeRequestDownloadPathsQuery from '~/vue_merge_request_widget/extensions/security_reports/graphql/security_report_merge_request_download_paths.query.graphql'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { mockArtifacts } from './mock_data'; + +Vue.use(VueApollo); + +describe('vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue', () => { + let wrapper; + + const createComponent = ({ propsData, mockResponse = mockArtifacts() } = {}) => { + wrapper = mountExtended(MRSecurityWidget, { + apolloProvider: createMockApollo([ + [securityReportMergeRequestDownloadPathsQuery, jest.fn().mockResolvedValue(mockResponse)], + ]), + propsData: { + ...propsData, + mr: {}, + }, + }); + }; + + const findWidget = () => wrapper.findComponent(Widget); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItem = (name) => wrapper.findByTestId(name); + + describe('with data', () => { + beforeEach(async () => { + createComponent(); + await waitForPromises(); + }); + + it('displays the correct message', () => { + expect(wrapper.findByText('Security scans have run').exists()).toBe(true); + }); + + it('displays the help popover', () => { + expect(findWidget().props('helpPopover')).toEqual({ + content: { + learnMorePath: + '/help/user/application_security/index#view-security-scan-information-in-merge-requests', + text: + 'New vulnerabilities are vulnerabilities that the security scan detects in the merge request that are different to existing vulnerabilities in the default branch.', + }, + options: { + title: 'Security scan results', + }, + }); + }); + + it.each` + artifactName | exists | downloadPath + ${'sam_scan'} | ${true} | ${'/root/security-reports/-/jobs/14/artifacts/download?file_type=sast'} + ${'sast-spotbugs'} | ${true} | ${'/root/security-reports/-/jobs/11/artifacts/download?file_type=sast'} + ${'sast-sobelow'} | ${false} | ${''} + ${'sast-pmd-apex'} | ${false} | ${''} + ${'sast-eslint'} | ${true} | ${'/root/security-reports/-/jobs/8/artifacts/download?file_type=sast'} + ${'secrets'} | ${true} | ${'/root/security-reports/-/jobs/7/artifacts/download?file_type=secret_detection'} + `( + 'has a dropdown to download $artifactName artifacts', + ({ artifactName, exists, downloadPath }) => { + expect(findDropdown().exists()).toBe(true); + expect(wrapper.findByText(`Download ${artifactName}`).exists()).toBe(exists); + + if (exists) { + const dropdownItem = findDropdownItem(`download-${artifactName}`); + expect(dropdownItem.attributes('download')).toBe(''); + expect(dropdownItem.attributes('href')).toBe(downloadPath); + } + }, + ); + }); + + describe('without data', () => { + beforeEach(() => { + createComponent({ mockResponse: { data: { project: { id: 'project-id' } } } }); + }); + + it('displays the correct message', () => { + expect(wrapper.findByText('Security scans have run').exists()).toBe(true); + }); + + it('should not display the artifacts dropdown', () => { + expect(findDropdown().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js index baef247b649..548b68bc103 100644 --- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js @@ -8,7 +8,11 @@ import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; -import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; +import { + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, +} from '~/lib/utils/http_status'; import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue'; import { failedReport } from 'jest/ci/reports/mock_data/mock_data'; @@ -57,7 +61,7 @@ describe('Test report extension', () => { }; const createExpandedWidgetWithData = async (data = mixedResultsTestReports) => { - mockApi(httpStatusCodes.OK, data); + mockApi(HTTP_STATUS_OK, data); createComponent(); await waitForPromises(); findToggleCollapsedButton().trigger('click'); @@ -75,7 +79,7 @@ describe('Test report extension', () => { describe('summary', () => { it('displays loading state initially', () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); expect(wrapper.text()).toContain(i18n.loading); @@ -91,7 +95,7 @@ describe('Test report extension', () => { }); it('with an error response, displays failed to load text', async () => { - mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR); + mockApi(HTTP_STATUS_INTERNAL_SERVER_ERROR); createComponent(); await waitForPromises(); @@ -107,7 +111,7 @@ describe('Test report extension', () => { ${'failed test results'} | ${newFailedTestReports} | ${'Test summary: 2 failed, 11 total tests'} ${'resolved failures'} | ${resolvedFailures} | ${'Test summary: 4 fixed test results, 11 total tests'} `('displays summary text for $description', async ({ mockData, expectedResult }) => { - mockApi(httpStatusCodes.OK, mockData); + mockApi(HTTP_STATUS_OK, mockData); createComponent(); await waitForPromises(); @@ -116,7 +120,7 @@ describe('Test report extension', () => { }); it('displays report level recently failed count', async () => { - mockApi(httpStatusCodes.OK, recentFailures); + mockApi(HTTP_STATUS_OK, recentFailures); createComponent(); await waitForPromises(); @@ -127,7 +131,7 @@ describe('Test report extension', () => { }); it('displays a link to the full report', async () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); await waitForPromises(); @@ -137,7 +141,7 @@ describe('Test report extension', () => { }); it('hides copy failed tests button when there are no failing tests', async () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); await waitForPromises(); @@ -146,7 +150,7 @@ describe('Test report extension', () => { }); it('displays copy failed tests button when there are failing tests', async () => { - mockApi(httpStatusCodes.OK, newFailedTestReports); + mockApi(HTTP_STATUS_OK, newFailedTestReports); createComponent(); await waitForPromises(); @@ -159,7 +163,7 @@ describe('Test report extension', () => { }); it('hides copy failed tests button when endpoint returns null files', async () => { - mockApi(httpStatusCodes.OK, newFailedTestWithNullFilesReport); + mockApi(HTTP_STATUS_OK, newFailedTestWithNullFilesReport); createComponent(); await waitForPromises(); @@ -168,7 +172,7 @@ describe('Test report extension', () => { }); it('copy failed tests button updates tooltip text when clicked', async () => { - mockApi(httpStatusCodes.OK, newFailedTestReports); + mockApi(HTTP_STATUS_OK, newFailedTestReports); createComponent(); await waitForPromises(); @@ -195,7 +199,7 @@ describe('Test report extension', () => { }); it('shows an error when a suite has a parsing error', async () => { - mockApi(httpStatusCodes.OK, reportWithParsingErrors); + mockApi(HTTP_STATUS_OK, reportWithParsingErrors); createComponent(); await waitForPromises(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js index a06ad930abe..01049e54a7f 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js @@ -6,7 +6,7 @@ import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import accessibilityExtension from '~/vue_merge_request_widget/extensions/accessibility'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { accessibilityReportResponseErrors, accessibilityReportResponseSuccess } from './mock_data'; describe('Accessibility extension', () => { @@ -45,7 +45,7 @@ describe('Accessibility extension', () => { describe('summary', () => { it('displays loading text', () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); @@ -53,7 +53,7 @@ describe('Accessibility extension', () => { }); it('displays failed loading text', async () => { - mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR); + mockApi(HTTP_STATUS_INTERNAL_SERVER_ERROR); createComponent(); @@ -63,7 +63,7 @@ describe('Accessibility extension', () => { }); it('displays detected errors and is expandable', async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); @@ -76,7 +76,7 @@ describe('Accessibility extension', () => { }); it('displays no detected errors and is not expandable', async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseSuccess); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseSuccess); createComponent(); @@ -91,7 +91,7 @@ describe('Accessibility extension', () => { describe('expanded data', () => { beforeEach(async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js index f0ebbb1a82e..67b327217ef 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js @@ -7,10 +7,18 @@ import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import codeQualityExtension from '~/vue_merge_request_widget/extensions/code_quality'; -import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; -import { i18n } from '~/vue_merge_request_widget/extensions/code_quality/constants'; +import { + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, +} from '~/lib/utils/http_status'; +import { + i18n, + codeQualityPrefixes, +} from '~/vue_merge_request_widget/extensions/code_quality/constants'; import { codeQualityResponseNewErrors, + codeQualityResponseResolvedErrors, codeQualityResponseResolvedAndNewErrors, codeQualityResponseNoErrors, } from './mock_data'; @@ -29,6 +37,10 @@ describe('Code Quality extension', () => { const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button'); const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item'); + const isCollapsable = () => wrapper.findByTestId('toggle-button').exists(); + const getNeutralIcon = () => wrapper.findByTestId('status-neutral-icon').exists(); + const getAlertIcon = () => wrapper.findByTestId('status-alert-icon').exists(); + const getSuccessIcon = () => wrapper.findByTestId('status-success-icon').exists(); const createComponent = () => { wrapper = mountExtended(extensionsContainer, { @@ -55,7 +67,7 @@ describe('Code Quality extension', () => { describe('summary', () => { it('displays loading text', () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseNewErrors); createComponent(); @@ -72,28 +84,57 @@ describe('Code Quality extension', () => { }); it('displays failed loading text', async () => { - mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR); + mockApi(HTTP_STATUS_INTERNAL_SERVER_ERROR); createComponent(); await waitForPromises(); + expect(wrapper.text()).toBe(i18n.error); + expect(isCollapsable()).toBe(false); }); - it('displays correct single Report', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNewErrors); + it('displays new Errors finding', async () => { + mockApi(HTTP_STATUS_OK, codeQualityResponseNewErrors); createComponent(); await waitForPromises(); + expect(wrapper.text()).toBe( + i18n + .singularCopy( + i18n.findings(codeQualityResponseNewErrors.new_errors, codeQualityPrefixes.new), + ) + .replace(/%{strong_start}/g, '') + .replace(/%{strong_end}/g, ''), + ); + expect(isCollapsable()).toBe(true); + expect(getAlertIcon()).toBe(true); + }); + + it('displays resolved Errors finding', async () => { + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedErrors); + createComponent(); + + await waitForPromises(); expect(wrapper.text()).toBe( - i18n.degradedCopy(i18n.singularReport(codeQualityResponseNewErrors.new_errors)), + i18n + .singularCopy( + i18n.findings( + codeQualityResponseResolvedErrors.resolved_errors, + codeQualityPrefixes.fixed, + ), + ) + .replace(/%{strong_start}/g, '') + .replace(/%{strong_end}/g, ''), ); + expect(isCollapsable()).toBe(true); + expect(getSuccessIcon()).toBe(true); }); it('displays quality improvement and degradation', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseResolvedAndNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedAndNewErrors); createComponent(); await waitForPromises(); @@ -102,28 +143,38 @@ describe('Code Quality extension', () => { expect(wrapper.text()).toBe( i18n .improvementAndDegradationCopy( - i18n.pluralReport(codeQualityResponseResolvedAndNewErrors.resolved_errors), - i18n.pluralReport(codeQualityResponseResolvedAndNewErrors.new_errors), + i18n.findings( + codeQualityResponseResolvedAndNewErrors.resolved_errors, + codeQualityPrefixes.fixed, + ), + i18n.findings( + codeQualityResponseResolvedAndNewErrors.new_errors, + codeQualityPrefixes.new, + ), ) .replace(/%{strong_start}/g, '') .replace(/%{strong_end}/g, ''), ); + expect(isCollapsable()).toBe(true); + expect(getAlertIcon()).toBe(true); }); it('displays no detected errors', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNoErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseNoErrors); createComponent(); await waitForPromises(); expect(wrapper.text()).toBe(i18n.noChanges); + expect(isCollapsable()).toBe(false); + expect(getNeutralIcon()).toBe(true); }); }); describe('expanded data', () => { beforeEach(async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseResolvedAndNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedAndNewErrors); createComponent(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js index 2e8e70f25db..cb23b730a93 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js @@ -17,9 +17,34 @@ export const codeQualityResponseNewErrors = { resolved_errors: [], existing_errors: [], summary: { - total: 2, + total: 12235, resolved: 0, - errored: 2, + errored: 12235, + }, +}; + +export const codeQualityResponseResolvedErrors = { + status: 'success', + new_errors: [], + resolved_errors: [ + { + description: "Parsing error: 'return' outside of function", + severity: 'minor', + file_path: 'index.js', + line: 12, + }, + { + description: 'TODO found', + severity: 'minor', + file_path: '.gitlab-ci.yml', + line: 73, + }, + ], + existing_errors: [], + summary: { + total: 12235, + resolved: 0, + errored: 12235, }, }; @@ -43,9 +68,9 @@ export const codeQualityResponseResolvedAndNewErrors = { ], existing_errors: [], summary: { - total: 2, + total: 12233, resolved: 1, - errored: 1, + errored: 12233, }, }; @@ -55,8 +80,8 @@ export const codeQualityResponseNoErrors = { resolved_errors: [], existing_errors: [], summary: { - total: 0, + total: 12234, resolved: 0, - errored: 0, + errored: 12234, }, }; diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js index d038660e6d3..015d394312a 100644 --- a/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js +++ b/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js @@ -34,7 +34,7 @@ describe('MRWidgetHowToMerge', () => { }); it('renders a selection of markdown fields', () => { - expect(findInstructionsFields().length).toBe(3); + expect(findInstructionsFields().length).toBe(2); }); it('renders a tip including a link to docs when a valid link is present', () => { @@ -48,23 +48,11 @@ describe('MRWidgetHowToMerge', () => { it('should render different instructions based on if the user can merge', () => { mountComponent({ props: { canMerge: true } }); - expect(findInstructionsFields().at(2).text()).toContain('git push origin'); - }); - - it('should render different instructions based on if the merge is based off a fork', () => { - mountComponent({ props: { isFork: true } }); - expect(findInstructionsFields().at(0).text()).toContain('FETCH_HEAD'); - }); - - it('escapes the target branch name shell-secure', () => { - mountComponent({ props: { targetBranch: '";echo$IFS"you_shouldnt_run_this' } }); - - expect(findInstructionsFields().at(1).text()).toContain('\'";echo$IFS"you_shouldnt_run_this\''); + expect(findInstructionsFields().at(1).text()).toContain('git push origin'); }); it('escapes the source branch name shell-secure', () => { mountComponent({ props: { sourceBranch: 'branch-of-$USER' } }); - expect(findInstructionsFields().at(0).text()).toContain("'branch-of-$USER'"); }); }); |