diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
commit | edaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch) | |
tree | 11f143effbfeba52329fb7afbd05e6e2a3790241 /spec/frontend/pipeline_editor | |
parent | d8a5691316400a0f7ec4f83832698f1988eb27c1 (diff) |
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'spec/frontend/pipeline_editor')
5 files changed, 174 insertions, 151 deletions
diff --git a/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js index cab4810cbf1..f15d5f334d6 100644 --- a/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js +++ b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js @@ -17,19 +17,12 @@ describe('Pipeline Editor | Text editor component', () => { let editorReadyListener; let mockUse; let mockRegisterCiSchema; + let mockEditorInstance; + let editorInstanceDetail; const MockSourceEditor = { template: '<div/>', props: ['value', 'fileName'], - mounted() { - this.$emit(EDITOR_READY_EVENT); - }, - methods: { - getEditor: () => ({ - use: mockUse, - registerCiSchema: mockRegisterCiSchema, - }), - }, }; const createComponent = (glFeatures = {}, mountFn = shallowMount) => { @@ -58,6 +51,21 @@ describe('Pipeline Editor | Text editor component', () => { const findEditor = () => wrapper.findComponent(MockSourceEditor); + beforeEach(() => { + editorReadyListener = jest.fn(); + mockUse = jest.fn(); + mockRegisterCiSchema = jest.fn(); + mockEditorInstance = { + use: mockUse, + registerCiSchema: mockRegisterCiSchema, + }; + editorInstanceDetail = { + detail: { + instance: mockEditorInstance, + }, + }; + }); + afterEach(() => { wrapper.destroy(); @@ -67,10 +75,6 @@ describe('Pipeline Editor | Text editor component', () => { describe('template', () => { beforeEach(() => { - editorReadyListener = jest.fn(); - mockUse = jest.fn(); - mockRegisterCiSchema = jest.fn(); - createComponent(); }); @@ -87,7 +91,7 @@ describe('Pipeline Editor | Text editor component', () => { }); it('bubbles up events', () => { - findEditor().vm.$emit(EDITOR_READY_EVENT); + findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); expect(editorReadyListener).toHaveBeenCalled(); }); @@ -97,11 +101,7 @@ describe('Pipeline Editor | Text editor component', () => { describe('when `schema_linting` feature flag is on', () => { beforeEach(() => { createComponent({ schemaLinting: true }); - // Since the editor will have already mounted, the event will have fired. - // To ensure we properly test this, we clear the mock and re-remit the event. - mockRegisterCiSchema.mockClear(); - mockUse.mockClear(); - findEditor().vm.$emit(EDITOR_READY_EVENT); + findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); }); it('configures editor with syntax highlight', () => { @@ -113,7 +113,7 @@ describe('Pipeline Editor | Text editor component', () => { describe('when `schema_linting` feature flag is off', () => { beforeEach(() => { createComponent(); - findEditor().vm.$emit(EDITOR_READY_EVENT); + findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); }); it('does not call the register CI schema function', () => { diff --git a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js index fd8a100bb2c..570323826d1 100644 --- a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js +++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js @@ -1,40 +1,61 @@ +import VueApollo from 'vue-apollo'; import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import { escape } from 'lodash'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { sprintf } from '~/locale'; import ValidationSegment, { i18n, } from '~/pipeline_editor/components/header/validation_segment.vue'; +import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql'; import { CI_CONFIG_STATUS_INVALID, EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_INVALID, EDITOR_APP_STATUS_LOADING, + EDITOR_APP_STATUS_LINT_UNAVAILABLE, EDITOR_APP_STATUS_VALID, } from '~/pipeline_editor/constants'; -import { mockYmlHelpPagePath, mergeUnwrappedCiConfig, mockCiYml } from '../../mock_data'; +import { + mergeUnwrappedCiConfig, + mockCiYml, + mockLintUnavailableHelpPagePath, + mockYmlHelpPagePath, +} from '../../mock_data'; + +const localVue = createLocalVue(); +localVue.use(VueApollo); describe('Validation segment component', () => { let wrapper; - const createComponent = ({ props = {}, appStatus }) => { + const mockApollo = createMockApollo(); + + const createComponent = ({ props = {}, appStatus = EDITOR_APP_STATUS_INVALID }) => { + mockApollo.clients.defaultClient.cache.writeQuery({ + query: getAppStatus, + data: { + app: { + __typename: 'PipelineEditorApp', + status: appStatus, + }, + }, + }); + wrapper = extendedWrapper( shallowMount(ValidationSegment, { + localVue, + apolloProvider: mockApollo, provide: { ymlHelpPagePath: mockYmlHelpPagePath, + lintUnavailableHelpPagePath: mockLintUnavailableHelpPagePath, }, propsData: { ciConfig: mergeUnwrappedCiConfig(), ciFileContent: mockCiYml, ...props, }, - // Simulate graphQL client query result - data() { - return { - appStatus, - }; - }, }), ); }; @@ -92,6 +113,7 @@ describe('Validation segment component', () => { appStatus: EDITOR_APP_STATUS_INVALID, }); }); + it('has warning icon', () => { expect(findIcon().props('name')).toBe('warning-solid'); }); @@ -149,4 +171,28 @@ describe('Validation segment component', () => { }); }); }); + + describe('when the lint service is unavailable', () => { + beforeEach(() => { + createComponent({ + appStatus: EDITOR_APP_STATUS_LINT_UNAVAILABLE, + props: { + ciConfig: {}, + }, + }); + }); + + it('show a message that the service is unavailable', () => { + expect(findValidationMsg().text()).toBe(i18n.unavailableValidation); + }); + + it('shows the time-out icon', () => { + expect(findIcon().props('name')).toBe('time-out'); + }); + + it('shows the learn more link', () => { + expect(findLearnMoreLink().attributes('href')).toBe(mockLintUnavailableHelpPagePath); + expect(findLearnMoreLink().text()).toBe(i18n.learnMore); + }); + }); }); diff --git a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js index 3becf82ed6e..6206a0f6aed 100644 --- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js +++ b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js @@ -75,34 +75,83 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => { expect(mockChildMounted).toHaveBeenCalledWith(mockContent1); }); - describe('showing the tab content depending on `isEmpty` and `isInvalid`', () => { + describe('alerts', () => { + describe('unavailable state', () => { + beforeEach(() => { + createWrapper({ props: { isUnavailable: true } }); + }); + + it('shows the invalid alert when the status is invalid', () => { + const alert = findAlert(); + + expect(alert.exists()).toBe(true); + expect(alert.text()).toContain(wrapper.vm.$options.i18n.unavailable); + }); + }); + + describe('invalid state', () => { + beforeEach(() => { + createWrapper({ props: { isInvalid: true } }); + }); + + it('shows the invalid alert when the status is invalid', () => { + const alert = findAlert(); + + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe(wrapper.vm.$options.i18n.invalid); + }); + }); + + describe('empty state', () => { + const text = 'my custom alert message'; + + beforeEach(() => { + createWrapper({ + props: { isEmpty: true, emptyMessage: text }, + }); + }); + + it('displays an empty message', () => { + createWrapper({ + props: { isEmpty: true }, + }); + + const alert = findAlert(); + + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe( + 'This tab will be usable when the CI/CD configuration file is populated with valid syntax.', + ); + }); + + it('can have a custom empty message', () => { + const alert = findAlert(); + + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe(text); + }); + }); + }); + + describe('showing the tab content depending on `isEmpty`, `isUnavailable` and `isInvalid`', () => { it.each` - isEmpty | isInvalid | showSlotComponent | text - ${undefined} | ${undefined} | ${true} | ${'renders'} - ${false} | ${false} | ${true} | ${'renders'} - ${undefined} | ${true} | ${false} | ${'hides'} - ${true} | ${false} | ${false} | ${'hides'} - ${false} | ${true} | ${false} | ${'hides'} + isEmpty | isUnavailable | isInvalid | showSlotComponent | text + ${undefined} | ${undefined} | ${undefined} | ${true} | ${'renders'} + ${false} | ${false} | ${false} | ${true} | ${'renders'} + ${undefined} | ${true} | ${true} | ${false} | ${'hides'} + ${true} | ${false} | ${false} | ${false} | ${'hides'} + ${false} | ${true} | ${false} | ${false} | ${'hides'} + ${false} | ${false} | ${true} | ${false} | ${'hides'} `( - '$text the slot component when isEmpty:$isEmpty and isInvalid:$isInvalid', - ({ isEmpty, isInvalid, showSlotComponent }) => { + '$text the slot component when isEmpty:$isEmpty, isUnavailable:$isUnavailable and isInvalid:$isInvalid', + ({ isEmpty, isUnavailable, isInvalid, showSlotComponent }) => { createWrapper({ - props: { isEmpty, isInvalid }, + props: { isEmpty, isUnavailable, isInvalid }, }); expect(findSlotComponent().exists()).toBe(showSlotComponent); expect(findAlert().exists()).toBe(!showSlotComponent); }, ); - - it('can have a custom empty message', () => { - const text = 'my custom alert message'; - createWrapper({ props: { isEmpty: true, emptyMessage: text } }); - - const alert = findAlert(); - - expect(alert.exists()).toBe(true); - expect(alert.text()).toBe(text); - }); }); describe('user interaction', () => { diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js index fc2cbdeda0a..f02f6870653 100644 --- a/spec/frontend/pipeline_editor/mock_data.js +++ b/spec/frontend/pipeline_editor/mock_data.js @@ -10,6 +10,7 @@ export const mockNewMergeRequestPath = '/-/merge_requests/new'; export const mockCommitSha = 'aabbccdd'; export const mockCommitNextSha = 'eeffgghh'; export const mockLintHelpPagePath = '/-/lint-help'; +export const mockLintUnavailableHelpPagePath = '/-/pipeline-editor/troubleshoot'; export const mockYmlHelpPagePath = '/-/yml-help'; export const mockCommitMessage = 'My commit message'; diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js index 09d7d4f7ca6..63eca253c48 100644 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js @@ -5,10 +5,15 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import setWindowLocation from 'helpers/set_window_location_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { resolvers } from '~/pipeline_editor/graphql/resolvers'; import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue'; import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue'; -import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants'; +import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; +import ValidationSegment, { + i18n as validationSegmenti18n, +} from '~/pipeline_editor/components/header/validation_segment.vue'; +import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants'; import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql'; import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.query.graphql'; import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql'; @@ -61,11 +66,6 @@ describe('Pipeline editor app component', () => { wrapper = shallowMount(PipelineEditorApp, { provide: { ...mockProvide, ...provide }, stubs, - data() { - return { - commitSha: '', - }; - }, mocks: { $apollo: { queries: { @@ -90,17 +90,11 @@ describe('Pipeline editor app component', () => { [getLatestCommitShaQuery, mockLatestCommitShaQuery], [getPipelineQuery, mockPipelineQuery], ]; - mockApollo = createMockApollo(handlers); + + mockApollo = createMockApollo(handlers, resolvers); const options = { localVue, - data() { - return { - currentBranch: mockDefaultBranch, - lastCommitBranch: '', - appStatus: '', - }; - }, mocks: {}, apolloProvider: mockApollo, }; @@ -116,6 +110,7 @@ describe('Pipeline editor app component', () => { const findEmptyState = () => wrapper.findComponent(PipelineEditorEmptyState); const findEmptyStateButton = () => wrapper.findComponent(PipelineEditorEmptyState).findComponent(GlButton); + const findValidationSegment = () => wrapper.findComponent(ValidationSegment); beforeEach(() => { mockBlobContentData = jest.fn(); @@ -240,6 +235,26 @@ describe('Pipeline editor app component', () => { }); }); + describe('when the lint query returns a 500 error', () => { + beforeEach(async () => { + mockCiConfigData.mockRejectedValueOnce(new Error(500)); + await createComponentWithApollo({ + stubs: { PipelineEditorHome, PipelineEditorHeader, ValidationSegment }, + }); + }); + + it('shows that the lint service is down', () => { + expect(findValidationSegment().text()).toContain( + validationSegmenti18n.unavailableValidation, + ); + }); + + it('does not report an error or scroll to the top', () => { + expect(findAlert().exists()).toBe(false); + expect(window.scrollTo).not.toHaveBeenCalled(); + }); + }); + describe('when the user commits', () => { const updateFailureMessage = 'The GitLab CI configuration could not be updated.'; const updateSuccessMessage = 'Your changes have been successfully committed.'; @@ -411,94 +426,6 @@ describe('Pipeline editor app component', () => { }); }); - describe('when multiple errors occurs in a row', () => { - const updateFailureMessage = 'The GitLab CI configuration could not be updated.'; - const unknownFailureMessage = 'The CI configuration was not loaded, please try again.'; - const unknownReasons = ['Commit failed']; - const alertErrorMessage = `${updateFailureMessage} ${unknownReasons[0]}`; - - const emitError = (type = COMMIT_FAILURE, reasons = unknownReasons) => - findEditorHome().vm.$emit('showError', { - type, - reasons, - }); - - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - - window.scrollTo = jest.fn(); - - await createComponentWithApollo({ stubs: { PipelineEditorMessages } }); - await emitError(); - }); - - it('shows an error message for the first error', () => { - expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage); - }); - - it('scrolls to the top of the page to bring attention to the error message', () => { - expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); - expect(window.scrollTo).toHaveBeenCalledTimes(1); - }); - - it('does not scroll to the top of the page if the same error occur multiple times in a row', async () => { - await emitError(); - - expect(window.scrollTo).toHaveBeenCalledTimes(1); - expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage); - }); - - it('scrolls to the top if the error is different', async () => { - await emitError(LOAD_FAILURE_UNKNOWN, []); - - expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage); - expect(window.scrollTo).toHaveBeenCalledTimes(2); - }); - - describe('when a user dismiss the alert', () => { - beforeEach(async () => { - await findAlert().vm.$emit('dismiss'); - }); - - it('shows an error if the type is the same, but the reason is different', async () => { - const newReason = 'Something broke'; - - await emitError(COMMIT_FAILURE, [newReason]); - - expect(window.scrollTo).toHaveBeenCalledTimes(2); - expect(findAlert().text()).toMatchInterpolatedText(`${updateFailureMessage} ${newReason}`); - }); - - it('does not show an error or scroll if a new error with the same type occurs', async () => { - await emitError(); - - expect(window.scrollTo).toHaveBeenCalledTimes(1); - expect(findAlert().exists()).toBe(false); - }); - - it('it shows an error and scroll when a new type is emitted', async () => { - await emitError(LOAD_FAILURE_UNKNOWN, []); - - expect(window.scrollTo).toHaveBeenCalledTimes(2); - expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage); - }); - - it('it shows an error and scroll if a previously shown type happen again', async () => { - await emitError(LOAD_FAILURE_UNKNOWN, []); - - expect(window.scrollTo).toHaveBeenCalledTimes(2); - expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage); - - await emitError(); - - expect(window.scrollTo).toHaveBeenCalledTimes(3); - expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage); - }); - }); - }); - describe('when add_new_config_file query param is present', () => { const originalLocation = window.location.href; |