diff options
Diffstat (limited to 'spec/frontend/pipeline_editor')
37 files changed, 0 insertions, 5510 deletions
diff --git a/spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js b/spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js deleted file mode 100644 index d03f12bc249..00000000000 --- a/spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { within } from '@testing-library/dom'; -import { mount } from '@vue/test-utils'; -import { merge } from 'lodash'; -import { TEST_HOST } from 'helpers/test_constants'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import CodeSnippetAlert from '~/pipeline_editor/components/code_snippet_alert/code_snippet_alert.vue'; -import { CODE_SNIPPET_SOURCE_API_FUZZING } from '~/pipeline_editor/components/code_snippet_alert/constants'; - -const apiFuzzingConfigurationPath = '/namespace/project/-/security/configuration/api_fuzzing'; - -describe('EE - CodeSnippetAlert', () => { - let wrapper; - - const createWrapper = (options) => { - wrapper = extendedWrapper( - mount( - CodeSnippetAlert, - merge( - { - provide: { - configurationPaths: { - [CODE_SNIPPET_SOURCE_API_FUZZING]: apiFuzzingConfigurationPath, - }, - }, - propsData: { - source: CODE_SNIPPET_SOURCE_API_FUZZING, - }, - }, - options, - ), - ), - ); - }; - - const withinComponent = () => within(wrapper.element); - const findDocsLink = () => withinComponent().getByRole('link', { name: /read documentation/i }); - const findConfigurationLink = () => - withinComponent().getByRole('link', { name: /Go back to configuration/i }); - - beforeEach(() => { - createWrapper(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it("provides a link to the feature's documentation", () => { - const docsLink = findDocsLink(); - - expect(docsLink).not.toBe(null); - expect(docsLink.href).toBe(`${TEST_HOST}/help/user/application_security/api_fuzzing/index`); - }); - - it("provides a link to the feature's configuration form", () => { - const configurationLink = findConfigurationLink(); - - expect(configurationLink).not.toBe(null); - expect(configurationLink.href).toBe(TEST_HOST + apiFuzzingConfigurationPath); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js deleted file mode 100644 index 0ee6da9d329..00000000000 --- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import { nextTick } from 'vue'; -import { GlFormInput, GlFormTextarea } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; - -import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; - -import { mockCommitMessage, mockDefaultBranch } from '../../mock_data'; - -const scrollIntoViewMock = jest.fn(); -HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; - -describe('Pipeline Editor | Commit Form', () => { - let wrapper; - - const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => { - wrapper = mountFn(CommitForm, { - propsData: { - defaultMessage: mockCommitMessage, - currentBranch: mockDefaultBranch, - hasUnsavedChanges: true, - isNewCiConfigFile: false, - ...props, - }, - - // attachTo is required for input/submit events - attachTo: mountFn === mount ? document.body : null, - }); - }; - - const findCommitTextarea = () => wrapper.findComponent(GlFormTextarea); - const findBranchInput = () => wrapper.findComponent(GlFormInput); - const findNewMrCheckbox = () => wrapper.find('[data-testid="new-mr-checkbox"]'); - const findSubmitBtn = () => wrapper.find('[type="submit"]'); - const findCancelBtn = () => wrapper.find('[type="reset"]'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when the form is displayed', () => { - beforeEach(async () => { - createComponent(); - }); - - it('shows a default commit message', () => { - expect(findCommitTextarea().attributes('value')).toBe(mockCommitMessage); - }); - - it('shows current branch', () => { - expect(findBranchInput().attributes('value')).toBe(mockDefaultBranch); - }); - - it('shows buttons', () => { - expect(findSubmitBtn().exists()).toBe(true); - expect(findCancelBtn().exists()).toBe(true); - }); - - it('does not show a new MR checkbox by default', () => { - expect(findNewMrCheckbox().exists()).toBe(false); - }); - }); - - describe('when buttons are clicked', () => { - beforeEach(async () => { - createComponent({}, mount); - }); - - it('emits an event when the form submits', () => { - findSubmitBtn().trigger('click'); - - expect(wrapper.emitted('submit')[0]).toEqual([ - { - message: mockCommitMessage, - sourceBranch: mockDefaultBranch, - openMergeRequest: false, - }, - ]); - }); - - it('emits an event when the form resets', () => { - findCancelBtn().trigger('click'); - - expect(wrapper.emitted('resetContent')).toHaveLength(1); - }); - }); - - describe('submit button', () => { - it.each` - hasUnsavedChanges | isNewCiConfigFile | isDisabled | btnState - ${false} | ${false} | ${true} | ${'disabled'} - ${true} | ${false} | ${false} | ${'enabled'} - ${true} | ${true} | ${false} | ${'enabled'} - ${false} | ${true} | ${false} | ${'enabled'} - `( - 'is $btnState when hasUnsavedChanges:$hasUnsavedChanges and isNewCiConfigfile:$isNewCiConfigFile', - ({ hasUnsavedChanges, isNewCiConfigFile, isDisabled }) => { - createComponent({ props: { hasUnsavedChanges, isNewCiConfigFile } }); - - if (isDisabled) { - expect(findSubmitBtn().attributes('disabled')).toBe('true'); - } else { - expect(findSubmitBtn().attributes('disabled')).toBeUndefined(); - } - }, - ); - }); - - describe('when user inputs values', () => { - const anotherMessage = 'Another commit message'; - const anotherBranch = 'my-branch'; - - beforeEach(() => { - createComponent({}, mount); - - findCommitTextarea().setValue(anotherMessage); - findBranchInput().setValue(anotherBranch); - }); - - it('shows a new MR checkbox', () => { - expect(findNewMrCheckbox().exists()).toBe(true); - }); - - it('emits an event with values', async () => { - await findNewMrCheckbox().setChecked(); - await findSubmitBtn().trigger('click'); - - expect(wrapper.emitted('submit')[0]).toEqual([ - { - message: anotherMessage, - sourceBranch: anotherBranch, - openMergeRequest: true, - }, - ]); - }); - - it('when the commit message is empty, submit button is disabled', async () => { - await findCommitTextarea().setValue(''); - - expect(findSubmitBtn().attributes('disabled')).toBe('disabled'); - }); - }); - - describe('when scrollToCommitForm becomes true', () => { - beforeEach(async () => { - createComponent(); - wrapper.setProps({ scrollToCommitForm: true }); - await nextTick(); - }); - - it('scrolls into view', () => { - expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth' }); - }); - - it('emits "scrolled-to-commit-form"', () => { - expect(wrapper.emitted()['scrolled-to-commit-form']).toHaveLength(1); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js deleted file mode 100644 index 744b0378a75..00000000000 --- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js +++ /dev/null @@ -1,287 +0,0 @@ -import VueApollo from 'vue-apollo'; -import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; -import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; -import { - COMMIT_ACTION_CREATE, - COMMIT_ACTION_UPDATE, - COMMIT_SUCCESS, - COMMIT_SUCCESS_WITH_REDIRECT, -} from '~/pipeline_editor/constants'; -import { resolvers } from '~/pipeline_editor/graphql/resolvers'; -import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql'; -import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql'; -import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql'; - -import { - mockCiConfigPath, - mockCiYml, - mockCommitCreateResponse, - mockCommitCreateResponseNewEtag, - mockCommitSha, - mockCommitMessage, - mockDefaultBranch, - mockProjectFullPath, -} from '../../mock_data'; - -const mockVariables = { - action: COMMIT_ACTION_UPDATE, - projectPath: mockProjectFullPath, - startBranch: mockDefaultBranch, - message: mockCommitMessage, - filePath: mockCiConfigPath, - content: mockCiYml, - lastCommitId: mockCommitSha, -}; - -const mockProvide = { - ciConfigPath: mockCiConfigPath, - projectFullPath: mockProjectFullPath, -}; - -describe('Pipeline Editor | Commit section', () => { - let wrapper; - let mockApollo; - const mockMutateCommitData = jest.fn(); - - const defaultProps = { - ciFileContent: mockCiYml, - commitSha: mockCommitSha, - hasUnsavedChanges: true, - isNewCiConfigFile: false, - }; - - const createComponent = ({ apolloConfig = {}, props = {}, options = {}, provide = {} } = {}) => { - wrapper = mount(CommitSection, { - propsData: { ...defaultProps, ...props }, - provide: { ...mockProvide, ...provide }, - data() { - return { - currentBranch: mockDefaultBranch, - }; - }, - attachTo: document.body, - ...apolloConfig, - ...options, - }); - }; - - const createComponentWithApollo = (options) => { - const handlers = [[commitCreate, mockMutateCommitData]]; - Vue.use(VueApollo); - mockApollo = createMockApollo(handlers, resolvers); - - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getCurrentBranch, - data: { - workBranches: { - __typename: 'BranchList', - current: { - __typename: 'WorkBranch', - name: mockDefaultBranch, - }, - }, - }, - }); - - const apolloConfig = { - apolloProvider: mockApollo, - }; - - createComponent({ ...options, apolloConfig }); - }; - - const findCommitForm = () => wrapper.findComponent(CommitForm); - const findCommitBtnLoadingIcon = () => - wrapper.find('[type="submit"]').findComponent(GlLoadingIcon); - - const submitCommit = async ({ - message = mockCommitMessage, - branch = mockDefaultBranch, - openMergeRequest = false, - } = {}) => { - await findCommitForm().findComponent(GlFormTextarea).setValue(message); - await findCommitForm().findComponent(GlFormInput).setValue(branch); - if (openMergeRequest) { - await findCommitForm().find('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest); - } - await findCommitForm().find('[type="submit"]').trigger('click'); - await waitForPromises(); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when the user commits a new file', () => { - beforeEach(async () => { - mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse); - createComponentWithApollo({ props: { isNewCiConfigFile: true } }); - await submitCommit(); - }); - - it('calls the mutation with the CREATE action', () => { - expect(mockMutateCommitData).toHaveBeenCalledTimes(1); - expect(mockMutateCommitData).toHaveBeenCalledWith({ - ...mockVariables, - action: COMMIT_ACTION_CREATE, - branch: mockDefaultBranch, - }); - }); - }); - - describe('when the user commits an update to an existing file', () => { - beforeEach(async () => { - createComponentWithApollo(); - await submitCommit(); - }); - - it('calls the mutation with the UPDATE action', () => { - expect(mockMutateCommitData).toHaveBeenCalledTimes(1); - expect(mockMutateCommitData).toHaveBeenCalledWith({ - ...mockVariables, - action: COMMIT_ACTION_UPDATE, - branch: mockDefaultBranch, - }); - }); - }); - - describe('when the user commits changes to the current branch', () => { - beforeEach(async () => { - createComponentWithApollo(); - await submitCommit(); - }); - - it('calls the mutation with the current branch', () => { - expect(mockMutateCommitData).toHaveBeenCalledTimes(1); - expect(mockMutateCommitData).toHaveBeenCalledWith({ - ...mockVariables, - branch: mockDefaultBranch, - }); - }); - - it('emits an event to communicate the commit was successful', () => { - expect(wrapper.emitted('commit')).toHaveLength(1); - expect(wrapper.emitted('commit')[0]).toEqual([{ type: COMMIT_SUCCESS }]); - }); - - it('emits an event to refetch the commit sha', () => { - expect(wrapper.emitted('updateCommitSha')).toHaveLength(1); - }); - - it('shows no saving state', () => { - expect(findCommitBtnLoadingIcon().exists()).toBe(false); - }); - - it('a second commit submits the latest sha, keeping the form updated', async () => { - await submitCommit(); - - expect(mockMutateCommitData).toHaveBeenCalledTimes(2); - expect(mockMutateCommitData).toHaveBeenCalledWith({ - ...mockVariables, - branch: mockDefaultBranch, - }); - }); - }); - - describe('when the user commits changes to a new branch', () => { - const newBranch = 'new-branch'; - - beforeEach(async () => { - createComponentWithApollo(); - await submitCommit({ - branch: newBranch, - }); - }); - - it('calls the mutation with the new branch', () => { - expect(mockMutateCommitData).toHaveBeenCalledWith({ - ...mockVariables, - branch: newBranch, - }); - }); - - it('does not emit an event to refetch the commit sha', () => { - expect(wrapper.emitted('updateCommitSha')).toBeUndefined(); - }); - }); - - describe('when the user commits changes to open a new merge request', () => { - const newBranch = 'new-branch'; - - beforeEach(async () => { - mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse); - createComponentWithApollo(); - mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse); - await submitCommit({ - branch: newBranch, - openMergeRequest: true, - }); - }); - - it('emits a commit event with the right type, sourceBranch and targetBranch', () => { - expect(wrapper.emitted('commit')).toHaveLength(1); - expect(wrapper.emitted('commit')[0]).toMatchObject([ - { - type: COMMIT_SUCCESS_WITH_REDIRECT, - params: { sourceBranch: newBranch, targetBranch: mockDefaultBranch }, - }, - ]); - }); - }); - - describe('when the commit is ocurring', () => { - beforeEach(() => { - createComponentWithApollo(); - }); - - it('shows a saving state', async () => { - mockMutateCommitData.mockImplementationOnce(() => { - expect(findCommitBtnLoadingIcon().exists()).toBe(true); - return Promise.resolve(); - }); - - await submitCommit({ - message: mockCommitMessage, - branch: mockDefaultBranch, - openMergeRequest: false, - }); - }); - }); - - describe('when the commit returns a different etag path', () => { - beforeEach(async () => { - createComponentWithApollo(); - jest.spyOn(wrapper.vm.$apollo, 'mutate'); - mockMutateCommitData.mockResolvedValue(mockCommitCreateResponseNewEtag); - await submitCommit(); - }); - - it('calls the client mutation to update the etag', () => { - // 1:Commit submission, 2:etag update, 3:currentBranch update, 4:lastCommit update - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(4); - expect(wrapper.vm.$apollo.mutate).toHaveBeenNthCalledWith(2, { - mutation: updatePipelineEtag, - variables: { - pipelineEtag: mockCommitCreateResponseNewEtag.data.commitCreate.commitPipelinePath, - }, - }); - }); - }); - - it('sets listeners on commit form', () => { - const handler = jest.fn(); - createComponent({ options: { listeners: { event: handler } } }); - findCommitForm().vm.$emit('event'); - expect(handler).toHaveBeenCalled(); - }); - - it('passes down scroll-to-commit-form prop to commit form', () => { - createComponent({ props: { 'scroll-to-commit-form': true } }); - expect(findCommitForm().props('scrollToCommitForm')).toBe(true); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js deleted file mode 100644 index 7e1e5004d91..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js +++ /dev/null @@ -1,60 +0,0 @@ -import { getByRole } from '@testing-library/dom'; -import { mount } from '@vue/test-utils'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import FirstPipelineCard from '~/pipeline_editor/components/drawer/cards/first_pipeline_card.vue'; -import { pipelineEditorTrackingOptions } from '~/pipeline_editor/constants'; - -describe('First pipeline card', () => { - let wrapper; - let trackingSpy; - - const createComponent = () => { - wrapper = mount(FirstPipelineCard); - }; - - const getLinkByName = (name) => getByRole(wrapper.element, 'link', { name }); - const findRunnersLink = () => getLinkByName(/make sure your instance has runners available/i); - const findInstructionsList = () => wrapper.find('ol'); - const findAllInstructions = () => findInstructionsList().findAll('li'); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the title', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title); - }); - - it('renders the content', () => { - expect(findInstructionsList().exists()).toBe(true); - expect(findAllInstructions()).toHaveLength(3); - }); - - it('renders the link', () => { - expect(findRunnersLink().href).toBe(wrapper.vm.$options.RUNNER_HELP_URL); - }); - - describe('tracking', () => { - beforeEach(() => { - createComponent(); - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - afterEach(() => { - unmockTracking(); - }); - - it('tracks runners help page click', async () => { - const { label } = pipelineEditorTrackingOptions; - const { runners } = pipelineEditorTrackingOptions.actions.helpDrawerLinks; - - await findRunnersLink().click(); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, runners, { label }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js deleted file mode 100644 index c592e959068..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import GettingStartedCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue'; - -describe('Getting started card', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(GettingStartedCard); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the title', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title); - }); - - it('renders the content', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js deleted file mode 100644 index 49177befe0e..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js +++ /dev/null @@ -1,89 +0,0 @@ -import { getByRole } from '@testing-library/dom'; -import { mount } from '@vue/test-utils'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import PipelineConfigReferenceCard from '~/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue'; -import { pipelineEditorTrackingOptions } from '~/pipeline_editor/constants'; - -describe('Pipeline config reference card', () => { - let wrapper; - let trackingSpy; - - const defaultProvide = { - ciExamplesHelpPagePath: 'help/ci/examples/', - ciHelpPagePath: 'help/ci/introduction', - needsHelpPagePath: 'help/ci/yaml#needs', - ymlHelpPagePath: 'help/ci/yaml', - }; - - const createComponent = () => { - wrapper = mount(PipelineConfigReferenceCard, { - provide: { - ...defaultProvide, - }, - }); - }; - - const getLinkByName = (name) => getByRole(wrapper.element, 'link', { name }); - const findCiExamplesLink = () => getLinkByName(/CI\/CD examples and templates/i); - const findCiIntroLink = () => getLinkByName(/GitLab CI\/CD concepts/i); - const findNeedsLink = () => getLinkByName(/Needs keyword/i); - const findYmlSyntaxLink = () => getLinkByName(/.gitlab-ci.yml syntax reference/i); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the title', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title); - }); - - it('renders the content', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph); - }); - - it('renders the links', () => { - expect(findCiExamplesLink().href).toContain(defaultProvide.ciExamplesHelpPagePath); - expect(findCiIntroLink().href).toContain(defaultProvide.ciHelpPagePath); - expect(findNeedsLink().href).toContain(defaultProvide.needsHelpPagePath); - expect(findYmlSyntaxLink().href).toContain(defaultProvide.ymlHelpPagePath); - }); - - describe('tracking', () => { - beforeEach(() => { - createComponent(); - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - afterEach(() => { - unmockTracking(); - }); - - const testTracker = async (element, expectedAction) => { - const { label } = pipelineEditorTrackingOptions; - - await element.click(); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, expectedAction, { - label, - }); - }; - - it('tracks help page links', async () => { - const { - CI_EXAMPLES_LINK, - CI_HELP_LINK, - CI_NEEDS_LINK, - CI_YAML_LINK, - } = pipelineEditorTrackingOptions.actions.helpDrawerLinks; - - testTracker(findCiExamplesLink(), CI_EXAMPLES_LINK); - testTracker(findCiIntroLink(), CI_HELP_LINK); - testTracker(findNeedsLink(), CI_NEEDS_LINK); - testTracker(findYmlSyntaxLink(), CI_YAML_LINK); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js deleted file mode 100644 index bebd2484c1d..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import VisualizeAndLintCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue'; - -describe('Visual and Lint card', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(VisualizeAndLintCard); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the title', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title); - }); - - it('renders the content', () => { - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js b/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js deleted file mode 100644 index 33b53bf6a56..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlDrawer } from '@gitlab/ui'; -import PipelineEditorDrawer from '~/pipeline_editor/components/drawer/pipeline_editor_drawer.vue'; - -describe('Pipeline editor drawer', () => { - let wrapper; - - const findDrawer = () => wrapper.findComponent(GlDrawer); - - const createComponent = () => { - wrapper = shallowMount(PipelineEditorDrawer); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('emits close event when closing the drawer', () => { - createComponent(); - - expect(wrapper.emitted('close-drawer')).toBeUndefined(); - - findDrawer().vm.$emit('close'); - - expect(wrapper.emitted('close-drawer')).toHaveLength(1); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js b/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js deleted file mode 100644 index edd2b45569a..00000000000 --- a/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import DemoJobPill from '~/pipeline_editor/components/drawer/ui/demo_job_pill.vue'; - -describe('Demo job pill', () => { - let wrapper; - const jobName = 'my-build-job'; - - const createComponent = () => { - wrapper = shallowMount(DemoJobPill, { - propsData: { - jobName, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the jobName', () => { - expect(wrapper.text()).toContain(jobName); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js deleted file mode 100644 index 7dd8a77d055..00000000000 --- a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; - -import { EDITOR_READY_EVENT } from '~/editor/constants'; -import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue'; -import { mockLintResponse, mockCiConfigPath } from '../../mock_data'; - -describe('Text editor component', () => { - let wrapper; - - const MockSourceEditor = { - template: '<div/>', - props: ['value', 'fileName', 'editorOptions'], - mounted() { - this.$emit(EDITOR_READY_EVENT); - }, - }; - - const createComponent = ({ props = {} } = {}) => { - wrapper = shallowMount(CiConfigMergedPreview, { - propsData: { - ciConfigData: mockLintResponse, - ...props, - }, - provide: { - ciConfigPath: mockCiConfigPath, - }, - stubs: { - SourceEditor: MockSourceEditor, - }, - }); - }; - - const findIcon = () => wrapper.findComponent(GlIcon); - const findEditor = () => wrapper.findComponent(MockSourceEditor); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when status is valid', () => { - beforeEach(() => { - createComponent(); - }); - - it('shows an information message that the section is not editable', () => { - expect(findIcon().exists()).toBe(true); - expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.viewOnlyMessage); - }); - - it('contains an editor', () => { - expect(findEditor().exists()).toBe(true); - }); - - it('editor contains the value provided', () => { - expect(findEditor().props('value')).toBe(mockLintResponse.mergedYaml); - }); - - it('editor is configured for the CI config path', () => { - expect(findEditor().props('fileName')).toBe(mockCiConfigPath); - }); - - it('editor is readonly', () => { - expect(findEditor().props('editorOptions')).toMatchObject({ - readOnly: true, - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/editor/ci_editor_header_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_editor_header_spec.js deleted file mode 100644 index 930f08ef545..00000000000 --- a/spec/frontend/pipeline_editor/components/editor/ci_editor_header_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import CiEditorHeader from '~/pipeline_editor/components/editor/ci_editor_header.vue'; -import { - pipelineEditorTrackingOptions, - TEMPLATE_REPOSITORY_URL, -} from '~/pipeline_editor/constants'; - -describe('CI Editor Header', () => { - let wrapper; - let trackingSpy = null; - - const createComponent = ({ showDrawer = false } = {}) => { - wrapper = extendedWrapper( - shallowMount(CiEditorHeader, { - propsData: { - showDrawer, - }, - }), - ); - }; - - const findLinkBtn = () => wrapper.findByTestId('template-repo-link'); - const findHelpBtn = () => wrapper.findByTestId('drawer-toggle'); - - afterEach(() => { - wrapper.destroy(); - unmockTracking(); - }); - - const testTracker = async (element, expectedAction) => { - const { label } = pipelineEditorTrackingOptions; - - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await element.vm.$emit('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, expectedAction, { - label, - }); - }; - - describe('link button', () => { - beforeEach(() => { - createComponent(); - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - it('finds the browse template button', () => { - expect(findLinkBtn().exists()).toBe(true); - }); - - it('contains the link to the template repo', () => { - expect(findLinkBtn().attributes('href')).toBe(TEMPLATE_REPOSITORY_URL); - }); - - it('has the external-link icon', () => { - expect(findLinkBtn().props('icon')).toBe('external-link'); - }); - - it('tracks the click on the browse button', async () => { - const { browseTemplates } = pipelineEditorTrackingOptions.actions; - - testTracker(findLinkBtn(), browseTemplates); - }); - }); - - describe('help button', () => { - beforeEach(() => { - createComponent(); - }); - - it('finds the help button', () => { - expect(findHelpBtn().exists()).toBe(true); - }); - - it('has the information-o icon', () => { - expect(findHelpBtn().props('icon')).toBe('information-o'); - }); - - describe('when pipeline editor drawer is closed', () => { - beforeEach(() => { - createComponent({ showDrawer: false }); - }); - - it('emits open drawer event when clicked', () => { - expect(wrapper.emitted('open-drawer')).toBeUndefined(); - - findHelpBtn().vm.$emit('click'); - - expect(wrapper.emitted('open-drawer')).toHaveLength(1); - }); - - it('tracks open help drawer action', async () => { - const { actions } = pipelineEditorTrackingOptions; - - testTracker(findHelpBtn(), actions.openHelpDrawer); - }); - }); - - describe('when pipeline editor drawer is open', () => { - beforeEach(() => { - createComponent({ showDrawer: true }); - }); - - it('emits close drawer event when clicked', () => { - expect(wrapper.emitted('close-drawer')).toBeUndefined(); - - findHelpBtn().vm.$emit('click'); - - expect(wrapper.emitted('close-drawer')).toHaveLength(1); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js deleted file mode 100644 index 6cdf9a93d55..00000000000 --- a/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js +++ /dev/null @@ -1,134 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; - -import { EDITOR_READY_EVENT } from '~/editor/constants'; -import { SOURCE_EDITOR_DEBOUNCE } from '~/pipeline_editor/constants'; -import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue'; -import { - mockCiConfigPath, - mockCiYml, - mockCommitSha, - mockProjectPath, - mockProjectNamespace, - mockDefaultBranch, -} from '../../mock_data'; - -describe('Pipeline Editor | Text editor component', () => { - let wrapper; - - let editorReadyListener; - let mockUse; - let mockRegisterCiSchema; - let mockEditorInstance; - let editorInstanceDetail; - - const MockSourceEditor = { - template: '<div/>', - props: ['value', 'fileName', 'editorOptions', 'debounceValue'], - }; - - const createComponent = (glFeatures = {}, mountFn = shallowMount) => { - wrapper = mountFn(TextEditor, { - provide: { - projectPath: mockProjectPath, - projectNamespace: mockProjectNamespace, - ciConfigPath: mockCiConfigPath, - defaultBranch: mockDefaultBranch, - glFeatures, - }, - propsData: { - commitSha: mockCommitSha, - }, - attrs: { - value: mockCiYml, - }, - listeners: { - [EDITOR_READY_EVENT]: editorReadyListener, - }, - stubs: { - SourceEditor: MockSourceEditor, - }, - }); - }; - - 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(); - - mockUse.mockClear(); - mockRegisterCiSchema.mockClear(); - }); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('contains an editor', () => { - expect(findEditor().exists()).toBe(true); - }); - - it('editor contains the value provided', () => { - expect(findEditor().props('value')).toBe(mockCiYml); - }); - - it('editor is configured for the CI config path', () => { - expect(findEditor().props('fileName')).toBe(mockCiConfigPath); - }); - - it('passes down editor configs options', () => { - expect(findEditor().props('editorOptions')).toEqual({ quickSuggestions: true }); - }); - - it('passes down editor debounce value', () => { - expect(findEditor().props('debounceValue')).toBe(SOURCE_EDITOR_DEBOUNCE); - }); - - it('bubbles up events', () => { - findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); - - expect(editorReadyListener).toHaveBeenCalled(); - }); - }); - - describe('CI schema', () => { - describe('when `schema_linting` feature flag is on', () => { - beforeEach(() => { - createComponent({ schemaLinting: true }); - findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); - }); - - it('configures editor with syntax highlight', () => { - expect(mockUse).toHaveBeenCalledTimes(1); - expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1); - }); - }); - - describe('when `schema_linting` feature flag is off', () => { - beforeEach(() => { - createComponent(); - findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail); - }); - - it('does not call the register CI schema function', () => { - expect(mockUse).not.toHaveBeenCalled(); - expect(mockRegisterCiSchema).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js deleted file mode 100644 index f0347ad19ac..00000000000 --- a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js +++ /dev/null @@ -1,432 +0,0 @@ -import { - GlDropdown, - GlDropdownItem, - GlInfiniteScroll, - GlLoadingIcon, - GlSearchBoxByType, -} from '@gitlab/ui'; -import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue'; -import { DEFAULT_FAILURE } from '~/pipeline_editor/constants'; -import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql'; -import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql'; -import getLastCommitBranch from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql'; -import { resolvers } from '~/pipeline_editor/graphql/resolvers'; - -import { - mockBranchPaginationLimit, - mockDefaultBranch, - mockEmptySearchBranches, - mockProjectBranches, - mockProjectFullPath, - mockSearchBranches, - mockTotalBranches, - mockTotalBranchResults, - mockTotalSearchResults, -} from '../../mock_data'; - -const localVue = createLocalVue(); -localVue.use(VueApollo); - -describe('Pipeline editor branch switcher', () => { - let wrapper; - let mockApollo; - let mockAvailableBranchQuery; - - const createComponent = ({ - currentBranch = mockDefaultBranch, - availableBranches = ['main'], - isQueryLoading = false, - mountFn = shallowMount, - options = {}, - props = {}, - } = {}) => { - wrapper = mountFn(BranchSwitcher, { - propsData: { - ...props, - paginationLimit: mockBranchPaginationLimit, - }, - provide: { - projectFullPath: mockProjectFullPath, - totalBranches: mockTotalBranches, - }, - mocks: { - $apollo: { - queries: { - availableBranches: { - loading: isQueryLoading, - }, - }, - }, - }, - data() { - return { - availableBranches, - currentBranch, - }; - }, - ...options, - }); - }; - - const createComponentWithApollo = ({ - mountFn = shallowMount, - props = {}, - availableBranches = ['main'], - } = {}) => { - const handlers = [[getAvailableBranchesQuery, mockAvailableBranchQuery]]; - mockApollo = createMockApollo(handlers, resolvers); - - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getCurrentBranch, - data: { - workBranches: { - __typename: 'BranchList', - current: { - __typename: 'WorkBranch', - name: mockDefaultBranch, - }, - }, - }, - }); - - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getLastCommitBranch, - data: { - workBranches: { - __typename: 'BranchList', - lastCommit: { - __typename: 'WorkBranch', - name: '', - }, - }, - }, - }); - - createComponent({ - mountFn, - props, - availableBranches, - options: { - localVue, - apolloProvider: mockApollo, - mocks: {}, - }, - }); - }; - - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); - const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll); - const defaultBranchInDropdown = () => findDropdownItems().at(0); - - const setAvailableBranchesMock = (availableBranches) => { - mockAvailableBranchQuery.mockResolvedValue(availableBranches); - }; - - beforeEach(() => { - mockAvailableBranchQuery = jest.fn(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const testErrorHandling = () => { - expect(wrapper.emitted('showError')).toBeDefined(); - expect(wrapper.emitted('showError')[0]).toEqual([ - { - reasons: [wrapper.vm.$options.i18n.fetchError], - type: DEFAULT_FAILURE, - }, - ]); - }; - - describe('when querying for the first time', () => { - beforeEach(() => { - createComponentWithApollo({ availableBranches: [] }); - }); - - it('disables the dropdown', () => { - expect(findDropdown().props('disabled')).toBe(true); - }); - }); - - describe('after querying', () => { - beforeEach(async () => { - setAvailableBranchesMock(mockProjectBranches); - createComponentWithApollo({ mountFn: mount }); - await waitForPromises(); - }); - - it('renders search box', () => { - expect(findSearchBox().exists()).toBe(true); - }); - - it('renders list of branches', () => { - expect(findDropdown().exists()).toBe(true); - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); - }); - - it('renders current branch with a check mark', () => { - expect(defaultBranchInDropdown().text()).toBe(mockDefaultBranch); - expect(defaultBranchInDropdown().props('isChecked')).toBe(true); - }); - - it('does not render check mark for other branches', () => { - const nonDefaultBranch = findDropdownItems().at(1); - - expect(nonDefaultBranch.text()).not.toBe(mockDefaultBranch); - expect(nonDefaultBranch.props('isChecked')).toBe(false); - }); - }); - - describe('on fetch error', () => { - beforeEach(async () => { - setAvailableBranchesMock(new Error()); - createComponentWithApollo({ availableBranches: [] }); - await waitForPromises(); - }); - - it('does not render dropdown', () => { - expect(findDropdown().props('disabled')).toBe(true); - }); - - it('shows an error message', () => { - testErrorHandling(); - }); - }); - - describe('when switching branches', () => { - beforeEach(async () => { - jest.spyOn(window.history, 'pushState').mockImplementation(() => {}); - setAvailableBranchesMock(mockProjectBranches); - createComponentWithApollo({ mountFn: mount }); - await waitForPromises(); - }); - - it('updates session history when selecting a different branch', async () => { - const branch = findDropdownItems().at(1); - branch.vm.$emit('click'); - await waitForPromises(); - - expect(window.history.pushState).toHaveBeenCalled(); - expect(window.history.pushState.mock.calls[0][2]).toContain(`?branch_name=${branch.text()}`); - }); - - it('does not update session history when selecting current branch', async () => { - const branch = findDropdownItems().at(0); - branch.vm.$emit('click'); - await waitForPromises(); - - expect(branch.text()).toBe(mockDefaultBranch); - expect(window.history.pushState).not.toHaveBeenCalled(); - }); - - it('emits the refetchContent event when selecting a different branch', async () => { - const branch = findDropdownItems().at(1); - - expect(branch.text()).not.toBe(mockDefaultBranch); - expect(wrapper.emitted('refetchContent')).toBeUndefined(); - - branch.vm.$emit('click'); - await waitForPromises(); - - expect(wrapper.emitted('refetchContent')).toBeDefined(); - expect(wrapper.emitted('refetchContent')).toHaveLength(1); - }); - - it('does not emit the refetchContent event when selecting the current branch', async () => { - const branch = findDropdownItems().at(0); - - expect(branch.text()).toBe(mockDefaultBranch); - expect(wrapper.emitted('refetchContent')).toBeUndefined(); - - branch.vm.$emit('click'); - await waitForPromises(); - - expect(wrapper.emitted('refetchContent')).toBeUndefined(); - }); - - describe('with unsaved changes', () => { - beforeEach(async () => { - createComponentWithApollo({ mountFn: mount, props: { hasUnsavedChanges: true } }); - await waitForPromises(); - }); - - it('emits `select-branch` event and does not switch branch', async () => { - expect(wrapper.emitted('select-branch')).toBeUndefined(); - - const branch = findDropdownItems().at(1); - await branch.vm.$emit('click'); - - expect(wrapper.emitted('select-branch')).toEqual([[branch.text()]]); - expect(wrapper.emitted('refetchContent')).toBeUndefined(); - }); - }); - }); - - describe('when searching', () => { - beforeEach(async () => { - setAvailableBranchesMock(mockProjectBranches); - createComponentWithApollo({ mountFn: mount }); - await waitForPromises(); - }); - - afterEach(() => { - mockAvailableBranchQuery.mockClear(); - }); - - it('shows error message on fetch error', async () => { - mockAvailableBranchQuery.mockResolvedValue(new Error()); - - findSearchBox().vm.$emit('input', 'te'); - await waitForPromises(); - - testErrorHandling(); - }); - - describe('with a search term', () => { - beforeEach(async () => { - mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches); - }); - - it('calls query with correct variables', async () => { - findSearchBox().vm.$emit('input', 'te'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ - limit: mockTotalBranches, // fetch all branches - offset: 0, - projectFullPath: mockProjectFullPath, - searchPattern: '*te*', - }); - }); - - it('fetches new list of branches', async () => { - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); - - findSearchBox().vm.$emit('input', 'te'); - await waitForPromises(); - - expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); - }); - - it('does not hide dropdown when search result is empty', async () => { - mockAvailableBranchQuery.mockResolvedValue(mockEmptySearchBranches); - findSearchBox().vm.$emit('input', 'aaaaa'); - await waitForPromises(); - - expect(findDropdown().exists()).toBe(true); - expect(findDropdownItems()).toHaveLength(0); - }); - }); - - describe('without a search term', () => { - beforeEach(async () => { - mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches); - findSearchBox().vm.$emit('input', 'te'); - await waitForPromises(); - - mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches); - }); - - it('calls query with correct variables', async () => { - findSearchBox().vm.$emit('input', ''); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ - limit: mockBranchPaginationLimit, // only fetch first n branches first - offset: 0, - projectFullPath: mockProjectFullPath, - searchPattern: '*', - }); - }); - - it('fetches new list of branches', async () => { - expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); - - findSearchBox().vm.$emit('input', ''); - await waitForPromises(); - - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); - }); - }); - }); - - describe('loading icon', () => { - it.each` - isQueryLoading | isRendered - ${true} | ${true} - ${false} | ${false} - `('checks if query is loading before rendering', ({ isQueryLoading, isRendered }) => { - createComponent({ isQueryLoading, mountFn: mount }); - - expect(findLoadingIcon().exists()).toBe(isRendered); - }); - }); - - describe('when scrolling to the bottom of the list', () => { - beforeEach(async () => { - setAvailableBranchesMock(mockProjectBranches); - createComponentWithApollo(); - await waitForPromises(); - }); - - afterEach(() => { - mockAvailableBranchQuery.mockClear(); - }); - - describe('when search term is empty', () => { - it('fetches more branches', async () => { - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(1); - - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); - }); - - it('calls the query with the correct variables', async () => { - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ - limit: mockBranchPaginationLimit, - offset: mockBranchPaginationLimit, // offset changed - projectFullPath: mockProjectFullPath, - searchPattern: '*', - }); - }); - - it('shows error message on fetch error', async () => { - mockAvailableBranchQuery.mockResolvedValue(new Error()); - - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - testErrorHandling(); - }); - }); - - describe('when search term exists', () => { - it('does not fetch more branches', async () => { - findSearchBox().vm.$emit('input', 'te'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); - mockAvailableBranchQuery.mockClear(); - - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js deleted file mode 100644 index d503aff40b8..00000000000 --- a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js +++ /dev/null @@ -1,126 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue'; -import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue'; -import FileTreePopover from '~/pipeline_editor/components/popovers/file_tree_popover.vue'; -import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql'; -import { - EDITOR_APP_STATUS_EMPTY, - EDITOR_APP_STATUS_LOADING, - EDITOR_APP_STATUS_VALID, -} from '~/pipeline_editor/constants'; - -Vue.use(VueApollo); - -describe('Pipeline editor file nav', () => { - let wrapper; - - const mockApollo = createMockApollo(); - - const createComponent = ({ - appStatus = EDITOR_APP_STATUS_VALID, - isNewCiConfigFile = false, - } = {}) => { - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getAppStatus, - data: { - app: { - __typename: 'PipelineEditorApp', - status: appStatus, - }, - }, - }); - - wrapper = extendedWrapper( - shallowMount(PipelineEditorFileNav, { - apolloProvider: mockApollo, - propsData: { - isNewCiConfigFile, - }, - }), - ); - }; - - const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher); - const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle'); - const findPopoverContainer = () => wrapper.findComponent(FileTreePopover); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders the branch switcher', () => { - expect(findBranchSwitcher().exists()).toBe(true); - }); - }); - - describe('file tree', () => { - describe('when editor is in the empty state', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_EMPTY, isNewCiConfigFile: false }); - }); - - it('does not render the file tree button', () => { - expect(findFileTreeBtn().exists()).toBe(false); - }); - - it('does not render the file tree popover', () => { - expect(findPopoverContainer().exists()).toBe(false); - }); - }); - - describe('when user is about to create their config file for the first time', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_VALID, isNewCiConfigFile: true }); - }); - - it('does not render the file tree button', () => { - expect(findFileTreeBtn().exists()).toBe(false); - }); - - it('does not render the file tree popover', () => { - expect(findPopoverContainer().exists()).toBe(false); - }); - }); - - describe('when app is in a global loading state', () => { - it('renders the file tree button with a loading icon', () => { - createComponent({ appStatus: EDITOR_APP_STATUS_LOADING, isNewCiConfigFile: false }); - - expect(findFileTreeBtn().exists()).toBe(true); - expect(findFileTreeBtn().attributes('loading')).toBe('true'); - }); - }); - - describe('when editor has a non-empty config file open', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_VALID, isNewCiConfigFile: false }); - }); - - it('renders the file tree button', () => { - expect(findFileTreeBtn().exists()).toBe(true); - expect(findFileTreeBtn().props('icon')).toBe('file-tree'); - }); - - it('renders the file tree popover', () => { - expect(findPopoverContainer().exists()).toBe(true); - }); - - it('file tree button emits toggle-file-tree event', () => { - expect(wrapper.emitted('toggle-file-tree')).toBe(undefined); - - findFileTreeBtn().vm.$emit('click'); - - expect(wrapper.emitted('toggle-file-tree')).toHaveLength(1); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/file-tree/container_spec.js b/spec/frontend/pipeline_editor/components/file-tree/container_spec.js deleted file mode 100644 index f79074f1e0f..00000000000 --- a/spec/frontend/pipeline_editor/components/file-tree/container_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import { nextTick } from 'vue'; -import { shallowMount } from '@vue/test-utils'; -import { GlAlert } from '@gitlab/ui'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { createMockDirective } from 'helpers/vue_mock_directive'; -import PipelineEditorFileTreeContainer from '~/pipeline_editor/components/file_tree/container.vue'; -import PipelineEditorFileTreeItem from '~/pipeline_editor/components/file_tree/file_item.vue'; -import { FILE_TREE_TIP_DISMISSED_KEY } from '~/pipeline_editor/constants'; -import { mockCiConfigPath, mockIncludes, mockIncludesHelpPagePath } from '../../mock_data'; - -describe('Pipeline editor file nav', () => { - let wrapper; - - const createComponent = ({ includes = mockIncludes, stubs } = {}) => { - wrapper = extendedWrapper( - shallowMount(PipelineEditorFileTreeContainer, { - provide: { - ciConfigPath: mockCiConfigPath, - includesHelpPagePath: mockIncludesHelpPagePath, - }, - propsData: { - includes, - }, - directives: { - GlTooltip: createMockDirective(), - }, - stubs, - }), - ); - }; - - const findTip = () => wrapper.findComponent(GlAlert); - const findCurrentConfigFilename = () => wrapper.findByTestId('current-config-filename'); - const fileTreeItems = () => wrapper.findAllComponents(PipelineEditorFileTreeItem); - - afterEach(() => { - localStorage.clear(); - wrapper.destroy(); - }); - - describe('template', () => { - beforeEach(() => { - createComponent({ stubs: { GlAlert } }); - }); - - it('renders config file as a file item', () => { - expect(findCurrentConfigFilename().text()).toBe(mockCiConfigPath); - }); - }); - - describe('when includes list is empty', () => { - describe('when dismiss state is not saved in local storage', () => { - beforeEach(() => { - createComponent({ - includes: [], - stubs: { GlAlert }, - }); - }); - - it('does not render filenames', () => { - expect(fileTreeItems().exists()).toBe(false); - }); - - it('renders alert tip', async () => { - expect(findTip().exists()).toBe(true); - }); - - it('renders learn more link', async () => { - expect(findTip().props('secondaryButtonLink')).toBe(mockIncludesHelpPagePath); - }); - - it('can dismiss the tip', async () => { - expect(findTip().exists()).toBe(true); - - findTip().vm.$emit('dismiss'); - await nextTick(); - - expect(findTip().exists()).toBe(false); - }); - }); - - describe('when dismiss state is saved in local storage', () => { - beforeEach(() => { - localStorage.setItem(FILE_TREE_TIP_DISMISSED_KEY, 'true'); - createComponent({ - includes: [], - stubs: { GlAlert }, - }); - }); - - it('does not render alert tip', async () => { - expect(findTip().exists()).toBe(false); - }); - }); - - describe('when component receives new props with includes files', () => { - beforeEach(() => { - createComponent({ includes: [] }); - }); - - it('hides tip and renders list of files', async () => { - expect(findTip().exists()).toBe(true); - expect(fileTreeItems()).toHaveLength(0); - - await wrapper.setProps({ includes: mockIncludes }); - - expect(findTip().exists()).toBe(false); - expect(fileTreeItems()).toHaveLength(mockIncludes.length); - }); - }); - }); - - describe('when there are includes files', () => { - beforeEach(() => { - createComponent({ stubs: { GlAlert } }); - }); - - it('does not render alert tip', () => { - expect(findTip().exists()).toBe(false); - }); - - it('renders the list of files', () => { - expect(fileTreeItems()).toHaveLength(mockIncludes.length); - }); - - describe('when component receives new props with empty includes', () => { - it('shows tip and does not render list of files', async () => { - expect(findTip().exists()).toBe(false); - expect(fileTreeItems()).toHaveLength(mockIncludes.length); - - await wrapper.setProps({ includes: [] }); - - expect(findTip().exists()).toBe(true); - expect(fileTreeItems()).toHaveLength(0); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/file-tree/file_item_spec.js b/spec/frontend/pipeline_editor/components/file-tree/file_item_spec.js deleted file mode 100644 index f12ac14c6be..00000000000 --- a/spec/frontend/pipeline_editor/components/file-tree/file_item_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import FileIcon from '~/vue_shared/components/file_icon.vue'; -import PipelineEditorFileTreeItem from '~/pipeline_editor/components/file_tree/file_item.vue'; -import { mockIncludesWithBlob, mockDefaultIncludes } from '../../mock_data'; - -describe('Pipeline editor file nav', () => { - let wrapper; - - const createComponent = ({ file = mockDefaultIncludes } = {}) => { - wrapper = shallowMount(PipelineEditorFileTreeItem, { - propsData: { - file, - }, - }); - }; - - const fileIcon = () => wrapper.findComponent(FileIcon); - const link = () => wrapper.findComponent(GlLink); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders file icon', () => { - expect(fileIcon().exists()).toBe(true); - }); - - it('renders file name', () => { - expect(wrapper.text()).toBe(mockDefaultIncludes.location); - }); - - it('links to raw path by default', () => { - expect(link().attributes('href')).toBe(mockDefaultIncludes.raw); - }); - }); - - describe('when file has blob link', () => { - beforeEach(() => { - createComponent({ file: mockIncludesWithBlob }); - }); - - it('links to blob path', () => { - expect(link().attributes('href')).toBe(mockIncludesWithBlob.blob); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js deleted file mode 100644 index e1dc08b637f..00000000000 --- a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; -import PipelineStatus from '~/pipeline_editor/components/header/pipeline_status.vue'; -import ValidationSegment from '~/pipeline_editor/components/header/validation_segment.vue'; - -import { mockCiYml, mockLintResponse } from '../../mock_data'; - -describe('Pipeline editor header', () => { - let wrapper; - - const createComponent = ({ provide = {}, props = {} } = {}) => { - wrapper = shallowMount(PipelineEditorHeader, { - provide: { - ...provide, - }, - propsData: { - ciConfigData: mockLintResponse, - ciFileContent: mockCiYml, - isCiConfigDataLoading: false, - isNewCiConfigFile: false, - ...props, - }, - }); - }; - - const findPipelineStatus = () => wrapper.findComponent(PipelineStatus); - const findValidationSegment = () => wrapper.findComponent(ValidationSegment); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('template', () => { - it('hides the pipeline status for new projects without a CI file', () => { - createComponent({ props: { isNewCiConfigFile: true } }); - - expect(findPipelineStatus().exists()).toBe(false); - }); - - it('renders the pipeline status when CI file exists', () => { - createComponent({ props: { isNewCiConfigFile: false } }); - - expect(findPipelineStatus().exists()).toBe(true); - }); - - it('renders the validation segment', () => { - createComponent(); - - expect(findValidationSegment().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js deleted file mode 100644 index d40a9cc8100..00000000000 --- a/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue'; -import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; -import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; -import { PIPELINE_FAILURE } from '~/pipeline_editor/constants'; -import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data'; - -Vue.use(VueApollo); - -describe('Pipeline Status', () => { - let wrapper; - let mockApollo; - let mockLinkedPipelinesQuery; - - const createComponent = ({ hasStages = true, options } = {}) => { - wrapper = shallowMount(PipelineEditorMiniGraph, { - provide: { - dataMethod: 'graphql', - projectFullPath: mockProjectFullPath, - }, - propsData: { - pipeline: mockProjectPipeline({ hasStages }).pipeline, - }, - ...options, - }); - }; - - const createComponentWithApollo = (hasStages = true) => { - const handlers = [[getLinkedPipelinesQuery, mockLinkedPipelinesQuery]]; - mockApollo = createMockApollo(handlers); - - createComponent({ - hasStages, - options: { - apolloProvider: mockApollo, - }, - }); - }; - - const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); - - beforeEach(() => { - mockLinkedPipelinesQuery = jest.fn(); - }); - - afterEach(() => { - mockLinkedPipelinesQuery.mockReset(); - wrapper.destroy(); - }); - - describe('when there are stages', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders pipeline mini graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(true); - }); - }); - - describe('when there are no stages', () => { - beforeEach(() => { - createComponent({ hasStages: false }); - }); - - it('does not render pipeline mini graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(false); - }); - }); - - describe('when querying upstream and downstream pipelines', () => { - describe('when query succeeds', () => { - beforeEach(() => { - mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines()); - createComponentWithApollo(); - }); - - it('should call the query with the correct variables', () => { - expect(mockLinkedPipelinesQuery).toHaveBeenCalledTimes(1); - expect(mockLinkedPipelinesQuery).toHaveBeenCalledWith({ - fullPath: mockProjectFullPath, - iid: mockProjectPipeline().pipeline.iid, - }); - }); - }); - - describe('when query fails', () => { - beforeEach(async () => { - mockLinkedPipelinesQuery.mockRejectedValue(new Error()); - createComponentWithApollo(); - await waitForPromises(); - }); - - it('should emit an error event when query fails', async () => { - expect(wrapper.emitted('showError')).toHaveLength(1); - expect(wrapper.emitted('showError')[0]).toEqual([ - { - type: PIPELINE_FAILURE, - reasons: [wrapper.vm.$options.i18n.linkedPipelinesFetchError], - }, - ]); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js deleted file mode 100644 index 35315db39f8..00000000000 --- a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js +++ /dev/null @@ -1,132 +0,0 @@ -import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import PipelineStatus, { i18n } from '~/pipeline_editor/components/header/pipeline_status.vue'; -import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql'; -import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue'; -import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data'; - -Vue.use(VueApollo); - -describe('Pipeline Status', () => { - let wrapper; - let mockApollo; - let mockPipelineQuery; - - const createComponentWithApollo = () => { - const handlers = [[getPipelineQuery, mockPipelineQuery]]; - mockApollo = createMockApollo(handlers); - - wrapper = shallowMount(PipelineStatus, { - apolloProvider: mockApollo, - propsData: { - commitSha: mockCommitSha, - }, - provide: { - projectFullPath: mockProjectFullPath, - }, - stubs: { GlLink, GlSprintf }, - }); - }; - - const findIcon = () => wrapper.findComponent(GlIcon); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findPipelineEditorMiniGraph = () => wrapper.findComponent(PipelineEditorMiniGraph); - const findPipelineId = () => wrapper.find('[data-testid="pipeline-id"]'); - const findPipelineCommit = () => wrapper.find('[data-testid="pipeline-commit"]'); - const findPipelineErrorMsg = () => wrapper.find('[data-testid="pipeline-error-msg"]'); - const findPipelineLoadingMsg = () => wrapper.find('[data-testid="pipeline-loading-msg"]'); - const findPipelineViewBtn = () => wrapper.find('[data-testid="pipeline-view-btn"]'); - const findStatusIcon = () => wrapper.find('[data-testid="pipeline-status-icon"]'); - - beforeEach(() => { - mockPipelineQuery = jest.fn(); - }); - - afterEach(() => { - mockPipelineQuery.mockReset(); - wrapper.destroy(); - }); - - describe('loading icon', () => { - it('renders while query is being fetched', () => { - createComponentWithApollo(); - - expect(findLoadingIcon().exists()).toBe(true); - expect(findPipelineLoadingMsg().text()).toBe(i18n.fetchLoading); - }); - - it('does not render if query is no longer loading', async () => { - createComponentWithApollo(); - await waitForPromises(); - - expect(findLoadingIcon().exists()).toBe(false); - }); - }); - - describe('when querying data', () => { - describe('when data is set', () => { - beforeEach(async () => { - mockPipelineQuery.mockResolvedValue({ - data: { project: mockProjectPipeline() }, - }); - - createComponentWithApollo(); - await waitForPromises(); - }); - - it('query is called with correct variables', async () => { - expect(mockPipelineQuery).toHaveBeenCalledTimes(1); - expect(mockPipelineQuery).toHaveBeenCalledWith({ - fullPath: mockProjectFullPath, - sha: mockCommitSha, - }); - }); - - it('does not render error', () => { - expect(findPipelineErrorMsg().exists()).toBe(false); - }); - - it('renders pipeline data', () => { - const { - id, - commit: { title }, - detailedStatus: { detailsPath }, - } = mockProjectPipeline().pipeline; - - expect(findStatusIcon().exists()).toBe(true); - expect(findPipelineId().text()).toBe(`#${id.match(/\d+/g)[0]}`); - expect(findPipelineCommit().text()).toBe(`${mockCommitSha}: ${title}`); - expect(findPipelineViewBtn().attributes('href')).toBe(detailsPath); - }); - - it('renders the pipeline mini graph', () => { - expect(findPipelineEditorMiniGraph().exists()).toBe(true); - }); - }); - - describe('when data cannot be fetched', () => { - beforeEach(async () => { - mockPipelineQuery.mockRejectedValue(new Error()); - - createComponentWithApollo(); - await waitForPromises(); - }); - - it('renders error', () => { - expect(findIcon().attributes('name')).toBe('warning-solid'); - expect(findPipelineErrorMsg().text()).toBe(i18n.fetchError); - }); - - it('does not render pipeline data', () => { - expect(findStatusIcon().exists()).toBe(false); - expect(findPipelineId().exists()).toBe(false); - expect(findPipelineCommit().exists()).toBe(false); - expect(findPipelineViewBtn().exists()).toBe(false); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js deleted file mode 100644 index d40a9cc8100..00000000000 --- a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue'; -import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; -import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; -import { PIPELINE_FAILURE } from '~/pipeline_editor/constants'; -import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data'; - -Vue.use(VueApollo); - -describe('Pipeline Status', () => { - let wrapper; - let mockApollo; - let mockLinkedPipelinesQuery; - - const createComponent = ({ hasStages = true, options } = {}) => { - wrapper = shallowMount(PipelineEditorMiniGraph, { - provide: { - dataMethod: 'graphql', - projectFullPath: mockProjectFullPath, - }, - propsData: { - pipeline: mockProjectPipeline({ hasStages }).pipeline, - }, - ...options, - }); - }; - - const createComponentWithApollo = (hasStages = true) => { - const handlers = [[getLinkedPipelinesQuery, mockLinkedPipelinesQuery]]; - mockApollo = createMockApollo(handlers); - - createComponent({ - hasStages, - options: { - apolloProvider: mockApollo, - }, - }); - }; - - const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); - - beforeEach(() => { - mockLinkedPipelinesQuery = jest.fn(); - }); - - afterEach(() => { - mockLinkedPipelinesQuery.mockReset(); - wrapper.destroy(); - }); - - describe('when there are stages', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders pipeline mini graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(true); - }); - }); - - describe('when there are no stages', () => { - beforeEach(() => { - createComponent({ hasStages: false }); - }); - - it('does not render pipeline mini graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(false); - }); - }); - - describe('when querying upstream and downstream pipelines', () => { - describe('when query succeeds', () => { - beforeEach(() => { - mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines()); - createComponentWithApollo(); - }); - - it('should call the query with the correct variables', () => { - expect(mockLinkedPipelinesQuery).toHaveBeenCalledTimes(1); - expect(mockLinkedPipelinesQuery).toHaveBeenCalledWith({ - fullPath: mockProjectFullPath, - iid: mockProjectPipeline().pipeline.iid, - }); - }); - }); - - describe('when query fails', () => { - beforeEach(async () => { - mockLinkedPipelinesQuery.mockRejectedValue(new Error()); - createComponentWithApollo(); - await waitForPromises(); - }); - - it('should emit an error event when query fails', async () => { - expect(wrapper.emitted('showError')).toHaveLength(1); - expect(wrapper.emitted('showError')[0]).toEqual([ - { - type: PIPELINE_FAILURE, - reasons: [wrapper.vm.$options.i18n.linkedPipelinesFetchError], - }, - ]); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js deleted file mode 100644 index 1ad621e6f45..00000000000 --- a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js +++ /dev/null @@ -1,197 +0,0 @@ -import VueApollo from 'vue-apollo'; -import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -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 { - mergeUnwrappedCiConfig, - mockCiYml, - mockLintUnavailableHelpPagePath, - mockYmlHelpPagePath, -} from '../../mock_data'; - -Vue.use(VueApollo); - -describe('Validation segment component', () => { - let wrapper; - - 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, { - apolloProvider: mockApollo, - provide: { - ymlHelpPagePath: mockYmlHelpPagePath, - lintUnavailableHelpPagePath: mockLintUnavailableHelpPagePath, - }, - propsData: { - ciConfig: mergeUnwrappedCiConfig(), - ciFileContent: mockCiYml, - ...props, - }, - }), - ); - }; - - const findIcon = () => wrapper.findComponent(GlIcon); - const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink'); - const findValidationMsg = () => wrapper.findByTestId('validationMsg'); - - afterEach(() => { - wrapper.destroy(); - }); - - it('shows the loading state', () => { - createComponent({ appStatus: EDITOR_APP_STATUS_LOADING }); - - expect(wrapper.text()).toBe(i18n.loading); - }); - - describe('when config is empty', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_EMPTY }); - }); - - it('has check icon', () => { - expect(findIcon().props('name')).toBe('check'); - }); - - it('shows a message for empty state', () => { - expect(findValidationMsg().text()).toBe(i18n.empty); - }); - }); - - describe('when config is valid', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_VALID }); - }); - - it('has check icon', () => { - expect(findIcon().props('name')).toBe('check'); - }); - - it('shows a message for valid state', () => { - expect(findValidationMsg().text()).toContain(i18n.valid); - }); - - it('shows the learn more link', () => { - expect(findLearnMoreLink().attributes('href')).toBe(mockYmlHelpPagePath); - expect(findLearnMoreLink().text()).toBe(i18n.learnMore); - }); - }); - - describe('when config is invalid', () => { - beforeEach(() => { - createComponent({ - appStatus: EDITOR_APP_STATUS_INVALID, - }); - }); - - it('has warning icon', () => { - expect(findIcon().props('name')).toBe('warning-solid'); - }); - - it('has message for invalid state', () => { - expect(findValidationMsg().text()).toBe(i18n.invalid); - }); - - it('shows the learn more link', () => { - expect(findLearnMoreLink().attributes('href')).toBe(mockYmlHelpPagePath); - expect(findLearnMoreLink().text()).toBe('Learn more'); - }); - - describe('with multiple errors', () => { - const firstError = 'First Error'; - const secondError = 'Second Error'; - - beforeEach(() => { - createComponent({ - props: { - ciConfig: mergeUnwrappedCiConfig({ - status: CI_CONFIG_STATUS_INVALID, - errors: [firstError, secondError], - }), - }, - }); - }); - it('shows an invalid state with an error', () => { - // Test the error is shown _and_ the string matches - expect(findValidationMsg().text()).toContain(firstError); - expect(findValidationMsg().text()).toBe( - sprintf(i18n.invalidWithReason, { reason: firstError }), - ); - }); - }); - - describe('with XSS inside the error', () => { - const evilError = '<script>evil();</script>'; - - beforeEach(() => { - createComponent({ - props: { - ciConfig: mergeUnwrappedCiConfig({ - status: CI_CONFIG_STATUS_INVALID, - errors: [evilError], - }), - }, - }); - }); - it('shows an invalid state with an error while preventing XSS', () => { - const { innerHTML } = findValidationMsg().element; - - expect(innerHTML).not.toContain(evilError); - expect(innerHTML).toContain(escape(evilError)); - }); - }); - }); - - 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/lint/ci_lint_results_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js deleted file mode 100644 index 7f89eda4dff..00000000000 --- a/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js +++ /dev/null @@ -1,177 +0,0 @@ -import { GlTableLite, GlLink } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; -import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue'; -import { mockJobs, mockErrors, mockWarnings } from '../../mock_data'; - -describe('CI Lint Results', () => { - let wrapper; - const defaultProps = { - isValid: true, - jobs: mockJobs, - errors: [], - warnings: [], - dryRun: false, - lintHelpPagePath: '/help', - }; - - const createComponent = (props = {}, mountFn = shallowMount) => { - wrapper = mountFn(CiLintResults, { - propsData: { - ...defaultProps, - ...props, - }, - }); - }; - - const findTable = () => wrapper.findComponent(GlTableLite); - const findByTestId = (selector) => () => wrapper.find(`[data-testid="ci-lint-${selector}"]`); - const findAllByTestId = (selector) => () => - wrapper.findAll(`[data-testid="ci-lint-${selector}"]`); - const findLinkToDoc = () => wrapper.findComponent(GlLink); - const findErrors = findByTestId('errors'); - const findWarnings = findByTestId('warnings'); - const findStatus = findByTestId('status'); - const findOnlyExcept = findByTestId('only-except'); - const findLintParameters = findAllByTestId('parameter'); - const findLintValues = findAllByTestId('value'); - const findBeforeScripts = findAllByTestId('before-script'); - const findScripts = findAllByTestId('script'); - const findAfterScripts = findAllByTestId('after-script'); - const filterEmptyScripts = (property) => mockJobs.filter((job) => job[property].length !== 0); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('Empty results', () => { - it('renders with no jobs, errors or warnings defined', () => { - createComponent({ jobs: undefined, errors: undefined, warnings: undefined }, shallowMount); - expect(findTable().exists()).toBe(true); - }); - - it('renders when job has no properties defined', () => { - // job with no attributes such as `tagList` or `environment` - const job = { - stage: 'Stage Name', - name: 'test job', - }; - createComponent({ jobs: [job] }, mount); - - const param = findLintParameters().at(0); - const value = findLintValues().at(0); - - expect(param.text()).toBe(`${job.stage} Job - ${job.name}`); - - // This test should be updated once properties of each job are shown - // See https://gitlab.com/gitlab-org/gitlab/-/issues/291031 - expect(value.text()).toBe(''); - }); - }); - - describe('Invalid results', () => { - beforeEach(() => { - createComponent({ isValid: false, errors: mockErrors, warnings: mockWarnings }, mount); - }); - - it('does not display the table', () => { - expect(findTable().exists()).toBe(false); - }); - - it('displays the invalid status', () => { - expect(findStatus().text()).toContain(`Status: ${wrapper.vm.$options.incorrect.text}`); - expect(findStatus().props('variant')).toBe(wrapper.vm.$options.incorrect.variant); - }); - - it('contains the link to documentation', () => { - expect(findLinkToDoc().text()).toBe('More information'); - expect(findLinkToDoc().attributes('href')).toBe(defaultProps.lintHelpPagePath); - }); - - it('displays the error message', () => { - const [expectedError] = mockErrors; - - expect(findErrors().text()).toBe(expectedError); - }); - - it('displays the warning message', () => { - const [expectedWarning] = mockWarnings; - - expect(findWarnings().exists()).toBe(true); - expect(findWarnings().text()).toContain(expectedWarning); - }); - }); - - describe('Valid results with dry run', () => { - beforeEach(() => { - createComponent({ dryRun: true }, mount); - }); - - it('displays table', () => { - expect(findTable().exists()).toBe(true); - }); - - it('displays the valid status', () => { - expect(findStatus().text()).toContain(wrapper.vm.$options.correct.text); - expect(findStatus().props('variant')).toBe(wrapper.vm.$options.correct.variant); - }); - - it('does not display only/expect values with dry run', () => { - expect(findOnlyExcept().exists()).toBe(false); - }); - - it('contains the link to documentation', () => { - expect(findLinkToDoc().text()).toBe('More information'); - expect(findLinkToDoc().attributes('href')).toBe(defaultProps.lintHelpPagePath); - }); - }); - - describe('Lint results', () => { - beforeEach(() => { - createComponent({}, mount); - }); - - it('formats parameter value', () => { - findLintParameters().wrappers.forEach((job, index) => { - const { stage } = mockJobs[index]; - const { name } = mockJobs[index]; - - expect(job.text()).toBe(`${capitalizeFirstCharacter(stage)} Job - ${name}`); - }); - }); - - it('only shows before scripts when data is present', () => { - expect(findBeforeScripts()).toHaveLength(filterEmptyScripts('beforeScript').length); - }); - - it('only shows script when data is present', () => { - expect(findScripts()).toHaveLength(filterEmptyScripts('script').length); - }); - - it('only shows after script when data is present', () => { - expect(findAfterScripts()).toHaveLength(filterEmptyScripts('afterScript').length); - }); - }); - - describe('Hide Alert', () => { - it('hides alert on success if hide-alert prop is true', async () => { - await createComponent({ dryRun: true, hideAlert: true }, mount); - - expect(findStatus().exists()).toBe(false); - }); - - it('hides alert on error if hide-alert prop is true', async () => { - await createComponent( - { - hideAlert: true, - isValid: false, - errors: mockErrors, - warnings: mockWarnings, - }, - mount, - ); - - expect(findStatus().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js deleted file mode 100644 index 36052a2e16a..00000000000 --- a/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { GlAlert, GlSprintf } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { trimText } from 'helpers/text_helper'; -import CiLintWarnings from '~/pipeline_editor/components/lint/ci_lint_warnings.vue'; - -const warnings = ['warning 1', 'warning 2', 'warning 3']; - -describe('CI lint warnings', () => { - let wrapper; - - const createComponent = (limit = 25) => { - wrapper = mount(CiLintWarnings, { - propsData: { - warnings, - maxWarnings: limit, - }, - }); - }; - - const findWarningAlert = () => wrapper.findComponent(GlAlert); - const findWarnings = () => wrapper.findAll('[data-testid="ci-lint-warning"]'); - const findWarningMessage = () => trimText(wrapper.findComponent(GlSprintf).text()); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('displays the warning alert', () => { - createComponent(); - - expect(findWarningAlert().exists()).toBe(true); - }); - - it('displays all the warnings', () => { - createComponent(); - - expect(findWarnings()).toHaveLength(warnings.length); - }); - - it('shows the correct message when the limit is not passed', () => { - createComponent(); - - expect(findWarningMessage()).toBe(`${warnings.length} warnings found:`); - }); - - it('shows the correct message when the limit is passed', () => { - const limit = 2; - - createComponent(limit); - - expect(findWarningMessage()).toBe(`${warnings.length} warnings found: showing first ${limit}`); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js deleted file mode 100644 index 27707f8b01a..00000000000 --- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js +++ /dev/null @@ -1,342 +0,0 @@ -// TODO - -import { GlAlert, GlBadge, GlLoadingIcon, GlTabs } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import Vue, { nextTick } from 'vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue'; -import CiValidate from '~/pipeline_editor/components/validate/ci_validate.vue'; -import WalkthroughPopover from '~/pipeline_editor/components/popovers/walkthrough_popover.vue'; -import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; -import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue'; -import { - CREATE_TAB, - EDITOR_APP_STATUS_EMPTY, - EDITOR_APP_STATUS_LOADING, - EDITOR_APP_STATUS_INVALID, - EDITOR_APP_STATUS_VALID, - TAB_QUERY_PARAM, - VALIDATE_TAB, - VALIDATE_TAB_BADGE_DISMISSED_KEY, -} from '~/pipeline_editor/constants'; -import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; -import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql'; -import { - mockBlobContentQueryResponse, - mockCiLintPath, - mockCiYml, - mockLintResponse, - mockLintResponseWithoutMerged, -} from '../mock_data'; - -Vue.use(VueApollo); - -Vue.config.ignoredElements = ['gl-emoji']; - -describe('Pipeline editor tabs component', () => { - let wrapper; - const MockTextEditor = { - template: '<div />', - }; - - const createComponent = ({ - listeners = {}, - props = {}, - provide = {}, - appStatus = EDITOR_APP_STATUS_VALID, - mountFn = shallowMount, - options = {}, - } = {}) => { - wrapper = mountFn(PipelineEditorTabs, { - propsData: { - ciConfigData: mockLintResponse, - ciFileContent: mockCiYml, - currentTab: CREATE_TAB, - isNewCiConfigFile: true, - showDrawer: false, - ...props, - }, - data() { - return { - appStatus, - }; - }, - provide: { - ciConfigPath: '/path/to/ci-config', - ciLintPath: mockCiLintPath, - currentBranch: 'main', - projectFullPath: '/path/to/project', - simulatePipelineHelpPagePath: 'path/to/help/page', - validateTabIllustrationPath: 'path/to/svg', - ...provide, - }, - stubs: { - TextEditor: MockTextEditor, - EditorTab, - }, - listeners, - ...options, - }); - }; - - let mockBlobContentData; - let mockApollo; - - const createComponentWithApollo = ({ props, provide = {}, mountFn = shallowMount } = {}) => { - const handlers = [[getBlobContent, mockBlobContentData]]; - mockApollo = createMockApollo(handlers); - - createComponent({ - props, - provide, - mountFn, - options: { - apolloProvider: mockApollo, - }, - }); - }; - - const findEditorTab = () => wrapper.find('[data-testid="editor-tab"]'); - const findMergedTab = () => wrapper.find('[data-testid="merged-tab"]'); - const findValidateTab = () => wrapper.find('[data-testid="validate-tab"]'); - const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]'); - - const findAlert = () => wrapper.findComponent(GlAlert); - const findBadge = () => wrapper.findComponent(GlBadge); - const findCiValidate = () => wrapper.findComponent(CiValidate); - const findGlTabs = () => wrapper.findComponent(GlTabs); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findPipelineGraph = () => wrapper.findComponent(PipelineGraph); - const findTextEditor = () => wrapper.findComponent(MockTextEditor); - const findMergedPreview = () => wrapper.findComponent(CiConfigMergedPreview); - const findWalkthroughPopover = () => wrapper.findComponent(WalkthroughPopover); - - beforeEach(() => { - mockBlobContentData = jest.fn(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('editor tab', () => { - it('displays editor only after the tab is mounted', async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - createComponentWithApollo({ mountFn: mount }); - - expect(findTextEditor().exists()).toBe(false); - - await nextTick(); - - expect(findTextEditor().exists()).toBe(true); - expect(findEditorTab().exists()).toBe(true); - }); - }); - - describe('visualization tab', () => { - describe('while loading', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_LOADING }); - }); - - it('displays a loading icon if the lint query is loading', () => { - expect(findLoadingIcon().exists()).toBe(true); - expect(findPipelineGraph().exists()).toBe(false); - }); - }); - describe('after loading', () => { - beforeEach(() => { - createComponent(); - }); - - it('display the tab and visualization', () => { - expect(findVisualizationTab().exists()).toBe(true); - expect(findPipelineGraph().exists()).toBe(true); - }); - }); - }); - - describe('validate tab', () => { - describe('after loading', () => { - beforeEach(() => { - createComponent(); - }); - - it('displays the tab and the validate component', () => { - expect(findValidateTab().exists()).toBe(true); - expect(findCiValidate().exists()).toBe(true); - }); - }); - - describe('NEW badge', () => { - describe('default', () => { - beforeEach(() => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - createComponentWithApollo({ - mountFn: mount, - props: { - currentTab: VALIDATE_TAB, - }, - }); - }); - - it('renders badge by default', () => { - expect(findBadge().exists()).toBe(true); - expect(findBadge().text()).toBe(wrapper.vm.$options.i18n.new); - }); - - it('hides badge when moving away from the validate tab', async () => { - expect(findBadge().exists()).toBe(true); - - await findEditorTab().vm.$emit('click'); - - expect(findBadge().exists()).toBe(false); - }); - }); - - describe('if badge has been dismissed before', () => { - beforeEach(() => { - localStorage.setItem(VALIDATE_TAB_BADGE_DISMISSED_KEY, 'true'); - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - createComponentWithApollo({ mountFn: mount }); - }); - - it('does not render badge if it has been dismissed before', () => { - expect(findBadge().exists()).toBe(false); - }); - }); - }); - }); - - describe('merged tab', () => { - describe('while loading', () => { - beforeEach(() => { - createComponent({ appStatus: EDITOR_APP_STATUS_LOADING }); - }); - - it('displays a loading icon if the lint query is loading', () => { - expect(findLoadingIcon().exists()).toBe(true); - }); - }); - - describe('when there is a fetch error', () => { - beforeEach(() => { - createComponent({ props: { ciConfigData: mockLintResponseWithoutMerged } }); - }); - - it('show an error message', () => { - expect(findAlert().exists()).toBe(true); - expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts.loadMergedYaml); - }); - - it('does not render the `merged_preview` component', () => { - expect(findMergedPreview().exists()).toBe(false); - }); - }); - - describe('after loading', () => { - beforeEach(() => { - createComponent(); - }); - - it('display the tab and the merged preview component', () => { - expect(findMergedTab().exists()).toBe(true); - expect(findMergedPreview().exists()).toBe(true); - }); - }); - }); - - describe('show tab content based on status', () => { - it.each` - appStatus | editor | viz | validate | merged - ${undefined} | ${true} | ${true} | ${true} | ${true} - ${EDITOR_APP_STATUS_EMPTY} | ${true} | ${false} | ${true} | ${false} - ${EDITOR_APP_STATUS_INVALID} | ${true} | ${false} | ${true} | ${true} - ${EDITOR_APP_STATUS_VALID} | ${true} | ${true} | ${true} | ${true} - `( - 'when status is $appStatus, we show - editor:$editor | viz:$viz | validate:$validate | merged:$merged', - ({ appStatus, editor, viz, validate, merged }) => { - createComponent({ appStatus }); - - expect(findTextEditor().exists()).toBe(editor); - expect(findPipelineGraph().exists()).toBe(viz); - expect(findValidateTab().exists()).toBe(validate); - expect(findMergedPreview().exists()).toBe(merged); - }, - ); - }); - - describe('default tab based on url query param', () => { - const gitlabUrl = 'https://gitlab.test/ci/editor/'; - const matchObject = { - hostname: 'gitlab.test', - pathname: '/ci/editor/', - search: '', - }; - - it(`is ${CREATE_TAB} if the query param ${TAB_QUERY_PARAM} is not present`, () => { - setWindowLocation(gitlabUrl); - createComponent(); - - expect(window.location).toMatchObject(matchObject); - }); - - it(`is ${CREATE_TAB} tab if the query param ${TAB_QUERY_PARAM} is invalid`, () => { - const queryValue = 'FOO'; - setWindowLocation(`${gitlabUrl}?${TAB_QUERY_PARAM}=${queryValue}`); - createComponent(); - - // If the query param remains unchanged, then we have ignored it. - expect(window.location).toMatchObject({ - ...matchObject, - search: `?${TAB_QUERY_PARAM}=${queryValue}`, - }); - }); - }); - - describe('glTabs', () => { - beforeEach(() => { - createComponent(); - }); - - it('passes the `sync-active-tab-with-query-params` prop', () => { - expect(findGlTabs().props('syncActiveTabWithQueryParams')).toBe(true); - }); - }); - - describe('pipeline editor walkthrough', () => { - describe('when isNewCiConfigFile prop is true (default)', () => { - beforeEach(() => { - createComponent(); - }); - - it('shows walkthrough popover', async () => { - expect(findWalkthroughPopover().exists()).toBe(true); - }); - }); - - describe('when isNewCiConfigFile prop is false', () => { - it('does not show walkthrough popover', async () => { - createComponent({ props: { isNewCiConfigFile: false } }); - expect(findWalkthroughPopover().exists()).toBe(false); - }); - }); - }); - - it('sets listeners on walkthrough popover', async () => { - const handler = jest.fn(); - - createComponent({ - listeners: { - event: handler, - }, - }); - await nextTick(); - - findWalkthroughPopover().vm.$emit('event'); - - expect(handler).toHaveBeenCalled(); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js deleted file mode 100644 index 98ce3f6ea40..00000000000 --- a/spec/frontend/pipeline_editor/components/popovers/file_tree_popover_spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { nextTick } from 'vue'; -import { GlLink, GlPopover, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import FileTreePopover from '~/pipeline_editor/components/popovers/file_tree_popover.vue'; -import { FILE_TREE_POPOVER_DISMISSED_KEY } from '~/pipeline_editor/constants'; -import { mockIncludesHelpPagePath } from '../../mock_data'; - -describe('FileTreePopover component', () => { - let wrapper; - - const findPopover = () => wrapper.findComponent(GlPopover); - const findLink = () => findPopover().findComponent(GlLink); - - const createComponent = ({ stubs } = {}) => { - wrapper = shallowMount(FileTreePopover, { - provide: { - includesHelpPagePath: mockIncludesHelpPagePath, - }, - stubs, - }); - }; - - afterEach(() => { - localStorage.clear(); - wrapper.destroy(); - }); - - describe('default', () => { - beforeEach(async () => { - createComponent({ stubs: { GlSprintf } }); - }); - - it('renders dismissable popover', async () => { - expect(findPopover().exists()).toBe(true); - - findPopover().vm.$emit('close-button-clicked'); - await nextTick(); - - expect(findPopover().exists()).toBe(false); - }); - - it('renders learn more link', () => { - expect(findLink().exists()).toBe(true); - expect(findLink().attributes('href')).toBe(mockIncludesHelpPagePath); - }); - }); - - describe('when popover has already been dismissed before', () => { - it('does not render popover', async () => { - localStorage.setItem(FILE_TREE_POPOVER_DISMISSED_KEY, 'true'); - createComponent(); - - expect(findPopover().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js deleted file mode 100644 index 97f785a71bc..00000000000 --- a/spec/frontend/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import { GlLink, GlSprintf } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import ValidatePopover from '~/pipeline_editor/components/popovers/validate_pipeline_popover.vue'; -import { VALIDATE_TAB_FEEDBACK_URL } from '~/pipeline_editor/constants'; -import { mockSimulatePipelineHelpPagePath } from '../../mock_data'; - -describe('ValidatePopover component', () => { - let wrapper; - - const createComponent = ({ stubs } = {}) => { - wrapper = shallowMountExtended(ValidatePopover, { - provide: { - simulatePipelineHelpPagePath: mockSimulatePipelineHelpPagePath, - }, - stubs, - }); - }; - - const findHelpLink = () => wrapper.findByTestId('help-link'); - const findFeedbackLink = () => wrapper.findByTestId('feedback-link'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - beforeEach(async () => { - createComponent({ - stubs: { GlLink, GlSprintf }, - }); - }); - - it('renders help link', () => { - expect(findHelpLink().exists()).toBe(true); - expect(findHelpLink().attributes('href')).toBe(mockSimulatePipelineHelpPagePath); - }); - - it('renders feedback link', () => { - expect(findFeedbackLink().exists()).toBe(true); - expect(findFeedbackLink().attributes('href')).toBe(VALIDATE_TAB_FEEDBACK_URL); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js b/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js deleted file mode 100644 index b86c82850c5..00000000000 --- a/spec/frontend/pipeline_editor/components/popovers/walkthrough_popover_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import { mount, shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import WalkthroughPopover from '~/pipeline_editor/components/popovers/walkthrough_popover.vue'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; - -Vue.config.ignoredElements = ['gl-emoji']; - -describe('WalkthroughPopover component', () => { - let wrapper; - - const createComponent = (mountFn = shallowMount) => { - return extendedWrapper(mountFn(WalkthroughPopover)); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('CTA button clicked', () => { - beforeEach(async () => { - wrapper = createComponent(mount); - await wrapper.findByTestId('ctaBtn').trigger('click'); - }); - - it('emits "walkthrough-popover-cta-clicked" event', async () => { - expect(wrapper.emitted()['walkthrough-popover-cta-clicked']).toHaveLength(1); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js b/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js deleted file mode 100644 index 44fda2812d8..00000000000 --- a/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import ConfirmDialog from '~/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue'; - -describe('pipeline_editor/components/ui/confirm_unsaved_changes_dialog', () => { - let beforeUnloadEvent; - let setDialogContent; - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(ConfirmDialog, { - propsData, - }); - }; - - beforeEach(() => { - beforeUnloadEvent = new Event('beforeunload'); - jest.spyOn(beforeUnloadEvent, 'preventDefault'); - setDialogContent = jest.spyOn(beforeUnloadEvent, 'returnValue', 'set'); - }); - - afterEach(() => { - beforeUnloadEvent.preventDefault.mockRestore(); - setDialogContent.mockRestore(); - wrapper.destroy(); - }); - - it('shows confirmation dialog when there are unsaved changes', () => { - createComponent({ hasUnsavedChanges: true }); - window.dispatchEvent(beforeUnloadEvent); - - expect(beforeUnloadEvent.preventDefault).toHaveBeenCalled(); - expect(setDialogContent).toHaveBeenCalledWith(''); - }); - - it('does not show confirmation dialog when there are no unsaved changes', () => { - createComponent({ hasUnsavedChanges: false }); - window.dispatchEvent(beforeUnloadEvent); - - expect(beforeUnloadEvent.preventDefault).not.toHaveBeenCalled(); - expect(setDialogContent).not.toHaveBeenCalled(); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js deleted file mode 100644 index 24f27e8c5fb..00000000000 --- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js +++ /dev/null @@ -1,200 +0,0 @@ -import { GlAlert, GlBadge, GlTabs } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue'; - -const mockContent1 = 'MOCK CONTENT 1'; -const mockContent2 = 'MOCK CONTENT 2'; - -const MockSourceEditor = { - template: '<div>EDITOR</div>', -}; - -describe('~/pipeline_editor/components/ui/editor_tab.vue', () => { - let wrapper; - let mockChildMounted = jest.fn(); - - const MockChild = { - props: ['content'], - template: '<div>{{content}}</div>', - mounted() { - mockChildMounted(this.content); - }, - }; - - const MockTabbedContent = { - components: { - EditorTab, - GlTabs, - MockChild, - }, - template: ` - <gl-tabs> - <editor-tab title="Tab 1" :title-link-attributes="{ 'data-testid': 'tab1-btn' }" :lazy="true"> - <mock-child content="${mockContent1}"/> - </editor-tab> - <editor-tab title="Tab 2" :title-link-attributes="{ 'data-testid': 'tab2-btn' }" :lazy="true" badge-title="NEW"> - <mock-child content="${mockContent2}"/> - </editor-tab> - </gl-tabs> - `, - }; - - const createMockedWrapper = () => { - wrapper = mount(MockTabbedContent); - }; - - const createWrapper = ({ props } = {}) => { - wrapper = mount(EditorTab, { - propsData: { - title: 'Tab 1', - ...props, - }, - slots: { - default: MockSourceEditor, - }, - }); - }; - - const findSlotComponent = () => wrapper.findComponent(MockSourceEditor); - const findAlert = () => wrapper.findComponent(GlAlert); - const findBadges = () => wrapper.findAllComponents(GlBadge); - - beforeEach(() => { - mockChildMounted = jest.fn(); - }); - - it('tabs are mounted lazily', async () => { - createMockedWrapper(); - - expect(mockChildMounted).toHaveBeenCalledTimes(0); - }); - - it('first tab is only mounted after nextTick', async () => { - createMockedWrapper(); - - await nextTick(); - - expect(mockChildMounted).toHaveBeenCalledTimes(1); - expect(mockChildMounted).toHaveBeenCalledWith(mockContent1); - }); - - 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 | 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, isUnavailable:$isUnavailable and isInvalid:$isInvalid', - ({ isEmpty, isUnavailable, isInvalid, showSlotComponent }) => { - createWrapper({ - props: { isEmpty, isUnavailable, isInvalid }, - }); - expect(findSlotComponent().exists()).toBe(showSlotComponent); - expect(findAlert().exists()).toBe(!showSlotComponent); - }, - ); - }); - - describe('user interaction', () => { - const clickTab = async (testid) => { - wrapper.find(`[data-testid="${testid}"]`).trigger('click'); - await nextTick(); - }; - - beforeEach(() => { - createMockedWrapper(); - }); - - it('mounts a tab once after selecting it', async () => { - await clickTab('tab2-btn'); - - expect(mockChildMounted).toHaveBeenCalledTimes(2); - expect(mockChildMounted).toHaveBeenNthCalledWith(1, mockContent1); - expect(mockChildMounted).toHaveBeenNthCalledWith(2, mockContent2); - }); - - it('mounts each tab once after selecting each', async () => { - await clickTab('tab2-btn'); - await clickTab('tab1-btn'); - await clickTab('tab2-btn'); - - expect(mockChildMounted).toHaveBeenCalledTimes(2); - expect(mockChildMounted).toHaveBeenNthCalledWith(1, mockContent1); - expect(mockChildMounted).toHaveBeenNthCalledWith(2, mockContent2); - }); - }); - - describe('valid state', () => { - beforeEach(() => { - createMockedWrapper(); - }); - - it('renders correct number of badges', async () => { - expect(findBadges()).toHaveLength(1); - expect(findBadges().at(0).text()).toBe('NEW'); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js deleted file mode 100644 index c76c3460e99..00000000000 --- a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js +++ /dev/null @@ -1,92 +0,0 @@ -import { GlButton, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue'; -import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue'; - -describe('Pipeline editor empty state', () => { - let wrapper; - const defaultProvide = { - emptyStateIllustrationPath: 'my/svg/path', - usesExternalConfig: false, - }; - - const createComponent = ({ provide } = {}) => { - wrapper = shallowMount(PipelineEditorEmptyState, { - provide: { ...defaultProvide, ...provide }, - }); - }; - - const findFileNav = () => wrapper.findComponent(PipelineEditorFileNav); - const findSvgImage = () => wrapper.find('img'); - const findTitle = () => wrapper.find('h1'); - const findExternalCiInstructions = () => wrapper.find('p'); - const findConfirmButton = () => wrapper.findComponent(GlButton); - const findDescription = () => wrapper.findComponent(GlSprintf); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when project uses an external CI config', () => { - beforeEach(() => { - createComponent({ - provide: { usesExternalConfig: true }, - }); - }); - - it('renders an svg image', () => { - expect(findSvgImage().exists()).toBe(true); - }); - - it('renders the correct title and instructions', () => { - expect(findTitle().exists()).toBe(true); - expect(findExternalCiInstructions().exists()).toBe(true); - - expect(findExternalCiInstructions().html()).toContain( - wrapper.vm.$options.i18n.externalCiInstructions, - ); - expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.externalCiNote); - }); - - it('does not render the CTA button', () => { - expect(findConfirmButton().exists()).toBe(false); - }); - }); - - describe('when project uses an accessible CI config', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders an svg image', () => { - expect(findSvgImage().exists()).toBe(true); - }); - - it('renders a title', () => { - expect(findTitle().exists()).toBe(true); - expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title); - }); - - it('renders a description', () => { - expect(findDescription().exists()).toBe(true); - expect(findDescription().html()).toContain(wrapper.vm.$options.i18n.body); - }); - - it('renders the file nav', () => { - expect(findFileNav().exists()).toBe(true); - }); - - it('renders a CTA button', () => { - expect(findConfirmButton().exists()).toBe(true); - expect(findConfirmButton().text()).toBe(wrapper.vm.$options.i18n.btnText); - }); - - it('emits an event when clicking on the CTA', async () => { - const expectedEvent = 'createEmptyConfigFile'; - expect(wrapper.emitted(expectedEvent)).toBeUndefined(); - - await findConfirmButton().vm.$emit('click'); - expect(wrapper.emitted(expectedEvent)).toHaveLength(1); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js deleted file mode 100644 index d9ecee31e83..00000000000 --- a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js +++ /dev/null @@ -1,149 +0,0 @@ -import { GlAlert } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import { TEST_HOST } from 'helpers/test_constants'; -import CodeSnippetAlert from '~/pipeline_editor/components/code_snippet_alert/code_snippet_alert.vue'; -import { CODE_SNIPPET_SOURCES } from '~/pipeline_editor/components/code_snippet_alert/constants'; -import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue'; -import { - COMMIT_FAILURE, - COMMIT_SUCCESS, - COMMIT_SUCCESS_WITH_REDIRECT, - DEFAULT_FAILURE, - DEFAULT_SUCCESS, - LOAD_FAILURE_UNKNOWN, - PIPELINE_FAILURE, -} from '~/pipeline_editor/constants'; - -beforeEach(() => { - setWindowLocation(TEST_HOST); -}); - -describe('Pipeline Editor messages', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(PipelineEditorMessages, { - propsData: props, - }); - }; - - const findCodeSnippetAlert = () => wrapper.findComponent(CodeSnippetAlert); - const findAlert = () => wrapper.findComponent(GlAlert); - - describe('success alert', () => { - it('shows a message for successful commit type', () => { - createComponent({ successType: COMMIT_SUCCESS, showSuccess: true }); - - expect(findAlert().text()).toBe(wrapper.vm.$options.success[COMMIT_SUCCESS]); - }); - - it('shows a message for successful commit with redirect type', () => { - createComponent({ successType: COMMIT_SUCCESS_WITH_REDIRECT, showSuccess: true }); - - expect(findAlert().text()).toBe(wrapper.vm.$options.success[COMMIT_SUCCESS_WITH_REDIRECT]); - }); - - it('does not show alert when there is a successType but visibility is off', () => { - createComponent({ successType: COMMIT_SUCCESS, showSuccess: false }); - - expect(findAlert().exists()).toBe(false); - }); - - it('shows a success alert with default copy if `showSuccess` is true and the `successType` is not valid,', () => { - createComponent({ successType: 'random', showSuccess: true }); - - expect(findAlert().text()).toBe(wrapper.vm.$options.success[DEFAULT_SUCCESS]); - }); - - it('emit `hide-success` event when clicking on the dismiss button', async () => { - const expectedEvent = 'hide-success'; - - createComponent({ successType: COMMIT_SUCCESS, showSuccess: true }); - expect(wrapper.emitted(expectedEvent)).not.toBeDefined(); - - await findAlert().vm.$emit('dismiss'); - - expect(wrapper.emitted(expectedEvent)).toBeDefined(); - }); - }); - - describe('failure alert', () => { - it.each` - failureType | message | expectedFailureType - ${COMMIT_FAILURE} | ${'failed commit'} | ${COMMIT_FAILURE} - ${LOAD_FAILURE_UNKNOWN} | ${'loading failure'} | ${LOAD_FAILURE_UNKNOWN} - ${PIPELINE_FAILURE} | ${'pipeline failure'} | ${PIPELINE_FAILURE} - ${'random'} | ${'error without a specified type'} | ${DEFAULT_FAILURE} - `('shows a message for $message', ({ failureType, expectedFailureType }) => { - createComponent({ failureType, showFailure: true }); - - expect(findAlert().text()).toBe(wrapper.vm.$options.errors[expectedFailureType]); - }); - - it('show failure reasons when there are some', () => { - const failureReasons = ['There was a problem', 'ouppps']; - createComponent({ failureType: COMMIT_FAILURE, failureReasons, showFailure: true }); - - expect(wrapper.html()).toContain(failureReasons[0]); - expect(wrapper.html()).toContain(failureReasons[1]); - }); - - it('does not show a message for error with a disabled visibility', () => { - createComponent({ failureType: 'random', showFailure: false }); - - expect(findAlert().exists()).toBe(false); - }); - - it('emit `hide-failure` event when clicking on the dismiss button', async () => { - const expectedEvent = 'hide-failure'; - - createComponent({ failureType: COMMIT_FAILURE, showFailure: true }); - expect(wrapper.emitted(expectedEvent)).not.toBeDefined(); - - await findAlert().vm.$emit('dismiss'); - - expect(wrapper.emitted(expectedEvent)).toBeDefined(); - }); - }); - - describe('code snippet alert', () => { - const setCodeSnippetUrlParam = (value) => { - setWindowLocation(`${TEST_HOST}/?code_snippet_copied_from=${value}`); - }; - - it('does not show by default', () => { - createComponent(); - - expect(findCodeSnippetAlert().exists()).toBe(false); - }); - - it.each(CODE_SNIPPET_SOURCES)('shows if URL param is %s, and cleans up URL', (source) => { - jest.spyOn(window.history, 'replaceState'); - setCodeSnippetUrlParam(source); - createComponent(); - - expect(findCodeSnippetAlert().exists()).toBe(true); - expect(window.history.replaceState).toHaveBeenCalledWith({}, document.title, `${TEST_HOST}/`); - }); - - it('does not show if URL param is invalid', () => { - setCodeSnippetUrlParam('foo_bar'); - createComponent(); - - expect(findCodeSnippetAlert().exists()).toBe(false); - }); - - it('disappears on dismiss', async () => { - setCodeSnippetUrlParam('api_fuzzing'); - createComponent(); - const alert = findCodeSnippetAlert(); - - expect(alert.exists()).toBe(true); - - await alert.vm.$emit('dismiss'); - - expect(alert.exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js b/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js deleted file mode 100644 index 09d4f9736ad..00000000000 --- a/spec/frontend/pipeline_editor/components/validate/ci_validate_spec.js +++ /dev/null @@ -1,314 +0,0 @@ -import { GlAlert, GlDropdown, GlIcon, GlLoadingIcon, GlPopover } from '@gitlab/ui'; -import { nextTick } from 'vue'; -import { createLocalVue } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue'; -import CiValidate, { i18n } from '~/pipeline_editor/components/validate/ci_validate.vue'; -import ValidatePipelinePopover from '~/pipeline_editor/components/popovers/validate_pipeline_popover.vue'; -import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql'; -import lintCIMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql'; -import { pipelineEditorTrackingOptions } from '~/pipeline_editor/constants'; -import { - mockBlobContentQueryResponse, - mockCiLintPath, - mockCiYml, - mockSimulatePipelineHelpPagePath, -} from '../../mock_data'; -import { mockLintDataError, mockLintDataValid } from '../../../ci_lint/mock_data'; - -const localVue = createLocalVue(); -localVue.use(VueApollo); - -describe('Pipeline Editor Validate Tab', () => { - let wrapper; - let mockApollo; - let mockBlobContentData; - let trackingSpy; - - const createComponent = ({ - props, - stubs, - options, - isBlobLoading = false, - isSimulationLoading = false, - } = {}) => { - wrapper = shallowMountExtended(CiValidate, { - propsData: { - ciFileContent: mockCiYml, - ...props, - }, - provide: { - ciConfigPath: '/path/to/ci-config', - ciLintPath: mockCiLintPath, - currentBranch: 'main', - projectFullPath: '/path/to/project', - validateTabIllustrationPath: '/path/to/img', - simulatePipelineHelpPagePath: mockSimulatePipelineHelpPagePath, - }, - stubs, - mocks: { - $apollo: { - queries: { - initialBlobContent: { - loading: isBlobLoading, - }, - }, - mutations: { - lintCiMutation: { - loading: isSimulationLoading, - }, - }, - }, - }, - ...options, - }); - }; - - const createComponentWithApollo = ({ props, stubs } = {}) => { - const handlers = [[getBlobContent, mockBlobContentData]]; - mockApollo = createMockApollo(handlers); - - createComponent({ - props, - stubs, - options: { - localVue, - apolloProvider: mockApollo, - mocks: {}, - }, - }); - }; - - const findAlert = () => wrapper.findComponent(GlAlert); - const findCancelBtn = () => wrapper.findByTestId('cancel-simulation'); - const findContentChangeStatus = () => wrapper.findByTestId('content-status'); - const findCta = () => wrapper.findByTestId('simulate-pipeline-button'); - const findDisabledCtaTooltip = () => wrapper.findByTestId('cta-tooltip'); - const findHelpIcon = () => wrapper.findComponent(GlIcon); - const findIllustration = () => wrapper.findByRole('img'); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findPipelineSource = () => wrapper.findComponent(GlDropdown); - const findPopover = () => wrapper.findComponent(GlPopover); - const findCiLintResults = () => wrapper.findComponent(CiLintResults); - const findResultsCta = () => wrapper.findByTestId('resimulate-pipeline-button'); - - beforeEach(() => { - mockBlobContentData = jest.fn(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('while initial CI content is loading', () => { - beforeEach(() => { - createComponent({ isBlobLoading: true }); - }); - - it('renders disabled CTA with tooltip', () => { - expect(findCta().props('disabled')).toBe(true); - expect(findDisabledCtaTooltip().exists()).toBe(true); - }); - }); - - describe('after initial CI content is loaded', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - await createComponentWithApollo({ stubs: { GlPopover, ValidatePipelinePopover } }); - }); - - it('renders disabled pipeline source dropdown', () => { - expect(findPipelineSource().exists()).toBe(true); - expect(findPipelineSource().attributes('text')).toBe(i18n.pipelineSourceDefault); - expect(findPipelineSource().props('disabled')).toBe(true); - }); - - it('renders enabled CTA without tooltip', () => { - expect(findCta().exists()).toBe(true); - expect(findCta().props('disabled')).toBe(false); - expect(findDisabledCtaTooltip().exists()).toBe(false); - }); - - it('popover is set to render when hovering over help icon', () => { - expect(findPopover().props('target')).toBe(findHelpIcon().attributes('id')); - expect(findPopover().props('triggers')).toBe('hover focus'); - }); - }); - - describe('simulating the pipeline', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - await createComponentWithApollo(); - - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid); - }); - - afterEach(() => { - unmockTracking(); - }); - - it('tracks the simulation event', () => { - const { - label, - actions: { simulatePipeline }, - } = pipelineEditorTrackingOptions; - findCta().vm.$emit('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, simulatePipeline, { label }); - }); - - it('renders loading state while simulation is ongoing', async () => { - findCta().vm.$emit('click'); - await nextTick(); - - expect(findLoadingIcon().exists()).toBe(true); - expect(findCancelBtn().exists()).toBe(true); - expect(findCta().props('loading')).toBe(true); - }); - - it('calls mutation with the correct input', async () => { - await findCta().vm.$emit('click'); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: lintCIMutation, - variables: { - dry: true, - content: mockCiYml, - endpoint: mockCiLintPath, - }, - }); - }); - - describe('when results are successful', () => { - beforeEach(async () => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid); - await findCta().vm.$emit('click'); - }); - - it('renders success alert', () => { - expect(findAlert().exists()).toBe(true); - expect(findAlert().attributes('variant')).toBe('success'); - expect(findAlert().attributes('title')).toBe(i18n.successAlertTitle); - }); - - it('does not render content change status or CTA for results page', () => { - expect(findContentChangeStatus().exists()).toBe(false); - expect(findResultsCta().exists()).toBe(false); - }); - - it('renders CI lint results with correct props', () => { - expect(findCiLintResults().exists()).toBe(true); - expect(findCiLintResults().props()).toMatchObject({ - dryRun: true, - hideAlert: true, - isValid: true, - jobs: mockLintDataValid.data.lintCI.jobs, - }); - }); - }); - - describe('when results have errors', () => { - beforeEach(async () => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataError); - await findCta().vm.$emit('click'); - }); - - it('renders error alert', () => { - expect(findAlert().exists()).toBe(true); - expect(findAlert().attributes('variant')).toBe('danger'); - expect(findAlert().attributes('title')).toBe(i18n.errorAlertTitle); - }); - - it('renders CI lint results with correct props', () => { - expect(findCiLintResults().exists()).toBe(true); - expect(findCiLintResults().props()).toMatchObject({ - dryRun: true, - hideAlert: true, - isValid: false, - errors: mockLintDataError.data.lintCI.errors, - warnings: mockLintDataError.data.lintCI.warnings, - }); - }); - }); - }); - - describe('when CI content has changed after a simulation', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - await createComponentWithApollo(); - - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid); - await findCta().vm.$emit('click'); - }); - - afterEach(() => { - unmockTracking(); - }); - - it('tracks the second simulation event', async () => { - const { - label, - actions: { resimulatePipeline }, - } = pipelineEditorTrackingOptions; - - await wrapper.setProps({ ciFileContent: 'new yaml content' }); - findResultsCta().vm.$emit('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, resimulatePipeline, { label }); - }); - - it('renders content change status', async () => { - await wrapper.setProps({ ciFileContent: 'new yaml content' }); - - expect(findContentChangeStatus().exists()).toBe(true); - expect(findResultsCta().exists()).toBe(true); - }); - - it('calls mutation with new content', async () => { - await wrapper.setProps({ ciFileContent: 'new yaml content' }); - await findResultsCta().vm.$emit('click'); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(2); - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: lintCIMutation, - variables: { - dry: true, - content: 'new yaml content', - endpoint: mockCiLintPath, - }, - }); - }); - }); - - describe('canceling a simulation', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - await createComponentWithApollo(); - }); - - it('returns to init state', async () => { - // init state - expect(findIllustration().exists()).toBe(true); - expect(findCiLintResults().exists()).toBe(false); - - // mutations should have successful results - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid); - findCta().vm.$emit('click'); - await nextTick(); - - // cancel before simulation succeeds - expect(findCancelBtn().exists()).toBe(true); - await findCancelBtn().vm.$emit('click'); - - // should still render init state - expect(findIllustration().exists()).toBe(true); - expect(findCiLintResults().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap b/spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap deleted file mode 100644 index ee5a3cb288f..00000000000 --- a/spec/frontend/pipeline_editor/graphql/__snapshots__/resolvers_spec.js.snap +++ /dev/null @@ -1,73 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`~/pipeline_editor/graphql/resolvers Mutation lintCI lint data is as expected 1`] = ` -Object { - "__typename": "CiLintContent", - "errors": Array [], - "jobs": Array [ - Object { - "__typename": "CiLintJob", - "afterScript": Array [ - "echo 'after script 1", - ], - "allowFailure": false, - "beforeScript": Array [ - "echo 'before script 1'", - ], - "environment": "prd", - "except": Object { - "refs": Array [ - "main@gitlab-org/gitlab", - "/^release/.*$/@gitlab-org/gitlab", - ], - }, - "name": "job_1", - "only": null, - "script": Array [ - "echo 'script 1'", - ], - "stage": "test", - "tags": Array [ - "tag 1", - ], - "when": "on_success", - }, - Object { - "__typename": "CiLintJob", - "afterScript": Array [ - "echo 'after script 2", - ], - "allowFailure": true, - "beforeScript": Array [ - "echo 'before script 2'", - ], - "environment": "stg", - "except": Object { - "refs": Array [ - "main@gitlab-org/gitlab", - "/^release/.*$/@gitlab-org/gitlab", - ], - }, - "name": "job_2", - "only": Object { - "__typename": "CiLintJobOnlyPolicy", - "refs": Array [ - "web", - "chat", - "pushes", - ], - }, - "script": Array [ - "echo 'script 2'", - ], - "stage": "test", - "tags": Array [ - "tag 2", - ], - "when": "on_success", - }, - ], - "valid": true, - "warnings": Array [], -} -`; diff --git a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js deleted file mode 100644 index 76ae96c623a..00000000000 --- a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; -import { resolvers } from '~/pipeline_editor/graphql/resolvers'; -import { mockLintResponse } from '../mock_data'; - -jest.mock('~/api', () => { - return { - getRawFile: jest.fn(), - }; -}); - -describe('~/pipeline_editor/graphql/resolvers', () => { - describe('Mutation', () => { - describe('lintCI', () => { - let mock; - let result; - - const endpoint = '/ci/lint'; - - beforeEach(async () => { - mock = new MockAdapter(axios); - mock.onPost(endpoint).reply(httpStatus.OK, mockLintResponse); - - result = await resolvers.Mutation.lintCI(null, { - endpoint, - content: 'content', - dry_run: true, - }); - }); - - afterEach(() => { - mock.restore(); - }); - - /* eslint-disable no-underscore-dangle */ - it('lint data has correct type names', async () => { - expect(result.__typename).toBe('CiLintContent'); - - expect(result.jobs[0].__typename).toBe('CiLintJob'); - expect(result.jobs[1].__typename).toBe('CiLintJob'); - - expect(result.jobs[1].only.__typename).toBe('CiLintJobOnlyPolicy'); - }); - /* eslint-enable no-underscore-dangle */ - - it('lint data is as expected', () => { - expect(result).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js deleted file mode 100644 index 2ea580b7b53..00000000000 --- a/spec/frontend/pipeline_editor/mock_data.js +++ /dev/null @@ -1,541 +0,0 @@ -import { CI_CONFIG_STATUS_INVALID, CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants'; -import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; - -export const mockProjectNamespace = 'user1'; -export const mockProjectPath = 'project1'; -export const mockProjectFullPath = `${mockProjectNamespace}/${mockProjectPath}`; -export const mockDefaultBranch = 'main'; -export const mockNewBranch = 'new-branch'; -export const mockNewMergeRequestPath = '/-/merge_requests/new'; -export const mockCiLintPath = '/-/ci/lint'; -export const mockCommitSha = 'aabbccdd'; -export const mockCommitNextSha = 'eeffgghh'; -export const mockIncludesHelpPagePath = '/-/includes/help'; -export const mockLintHelpPagePath = '/-/lint-help'; -export const mockLintUnavailableHelpPagePath = '/-/pipeline-editor/troubleshoot'; -export const mockSimulatePipelineHelpPagePath = '/-/simulate-pipeline-help'; -export const mockYmlHelpPagePath = '/-/yml-help'; -export const mockCommitMessage = 'My commit message'; - -export const mockCiConfigPath = '.gitlab-ci.yml'; -export const mockCiYml = ` -stages: - - test - - build - -job_test_1: - stage: test - script: - - echo "test 1" - -job_test_2: - stage: test - script: - - echo "test 2" - -job_build: - stage: build - script: - - echo "build" - needs: ["job_test_2"] -`; - -export const mockCiTemplateQueryResponse = { - data: { - project: { - id: 'project-1', - ciTemplate: { - content: mockCiYml, - }, - }, - }, -}; - -export const mockBlobContentQueryResponse = { - data: { - project: { - id: 'project-1', - repository: { blobs: { nodes: [{ id: 'blob-1', rawBlob: mockCiYml }] } }, - }, - }, -}; - -export const mockBlobContentQueryResponseNoCiFile = { - data: { - project: { id: 'project-1', repository: { blobs: { nodes: [] } } }, - }, -}; - -export const mockBlobContentQueryResponseEmptyCiFile = { - data: { - project: { id: 'project-1', repository: { blobs: { nodes: [{ rawBlob: '' }] } } }, - }, -}; - -const mockJobFields = { - beforeScript: [], - afterScript: [], - environment: null, - allowFailure: false, - tags: [], - when: 'on_success', - only: { refs: ['branches', 'tags'], __typename: 'CiJobLimitType' }, - except: null, - needs: { nodes: [], __typename: 'CiConfigNeedConnection' }, - __typename: 'CiConfigJob', -}; - -export const mockIncludesWithBlob = { - location: 'test-include.yml', - type: 'local', - blob: - 'http://gdk.test:3000/root/upstream/-/blob/dd54f00bb3645f8ddce7665d2ffb3864540399cb/test-include.yml', - raw: - 'http://gdk.test:3000/root/upstream/-/raw/dd54f00bb3645f8ddce7665d2ffb3864540399cb/test-include.yml', - __typename: 'CiConfigInclude', -}; - -export const mockDefaultIncludes = { - location: 'npm.gitlab-ci.yml', - type: 'template', - blob: null, - raw: - 'https://gitlab.com/gitlab-org/gitlab/-/raw/master/lib/gitlab/ci/templates/npm.gitlab-ci.yml', - __typename: 'CiConfigInclude', -}; - -export const mockIncludes = [ - mockDefaultIncludes, - mockIncludesWithBlob, - { - location: 'a_really_really_long_name_for_includes_file.yml', - type: 'local', - blob: - 'http://gdk.test:3000/root/upstream/-/blob/dd54f00bb3645f8ddce7665d2ffb3864540399cb/a_really_really_long_name_for_includes_file.yml', - raw: - 'http://gdk.test:3000/root/upstream/-/raw/dd54f00bb3645f8ddce7665d2ffb3864540399cb/a_really_really_long_name_for_includes_file.yml', - __typename: 'CiConfigInclude', - }, -]; - -// Mock result of the graphql query at: -// app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql -export const mockCiConfigQueryResponse = { - data: { - ciConfig: { - errors: [], - includes: mockIncludes, - mergedYaml: mockCiYml, - status: CI_CONFIG_STATUS_VALID, - stages: { - __typename: 'CiConfigStageConnection', - nodes: [ - { - name: 'test', - groups: { - nodes: [ - { - id: 'group-1', - name: 'job_test_1', - size: 1, - jobs: { - nodes: [ - { - name: 'job_test_1', - script: ['echo "test 1"'], - ...mockJobFields, - }, - ], - __typename: 'CiConfigJobConnection', - }, - __typename: 'CiConfigGroup', - }, - { - id: 'group-2', - name: 'job_test_2', - size: 1, - jobs: { - nodes: [ - { - name: 'job_test_2', - script: ['echo "test 2"'], - ...mockJobFields, - }, - ], - __typename: 'CiConfigJobConnection', - }, - __typename: 'CiConfigGroup', - }, - ], - __typename: 'CiConfigGroupConnection', - }, - __typename: 'CiConfigStage', - }, - { - name: 'build', - groups: { - nodes: [ - { - name: 'job_build', - size: 1, - jobs: { - nodes: [ - { - name: 'job_build', - script: ['echo "build"'], - ...mockJobFields, - }, - ], - __typename: 'CiConfigJobConnection', - }, - __typename: 'CiConfigGroup', - }, - ], - __typename: 'CiConfigGroupConnection', - }, - __typename: 'CiConfigStage', - }, - ], - }, - __typename: 'CiConfig', - }, - }, -}; - -export const mergeUnwrappedCiConfig = (mergedConfig) => { - const { ciConfig } = mockCiConfigQueryResponse.data; - return { - ...ciConfig, - stages: unwrapStagesWithNeeds(ciConfig.stages.nodes), - ...mergedConfig, - }; -}; - -export const mockCommitShaResults = { - data: { - project: { - id: '1', - repository: { - tree: { - lastCommit: { - id: 'commit-1', - sha: mockCommitSha, - }, - }, - }, - }, - }, -}; - -export const mockNewCommitShaResults = { - data: { - project: { - id: '1', - repository: { - tree: { - lastCommit: { - id: 'commit-1', - sha: 'eeff1122', - }, - }, - }, - }, - }, -}; - -export const mockEmptyCommitShaResults = { - data: { - project: { - id: '1', - repository: { - tree: { - lastCommit: { - id: 'commit-1', - sha: '', - }, - }, - }, - }, - }, -}; - -export const mockProjectBranches = { - data: { - project: { - id: '1', - repository: { - branchNames: [ - 'main', - 'develop', - 'production', - 'test', - 'better-feature', - 'feature-abc', - 'update-ci', - 'mock-feature', - 'test-merge-request', - 'staging', - ], - }, - }, - }, -}; - -export const mockTotalBranchResults = - mockProjectBranches.data.project.repository.branchNames.length; - -export const mockSearchBranches = { - data: { - project: { - id: '1', - repository: { - branchNames: ['test', 'better-feature', 'update-ci', 'test-merge-request'], - }, - }, - }, -}; - -export const mockTotalSearchResults = mockSearchBranches.data.project.repository.branchNames.length; - -export const mockEmptySearchBranches = { - data: { - project: { - id: '1', - repository: { - branchNames: [], - }, - }, - }, -}; - -export const mockBranchPaginationLimit = 10; -export const mockTotalBranches = 20; // must be greater than mockBranchPaginationLimit to test pagination - -export const mockProjectPipeline = ({ hasStages = true } = {}) => { - const stages = hasStages - ? { - edges: [ - { - node: { - id: 'gid://gitlab/Ci::Stage/605', - name: 'prepare', - status: 'success', - detailedStatus: { - detailsPath: '/root/sample-ci-project/-/pipelines/268#prepare', - group: 'success', - hasDetails: true, - icon: 'status_success', - id: 'success-605-605', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - }, - }, - ], - } - : null; - - return { - id: '1', - pipeline: { - id: 'gid://gitlab/Ci::Pipeline/118', - iid: '28', - shortSha: mockCommitSha, - status: 'SUCCESS', - commit: { - id: 'commit-1', - title: 'Update .gitlabe-ci.yml', - webPath: '/-/commit/aabbccdd', - }, - detailedStatus: { - id: 'status-1', - detailsPath: '/root/sample-ci-project/-/pipelines/118', - group: 'success', - icon: 'status_success', - text: 'passed', - }, - stages, - }, - }; -}; - -export const mockLinkedPipelines = ({ hasDownstream = true, hasUpstream = true } = {}) => { - let upstream = null; - let downstream = { - nodes: [], - __typename: 'PipelineConnection', - }; - - if (hasDownstream) { - downstream = { - nodes: [ - { - id: 'gid://gitlab/Ci::Pipeline/612', - path: '/root/job-log-sections/-/pipelines/612', - project: { name: 'job-log-sections', __typename: 'Project' }, - detailedStatus: { - group: 'success', - icon: 'status_success', - label: 'passed', - __typename: 'DetailedStatus', - }, - __typename: 'Pipeline', - }, - ], - __typename: 'PipelineConnection', - }; - } - - if (hasUpstream) { - upstream = { - id: 'gid://gitlab/Ci::Pipeline/610', - path: '/root/trigger-downstream/-/pipelines/610', - project: { name: 'trigger-downstream', __typename: 'Project' }, - detailedStatus: { - group: 'success', - icon: 'status_success', - label: 'passed', - __typename: 'DetailedStatus', - }, - __typename: 'Pipeline', - }; - } - - return { - data: { - project: { - pipeline: { - path: '/root/ci-project/-/pipelines/790', - downstream, - upstream, - }, - __typename: 'Project', - }, - }, - }; -}; - -export const mockLintResponse = { - valid: true, - mergedYaml: mockCiYml, - status: CI_CONFIG_STATUS_VALID, - errors: [], - warnings: [], - jobs: [ - { - name: 'job_1', - stage: 'test', - before_script: ["echo 'before script 1'"], - script: ["echo 'script 1'"], - after_script: ["echo 'after script 1"], - tag_list: ['tag 1'], - environment: 'prd', - when: 'on_success', - allow_failure: false, - only: null, - except: { refs: ['main@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] }, - }, - { - name: 'job_2', - stage: 'test', - before_script: ["echo 'before script 2'"], - script: ["echo 'script 2'"], - after_script: ["echo 'after script 2"], - tag_list: ['tag 2'], - environment: 'stg', - when: 'on_success', - allow_failure: true, - only: { refs: ['web', 'chat', 'pushes'] }, - except: { refs: ['main@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] }, - }, - ], -}; - -export const mockLintResponseWithoutMerged = { - valid: false, - status: CI_CONFIG_STATUS_INVALID, - errors: ['error'], - warnings: [], - jobs: [], -}; - -export const mockJobs = [ - { - name: 'job_1', - stage: 'build', - beforeScript: [], - script: ["echo 'Building'"], - afterScript: [], - tagList: [], - environment: null, - when: 'on_success', - allowFailure: true, - only: { refs: ['web', 'chat', 'pushes'] }, - except: null, - }, - { - name: 'multi_project_job', - stage: 'test', - beforeScript: [], - script: [], - afterScript: [], - tagList: [], - environment: null, - when: 'on_success', - allowFailure: false, - only: { refs: ['branches', 'tags'] }, - except: null, - }, - { - name: 'job_2', - stage: 'test', - beforeScript: ["echo 'before script'"], - script: ["echo 'script'"], - afterScript: ["echo 'after script"], - tagList: [], - environment: null, - when: 'on_success', - allowFailure: false, - only: { refs: ['branches@gitlab-org/gitlab'] }, - except: { refs: ['main@gitlab-org/gitlab', '/^release/.*$/@gitlab-org/gitlab'] }, - }, -]; - -export const mockErrors = [ - '"job_1 job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post"', -]; - -export const mockWarnings = [ - '"jobs:multi_project_job may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings"', -]; - -export const mockCommitCreateResponse = { - data: { - commitCreate: { - __typename: 'CommitCreatePayload', - errors: [], - commit: { - __typename: 'Commit', - id: 'commit-1', - sha: mockCommitNextSha, - }, - commitPipelinePath: '', - }, - }, -}; - -export const mockCommitCreateResponseNewEtag = { - data: { - commitCreate: { - __typename: 'CommitCreatePayload', - errors: [], - commit: { - __typename: 'Commit', - id: 'commit-2', - sha: mockCommitNextSha, - }, - commitPipelinePath: '/api/graphql:pipelines/sha/550ceace1acd373c84d02bd539cb9d4614f786db', - }, - }, -}; diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js deleted file mode 100644 index 9fe1536d3f5..00000000000 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ /dev/null @@ -1,589 +0,0 @@ -import { GlAlert, GlButton, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import waitForPromises from 'helpers/wait_for_promises'; - -import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; -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 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_SUCCESS_WITH_REDIRECT, - COMMIT_FAILURE, - EDITOR_APP_STATUS_LOADING, -} 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'; -import getLatestCommitShaQuery from '~/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql'; -import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql'; -import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql'; -import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql'; - -import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue'; -import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; - -import { - mockCiConfigPath, - mockCiConfigQueryResponse, - mockBlobContentQueryResponse, - mockBlobContentQueryResponseNoCiFile, - mockCiYml, - mockCiTemplateQueryResponse, - mockCommitSha, - mockCommitShaResults, - mockDefaultBranch, - mockEmptyCommitShaResults, - mockNewCommitShaResults, - mockNewMergeRequestPath, - mockProjectFullPath, -} from './mock_data'; - -jest.mock('~/lib/utils/url_utility', () => ({ - ...jest.requireActual('~/lib/utils/url_utility'), - redirectTo: jest.fn(), -})); - -const localVue = createLocalVue(); -localVue.use(VueApollo); - -const defaultProvide = { - ciConfigPath: mockCiConfigPath, - defaultBranch: mockDefaultBranch, - newMergeRequestPath: mockNewMergeRequestPath, - projectFullPath: mockProjectFullPath, - usesExternalConfig: false, -}; - -describe('Pipeline editor app component', () => { - let wrapper; - - let mockApollo; - let mockBlobContentData; - let mockCiConfigData; - let mockGetTemplate; - let mockLatestCommitShaQuery; - let mockPipelineQuery; - - const createComponent = ({ - blobLoading = false, - options = {}, - provide = {}, - stubs = {}, - } = {}) => { - wrapper = shallowMount(PipelineEditorApp, { - provide: { ...defaultProvide, ...provide }, - stubs, - mocks: { - $apollo: { - queries: { - initialCiFileContent: { - loading: blobLoading, - }, - }, - }, - }, - ...options, - }); - }; - - const createComponentWithApollo = async ({ - provide = {}, - stubs = {}, - withUndefinedBranch = false, - } = {}) => { - const handlers = [ - [getBlobContent, mockBlobContentData], - [getCiConfigData, mockCiConfigData], - [getTemplate, mockGetTemplate], - [getLatestCommitShaQuery, mockLatestCommitShaQuery], - [getPipelineQuery, mockPipelineQuery], - ]; - - mockApollo = createMockApollo(handlers, resolvers); - - if (!withUndefinedBranch) { - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getCurrentBranch, - data: { - workBranches: { - __typename: 'BranchList', - current: { - __typename: 'WorkBranch', - name: mockDefaultBranch, - }, - }, - }, - }); - } - - mockApollo.clients.defaultClient.cache.writeQuery({ - query: getAppStatus, - data: { - app: { - __typename: 'AppData', - status: EDITOR_APP_STATUS_LOADING, - }, - }, - }); - - const options = { - localVue, - mocks: {}, - apolloProvider: mockApollo, - }; - - createComponent({ provide, stubs, options }); - - return waitForPromises(); - }; - - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findAlert = () => wrapper.findComponent(GlAlert); - const findEditorHome = () => wrapper.findComponent(PipelineEditorHome); - const findEmptyState = () => wrapper.findComponent(PipelineEditorEmptyState); - const findEmptyStateButton = () => findEmptyState().findComponent(GlButton); - const findValidationSegment = () => wrapper.findComponent(ValidationSegment); - - beforeEach(() => { - mockBlobContentData = jest.fn(); - mockCiConfigData = jest.fn(); - mockGetTemplate = jest.fn(); - mockLatestCommitShaQuery = jest.fn(); - mockPipelineQuery = jest.fn(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('loading state', () => { - it('displays a loading icon if the blob query is loading', () => { - createComponent({ blobLoading: true }); - - expect(findLoadingIcon().exists()).toBe(true); - expect(findEditorHome().exists()).toBe(false); - }); - }); - - describe('skipping queries', () => { - describe('when branchName is undefined', () => { - beforeEach(async () => { - await createComponentWithApollo({ withUndefinedBranch: true }); - }); - - it('does not calls getBlobContent', () => { - expect(mockBlobContentData).not.toHaveBeenCalled(); - }); - }); - - describe('when branchName is defined', () => { - beforeEach(async () => { - await createComponentWithApollo(); - }); - - it('calls getBlobContent', () => { - expect(mockBlobContentData).toHaveBeenCalled(); - }); - }); - - describe('when commit sha is undefined', () => { - beforeEach(async () => { - mockLatestCommitShaQuery.mockResolvedValue(undefined); - await createComponentWithApollo(); - }); - - it('calls getBlobContent', () => { - expect(mockBlobContentData).toHaveBeenCalled(); - }); - - it('does not call ciConfigData', () => { - expect(mockCiConfigData).not.toHaveBeenCalled(); - }); - }); - - describe('when commit sha is defined', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - await createComponentWithApollo(); - }); - - it('calls ciConfigData', () => { - expect(mockCiConfigData).toHaveBeenCalled(); - }); - }); - }); - - describe('when queries are called', () => { - beforeEach(() => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - }); - - describe('when project uses an external CI config file', () => { - beforeEach(async () => { - await createComponentWithApollo({ - provide: { - usesExternalConfig: true, - }, - }); - }); - - it('shows an empty state and does not show editor home component', () => { - expect(findEmptyState().exists()).toBe(true); - expect(findAlert().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(false); - }); - }); - - describe('when file exists', () => { - beforeEach(async () => { - await createComponentWithApollo(); - - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling') - .mockImplementation(jest.fn()); - }); - - it('shows pipeline editor home component', () => { - expect(findEditorHome().exists()).toBe(true); - }); - - it('no error is shown when data is set', () => { - expect(findAlert().exists()).toBe(false); - }); - - it('ci config query is called with correct variables', async () => { - expect(mockCiConfigData).toHaveBeenCalledWith({ - content: mockCiYml, - projectPath: mockProjectFullPath, - sha: mockCommitSha, - }); - }); - - it('does not poll for the commit sha', () => { - expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(0); - }); - }); - - describe('when no CI config file exists', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); - await createComponentWithApollo({ - stubs: { - PipelineEditorEmptyState, - }, - }); - - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling') - .mockImplementation(jest.fn()); - }); - - it('shows an empty state and does not show editor home component', async () => { - expect(findEmptyState().exists()).toBe(true); - expect(findAlert().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(false); - }); - - it('does not poll for the commit sha', () => { - expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(0); - }); - - describe('because of a fetching error', () => { - it('shows a unkown error message', async () => { - const loadUnknownFailureText = 'The CI configuration was not loaded, please try again.'; - - mockBlobContentData.mockRejectedValueOnce(); - await createComponentWithApollo({ - stubs: { - PipelineEditorMessages, - }, - }); - - expect(findEmptyState().exists()).toBe(false); - - expect(findAlert().text()).toBe(loadUnknownFailureText); - expect(findEditorHome().exists()).toBe(true); - }); - }); - }); - - describe('with no CI config setup', () => { - it('user can click on CTA button to get started', async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); - mockLatestCommitShaQuery.mockResolvedValue(mockEmptyCommitShaResults); - - await createComponentWithApollo({ - stubs: { - PipelineEditorHome, - PipelineEditorEmptyState, - }, - }); - - expect(findEmptyState().exists()).toBe(true); - expect(findEditorHome().exists()).toBe(false); - - await findEmptyStateButton().vm.$emit('click'); - - expect(findEmptyState().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(true); - }); - }); - - 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.'; - - describe('and the commit mutation succeeds', () => { - beforeEach(async () => { - window.scrollTo = jest.fn(); - await createComponentWithApollo({ stubs: { PipelineEditorMessages } }); - - findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS }); - }); - - it('shows a confirmation message', () => { - expect(findAlert().text()).toBe(updateSuccessMessage); - }); - - it('scrolls to the top of the page to bring attention to the confirmation message', () => { - expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); - }); - - it('polls for commit sha while pipeline data is not yet available for current branch', async () => { - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling') - .mockImplementation(jest.fn()); - - // simulate a commit to the current branch - findEditorHome().vm.$emit('updateCommitSha'); - await waitForPromises(); - - expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(1); - }); - - it('stops polling for commit sha when pipeline data is available for newly committed branch', async () => { - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'stopPolling') - .mockImplementation(jest.fn()); - - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - await wrapper.vm.$apollo.queries.commitSha.refetch(); - - expect(wrapper.vm.$apollo.queries.commitSha.stopPolling).toHaveBeenCalledTimes(1); - }); - - it('stops polling for commit sha when pipeline data is available for current branch', async () => { - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'stopPolling') - .mockImplementation(jest.fn()); - - mockLatestCommitShaQuery.mockResolvedValue(mockNewCommitShaResults); - findEditorHome().vm.$emit('updateCommitSha'); - await waitForPromises(); - - expect(wrapper.vm.$apollo.queries.commitSha.stopPolling).toHaveBeenCalledTimes(1); - }); - }); - - describe('when the commit succeeds with a redirect', () => { - const newBranch = 'new-branch'; - - beforeEach(async () => { - await createComponentWithApollo({ stubs: { PipelineEditorMessages } }); - - findEditorHome().vm.$emit('commit', { - type: COMMIT_SUCCESS_WITH_REDIRECT, - params: { sourceBranch: newBranch, targetBranch: mockDefaultBranch }, - }); - }); - - it('redirects to the merge request page with source and target branches', () => { - const branchesQuery = objectToQuery({ - 'merge_request[source_branch]': newBranch, - 'merge_request[target_branch]': mockDefaultBranch, - }); - - expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`); - }); - }); - - describe('and the commit mutation fails', () => { - const commitFailedReasons = ['Commit failed']; - - beforeEach(async () => { - window.scrollTo = jest.fn(); - await createComponentWithApollo({ stubs: { PipelineEditorMessages } }); - - findEditorHome().vm.$emit('showError', { - type: COMMIT_FAILURE, - reasons: commitFailedReasons, - }); - }); - - it('shows an error message', () => { - expect(findAlert().text()).toMatchInterpolatedText( - `${updateFailureMessage} ${commitFailedReasons[0]}`, - ); - }); - - it('scrolls to the top of the page to bring attention to the error message', () => { - expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); - }); - }); - - describe('when an unknown error occurs', () => { - const unknownReasons = ['Commit failed']; - - beforeEach(async () => { - window.scrollTo = jest.fn(); - await createComponentWithApollo({ stubs: { PipelineEditorMessages } }); - - findEditorHome().vm.$emit('showError', { - type: COMMIT_FAILURE, - reasons: unknownReasons, - }); - }); - - it('shows an error message', () => { - expect(findAlert().text()).toMatchInterpolatedText( - `${updateFailureMessage} ${unknownReasons[0]}`, - ); - }); - - it('scrolls to the top of the page to bring attention to the error message', () => { - expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); - }); - }); - }); - }); - - describe('when refetching content', () => { - beforeEach(() => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - }); - - it('refetches blob content', async () => { - await createComponentWithApollo(); - jest - .spyOn(wrapper.vm.$apollo.queries.initialCiFileContent, 'refetch') - .mockImplementation(jest.fn()); - - expect(wrapper.vm.$apollo.queries.initialCiFileContent.refetch).toHaveBeenCalledTimes(0); - - await wrapper.vm.refetchContent(); - - expect(wrapper.vm.$apollo.queries.initialCiFileContent.refetch).toHaveBeenCalledTimes(1); - }); - - it('hides start screen when refetch fetches CI file', async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); - await createComponentWithApollo(); - - expect(findEmptyState().exists()).toBe(true); - expect(findEditorHome().exists()).toBe(false); - - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - await wrapper.vm.$apollo.queries.initialCiFileContent.refetch(); - - expect(findEmptyState().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(true); - }); - }); - - describe('when a template parameter is present in the URL', () => { - const originalLocation = window.location.href; - - beforeEach(() => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); - mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); - mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults); - mockGetTemplate.mockResolvedValue(mockCiTemplateQueryResponse); - setWindowLocation('?template=Android'); - }); - - afterEach(() => { - setWindowLocation(originalLocation); - }); - - it('renders the given template', async () => { - await createComponentWithApollo({ - stubs: { PipelineEditorHome, PipelineEditorTabs }, - }); - - expect(mockGetTemplate).toHaveBeenCalledWith({ - projectPath: mockProjectFullPath, - templateName: 'Android', - }); - - expect(findEmptyState().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(true); - }); - }); - - describe('when add_new_config_file query param is present', () => { - const originalLocation = window.location.href; - - beforeEach(() => { - setWindowLocation('?add_new_config_file=true'); - - mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); - }); - - afterEach(() => { - setWindowLocation(originalLocation); - }); - - describe('when CI config file does not exist', () => { - beforeEach(async () => { - mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); - mockLatestCommitShaQuery.mockResolvedValue(mockEmptyCommitShaResults); - mockGetTemplate.mockResolvedValue(mockCiTemplateQueryResponse); - - await createComponentWithApollo(); - - jest - .spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling') - .mockImplementation(jest.fn()); - }); - - it('skips empty state and shows editor home component', () => { - expect(findEmptyState().exists()).toBe(false); - expect(findEditorHome().exists()).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js deleted file mode 100644 index 2b06660c4b3..00000000000 --- a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js +++ /dev/null @@ -1,330 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { GlButton, GlDrawer, GlModal } from '@gitlab/ui'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import CiEditorHeader from '~/pipeline_editor/components/editor/ci_editor_header.vue'; -import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; -import PipelineEditorDrawer from '~/pipeline_editor/components/drawer/pipeline_editor_drawer.vue'; -import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue'; -import PipelineEditorFileTree from '~/pipeline_editor/components/file_tree/container.vue'; -import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue'; -import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; -import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; -import { - CREATE_TAB, - FILE_TREE_DISPLAY_KEY, - VALIDATE_TAB, - MERGED_TAB, - TABS_INDEX, - VISUALIZE_TAB, -} from '~/pipeline_editor/constants'; -import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; - -import { mockLintResponse, mockCiYml } from './mock_data'; - -jest.mock('~/lib/utils/common_utils'); - -describe('Pipeline editor home wrapper', () => { - let wrapper; - - const createComponent = ({ props = {}, glFeatures = {}, data = {}, stubs = {} } = {}) => { - wrapper = extendedWrapper( - shallowMount(PipelineEditorHome, { - data: () => data, - propsData: { - ciConfigData: mockLintResponse, - ciFileContent: mockCiYml, - isCiConfigDataLoading: false, - isNewCiConfigFile: false, - ...props, - }, - provide: { - projectFullPath: '', - totalBranches: 19, - glFeatures: { - ...glFeatures, - }, - }, - stubs, - }), - ); - }; - - const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher); - const findCommitSection = () => wrapper.findComponent(CommitSection); - const findFileNav = () => wrapper.findComponent(PipelineEditorFileNav); - const findModal = () => wrapper.findComponent(GlModal); - const findPipelineEditorDrawer = () => wrapper.findComponent(PipelineEditorDrawer); - const findPipelineEditorFileTree = () => wrapper.findComponent(PipelineEditorFileTree); - const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader); - const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs); - const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle'); - const findHelpBtn = () => wrapper.findByTestId('drawer-toggle'); - - afterEach(() => { - localStorage.clear(); - wrapper.destroy(); - }); - - describe('renders', () => { - beforeEach(() => { - createComponent(); - }); - - it('shows the file nav', () => { - expect(findFileNav().exists()).toBe(true); - }); - - it('shows the pipeline editor header', () => { - expect(findPipelineEditorHeader().exists()).toBe(true); - }); - - it('shows the pipeline editor tabs', () => { - expect(findPipelineEditorTabs().exists()).toBe(true); - }); - - it('shows the commit section by default', () => { - expect(findCommitSection().exists()).toBe(true); - }); - }); - - describe('modal when switching branch', () => { - describe('when `showSwitchBranchModal` value is false', () => { - beforeEach(() => { - createComponent(); - }); - - it('is not visible', () => { - expect(findModal().exists()).toBe(false); - }); - }); - describe('when `showSwitchBranchModal` value is true', () => { - beforeEach(() => { - createComponent({ - data: { showSwitchBranchModal: true }, - stubs: { PipelineEditorFileNav }, - }); - }); - - it('is visible', () => { - expect(findModal().exists()).toBe(true); - }); - - it('pass down `shouldLoadNewBranch` to the branch switcher when primary is selected', async () => { - expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(false); - - await findModal().vm.$emit('primary'); - - expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(true); - }); - - it('closes the modal when secondary action is selected', async () => { - expect(findModal().exists()).toBe(true); - - await findModal().vm.$emit('secondary'); - - expect(findModal().exists()).toBe(false); - }); - }); - }); - - describe('commit form toggle', () => { - beforeEach(() => { - createComponent(); - }); - - it.each` - tab | shouldShow - ${MERGED_TAB} | ${false} - ${VISUALIZE_TAB} | ${false} - ${VALIDATE_TAB} | ${false} - ${CREATE_TAB} | ${true} - `( - 'when the active tab is $tab the commit form is shown: $shouldShow', - async ({ tab, shouldShow }) => { - expect(findCommitSection().exists()).toBe(true); - - findPipelineEditorTabs().vm.$emit('set-current-tab', tab); - - await nextTick(); - - expect(findCommitSection().isVisible()).toBe(shouldShow); - }, - ); - - it('shows the commit form again when coming back to the create tab', async () => { - expect(findCommitSection().isVisible()).toBe(true); - - findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB); - await nextTick(); - expect(findCommitSection().isVisible()).toBe(false); - - findPipelineEditorTabs().vm.$emit('set-current-tab', CREATE_TAB); - await nextTick(); - expect(findCommitSection().isVisible()).toBe(true); - }); - - describe('rendering with tab params', () => { - it.each` - tab | shouldShow - ${MERGED_TAB} | ${false} - ${VISUALIZE_TAB} | ${false} - ${VALIDATE_TAB} | ${false} - ${CREATE_TAB} | ${true} - `( - 'when the tab query param is $tab the commit form is shown: $shouldShow', - async ({ tab, shouldShow }) => { - setWindowLocation(`https://gitlab.test/ci/editor/?tab=${TABS_INDEX[tab]}`); - await createComponent({ stubs: { PipelineEditorTabs } }); - - expect(findCommitSection().isVisible()).toBe(shouldShow); - }, - ); - }); - }); - - describe('WalkthroughPopover events', () => { - beforeEach(() => { - createComponent(); - }); - - describe('when "walkthrough-popover-cta-clicked" is emitted from pipeline editor tabs', () => { - it('passes down `scrollToCommitForm=true` to commit section', async () => { - expect(findCommitSection().props('scrollToCommitForm')).toBe(false); - await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked'); - expect(findCommitSection().props('scrollToCommitForm')).toBe(true); - }); - }); - - describe('when "scrolled-to-commit-form" is emitted from commit section', () => { - it('passes down `scrollToCommitForm=false` to commit section', async () => { - await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked'); - expect(findCommitSection().props('scrollToCommitForm')).toBe(true); - await findCommitSection().vm.$emit('scrolled-to-commit-form'); - expect(findCommitSection().props('scrollToCommitForm')).toBe(false); - }); - }); - }); - - describe('help drawer', () => { - const clickHelpBtn = async () => { - findHelpBtn().vm.$emit('click'); - await nextTick(); - }; - - it('hides the drawer by default', () => { - createComponent(); - - expect(findPipelineEditorDrawer().props('isVisible')).toBe(false); - }); - - it('toggles the drawer on button click', async () => { - createComponent({ - stubs: { - CiEditorHeader, - GlButton, - GlDrawer, - PipelineEditorTabs, - PipelineEditorDrawer, - }, - }); - - await clickHelpBtn(); - - expect(findPipelineEditorDrawer().props('isVisible')).toBe(true); - - await clickHelpBtn(); - - expect(findPipelineEditorDrawer().props('isVisible')).toBe(false); - }); - - it("closes the drawer through the drawer's close button", async () => { - createComponent({ - stubs: { - CiEditorHeader, - GlButton, - GlDrawer, - PipelineEditorTabs, - PipelineEditorDrawer, - }, - }); - - await clickHelpBtn(); - - expect(findPipelineEditorDrawer().props('isVisible')).toBe(true); - - findPipelineEditorDrawer().findComponent(GlDrawer).vm.$emit('close'); - await nextTick(); - - expect(findPipelineEditorDrawer().props('isVisible')).toBe(false); - }); - }); - - describe('file tree', () => { - const toggleFileTree = async () => { - findFileTreeBtn().vm.$emit('click'); - await nextTick(); - }; - - describe('button toggle', () => { - beforeEach(() => { - createComponent({ - stubs: { - GlButton, - PipelineEditorFileNav, - }, - }); - }); - - it('shows button toggle', () => { - expect(findFileTreeBtn().exists()).toBe(true); - }); - - it('toggles the drawer on button click', async () => { - await toggleFileTree(); - - expect(findPipelineEditorFileTree().exists()).toBe(true); - - await toggleFileTree(); - - expect(findPipelineEditorFileTree().exists()).toBe(false); - }); - - it('sets the display state in local storage', async () => { - await toggleFileTree(); - - expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('true'); - - await toggleFileTree(); - - expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('false'); - }); - }); - - describe('when file tree display state is saved in local storage', () => { - beforeEach(() => { - localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true'); - createComponent({ - stubs: { PipelineEditorFileNav }, - }); - }); - - it('shows the file tree by default', () => { - expect(findPipelineEditorFileTree().exists()).toBe(true); - }); - }); - - describe('when file tree display state is not saved in local storage', () => { - beforeEach(() => { - createComponent({ - stubs: { PipelineEditorFileNav }, - }); - }); - - it('hides the file tree by default', () => { - expect(findPipelineEditorFileTree().exists()).toBe(false); - }); - }); - }); -}); |