diff options
Diffstat (limited to 'spec/frontend/pipeline_editor/components')
13 files changed, 710 insertions, 106 deletions
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js index aae25a3aa6d..5dae77a4626 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js @@ -1,11 +1,11 @@ -import { shallowMount, mount } from '@vue/test-utils'; 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'; -describe('~/pipeline_editor/pipeline_editor_app.vue', () => { +describe('Pipeline Editor | Commit Form', () => { let wrapper; const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => { @@ -21,8 +21,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { }); }; - const findCommitTextarea = () => wrapper.find(GlFormTextarea); - const findBranchInput = () => wrapper.find(GlFormInput); + 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"]'); diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js new file mode 100644 index 00000000000..b87ff6ec0de --- /dev/null +++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js @@ -0,0 +1,223 @@ +import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; +import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; +import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; +import { COMMIT_SUCCESS } from '~/pipeline_editor/constants'; +import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql'; + +import { + mockCiConfigPath, + mockCiYml, + mockCommitSha, + mockCommitNextSha, + mockCommitMessage, + mockDefaultBranch, + mockProjectFullPath, + mockNewMergeRequestPath, +} from '../../mock_data'; + +jest.mock('~/lib/utils/url_utility', () => ({ + redirectTo: jest.fn(), + refreshCurrentPage: jest.fn(), + objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery, + mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams, +})); + +const mockVariables = { + projectPath: mockProjectFullPath, + startBranch: mockDefaultBranch, + message: mockCommitMessage, + filePath: mockCiConfigPath, + content: mockCiYml, + lastCommitId: mockCommitSha, +}; + +const mockProvide = { + ciConfigPath: mockCiConfigPath, + defaultBranch: mockDefaultBranch, + projectFullPath: mockProjectFullPath, + newMergeRequestPath: mockNewMergeRequestPath, +}; + +describe('Pipeline Editor | Commit section', () => { + let wrapper; + let mockMutate; + + const defaultProps = { ciFileContent: mockCiYml }; + + const createComponent = ({ props = {}, options = {}, provide = {} } = {}) => { + mockMutate = jest.fn().mockResolvedValue({ + data: { + commitCreate: { + errors: [], + commit: { + sha: mockCommitNextSha, + }, + }, + }, + }); + + wrapper = mount(CommitSection, { + propsData: { ...defaultProps, ...props }, + provide: { ...mockProvide, ...provide }, + data() { + return { + commitSha: mockCommitSha, + }; + }, + mocks: { + $apollo: { + mutate: mockMutate, + }, + }, + attachTo: document.body, + ...options, + }); + }; + + 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'); + // Simulate the write to local cache that occurs after a commit + await wrapper.setData({ commitSha: mockCommitNextSha }); + }; + + const cancelCommitForm = async () => { + const findCancelBtn = () => wrapper.find('[type="reset"]'); + await findCancelBtn().trigger('click'); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + mockMutate.mockReset(); + + wrapper.destroy(); + wrapper = null; + }); + + describe('when the user commits changes to the current branch', () => { + beforeEach(async () => { + await submitCommit(); + }); + + it('calls the mutation with the default branch', () => { + expect(mockMutate).toHaveBeenCalledTimes(1); + expect(mockMutate).toHaveBeenCalledWith({ + mutation: commitCreate, + update: expect.any(Function), + variables: { + ...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('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(mockMutate).toHaveBeenCalledTimes(2); + expect(mockMutate).toHaveBeenCalledWith({ + mutation: commitCreate, + update: expect.any(Function), + variables: { + ...mockVariables, + lastCommitId: mockCommitNextSha, + branch: mockDefaultBranch, + }, + }); + }); + }); + + describe('when the user commits changes to a new branch', () => { + const newBranch = 'new-branch'; + + beforeEach(async () => { + await submitCommit({ + branch: newBranch, + }); + }); + + it('calls the mutation with the new branch', () => { + expect(mockMutate).toHaveBeenCalledWith({ + mutation: commitCreate, + update: expect.any(Function), + variables: { + ...mockVariables, + branch: newBranch, + }, + }); + }); + }); + + describe('when the user commits changes to open a new merge request', () => { + const newBranch = 'new-branch'; + + beforeEach(async () => { + await submitCommit({ + branch: newBranch, + openMergeRequest: true, + }); + }); + + 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('when the commit is ocurring', () => { + it('shows a saving state', async () => { + mockMutate.mockImplementationOnce(() => { + expect(findCommitBtnLoadingIcon().exists()).toBe(true); + return Promise.resolve(); + }); + + await submitCommit({ + message: mockCommitMessage, + branch: mockDefaultBranch, + openMergeRequest: false, + }); + }); + }); + + describe('when the commit form is cancelled', () => { + beforeEach(async () => { + createComponent(); + }); + + it('emits an event so that it cab be reseted', async () => { + await cancelCommitForm(); + + expect(wrapper.emitted('resetContent')).toHaveLength(1); + }); + }); +}); 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 new file mode 100644 index 00000000000..866069f337b --- /dev/null +++ b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js @@ -0,0 +1,88 @@ +import { GlAlert, 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 { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; +import { INVALID_CI_CONFIG } from '~/pipelines/constants'; +import { mockLintResponse, mockCiConfigPath } from '../../mock_data'; + +describe('Text editor component', () => { + let wrapper; + + const MockEditorLite = { + 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: { + EditorLite: MockEditorLite, + }, + }); + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + const findIcon = () => wrapper.findComponent(GlIcon); + const findEditor = () => wrapper.findComponent(MockEditorLite); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('when status is invalid', () => { + beforeEach(() => { + createComponent({ props: { ciConfigData: { status: CI_CONFIG_STATUS_INVALID } } }); + }); + + it('show an error message', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[INVALID_CI_CONFIG]); + }); + + it('hides the editor', () => { + expect(findEditor().exists()).toBe(false); + }); + }); + + 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/text_editor_spec.js b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js new file mode 100644 index 00000000000..3bf5a291c69 --- /dev/null +++ b/spec/frontend/pipeline_editor/components/editor/text_editor_spec.js @@ -0,0 +1,120 @@ +import { shallowMount } from '@vue/test-utils'; + +import { EDITOR_READY_EVENT } from '~/editor/constants'; +import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue'; +import { + mockCiConfigPath, + mockCiYml, + mockCommitSha, + mockProjectPath, + mockProjectNamespace, +} from '../../mock_data'; + +describe('Pipeline Editor | Text editor component', () => { + let wrapper; + + let editorReadyListener; + let mockUse; + let mockRegisterCiSchema; + + const MockEditorLite = { + template: '<div/>', + props: ['value', 'fileName'], + mounted() { + this.$emit(EDITOR_READY_EVENT); + }, + methods: { + getEditor: () => ({ + use: mockUse, + registerCiSchema: mockRegisterCiSchema, + }), + }, + }; + + const createComponent = (opts = {}, mountFn = shallowMount) => { + wrapper = mountFn(TextEditor, { + provide: { + projectPath: mockProjectPath, + projectNamespace: mockProjectNamespace, + ciConfigPath: mockCiConfigPath, + }, + attrs: { + value: mockCiYml, + }, + // Simulate graphQL client query result + data() { + return { + commitSha: mockCommitSha, + }; + }, + listeners: { + [EDITOR_READY_EVENT]: editorReadyListener, + }, + stubs: { + EditorLite: MockEditorLite, + }, + ...opts, + }); + }; + + const findEditor = () => wrapper.findComponent(MockEditorLite); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + + mockUse.mockClear(); + mockRegisterCiSchema.mockClear(); + }); + + describe('template', () => { + beforeEach(() => { + editorReadyListener = jest.fn(); + mockUse = jest.fn(); + mockRegisterCiSchema = jest.fn(); + + 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('bubbles up events', () => { + findEditor().vm.$emit(EDITOR_READY_EVENT); + + expect(editorReadyListener).toHaveBeenCalled(); + }); + }); + + describe('register CI schema', () => { + beforeEach(async () => { + createComponent(); + + // Since the editor will have already mounted, the event will have fired. + // To ensure we properly test this, we clear the mock and re-remit the event. + mockRegisterCiSchema.mockClear(); + mockUse.mockClear(); + + findEditor().vm.$emit(EDITOR_READY_EVENT); + }); + + it('configures editor with syntax highlight', async () => { + expect(mockUse).toHaveBeenCalledTimes(1); + expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1); + expect(mockRegisterCiSchema).toHaveBeenCalledWith({ + projectNamespace: mockProjectNamespace, + projectPath: mockProjectPath, + ref: mockCommitSha, + }); + }); + }); +}); 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 new file mode 100644 index 00000000000..df15a6c8e7f --- /dev/null +++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js @@ -0,0 +1,34 @@ +import { shallowMount } from '@vue/test-utils'; +import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; +import ValidationSegment from '~/pipeline_editor/components/header/validation_segment.vue'; + +import { mockLintResponse } from '../../mock_data'; + +describe('Pipeline editor header', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(PipelineEditorHeader, { + props: { + ciConfigData: mockLintResponse, + isCiConfigDataLoading: false, + }, + }); + }; + + const findValidationSegment = () => wrapper.findComponent(ValidationSegment); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + it('renders the validation segment', () => { + expect(findValidationSegment().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/pipeline_editor/components/info/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js index 8a991d82018..cf1d89e1d7c 100644 --- a/spec/frontend/pipeline_editor/components/info/validation_segment_spec.js +++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js @@ -1,9 +1,11 @@ -import { escape } from 'lodash'; -import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { escape } from 'lodash'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { sprintf } from '~/locale'; -import ValidationSegment, { i18n } from '~/pipeline_editor/components/info/validation_segment.vue'; +import ValidationSegment, { + i18n, +} from '~/pipeline_editor/components/header/validation_segment.vue'; import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; import { mockYmlHelpPagePath, mergeUnwrappedCiConfig } from '../../mock_data'; @@ -29,6 +31,11 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => { const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink'); const findValidationMsg = () => wrapper.findByTestId('validationMsg'); + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + it('shows the loading state', () => { createComponent({ loading: true }); 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 index 5e9471376bd..6775433deb9 100644 --- a/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js +++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js @@ -1,7 +1,7 @@ -import { shallowMount, mount } from '@vue/test-utils'; import { GlTable, GlLink } from '@gitlab/ui'; -import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue'; +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', () => { diff --git a/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js b/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js index 5ccf4bbdab4..fdddca3d62b 100644 --- a/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js +++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js @@ -1,5 +1,5 @@ -import { shallowMount, mount } from '@vue/test-utils'; import { GlAlert, GlLink } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue'; import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; import { mergeUnwrappedCiConfig, mockLintHelpPagePath } from '../../mock_data'; 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 index b441d26c146..4b576508ee9 100644 --- a/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js +++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js @@ -1,5 +1,5 @@ -import { mount } from '@vue/test-utils'; 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'; diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js new file mode 100644 index 00000000000..24af17e9ce6 --- /dev/null +++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js @@ -0,0 +1,183 @@ +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue'; +import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue'; +import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; +import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; + +import { mockLintResponse, mockCiYml } from '../mock_data'; + +describe('Pipeline editor tabs component', () => { + let wrapper; + const MockTextEditor = { + template: '<div />', + }; + const mockProvide = { + glFeatures: { + ciConfigVisualizationTab: true, + ciConfigMergedTab: true, + }, + }; + + const createComponent = ({ props = {}, provide = {}, mountFn = shallowMount } = {}) => { + wrapper = mountFn(PipelineEditorTabs, { + propsData: { + ciConfigData: mockLintResponse, + ciFileContent: mockCiYml, + isCiConfigDataLoading: false, + ...props, + }, + provide: { ...mockProvide, ...provide }, + stubs: { + TextEditor: MockTextEditor, + }, + }); + }; + + const findEditorTab = () => wrapper.find('[data-testid="editor-tab"]'); + const findLintTab = () => wrapper.find('[data-testid="lint-tab"]'); + const findMergedTab = () => wrapper.find('[data-testid="merged-tab"]'); + const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]'); + + const findAlert = () => wrapper.findComponent(GlAlert); + const findCiLint = () => wrapper.findComponent(CiLint); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findPipelineGraph = () => wrapper.findComponent(PipelineGraph); + const findTextEditor = () => wrapper.findComponent(MockTextEditor); + const findMergedPreview = () => wrapper.findComponent(CiConfigMergedPreview); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('editor tab', () => { + it('displays editor only after the tab is mounted', async () => { + createComponent({ mountFn: mount }); + + expect(findTextEditor().exists()).toBe(false); + + await nextTick(); + + expect(findTextEditor().exists()).toBe(true); + expect(findEditorTab().exists()).toBe(true); + }); + }); + + describe('visualization tab', () => { + describe('with feature flag on', () => { + describe('while loading', () => { + beforeEach(() => { + createComponent({ props: { isCiConfigDataLoading: true } }); + }); + + 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('with feature flag off', () => { + beforeEach(() => { + createComponent({ + provide: { + glFeatures: { ciConfigVisualizationTab: false }, + }, + }); + }); + + it('does not display the tab or component', () => { + expect(findVisualizationTab().exists()).toBe(false); + expect(findPipelineGraph().exists()).toBe(false); + }); + }); + }); + + describe('lint tab', () => { + describe('while loading', () => { + beforeEach(() => { + createComponent({ props: { isCiConfigDataLoading: true } }); + }); + + it('displays a loading icon if the lint query is loading', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + + it('does not display the lint component', () => { + expect(findCiLint().exists()).toBe(false); + }); + }); + describe('after loading', () => { + beforeEach(() => { + createComponent(); + }); + + it('display the tab and the lint component', () => { + expect(findLintTab().exists()).toBe(true); + expect(findCiLint().exists()).toBe(true); + }); + }); + }); + + describe('merged tab', () => { + describe('with feature flag on', () => { + describe('while loading', () => { + beforeEach(() => { + createComponent({ props: { isCiConfigDataLoading: true } }); + }); + + it('displays a loading icon if the lint query is loading', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + }); + + describe('when `mergedYaml` is undefined', () => { + beforeEach(() => { + createComponent({ props: { ciConfigData: {} } }); + }); + + it('show an error message', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts.loadMergedYaml); + }); + + it('does not render the `meged_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('with feature flag off', () => { + beforeEach(() => { + createComponent({ provide: { glFeatures: { ciConfigMergedTab: false } } }); + }); + + it('does not display the merged tab', () => { + expect(findMergedTab().exists()).toBe(false); + expect(findMergedPreview().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/pipeline_editor/components/text_editor_spec.js b/spec/frontend/pipeline_editor/components/text_editor_spec.js deleted file mode 100644 index 9221d64c44b..00000000000 --- a/spec/frontend/pipeline_editor/components/text_editor_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { - mockCiConfigPath, - mockCiYml, - mockCommitSha, - mockProjectPath, - mockProjectNamespace, -} from '../mock_data'; - -import TextEditor from '~/pipeline_editor/components/text_editor.vue'; - -describe('~/pipeline_editor/components/text_editor.vue', () => { - let wrapper; - - let editorReadyListener; - let mockUse; - let mockRegisterCiSchema; - - const MockEditorLite = { - template: '<div/>', - props: ['value', 'fileName'], - mounted() { - this.$emit('editor-ready'); - }, - methods: { - getEditor: () => ({ - use: mockUse, - registerCiSchema: mockRegisterCiSchema, - }), - }, - }; - - const createComponent = (opts = {}, mountFn = shallowMount) => { - wrapper = mountFn(TextEditor, { - provide: { - projectPath: mockProjectPath, - projectNamespace: mockProjectNamespace, - }, - propsData: { - ciConfigPath: mockCiConfigPath, - commitSha: mockCommitSha, - }, - attrs: { - value: mockCiYml, - }, - listeners: { - 'editor-ready': editorReadyListener, - }, - stubs: { - EditorLite: MockEditorLite, - }, - ...opts, - }); - }; - - const findEditor = () => wrapper.find(MockEditorLite); - - beforeEach(() => { - editorReadyListener = jest.fn(); - mockUse = jest.fn(); - mockRegisterCiSchema = jest.fn(); - - 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('editor is configured with syntax highligting', async () => { - expect(mockUse).toHaveBeenCalledTimes(1); - expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1); - expect(mockRegisterCiSchema).toHaveBeenCalledWith({ - projectNamespace: mockProjectNamespace, - projectPath: mockProjectPath, - ref: mockCommitSha, - }); - }); - - it('bubbles up events', () => { - findEditor().vm.$emit('editor-ready'); - - expect(editorReadyListener).toHaveBeenCalled(); - }); -}); 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 new file mode 100644 index 00000000000..44fda2812d8 --- /dev/null +++ b/spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js @@ -0,0 +1,42 @@ +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 index d3d9bf08209..291468c5229 100644 --- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js +++ b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js @@ -1,6 +1,6 @@ -import { nextTick } from 'vue'; -import { mount } from '@vue/test-utils'; import { GlTabs } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue'; |