summaryrefslogtreecommitdiff
path: root/spec/frontend/pipeline_editor/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipeline_editor/components')
-rw-r--r--spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js61
-rw-r--r--spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js21
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js123
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js49
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js16
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js55
-rw-r--r--spec/frontend/pipeline_editor/components/header/validation_segment_spec.js93
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js5
-rw-r--r--spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js17
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js54
-rw-r--r--spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js57
11 files changed, 419 insertions, 132 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
new file mode 100644
index 00000000000..d03f12bc249
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js
@@ -0,0 +1,61 @@
+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/editor/ci_config_merged_preview_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
index 866069f337b..fb191fccb0d 100644
--- 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
@@ -1,10 +1,8 @@
-import { GlAlert, GlIcon } from '@gitlab/ui';
+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 { 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', () => {
@@ -33,28 +31,11 @@ describe('Text editor component', () => {
});
};
- 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', () => {
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
new file mode 100644
index 00000000000..fa937100982
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -0,0 +1,123 @@
+import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { shallowMount, createLocalVue } 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 { mockDefaultBranch, mockProjectBranches, mockProjectFullPath } from '../../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('Pipeline editor branch switcher', () => {
+ let wrapper;
+ let mockApollo;
+ let mockAvailableBranchQuery;
+
+ const createComponentWithApollo = () => {
+ const resolvers = {
+ Query: {
+ project: mockAvailableBranchQuery,
+ },
+ };
+
+ mockApollo = createMockApollo([], resolvers);
+ wrapper = shallowMount(BranchSwitcher, {
+ localVue,
+ apolloProvider: mockApollo,
+ provide: {
+ projectFullPath: mockProjectFullPath,
+ },
+ data() {
+ return {
+ currentBranch: mockDefaultBranch,
+ };
+ },
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+
+ beforeEach(() => {
+ mockAvailableBranchQuery = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('while querying', () => {
+ beforeEach(() => {
+ createComponentWithApollo();
+ });
+
+ it('does not render dropdown', () => {
+ expect(findDropdown().exists()).toBe(false);
+ });
+ });
+
+ describe('after querying', () => {
+ beforeEach(async () => {
+ mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches);
+ createComponentWithApollo();
+ await waitForPromises();
+ });
+
+ it('query is called with correct variables', async () => {
+ expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(1);
+ expect(mockAvailableBranchQuery).toHaveBeenCalledWith(
+ expect.anything(),
+ {
+ fullPath: mockProjectFullPath,
+ },
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+
+ it('renders list of branches', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDropdownItems()).toHaveLength(mockProjectBranches.repository.branches.length);
+ });
+
+ it('renders current branch at the top of the list with a check mark', () => {
+ const firstDropdownItem = findDropdownItems().at(0);
+ const icon = firstDropdownItem.findComponent(GlIcon);
+
+ expect(firstDropdownItem.text()).toBe(mockDefaultBranch);
+ expect(icon.exists()).toBe(true);
+ expect(icon.props('name')).toBe('check');
+ });
+
+ it('does not render check mark for other branches', () => {
+ const secondDropdownItem = findDropdownItems().at(1);
+ const icon = secondDropdownItem.findComponent(GlIcon);
+
+ expect(icon.classes()).toContain('gl-visibility-hidden');
+ });
+ });
+
+ describe('on fetch error', () => {
+ beforeEach(async () => {
+ mockAvailableBranchQuery.mockResolvedValue(new Error());
+ createComponentWithApollo();
+ await waitForPromises();
+ });
+
+ it('does not render dropdown', () => {
+ expect(findDropdown().exists()).toBe(false);
+ });
+
+ it('shows an error message', () => {
+ expect(wrapper.emitted('showError')).toBeDefined();
+ expect(wrapper.emitted('showError')[0]).toEqual([
+ {
+ reasons: [wrapper.vm.$options.i18n.fetchError],
+ type: DEFAULT_FAILURE,
+ },
+ ]);
+ });
+ });
+});
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
new file mode 100644
index 00000000000..94a0a7d14ee
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
+import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue';
+
+describe('Pipeline editor file nav', () => {
+ let wrapper;
+ const mockProvide = {
+ glFeatures: {
+ pipelineEditorBranchSwitcher: true,
+ },
+ };
+
+ const createComponent = ({ provide = {} } = {}) => {
+ wrapper = shallowMount(PipelineEditorFileNav, {
+ provide: {
+ ...mockProvide,
+ ...provide,
+ },
+ });
+ };
+
+ const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the branch switcher', () => {
+ expect(findBranchSwitcher().exists()).toBe(true);
+ });
+ });
+
+ describe('with branch switcher feature flag OFF', () => {
+ it('does not render the branch switcher', () => {
+ createComponent({
+ provide: {
+ glFeatures: { pipelineEditorBranchSwitcher: false },
+ },
+ });
+
+ expect(findBranchSwitcher().exists()).toBe(false);
+ });
+ });
+});
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
index ef8ca574e59..27652bb268b 100644
--- a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js
@@ -13,7 +13,7 @@ describe('Pipeline editor header', () => {
},
};
- const createComponent = ({ provide = {} } = {}) => {
+ const createComponent = ({ provide = {}, props = {} } = {}) => {
wrapper = shallowMount(PipelineEditorHeader, {
provide: {
...mockProvide,
@@ -23,6 +23,8 @@ describe('Pipeline editor header', () => {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
isCiConfigDataLoading: false,
+ isNewCiConfigFile: false,
+ ...props,
},
});
};
@@ -36,15 +38,21 @@ describe('Pipeline editor header', () => {
});
describe('template', () => {
- beforeEach(() => {
- createComponent();
+ 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', () => {
+ 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_status_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
index de6e112866b..b6d49d0d0f8 100644
--- a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
@@ -4,6 +4,7 @@ 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/client/pipeline.graphql';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data';
@@ -19,32 +20,9 @@ describe('Pipeline Status', () => {
let mockApollo;
let mockPipelineQuery;
- const createComponent = ({ hasPipeline = true, isQueryLoading = false }) => {
- const pipeline = hasPipeline
- ? { loading: isQueryLoading, ...mockProjectPipeline.pipeline }
- : { loading: isQueryLoading };
-
- wrapper = shallowMount(PipelineStatus, {
- provide: mockProvide,
- stubs: { GlLink, GlSprintf },
- data: () => (hasPipeline ? { pipeline } : {}),
- mocks: {
- $apollo: {
- queries: {
- pipeline,
- },
- },
- },
- });
- };
-
const createComponentWithApollo = () => {
- const resolvers = {
- Query: {
- project: mockPipelineQuery,
- },
- };
- mockApollo = createMockApollo([], resolvers);
+ const handlers = [[getPipelineQuery, mockPipelineQuery]];
+ mockApollo = createMockApollo(handlers);
wrapper = shallowMount(PipelineStatus, {
localVue,
@@ -78,16 +56,17 @@ describe('Pipeline Status', () => {
wrapper = null;
});
- describe('while querying', () => {
- it('renders loading icon', () => {
- createComponent({ isQueryLoading: true, hasPipeline: false });
+ 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 loading icon if pipeline data is already set', () => {
- createComponent({ isQueryLoading: true });
+ it('does not render if query is no longer loading', async () => {
+ createComponentWithApollo();
+ await waitForPromises();
expect(findLoadingIcon().exists()).toBe(false);
});
@@ -96,7 +75,9 @@ describe('Pipeline Status', () => {
describe('when querying data', () => {
describe('when data is set', () => {
beforeEach(async () => {
- mockPipelineQuery.mockResolvedValue(mockProjectPipeline);
+ mockPipelineQuery.mockResolvedValue({
+ data: { project: mockProjectPipeline },
+ });
createComponentWithApollo();
await waitForPromises();
@@ -104,14 +85,10 @@ describe('Pipeline Status', () => {
it('query is called with correct variables', async () => {
expect(mockPipelineQuery).toHaveBeenCalledTimes(1);
- expect(mockPipelineQuery).toHaveBeenCalledWith(
- expect.anything(),
- {
- fullPath: mockProjectFullPath,
- },
- expect.anything(),
- expect.anything(),
- );
+ expect(mockPipelineQuery).toHaveBeenCalledWith({
+ fullPath: mockProjectFullPath,
+ sha: mockCommitSha,
+ });
});
it('does not render error', () => {
diff --git a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
index 274c2d1b8da..fd8a100bb2c 100644
--- a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
@@ -6,13 +6,19 @@ import { sprintf } from '~/locale';
import ValidationSegment, {
i18n,
} from '~/pipeline_editor/components/header/validation_segment.vue';
-import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
+import {
+ CI_CONFIG_STATUS_INVALID,
+ EDITOR_APP_STATUS_EMPTY,
+ EDITOR_APP_STATUS_INVALID,
+ EDITOR_APP_STATUS_LOADING,
+ EDITOR_APP_STATUS_VALID,
+} from '~/pipeline_editor/constants';
import { mockYmlHelpPagePath, mergeUnwrappedCiConfig, mockCiYml } from '../../mock_data';
describe('Validation segment component', () => {
let wrapper;
- const createComponent = (props = {}) => {
+ const createComponent = ({ props = {}, appStatus }) => {
wrapper = extendedWrapper(
shallowMount(ValidationSegment, {
provide: {
@@ -21,9 +27,14 @@ describe('Validation segment component', () => {
propsData: {
ciConfig: mergeUnwrappedCiConfig(),
ciFileContent: mockCiYml,
- loading: false,
...props,
},
+ // Simulate graphQL client query result
+ data() {
+ return {
+ appStatus,
+ };
+ },
}),
);
};
@@ -34,18 +45,17 @@ describe('Validation segment component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
it('shows the loading state', () => {
- createComponent({ loading: true });
+ createComponent({ appStatus: EDITOR_APP_STATUS_LOADING });
expect(wrapper.text()).toBe(i18n.loading);
});
describe('when config is empty', () => {
beforeEach(() => {
- createComponent({ ciFileContent: '' });
+ createComponent({ appStatus: EDITOR_APP_STATUS_EMPTY });
});
it('has check icon', () => {
@@ -59,7 +69,7 @@ describe('Validation segment component', () => {
describe('when config is valid', () => {
beforeEach(() => {
- createComponent({});
+ createComponent({ appStatus: EDITOR_APP_STATUS_VALID });
});
it('has check icon', () => {
@@ -79,12 +89,9 @@ describe('Validation segment component', () => {
describe('when config is invalid', () => {
beforeEach(() => {
createComponent({
- ciConfig: mergeUnwrappedCiConfig({
- status: CI_CONFIG_STATUS_INVALID,
- }),
+ appStatus: EDITOR_APP_STATUS_INVALID,
});
});
-
it('has warning icon', () => {
expect(findIcon().props('name')).toBe('warning-solid');
});
@@ -93,43 +100,53 @@ describe('Validation segment component', () => {
expect(findValidationMsg().text()).toBe(i18n.invalid);
});
- it('shows an invalid state with an error', () => {
+ 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';
- createComponent({
- ciConfig: mergeUnwrappedCiConfig({
- status: CI_CONFIG_STATUS_INVALID,
- errors: [firstError, secondError],
- }),
+ 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 }),
+ );
});
-
- // Test the error is shown _and_ the string matches
- expect(findValidationMsg().text()).toContain(firstError);
- expect(findValidationMsg().text()).toBe(
- sprintf(i18n.invalidWithReason, { reason: firstError }),
- );
});
- it('shows an invalid state with an error while preventing XSS', () => {
+ describe('with XSS inside the error', () => {
const evilError = '<script>evil();</script>';
- createComponent({
- ciConfig: mergeUnwrappedCiConfig({
- status: CI_CONFIG_STATUS_INVALID,
- errors: [evilError],
- }),
+ 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;
- const { innerHTML } = findValidationMsg().element;
-
- expect(innerHTML).not.toContain(evilError);
- expect(innerHTML).toContain(escape(evilError));
- });
-
- it('shows the learn more link', () => {
- expect(findLearnMoreLink().attributes('href')).toBe(mockYmlHelpPagePath);
- expect(findLearnMoreLink().text()).toBe('Learn more');
+ expect(innerHTML).not.toContain(evilError);
+ expect(innerHTML).toContain(escape(evilError));
+ });
});
});
});
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 6775433deb9..5fc0880b09e 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
@@ -7,7 +7,7 @@ import { mockJobs, mockErrors, mockWarnings } from '../../mock_data';
describe('CI Lint Results', () => {
let wrapper;
const defaultProps = {
- valid: true,
+ isValid: true,
jobs: mockJobs,
errors: [],
warnings: [],
@@ -42,7 +42,6 @@ describe('CI Lint Results', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('Empty results', () => {
@@ -72,7 +71,7 @@ describe('CI Lint Results', () => {
describe('Invalid results', () => {
beforeEach(() => {
- createComponent({ valid: false, errors: mockErrors, warnings: mockWarnings }, mount);
+ createComponent({ isValid: false, errors: mockErrors, warnings: mockWarnings }, mount);
});
it('does not display the table', () => {
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 fdddca3d62b..238942a34ff 100644
--- a/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js
+++ b/spec/frontend/pipeline_editor/components/lint/ci_lint_spec.js
@@ -1,13 +1,12 @@
import { GlAlert, GlLink } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { mount, shallowMount } 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';
describe('~/pipeline_editor/components/lint/ci_lint.vue', () => {
let wrapper;
- const createComponent = (props = {}, mountFn = shallowMount) => {
+ const createComponent = ({ props, mountFn = shallowMount } = {}) => {
wrapper = mountFn(CiLint, {
provide: {
lintHelpPagePath: mockLintHelpPagePath,
@@ -27,12 +26,11 @@ describe('~/pipeline_editor/components/lint/ci_lint.vue', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('Valid Results', () => {
beforeEach(() => {
- createComponent({}, mount);
+ createComponent({ props: { isValid: true }, mountFn: mount });
});
it('displays valid results', () => {
@@ -66,14 +64,7 @@ describe('~/pipeline_editor/components/lint/ci_lint.vue', () => {
});
it('displays invalid results', () => {
- createComponent(
- {
- ciConfig: mergeUnwrappedCiConfig({
- status: CI_CONFIG_STATUS_INVALID,
- }),
- },
- mount,
- );
+ createComponent({ props: { isValid: false }, mountFn: mount });
expect(findAlert().text()).toMatch('Status: Syntax is incorrect.');
});
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
index 24af17e9ce6..eba853180cd 100644
--- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -4,8 +4,15 @@ 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 EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue';
+import {
+ EDITOR_APP_STATUS_EMPTY,
+ EDITOR_APP_STATUS_ERROR,
+ EDITOR_APP_STATUS_LOADING,
+ EDITOR_APP_STATUS_INVALID,
+ EDITOR_APP_STATUS_VALID,
+} from '~/pipeline_editor/constants';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
-
import { mockLintResponse, mockCiYml } from '../mock_data';
describe('Pipeline editor tabs component', () => {
@@ -20,17 +27,27 @@ describe('Pipeline editor tabs component', () => {
},
};
- const createComponent = ({ props = {}, provide = {}, mountFn = shallowMount } = {}) => {
+ const createComponent = ({
+ props = {},
+ provide = {},
+ appStatus = EDITOR_APP_STATUS_VALID,
+ mountFn = shallowMount,
+ } = {}) => {
wrapper = mountFn(PipelineEditorTabs, {
propsData: {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
- isCiConfigDataLoading: false,
...props,
},
+ data() {
+ return {
+ appStatus,
+ };
+ },
provide: { ...mockProvide, ...provide },
stubs: {
TextEditor: MockTextEditor,
+ EditorTab,
},
});
};
@@ -49,7 +66,6 @@ describe('Pipeline editor tabs component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('editor tab', () => {
@@ -69,7 +85,7 @@ describe('Pipeline editor tabs component', () => {
describe('with feature flag on', () => {
describe('while loading', () => {
beforeEach(() => {
- createComponent({ props: { isCiConfigDataLoading: true } });
+ createComponent({ appStatus: EDITOR_APP_STATUS_LOADING });
});
it('displays a loading icon if the lint query is loading', () => {
@@ -108,7 +124,7 @@ describe('Pipeline editor tabs component', () => {
describe('lint tab', () => {
describe('while loading', () => {
beforeEach(() => {
- createComponent({ props: { isCiConfigDataLoading: true } });
+ createComponent({ appStatus: EDITOR_APP_STATUS_LOADING });
});
it('displays a loading icon if the lint query is loading', () => {
@@ -135,7 +151,7 @@ describe('Pipeline editor tabs component', () => {
describe('with feature flag on', () => {
describe('while loading', () => {
beforeEach(() => {
- createComponent({ props: { isCiConfigDataLoading: true } });
+ createComponent({ appStatus: EDITOR_APP_STATUS_LOADING });
});
it('displays a loading icon if the lint query is loading', () => {
@@ -143,9 +159,9 @@ describe('Pipeline editor tabs component', () => {
});
});
- describe('when `mergedYaml` is undefined', () => {
+ describe('when there is a fetch error', () => {
beforeEach(() => {
- createComponent({ props: { ciConfigData: {} } });
+ createComponent({ appStatus: EDITOR_APP_STATUS_ERROR });
});
it('show an error message', () => {
@@ -180,4 +196,24 @@ describe('Pipeline editor tabs component', () => {
});
});
});
+
+ describe('show tab content based on status', () => {
+ it.each`
+ appStatus | editor | viz | lint | merged
+ ${undefined} | ${true} | ${true} | ${true} | ${true}
+ ${EDITOR_APP_STATUS_EMPTY} | ${true} | ${false} | ${false} | ${false}
+ ${EDITOR_APP_STATUS_INVALID} | ${true} | ${false} | ${true} | ${false}
+ ${EDITOR_APP_STATUS_VALID} | ${true} | ${true} | ${true} | ${true}
+ `(
+ 'when status is $appStatus, we show - editor:$editor | viz:$viz | lint:$lint | merged:$merged ',
+ ({ appStatus, editor, viz, lint, merged }) => {
+ createComponent({ appStatus });
+
+ expect(findTextEditor().exists()).toBe(editor);
+ expect(findPipelineGraph().exists()).toBe(viz);
+ expect(findCiLint().exists()).toBe(lint);
+ expect(findMergedPreview().exists()).toBe(merged);
+ },
+ );
+ });
});
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 291468c5229..8def83d578b 100644
--- a/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
+++ b/spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
@@ -1,12 +1,15 @@
-import { GlTabs } from '@gitlab/ui';
+import { GlAlert, 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 MockEditorLite = {
+ template: '<div>EDITOR</div>',
+};
+
describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
let wrapper;
let mockChildMounted = jest.fn();
@@ -37,22 +40,34 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
`,
};
- const createWrapper = () => {
+ const createMockedWrapper = () => {
wrapper = mount(MockTabbedContent);
};
+ const createWrapper = ({ props } = {}) => {
+ wrapper = mount(EditorTab, {
+ propsData: props,
+ slots: {
+ default: MockEditorLite,
+ },
+ });
+ };
+
+ const findSlotComponent = () => wrapper.findComponent(MockEditorLite);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+
beforeEach(() => {
mockChildMounted = jest.fn();
});
it('tabs are mounted lazily', async () => {
- createWrapper();
+ createMockedWrapper();
expect(mockChildMounted).toHaveBeenCalledTimes(0);
});
it('first tab is only mounted after nextTick', async () => {
- createWrapper();
+ createMockedWrapper();
await nextTick();
@@ -60,6 +75,36 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
expect(mockChildMounted).toHaveBeenCalledWith(mockContent1);
});
+ describe('showing the tab content depending on `isEmpty` and `isInvalid`', () => {
+ it.each`
+ isEmpty | isInvalid | showSlotComponent | text
+ ${undefined} | ${undefined} | ${true} | ${'renders'}
+ ${false} | ${false} | ${true} | ${'renders'}
+ ${undefined} | ${true} | ${false} | ${'hides'}
+ ${true} | ${false} | ${false} | ${'hides'}
+ ${false} | ${true} | ${false} | ${'hides'}
+ `(
+ '$text the slot component when isEmpty:$isEmpty and isInvalid:$isInvalid',
+ ({ isEmpty, isInvalid, showSlotComponent }) => {
+ createWrapper({
+ props: { isEmpty, isInvalid },
+ });
+ expect(findSlotComponent().exists()).toBe(showSlotComponent);
+ expect(findAlert().exists()).toBe(!showSlotComponent);
+ },
+ );
+
+ it('can have a custom empty message', () => {
+ const text = 'my custom alert message';
+ createWrapper({ props: { isEmpty: true, emptyMessage: text } });
+
+ const alert = findAlert();
+
+ expect(alert.exists()).toBe(true);
+ expect(alert.text()).toBe(text);
+ });
+ });
+
describe('user interaction', () => {
const clickTab = async (testid) => {
wrapper.find(`[data-testid="${testid}"]`).trigger('click');
@@ -67,7 +112,7 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
};
beforeEach(() => {
- createWrapper();
+ createMockedWrapper();
});
it('mounts a tab once after selecting it', async () => {