Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipeline_editor/components')
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_form_spec.js8
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_section_spec.js223
-rw-r--r--spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js88
-rw-r--r--spec/frontend/pipeline_editor/components/editor/text_editor_spec.js120
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js34
-rw-r--r--spec/frontend/pipeline_editor/components/header/validation_segment_spec.js (renamed from spec/frontend/pipeline_editor/components/info/validation_segment_spec.js)13
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js4
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_warnings_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js183
-rw-r--r--spec/frontend/pipeline_editor/components/text_editor_spec.js93
-rw-r--r--spec/frontend/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js42
-rw-r--r--spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js4
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';