Commit 9903c502 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '292930-breakdown-pipeline_editor_app-into-smaller-components' into 'master'

Merge feature branch for Pipeline Editor app refactor

See merge request gitlab-org/gitlab!52200
parents bc27d459 80a8670e
<script>
import CommitForm from './commit_form.vue';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
export default {
alertTexts: {
[COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
[COMMIT_SUCCESS]: __('Your changes have been successfully committed.'),
},
i18n: {
defaultCommitMessage: __('Update %{sourcePath} file'),
},
components: {
CommitForm,
},
inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'],
props: {
ciFileContent: {
type: String,
required: true,
},
},
data() {
return {
commit: {},
isSaving: false,
};
},
apollo: {
commitSha: {
query: getCommitSha,
},
},
computed: {
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
},
methods: {
redirectToNewMergeRequest(sourceBranch) {
const url = mergeUrlParams(
{
[MR_SOURCE_BRANCH]: sourceBranch,
[MR_TARGET_BRANCH]: this.defaultBranch,
},
this.newMergeRequestPath,
);
redirectTo(url);
},
async onCommitSubmit({ message, branch, openMergeRequest }) {
this.isSaving = true;
try {
const {
data: {
commitCreate: { errors },
},
} = await this.$apollo.mutate({
mutation: commitCIFile,
variables: {
projectPath: this.projectFullPath,
branch,
startBranch: this.defaultBranch,
message,
filePath: this.ciConfigPath,
content: this.ciFileContent,
lastCommitId: this.commitSha,
},
update(store, { data }) {
const commitSha = data?.commitCreate?.commit?.sha;
if (commitSha) {
store.writeQuery({ query: getCommitSha, data: { commitSha } });
}
},
});
if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else if (openMergeRequest) {
this.redirectToNewMergeRequest(branch);
} else {
this.$emit('commit', { type: COMMIT_SUCCESS });
}
} catch (error) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: [error?.message] });
} finally {
this.isSaving = false;
}
},
onCommitCancel() {
this.$emit('resetContent');
},
},
};
</script>
<template>
<commit-form
:default-branch="defaultBranch"
:default-message="defaultCommitMessage"
:is-saving="isSaving"
@cancel="onCommitCancel"
@submit="onCommitSubmit"
/>
</template>
<script>
import ValidationSegment from './validation_segment.vue';
export default {
validationSegmentClasses:
'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base',
components: {
ValidationSegment,
},
props: {
ciConfigData: {
type: Object,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div class="gl-mb-5">
<validation-segment
:class="$options.validationSegmentClasses"
:loading="isCiConfigDataLoading"
:ci-config="ciConfigData"
/>
</div>
</template>
<script>
import { GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CiLint from './lint/ci_lint.vue';
import EditorTab from './ui/editor_tab.vue';
import TextEditor from './text_editor.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
export default {
i18n: {
tabEdit: s__('Pipelines|Write pipeline configuration'),
tabGraph: s__('Pipelines|Visualize'),
tabLint: s__('Pipelines|Lint'),
},
components: {
CiLint,
EditorTab,
GlLoadingIcon,
GlTab,
GlTabs,
PipelineGraph,
TextEditor,
},
mixins: [glFeatureFlagsMixin()],
props: {
ciConfigData: {
type: Object,
required: true,
},
ciFileContent: {
type: String,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<gl-tabs class="file-editor gl-mb-3">
<editor-tab :title="$options.i18n.tabEdit" lazy data-testid="editor-tab">
<text-editor :value="ciFileContent" v-on="$listeners" />
</editor-tab>
<gl-tab
v-if="glFeatures.ciConfigVisualizationTab"
:title="$options.i18n.tabGraph"
lazy
data-testid="visualization-tab"
>
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" />
</gl-tab>
<editor-tab :title="$options.i18n.tabLint" data-testid="lint-tab">
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<ci-lint v-else :ci-config="ciConfigData" />
</editor-tab>
</gl-tabs>
</template>
...@@ -2,26 +2,29 @@ ...@@ -2,26 +2,29 @@
import EditorLite from '~/vue_shared/components/editor_lite.vue'; import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
import { EDITOR_READY_EVENT } from '~/editor/constants'; import { EDITOR_READY_EVENT } from '~/editor/constants';
import getCommitSha from '../graphql/queries/client/commit_sha.graphql';
export default { export default {
components: { components: {
EditorLite, EditorLite,
}, },
inject: ['projectPath', 'projectNamespace'], inject: ['ciConfigPath', 'projectPath', 'projectNamespace'],
inheritAttrs: false, inheritAttrs: false,
props: { data() {
ciConfigPath: { return {
type: String, commitSha: '',
required: true, };
}, },
apollo: {
commitSha: { commitSha: {
type: String, query: getCommitSha,
required: false,
default: null,
}, },
}, },
methods: { methods: {
onEditorReady() { onCiConfigUpdate(content) {
this.$emit('updateCiConfig', content);
},
registerCiSchema() {
const editorInstance = this.$refs.editor.getEditor(); const editorInstance = this.$refs.editor.getEditor();
editorInstance.use(new CiSchemaExtension()); editorInstance.use(new CiSchemaExtension());
...@@ -41,7 +44,8 @@ export default { ...@@ -41,7 +44,8 @@ export default {
ref="editor" ref="editor"
:file-name="ciConfigPath" :file-name="ciConfigPath"
v-bind="$attrs" v-bind="$attrs"
@[$options.readyEvent]="onEditorReady" @[$options.readyEvent]="registerCiSchema"
@input="onCiConfigUpdate"
v-on="$listeners" v-on="$listeners"
/> />
</div> </div>
......
export const CI_CONFIG_STATUS_VALID = 'VALID'; export const CI_CONFIG_STATUS_VALID = 'VALID';
export const CI_CONFIG_STATUS_INVALID = 'INVALID'; export const CI_CONFIG_STATUS_INVALID = 'INVALID';
export const COMMIT_FAILURE = 'COMMIT_FAILURE';
export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
mutation commitCIFileMutation( mutation commitCIFile(
$projectPath: ID! $projectPath: ID!
$branch: String! $branch: String!
$startBranch: String $startBranch: String
......
...@@ -15,14 +15,13 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -15,14 +15,13 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
} }
const { const {
// props // Add to apollo cache as it can be updated by future queries
ciConfigPath,
commitSha, commitSha,
// Add to provide/inject API for static values
ciConfigPath,
defaultBranch, defaultBranch,
newMergeRequestPath,
// `provide/inject` data
lintHelpPagePath, lintHelpPagePath,
newMergeRequestPath,
projectFullPath, projectFullPath,
projectPath, projectPath,
projectNamespace, projectNamespace,
...@@ -35,25 +34,27 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -35,25 +34,27 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
defaultClient: createDefaultClient(resolvers, { typeDefs }), defaultClient: createDefaultClient(resolvers, { typeDefs }),
}); });
apolloProvider.clients.defaultClient.cache.writeData({
data: {
commitSha,
},
});
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
provide: { provide: {
ciConfigPath,
defaultBranch,
lintHelpPagePath, lintHelpPagePath,
newMergeRequestPath,
projectFullPath, projectFullPath,
projectPath, projectPath,
projectNamespace, projectNamespace,
ymlHelpPagePath, ymlHelpPagePath,
}, },
render(h) { render(h) {
return h(PipelineEditorApp, { return h(PipelineEditorApp);
props: {
ciConfigPath,
commitSha,
defaultBranch,
newMergeRequestPath,
},
});
}, },
}); });
}; };
<script>
import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
export default {
components: {
CommitSection,
PipelineEditorHeader,
PipelineEditorTabs,
},
props: {
ciConfigData: {
type: Object,
required: true,
},
ciFileContent: {
type: String,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div>
<pipeline-editor-header
:ci-config-data="ciConfigData"
:is-ci-config-data-loading="isCiConfigDataLoading"
/>
<pipeline-editor-tabs
:ci-config-data="ciConfigData"
:ci-file-content="ciFileContent"
:is-ci-config-data-loading="isCiConfigDataLoading"
v-on="$listeners"
/>
<commit-section :ci-file-content="ciFileContent" v-on="$listeners" />
</div>
</template>
...@@ -5,7 +5,7 @@ import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; ...@@ -5,7 +5,7 @@ import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import { mockCommitMessage, mockDefaultBranch } from '../../mock_data'; import { mockCommitMessage, mockDefaultBranch } from '../../mock_data';
describe('~/pipeline_editor/pipeline_editor_app.vue', () => { describe('Pipeline Editor | Commit Form', () => {
let wrapper; let wrapper;
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => { const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
...@@ -21,8 +21,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { ...@@ -21,8 +21,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
}); });
}; };
const findCommitTextarea = () => wrapper.find(GlFormTextarea); const findCommitTextarea = () => wrapper.findComponent(GlFormTextarea);
const findBranchInput = () => wrapper.find(GlFormInput); const findBranchInput = () => wrapper.findComponent(GlFormInput);
const findNewMrCheckbox = () => wrapper.find('[data-testid="new-mr-checkbox"]'); const findNewMrCheckbox = () => wrapper.find('[data-testid="new-mr-checkbox"]');
const findSubmitBtn = () => wrapper.find('[type="submit"]'); const findSubmitBtn = () => wrapper.find('[type="submit"]');
const findCancelBtn = () => wrapper.find('[type="reset"]'); const findCancelBtn = () => wrapper.find('[type="reset"]');
......
import { mount } from '@vue/test-utils';
import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
import {
mockCiConfigPath,
mockCiYml,
mockCommitSha,
mockCommitNextSha,
mockCommitMessage,
mockDefaultBranch,
mockProjectFullPath,
mockNewMergeRequestPath,
} from '../../mock_data';
import { COMMIT_SUCCESS } from '~/pipeline_editor/constants';
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);
});
});
});
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);
});
});
});
...@@ -3,7 +3,9 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,7 +3,9 @@ import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale'; 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 { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import { mockYmlHelpPagePath, mergeUnwrappedCiConfig } from '../../mock_data'; import { mockYmlHelpPagePath, mergeUnwrappedCiConfig } from '../../mock_data';
...@@ -29,6 +31,11 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => { ...@@ -29,6 +31,11 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => {
const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink'); const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink');
const findValidationMsg = () => wrapper.findByTestId('validationMsg'); const findValidationMsg = () => wrapper.findByTestId('validationMsg');
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('shows the loading state', () => { it('shows the loading state', () => {
createComponent({ loading: true }); createComponent({ loading: true });
......
import { nextTick } from 'vue';
import { shallowMount, mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import { mockLintResponse, mockCiYml } from '../mock_data';
describe('Pipeline editor tabs component', () => {
let wrapper;
const MockTextEditor = {
template: '<div />',
};
const mockProvide = {
glFeatures: {
ciConfigVisualizationTab: 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 findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
const findCiLint = () => wrapper.findComponent(CiLint);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPipelineGraph = () => wrapper.findComponent(PipelineGraph);
const findTextEditor = () => wrapper.findComponent(MockTextEditor);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('tabs', () => {
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);
});
});
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { EDITOR_READY_EVENT } from '~/editor/constants';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
import { import {
mockCiConfigPath, mockCiConfigPath,
mockCiYml, mockCiYml,
...@@ -10,7 +8,10 @@ import { ...@@ -10,7 +8,10 @@ import {
mockProjectNamespace, mockProjectNamespace,
} from '../mock_data'; } from '../mock_data';
describe('~/pipeline_editor/components/text_editor.vue', () => { import { EDITOR_READY_EVENT } from '~/editor/constants';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
describe('Pipeline Editor | Text editor component', () => {
let wrapper; let wrapper;
let editorReadyListener; let editorReadyListener;
...@@ -36,14 +37,17 @@ describe('~/pipeline_editor/components/text_editor.vue', () => { ...@@ -36,14 +37,17 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
provide: { provide: {
projectPath: mockProjectPath, projectPath: mockProjectPath,
projectNamespace: mockProjectNamespace, projectNamespace: mockProjectNamespace,
},
propsData: {
ciConfigPath: mockCiConfigPath, ciConfigPath: mockCiConfigPath,
commitSha: mockCommitSha,
}, },
attrs: { attrs: {
value: mockCiYml, value: mockCiYml,
}, },
// Simulate graphQL client query result
data() {
return {
commitSha: mockCommitSha,
};
},
listeners: { listeners: {
[EDITOR_READY_EVENT]: editorReadyListener, [EDITOR_READY_EVENT]: editorReadyListener,
}, },
...@@ -54,41 +58,64 @@ describe('~/pipeline_editor/components/text_editor.vue', () => { ...@@ -54,41 +58,64 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
}); });
}; };
const findEditor = () => wrapper.find(MockEditorLite); const findEditor = () => wrapper.findComponent(MockEditorLite);
beforeEach(() => { afterEach(() => {
editorReadyListener = jest.fn(); wrapper.destroy();
mockUse = jest.fn(); wrapper = null;
mockRegisterCiSchema = jest.fn();
createComponent(); mockUse.mockClear();
mockRegisterCiSchema.mockClear();
}); });
it('contains an editor', () => { describe('template', () => {
expect(findEditor().exists()).toBe(true); beforeEach(() => {
}); editorReadyListener = jest.fn();
mockUse = jest.fn();
mockRegisterCiSchema = jest.fn();
it('editor contains the value provided', () => { createComponent();
expect(findEditor().props('value')).toBe(mockCiYml); });
});
it('editor is configured for the CI config path', () => { it('contains an editor', () => {
expect(findEditor().props('fileName')).toBe(mockCiConfigPath); expect(findEditor().exists()).toBe(true);
}); });
it('editor is configured with syntax highligting', async () => { it('editor contains the value provided', () => {
expect(mockUse).toHaveBeenCalledTimes(1); expect(findEditor().props('value')).toBe(mockCiYml);
expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1); });
expect(mockRegisterCiSchema).toHaveBeenCalledWith({
projectNamespace: mockProjectNamespace, it('editor is configured for the CI config path', () => {
projectPath: mockProjectPath, expect(findEditor().props('fileName')).toBe(mockCiConfigPath);
ref: mockCommitSha, });
it('bubbles up events', () => {
findEditor().vm.$emit(EDITOR_READY_EVENT);
expect(editorReadyListener).toHaveBeenCalled();
}); });
}); });
it('bubbles up events', () => { describe('register CI schema', () => {
findEditor().vm.$emit(EDITOR_READY_EVENT); 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();
expect(editorReadyListener).toHaveBeenCalled(); 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,
});
});
}); });
}); });
import { shallowMount } from '@vue/test-utils';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
import { mockLintResponse, mockCiYml } from './mock_data';
describe('Pipeline editor home wrapper', () => {
let wrapper;
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(PipelineEditorHome, {
propsData: {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
isCiConfigDataLoading: false,
...props,
},
});
};
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorTabs);
const findPipelineEditorTabs = () => wrapper.findComponent(CommitSection);
const findCommitSection = () => wrapper.findComponent(PipelineEditorHeader);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('renders', () => {
beforeEach(() => {
createComponent();
});
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', () => {
expect(findCommitSection().exists()).toBe(true);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment